Skip to content

Follow-ups to the sponsor governance change - #285

Merged
PetrefiedThunder merged 3 commits into
mainfrom
claude/sponsor-governance-followups
Nov 2, 2026
Merged

PetrefiedThunder merged 3 commits into
mainfrom
claude/sponsor-governance-followups

Conversation

@PetrefiedThunder

@PetrefiedThunder PetrefiedThunder commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Three small follow-ups from review of #282. No authorization behavior changes.

  1. gateway/docs/api-key-rotation.md: an ancestor using a database key ignores create_new_key (no replacement key, new_key is null); an authorized JWT ancestor must set create_new_key to false, because the default true is refused with 403 jwt_cannot_mint_api_key before revocation.
  2. emergency_revoke docstring in gateway/app/routers/api_keys.py says the same. The route's OpenAPI description is set explicitly, so docs/openapi.json is unchanged.
  3. wallet_is_or_descends_from (the permit-creation rule) again reads the chain one wallet at a time and returns at the first parent equal to the ancestor, as it did before Let a sponsor govern its descendants permits and keys #282. A direct child is one session.get instead of two, and an ancestor near the child no longer loads up to 32 wallets. The answers are the same as before (the differential fuzz in the Let a sponsor govern its descendants permits and keys #282 review found 0 mismatches over 1,815,000 cases). wallet_strictly_governs and walk_wallet_ancestry are not touched, so the strict rule still needs the complete chain and keeps its depth cap.

Not changed on purpose: who may act on a descendant, the depth cap on the strict walk, tenant checks, actor records.

Related issues

Closes #284

Area and license

  • I identified the repository area changed by this PR (gateway).
  • I understand that spec/, verifier/, sdk/python/, sdk/typescript/, integrations/, and examples/ are Apache-2.0.
  • I understand that gateway/ contributions are under FSL-1.1-ALv2 (Fair Source).
  • This PR does not include proprietary hosted-service code or secrets.

Spec changes

  • This PR does not change normative spec behavior.
  • If it changes normative spec behavior, I opened or linked an RFC issue.

Validation

Two new tests in gateway/tests/test_sponsor_downward_governance.py use the real database-backed world fixture (sponsor S, agent A, grandchild G, a sibling) and a thin wrapper that counts get calls on a real session; no invented wallet data:

  • test_permit_creation_rule_stops_at_the_first_match: direct child (A under S, G under A) is True after 1 lookup; ancestor at hop 2 is True after 2 lookups; no match walks to the root and answers False (2 lookups); equal ids need 0 lookups; the strict rule needs the whole chain (3 lookups for S over G).
  • test_permit_creation_rule_ignores_a_malformed_link_above_the_match: with a cycle above the match (S points back at A) and with a missing row above the match (S points at a wallet that does not exist, made by tampering the real row with foreign keys suspended), the creation rule is still True after the first lookup while wallet_strictly_governs is False for the same chain.

Mutation check: with the full-chain walk put back into wallet_is_or_descends_from, both new tests fail ((True, 2) == (True, 1) and (True, 3) == (True, 1)) and the other 19 sponsor tests pass. The fix was restored and all 21 pass.

Commands (serial, python -m pytest, from gateway/ with the pinned requirements and the local SDK verify extra):

  • tests/test_sponsor_downward_governance.py tests/test_permits.py tests/test_permit_break_it.py tests/test_owner_enforcement.py tests/test_api_keys.py tests/test_rotate_api_keys.py tests/test_route_auth_inventory.py plus the docs tests test_docs_reference_integrity.py test_docs_links.py test_check_doc_references.py test_operator_doc_contracts.py test_onboarding_doc_contracts.py test_testing_tiers_doc.py test_proof_matrix_doc.py: 203 passed. No existing test was edited.
  • Production-like leg (TRUST_MODE_ENABLED=true ALLOW_LEGACY_UNPERMITTED_MCP=false ENABLE_PROOF_SURFACES=false, -m production_trust): 12 passed.
  • ruff check . and ruff format --check . (pinned version): clean. mypy==2.4.0 mypy app/: no issues in 213 files. python scripts/export_openapi.py --check: matches.

Risk and compatibility

  • No migration, no schema or OpenAPI change, no new route.
  • The only code change is lookup count in the permit-creation walk, with the same answers; the strict authorization path is byte-for-byte untouched.
  • The incremental loop in wallet_is_or_descends_from duplicates a few lines of the shared walk on purpose, so the strict rule is not touched.

Contributor checklist

  • I added or updated tests for behavior changes, or explained why tests are not applicable.
  • I updated relevant documentation and examples.
  • Every commit in this PR is signed off under the Developer Certificate of Origin.
  • I agree to submit this contribution under the license applicable to the files I changed.

https://claude.ai/code/session_01GyhDYK2VFo5T1XxHSxvksN


Generated by Claude Code


Note

Low Risk
Lookup optimization preserves prior permit-creation semantics; changes are docs plus tests with strict governance paths untouched.

Overview
Follow-up to sponsor governance review: no authorization rule changes, only clearer docs, a cheaper permit-creation hierarchy walk, and tests.

wallet_is_or_descends_from again walks parent links one wallet at a time and returns as soon as the ancestor matches, instead of loading the full chain via walk_wallet_ancestry. Same boolean answers as before; direct-child checks use one DB get instead of walking up to 32 rows. wallet_strictly_governs is unchanged and still requires a complete, sound chain.

Emergency revoke behavior is documented in the runbook and emergency_revoke docstring: strict ancestors acting on a descendant never get a replacement key (create_new_key ignored for DB keys; JWT with default create_new_key: true gets 403 jwt_cannot_mint_api_key before revocation).

Two integration tests assert lookup counts and that malformed links above a match do not alter the permit-creation rule while the strict rule still fails closed.

Reviewed by Cursor Bugbot for commit 551652a. Bugbot is set up for automated code reviews on this repo. Configure here.

- Restore the incremental first-match loop in wallet_is_or_descends_from so
  the permit-creation rule stops at the first match again (a direct child is
  one lookup). Answers are unchanged; wallet_strictly_governs and the shared
  walk it uses are untouched.
- Say in api-key-rotation.md and the emergency_revoke docstring that an
  ancestor's create_new_key is ignored and that a JWT ancestor must send
  create_new_key false.
- Add a test that counts session.get calls and pins "stops at the first
  match".

Closes #284

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GyhDYK2VFo5T1XxHSxvksN
Signed-off-by: Claude <noreply@anthropic.com>
@vercel

vercel Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
agent-middleware-web Ready Ready Preview Nov 2, 2026 10:14am UTC

Request Review

@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

Run configuration
Commits

Reviewing files that changed from the base of the PR and between a78c9ce and 551652a.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between b85b77a and a78c9ce.

Files selected for processing: 1
  • gateway/tests/test_sponsor_downward_governance.py

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review.

Walkthrough

The wallet ancestry check now walks parent wallets incrementally and stops at the first match. Tests check lookup counts and results for matches, malformed chains, no match, and equal wallet IDs. The emergency-revoke docstring and API-key rotation guide clarify replacement-key behavior for database-key and JWT ancestors.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: Minimal · previous review a78c9

The ancestry walk stops at the first match, and the revoke guidance clarifies replacement-key behavior. No merge-blocking issue was identified; run normal checks.

Security architecture risk: Minimal · up to b85b7

The ancestry change reduces database reads without changing permit authorization results for a stable wallet hierarchy. Strict ancestor governance and key-revocation restrictions remain unchanged. No introduced or materially worsened security issue was identified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The changed predicate does not expand the set of descendant subjects reachable by an authenticated non-bootstrap issuer for a stable hierarchy. Existing wallet-access checks, equal-identifier rejection for action permits, and the ancestry depth bound continue to constrain that authority.

Trust Boundaries and Controls

  • observed — The affected authentication path retains wallet-bound identity: database-key authentication constructs a wallet-scoped context, JWT authentication checks the originating key's wallet against the token subject, and require_wallet_access enforces exact wallet access except for the existing bootstrap-admin exemption.

Resilience and Maintainability Implications

  • observed — Permit issuance retains its existing state-transition controls: idempotency admission is scoped to issuer wallet, endpoint, and key; completed requests replay their stored response; expected creation rejection abandons admission; and permit persistence retains validation, signing, and commit. The PR changes neither these transitions nor their recovery handling.
Pre-merge checks: Passed 5
Check name Status Explanation
Title check Passed The title accurately identifies the pull request as follow-up work for the sponsor governance change. It is concise and related to the documented and code changes.
Description check Passed The description directly explains the documentation updates, incremental wallet hierarchy walk, tests, validation, and unchanged authorization scope.
Linked Issues check Passed Directly linked issue #284 has three coding requirements. The documentation update states that a database-key ancestor ignores create_new_key and returns no replacement key, and that a JWT ancestor …
Out of Scope Changes check Passed The changed files are limited to the issue-linked API-key documentation, the emergency_revoke docstring, the permit-creation hierarchy walk, and focused sponsor-governance tests. These changes suppo…
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat…
Full details: Linked Issues check

Explanation

Directly linked issue #284 has three coding requirements. The documentation update states that a database-key ancestor ignores create_new_key and returns no replacement key, and that a JWT ancestor must set it to false because the default returns 403 jwt_cannot_mint_api_key before revocation. The emergency_revoke docstring states the strict-ancestor replacement-key rule. wallet_is_or_descends_from uses an incremental parent walk and stops at the first match. The added tests cover lookup counts, malformed links above a match, and the unchanged strict rule. The reported sponsor and permit tests pass unchanged, and the required documentation tests pass.

Full details: Out of Scope Changes check

Explanation

The changed files are limited to the issue-linked API-key documentation, the emergency_revoke docstring, the permit-creation hierarchy walk, and focused sponsor-governance tests. These changes support issue #284. The strict walk, depth cap, descendant authority, tenant checks, actor records, schema, migration, and OpenAPI output remain unchanged according to the reviewed PR evidence. No unrelated change is demonstrated.

Full details: Docstring Coverage

Explanation

Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 2 functions across 3 files.

Finishing Touches
Generate docstrings
  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@PetrefiedThunder
PetrefiedThunder marked this pull request as ready for review November 2, 2026 09:58
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-02T09:59:13.403032Z b85b77a Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b85b77adc2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Replace the invented parent map and fake wallet objects with the real
database-backed world fixture and a wrapper that counts get calls on a real
session. The malformed chains are made by tampering real rows.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GyhDYK2VFo5T1XxHSxvksN
Signed-off-by: Claude <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Fix comments: Fix CodeRabbit comments on this PR

@PetrefiedThunder

Copy link
Copy Markdown
Collaborator Author

REVIEW: PASS hermes a78c9ce

Scope

Claude follow-ups to merged #282 (sponsor governs descendants' permits and keys). Restores the incremental first-match walk in wallet_is_or_descends_from, documents ancestor create_new_key / JWT mint refusal on emergency revoke, and adds first-match lookup-count tests on real wallet rows.

Authorship

  • PR author: PetrefiedThunder
  • Commits: Claude (noreply@anthropic.com) only — b85b77a, a78c9ce
  • No hermes author/committer — reviewed

Behind main

  • Behind by 1 commit; ahead by 2; MERGEABLE (no conflicts). Review stands on current head; Pi should merge main afterwards for a delta.

Correctness / security

  • wallet_is_or_descends_from (gateway/app/services/wallet_hierarchy.py:57-87): early-exit first-match loop; cycle/seen fail-closed; missing row / no parent → False; depth cap → False. Equal ids still True (permit-creation self rule). Hot path no longer loads the whole chain via walk_wallet_ancestry.
  • wallet_strictly_governs / walk_wallet_ancestry byte-untouched — still requires a complete sound chain. Key/permit governance call sites (wallet_resolution.require_governing_wallet_access, permits.py, agent_money) still use the strict helper.
  • Sponsor invariants from Let a sponsor govern its descendants permits and keys #282 still hold under this change:
    • sibling / upward / cross-tenant: still False under strict rule (existing suite)
    • create/rotate limited to exact wallet: unchanged
    • cycles and over-depth fail closed under strict rule; permit-creation first-match still stops at the match (intentional; tests pin it)
    • foreign / missing wallets: same False / not-found paths as before
  • Emergency-revoke docs (api_keys.py:485-489, api-key-rotation.md): match behavior — ancestor DB key's create_new_key is ignored because bounding_key_id is not among the descendant's keys (api_key_service.py:1212-1221); JWT + create_new_key true still 403 jwt_cannot_mint_api_key before revoke.
  • No migrations.

Tests / CI

  • Local: tests/test_sponsor_downward_governance.py → 21 passed (includes new first-match + malformed-above-match cases).
  • CI on a78c9cee…: required checks green (CI result, Python 3.11–3.14, Postgres proofs/concurrency, lint, CodeQL, gitleaks). CodeRabbit still running (non-blocking).

Verdict

PASS. No blockers. Behind main by 1 — delta after Pi merges main.

…ance-followups

Signed-off-by: Christopher <131207452+PetrefiedThunder@users.noreply.github.com>

Copy link
Copy Markdown
Collaborator Author

Independent check of a78c9ce by the coordinating session (not a QA verdict; hermes already passed this head).

  • Behavior of the restored loop: compared with the pre-Let a sponsor govern its descendants permits and keys #282 AgentMoney.is_wallet_or_descendant loop over 300,000 random parent maps (cycles, missing rows, None and empty parents, five depth caps, None and empty ids): 0 differences in the answer and the same number of session.get calls in every case. wallet_strictly_governs and walk_wallet_ancestry are untouched in the diff.
  • Mutation: putting the full-chain walk back into wallet_is_or_descends_from fails exactly test_permit_creation_rule_stops_at_the_first_match and test_permit_creation_rule_ignores_a_malformed_link_above_the_match; the other 19 tests in the file stay green. Worktree restored afterwards.
  • Tests: 125 passed on the head (sponsor governance, permit creation and break-it tests, owner enforcement, route auth inventory, and the seven docs tests). No production change besides the loop and one docstring.
  • Threads: I resolved both open threads on behalf of the author, who could not from their session. The Codex P1 (real data only) was fixed in a78c9ce. The CodeRabbit trivial one (a route-level POST /v1/permits case with a malformed link above the match) was declined by the author as non-blocking and CodeRabbit agreed; it is recorded as an optional test follow-up and is not a defect.

Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

REVIEW: PASS hermes 551652a

Delta review after Pi merged main (09fae4d) into PASSed a78c9ce.

  • Merge parents: a78c9ce + 09fae4d
  • git show --remerge-diff empty
  • git merge-tree --write-tree a78c9cee 09fae4db tree == head tree (10e36018916f0ce6becb00317acc615b426a550f)
  • PR patch vs main unchanged (sha256 match for two-dot and three-dot diffs; still 4 files, +93/−6)
  • CI on head: all required checks green (CI result, Python 3.11–3.14, lint, CodeQL, gitleaks, Postgres concurrency/proofs, Trust release gate, Gateway image import smoke)
  • Local pytest -k sponsor: 58 passed

No push. Nothing else changed in the PR patch.

Copy link
Copy Markdown
Collaborator Author

READY FOR QA 551652a

This head is the maintainer's merger merging main into the branch (merge commit 551652a, parents a78c9ce and 09fae4d). I pushed nothing. REVIEW: PASS hermes already names a78c9ce; the delta since then is the merge only.

Files the merge of main touched (main's own change, #207):

  • gateway/docs/failure-lab.md
  • gateway/scripts/failure_lab.py
  • gateway/tests/test_failure_lab.py

The PR's own 4 files, none of which the merge touched (no overlap):

  • gateway/app/routers/api_keys.py (docstring only)
  • gateway/app/services/wallet_hierarchy.py (first-match loop in wallet_is_or_descends_from)
  • gateway/docs/api-key-rotation.md
  • gateway/tests/test_sponsor_downward_governance.py

Evidence:

  • CI result, License result, gitleaks: success on this head. PR state: mergeable, clean.
  • CodeRabbit skipped this head (no reviewable changes in the selected files); its review of a78c9ce had no actionable comments. One trivial CodeRabbit suggestion (a route-level POST /v1/permits case) was answered on its thread and not applied.

Generated by Claude Code

@PetrefiedThunder
PetrefiedThunder merged commit aae40de into main Nov 2, 2026
26 checks passed
@PetrefiedThunder
PetrefiedThunder deleted the claude/sponsor-governance-followups branch November 2, 2026 10:25

This branch was successfully deployed

1 active deployment
Preview — 551652aa Deployed Nov 2, 2026 by vercel[bot]
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.

Follow-ups to the sponsor governance change: JWT ancestor doc note, emergency revoke docstring, first-match walk

2 participants