Skip to content

fix: bootstrap fixable wallet_not_trusted denials into identity_verification_required#1

Merged
vvillait88 merged 8 commits intomainfrom
bootstrap-fixable-wallet-denials
Apr 30, 2026
Merged

fix: bootstrap fixable wallet_not_trusted denials into identity_verification_required#1
vvillait88 merged 8 commits intomainfrom
bootstrap-fixable-wallet-denials

Conversation

@vvillait88
Copy link
Copy Markdown
Contributor

Summary

  • Gate now auto-mints a verification session when /v1/assess denies on a fixable compliance reason (kyc_required, kyc_pending, kyc_failed, jurisdiction_required), giving the agent the same poll-and-retry UX as missing_identity. Unfixable reasons (sanctions / age / jurisdiction_restricted) keep the bare wallet_not_trusted denial.
  • Canonical wallet_not_trusted agent_instructions action changes from claim_wallet_or_switch_identitycontact_support to match the new (unfixable-only) semantics.
  • Applied across all 6 framework adapters (fastapi, flask, django, aiohttp, sanic, middleware/ASGI) plus the shared response marshaller. New session-bootstrap test coverage (fixable → identity_verification_required, unfixable → bare wallet_not_trusted) for every adapter.
  • OpenAPI denial-code description documents the re-route + contact_support action.
  • Coverage 95.29% (was 94.94%).

Test plan

  • uv run pytest tests/ — 554 passed, 95.29% coverage
  • uv run ruff check . clean
  • uv run ty check agentscore_commerce/ clean

🤖 Generated with Claude Code

vvillait88 and others added 8 commits April 29, 2026 18:47
…ication_required

Fixable compliance reasons (kyc_required, kyc_pending, kyc_failed,
jurisdiction_required without explicit restriction) now get the same UX as
missing_identity: the gate auto-mints a verification session, the agent polls
until status=verified, gets a fresh opc_..., and retries with X-Operator-Token.
Unfixable reasons (sanctions_flagged, age_insufficient, jurisdiction_restricted)
keep the bare wallet_not_trusted denial — re-verification won't change the
outcome, so the canonical agent_instructions action is now contact_support.

Applied to all 6 framework adapters (fastapi, flask, django, aiohttp, sanic,
middleware/ASGI) plus the shared response marshaller. Adds session-bootstrap
test coverage (fixable → identity_verification_required, unfixable → bare
wallet_not_trusted) for every adapter. OpenAPI denial-code description
documents the re-route and the contact_support action.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The API only emits jurisdiction_restricted AFTER KYC is verified — meaning the
user's KYC'd country is in the merchant's blocked list (or absent from the
allowed list). Re-doing KYC won't change the country, so it's permanent. Same
shape as sanctions_flagged / age_insufficient — should surface contact_support,
not bootstrap a doomed verification session.

Also flips empty/None reasons to return False (don't bootstrap on unknown deny —
default to bare denial). Updates the canonical wallet_not_trusted instructions
copy and all six adapter comments to spell out the API-side rationale.

Tests updated: jurisdiction_restricted now in the unfixable bucket alongside
sanctions/age, empty reasons returns False.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e architecture

The gate now re-routes fixable reasons (kyc_required/pending/failed) upstream,
so by the time wallet_not_trusted reaches the merchant's on_denied, reasons
should be unfixable. The is_fixable_denial branch in the example becomes a
defensive fallback (only fires if the gate's /v1/sessions mint blipped).
Also clarify jurisdiction_restricted is in the unfixable bucket alongside
sanctions/age — the API only emits it after KYC is verified.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tests used invented reason strings like \`not_kyc\`, \`score_too_low\`,
\`sanctions_check_pending\` that don't exist in the API surface (the real
codes are \`kyc_required\`, \`kyc_pending\`, \`kyc_failed\`, \`sanctions_flagged\`,
\`age_insufficient\`, \`jurisdiction_restricted\`). Tests pass either way (the
gate passes reasons through verbatim), but the fake strings propagate
misinformation. Replace with real codes for documentation accuracy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hardcoding `__version__ = "1.0.0"` in `__init__.py` while pyproject.toml is at
"1.0.1" creates a two-spot version drift. Read from `importlib.metadata` so
pyproject.toml is the single source of truth (matches python-sdk's pattern).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…sitive

CodeQL flagged \`assert "https://my.merchant" in section\` as
\`py/incomplete-url-substring-sanitization\` (high severity). The pattern
matters when checking whether a user-supplied URL falls inside an allowlist
substring; here it's a test assertion verifying the rendered llms.txt section
contains the test fixture's app_url. Same effect with a more specific
substring (\`agentscore-pay pay POST https://my.merchant\`) — CodeQL no
longer matches the dangerous pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Picks up the just-published 2.0.1 (invalid_credential DenialCode + assess
refresh wire-format parity with node-sdk).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vvillait88 vvillait88 merged commit ba575b6 into main Apr 30, 2026
7 checks passed
@vvillait88 vvillait88 deleted the bootstrap-fixable-wallet-denials branch April 30, 2026 04:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant