Skip to content

feat: approval intent receipts, role-route enforcement and operator docs (#220) - #378

Merged
github-actions[bot] merged 4 commits into
TRELLIS-STELLAR:mainfrom
OkeyAmy:feat/220-approval-intent-completion
Oct 18, 2026
Merged

github-actions[bot] merged 4 commits into
TRELLIS-STELLAR:mainfrom
OkeyAmy:feat/220-approval-intent-completion

Conversation

@OkeyAmy

@OkeyAmy OkeyAmy commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

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

  • Receipt storage. The signed receipt document is kept (jsonb) next to its hash, so verification can be re-run after the fact; verification re-canonicalizes before hashing, so jsonb key order cannot invalidate a receipt. Additive, idempotent migration.
  • Lifecycle auditing. The approval flow leaves the same hash-chained trail as every other sensitive surface: maintainer.action.proposed, .cancelled, .executed under a new APPROVAL scope.
  • Service. Propose (identical-commitment dedupe, mandatory reason committed to by the fingerprint, self-approval rejected), single-use execution via an atomic conditional claim, signed receipts on completion and failure, cancellation, single and bulk expiry, and post-hoc receipt verification. Proposal evidence fails closed: if the evidence append fails, the intent is cancelled and the caller gets a 503 — nothing stays executable that cannot be evidenced.
  • Admin REST surface at /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.
  • Enforcement on the role routes. With MAINTAINER_APPROVAL_INTENTS_ENFORCED on (read per request, default off = byte-identical behaviour), 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, 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.
  • Operator guide — 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 --noEmit reports 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.

…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.
@drips-wave

drips-wave Bot commented Oct 18, 2026

Copy link
Copy Markdown

@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! 🚀

Learn more about application limits

@github-actions

Copy link
Copy Markdown

Code Review & Auto-Merge — @dorismaduegbunam 🚀

Thank you @OkeyAmy for your contribution!

Verified code changes and repository requirements. Merging pull request.

@github-actions
github-actions Bot merged commit 2b8a3ab into TRELLIS-STELLAR:main Oct 18, 2026
1 of 3 checks passed
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.

Trellis API: Add maintainer action approval flow with signed intent receipts

1 participant