Repository navigation
feat: approval intent receipts, role-route enforcement and operator docs (#220) - #378
Merged
github-actions[bot] merged 4 commits intoOct 18, 2026
Conversation
…fecycle (TRELLIS-STELLAR#220) The first slice kept only receiptHash/receiptSignature, which proves a receipt was signed but cannot re-derive what was signed. Store the exact receipt document (jsonb) so verification can be re-run after the fact; verification re-canonicalizes before hashing, so jsonb's key order is irrelevant. Additive, idempotent migration. Catalogue the approval lifecycle as sensitive actions under a new APPROVAL scope: maintainer.action.proposed (reason required, state captured), .cancelled (reason required) and .executed (state captured), so the intent flow leaves the same hash-chained trail as every other sensitive surface.
…#220) ApprovalIntentService: propose (canonical fingerprint, HMAC at rest, identical-commitment dedupe), single-use execute (atomic conditional claim, executor re-check, expiry settled before the claim, signed receipt on completion AND failure), cancel, single and bulk expiry, and post-hoc receipt verification. Fail-closed proposal evidence: an intent whose evidence cannot be written is cancelled, never left executable. Admin REST surface at /admin/intents (propose, list, get, cancel, expire, receipt verify), behind the same JWT + role + 2FA guard stack as AdminRoleController. Execution is deliberately not exposed here: an intent is consumed by the route it authorizes, so a caller cannot attach an arbitrary operation to a signed intent. 22 service tests (fake repository observing claim semantics), 5 wiring tests, plus the existing foundation suites.
…TRELLIS-STELLAR#220) With MAINTAINER_APPROVAL_INTENTS_ENFORCED on (read per request), PATCH /admin/users/:id/role requires approvalIntentId in the body and DELETE requires it as a query parameter; the intent must commit to exactly this action (access.role.assigned / .revoked), this user and this role, or the route answers 409. Execution runs through ApprovalIntentService.execute, so the change consumes the intent once and stores a signed receipt. Flag off (default): behaviour is identical to the previous API, proven by the untouched pre-existing specs; a supplied approvalIntentId is ignored while enforcement is off. 8 new controller tests cover both flag states, every binding mismatch and the double-claim conflict passthrough.
Endpoints, the enforcement flag, the lifecycle diagram, an edge-case table (dedupe, late execution, races, approver rules, tampered receipts), failure modes (evidence outages fail closed, missing signing key), and rollback paths for both the flag and a code revert.
|
@OkeyAmy Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
Code Review & Auto-Merge — @dorismaduegbunam 🚀Thank you @OkeyAmy for your contribution!
Verified code changes and repository requirements. Merging pull request. |
github-actions
Bot
merged commit Oct 18, 2026
2b8a3ab
into
TRELLIS-STELLAR:main
1 of 3 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #220
Why this PR exists
#377 was squash-merged by the automation while its squash snapshot still held only the foundation slice — the merge cut happened before the full-scope commits landed on the branch. Upstream main therefore received the state machine, canonical fingerprints, entity and migrations, but not the receipt storage, the service, the REST surface, the enforcement wiring or the docs, and #220 stayed open. This PR carries exactly the four commits that never landed; everything in them was already part of #377's final reviewed state.
What lands here
maintainer.action.proposed,.cancelled,.executedunder a new APPROVAL scope./admin/intents(propose, list, get, cancel, expire, receipt verify) behind the same JWT + role + 2FA guard stack as the existing admin controllers. Execution is deliberately not a generic endpoint: an intent is consumed by the route it authorizes, so a caller cannot attach an arbitrary operation to a signed intent.MAINTAINER_APPROVAL_INTENTS_ENFORCEDon (read per request, default off = byte-identical behaviour),PATCH /admin/users/:id/rolerequiresapprovalIntentIdin the body andDELETErequires it as a query parameter; the intent must commit to exactly this action, user and role — 409 on mismatch, 428 when absent. The executor is re-checked server-side, and a designated approver is the only permitted executor.docs/MAINTAINER_ACTION_APPROVALS.md: endpoints, flag, lifecycle diagram, edge-case table (dedupe, late execution, double-claim races, approver rules, tampered receipts), failure modes (evidence outages, missing signing key) and both rollback paths.Verification
79 tests across the affected suites — intent foundation, service lifecycle, REST wiring, both migrations, and the admin-role controller including 8 enforcement tests — all green on this branch.
npx tsc --noEmitreports nothing for any file here. The repo's pre-existing baseline failures are untouched.After this merges
#218 (the multi-actor concurrency harness, already written against this code) rebases down to exactly its own five commits and lands as a separate PR with
Closes #218.