Skip to content

Promote dev to unstable — Quick workspace and governed repair contracts - #222

Open
mberrys wants to merge 29 commits into
unstablefrom
dev
Open

mberrys wants to merge 29 commits into
unstablefrom
dev

Conversation

@mberrys

@mberrys mberrys commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

What changed

Promotes the current dev train toward unstable. This range delivers the remaining L03 Quick operator-workspace slices in this train—partial-inspection proof, explicit production-preview fidelity/origin, Compare review, exact-plan approval proof, and installed-package accessibility qualification—alongside the first two L04 governed-operation contract slices for registry/plan identity and operation-owned save/impact policy.

GitHub currently reports the branches as diverged: dev is 28 commits ahead of and 3 commits behind unstable. The merge base is c9546514d219ab7d6dd9ba681f15f593eb90b441; the current unstable tip is 275e272331a9ba607bc2eb74f8ee38641f726aa4; this PR head is 1077bd864e85e3862f4254e87e6497b9a3a66bd3.

Release changelog

dev → unstable — Quick workspace and governed repair contracts

This promotion advances the operator-facing Quick workspace and the governed repair boundary without changing persistence formats or introducing a second PDF/publication authority. It does not claim final stable-branch release admission.

Scope: merge base c9546514 → 1077bd86. GitHub compare reports 28 commits ahead; the available connector does not expose a verified non-merge-only count. Current unstable is also 3 commits ahead of the merge base.

Added

  • L03-03 — Add Compare and review workspace #29 — Compare and review workspace. ComparePane presents exact before/candidate/published artifact identities, finding deltas, preserved attributes, and unresolved risk from Core-owned review data; stale previews and mismatched plan digests are blocked instead of silently refreshed.
  • L03-04 — Present plan and approval journey #30 — Exact plan/approval journey proof. End-to-end acceptance coverage proves that unapproved, rejected, stale-revision, and superseded plans cannot execute or publish, and that completion presents the revalidated verdict plus the digest of the artifact actually published.

Changed

  • L03-01 — Build one-document Quick operator shell #27 — Quick operator-shell incomplete-state proof. The representative Quick journey now explicitly proves that partial or unsupported inspection remains visibly incomplete and cannot read as a clear document.
  • L03-02 — Separate Production Preview from fast canvas #28 — Production Preview fidelity/origin contract. The ordinary canvas and Production Preview share one fidelity-and-origin projection; overprint-sensitive fast-canvas pixels are marked approximate and cannot serve as print-safe proof, with an explicit action to switch to the authoritative output-preview render.
  • L03-05 — Qualify native accessibility and Widgets-free graph #31 — Installed-package native accessibility qualification. Linux and Windows packaging workflows run the Quick accessibility harness against a staged installed tree, keep native and software evidence distinct, add Windows UI Automation qualification, and fail closed when software-only evidence is offered as a native claim.
  • L04-01 — Reconcile registry and canonical plan contract #33 — Registry and canonical plan identity. PDFRepairRegistry becomes the single registration authority; registry identity is bound into deterministic plan digests; parameter/schema validation, duplicate registration, stale-source revision, and repeated CLI-parameter failures are rejected before candidate execution.
  • L04-02 — Enforce operation-owned save and impact policy #34 — Operation-owned save and impact policy. Action List plans/results carry the operation-declared save policy through execution and publication; weakened requests are refused, policy is bound into the plan digest, and PdfTool repair refuses publication over its trusted input even with --overwrite.

Internal

  • Adds golden plan/comparison fixtures, mapped unit and operator-loop coverage, accessibility evidence verification, installed-tree staging helpers, UI Automation qualification tooling, and corresponding architecture/CI/documentation updates.
  • Removes the disabled Compare placeholder and makes Compare a real workspace in the rail and keyboard traversal.
  • Extends state-visual tokens so fidelity is represented as unavailable/stale/exact/approximate/authoritative rather than being conflated with a generic pass state.

Refs / admission status

This PR targets unstable, not the default stable branch, so it intentionally does not close the implementation issues here.

Refs #27 — software Quick interaction fixtures are recorded; the evidence manifest still notes the Windows-only native-UIA host-inspection lane was not run for this slice.

Refs #28 — focused fidelity tests are recorded locally; remaining mapped Quick/interaction coverage is delegated to hosted Linux/Windows CI.

Refs #29 — evidence manifest reports no unresolved items; keep open through stable/default-branch admission.

Refs #30 — evidence manifest reports no unresolved items; keep open through stable/default-branch admission.

Refs #31 — installed-package accessibility evidence manifest reports no unresolved items; keep open through stable/default-branch admission.

Refs #33 — independent-validation/sealed-eval coverage and canonical digest-stability enforcement remain outstanding in the evidence manifest; hosted packaging lanes are cited rather than locally reproduced.

Refs #34 — independent-validation/sealed-eval coverage remains outstanding, and the evidence manifest notes focused Action List/PdfTool runtime tests for the PR 212 review fixes were not rerun against that earlier isolated head.

Verification

At PR head 1077bd864e85e3862f4254e87e6497b9a3a66bd3, GitHub currently returns no PR-triggered workflow runs or combined status checks through the connected API, so this body does not claim a green head-level CI result.

The committed evidence manifests in this range name the mapped unit, architecture, packaging, differential, fixture, and security lanes supporting each slice and explicitly retain the unresolved items listed above.

Breaking changes

None. These changes tighten execution/publication validation and add/extend operator surfaces and evidence contracts; no persistence-format or public schema migration is introduced by the changelog fragments in this promotion.

Security and rollback

  • Fail-closed behavior is strengthened at repair registration, parameter validation, source-revision fencing, save-policy enforcement, publication, and accessibility-evidence verification boundaries.
  • Rollback: do not promote this head; restore unstable to its prior admitted tip and re-run the promotion after reconciling the three commits currently unique to unstable.

Docs

Updated in this range, including governed execution, incremental save, Quick accessibility, renderer differentials, repair operations, shell/workspace contracts, CI, and packaging/licensing documentation.

mberrys and others added 28 commits October 3, 2026 23:18
The canvas and the Production Preview both showed something about render
fidelity, but neither named the render origin, the document-wide preview
authority never left Approximate, and no surface said that an approximate
overprint render is not proof of print-safe output (#28).

Add pdfquick::tokens::classifyPreviewFidelityState / previewFidelityOriginName /
resolvePreviewFidelityStateVisual and project them through EditorHost as
previewFidelityStateName / previewFidelityOriginName / previewFidelityVisual /
previewFidelityColor / previewFidelitySummary / previewRequiresAuthoritative.
Both the ordinary canvas banner and the Production Preview render that one
projection: fidelity unavailable/stale/exact/approximate/authoritative, origin
none/fast-canvas/output-preview. The overprint-sensitive fast render is the
`approximate` state, whose summary says its pixels cannot stand as proof of
print-safe output and which no state resolves to a pass.

EditorHost::ensureAuthoritativePreview() is the explicit switch the preview
surfaces call before a page is presented as proof: it moves the current page to
the output-preview render and returns false when it is already authoritative or
exact. The Production Preview gains a "proof this page" affordance bound to it.

Proof: UnitTestsLoopStateVisual (classification, origin, distinct shapes, never
a pass), UnitTestsEditorHost::previewFidelityNamesTheOriginAndSwitchesExplicitly
(the projection and the explicit switch on a real overprint fixture), and
UnitTestsPageSurface::fastCanvasOverprintRenderIsNotTheAuthoritativeGolden (the
fast render does not match overprint-cmyk-mode1-on.png while the authoritative
render does, and the fast diagnostics report the approximation).
Run the product Quick accessibility qualification against a staged copy of
the installed tree, not the developer build tree, on Linux and Windows. The
native and software runs write separate records with distinct claims; Windows
drives the native UI Automation client and requires it; Linux records the OS
accessibility lane unavailable. scripts/ci/verify_quick_accessibility_evidence.py
fails closed when a required lane is missing, failed, captured outside an
installed tree, or replaced by a software-only record.
Present the before/after artifact identities, the technical finding delta,
the preserved attributes and the unresolved risk Core already produced for
the plan or run on screen; let the operator navigate material deltas; and
block a stale preview or mismatched plan digest instead of silently
refreshing it.

- ComparePane.qml reads EditorHost.compareReview(), a read-only projection
  of fixPlanIdentity/fixPreview/fixRecheck/fixSignOff (no second comparison
  model in QML); isWorkspaceEnabled(Compare) is now true and the placeholder
  pane is retired.
- UnitTestsProductOperatorLoop: a golden comparison fixture pins the Core
  PDFRepairFindingDelta classification, and a stale-preview test proves the
  guard blocks and refuses navigation; UnitTestsShellWorkspace proves Compare
  is reachable.
…erator shell (#27)

Add the operator-journey proof that a missing or partial inspection stays
visible and never reads as a clear document, in both the mapped
product-operator-loop suite and the Quick interaction smoke.

- UnitTests/tst_productoperatorloop.cpp: run the shipped representative
  fixture (bleed-missing.pdf) under the corpus restriction-pages profile
  through the real preflight engine, and assert the observable progress,
  the incomplete verdict, the operator summary/limitation and Core's
  incomplete visual treatment (never a pass).
- tools/ProductQuickAccessibilitySmoke: add an incomplete-inspection
  fixture to the software-backend interaction smoke and chain it after the
  existing finding-navigation (representative) fixture.

The representative open/select-find/navigate journey already shipped with
the L02 slices; it is covered, not re-implemented. No Core, editor-host or
QML behavior changes.
Add the acceptance fixture for issue #30: an unapproved plan, a rejected plan, a plan whose document revision has moved, and a plan replanned after approval each neither execute nor publish anything; a run requires an approval given for the plan displayed at that moment; and completion presents the revalidated verdict with the 64-hex digest of the artifact it published, distinct from the as-received input.

The fixture is mutation-probed: removing the execution arming check, the revision comparison, the superseded predicate or the review clearing each makes it fail. The reviewed-digest equality in fixLifecycleStateName() and the fixPlanIsCurrent() clause in approveActionListPlan() are defensive against a state the journey offers no path to, and are documented as such rather than claimed as coverage.
…he async open

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…lane on any platform

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ritative-but-inexact visible

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…rkspace

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…or-shell

L03-01: keep a partial inspection visible in the one-document Quick operator shell (#27)
…l-journey

Prove the plan and approval journey publishes only an approved plan
…qualification

chore(ci): qualify native accessibility against the installed package (#31)
…review

Separate the preview fidelity and origin from the fast canvas (#28)
L03-03: Add the Compare and review workspace (#29)
…into the plan digest (#33)

Make PDFRepairRegistry the single registration authority: registerOperation now
returns PDFOperationResult and refuses a null operation, an empty id, and a
duplicate id (first registration wins). The two registration units capture the
result and Q_ASSERT it so a debug build fails loudly. A public default
constructor is the isolated-registry test seam; instance() stays the production
singleton.

Bind registry identity into the plan contract: PDFRepairRegistry::digest() is
the SHA-256 of the canonical {id, version} set, and computeOperationPlanDigest
adds it as registry_digest to the operation-plan envelope, so a registry change
moves every plan digest and invalidates bound approvals. Envelope
schema_version stays "1.0": additions are non-breaking.

Close the four failure gates before candidate computation:
- duplicate registration is refused instead of silently replaced;
- unknown operation stays refused at find()/add(nullptr);
- ambiguous parameters are refused by PDFRepairTransaction::add() through the
  shared validateJsonSchemaFragment() (lifted from pdfactionlist.cpp, which now
  delegates to it), and PdfTool repair refuses a repeated --param key;
- PDFRepairTransactionOptions::expectedSourceSha256 refuses a stale source
  revision at the top of analyze() before any operation runs.

Tests: UnitTestsRepairOperation gains duplicate-registration,
unknown-operation, ambiguous-parameter and stale-revision slots;
UnitTestsGovernedExecution gains registry-digest format/stability and a plan
digest pinned to the committed operation-plan golden vector (envelope test
updated for registry_digest); UnitTestsPdfToolContract gains the repeated
--param refusal with a positive control. Fixtures: canonical-json/
operation-plan-envelope.json plus expected-plan-digests.json pin the
cross-platform plan-envelope bytes.

Verified locally (MSVC + Qt 6.11.1, Ninja, C:/.dev/repos/loop-build-l04):
- cmake --build --target LoopLibCore PdfTool UnitTestsRepairOperation
  UnitTestsGovernedExecution UnitTestsPdfToolContract UnitTestsActionList: ok
- ctest -R '^(UnitTestsRepairOperation|UnitTestsGovernedExecution|UnitTestsPdfToolContract|UnitTestsActionList)$':
  4/4 passed (75.7 s)
- clang-format --dry-run --Werror on every touched C++ file: clean
- python scripts/agent/check-architecture.py (static): ok
- python scripts/agent/test_architecture_contracts.py: OK (1 skipped)
- python scripts/ci/test_correction_operation_catalog.py: OK
- python scripts/generate-architecture-catalogs.py --check: ok

Skipped lanes: packaging:linux-build / packaging:windows-build are hosted CI;
the mapped check-change proof runs at the coordinator. UnitTestsActionList ran
as a manual regression check (target not mapped in agent-policy).

Migration decision (recorded in docs/GOVERNED_EXECUTION.md): legacy pdftool
addbleed/rgbtocmyk stay registry-metadata consumers; convergence is L04-05
(#37) scope.
…33)

validateJsonSchemaFragment recorded required/additionalProperties violations
without failing, so a caller that consumes only the bool (the Action List
planner) accepted an unknown key or a missing required parameter. The lifted
behaviour is preserved everywhere else; the structural branches now fail the
result, and PDFRepairTransaction::add() refuses on that single contract again.

Tests: a validator-contract slot in UnitTestsRepairOperation (missing required,
unknown key, nullptr error sink, valid control) and an Action List slot in
UnitTestsActionList (unknown key + missing required refused on a recipe).
UnitTestsRepairOperation / UnitTestsActionList / UnitTestsGovernedExecution /
UnitTestsEditorHost / UnitTestsProductOperatorLoop: 5/5 passed.
…heck (#33)

valuesEqual serialised both sides through QJsonValue::toObject(), which yields an empty
object for any non-object value, so every pair of scalars compared equal and the enum
branch accepted any correctly-typed value (an add-bleed mode of "stretch" passed an enum
of mirror/pixel-repeat). Compare the typed values instead, and pin the refusal with
validateJsonSchemaFragment_rejectsValuesOutsideTheAllowedSet.

Measured: the new slot fails on the pre-fix validator and passes with the fix;
UnitTestsRepairOperation 34/34, UnitTestsActionList cli-parity/dry-run slots pass.
…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).
L04-02: enforce operation-owned save and impact policy (#34)
@mberrys mberrys changed the title Dev Promote dev to unstable — Quick workspace and governed repair contracts Oct 6, 2026
@mberrys
mberrys added this pull request to stack #223 October 6, 2026 03:56
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