Skip to content

L04-04: bind exact-plan approval with expiry, revocation, and approver rights (#36) - #214

Merged
mberrys merged 5 commits into
l04-03-isolated-previewfrom
l04-04-approval-binding
Oct 5, 2026
Merged

mberrys merged 5 commits into
l04-03-isolated-previewfrom
l04-04-approval-binding

Conversation

@mberrys

@mberrys mberrys commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

L04-04 (#36), slice 4 of the L04 Governed Operation Engine stack (refs #5). The execution gateway now decides authorization from the approval's own facts: expiry, revocation (history-chain ApprovalRevoked events, mirroring certificate invalidation), approver rights (actor/kind allowlists), and an optional effective-profile binding. Every in-repo production caller passes an explicit authorization context.

Stack position

Slice 4 of 8. Base: l04-03-isolated-preview (#213). Children stack on this branch; land bottom-up.

Per-criterion: already satisfied vs built here

Acceptance criterion State Proof
Only a current authorized approval passes the gateway digest binding / affirmative decision / waiver exclusion already satisfied; currency + authorization built here resolveApprovalAuthorization refuses non-affirmative decisions, empty actor, unauthorized kind/actor, expired or undeclared-required expiry, revoked reference, and profile mismatch; threaded through validateGovernedApproval / finalizeGovernedPublication / publishGovernedArtifact / validateGovernedSignOff
Failure: scope change invalidates already satisfied plan-digest mismatch (existing)
Failure: input change invalidates already satisfied source/candidate digest mismatch (existing)
Failure: profile change invalidates built here (repair path) PDFGovernedExecutionApproval::effectiveProfileDigest + context.expectedProfileDigest; profile-mismatch fixture
Failure: registry change invalidates already satisfied by #33 registry_digest inside the operation-plan envelope
Failure: approver-rights change invalidates built here actor/kind allowlist fixtures
Expiry built here PDFApprovalRecord::expiresUtc + isExpiredAt; expired / requireExpiry-without-expiry / fresh fixtures
Revocation built here ApprovalRevoked kind + resolver history scan; revocation race (validate passes, revocation appended, publish refused, no output) + ordering variant

Required proof

  • python scripts/agent/check-change.py --base 99e08022 --head 2d18b060 --head-branch l04-04-approval-binding --build-dir <loop-build-l04> -> status: pass, 96/96 checks (LoopLibCore/PdfTool/loop-pdf-worker builds, mapped suites, clang-tidy, format, changelog, architecture contracts).
  • Provenance: the proof was recorded at the pre-rebase head ef80c0f9. The stack rebase left this slice's own diff unchanged (patch-id verified) and the only later edit is a documentation-only fragment correction, so the proof carries for the code; CI re-runs the mapped lanes on the head above.
  • Focused: UnitTestsGovernedExecution / UnitTestsOperationHistory / UnitTestsPdfToolContract / UnitTestsActionList / UnitTestsPageMasterExport -> 5/5.
  • Five new governed fixtures are RED-verified (short-circuiting the resolver makes the binary exit 5); the operation-history expiry/kind round-trip is a guard.
  • Backward compatibility: the event hash drops an absent expiresUtc, so already-committed chains stay verifiable; the kind is persisted by name, so the new enumerator does not shift stored values.
  • Architecture: check-architecture.py, test_architecture_contracts.py, test_check_governed_parity.py -> green.
  • Pre-existing flake UnitTestsPdfWorkerIsolation supervisorFaults(cpu) is a razor-margin race against the 135s client timeout #211 can redden a mapped run; CI agent-fast is authoritative.

Notes

  • Closes #36.
  • Deviations, stated plainly: the resolver takes the governed-approval envelope rather than the bare record because the profile binding lives on the envelope; PageMaster and the Editor worker have no history store in scope, so their contexts leave revocation resolution to L04-05 — Centralize approved Core execution #37 (which routes execution through one gateway); no requiredPolicyId was added (the allowlists plus the approval's own policyId cover the declared rights).
  • Breaking-change: no.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

…#36)

Add the exact-plan approval authorization model for issue #36. PDFApprovalRecord gains an optional expiresUtc (null = no declared expiry) and isExpiredAt(); PDFGovernedExecutionApproval gains an optional effectiveProfileDigest. A new ApprovalRevoked provenance kind revokes an approval through the append-only history chain, mirroring certificate invalidation with no second registry.

resolveApprovalAuthorization() is the single decision point and fails closed on a non-affirmative decision, an unauthorized kind/actor, an expired or undeclared-required expiry, a revoked decision reference, or a profile-digest mismatch. validateGovernedApproval, finalizeGovernedPublication, publishGovernedArtifact, and validateGovernedSignOff call it before any write through an optional PDFApprovalAuthorizationContext (default {} keeps existing call sites compiling).

Tests: UnitTestsGovernedExecution adds approvalExpiryWindowIsEnforced, approverRightsAreAllowlisted, profileBindingRefusesMismatch, revokedApprovalIsRefusedBeforeWrite, revokedApprovalRefusesLaterAttempt (RED-verified: short-circuiting the resolver makes all five fail). UnitTestsOperationHistory adds approvalExpiryAndRevocationKindRoundTrip and pins ApprovalRevoked in the kind list. The governed-execution test switches QTEST_APPLESS_MAIN to QTEST_GUILESS_MAIN because the revocation fixtures open a QSqlDatabase.
)

Every in-repo production caller of the governed gateway now passes an explicit PDFApprovalAuthorizationContext instead of relying on the permissive default. PdfTool repair and action-list run/batch resolve revocation against the output .loop-history chain and bind the approval to computeProfileDigest() of the profile in scope; the operator --approval-file path keeps the operator's actor id and is checked against the profile actually in scope. PageMaster binds the resolved effective profile digest. PageMaster and the Editor Action List worker have no operation-history store in scope, so their contexts leave history null and revocation cannot be resolved there until #37 centralizes execution.
@mberrys
mberrys force-pushed the l04-04-approval-binding branch from 08ddc82 to 2d18b06 Compare October 5, 2026 14:04
@mberrys
mberrys added this pull request to stack #220 October 5, 2026 21:52
@mberrys
mberrys merged commit bef8e06 into l04-02-save-impact-policy Oct 5, 2026
3 of 5 checks passed
@mberrys
mberrys deleted the l04-04-approval-binding branch October 5, 2026 22:33
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