Skip to content

Commit 127659d

Browse files
Update app.py
1 parent 8ac47f0 commit 127659d

1 file changed

Lines changed: 126 additions & 11 deletions

File tree

app.py

Lines changed: 126 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,10 +1226,111 @@ def preflight_next_step_label(action: str) -> str:
12261226
"MANUAL_REVIEW": "Escalate for manual review",
12271227
"DO_NOT_SEND": "Do not send",
12281228
"REVIEW_REQUEST_TEMPLATE": "Correct the approved payout instructions",
1229+
"REVERIFY_DESTINATION": "Re-verify the destination route",
1230+
"CONFIRM_DESTINATION": "Confirm the destination before approval",
12291231
}
12301232
return labels.get(str(action or "").upper(), str(action or "").replace("_", " ").title() or "Review required")
12311233

12321234

1235+
POLICY_PROFILE_ALIASES = {
1236+
"": "standard",
1237+
"default": "standard",
1238+
"standard": "standard",
1239+
"payout_strict": "payout_strict",
1240+
"strict": "payout_strict",
1241+
"deposit_review": "deposit_review",
1242+
"deposit": "deposit_review",
1243+
"treasury_review": "treasury_review",
1244+
"treasury": "treasury_review",
1245+
}
1246+
1247+
1248+
def normalize_policy_profile(value: Any) -> str:
1249+
key = str(value or "").strip().lower()
1250+
return POLICY_PROFILE_ALIASES.get(key, "standard")
1251+
1252+
1253+
def policy_profile_label(profile: str) -> str:
1254+
labels = {
1255+
"standard": "Standard",
1256+
"payout_strict": "Payout Strict",
1257+
"deposit_review": "Deposit Review",
1258+
"treasury_review": "Treasury Review",
1259+
}
1260+
return labels.get(normalize_policy_profile(profile), "Standard")
1261+
1262+
1263+
def derive_preflight_policy_override(destination_class: str, policy_profile: str) -> Optional[Dict[str, str]]:
1264+
profile = normalize_policy_profile(policy_profile)
1265+
destination_class = str(destination_class or "").lower()
1266+
if profile == "standard" or destination_class in {"", "personal_wallet", "unavailable"}:
1267+
return None
1268+
1269+
reason_map = {
1270+
"contract_or_app": "DESTINATION_IS_CONTRACT_OR_APP",
1271+
"bridge_router": "DESTINATION_IS_BRIDGE_ROUTER",
1272+
"exchange_like_deposit": "DESTINATION_REQUIRES_MEMO_OR_VENUE_CHECK",
1273+
"unknown": "DESTINATION_NOT_CLASSIFIED",
1274+
"not_found": "DESTINATION_NOT_CLASSIFIED",
1275+
}
1276+
summary_map = {
1277+
"contract_or_app": "The destination looks like a contract or app rather than a simple wallet route.",
1278+
"bridge_router": "The destination looks like bridge or router infrastructure rather than a simple wallet route.",
1279+
"exchange_like_deposit": "The destination looks like an exchange-style or venue-dependent deposit route.",
1280+
"unknown": "The destination could not be classified confidently.",
1281+
"not_found": "The destination could not be classified confidently.",
1282+
}
1283+
reason_code = reason_map.get(destination_class)
1284+
summary = summary_map.get(destination_class)
1285+
if not reason_code or not summary:
1286+
return None
1287+
1288+
if profile == "payout_strict":
1289+
return {
1290+
"status": "blocked",
1291+
"verdict": "BLOCK",
1292+
"reason_code": reason_code,
1293+
"next_action": "BLOCK_AND_REVERIFY",
1294+
"confidence": "High",
1295+
"summary": summary,
1296+
"why": "Payout Strict only approves clear personal-wallet style destinations. Ambiguous, infrastructure, or venue-dependent routes should be stopped and re-verified before funds move.",
1297+
}
1298+
1299+
if profile == "deposit_review":
1300+
return {
1301+
"status": "review_required",
1302+
"verdict": "REVERIFY",
1303+
"reason_code": reason_code,
1304+
"next_action": "REVERIFY_DESTINATION" if destination_class != "exchange_like_deposit" else "RECHECK_MEMO_OR_TAG",
1305+
"confidence": "Medium",
1306+
"summary": summary,
1307+
"why": "Deposit Review keeps ambiguous or venue-dependent routes in a confirmation path instead of treating them like normal wallet payouts.",
1308+
}
1309+
1310+
if profile == "treasury_review":
1311+
if destination_class in {"exchange_like_deposit", "bridge_router"}:
1312+
return {
1313+
"status": "blocked",
1314+
"verdict": "BLOCK",
1315+
"reason_code": reason_code,
1316+
"next_action": "BLOCK_AND_REVERIFY",
1317+
"confidence": "High",
1318+
"summary": summary,
1319+
"why": "Treasury Review blocks routes that look like infrastructure or venue-dependent deposit paths until the destination is re-confirmed through an approved internal process.",
1320+
}
1321+
return {
1322+
"status": "review_required",
1323+
"verdict": "REVERIFY",
1324+
"reason_code": reason_code,
1325+
"next_action": "REVERIFY_DESTINATION",
1326+
"confidence": "Medium",
1327+
"summary": summary,
1328+
"why": "Treasury Review keeps ambiguous destinations in a cautious review path before release.",
1329+
}
1330+
1331+
return None
1332+
1333+
12331334
def derive_preflight_outcome(
12341335
*,
12351336
checks: Dict[str, bool],
@@ -1243,6 +1344,7 @@ def derive_preflight_outcome(
12431344
provided_valid: bool,
12441345
provided_destination: Dict[str, Any],
12451346
risk_flags: List[str],
1347+
policy_profile: str = "standard",
12461348
) -> Dict[str, str]:
12471349
mismatch_reason = None
12481350
for code, is_failed in [
@@ -1319,6 +1421,22 @@ def derive_preflight_outcome(
13191421
}
13201422

13211423
destination_class = provided_destination.get("classification")
1424+
1425+
if provided_destination.get("source") == "unavailable":
1426+
return {
1427+
"status": "unavailable",
1428+
"verdict": "UNAVAILABLE",
1429+
"reason_code": "DESTINATION_LOOKUP_UNAVAILABLE",
1430+
"next_action": "CHECK_BACKEND",
1431+
"confidence": "Limited",
1432+
"summary": "Live destination lookup is currently unavailable.",
1433+
"why": "The service could compare the transfer fields, but the live destination classification step did not complete. Retry or review manually.",
1434+
}
1435+
1436+
profile_override = derive_preflight_policy_override(destination_class, policy_profile)
1437+
if profile_override:
1438+
return profile_override
1439+
13221440
if destination_class == "bridge_router":
13231441
return {
13241442
"status": "review_required",
@@ -1352,17 +1470,6 @@ def derive_preflight_outcome(
13521470
"why": "Deposit-style destinations often depend on the exact venue, network, asset, and memo or tag. Re-verify all of them before sending.",
13531471
}
13541472

1355-
if provided_destination.get("source") == "unavailable":
1356-
return {
1357-
"status": "unavailable",
1358-
"verdict": "UNAVAILABLE",
1359-
"reason_code": "DESTINATION_LOOKUP_UNAVAILABLE",
1360-
"next_action": "CHECK_BACKEND",
1361-
"confidence": "Limited",
1362-
"summary": "Live destination lookup is currently unavailable.",
1363-
"why": "The service could compare the transfer fields, but the live destination classification step did not complete. Retry or review manually.",
1364-
}
1365-
13661473
if destination_class in {"unknown", "not_found"}:
13671474
return {
13681475
"status": "review_required",
@@ -3805,6 +3912,8 @@ def preflight_check():
38053912
payload = request.get_json(silent=True) or {}
38063913
expected = payload.get("expected") or {}
38073914
provided = payload.get("provided") or {}
3915+
context = payload.get("context") or {}
3916+
policy_profile = normalize_policy_profile(payload.get("policy_profile") or context.get("policy_profile"))
38083917

38093918
expected_chain = normalize_chain(expected.get("network") or expected.get("chain"))
38103919
provided_chain = normalize_chain(provided.get("network") or provided.get("chain"))
@@ -3924,6 +4033,7 @@ def preflight_check():
39244033
provided_valid=provided_valid,
39254034
provided_destination=provided_destination,
39264035
risk_flags=risk_flags,
4036+
policy_profile=policy_profile,
39274037
)
39284038

39294039
checked_at = utc_now_iso()
@@ -3940,6 +4050,7 @@ def preflight_check():
39404050
"verdict": outcome["verdict"],
39414051
"next_action": outcome["next_action"],
39424052
"risk_flags": sorted(set(risk_flags)),
4053+
"policy_profile": policy_profile,
39434054
},
39444055
)
39454056
response_payload = {
@@ -3949,6 +4060,8 @@ def preflight_check():
39494060
"trace_id": current_request_id(),
39504061
"checked_at": checked_at,
39514062
"chain": provided_chain,
4063+
"policy_profile": policy_profile,
4064+
"policy_profile_label": policy_profile_label(policy_profile),
39524065
"status": outcome["status"],
39534066
"verdict": outcome["verdict"],
39544067
"reason_code": outcome["reason_code"],
@@ -3969,6 +4082,7 @@ def preflight_check():
39694082
"reason_code": outcome["reason_code"],
39704083
"next_action": outcome["next_action"],
39714084
"next_action_label": preflight_next_step_label(outcome["next_action"]),
4085+
"policy_profile": policy_profile,
39724086
},
39734087
"expected": {
39744088
"network": expected_chain,
@@ -4024,6 +4138,7 @@ def preflight_check():
40244138
metadata={
40254139
"next_action": outcome["next_action"],
40264140
"confidence": outcome["confidence"],
4141+
"policy_profile": policy_profile,
40274142
},
40284143
)
40294144
g.limit_state = build_limit_state(access, "this_month")

0 commit comments

Comments
 (0)