From 54392cc0de7c8391c4f216cb83eee64be322ec72 Mon Sep 17 00:00:00 2001 From: fy59 Date: Tue, 13 Oct 2026 18:56:48 +0200 Subject: [PATCH] docs(mosaic): preserve nested receipt integrity and independent review evidence --- CHANGELOG.md | 18 + docs/ARCHITECTURE.md | 18 + docs/KNOWN_LIMITATIONS.md | 18 + docs/README.md | 18 + docs/ROADMAP.md | 18 + ...0C_STATIC_NESTED_RECEIPT_INTEGRITY_GATE.md | 224 +++++++++++++ .../README.md | 47 ++- .../receipt_integrity.py | 314 ++++++++++++++++++ .../tests_offline.py | 295 +++++++++++++--- .../write_results.py | 116 +++---- 10 files changed, 982 insertions(+), 104 deletions(-) create mode 100644 docs/design/STEP20C_STATIC_NESTED_RECEIPT_INTEGRITY_GATE.md create mode 100644 tooling/probes/step20c-static-nested-geometry-replay/receipt_integrity.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a495b6..51877fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,3 +88,21 @@ - Preserved the [geometry gate](docs/design/STEP20C_STATIC_NESTED_GEOMETRY_REPLAY_GATE.md) and experimental source fixture with its historical INCONCLUSIVE verdict; STEP20C NEXT, architecture UNDECIDED and production multi-output NOT YET SUPPORTED. No production/package/UX change. + +## Local receipt semantic integrity gate — offline only + +The [R02 receipt-integrity gate](docs/design/STEP20C_STATIC_NESTED_RECEIPT_INTEGRITY_GATE.md) +is local and experimental. The real writer/aggregate share semantic validation +through the existing immutable-data replay and R01 input correlation; typed +receipt content, scenario-specific omissions, AST/test inventories and final +review bindings are checked. Historical geometry evidence remains INCONCLUSIVE; +this correction cannot retroactively qualify static popups. The final local gate +is READY_FOR_NEW_RUNTIME: 206 offline tests pass, 13478 recorded invalid mutations +are rejected, and independent review accepts the final 18-source/15-receipt +bindings after one bounded B-01 writer repair. The actual writer returns READY +with five PASS markers and no blockers. R02 is RESOLVED for this offline fixture; +partial AST coverage and runtime limitations remain explicit. Phase B is +uncommitted and unpublished, and readiness only permits requesting a separate +future authorization. No compositor/runtime authorization, +production/UX/package change or STEP20D work follows. Architecture UNDECIDED; +STEP20C NEXT; production multi-output NOT YET SUPPORTED. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index bcf6317..ad6077c 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -454,3 +454,21 @@ terminal-code repairs are accepted; no readiness or new static popup result. No graphical run or execution reservation occurred. Close the residual offline receipt-validation gate before proposing runtime. Architecture UNDECIDED; STEP20C NEXT; production multi-output NOT YET SUPPORTED. + +## Local receipt semantic integrity gate — offline only + +The [R02 receipt-integrity gate](design/STEP20C_STATIC_NESTED_RECEIPT_INTEGRITY_GATE.md) +is local and experimental. The real writer/aggregate share semantic validation +through the existing immutable-data replay and R01 input correlation; typed +receipt content, scenario-specific omissions, AST/test inventories and final +review bindings are checked. Historical geometry evidence remains INCONCLUSIVE; +this correction cannot retroactively qualify static popups. The final local gate +is READY_FOR_NEW_RUNTIME: 206 offline tests pass, 13478 recorded invalid mutations +are rejected, and independent review accepts the final 18-source/15-receipt +bindings after one bounded B-01 writer repair. The actual writer returns READY +with five PASS markers and no blockers. R02 is RESOLVED for this offline fixture; +partial AST coverage and runtime limitations remain explicit. Phase B is +uncommitted and unpublished, and readiness only permits requesting a separate +future authorization. No compositor/runtime authorization, +production/UX/package change or STEP20D work follows. Architecture UNDECIDED; +STEP20C NEXT; production multi-output NOT YET SUPPORTED. diff --git a/docs/KNOWN_LIMITATIONS.md b/docs/KNOWN_LIMITATIONS.md index 9b377c5..813217b 100644 --- a/docs/KNOWN_LIMITATIONS.md +++ b/docs/KNOWN_LIMITATIONS.md @@ -395,3 +395,21 @@ terminal-code repairs are accepted; no readiness or new static popup result. No graphical run or execution reservation occurred. Close the residual offline receipt-validation gate before proposing runtime. Architecture UNDECIDED; STEP20C NEXT; production multi-output NOT YET SUPPORTED. + +## Local receipt semantic integrity gate — offline only + +The [R02 receipt-integrity gate](design/STEP20C_STATIC_NESTED_RECEIPT_INTEGRITY_GATE.md) +is local and experimental. The real writer/aggregate share semantic validation +through the existing immutable-data replay and R01 input correlation; typed +receipt content, scenario-specific omissions, AST/test inventories and final +review bindings are checked. Historical geometry evidence remains INCONCLUSIVE; +this correction cannot retroactively qualify static popups. The final local gate +is READY_FOR_NEW_RUNTIME: 206 offline tests pass, 13478 recorded invalid mutations +are rejected, and independent review accepts the final 18-source/15-receipt +bindings after one bounded B-01 writer repair. The actual writer returns READY +with five PASS markers and no blockers. R02 is RESOLVED for this offline fixture; +partial AST coverage and runtime limitations remain explicit. Phase B is +uncommitted and unpublished, and readiness only permits requesting a separate +future authorization. No compositor/runtime authorization, +production/UX/package change or STEP20D work follows. Architecture UNDECIDED; +STEP20C NEXT; production multi-output NOT YET SUPPORTED. diff --git a/docs/README.md b/docs/README.md index eb30a6e..2e67dc0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -313,3 +313,21 @@ terminal-code repairs are accepted; no readiness or new static popup result. No graphical run or execution reservation occurred. Close the residual offline receipt-validation gate before proposing runtime. Architecture UNDECIDED; STEP20C NEXT; production multi-output NOT YET SUPPORTED. + +## Local receipt semantic integrity gate — offline only + +The [R02 receipt-integrity gate](design/STEP20C_STATIC_NESTED_RECEIPT_INTEGRITY_GATE.md) +is local and experimental. The real writer/aggregate share semantic validation +through the existing immutable-data replay and R01 input correlation; typed +receipt content, scenario-specific omissions, AST/test inventories and final +review bindings are checked. Historical geometry evidence remains INCONCLUSIVE; +this correction cannot retroactively qualify static popups. The final local gate +is READY_FOR_NEW_RUNTIME: 206 offline tests pass, 13478 recorded invalid mutations +are rejected, and independent review accepts the final 18-source/15-receipt +bindings after one bounded B-01 writer repair. The actual writer returns READY +with five PASS markers and no blockers. R02 is RESOLVED for this offline fixture; +partial AST coverage and runtime limitations remain explicit. Phase B is +uncommitted and unpublished, and readiness only permits requesting a separate +future authorization. No compositor/runtime authorization, +production/UX/package change or STEP20D work follows. Architecture UNDECIDED; +STEP20C NEXT; production multi-output NOT YET SUPPORTED. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 2295421..d70dacd 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -399,3 +399,21 @@ terminal-code repairs are accepted; no readiness or new static popup result. No graphical run or execution reservation occurred. Close the residual offline receipt-validation gate before proposing runtime. Architecture UNDECIDED; STEP20C NEXT; production multi-output NOT YET SUPPORTED. + +## Local receipt semantic integrity gate — offline only + +The [R02 receipt-integrity gate](design/STEP20C_STATIC_NESTED_RECEIPT_INTEGRITY_GATE.md) +is local and experimental. The real writer/aggregate share semantic validation +through the existing immutable-data replay and R01 input correlation; typed +receipt content, scenario-specific omissions, AST/test inventories and final +review bindings are checked. Historical geometry evidence remains INCONCLUSIVE; +this correction cannot retroactively qualify static popups. The final local gate +is READY_FOR_NEW_RUNTIME: 206 offline tests pass, 13478 recorded invalid mutations +are rejected, and independent review accepts the final 18-source/15-receipt +bindings after one bounded B-01 writer repair. The actual writer returns READY +with five PASS markers and no blockers. R02 is RESOLVED for this offline fixture; +partial AST coverage and runtime limitations remain explicit. Phase B is +uncommitted and unpublished, and readiness only permits requesting a separate +future authorization. No compositor/runtime authorization, +production/UX/package change or STEP20D work follows. Architecture UNDECIDED; +STEP20C NEXT; production multi-output NOT YET SUPPORTED. diff --git a/docs/design/STEP20C_STATIC_NESTED_RECEIPT_INTEGRITY_GATE.md b/docs/design/STEP20C_STATIC_NESTED_RECEIPT_INTEGRITY_GATE.md new file mode 100644 index 0000000..2741bfd --- /dev/null +++ b/docs/design/STEP20C_STATIC_NESTED_RECEIPT_INTEGRITY_GATE.md @@ -0,0 +1,224 @@ +# STEP20C static nested receipt semantic integrity gate + +EXPERIMENTAL, offline only; semantic integrity gate READY_FOR_NEW_RUNTIME, +with independent acceptance of the final bound fixture and receipts. +Historical geometry gate remains INCONCLUSIVE. Architecture UNDECIDED; +STEP20C NEXT, production multi-output NOT YET SUPPORTED. No graphical run, +Mosaic/SceneFX build, client execution, capture, input, grab, camera or runtime reservation. + +## Pre-implementation contract + +The published `write_results.py` SHA-256 is +`94ddab595dd9765d3edaa7158a6a98319f3be6b15a62842e4b10eeacfb530cfa`. +Its `REQUIRED` names fifteen receipts; independent-review is the sixteenth +dependency. `write()` loads the receipts, computes bindings, and calls +`aggregate(evidence, hash_file, bindings)`. The pre-correction function checks status, +field presence, nine-case cardinality and some file hashes. It does not verify +the effective nested content. Three actual in-memory calls accept a bare PASS +input correlation, null rendered content/bbox/count/intersection, or nine +empty root objects. These are R02-A/B/C, not compositor observations. + +The correction must use this entrypoint for all decisions. Downstream gates not checked after a failed prerequisite are also INCONCLUSIVE. Invalid evidence +returns INCONCLUSIVE with a named blocker and degrades its dependent marker; +invalid evidence is neither a compositor crash nor an architectural rejection. +Unexpected implementation failures cannot become PASS. Missing files and +malformed JSON must also produce blockers. No old receipts are overwritten. + +## Required receipt schema and relationships + +| Receipt | Required real content and types | Provenance and semantic relationship | +| --- | --- | --- | +| native-shm | positive integer buffer, rectangle size, positive stride, format, source, identity qualification | Replay actual SHM create/attach/ACK; 420×320, stride 1680, format 1; frame identity remains NOT PROVEN | +| xdg-configure | toplevel rectangle, positive initial/assigned ACK, initial configure, new-buffer boolean, popup qualification | Replay initial 0×0/ACK20 versus assigned 636×573/ACK21; no replacement buffer | +| applied | positive owner/PID/workspace, scene/window rectangles, titlebar, finite scroll, globalclip, generation qualification | Replay IPC/source/applied trace; scene (-320,0,640,600), titlebar25, scroll160, viewport(-800,0,800,600) | +| rendered | content rectangle, bbox, nonnegative integer count, visible intersection, native/destination distinction | Replay native geometry and retained image through existing typed model: (-318,25,636,573), bbox[482,25,800,598), 182214 pixels; output origin, clipping and area agree | +| positioner | integer flags, A/B local rectangles, content origin, predicted A/B, runtime qualification | Source-backed flags0; translate A then B from painted origin; prediction never runtime | +| historical-evidence | complete mandatory input path/hash mapping, diagnostic path, runtime qualification | Actual immutable files must exist; replay source/IPC/calibration/reference inputs are all verified | +| historical-replay | exactly nine unique campaign identities; owner/PID/workspace, origin, A/B/configured, protocol, clip, colors, callbacks, injections, buttons, lifecycle, file inventory, input correlation | Re-execute unchanged replay and R01 correlation on retained logs/IPC/images in a disposable directory, then compare every field, type and value | +| popup-root | nine case/origin/A/clip projections | Exact identity join and projection from independently replayed original data; owner is supplied by that joined replay, not fabricated in the historical root schema | +| popup-nested | nine case/B/protocol/standard/dismissal projections | B→A parent, distinct actual serials, configure/ACK/map and lifecycle replay; same identities/geometries/protocol as historical-replay | +| source-audit | pins, 17 unique source contracts, functions, claims, excerpts, source-version receipt/hash | Mandatory historical contract set; actual source/excerpt/version bytes verified | +| client-provenance | mandatory inputs, patch, adapted source, four protocols, comparison, three builds, C17 command receipts | Retained compilation receipts only; source/output/protocol hashes and flags verified, no new build or execution | +| assertion-register | full source/artifact inventories, AST records, counts, 145 historical assertions | Enumerate current AST and use the existing assertion checker for identities, classification and actual test correlations | +| assertion-coverage | entries, 145 assertions, coverage counts, unresolved list, runtime scope | Recompute from register; partial executed coverage remains explicit | +| offline-core-coverage | status, unproved list | Recompute necessary existing pure model guards from register; no invented runtime coverage | +| test-receipts | complete source inventory, unique actual tests, mode/line/contract observations, statuses | Fresh current code; genuine non-aggregator then negative-suite preflights bootstrap aggregation tests without forcing PASS; the complete mandatory negative test set and final full suite must pass before review | +| independent-review | ACCEPTED, independent=true, exact final source and evidence bindings | Genuine report file/hash, reviewer provenance, executed commands, mandatory independent attack results, findings and limitations; positive unit control has memory-only synthetic acceptance, rejected by the real file-backed writer | + +Rectangles use [x,y,width,height]; PNG bbox uses [x1,y1,x2,y2). +IDs/serials/counts are integers (booleans are invalid), coordinates are finite. +Positive visible counts require positive valid bboxes; count cannot exceed area. +No semantic trust is granted merely because a file hashes to its own receipt. +Re-derived source observations must match, including owner, workspace, camera, +output origin, titlebar, source/destination and input local/global translation. + +The fixed nine-campaign gate is intentionally scenario-specific. Standard +control `clip=null` is required and legitimate. Color `bbox=null` is legitimate +with zero pixels. Nested `standard=null` and `dismissal=null` are legitimate +only where their original scenario did not exercise that operation. Toggle +requires its real standard receipt; close_b requires explicit B close and A +survival in the original replay (the historical nested projection lacks that +field); outside/dismissal scenarios require real done/destruction and absence +of forbidden popup buttons. Unattributable early seat callbacks keep their +explicit NOT AVAILABLE label. Native-frame and atomic-generation qualifications +remain unproved. Missing scenario-required facts are invalid, never filled in. + +## Freshness, mutation tests and independent review + +Bind all fixture source files and all fifteen evidence files. The aggregator +checks the current source bytes and actual loaded evidence against these files; +review must contain the identical bindings. A source or receipt change after +review invalidates acceptance, even if a new matching author hash is supplied. +Inventories with defined mandatory sets reject omitted members. The historical +67/88-entry sealed manifests remain read-only and are never regenerated. + +Order: reproduce R02; correct real aggregate; positive control; deletion, +null/{}/[]/empty-string mutations; cross-receipt contradictions; stale/missing +source/evidence/patch/review bindings; writer test in a private directory; +nine-case replay; new receipts; AST/Shell/Git/inventory; source freeze; +independent review of final bytes; binding verification; verdict; new manifests. +Each original counterexample has its own test and receipt. Tests use independent +deep copies and call the actual aggregate, with no replacement policy/mock. + +One configured Astra/high final-reviewer independently reproduces attacks +without relying on the author's mutation suite. It records files/hashes, +commands/results, limits and ACCEPTED/CORRECTION_REQUIRED/NOT_PROVEN. One +bounded correction and delta review are allowed; unresolved material findings +keep INCONCLUSIVE. An accepted earlier version cannot sign changed bytes. + +Marker dependencies retain the existing geometry/XDG/assertion/regression/ +review groups; any invalid mandatory dependency prevents READY_FOR_NEW_RUNTIME. +READY means only readiness to request authorization in a separate ticket. +Static popup qualification and architectural conclusions are untouched. + +## Results + +Phase A was published at `bbd7da24036f741130b2ff542f429b416fb9c7e5` on both +origin/main and github/main, with a clean worktree at its gate. Phase B remains +local, uncommitted and unpublished. The historical verdict is not superseded. + +`SWAYFX_MOSAIC_STATIC_NESTED_RECEIPT_INTEGRITY_GATE=READY_FOR_NEW_RUNTIME` + +The final genuine independent review is ACCEPTED. The actual file-backed writer, +using that review rather than a synthetic test receipt, returns READY_FOR_NEW_RUNTIME +with all five markers PASS and no blockers. R02 is RESOLVED for this offline +fixture; R01/R03 retain their historical accepted status. No material finding +remains in the bounded review scope. Architecture remains UNDECIDED. + +## Initial independent review and bounded repair + +The initial Astra/high read-only audit rejects the version with B-01: +`write()` lets a JSON decoder RecursionError escape, leaving pre-existing +runner-readiness/offline-gate READY files unchanged. The direct reproduction +uses a 200001-byte rendered.json with 100000 nested arrays. The initial +CORRECTION_REQUIRED report and bindings are retained separately; no acceptance +of changed bytes is implied. + +The authorized repair is limited to treating decoder/encoder RecursionError +as an invalid required receipt, and a real writer regression proving both +old READY artifacts become INCONCLUSIVE. No parser framework, runtime change +or scientific dataset limit is introduced. The current-code offline +validation was rerun and the repaired delta submitted to an independent reviewer. + + +## Final executed evidence and limitations + +Private laboratory: `/home/fy59/swayfx-mosaic-receipt-integrity-cof0umuk/`. +Historical laboratory: `/home/fy59/swayfx-mosaic-static-nested-geometry-replay-a56csvoo/`. +The original `aggregate()` and fixture are retained in `sources-before/`; the +initial reviewed version is in `reviewed-fixture-v1/`, and final code in +`proposed-fixture/`. `raw/phase-B-complete.diff` records the complete local delta. + +| Executed check | Final observed result | +| --- | --- | +| Non-aggregator bootstrap | 145 tests PASS, genuine receipts | +| Mandatory negative bootstrap | 204 tests PASS, genuine receipts | +| Final full offline suite | 206 tests PASS, 474.694 seconds | +| Original R02-A/B/C | 3 separate mutations INCONCLUSIVE with named blockers | +| Field deletion | 2668 mutations INCONCLUSIVE | +| Null / empty object / empty list / empty string | 10604 invalid mutations INCONCLUSIVE; legitimate omissions separately accepted | +| Cross-receipt contradiction | 173 mutations INCONCLUSIVE | +| Stale/missing files, source/patch/review bindings | 30 mutations INCONCLUSIVE | +| Recorded mutation total | 13478 rejected, no recorded invalid mutation READY | +| Positive complete receipt control | READY_FOR_NEW_RUNTIME; SYNTHETIC_IN_MEMORY_ONLY | +| Historical replay | All nine campaigns PASS through unchanged replay/R01 functions | +| AST register | 2371 sites, including 145 frozen historical assertions; 314 positive+negative, 872 positive-only, 1179 unexecuted | +| Pure model coverage | No unresolved pure invariant; partial executed coverage remains explicit | +| Independent final delta | 12 required attacks rejected, one memory positive accepted, three real-writer controls PASS | +| Actual accepted writer | READY_FOR_NEW_RUNTIME, five PASS markers, no blockers; final source/receipt bindings unchanged | + +The initial independent Astra/high report is CORRECTION_REQUIRED due to B-01. +One bounded repair was performed, followed by the read-only Sol/medium delta +review ACCEPTED for the new 18-source/15-receipt bindings. These are distinct +reviews; the older review was never reused as acceptance of changed code. +The final report is `independent-review.md` (SHA-256 +`311af1fe694d1bde88884afa21ef16a4fe908d6cfa47b6cbed515d1a21fedc9e`), +and its receipt is `results/independent-review.json` (SHA-256 +`4d8401978e69a6c54f2f6144ba06deff4952671bc8e1ae3008376ab6ffe01e09`). + +Final source SHA-256 values: + +- `write_results.py`: `9202522721caa1815d3824a3db315c8cdb27e607ce1678907774faa1c9c14c0c`. +- `receipt_integrity.py`: `3f32003f7de74336f2be17e063295d51f683d40936568fd8aebebeb5d3f9b45f`. +- `tests_offline.py`: `0e188fe64d0c3d8a6fda227bf4ef8b31658b86390b5ed73d97d0e3bf851b67d1`. + +Commands and exit codes are retained in `results/commands.json`, reviewer +receipts, final validation receipts and `raw/`. Intermediate author test failures +(including an incomplete assertion inventory and test-bootstrap completeness) +were corrected before freeze and remain preserved. The delta review's first +static-harness attempt exited 1 due to a Path conversion typo; its correction +and successful retry are retained. None is concealed as a successful run. + +The existing offline suite prepared three client-only C17 utilities in disposable +test directories; no client was executed, and neither compositor nor SceneFX +was built. No new image, serial, pointer injection, grab, camera operation, +compositor socket, runtime reservation or installation was produced. Retained +image parsing is offline replay, not capture. Protected production files, +package, cumulative patch and historical sealed manifest entries are unchanged. + +Freshness binds raw receipt bytes as well as canonical parsed content and source +bytes. A formatting change also expires a reviewed file. New source or receipt +bytes require new independent acceptance; matching freshly calculated author +hashes alone cannot preserve a prior ACCEPTED verdict. Source/evidence manifests +are sealed only for this new laboratory after final checks. Historical manifests +remain exactly the published 67/88-entry originals. + +This finite mutation campaign and independent audit support the fixed historical +fixture contract; they do not prove arbitrary future receipt schemas or universal +parser/resource resilience. READY is solely permission-request readiness. +Native-frame identity, atomic transaction generation, GPU-reference lifetime, +new static popup geometry/pixels/input/grabs/dismissal/standard return, and the +remaining output/damage/effects/ABI matrix remain NOT TESTED or NOT PROVEN as +previously documented. No architectural or UX decision follows. STEP20C stays +NEXT and production multi-output stays NOT YET SUPPORTED. The smallest next +step is a separately authorized static-popup runtime ticket; this ticket +performs no such run. + +## Historical receipt-integrity evidence freeze — 2026-10-13 + +Ticket `SWAYFX_MOSAIC_STEP20C_RECEIPT_FREEZE_AND_STATIC_NESTED_RUNTIME_V1`, +phase A, preserves the accepted R02 version for publication. Statements above +about local/unpublished phase B and absent runtime authorization describe the +previous offline ticket. This freeze changes no reviewed fixture byte and no +sealed laboratory file; the separate runtime handoff requires its own evidence, +authorization and independent acceptance. + +The 76 source entries and 199 evidence entries in the receipt-integrity LAB +were individually verified. Manifest SHA-256 values are respectively +`dbd96db3f19f6eb60fc8be13c62b1e71944a759b481d008a7825b3720cf525ce` and +`f00ca54e39bff031a5bda585ea52cfebe9239e71b4c7ee7dc5c41a930e1598b3`. +The final report and review receipt hashes remain exactly those recorded above. +All 18 source and 15 mandatory receipt bindings match independent acceptance. +The actual aggregate was freshly called: READY_FOR_NEW_RUNTIME, five PASS +markers, no blocker. The three original R02 counterexamples were freshly +rejected; a disposable real-writer B-01 check replaced both prior READY files +with INCONCLUSIVE. No synthetic independent acceptance was persisted. + +The retained 206-test, nine-replay and 13478-rejected-mutation results were +verified from their immutable receipts, not relabelled as a newly executed full +suite. Python AST, Markdown links, complete diff including untracked files, +310 protected paths and `git diff --check` passed. No runtime, build, capture, +client execution, socket or reservation belongs to phase A. Historical initial +CORRECTION_REQUIRED and final ACCEPTED reviews remain separate. Architecture +UNDECIDED; static popup runtime NOT TESTED; STEP20C NEXT, production multi-output +NOT YET SUPPORTED. diff --git a/tooling/probes/step20c-static-nested-geometry-replay/README.md b/tooling/probes/step20c-static-nested-geometry-replay/README.md index 27e1fe9..3eaa908 100644 --- a/tooling/probes/step20c-static-nested-geometry-replay/README.md +++ b/tooling/probes/step20c-static-nested-geometry-replay/README.md @@ -125,7 +125,7 @@ forbidden action is separated from earlier legitimate visible buttons and its actual observed events feed dismissal. Corrupted coordinates, recipients, extra invisible buttons and button order fail closed. -R02: `aggregate` is the single gate implementation used by writer and tests. +Historical R02 disposition (published freeze): `aggregate` was the single gate implementation used by writer and tests. SHM, XDG/configured geometry, source contracts/pins, client provenance/C17, all nine historical cases, register/coverage/core and test receipts are mandatory and checked for freshness and structural presence. The bounded @@ -151,7 +151,7 @@ remain inventoried. Re-run the offline sequence after source changes before handing the final unsealed fixture to the parent for bounded review. -## Final offline disposition — INCONCLUSIVE / NOT READY +## Historical offline disposition — INCONCLUSIVE / NOT READY The independent tester ran195 tests with0 failures and all9 historical cases. The bounded independent reviewer ran65 targeted tests PASS and resolved R01/R03, @@ -167,3 +167,46 @@ The four raw technical PASS labels do not prove effective receipt completeness. The fixture is prepared but NOT READY; run.py must remain unexecuted. The next step is a separately scoped offline correction of aggregate's semantic-content guards and their negative regressions. No new runtime/budget is authorized. + +## Local R02 semantic integrity correction + +See [the receipt contract](../../../docs/design/STEP20C_STATIC_NESTED_RECEIPT_INTEGRITY_GATE.md). +`aggregate()` remains the single decision function used by `write()` and tests. +`receipt_integrity.py` replays the original data through existing geometry, +protocol and R01 correlation functions in disposable offline directories, and +compares every typed receipt field. It uses the existing AST checker and checks +complete mandatory file inventories. Null clips in standard controls, zero-color +null bboxes and scenario-specific absent standard/dismissal observations remain +legitimate; required toggle/close/dismissal observations cannot be deleted. +No historical receipt, raw log, image or sealed manifest is rewritten. + +Bindings cover all fixture sources, all mandatory receipt file bytes and the +canonical parsed content. A changed source/receipt loses prior acceptance. +Unit M0 acceptance is synthetic, explicitly memory-only. The real writer only +reads the external independent review; it never signs acceptance. Final readiness +is determined by that exact bound review and the genuine test receipts. + +In a new offline LAB, run the existing replay, run `tests_offline.py LAB --preflight`, +then `assertions.py LAB`. Copy these genuine receipts under `LAB/preflight/results/` +before running `tests_offline.py LAB --negative-preflight`. Regenerate the AST +receipts and replace the new LAB preflight snapshot with these genuinely executed +negative-suite receipts, then run `tests_offline.py LAB` for the final validation. +This prevents circular test bootstrapping +without forcing PASS or substituting old source hashes. Regenerate the final +AST receipts, freeze source/evidence bytes, and request independent review. +Initial preflight covers non-aggregator tests. Negative preflight collects all +required negative test IDs without claiming M0 readiness. Final validation uses +that complete snapshot and must pass its positive controls as well. The suite +executes the R02/mutation/writer tests. No synthetic review file is written. +All generated output belongs outside Git. These are Python/offline operations +only; no runtime command or ledger initialization is authorized. + +The historical INCONCLUSIVE verdict above remains frozen. The local semantic +gate can establish only readiness to request a separate future authorization, +never static popup success. R01/R03 and the runner are unchanged. Phase B is +local, uncommitted and unpublished. + +The bounded B-01 repair treats JSON decoder/encoder recursion exhaustion as +malformed required evidence. The real writer must replace pre-existing READY +artifacts with INCONCLUSIVE; decoder failure never retains a qualified gate. +This is receipt error handling, not a compositor/SceneFX rejection. diff --git a/tooling/probes/step20c-static-nested-geometry-replay/receipt_integrity.py b/tooling/probes/step20c-static-nested-geometry-replay/receipt_integrity.py new file mode 100644 index 0000000..e60a20b --- /dev/null +++ b/tooling/probes/step20c-static-nested-geometry-replay/receipt_integrity.py @@ -0,0 +1,314 @@ +"""Semantic checks for the fixed, retained offline campaign; no runtime action. + +WHY: self-consistent labels/hashes cannot establish observations. Reuse the +qualified replay, input correlation and AST checker instead of a second model. +CONTRACT: historical inputs are read-only; all checker output is disposable. +INVARIANT: cached replay is bound to sealed input bytes, never caller receipts. +""" +from functools import lru_cache +import hashlib +import json +import math +from pathlib import Path +import tempfile + +from support import sha + +HERE = Path(__file__).resolve().parent +SEALED = Path('/home/fy59/swayfx-mosaic-static-nested-geometry-replay-a56csvoo') +SEALS = {'source-sha256.txt': 'e1230ba1292f12d913b762f0678e638a2effb513b74eb44dd232c12aa4acea1d', + 'evidence-sha256.txt': '22b9ac035da95ba914c28d72d8aa6f019c16c2854095d81ceb62c0bd10da8dbe'} +REVIEW_ATTACKS = {'R02-A','R02-B','R02-C','root-empty','nested-empty','input-partial', + 'render-incoherent','standard-missing','dismissal-missing', + 'serial-contradiction','source-stale','old-review-bindings'} + + +def check(ok, reason): + if not ok: + raise ValueError(reason) + + +def encoded(value): + return json.dumps(value, sort_keys=True, separators=(',', ':'), allow_nan=False) + + +def same(actual, expected, path='receipt'): + """Types matter: Python's True == 1 must not accept a serial or count.""" + check(type(actual) is type(expected), path + ': wrong type') + if isinstance(expected, dict): + check(set(actual) == set(expected), path + ': missing/unexpected fields') + for key in expected: + same(actual[key], expected[key], path + '.' + key) + elif isinstance(expected, list): + check(len(actual) == len(expected), path + ': incomplete observations') + for index, value in enumerate(expected): + same(actual[index], value, path + '[' + str(index) + ']') + else: + if isinstance(actual, float): + check(math.isfinite(actual), path + ': nonfinite coordinate') + check(actual == expected, path + ': contradicts original observation') + + +def fresh(mapping, hash_file, required=None): + check(isinstance(mapping, dict) and bool(mapping), 'missing hash inventory') + if required is not None: + check(set(mapping) == set(required), 'incomplete mandatory inventory') + for path, digest in mapping.items(): + check(isinstance(path, str) and bool(path), 'invalid input path') + check(isinstance(digest, str) and len(digest) == 64 and + all(c in '0123456789abcdef' for c in digest), 'missing/invalid SHA-256') + check(hash_file(path) == digest, 'missing/stale file: ' + path) + + +@lru_cache(maxsize=1) +def sealed_receipts(): + """Bootstrap only the immutable historical source/provenance contracts. + + The fixed manifests authenticate the campaign used by this bounded gate. + This does not accept derived semantics: geometry/input are replayed below. + """ + for name, digest in SEALS.items(): + check(sha(SEALED / 'results' / name) == digest, 'historical seal changed') + rows = (SEALED / 'results' / name).read_text().splitlines() + check(len(rows) == (67 if name.startswith('source') else 88), 'seal inventory incomplete') + for row in rows: + expected, path = row.split(' ', 1) + check(sha(path) == expected, 'sealed historical file changed: ' + path) + return {name: json.loads((SEALED / 'results' / (name + '.json')).read_text()) + for name in ('source-audit', 'client-provenance', 'historical-replay', 'historical-evidence')} + + +def historical_inputs(): + from replay import NESTED, HISTORICAL + old = sealed_receipts() + expected = dict(old['historical-evidence']['inputs']) + manifest = HISTORICAL / 'results/evidence-sha256.txt' + check(sha(manifest) == '5750d1e74636c2d276800f795ed0214c3e12999908149855af0b382a2da9bfb4', + 'original geometry evidence seal changed') + sealed_raw = {path: digest for digest,path in + (line.split(' ',1) for line in manifest.read_text().splitlines())} + # Replay also reads these original IPC/calibration/reference observations. + extra = [str(HISTORICAL / rel) for rel in ( + 'results/camera-calibration.json', 'results/E1-applied-snapshot.json', + 'results/geometry-fixture-diagnostic.json', 'run/H2-reference-1.png', 'run/H2-reference-2.png')] + for path in extra:expected[path]=sealed_raw[path] + for row in old['historical-replay']['cases']: + expected.update({str(NESTED / row['case'] / name): digest for name,digest in row['files'].items()}) + fresh(expected, sha) + return dict(sorted(expected.items())) + + +@lru_cache(maxsize=1) +def rederived(input_key): + """Bounded cache: one replay, keyed by every raw input's real SHA-256. + + Rechecking hashes before lookup prevents a stale cache from authorizing + changed observations. No subprocess, client or compositor is executed. + """ + from replay import replay_native, replay_nested + with tempfile.TemporaryDirectory(prefix='mosaic-receipt-replay-') as tmp: + root = Path(tmp) + (root / 'results').mkdir() + replay_native(root) + replay_nested(root) + values = {p.stem: json.loads(p.read_text()) for p in (root / 'results').glob('*.json')} + # The positioner is a source prediction, separate from historical configure. + values['positioner'] = dict(status='PASS source-backed prediction', flags=0, + A=[100,80,350,170], B=[100,60,300,130], predicted_content_origin=[-318,25], + predicted_A=[-218,105,350,170], predicted_B=[-118,165,300,130], + runtime='NOT TESTED; actual configure/ACK/map required') + return values + + +@lru_cache(maxsize=16) +def checked_historical(name, receipt_key, input_key): + same(json.loads(receipt_key), rederived(input_key)[name], name) + + +def validate_historical(name, data, hash_file, inputs=None): + inputs = historical_inputs() if inputs is None else inputs + # Exact typed comparisons include all scenario-specific observations, not + # just lists/status. Standard/null/zero-color cases follow the real replay. + checked_historical(name, encoded(data), tuple(inputs.items())) + + +def validate_source_audit(data, hash_file): + expected = sealed_receipts()['source-audit'] + same(data, expected, 'source-audit') + for contract in data['contracts']: + fresh({contract['source']: contract['sha256']}, hash_file) + lines = Path(contract['source']).read_text().splitlines() + for excerpt in contract['excerpts']: + check(excerpt['token'] in lines[excerpt['line'] - 1], 'source excerpt token/line mismatch') + check(excerpt['text'] in '\n'.join(lines), 'source excerpt absent') + fresh({data['source_version_receipt']: data['source_version_sha256']}, hash_file) + + +def validate_client(data, hash_file): + expected = sealed_receipts()['client-provenance'] + same(data, expected, 'client-provenance') + fresh(data['inputs'], hash_file, expected['inputs']) + fresh({str(HERE / 'placement.patch'): data['patch']}, hash_file) + builds = {b['client']: b for b in data['builds']} + for command in data['commands']: + check(command['exit'] == 0, 'historical C17 command failed') + label, argv = command['label'], command['argv'] + if label.endswith('-compile') and label[:-8] in builds: + build = builds[label[:-8]] + source = next(v for v in argv if v.endswith('/' + label[:-8] + '.c')) + output = argv[argv.index('-o') + 1] + fresh({source: build['source'], output: build['binary']}, hash_file) + fresh({str(Path(source).parent / n): v for n, v in data['protocols'].items()}, hash_file) + + +def validate_tests(data, hash_file): + check(set(data)=={'status','source_inventory','tests'}, 'missing test receipt fields') + files = {str(p) for p in HERE.iterdir() if p.suffix in ('.py', '.patch', '.json')} + fresh(data['source_inventory'], hash_file, files) + tests = data['tests'] + check(isinstance(tests, list) and bool(tests), 'missing executed tests') + ids = set() + # WHY: M0 must consume a genuinely completed negative suite. Excluding the + # two positive controls avoids a circular requirement for their own PASS. + import tests_offline + mandatory = {'Case.' + n for n in dir(tests_offline.Case) + if n.startswith('test_') and n not in ( + 'test_positive_aggregate_complete_bound','test_integrity_legitimate_omissions')} + allowed={'Case.'+n for n in dir(tests_offline.Case) if n.startswith('test_')} + for test in tests: + check(isinstance(test, dict), 'malformed test receipt') + check(test['status'] == 'PASS' and test['mode'] in ('positive', 'negative'), 'failed/unclassified test') + check(isinstance(test['id'], str) and '.Case.test_' in test['id'], 'invalid test identity') + check(test['id'] not in ids, 'duplicate test identity') + ids.add(test['id']) + check(test['id'].split('.Case.')[0] in ('__main__','tests_offline') and + test['id'][test['id'].index('Case.'):] in allowed,'unknown executed test identity') + for field in ('executed_lines', 'failed_lines', 'contracts'): + check(isinstance(test[field], list), 'missing test trace: ' + field) + check(bool(test['executed_lines']) and all(isinstance(line,str) and bool(line) + for line in test['executed_lines']), 'empty executed test trace') + for c in test['contracts']: + check(isinstance(c['contract'], str) and bool(c['contract']) and type(c['pass']) is bool, + 'invalid executed contract') + check(mandatory <= {n[n.index('Case.'):] for n in ids}, 'incomplete mandatory negative suite') + + +@lru_cache(maxsize=1) +def checked_register(key): + """Use the original qualified AST checker, writing only to a temp LAB.""" + import assertions + evidence = json.loads(key) + with tempfile.TemporaryDirectory(prefix='mosaic-receipt-register-') as tmp: + lab = Path(tmp) + (lab / 'results').mkdir() + for name, data in evidence.items(): + (lab / 'results' / (name + '.json')).write_text(json.dumps(data)) + # WHY: checking existing AST keys alone allowed null or misleading + # derived classification/coverage fields. Regenerate every record + # through the same classifier and executed-receipt logic as the writer + # workflow, then compare the full caller-provided register and core. + coverage=assertions.build(lab) + generated=json.loads((lab/'results/assertion-register.json').read_text()) + same(evidence['assertion-register'],generated,'assertion-register effective content') + same(evidence['offline-core-coverage'],json.loads((lab/'results/offline-core-coverage.json').read_text()),'offline-core-coverage effective content') + return coverage + + +def validate_register(name, evidence, hash_file): + import assertions + register = evidence['assertion-register'] + check(set(register)=={'status','source_inventory','artifact_inventory','records','counts','historical_asserts'}, + 'missing register receipt fields') + same(register['historical_asserts'],145,'historical assertion count') + same(register['status'],'CLASSIFIED; execution status is per-entry','register classification scope') + paths = [assertions.HISTORY / n for n in assertions.HISTORICAL_FILES] + [ + p for p in sorted(HERE.glob('*.py')) if p.name != 'tests_offline.py'] + required = {str(p.relative_to(assertions.REPO)) for p in paths} + check(set(register['source_inventory']) == required, 'incomplete AST source inventory') + fresh({str(assertions.REPO / p): v for p,v in register['source_inventory'].items()}, hash_file) + artifacts = {str(p) for p in HERE.iterdir() if p.suffix in ('.patch', '.json')} + fresh(register['artifact_inventory'], hash_file, artifacts) + selected = {n: evidence[n] for n in ('assertion-register', 'test-receipts', 'offline-core-coverage')} + coverage = checked_register(encoded(selected)) + records = register['records'] + counts = {kind: sum(r['kind'] == kind for r in records) for kind in sorted({r['kind'] for r in records})} + same(register['counts'], counts, 'AST counts') + same(evidence['assertion-coverage'], coverage, 'assertion-coverage') + core = {'model.py', 'ledger.py', 'authorization.py', 'observations.py', 'pixels.py'} + unproved = [r['id'] for r in records if Path(r['source']).name in core and + r['kind'] == 'checked invariant' and not + (r['positive_executed_test_ids'] and r['negative_executed_test_ids'])] + same(evidence['offline-core-coverage'], dict(status='PASS' if not unproved else 'FAIL NOT PROVEN', unproved=unproved), 'offline-core-coverage') + check(not unproved and coverage['unresolved_pure_invariants'] == [], 'necessary pure coverage unproved') + + +def make_bindings(evidence, evidence_files=None): + """Canonical content digest supports M0 in memory and binds real receipts. + + WHY: raw file hashes alone cannot bind a caller's mutated parsed object. + File hashes remain separate; formatting changes after review also expire it. + """ + from write_results import REQUIRED + result = dict(source_delta={p.name: sha(p) for p in HERE.iterdir() if p.is_file()}, + evidence_content={n: hashlib.sha256(encoded(evidence[n]).encode()).hexdigest() for n in REQUIRED}) + if evidence_files is not None: + result['evidence_files'] = {n: str(p) for n,p in evidence_files.items()} + result['evidence_sha256'] = {n: sha(p) for n,p in evidence_files.items()} + else: + # Memory-only tests bind canonical bytes, not imaginary evidence files. + result['evidence_sha256'] = dict(result['evidence_content']) + return result + + +def validate_review(evidence, bindings, hash_file): + from write_results import REQUIRED + check(isinstance(bindings, dict), 'missing final bindings') + current = make_bindings(evidence) + same(bindings['source_delta'], current['source_delta'], 'review final sources') + fresh({str(HERE / n): v for n,v in bindings['source_delta'].items()}, hash_file) + same(bindings['evidence_content'], current['evidence_content'], 'review final receipt content') + check(set(bindings['evidence_sha256']) == set(REQUIRED), 'missing evidence bindings') + if 'evidence_files' in bindings: + check(set(bindings['evidence_files']) == set(REQUIRED), 'missing evidence paths') + for name in REQUIRED: + path = bindings['evidence_files'][name] + fresh({path: bindings['evidence_sha256'][name]}, hash_file) + same(json.loads(Path(path).read_text()), evidence[name], 'loaded proof ' + name) + else: + same(bindings['evidence_sha256'], current['evidence_content'], 'memory evidence hashes') + review = evidence['independent-review'] + check(review['status'] == 'ACCEPTED' and review['independent'] is True, 'review not independently ACCEPTED') + for key, value in bindings.items(): + same(review[key], value, 'review not bound to final source/evidence hashes: ' + key) + if 'evidence_files' not in bindings: + # CONTRACT: only the in-memory positive control may use synthetic + # acceptance. The writer always supplies evidence_files and cannot + # turn this test receipt into independent publication authority. + check(review['scope']=='SYNTHETIC_IN_MEMORY_ONLY','unqualified memory review') + return + check(review['scope']=='FINAL_OFFLINE_RECEIPT_INTEGRITY','synthetic/incorrect persisted review scope') + check(review['reviewer_role'] in ('final-reviewer','reviewer') and + isinstance(review['reviewer_agent'],str) and bool(review['reviewer_agent']) and + review['read_only'] is True,'missing independent reviewer provenance') + fresh({review['report_file']:review['report_sha256']},hash_file) + report=Path(review['report_file']).read_text() + check('RECEIPT_INTEGRITY_REVIEW=ACCEPTED' in report,'report does not accept final version') + commands=review['commands'] + check(isinstance(commands,list) and bool(commands),'missing executed review commands') + for command in commands: + check(isinstance(command['argv'],list) and bool(command['argv']) and + all(isinstance(a,str) and bool(a) for a in command['argv']) and + type(command['exit_code']) is int and command['exit_code']==0, + 'review command absent/failed') + mutations=review['mutations'] + check(isinstance(mutations,dict) and REVIEW_ATTACKS<=set(mutations),'incomplete independent mutation set') + for name in REVIEW_ATTACKS: + result=mutations[name] + check(result['status']=='INCONCLUSIVE' and isinstance(result['blockers'],list) and + bool(result['blockers']) and all(isinstance(b,str) and bool(b) for b in result['blockers']), + 'independent attack not rejected: '+name) + check(isinstance(review['findings'],list) and + all(isinstance(f,dict) and f.get('severity') in ('NON_BLOCKING','FUTURE_SCOPE') for f in review['findings']), + 'material independent finding unresolved') + check(isinstance(review['limitations'],list) and bool(review['limitations']) and + all(isinstance(s,str) and bool(s) for s in review['limitations']), 'review scope absent') diff --git a/tooling/probes/step20c-static-nested-geometry-replay/tests_offline.py b/tooling/probes/step20c-static-nested-geometry-replay/tests_offline.py index 2f9aa8c..4b5ea4e 100644 --- a/tooling/probes/step20c-static-nested-geometry-replay/tests_offline.py +++ b/tooling/probes/step20c-static-nested-geometry-replay/tests_offline.py @@ -20,6 +20,9 @@ from support import save HERE=Path(__file__).resolve().parent RECEIPTS=[] +FIXTURE_SNAPSHOT=None +BASELINE_CHECKED=False +BOOTSTRAP=False ACTIVE=None TRACKED={str(p.resolve()) for p in HERE.glob('*.py')} LINES={} @@ -331,8 +334,8 @@ class Case(unittest.TestCase): auth['registered_lab']='wrong';f.write_text(json.dumps(auth));self.reject('registered-fresh-lab-required',lambda:authorize(root,f)) def test_negative_offline_lab_authorization(self): with tempfile.TemporaryDirectory(prefix='geometry-auth-test-') as tmp: - root=Path(tmp);f=root/'auth.json';f.write_text(json.dumps(dict(scope='one-new-static-nested-headless-run',explicit_user_authorization=True,ticket='synthetic-future-unit-test',registered_lab=str(LAB.resolve())))) - self.reject('historical-or-offline-lab-forbidden',lambda:authorize(LAB,f)) + root=Path(tmp);f=root/'auth.json';f.write_text(json.dumps(dict(scope='one-new-static-nested-headless-run',explicit_user_authorization=True,ticket='synthetic-future-unit-test',registered_lab=str(HISTORICAL.resolve())))) + self.reject('historical-or-offline-lab-forbidden',lambda:authorize(HISTORICAL,f)) def test_negative_ledger_sequences(self): with tempfile.TemporaryDirectory(prefix='geometry-ledger-test-') as tmp: p=Path(tmp)/'budget.json';initial_for_offline_test(p);reserve(p,'click');state=json.loads(p.read_text());state['events'][0]['seq']=5 @@ -455,40 +458,41 @@ class Case(unittest.TestCase): data,recorded=self.exchange(invocation=True);self.assertIsNone(data['ipc_exit']) self.assertIn('ipc_error',data);self.assertFalse(data['scientific_cleanup_pass']);self.assertEqual(recorded,[]) + def test_receipt_fail_closed_empty(self): + from write_results import aggregate + result=aggregate({},lambda path:None,{}) + self.assertEqual(result['status'],'INCONCLUSIVE') + self.assertTrue(result['blockers']) + + def test_receipt_fail_closed_status_only(self): + from write_results import aggregate,REQUIRED + result=aggregate({n:{'status':'PASS'} for n in REQUIRED},lambda path:None,{}) + self.assertEqual(result['status'],'INCONCLUSIVE') + self.assertEqual(set(result['markers'].values()),{'INCONCLUSIVE'}) + def aggregate_fixture(self): from write_results import REQUIRED,aggregate from support import sha - evidence={n:json.loads((LAB/'results'/(n+'.json')).read_text()) for n in REQUIRED} - # CONTRACT: all acceptance here is a synthetic complete snapshot in memory. - # The real receipts are written only after the entire suite finishes. - evidence['test-receipts']['status']='PASS' - for receipt in evidence['test-receipts']['tests']:receipt['status']='PASS' - # CONTRACT: this is synthetic independent acceptance in memory only. - bindings={'source_delta':{'fixture':'synthetic-final-hash'},'evidence_sha256':{'proof':'synthetic-proof-hash'}} - evidence['independent-review']=dict(status='ACCEPTED',independent=True,**bindings) - # INVARIANT: immutable synthetic hash provider avoids bootstrapping current - # test receipts from the suite that is still producing those receipts. - # WHY: source inventories in this synthetic snapshot must describe the - # same bytes even while the real on-disk register awaits regeneration. - evidence['test-receipts']['source_inventory']={n:sha(n) for n in evidence['test-receipts']['source_inventory']} - evidence['assertion-register']['source_inventory']={n:sha(HERE.parents[2]/n) for n in evidence['assertion-register']['source_inventory']} - hashes={} - for name in ('historical-evidence','client-provenance','test-receipts'): - hashes.update(evidence[name].get('inputs',{}));hashes.update(evidence[name].get('source_inventory',{})) - reg=evidence['assertion-register'];hashes.update({str(HERE.parents[2]/n):v for n,v in reg['source_inventory'].items()});hashes.update(reg['artifact_inventory']) - audit=evidence['source-audit'];hashes.update({c['source']:c['sha256'] for c in audit['contracts']}) - hashes[audit['source_version_receipt']]=audit['source_version_sha256'] - for case in evidence['historical-replay']['cases']: - hashes.update({str(NESTED/case['case']/n):v for n,v in case['files'].items()}) - client=evidence['client-provenance'];hashes[str(HERE/'placement.patch')]=client['patch'] - builds={v['client']:v for v in client['builds']} - for command in client['commands']: - label=command['label'];argv=command['argv'] - if label.endswith('-compile') and label[:-8] in builds: - build=builds[label[:-8]];source=next(v for v in argv if v.endswith('/'+label[:-8]+'.c')) - hashes[source]=build['source'];hashes[argv[argv.index('-o')+1]]=build['binary'] - hashes.update({str(Path(source).parent/n):v for n,v in client['protocols'].items()}) - return aggregate,evidence,lambda path:hashes.get(str(path)),bindings + from receipt_integrity import make_bindings + # WHY: bootstrap with genuine current preflight receipts, collected + # before aggregation tests, not forced statuses or obsolete hashes. + root=LAB/'preflight'/'results' + global FIXTURE_SNAPSHOT + import pickle,copy + if FIXTURE_SNAPSHOT is None: + evidence={n:json.loads((root/(n+'.json')).read_text()) for n in REQUIRED} + FIXTURE_SNAPSHOT=(pickle.dumps(evidence),make_bindings(evidence)) + # Separate object graphs for every mutation. The serialization is an + # in-memory copy of our own JSON, never untrusted external pickle input. + evidence=pickle.loads(FIXTURE_SNAPSHOT[0]) + bindings=copy.deepcopy(FIXTURE_SNAPSHOT[1]) + # CONTRACT: synthetic acceptance exists in this independent memory + # snapshot only. It is never persisted as the real review. + evidence['independent-review']=dict(status='ACCEPTED',independent=True,scope='SYNTHETIC_IN_MEMORY_ONLY',**__import__('copy').deepcopy(bindings)) + def safe_hash(path): + try:return sha(path) + except (OSError,ValueError):return None + return aggregate,evidence,safe_hash,bindings def test_positive_aggregate_complete_bound(self): fn,e,h,b=self.aggregate_fixture();result=fn(e,h,b) @@ -576,24 +580,237 @@ for gate in REQUIRED: current=copy.deepcopy(original);current['evidence_sha256'][gate]='changed-proof' result=fn(e,h,current) self.assertEqual(result['status'],'INCONCLUSIVE') - self.assertTrue(any('review not bound' in x for x in result['blockers'])) + self.assertTrue(any(x.startswith('independent-review:') for x in result['blockers'])) setattr(Case,'test_negative_aggregate_'+gate.replace('-','_')+'_stale_review_binding',test) +def integrity_negative(self, label, mutate, group, category): + import copy + from receipt_integrity import make_bindings + fn,e,h,b=self.aggregate_fixture() + global BASELINE_CHECKED + if not BOOTSTRAP and not BASELINE_CHECKED: + self.assertEqual(fn(e,h,b)['status'],'READY_FOR_NEW_RUNTIME','mutation baseline must be complete') + BASELINE_CHECKED=True + mutate(e) + # Rebind the synthetic memory-only review to the mutation so the semantic + # checker itself must reject it; stale-review rejection alone is inadequate. + if group!='independent-review': + import hashlib + from receipt_integrity import encoded + b=copy.deepcopy(b) + b['evidence_content'][group]=hashlib.sha256(encoded(e[group]).encode()).hexdigest() + b['evidence_sha256'][group]=b['evidence_content'][group] + e['independent-review']=dict(status='ACCEPTED',independent=True,scope='SYNTHETIC_IN_MEMORY_ONLY',**copy.deepcopy(b)) + r=fn(e,h,b) + self.assertEqual(r['status'],'INCONCLUSIVE',label) + self.assertTrue(any(x.startswith(group+':') for x in r['blockers']),(label,r['blockers'])) + for marker,dependencies in r['marker_scope'].items(): + if group in dependencies:self.assertEqual(r['markers'][marker],'INCONCLUSIVE',label) + MUTATIONS.append(dict(label=label,category=category,gate=group,status=r['status'],blockers=r['blockers'],markers=r['markers'])) + + +MUTATIONS=[] +def original_R02(self, name): + def mutate(e): + if name=='A':e['historical-replay']['cases'][0]['input_correlation']={'status':'PASS'} + elif name=='B': + for k in ('content','PNG_bbox','count','visible_intersection'):e['rendered'][k]=None + else:e['popup-root']['cases']=[{} for _ in range(9)] + integrity_negative(self,'R02-'+name,mutate,{'A':'historical-replay','B':'rendered','C':'popup-root'}[name],'original') + +for letter in 'ABC': + def test(self,letter=letter):original_R02(self,letter) + setattr(Case,'test_integrity_R02_'+letter,test) + + +def essential_paths(evidence): + """Required fields, including scenario-specific nested original facts. + + All root/nested observations are inspected in all nine cases. Empty + failed_lines/contracts and scenario nulls are legitimate and not mutated + into the same value; deleting their schema key still must be rejected. + """ + for gate in REQUIRED: + for key in evidence[gate]:yield gate,[key] + for gate in ('historical-replay','popup-root','popup-nested'): + for index,row in enumerate(evidence[gate]['cases']): + def walk(value,path): + if isinstance(value,dict): + for k,v in value.items(): + yield path+[k] + # File inventories are complete mappings, not samples. + yield from walk(v,path+[k]) + elif isinstance(value,list) and value and isinstance(value[0],dict): + for i,v in enumerate(value):yield from walk(v,path+[i]) + for path in walk(row,['cases',index]):yield gate,path + + +def access(e,gate,path): + node=e[gate] + for part in path[:-1]:node=node[part] + return node,path[-1] + + +def test_deletions(self): + # WHY: bulk mutation receipts prove returned verdicts, not line coverage. + # Individual R02/positive tests keep tracing; suppressing tracing here + # avoids charging repeated read/hash loops as scientific branch proof. + sys.settrace(None) + _,e,_,_=self.aggregate_fixture() + for gate,path in essential_paths(e): + def mutate(d,gate=gate,path=path): + parent,key=access(d,gate,path);del parent[key] + integrity_negative(self,gate+'/'+str(path),mutate,gate,'deletion') + + +def test_nullifications(self): + sys.settrace(None) + _,e,_,_=self.aggregate_fixture() + for gate,path in essential_paths(e): + parent,key=access(e,gate,path);original=parent[key] + for value in (None,{},[], ''): + if type(value) is type(original) and value==original:continue + def mutate(d,gate=gate,path=path,value=value): + parent,key=access(d,gate,path);parent[key]=value + integrity_negative(self,gate+'/'+str(path)+'='+repr(value),mutate,gate,'nullification') + + +def test_contradictions(self): + sys.settrace(None) + attacks=[('rendered',['content',3],320),('rendered',['content',1],0), + ('rendered',['PNG_bbox',0],483),('rendered',['count'],182215), + ('rendered',['count'],True),('applied',['scroll'],161.0), + ('applied',['globalclip',0],0),('applied',['titlebar'],0), + ('native-shm',['stride'],1280),('native-shm',['buffer'],True), + ('xdg-configure',['assigned_ACK'],20),('positioner',['flags'],1)] + attacks += [('assertion-register',['records',0,'test_status'],None), + ('assertion-register',['records',0,'domain'],'invented classification'), + ('assertion-register',['historical_asserts'],True), + ('test-receipts',['tests',0,'executed_lines'],[]), + ('test-receipts',['tests',0,'id'],'__main__.Case.test_fabricated')] + for index in range(9): + for field,value in [('owner',999),('workspace',999),('origin',[0,0]),('A',[0,0,300,170]),('B',[0,0,300,130]),('case','unknown')]: + attacks.append(('historical-replay',['cases',index,field],value)) + attacks += [('historical-replay',['cases',index,'protocol','grabs',1,'parent'],'TOPLEVEL'), + ('historical-replay',['cases',index,'protocol','grabs',1,'serial'],55), + ('historical-replay',['cases',index,'input_correlation','buttons',0,'target'],'POPUP_B'), + ('historical-replay',['cases',index,'input_correlation','buttons',0,'serial'],999), + ('historical-replay',['cases',index,'input_correlation','buttons',1,'state'],1), + ('historical-replay',['cases',index,'input_correlation','callbacks',-1,'local'],[99999.0,1.0]), + ('historical-replay',['cases',index,'input_correlation','actions',0,'motion_msec'],99999), + ('popup-root',['cases',index,'origin'],[0,0]), + ('popup-root',['cases',index,'clip'],[0,0,1,1]), + ('popup-nested',['cases',index,'B'],[0,0,300,130]), + ('popup-nested',['cases',index,'protocol','grabs',1,'ack'],999)] + for gate,path,value in attacks: + def mutate(d,gate=gate,path=path,value=value): + parent,key=access(d,gate,path);parent[key]=value + integrity_negative(self,gate+'/'+str(path),mutate,gate,'contradiction') + for gate in ('historical-replay','popup-root','popup-nested'): + integrity_negative(self,gate+' duplicated identity',lambda d,gate=gate:d[gate]['cases'].__setitem__(1,__import__('copy').deepcopy(d[gate]['cases'][0])),gate,'contradiction') + + +def test_freshness(self): + import copy + fn,e,h,b=self.aggregate_fixture() + from receipt_integrity import make_bindings + from support import sha + # A correct source hash replaced by the hash of its own mutated file must + # still lose the previously signed acceptance. + for key in ('source_delta','evidence_sha256','evidence_content'): + current=copy.deepcopy(b);first=next(iter(current[key]));current[key][first]='0'*64 + r=fn(e,h,current);self.assertEqual(r['status'],'INCONCLUSIVE');self.assertTrue(r['blockers']) + MUTATIONS.append(dict(label='stale '+key,category='freshness',status=r['status'],blockers=r['blockers'])) + for gate in REQUIRED: + d=copy.deepcopy(e);d[gate]={};current=make_bindings(d) + # Old acceptance, even with fresh current bindings, is not reusable. + r=fn(d,h,current);self.assertEqual(r['status'],'INCONCLUSIVE') + self.assertTrue(any(x.startswith('independent-review:') for x in r['blockers'])) + for key in ('status','independent','scope','source_delta','evidence_sha256','evidence_content'): + for value in (None,{},[], ''): + integrity_negative(self,'review '+key+'='+repr(value),lambda d,key=key,value=value:d['independent-review'].__setitem__(key,value),'independent-review','freshness') + for file in (str(HERE/'placement.patch'),str(HERE/'receipt_integrity.py'),next(iter(e['historical-evidence']['inputs']))): + r=fn(e,lambda p,file=file:None if str(p)==file else h(p),b) + self.assertEqual(r['status'],'INCONCLUSIVE');self.assertTrue(r['blockers']) + MUTATIONS.append(dict(label='missing '+file,category='freshness',status=r['status'],blockers=r['blockers'])) + + +def test_omissions(self): + fn,e,h,b=self.aggregate_fixture() + self.assertIsNone(e['popup-root']['cases'][4]['clip']) + self.assertIsNone(e['popup-nested']['cases'][0]['standard']) + self.assertIsNone(e['popup-nested']['cases'][2]['dismissal']) + self.assertIsNone(e['historical-replay']['cases'][0]['colors']['right']['POPUP_B']['bbox']) + self.assertEqual(e['historical-replay']['cases'][0]['colors']['right']['POPUP_B']['count'],0) + self.assertEqual(fn(e,h,b)['status'],'READY_FOR_NEW_RUNTIME') + + +def test_writer(self): + import contextlib,io,copy + from write_results import write,aggregate + from receipt_integrity import make_bindings + fn,e,h,b=self.aggregate_fixture() + # CONTRACT: no synthetic acceptance is persisted, even in the writer test. + e['independent-review']={'status':'NOT RUN','independent':False} + with tempfile.TemporaryDirectory(prefix='mosaic-real-writer-') as tmp: + root=Path(tmp);(root/'results').mkdir() + for name,data in e.items():save(root,name,data) + current=make_bindings(e,{n:root/'results'/(n+'.json') for n in REQUIRED}) + expected=aggregate(e,h,current) + with contextlib.redirect_stdout(io.StringIO()):observed=write(root) + self.assertEqual(observed['status'],expected['status']);self.assertEqual(observed['markers'],expected['markers']) + e['rendered']['count']=None;save(root,'rendered',e['rendered']) + with contextlib.redirect_stdout(io.StringIO()):observed=write(root) + self.assertEqual(observed['status'],'INCONCLUSIVE') + self.assertEqual(observed['markers']['STATIC_NESTED_SHM_RENDER_GEOMETRY_REPLAY'],'INCONCLUSIVE') + self.assertTrue(any(x.startswith('rendered:') for x in observed['blockers'])) + # CONTRACT B-01: decoder exhaustion must invalidate persisted READY, + # not merely raise before publication. The fake prior marker is an + # adversarial receipt in this test-owned temp LAB, not signed review. + prior={'status':'READY_FOR_NEW_RUNTIME','marker':'SWAYFX_MOSAIC_STATIC_NESTED_GEOMETRY_OFFLINE_GATE=READY_FOR_NEW_RUNTIME'} + save(root,'runner-readiness',prior);save(root,'offline-gate',prior) + (root/'results/rendered.json').write_text('['*100000+'0'+']'*100000) + with contextlib.redirect_stdout(io.StringIO()):observed=write(root) + self.assertEqual(observed['status'],'INCONCLUSIVE') + self.assertTrue(any(x.startswith('rendered:') for x in observed['blockers'])) + for name in ('runner-readiness','offline-gate'): + persisted=json.loads((root/'results'/(name+'.json')).read_text()) + self.assertEqual(persisted['status'],'INCONCLUSIVE') + self.assertEqual(persisted['markers']['STATIC_NESTED_SHM_RENDER_GEOMETRY_REPLAY'],'INCONCLUSIVE') + save(LAB,'writer-entrypoint-test',dict(status='PASS',aggregate_source=str(Path(aggregate.__code__.co_filename)),mock=False,synthetic_review_persisted=False,invalid=observed['blockers'])) + + +Case.test_integrity_deletions=test_deletions +Case.test_integrity_nullifications=test_nullifications +Case.test_integrity_contradictions=test_contradictions +Case.test_integrity_freshness=test_freshness +Case.test_integrity_legitimate_omissions=test_omissions +Case.test_integrity_writer=test_writer + + class Result(unittest.TextTestResult): def addSuccess(self,test): - super().addSuccess(test);RECEIPTS.append(dict(id=test.id(),status='PASS',mode='negative' if '.test_negative_' in test.id() else 'positive',executed_lines=sorted(LINES.get(test.id(),set())),failed_lines=sorted(FAILED.get(test.id(),set())),contracts=[{'contract':c,'pass':v} for c,v in sorted(CONTRACTS.get(test.id(),set()))])) + super().addSuccess(test);RECEIPTS.append(dict(id=test.id(),status='PASS',mode='negative' if '.test_negative_' in test.id() else 'positive',executed_lines=sorted(LINES.get(test.id(),set())),failed_lines=sorted(FAILED.get(test.id(),set())),contracts=[{'contract':c,'pass':v} for c,v in sorted(CONTRACTS.get(test.id(),set()))]));sys.settrace(trace) def addFailure(self,test,err): - super().addFailure(test,err);RECEIPTS.append(dict(id=test.id(),status='FAIL',error=str(err[1]))) + super().addFailure(test,err);RECEIPTS.append(dict(id=test.id(),status='FAIL',error=str(err[1])));sys.settrace(trace) def addError(self,test,err): - super().addError(test,err);RECEIPTS.append(dict(id=test.id(),status='FAIL',error=str(err[1]))) + super().addError(test,err);RECEIPTS.append(dict(id=test.id(),status='FAIL',error=str(err[1])));sys.settrace(trace) if __name__=='__main__': - parser=argparse.ArgumentParser();parser.add_argument('lab',type=Path);args=parser.parse_args();LAB=args.lab + parser=argparse.ArgumentParser();parser.add_argument('lab',type=Path);parser.add_argument('--preflight',action='store_true');parser.add_argument('--negative-preflight',action='store_true');args=parser.parse_args();LAB=args.lab sys.settrace(trace) - result=unittest.TextTestRunner(verbosity=2,resultclass=Result).run(unittest.defaultTestLoader.loadTestsFromTestCase(Case)) + suite=unittest.defaultTestLoader.loadTestsFromTestCase(Case) + if args.preflight:suite=unittest.TestSuite(t for t in suite if 'aggregate' not in t.id() and 'integrity' not in t.id()) + if args.negative_preflight:suite=unittest.TestSuite(t for t in suite if t._testMethodName not in ('test_positive_aggregate_complete_bound','test_integrity_legitimate_omissions')) + BOOTSTRAP=args.negative_preflight + result=unittest.TextTestRunner(verbosity=2,resultclass=Result).run(suite) sys.settrace(None) save(LAB,'test-receipts',dict(source_inventory={str(p):__import__('hashlib').sha256(p.read_bytes()).hexdigest() for p in HERE.iterdir() if p.suffix in ('.py','.patch','.json')},status='PASS' if result.wasSuccessful() else 'FAIL',tests=RECEIPTS)) save(LAB,'positive',{'tests':[r for r in RECEIPTS if r.get('mode')=='positive']}) save(LAB,'negative',{'tests':[r for r in RECEIPTS if r.get('mode')=='negative']}) + if not args.preflight: + save(LAB,'aggregate-negative',{'status':'PASS' if result.wasSuccessful() else 'FAIL', 'mutations':MUTATIONS}) + for category in ('deletion','nullification','contradiction','freshness','original'): + save(LAB,category+'-tests',{'status':'PASS' if result.wasSuccessful() else 'FAIL','mutations':[r for r in MUTATIONS if r['category']==category]}) raise SystemExit(0 if result.wasSuccessful() else 1) diff --git a/tooling/probes/step20c-static-nested-geometry-replay/write_results.py b/tooling/probes/step20c-static-nested-geometry-replay/write_results.py index 624c758..2a2c86a 100644 --- a/tooling/probes/step20c-static-nested-geometry-replay/write_results.py +++ b/tooling/probes/step20c-static-nested-geometry-replay/write_results.py @@ -20,71 +20,48 @@ def aggregate(evidence,hash_file,bindings): WHY: no author test or successful compile can substitute for another gate. Bindings describe the exact final evidence/source bytes reviewed independently. """ + from receipt_integrity import (validate_historical, validate_source_audit, + validate_client, validate_register, validate_tests, validate_review, historical_inputs, fresh) failures={} def need(name,ok,reason): if not ok:failures.setdefault(name,[]).append(reason) - def fresh(mapping): - return bool(mapping) and all(hash_file(path)==digest for path,digest in mapping.items()) + def guarded(name, validator): + # CONTRACT: invalid/missing evidence degrades its gate, never crashes + # the writer or becomes a compositor/SceneFX architectural failure. + try: + validator() + except (OSError, ValueError, TypeError, KeyError, IndexError, AttributeError, + AssertionError, RuntimeError, StopIteration) as error: + need(name,False,str(error) or type(error).__name__) + if not isinstance(evidence,dict):evidence={} for name in REQUIRED: d=evidence.get(name) - need(name,isinstance(d,dict) and bool(d),'missing evidence') + need(name,isinstance(d,dict) and bool(d),'missing/malformed evidence') if not isinstance(d,dict):continue - status=d.get('status','') - need(name,status=='PASS' or status.startswith('PASS ') - or name=='assertion-register' and status=='CLASSIFIED; execution status is per-entry','gate not PASS') - def record(name):return evidence.get(name) or {} - for name in ('native-shm','xdg-configure','applied','rendered','positioner'): - fields={'native-shm':('size','buffer','stride','format'), 'xdg-configure':('toplevel','initial_ACK','assigned_ACK'), - 'applied':('owner','workspace','scene','window','titlebar','globalclip'), - 'rendered':('content','PNG_bbox','count','visible_intersection'), - 'positioner':('flags','A','B')}[name] - need(name,all(k in record(name) for k in fields),'incomplete geometry proof') - need('historical-evidence',fresh(record('historical-evidence').get('inputs',{})),'stale/missing historical inputs') - cases=record('historical-replay').get('cases',[]) - expected={'run-private_mosaic-crossing-b_cross','run-private_mosaic-crossing-both_cross', - 'run-private_mosaic-toggle-b_cross','run-private_mosaic-outside-visible', - 'run-control-crossing-b_cross','run-control-crossing-both_cross','run-control-outside-visible', - 'run-private_mosaic-close_b-visible','run-control-close_b-visible'} - need('historical-replay',len(cases)==9 and {c.get('case') for c in cases}==expected,'required nine cases incomplete') - for c in cases: - need('historical-replay',c.get('status')=='PASS historical sampled replay' and c.get('input_correlation',{}).get('status')=='PASS','case/input proof incomplete') - need('historical-replay',fresh({str(__import__('replay').NESTED/c.get('case','')/n):v for n,v in c.get('files',{}).items()}),'stale case files') - for name in ('popup-root','popup-nested'): - need(name,len(record(name).get('cases',[]))==9,'incomplete popup proof') - audit=record('source-audit');contracts=audit.get('contracts',[]) - need('source-audit',len(contracts)==17 and all(c.get('claim') and c.get('excerpts') and hash_file(c.get('source',''))==c.get('sha256') for c in contracts),'incomplete/stale source contracts') - need('source-audit',bool(audit.get('pins')) and hash_file(audit.get('source_version_receipt',''))==audit.get('source_version_sha256') and bool(audit.get('source_version_sha256')),'stale pinned receipt') - client=record('client-provenance');commands=client.get('commands',[]) - need('client-provenance',fresh(client.get('inputs',{})),'stale client sources') - need('client-provenance',all(client.get(k) for k in ('patch','adapted_nested','protocols','protocol_comparison','builds')),'incomplete provenance') - needed={n+'-'+mode for n in ('nested-client','pointer-daemon','static-reference') for mode in ('syntax','compile')} - need('client-provenance',needed<={c.get('label') for c in commands} and all(c.get('exit')==0 for c in commands),'C17 commands missing/failed') - for command in commands: - if command.get('label') in needed:need('client-provenance','-std=c17' in command.get('argv',[]) and '-Werror' in command.get('argv',[]),'C17 flags incomplete') - need('client-provenance',hash_file(str(HERE/'placement.patch'))==client.get('patch'),'stale placement patch') - builds={c.get('client'):c for c in client.get('builds',[])} - need('client-provenance',set(builds)=={'nested-client','pointer-daemon','static-reference'},'missing client builds') - for command in commands: - label=command.get('label','');argv=command.get('argv',[]) - if label.endswith('-compile') and label[:-8] in builds: - build=builds[label[:-8]] - output=argv[argv.index('-o')+1] if '-o' in argv else '' - source=next((v for v in argv if v.endswith('/'+label[:-8]+'.c')),'') - need('client-provenance',bool(output) and hash_file(output)==build.get('binary') and bool(source) and hash_file(source)==build.get('source'),'stale built client/source') - directory=Path(source).parent - need('client-provenance',all(hash_file(str(directory/n))==v for n,v in client.get('protocols',{}).items()),'stale generated protocols') - register=record('assertion-register');inventory=register.get('source_inventory',{}) - need('assertion-register',fresh({str(HERE.parents[2]/p):v for p,v in inventory.items()}) and fresh(register.get('artifact_inventory',{})),'stale inventory') - need('assertion-register',register.get('historical_asserts')==145 and bool(register.get('records')),'incomplete register') - coverage=record('assertion-coverage') - need('assertion-coverage',coverage.get('status')=='PASS' and coverage.get('historical_asserts')==145 and coverage.get('entries')==len(register.get('records',[])) and coverage.get('unresolved_pure_invariants')==[],'coverage incomplete') - need('offline-core-coverage',record('offline-core-coverage').get('unproved')==[],'core unproved') - tests=record('test-receipts');receipts=tests.get('tests',[]) - need('test-receipts',fresh(tests.get('source_inventory',{})),'stale test sources') - need('test-receipts',bool(receipts) and all(t.get('status')=='PASS' for t in receipts) and len({t.get('id') for t in receipts})==len(receipts),'missing/failed test receipts') - review=record('independent-review') - need('independent-review',review.get('status')=='ACCEPTED' and review.get('independent') is True,'review not independently ACCEPTED') - need('independent-review',bool(bindings.get('source_delta')) and bool(bindings.get('evidence_sha256')) and all(review.get(k)==v for k,v in bindings.items()),'review not bound to final source/evidence hashes') + status=d.get('status') + need(name,isinstance(status,str) and (status=='PASS' or status.startswith('PASS ') + or name=='assertion-register' and status=='CLASSIFIED; execution status is per-entry'),'gate not PASS') + input_error=None + try: + inputs=historical_inputs() + fresh(inputs,hash_file) + except (OSError,ValueError,TypeError,KeyError,AssertionError) as error: + inputs=None;input_error=str(error) or type(error).__name__ + for name in ('native-shm','xdg-configure','applied','rendered','positioner', + 'historical-evidence','historical-replay','popup-root','popup-nested'): + if input_error:need(name,False,input_error) + else:guarded(name,lambda name=name:validate_historical(name,evidence.get(name),hash_file,inputs)) + # CONTRACT: after a failed prerequisite, unchecked downstream gates are + # INCONCLUSIVE too. This bounds work on invalid input without granting PASS. + def downstream(name,validator): + if failures:need(name,False,'not qualified after failed prerequisite') + else:guarded(name,validator) + downstream('source-audit',lambda:validate_source_audit(evidence.get('source-audit'),hash_file)) + downstream('client-provenance',lambda:validate_client(evidence.get('client-provenance'),hash_file)) + downstream('test-receipts',lambda:validate_tests(evidence.get('test-receipts'),hash_file)) + for name in ('assertion-register','assertion-coverage','offline-core-coverage'): + downstream(name,lambda name=name:validate_register(name,evidence,hash_file)) + downstream('independent-review',lambda:validate_review(evidence,bindings,hash_file)) groups={'STATIC_NESTED_SHM_RENDER_GEOMETRY_REPLAY':('native-shm','applied','rendered','historical-evidence'), 'STATIC_NESTED_XDG_GEOMETRY_AUDIT':('xdg-configure','positioner','historical-replay','popup-root','popup-nested','source-audit','client-provenance'), 'STATIC_NESTED_ASSERTION_COVERAGE':('assertion-register','assertion-coverage','offline-core-coverage'), @@ -99,16 +76,25 @@ def write(lab): results=lab/'results' def read(name): path=results/(name+'.json') - return json.loads(path.read_text()) if path.exists() else {} + try:return json.loads(path.read_text()) if path.exists() else {} + # WHY: a decoder-depth failure must not leave previous READY artifacts + # authoritative. CONTRACT: malformed proof reaches the same aggregate + # as missing proof, which publishes explicit INCONCLUSIVE markers. + except (OSError,ValueError,RecursionError):return {} aliases={'native-shm-geometry':'native-shm','xdg-configure-geometry':'xdg-configure','applied-content-geometry':'applied','rendered-paint-geometry':'rendered','popup-positioner-audit':'positioner','historical-popup-replay':'historical-replay','root-clip-model':'popup-root','nested-clip-model':'popup-nested','positive-tests':'positive','negative-tests':'negative'} for name,source in aliases.items():save(lab,name,read(source)) coverage=read('assertion-coverage');tests=read('test-receipts');core=read('offline-core-coverage') + coverage=coverage if isinstance(coverage,dict) else {} + tests=tests if isinstance(tests,dict) else {} reviewfile=results/'independent-review.json' review=read('independent-review') if reviewfile.exists() else {'status':'NOT RUN','independent':False} evidence={name:read(name) for name in REQUIRED} evidence['independent-review']=review - source_delta={p.name:sha(p) for p in HERE.iterdir() if p.is_file()} - bindings=dict(source_delta=source_delta,evidence_sha256={name:sha(results/(name+'.json')) for name in REQUIRED if (results/(name+'.json')).exists()}) + from receipt_integrity import make_bindings + try: + bindings=make_bindings(evidence,{name:results/(name+'.json') for name in REQUIRED}) + except (OSError,ValueError,TypeError,KeyError,RecursionError): + bindings={} # Missing/malformed proof cannot carry accepted bindings. def safe_hash(path): try:return sha(path) except (OSError,ValueError):return None @@ -117,7 +103,10 @@ def write(lab): save(lab,'popup-geometry-model',dict(status=markers['STATIC_NESTED_XDG_GEOMETRY_AUDIT'],scope='offline typed model and sampled historical replay; new runtime NOT TESTED',root=read('popup-root'),nested=read('popup-nested'),prediction=read('positioner'),source='source-audit.json',native_input='Opening click must lie in actual native surface bounds; destination scaling alone is insufficient')) readiness=dict(status=status,marker='SWAYFX_MOSAIC_STATIC_NESTED_GEOMETRY_OFFLINE_GATE='+status,markers=markers,marker_scope=gates['marker_scope'],review_bindings=bindings,blockers=blockers,runtime='NOT TESTED',architectural_conclusion='UNDECIDED',fixture_only=True,current_ticket_runtime_authorized=False,runtime_budget_initialized=False,independent_review=review) save(lab,'runner-readiness',readiness);save(lab,'offline-gate',readiness) - save(lab,'summary',dict(status=status,markers=markers,tests=len(tests.get('tests',[])),coverage=coverage,core=core,historical_cases=len(read('historical-replay').get('cases',[])),runtime_runs=0,new_popups=0,new_grabs=0,new_captures=0,new_camera_operations=0,runtime_budget_initialized=False,production_changes=False,source_delta={p.name:sha(p) for p in HERE.iterdir() if p.is_file()},manifest='UNSEALED; parent review required',next='independent review of final fixture/register/evidence; separate future authorization required for runtime',limitations=['atomic transaction-generation/native-buffer-frame identity NOT PROVEN','new static popup crossing/grab/input/standard NOT TESTED','vertical/scaled/rotated/migration/full damage NOT TESTED','production cross-output viewport NOT YET SUPPORTED'])) + def count_rows(value,key): + rows=value.get(key) if isinstance(value,dict) else None + return len(rows) if isinstance(rows,list) else 0 + save(lab,'summary',dict(status=status,markers=markers,tests=count_rows(tests,'tests'),coverage=coverage,core=core,historical_cases=count_rows(read('historical-replay'),'cases'),runtime_runs=0,new_popups=0,new_grabs=0,new_captures=0,new_camera_operations=0,runtime_budget_initialized=False,production_changes=False,source_delta={p.name:sha(p) for p in HERE.iterdir() if p.is_file()},manifest='UNSEALED; parent review required',next='independent review of final fixture/register/evidence; separate future authorization required for runtime',limitations=['atomic transaction-generation/native-buffer-frame identity NOT PROVEN','new static popup crossing/grab/input/standard NOT TESTED','vertical/scaled/rotated/migration/full damage NOT TESTED','production cross-output viewport NOT YET SUPPORTED'])) (lab/'independent-review-input.md').write_text('''# Independent review input — EXPERIMENTAL Review this final fixture against the offline gate; review E1/E2, actual @@ -134,6 +123,7 @@ ACCEPTED/CORRECTION_REQUIRED/NOT PROVEN findings. No author review PASS, runtime launch, budget initialization or manifest sealing is authorized here. ''') print(json.dumps(readiness,indent=2)) + return readiness if __name__=='__main__': parser=argparse.ArgumentParser();parser.add_argument('lab',type=Path);write(parser.parse_args().lab)