Skip to content

L04-02: enforce operation-owned save and impact policy (#34) - #212

Closed
mberrys wants to merge 2 commits into
l04-01-registry-plan-contractfrom
l04-02-save-impact-policy
Closed

mberrys wants to merge 2 commits into
l04-01-registry-plan-contractfrom
l04-02-save-impact-policy

Conversation

@mberrys

@mberrys mberrys commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

L04-02 (#34), slice 2 of the L04 Governed Operation Engine stack (refs #5). It carries the operation-declared save policy to every Action List surface and holds each write boundary to it: step plans report the declared policy, the execution result carries the conservative merge, the Action List plan digest binds it, a weaker caller request is refused before any step work, and PdfTool action-list / repair validate the destination with the shared save contract (closing the repair --output <source> --overwrite trusted-source hole).

Stack position

Slice 2 of 8. Base: l04-01-registry-plan-contract (#208). Children stack on this branch; land bottom-up.

Interpretation note (P1)

"Preserved attributes" are the declared save-policy consequences themselves (mode, invalidates_signatures, reversible_in_session, rationale) — the tree already models them per operation; this slice carries them on every plan and refuses substitution. No new field was invented.

Per-criterion: already satisfied vs built here

Acceptance criterion State Proof
Mutation creates a new artifact according to operation policy Core/transaction already satisfied; Action List built here stepPlansCarryDeclaredSavePolicy (RED, mutation-verified), executionResultReportsMergedSavePolicy
Unknown impact selects full revalidation already satisfied; mapped regression added undeclaredImpactSelectsFullRevalidation (guard)
Failure: global save preference cannot bypass Core already satisfied; Action List built here executeRefusesWeakenedRequestedSavePolicy; action-list.save-policy-refused
Failure: omitted impact cannot bypass already satisfied (fail-closed) fullClassStepCannotBeNarrowed, oracleClassStepFailsClosedWithoutIndependentValidation (guards pinning existing fail-closed behavior at the Action List target)
Trusted-source overwrite via repair --output <source> --overwrite built here repairRefusesToWriteOverItsOwnInput (RED, mutation-verified: pre-change it exits 0 and publishes over the input)

Required proof

  • python scripts/agent/check-change.py --base cbad5614 --head 5985b011 --head-branch l04-02-save-impact-policy --build-dir <loop-build-l04> -> status: pass, 90/90 checks (builds LoopLibCore/PdfTool/LoopLibInteraction/loop-pdf-worker, 63 mapped suites, clang-tidy, format, changelog, architecture contracts).
  • Provenance: the proof was recorded at the pre-rebase head 89cdb965. After the stack was rebased onto the current dev, this slice's own diff is byte-identical (patch-id verified), so the proof carries; CI re-runs the mapped lanes on the head above.
  • First run: 89/90, failing only UnitTestsPdfWorkerIsolation::supervisorFaults(cpu) — the pre-existing razor-margin race now filed as UnitTestsPdfWorkerIsolation supervisorFaults(cpu) is a razor-margin race against the 135s client timeout #211 (120 s job CPU-time kill vs 135 s client timeout; base tree measured at 132.5-135.0 s, i.e. already on the boundary). Green on retry; CI agent-fast is the authoritative lane.
  • Focused: UnitTestsActionList (31/31) / UnitTestsRepairOperation / UnitTestsOperationImpact / UnitTestsGovernedExecution / UnitTestsPdfToolContract -> 5/5.
  • Mutation probes: removing the step-plan policy assignments fails stepPlansCarryDeclaredSavePolicy; removing the publish validation reproduces the pre-change hole (exit 0, output == input).
  • Architecture: check-architecture.py (+ diff mode), test_architecture_contracts.py, test_correction_operation_catalog.py, generate-architecture-catalogs.py --check -> green.
  • Mapping: UnitTestsActionList is now claimed by the core + interaction policy lanes and leaves migration.deferred_targets (check-architecture fails when a target is both claimed and deferred); tst_actionlisttest.cpp joins interaction.paths, tst_operationimpacttest.cpp joins core.paths.
  • Skipped locally: packaging lanes (hosted CI).

Notes

  • Closes #34.
  • P2: computeActionListPlanDigest now binds the merged policy; digest changes invalidate only transient approvals (no repo fixture pinned the old action-list digest).
  • Known inert-today call: the Editor publication-boundary validation has no source path at that layer (fresh temp artifact); it keeps the boundary on the same save contract and is documented as such.
  • 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.

…nd enforce it at every write (#34)

Carry the operation-declared save policy to every Action List surface and hold
each write boundary to it.

- Every Action List step plan now reports the declared policy as save_policy
  (plan() and the execute-time rebuild), and PDFActionListExecutionResult
  carries the conservative merge over all step operations
  (PDFActionListExecutor::mergedSavePolicy, mirroring
  PDFRepairTransaction::savePolicy()), serialized as save_policy.
  Interpretation (P1): "preserved attributes" are the declared consequences
  themselves - mode, invalidates_signatures, reversible_in_session, rationale -
  not a new field; nothing may substitute a different policy for them.
- computeActionListPlanDigest binds the merged policy into the
  action-list-plan envelope, parity with computeOperationPlanDigest (P2).
- PDFActionListExecutionOptions::requestedSavePolicy may ask for more safety
  than the declaration; a weaker request is refused with
  action-list.save-policy-refused before any step work, in plan() and execute()
  alike (dry runs included). Stricter requests are accepted; within one
  execution the refusal is not retried past (execute() consumes the refused
  plan result). The executor holds no cross-call latch because options are
  supplied per call.
- PdfTool action-list run/batch and the Editor publication boundary build the
  PDFSaveRequest and run pdf::validateSaveRequest before writing the candidate
  (save-policy.refused + processing failure on refusal). The run path's ad-hoc
  output==input check is subsumed: the Core validator covers every reachable
  collision (the input exists whenever the write is attempted) and also
  refuses canonical-path aliases.
- PdfTool repair arms transactionOptions.sourcePath and validates the publish
  destination through validateOperationSaveRequest with appendInPlace=false
  (P3: repair never appends in place), closing the --output <source>
  --overwrite hole before any publication side effect.

Tests: UnitTestsActionList gains stepPlansCarryDeclaredSavePolicy,
executeRefusesWeakenedRequestedSavePolicy, executionResultReportsMergedSavePolicy,
fullClassStepCannotBeNarrowed and oracleClassStepFailsClosedWithoutIndependentValidation;
UnitTestsOperationImpact gains undeclaredImpactSelectsFullRevalidation;
UnitTestsPdfToolContract gains repairRefusesToWriteOverItsOwnInput.

RED vs guard: stepPlansCarryDeclaredSavePolicy is RED (verified by mutation:
removing the two step-plan assignments fails it); repairRefusesToWriteOverItsOwnInput
is RED (verified by mutation: without the publish validation the run exits 0 and
publishes over its own input); executeRefusesWeakenedRequestedSavePolicy and
executionResultReportsMergedSavePolicy exercise API added by this change.
fullClassStepCannotBeNarrowed, oracleClassStepFailsClosedWithoutIndependentValidation
and undeclaredImpactSelectsFullRevalidation pass unchanged pre-change and pin
existing fail-closed behaviour at the Action List target (guards).

Mapping: UnitTestsActionList is claimed by the core and interaction policy
lanes and leaves migration.deferred_targets; UnitTests/tst_actionlisttest.cpp
joins interaction.paths and UnitTests/tst_operationimpacttest.cpp joins
core.paths.

Verified: clang-format --dry-run --Werror on all touched C++ files (clean);
build of LoopLibCore/PdfTool/LoopLibInteraction/UnitTestsActionList/
UnitTestsRepairOperation/UnitTestsOperationImpact/UnitTestsGovernedExecution/
UnitTestsPdfToolContract; ctest -R
'^(UnitTestsActionList|UnitTestsRepairOperation|UnitTestsOperationImpact|UnitTestsGovernedExecution|UnitTestsPdfToolContract)$'
- 5/5 passed; scripts/agent/check-architecture.py,
scripts/agent/test_architecture_contracts.py,
scripts/ci/test_correction_operation_catalog.py,
scripts/generate-architecture-catalogs.py --check - all green.
Skipped lanes: packaging linux-build/windows-build (hosted CI), sealed-eval
stub check_independent_validation_gate.py (holdout only).
@mberrys
mberrys force-pushed the l04-02-save-impact-policy branch from 758f29c to 5985b01 Compare October 5, 2026 12:59
@mberrys
mberrys force-pushed the l04-01-registry-plan-contract branch from bd9d12d to cbad561 Compare October 5, 2026 13:00
@mberrys
mberrys deleted the branch l04-01-registry-plan-contract October 5, 2026 19:50
@mberrys mberrys closed this Oct 5, 2026
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