Skip to content

L04-08: forward rollback with revalidation, and cross-surface parity (#40) - #218

Merged
mberrys merged 7 commits into
l04-07-provenance-signofffrom
l04-08-rollback-parity
Oct 5, 2026
Merged

mberrys merged 7 commits into
l04-07-provenance-signofffrom
l04-08-rollback-parity

Conversation

@mberrys

@mberrys mberrys commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

L04-08 (#40), the final slice of the L04 Governed Operation Engine stack (refs #5). Rollback now creates a new revision that is revalidated and signed off like any other publication, a CLI rollback surface exists, and the cross-surface parity claim is bound to a named fixture that runs in CI.

Stack position

Slice 8 of 8. Base: l04-07-provenance-signoff (#217). Root of the stack targets dev; land bottom-up.

Per-criterion: already satisfied vs built here

Acceptance criterion State Proof
Retain digest-verified prior artifacts already satisfied PDFArtifactStore (content-addressed, verify-then-restore) + history retention; unchanged
Rollback creates a new revision and revalidates it built here rollbackTo restores the verified prior bytes as a new revision, revalidates through the #38 finalize path, records a RolledBack event carrying {approval, revalidation, sign_off} + report/profile digests; on revalidation failure it fails closed (removes the destination, appends the Failed event)
History rewrite or direct restore of unverified bytes forbidden already satisfied for bytes; hardened here a verify()-before-append guard refuses to extend a compromised chain; fault injections: missing profile refuses, rewritten history refuses (no destination, no new event)
Editor, CLI, Action List, PageMaster reach semantically equal outputs and receipts for equivalent plans built here UnitTestsProductOperatorLoop::governedPublicationParityAcrossSurfaces drives CLI repair, CLI action-list, the Editor Action List route, and PageMaster over one fixture + plan, against committed receipts in UnitTests/testdata/governed-parity/; check_governed_parity.py now runs in CI over those receipts (closes the d5-cross-surface-identity-equality TODO)
New surface built here pdftool rollback (JSON-only), with the full registration train

Parity claim, stated precisely

The recon's "all four surfaces share source_sha256 + effective_profile_digest" is false for PageMaster: it publishes a derived source-identity digest and its own resolved-profile digest. The test therefore asserts literal shared identity across repair / action-list / Editor and pins PageMaster's own digest through its manifest fixture; action-list plan-digest equality holds for the CLI action-list <-> Editor pair.

Required proof

  • python scripts/agent/check-change.py --base 1a092d68 --head c587089b --head-branch l04-08-rollback-parity --build-dir <loop-build-l04> -> status: pass, 103/103 checks.
  • Provenance: the proof was recorded at the pre-rebase head 9d2728d9. 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: UnitTestsOperationHistory / UnitTestsGovernedExecution / UnitTestsPdfToolContract / UnitTestsPageMasterExport / UnitTestsProductOperatorLoop -> 5/5; UnitTestsEditorHost 24/24 after the fix below.
  • Fix commit 9d2728d: the Editor rollback now runs its revalidation through PDFJobScheduler (the first proof run caught the Editor calling PreflightEngine::run on the interactive thread, which the engine refuses by invariant, preflightengine.cpp:7147); the worker builds its own store because Qt SQL connections are thread-affine.
  • Architecture: check-architecture.py (+ diff mode), test_architecture_contracts.py, test_check_governed_parity.py, workflow-contract tests -> 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 #40.
  • Deferrals, stated: Action List and PageMaster do not get their own rollback surfaces in this slice (Editor + CLI do), recorded in docs; fixture-class registration was skipped because it would require a quality-budgets.yaml metric entry (protected file) — the lane + fixture paths are bound instead.
  • 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.

@mberrys
mberrys force-pushed the l04-08-rollback-parity branch from 9d2728d to 5b48094 Compare October 5, 2026 12:54
@mberrys
mberrys force-pushed the l04-08-rollback-parity branch from 5b48094 to a077678 Compare October 5, 2026 12:59
…hread (#40)

The Editor ran PDFOperationHistoryStore::rollbackTo synchronously on the GUI
thread, where the #38 revalidation reaches PreflightEngine::run and is refused:
"Preflight cannot run synchronously on the interactive thread."
(LoopLibCore/sources/preflightengine.cpp:7147). Measured: UnitTestsEditorHost
fixRollbackReturnsToARecordedRevision failed with "PreflightEngine::run must not
run on the interactive thread" immediately before
host.requestFixRollback(rollbackId) returned false, so "rollback revalidates the
restored revision" was unsatisfiable from the Editor.

Route the rollback through the session's PDFJobScheduler exactly like the Action
List run. requestFixRollback keeps every synchronous validation (availability,
path, store open, rollback point, current-bytes digest, audit event, validated
profile) and the request construction, then submits a PDFJobKind::Other /
Operator job. The worker builds its own PDFOperationHistoryStore and
PDFArtifactStore (Qt SQL connections are thread-affine, so the GUI-thread store
is never touched off-thread), runs rollbackTo and, on success, enforceRetention,
recording both in a RollbackJobOutcome. jobFinished dispatches finishRollbackJob
first, which announces the same success/retention/failure messages, re-opens the
rolled-back revision and refreshes the rollback points. A second request while
one is in flight is refused, and job cancellation is honored between restore and
retention.

The test pins the bundled :/profiles/loop-default.json deterministically and
waits on the scheduler's jobFinished (the RolledBack event is the completion
signal); probing the history while the worker held its SQLite connection
contended for the database and made rollbackTo fail "database is locked", so the
wait is on the scheduler, not the store. The unknown-revision refusal leg now
also asserts no sibling revision was written. Note: the bundled profile is
auto-selected at construction, so a refusal leg that calls
requestFixRollback(rollbackId) before an explicit profile selection is
unsatisfiable; the Editor's fail-closed refusal path is pinned by the
unknown-revision leg and the profile requirement by Core's
rollbackRefusesWithoutProfileOrOnCompromisedChain.

Commands/results:
- clang-format --dry-run --Werror LoopEditor/editorhost.h LoopEditor/editorhost.cpp
  UnitTests/tst_editorhosttest.cpp: clean.
- cmake --build C:/.dev/repos/loop-build-l04 --target LoopLibCore LoopEditor
  LoopLibInteraction UnitTestsEditorHost UnitTestsProductOperatorLoop
  UnitTestsOperationHistory UnitTestsGovernedExecution: ok.
- ctest --test-dir C:/.dev/repos/loop-build-l04 -R
  '^(UnitTestsEditorHost|UnitTestsProductOperatorLoop|UnitTestsOperationHistory|UnitTestsGovernedExecution)$':
  4/4 passed; UnitTestsEditorHost 24 passed / 0 failed.
- python scripts/agent/check-architecture.py: architecture contracts ok.
- python scripts/agent/test_architecture_contracts.py: OK (8 tests, 1 skipped).
@mberrys
mberrys force-pushed the l04-08-rollback-parity branch from a077678 to c587089 Compare October 5, 2026 14:04
# Conflicts:
#	LoopLibCore/sources/pdfoperationhistorystore.cpp
# Conflicts:
#	UnitTests/tst_operationhistorytest.cpp
@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
6 of 8 checks passed
@mberrys
mberrys deleted the l04-08-rollback-parity 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