From c5f96a34e3924275179365938d02f3a318c0c4bf Mon Sep 17 00:00:00 2001 From: fy59 Date: Tue, 13 Oct 2026 21:23:16 +0200 Subject: [PATCH] docs(mosaic): preserve static nested runtime and standard transition evidence --- docs/ARCHITECTURE.md | 16 + docs/KNOWN_LIMITATIONS.md | 16 + docs/README.md | 16 + docs/ROADMAP.md | 16 + ...C_NESTED_RUNTIME_HANDOFF_AND_VALID_GRAB.md | 329 ++++++++++++++++++ .../step20c-static-nested-runtime/README.md | 29 ++ .../step20c-static-nested-runtime/RESULTS.md | 33 ++ .../step20c-static-nested-runtime/handoff.py | 318 +++++++++++++++++ .../step20c-static-nested-runtime/launch.py | 39 +++ .../tests_offline.py | 178 ++++++++++ 10 files changed, 990 insertions(+) create mode 100644 docs/design/STEP20C_STATIC_NESTED_RUNTIME_HANDOFF_AND_VALID_GRAB.md create mode 100644 tooling/probes/step20c-static-nested-runtime/README.md create mode 100644 tooling/probes/step20c-static-nested-runtime/RESULTS.md create mode 100644 tooling/probes/step20c-static-nested-runtime/handoff.py create mode 100644 tooling/probes/step20c-static-nested-runtime/launch.py create mode 100644 tooling/probes/step20c-static-nested-runtime/tests_offline.py diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index ad6077c..988ac6a 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -472,3 +472,19 @@ 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. + + +## Preserved static nested runtime handoff — experimental evidence + +The [handoff and valid-grab report](design/STEP20C_STATIC_NESTED_RUNTIME_HANDOFF_AND_VALID_GRAB.md) +records PASS transfer/preflight and one unchanged-static-binary run, verdict +INCONCLUSIVE. Four genuine grabs and sampled E1/E2 Mosaic crossing pixels, +E1 visible/forbidden input and dismissal passed. E2 reached splith with mapped +popups and standard pixels, then a fixture root-window consistency predicate +rejected the old-to-cleared clip transition. Offline retained observations +support settled clip removal; standard A/B input, explicit child close/A survival +and final H2 restoration remain NOT TESTED. Raw runner/IPC exit1 and clean +compositor/client exits0 remain intact. No rerun or algorithm change; historical phase B +is preserved for publication by the E2 completion batch. Architecture UNDECIDED, STEP20C NEXT, +production multi-output NOT YET SUPPORTED; broader damage/generation/output/ +effects/ABI/distribution gates remain open. diff --git a/docs/KNOWN_LIMITATIONS.md b/docs/KNOWN_LIMITATIONS.md index 813217b..ddd2a21 100644 --- a/docs/KNOWN_LIMITATIONS.md +++ b/docs/KNOWN_LIMITATIONS.md @@ -413,3 +413,19 @@ 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. + + +## Preserved static nested runtime handoff — experimental evidence + +The [handoff and valid-grab report](design/STEP20C_STATIC_NESTED_RUNTIME_HANDOFF_AND_VALID_GRAB.md) +records PASS transfer/preflight and one unchanged-static-binary run, verdict +INCONCLUSIVE. Four genuine grabs and sampled E1/E2 Mosaic crossing pixels, +E1 visible/forbidden input and dismissal passed. E2 reached splith with mapped +popups and standard pixels, then a fixture root-window consistency predicate +rejected the old-to-cleared clip transition. Offline retained observations +support settled clip removal; standard A/B input, explicit child close/A survival +and final H2 restoration remain NOT TESTED. Raw runner/IPC exit1 and clean +compositor/client exits0 remain intact. No rerun or algorithm change; historical phase B +is preserved for publication by the E2 completion batch. Architecture UNDECIDED, STEP20C NEXT, +production multi-output NOT YET SUPPORTED; broader damage/generation/output/ +effects/ABI/distribution gates remain open. diff --git a/docs/README.md b/docs/README.md index 2e67dc0..e5a7321 100644 --- a/docs/README.md +++ b/docs/README.md @@ -331,3 +331,19 @@ 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. + + +## Preserved static nested runtime handoff — experimental evidence + +The [handoff and valid-grab report](design/STEP20C_STATIC_NESTED_RUNTIME_HANDOFF_AND_VALID_GRAB.md) +records PASS transfer/preflight and one unchanged-static-binary run, verdict +INCONCLUSIVE. Four genuine grabs and sampled E1/E2 Mosaic crossing pixels, +E1 visible/forbidden input and dismissal passed. E2 reached splith with mapped +popups and standard pixels, then a fixture root-window consistency predicate +rejected the old-to-cleared clip transition. Offline retained observations +support settled clip removal; standard A/B input, explicit child close/A survival +and final H2 restoration remain NOT TESTED. Raw runner/IPC exit1 and clean +compositor/client exits0 remain intact. No rerun or algorithm change; historical phase B +is preserved for publication by the E2 completion batch. Architecture UNDECIDED, STEP20C NEXT, +production multi-output NOT YET SUPPORTED; broader damage/generation/output/ +effects/ABI/distribution gates remain open. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index d70dacd..efa2fc1 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -417,3 +417,19 @@ 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. + + +## Preserved static nested runtime handoff — experimental evidence + +The [handoff and valid-grab report](design/STEP20C_STATIC_NESTED_RUNTIME_HANDOFF_AND_VALID_GRAB.md) +records PASS transfer/preflight and one unchanged-static-binary run, verdict +INCONCLUSIVE. Four genuine grabs and sampled E1/E2 Mosaic crossing pixels, +E1 visible/forbidden input and dismissal passed. E2 reached splith with mapped +popups and standard pixels, then a fixture root-window consistency predicate +rejected the old-to-cleared clip transition. Offline retained observations +support settled clip removal; standard A/B input, explicit child close/A survival +and final H2 restoration remain NOT TESTED. Raw runner/IPC exit1 and clean +compositor/client exits0 remain intact. No rerun or algorithm change; historical phase B +is preserved for publication by the E2 completion batch. Architecture UNDECIDED, STEP20C NEXT, +production multi-output NOT YET SUPPORTED; broader damage/generation/output/ +effects/ABI/distribution gates remain open. diff --git a/docs/design/STEP20C_STATIC_NESTED_RUNTIME_HANDOFF_AND_VALID_GRAB.md b/docs/design/STEP20C_STATIC_NESTED_RUNTIME_HANDOFF_AND_VALID_GRAB.md new file mode 100644 index 0000000..f6420b7 --- /dev/null +++ b/docs/design/STEP20C_STATIC_NESTED_RUNTIME_HANDOFF_AND_VALID_GRAB.md @@ -0,0 +1,329 @@ +# STEP20C static nested runtime handoff and valid-grab gate + +EXPERIMENTAL / INCONCLUSIVE; sole runtime stopped on a fixture observation-window +predicate during standard return. Architecture UNDECIDED. The pre-execution +contract below is preserved; observed results and remaining untested gates follow. +Ticket: `SWAYFX_MOSAIC_STEP20C_RECEIPT_FREEZE_AND_STATIC_NESTED_RUNTIME_V1`. +Phase A is published at `54392cc0de7c8391c4f216cb83eee64be322ec72`, verified +on Forgejo and GitHub with a clean worktree. The historical phase B is preserved for publication by +`SWAYFX_MOSAIC_STEP20C_E2_COMPLETION_AND_PRIVATE_INTEGRATION_BATCH_V1`. +Pre-execution publication restrictions below describe the consumed historical +ticket; they do not authorize a replay. STEP20A DONE, STEP20B EXPERIMENTAL, STEP20C NEXT, STEP20D–G +PLANNED; production multi-output NOT YET SUPPORTED. + +## Contract recorded before preparation or execution + +Canonical [architecture](../ARCHITECTURE.md), [UX](../UX.md), +[limitations](../KNOWN_LIMITATIONS.md) and [roadmap](../ROADMAP.md) govern. +The [accepted receipt-integrity gate](STEP20C_STATIC_NESTED_RECEIPT_INTEGRITY_GATE.md) +qualifies offline receipts, not runtime. Its LAB +`/home/fy59/swayfx-mosaic-receipt-integrity-cof0umuk/` is read-only. +Verify all 76 source and 199 evidence entries, their fixed manifest hashes, +the accepted report/receipt hashes, 18 source bindings, 15 mandatory receipts, +the actual aggregate's five PASS markers and empty blocker list. Preserve B-01, +the initial CORRECTION_REQUIRED and final independent ACCEPTED separately. + +The historical offline-gate lacks `fixture_sha256`; no historical runtime +fixture manifest exists. Its receipts must not be edited. A new transfer +receipt records exact source/destination copies and hashes, historical +acceptance scope, destination LAB and current ticket. Copy accepted receipts +under `accepted-offline/` without rewriting their embedded historical paths. +Retain their raw-byte and parsed-content bindings. The runtime offline-gate is +an explicitly derived transfer receipt, not a newly signed historical review. +Its fixture hash is computed from a real manifest after client preparation. + +The [runtime fixture](../../tooling/probes/step20c-static-nested-runtime/README.md) +adds preparation, read-only preflight and single-use dispatch around the +unchanged reviewed runner and modules. Preserve exact copies in LAB/fixture; +historical acceptance covers their bytes, and an independent handoff reviewer +must separately accept the new transfer/authorization/dispatch code and final +manifest. Changes to reviewed runner/modules would expire continuity and +require independent delta acceptance before launch. No Mosaic/SceneFX algorithm +or production source modification is authorized. + +## Immutable inventory and ownership + +Create one fresh mode-0700 LAB outside Git, one exclusive durable owner registry +for this ticket, one fresh campaign-owner and a registration identifying ticket, +LAB, local UID, binary, binary/wlroots hashes and fixture manifest hash. Do not +reuse old owners or ledgers. Registry creation and launch consumption use +exclusive creation and file/directory fsync; a consumed launch is never refunded. +The existing ledger remains the action authority: run1, chain2, grab4, click9, +capture16, camera2, transition1, move24; maximum runtime150 seconds. + +The fixture manifest contains exact runner/modules/scripts/patch/client sources, +protocols, prepared executables, immutable configuration template, accepted +receipt copies and referenced source/evidence manifest bytes. It excludes all +mutable budgets, logs, future PNGs/results, temporary files, authorization and +derived receipts containing the manifest hash, avoiding circular hashes. +Preflight checks excluded identity/authorization/transfer/review records through +explicit bindings. No manifest merely asserts a chosen hash. + +Reuse static binary SHA256 +`f82557c1a9978a5e9665b9b4291b74880253725359e3df1c5b8d554921f7603b`, +build ID `942db64269640778ee433e5970ba1100bc60f94c`, from the retained stage; +wlroots `/usr/lib/libwlroots-0.20.so` SHA256 +`7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837`. +Verify ELF64/x86-64, mode0755, clipping symbols, no SceneFX DT_NEEDED, no +RPATH/RUNPATH and resolved relocations. Build only three historical C17 clients +using the byte-identical static_cross patch and protocol hashes. Copy their +client-build executables exclusively to the runner's LAB root paths; compare +hashes/modes and reject symlinks. No compositor/library rebuild or installation. + +Authorization is explicit for this ticket and exact LAB, scope +`one-new-static-nested-headless-run`, with explicit_user_authorization=true, +owner_registry, binary, binary_sha256 and wlroots_sha256. The original +authorization.py is always called. Additional read-only preflight verifies +exact ticket, owner/UID, registration, immutable manifest and accepted review, +actual binary/wlroots/client identity and a completely unused ledger. +Offline negative tests must refuse every specified invalid authorization, +old/exhausted LAB, changed/missing manifest, unaccepted review and consumed run. +No socket, subprocess compositor or debit is allowed during those tests. + +Only after independent handoff acceptance and all controls PASS may preflight +publish `STATIC_NESTED_RUNTIME_PREFLIGHT=PASS`. Dispatch then exclusively consumes +the ticket launch token and executes the frozen runner, which atomically debits +run before runtime creation. A failed launch consumes authorization permanently. +No second run, even after a fixture failure. + +## Scientific run and stop rules + +HEADLESS-1(-800,0), HEADLESS-2(0,0), each800×600, scale1, normal. Runtime0700, +private Wayland and explicit private swaymsg -s only; inherited desktop/loader +settings are removed. Record observed renderer/log/mappings; WLR_RENDERER=pixman +alone does not identify the actual SceneFX renderer. Historical controls are +read-only; no new pristine run. Reference H2 is a unique PID/AppID/con_id on +workspace2, targeted border none, full800×600 #303038; stored border width need +not be zero. Two full RGB captures after calibration, at least500ms apart, +must be identical. Stop dependent measurements on instability. + +Each new E1/E2 owner has separately observed container/PID/workspace, applied +scene/content/titlebar, native420×320 SHM versus tiled destination, camera and +global clip[-800,0)×[0,600). At most one calculated camera correction per chain. +Never hardcode historical identities or adjust popup positions empirically. +Actual configure/ACK/map determines A and B geometry and both must cross x=0. +Open A with a real toplevel left-button serial and B with a fresh A serial; +require pre-map grabs and correct toplevel→A→B hierarchy and distinct serials. + +E1 measures H1 popup retention, whole-H2 equality, owner-derived root/inherited +clip, visible B right press/release and forbidden H2 continuation right click. +Distinguish actual invisible delivery from legitimate topmost-first popup_done +and child-first destruction; record H2 reference callbacks and residual pixels. +Close E1 normally and never reuse destroyed objects. + +E2 uses a fresh process/objects/serials in the same compositor. Open both grabs +under Mosaic, then targeted owner layout splith without outside click. Require +same workspace, mapped owner/A/B, no premature dismissal, tiled/root/inherited +clip removal. Remeasure standard origin, A/B H2 pixels and full masks; H2 may +normally differ from the empty reference while popups overflow. Click B on H2, +close B explicitly, verify A survives, click A, close owner and require whole-H2 +reference restoration. Preserve raw IPC/compositor/client exit codes and streams. +IPC exit1 is separable only after completed science, handled exit/compositor0, +clean client exits, no protocol/crash and no rescue masking earlier failure. + +Stop on identity/isolation/protocol/serial/parent failure, absent/non-comparable +popup, unstable reference, demonstrated leak, standard regression/residual clip, +crash or budget limit. Fixture failures are INCONCLUSIVE; demonstrated product +defects BLOCKED. No retry or algorithm repair is authorized. Available damage +extents qualify only measured intervals: no all-frame/minimal-damage/native-PNG +buffer/atomic-generation claim. Hidden owner, scale/rotation/migration/effects/ +saved buffers remain separate gates. + +## Closeout requirements + +Retain exact runner/clients/config, provenance/authorization/handoff/review, +preflight/budget, all measured geometry/protocol/serial/clip/pixel/input/lifecycle, +damage/terminal/cleanup/matrix/summary receipts, raw logs/IPC/PNGs and final +source/evidence manifests in LAB. Generated/binary/large files stay outside Git. +Document actual PASS/FAIL/NOT TESTED, raw codes and budget. A handoff failure +means runtime NOT_RUN and global INCONCLUSIVE. Otherwise apply the ticket's +FEASIBLE/PARTIALLY_FEASIBLE/INCONCLUSIVE/BLOCKED criteria. Architectural choice is +tested-matrix feasibility, requires-redesign, or UNDECIDED; no production gate +is closed by this isolated campaign. Phase B must remain uncommitted/unpublished. + +## Preparation candidate correction, before independent acceptance + +The first 19-test preflight suite had five harness errors: unchanged +model.require correctly raised Invalid(ValueError), but tests expected +AssertionError. Its FAIL receipt, logs, source candidate, manifest and original +registration are preserved separately. Only new test exception expectations +are corrected; no historical reviewed module changes. The same LAB and zero +ledger remain in place. The original exclusive registration is immutable. +A single exclusive/fsynced registration-amendment explicitly binds its hash, +superseded fixture hash, final fixture hash and unchanged ticket/LAB/UID/owner/ +binary identities. Handoff, authorization and new independent acceptance bind +both registration hashes separately. No budget/owner/token reset occurs. +The first candidate has no independent acceptance; its invalidated manifest +cannot authorize runtime. Only the reviewed final candidate can pass preflight. + +## Executed handoff and authorization + +New LAB: `/home/fy59/swayfx-mosaic-static-nested-runtime-pvn0osxm/`. +The initial preparation FAIL (19 tests, five harness errors) remains in +`candidate-v1/` and raw logs. The corrected suite passed 19 tests in8.008s; +the independent reviewer separately ran all19 PASS. Original registration +SHA256 `4a60089a548df2f9f2210e58e8fe23459104ff0002a01f6735a98d6f26faf0bf` +remains intact. The exclusive amendment SHA256 is +`362ab64728ca359c700bf1755d91788be9508ba776f03c711eda5f7d03df4d84`. +No ledger, owner or consumed token was reset. Final runtime inventory has92 +immutable entries, SHA256 +`1982bcc27dcc1d567997554c77791f42e5edaf447ccc12c21aea7f25659bf566`. +The historical runner SHA256 remains +`4382f2c0ffc8a41a6a65aecfca72dff762ded528f667288d179acbb354a9fcc9`. + +Independent handoff review `/root/handoff_review` (Sol/medium, isolated, +read-only) accepts final sources, transfers and bindings. Its exact report was +materialized by the parent without textual alteration, SHA256 +`af04fa9979ee24b5f87cff70807d7d853d316dbafb8660d7e779c0798088684c`. +This review is separate from the historical offline ACCEPTED; neither signs +new graphical results. Original historical report/review/76-source/199-proof +manifest bytes were checked again after runtime and remain unchanged. + +`STATIC_NESTED_RUNTIME_HANDOFF=PASS` + +`STATIC_NESTED_RUNTIME_PREFLIGHT=PASS` + +The complete reviewed preflight passed before any reservation, with all +counters zero and no run/socket. Dispatch consumed its exclusive ticket token, +then the unchanged runner reserved exactly one run. Post-run pure checks of +both original authorize() and strict preflight refuse `fresh-run-required`. +No second runner/compositor launch was attempted. The unused action allowance +does not permit another run or budget reimbursement. + +## Observed binary, outputs and reference + +The original and private copy remain SHA256 f82557c1…21f7603b, full hash above, +ELF64 x86-64, mode0755, expected build ID, both clipping definitions, no +SceneFX dynamic dependency or RPATH/RUNPATH and resolved relocations. Actual +process maps identify system `/usr/lib/libwlroots-0.20.so` with the expected +SHA256; pkg-config reports0.20.2. No dynamic SceneFX was loaded. The rendering +log observes Mesa EGL, AMD GL vendor, AMD Radeon780M radeonsi and successful +FX renderer shader initialization. The actual path is FX/GLES, not Pixman; +the requested environment variable alone was not treated as renderer evidence. + +Outputs have the exact requested signed geometry, scale1/normal. H2 reference +is PID2051837, con_id7, workspace2 (workspace ID5), AppID +org.mosaic.StaticReference, border none, empty decoration and full800×600 +window. Stored border width2 is legitimate. The targeted con_id command leaves +the H1 filler unaffected. Both full RGB references are uniform#303038 and +identical, zero changed pixels, capture starts834.619ms apart. + +E1 owner con_id9/PID2051849 and E2 con_id10/PID2052030 occupy workspace1 +(ID6) on H1. Each uses one computed correction -320, scroll480→160, scene +(-320,0,640,600), painted content origin(-318,25). Native SHM420×320 remains +distinct from configured tiled content636×573, border/titlebar translation and +paint. The global tiled and owner-derived root viewport is[-800,0)×[0,600). + +## E1 and E2 Mosaic observations + +| Measurement | E1 | E2 | +| --- | --- | --- | +| Root real left-button grab serial |33|91| +| Nested real left-button grab serial |42|100| +| Root configured local rectangle |100,80,350,170|100,80,350,170| +| Nested configured local rectangle |100,60,300,130|100,60,300,130| +| Root global rectangle |[-218,105,350,170]|[-218,105,350,170]| +| Nested global rectangle |[-118,165,300,130]|[-118,165,300,130]| +| H1 A/B exact color pixels |24080 /15340|24080 /15340| +| H2 A/B color pixels |0 /0|0 /0| +| Whole-H2 comparison |exact reference|exact reference| + +Both levels actually cross x=0 in both chains. Client and WAYLAND_DEBUG records +confirm real serial provenance, pre-map grab, bufferless initial commit, +configure/ACK/attach/map and TOPLEVEL→A→B parent IDs. Four serials are distinct. +E2 is a fresh process/Wayland connection; repeated numeric protocol IDs belong +to separate connection namespaces and denote fresh objects, not reused E1 +objects. Root trace is correlated with each actual owner and one live root; +B inherits through the preserved exact source hierarchy. No direct independent +child-clip instrumentation is invented. + +E1 visible B right click at(-108,175), local(10,10), delivers serial51 press +and52 release, button273, only to B. Forbidden point(91,195) lies within B's +geometric continuation on H2. Neither popup receives a button; reference H2 +receives the actual press/release serial62/63. popup_done and destruction occur +B then A. This is a legitimate outside dismissal, not an invisible popup hit. +E1's post-dismissal H2 is exactly restored; E1 toplevel closes normally with +child-first destruction. These are measured sampled outcomes, not universal +click-through semantics. + +## Standard return, actual failure and offline analysis + +E2 opens A/B under Mosaic and reaches targeted `[con_id=10] layout splith`, +IPC0/success true, without an outside click. The retained standard snapshot +has the same workspaceID6, layout splith, mapped/visible owner. A/B both remain +present in standard PNGs; no popup_done is recorded. Destruction occurs only +during terminal shutdown after the fixture stops. + +The runner fails `root-window-state-consistent` in `root_clip()`. Its window +begins before the layout command is applied and contains both states of the +same root tree0x56292fbcc170: + +| Trace ns | Applied observation | +| --- | --- | +|181923034904618|enabled1, clip_valid1, local clip(-482,-25,800,600), prior Mosaic state| +|181923236944243|enabled1, clip_valid0, clip(0,0,0,0), applied standard state| + +The layout IPC response is ns181923035203192. Requiring an unchanged root +through this transition is an invalid fixture precondition. The raw failure, +runner exit1 and incomplete-measurements flag remain unchanged. This evidence +does not establish an atomic transaction-generation contract or demonstrate a +product clipping defect. No runner/binary/algorithm was repaired or rerun. + +Separate offline reanalysis of the existing standard snapshot/logs/PNGs finds +content origin(-398,25), A[-298,105,350,170], B[-198,165,300,130], settled tiled +clip_valid0/layout1 and root clip_valid0. Exact A/B masks have3120/13260 pixels +on H2. These observations corroborate removal of the inherited boundary and +ordinary overflow; H2 reference equality is intentionally not required during +that overflow. They are labelled OFFLINE_REDERIVED, not a completed runner +standard gate. The parent collector initially used an incorrect PI_MOSAIC +trace name; its failed attempt is retained. Correct PI_SCENE parsing then +completed offline checks without modifying runtime evidence. + +Standard B right click, explicit B close with A survival, standard A right +click, normal E2 close and final whole-H2 restoration are **NOT TESTED**. +Missing measurements cannot be filled by source inspection or clean shutdown. +Verdict remains **INCONCLUSIVE**, architecture **UNDECIDED**; static nested +continuity is demonstrated for the sampled Mosaic portion, not the complete +requested standard-input/close matrix. No algorithmic change is justified by +this fixture-window failure. + +## Terminal codes, cleanup, budget and remaining limits + +Actual runner/dispatch exit1; terminal swaymsg exit1, stdout empty, stderr +`Unable to receive IPC response`. The compositor log shows handled exit and +shutdown; compositor exit0. E1/E2/reference/filler/pointer exits are all0. +No protocol error, crash, watchdog or rescue was recorded. All six PIDs are +reaped, runtime `/tmp/msng-hnvg_4l5` removed, no private socket remains. +Scientific measurements were incomplete: terminal transport is **not** granted +the completed-science exception; `scientific_cleanup_pass=false` and +`transport_incident=false` stay intact despite successful process cleanup. + +| Budget | Used | Limit | +| --- | ---: | ---: | +|compositor|1|1| +|XDG chains|2|2| +|grabs|4|4| +|complete clicks|6|9| +|PNG captures|13|16| +|camera corrections|2|2| +|splith transitions|1|1| +|positions|8|24| +|seconds|16.486893|150| + +370 damage extents are retained with capture windows. They describe sampled +mapping/dismissal/return intervals only, not every frame, minimal damage, +native PNG buffer identity or atomic generation. Direct child clip trace, +hidden owner, scale/rotation, migration/resize, effects, saved buffers, +exhaustive ABI/graphics maintenance and distribution gates remain open. +No new pristine run was performed; retained controls keep their original scope. +All310 protected source/system/package/patch paths remain unchanged. + +`SWAYFX_MOSAIC_STATIC_NESTED_RUNTIME_PROBE=INCONCLUSIVE` + +`SWAYFX_MOSAIC_STEP20C_RECEIPT_FREEZE_AND_STATIC_NESTED_RUNTIME_V1=INCONCLUSIVE` + +Smallest justified next gate: correct and independently validate the standard +transition observation-window predicate offline against these immutable traces, +then request a separate bounded authorization for still-unmeasured standard +A/B input, child close/survival and final H2 restoration. No current-ticket +rerun, production integration or UX decision follows. diff --git a/tooling/probes/step20c-static-nested-runtime/README.md b/tooling/probes/step20c-static-nested-runtime/README.md new file mode 100644 index 0000000..8076bae --- /dev/null +++ b/tooling/probes/step20c-static-nested-runtime/README.md @@ -0,0 +1,29 @@ +# Static nested runtime handoff — EXPERIMENTAL + +This preserved fixture belongs only to ticket +`SWAYFX_MOSAIC_STEP20C_RECEIPT_FREEZE_AND_STATIC_NESTED_RUNTIME_V1`. +Read the [pre-execution protocol](../../../docs/design/STEP20C_STATIC_NESTED_RUNTIME_HANDOFF_AND_VALID_GRAB.md). +The consumed runtime result is [INCONCLUSIVE](RESULTS.md); handoff and preflight +are PASS. Standard B/A input, explicit B close/A survival and final whole-H2 +restoration remain NOT TESTED. The original pre-execution fixture and receipts +remain byte-identical in the LAB; these documentation status updates do not +modify the executed scripts or authorize another launch. STEP20C NEXT, architecture +UNDECIDED, production multi-output NOT YET SUPPORTED. + +Preparation validates sealed offline evidence, preserves exact copies, builds +only C17 clients, verifies/copies the existing static binary, and creates one +new exclusive ticket-owned LAB/registry/ledger. The historical runner and its +modules are unchanged. A real immutable inventory protects runtime inputs; +mutable runtime output and circular derived hash records are excluded. + +The original authorization gate is called by both the new read-only preflight +and the frozen runner. A separate handoff acceptance binds new dispatch code, +manifest and provenance; historical ACCEPTED is never reused to sign this code. +Preflight requires offline negative tests, binary/client identity, current ticket, +owner/UID, unused persistent budget and no prior runtime. Dispatch consumes a +durable exclusive ticket token before the runner's existing debit. Neither +failed launches nor failed actions are refunded. No second run is authorized. + +Do not run any script from an old ticket/LAB. All outputs, clients, binaries, +PNGs, logs and sockets belong outside Git. No Mosaic/SceneFX build, production +change, package operation, daily IPC or desktop configuration is permitted. diff --git a/tooling/probes/step20c-static-nested-runtime/RESULTS.md b/tooling/probes/step20c-static-nested-runtime/RESULTS.md new file mode 100644 index 0000000..70594ba --- /dev/null +++ b/tooling/probes/step20c-static-nested-runtime/RESULTS.md @@ -0,0 +1,33 @@ +# Preserved static nested runtime result — INCONCLUSIVE + +See [the complete report](../../../docs/design/STEP20C_STATIC_NESTED_RUNTIME_HANDOFF_AND_VALID_GRAB.md) +for the preserved pre-execution contract, accepted handoff, real run, failure, +offline observations and limits. LAB: +`/home/fy59/swayfx-mosaic-static-nested-runtime-pvn0osxm/`. + +Handoff PASS, preflight PASS, one static headless compositor launched. Four +genuine root/nested grabs, two real crossing chains, sampled Mosaic pixels and +E1 input/dismissal PASS. E2 reached splith and standard captures, then the +unchanged root consistency predicate rejected its transition window containing +old and cleared clip states. Runner exit1 and all raw evidence are retained. +Standard B/A input, explicit child close/A survival and final H2 restoration +were not executed. Separate offline reanalysis supports standard clip removal +and A/B overflow pixels; it does not complete missing runtime gates. + +Used budgets: run1/1, chains2/2, grabs4/4, clicks6/9, captures13/16, camera2/2, +transition1/1, positions8/24;16.486893s/150s. All six owned processes exit0, +no protocol error/rescue/socket residue. Terminal IPC exit1 is retained and +does not qualify for the completed-measurements transport exception. +370 damage extents are partial observations. Renderer actually FX/GLES AMD, +not Pixman inferred from the environment. + +Initial preparation test FAIL and corrected19 PASS, append-only registration +amendment and genuine independent handoff acceptance are retained. Historical +offline reviewed bytes and runtime runner/modules/binary are unchanged. This +post-run result file is outside the immutable runtime inventory and its handoff +acceptance; it is covered only by final documentation/evidence validation. + +No retry, compositor/library build, install, production/package/patch/UX change +or daily IPC. Historical phase B is preserved for publication by the E2 +completion batch, without changing its verdict or executed bytes. Architecture +UNDECIDED, STEP20C NEXT, production multi-output NOT YET SUPPORTED. diff --git a/tooling/probes/step20c-static-nested-runtime/handoff.py b/tooling/probes/step20c-static-nested-runtime/handoff.py new file mode 100644 index 0000000..d7bfa38 --- /dev/null +++ b/tooling/probes/step20c-static-nested-runtime/handoff.py @@ -0,0 +1,318 @@ +#!/usr/bin/env python3 +"""Offline transfer and strict preflight around the unchanged historical gate. + +WHY: offline acceptance does not authenticate missing runtime installation data. +CONTRACT: historical bytes remain read-only; only new transfer records are made. +INVARIANT: checks create no socket, process, runtime directory or budget debit. +""" +import ast +import hashlib +import json +import os +from pathlib import Path +import shutil +import stat +import subprocess +import sys +import tempfile + +HERE = Path(__file__).resolve().parent +REPO = HERE.parents[2] +OLD_FIXTURE = REPO / 'tooling/probes/step20c-static-nested-geometry-replay' +OLD_LAB = Path('/home/fy59/swayfx-mosaic-receipt-integrity-cof0umuk') +TICKET = 'SWAYFX_MOSAIC_STEP20C_RECEIPT_FREEZE_AND_STATIC_NESTED_RUNTIME_V1' +REGISTRY = Path('/home/fy59/swayfx-mosaic-static-nested-runtime-v1.owner') +REGISTRATION = REGISTRY.with_suffix('.registration.json') +AMENDMENT = REGISTRY.with_suffix('.registration-amendment.json') +SPENT = REGISTRY.with_suffix('.launch-consumed.json') +BINARY = Path('/home/fy59/swayfx-mosaic-static-runtime-hm5kbdkt/stage-static/usr/bin/swayfx-mosaic') +BINARY_SHA = 'f82557c1a9978a5e9665b9b4291b74880253725359e3df1c5b8d554921f7603b' +BUILD_ID = '942db64269640778ee433e5970ba1100bc60f94c' +WLROOTS = Path('/usr/lib/libwlroots-0.20.so') +WLROOTS_SHA = '7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837' +SEALS = {'source-sha256.txt': (76, 'dbd96db3f19f6eb60fc8be13c62b1e71944a759b481d008a7825b3720cf525ce'), + 'evidence-sha256.txt': (199, 'f00ca54e39bff031a5bda585ea52cfebe9239e71b4c7ee7dc5c41a930e1598b3')} +REPORT_SHA = '311af1fe694d1bde88884afa21ef16a4fe908d6cfa47b6cbed515d1a21fedc9e' +REVIEW_SHA = '4d8401978e69a6c54f2f6144ba06deff4952671bc8e1ae3008376ab6ffe01e09' +sys.path.insert(0, str(OLD_FIXTURE)) +from authorization import authorize, MARKER +from ledger import LIMITS, valid +from support import environment, sha, verify +from write_results import REQUIRED, aggregate +from receipt_integrity import make_bindings + + +def need(ok, message): + if not ok: + raise ValueError(message) + + +def read(path): + return json.loads(Path(path).read_text()) + + +def exclusive(path, data, mode=0o600): + """File and parent fsync precede use; existing ownership is never overwritten.""" + path = Path(path) + fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, mode) + with os.fdopen(fd, 'w') as out: + out.write(data if isinstance(data, str) else json.dumps(data, indent=2) + '\n') + out.flush() + os.fsync(out.fileno()) + fd = os.open(path.parent, os.O_RDONLY | os.O_DIRECTORY) + try: + os.fsync(fd) + finally: + os.close(fd) + + +def regular(path, mode=None): + path = Path(path) + info = path.lstat() + need(stat.S_ISREG(info.st_mode) and info.st_uid == os.getuid(), 'unsafe-owned-file: ' + str(path)) + if mode is not None: + need(stat.S_IMODE(info.st_mode) == mode, 'file-mode: ' + str(path)) + + +def accepted(): + """Revalidate real file-backed bindings with the actual accepted aggregate.""" + manifests = {} + for name, (count, digest) in SEALS.items(): + p = OLD_LAB / 'results' / name + need(sha(p) == digest, 'historical-manifest-identity') + observed = verify(p) + need(observed['entries'] == count, 'historical-manifest-count') + manifests[name] = observed + need(sha(OLD_LAB / 'independent-review.md') == REPORT_SHA, 'historical-report-identity') + need(sha(OLD_LAB / 'results/independent-review.json') == REVIEW_SHA, 'historical-review-identity') + evidence = {n: read(OLD_LAB / 'results' / (n + '.json')) for n in REQUIRED} + evidence['independent-review'] = read(OLD_LAB / 'results/independent-review.json') + bindings = make_bindings(evidence, {n: OLD_LAB / 'results' / (n + '.json') for n in REQUIRED}) + need(bindings == read(OLD_LAB / 'results/review-bindings.json'), 'accepted-bindings') + for name, digest in bindings['source_delta'].items(): + need(sha(OLD_FIXTURE / name) == sha(OLD_LAB / 'proposed-fixture' / name) == digest, 'accepted-source: ' + name) + gates = aggregate(evidence, sha, bindings) + need(gates['status'] == 'READY_FOR_NEW_RUNTIME' and not gates['blockers'] and + len(gates['markers']) == 5 and set(gates['markers'].values()) == {'PASS'}, 'accepted-aggregate') + ready = read(OLD_LAB / 'results/offline-gate.json') + need(ready['status'] == 'READY_FOR_NEW_RUNTIME' and ready['marker'] == MARKER and + ready['review_bindings'] == bindings and not ready['blockers'] and ready['markers'] == gates['markers'], 'historical-READY') + return dict(manifests=manifests, bindings=bindings, aggregate=gates, + report_sha256=REPORT_SHA, review_sha256=REVIEW_SHA, historical_lab=str(OLD_LAB)) + + +def copy_exact(source, destination): + source, destination = Path(source), Path(destination) + need(source.is_file() and not source.is_symlink(), 'copy-source-regular') + with source.open('rb') as src, destination.open('xb') as dst: + shutil.copyfileobj(src, dst) + dst.flush() + os.fsync(dst.fileno()) + destination.chmod(stat.S_IMODE(source.stat().st_mode)) + need(sha(source) == sha(destination), 'copy-identity') + return dict(source=str(source), destination=str(destination), sha256=sha(source)) + + +def run_command(lab, label, argv): + result = subprocess.run(list(map(str, argv)), env=environment(), capture_output=True, text=True, timeout=30) + exclusive(lab / 'raw' / (label + '.json'), dict(argv=list(map(str, argv)), exit_code=result.returncode, + stdout=result.stdout, stderr=result.stderr)) + need(result.returncode == 0, 'preparation-command: ' + label) + return result.stdout + + +def prepare(): + proof = accepted() + need(not REGISTRY.exists() and not REGISTRATION.exists() and not AMENDMENT.exists() and not SPENT.exists(), 'ticket-already-registered') + need(not BINARY.is_symlink() and sha(BINARY) == BINARY_SHA and + stat.S_IMODE(BINARY.stat().st_mode) == 0o755 and sha(WLROOTS) == WLROOTS_SHA, 'binary-and-wlroots-before-copy') + lab = Path(tempfile.mkdtemp(prefix='swayfx-mosaic-static-nested-runtime-', dir='/home/fy59')) + lab.chmod(0o700) + exclusive(REGISTRY, str(lab) + '\n') + print('LAB=' + str(lab), flush=True) + for name in ('results', 'raw', 'fixture', 'accepted-offline', 'handoff-source'): + (lab / name).mkdir() + exclusive(lab / 'campaign-owner', str(REGISTRY) + '\n') + transfers = [] + # CONTRACT: copied receipts keep historical paths and scope byte-for-byte. + for n in REQUIRED: + transfers.append(copy_exact(OLD_LAB / 'results' / (n + '.json'), lab / 'accepted-offline' / (n + '.json'))) + for n in ('offline-gate.json', 'runner-readiness.json', 'review-bindings.json', *SEALS): + transfers.append(copy_exact(OLD_LAB / 'results' / n, lab / 'accepted-offline' / n)) + transfers.append(copy_exact(OLD_LAB / 'independent-review.md', lab / 'accepted-offline/independent-review.md')) + transfers.append(copy_exact(OLD_LAB / 'results/independent-review.json', lab / 'accepted-offline/independent-review.json')) + for name in proof['bindings']['source_delta']: + transfers.append(copy_exact(OLD_FIXTURE / name, lab / 'fixture' / name)) + for p in sorted(HERE.iterdir()): + if p.is_file(): + transfers.append(copy_exact(p, lab / 'handoff-source' / p.name)) + copy_exact(REPO / 'docs/design/STEP20C_STATIC_NESTED_RUNTIME_HANDOFF_AND_VALID_GRAB.md', lab / 'protocol-before-run.md') + binary_copy = copy_exact(BINARY, lab / 'swayfx-mosaic') + elf = run_command(lab, 'binary-ELF', ['readelf', '-hW', '-nW', '-dW', lab / 'swayfx-mosaic']) + need('ELF64' in elf and 'Advanced Micro Devices X86-64' in elf and BUILD_ID in elf and + all(s not in elf for s in ('libscenefx', '(RPATH)', '(RUNPATH)')), 'ELF-contract') + symbols = run_command(lab, 'binary-symbols', ['nm', lab / 'swayfx-mosaic']) + need(all(' T ' + n in symbols for n in ('wlr_scene_tree_set_clip', 'wlr_scene_tree_get_clip')), 'clip-symbols') + loader = run_command(lab, 'binary-relocations', ['ldd', '-r', lab / 'swayfx-mosaic']) + need(all(s not in loader for s in ('undefined symbol', 'not found', 'libscenefx')) and + str(WLROOTS.resolve()) in loader, 'resolved-relocations') + exclusive(lab / 'results/binary-identity.json', dict(status='PASS', sha256=BINARY_SHA, build_id=BUILD_ID, + wlroots_sha256=WLROOTS_SHA, copy=binary_copy, mode='0755', ELF='ELF64 x86-64', SceneFX_dynamic=False)) + from prepare_clients import prepare as prepare_clients + prepare_clients(lab) + clients = read(lab / 'results/client-provenance.json') + for name in ('nested-client', 'pointer-daemon', 'static-reference'): + installation = copy_exact(lab / 'client-build' / name, lab / name) + regular(lab / name, 0o755) + clients.setdefault('runtime_installation', []).append(installation) + # This is a new runtime build receipt, not one of the historical REQUIREDs. + (lab / 'results/client-provenance.json').write_text(json.dumps(clients, indent=2) + '\n') + tree = ast.parse((lab / 'fixture/run.py').read_text()) + configs = [ast.literal_eval(n.value) for n in tree.body if isinstance(n, ast.Assign) + and any(isinstance(t, ast.Name) and t.id == 'config' for t in n.targets)] + need(len(configs) == 1, 'runner-config-constant') + exclusive(lab / 'headless-config-template', configs[0]) + exclusive(lab / 'results/accepted-offline-provenance.json', proof) + # Freeze exact runtime inputs, including installed clients and binary, but + # never budgets/logs/future captures or circular manifest-bound records. + immutable = [p for root in ('fixture', 'handoff-source', 'accepted-offline', 'client-build') + for p in (lab / root).iterdir() if p.is_file()] + immutable += [lab / n for n in ('swayfx-mosaic', 'nested-client', 'pointer-daemon', 'static-reference', + 'campaign-owner', 'headless-config-template', 'protocol-before-run.md')] + immutable += [lab / 'results' / n for n in ('accepted-offline-provenance.json', 'binary-identity.json', 'client-provenance.json')] + immutable += [OLD_FIXTURE / n for n in proof['bindings']['source_delta']] + immutable += [p for p in HERE.iterdir() if p.is_file()] + immutable += [REGISTRY, BINARY, WLROOTS.resolve()] + need(len(immutable) == len(set(immutable)), 'unique-freeze-paths') + for p in immutable: + need(not p.is_symlink(), 'freeze-no-symlink') + manifest = lab / 'results/fixture-freeze-sha256.txt' + exclusive(manifest, ''.join(sha(p) + ' ' + str(p) + '\n' for p in sorted(immutable))) + fixture = verify(manifest) + registration = dict(ticket=TICKET, lab=str(lab), uid=os.getuid(), owner_registry=str(REGISTRY), + binary=str(BINARY), binary_sha256=BINARY_SHA, wlroots_sha256=WLROOTS_SHA, + fixture_sha256=fixture['sha256'], non_reusable=True) + exclusive(REGISTRATION, registration) + copy_exact(REGISTRATION, lab / 'results/registration.json') + auth = dict(scope='one-new-static-nested-headless-run', explicit_user_authorization=True, + ticket=TICKET, registered_lab=str(lab), owner_registry=str(REGISTRY), binary=str(BINARY), + binary_sha256=BINARY_SHA, wlroots_sha256=WLROOTS_SHA, fixture_sha256=fixture['sha256'], uid=os.getuid(), + authorization_basis='User transmitted the exact current ticket; one private launch only.') + exclusive(lab / 'results/authorization.json', auth) + exclusive(lab / 'results/budget.json', dict(limits=LIMITS, used=dict.fromkeys(LIMITS, 0), events=[])) + transfer = dict(status='PREPARED_FOR_INDEPENDENT_REVIEW', ticket=TICKET, destination_lab=str(lab), + fixture=fixture, transfers=transfers, provenance_sha256=sha(lab / 'results/accepted-offline-provenance.json'), + registration_sha256=sha(REGISTRATION), authorization_sha256=sha(lab / 'results/authorization.json'), + historical_review_scope='FINAL_OFFLINE_RECEIPT_INTEGRITY; does not sign this transfer or dispatch') + exclusive(lab / 'results/runtime-handoff.json', transfer) + # WHY: this derived gate satisfies the runner with verified transfer data, + # while the original historical gate and ACCEPTED review are left intact. + exclusive(lab / 'results/offline-gate.json', dict(status='READY_FOR_NEW_RUNTIME', marker=MARKER, + fixture_sha256=fixture['sha256'], markers=proof['aggregate']['markers'], blockers=[], + scope='DERIVED_TRANSFER_OF_ACCEPTED_OFFLINE_EVIDENCE', historical_gate_sha256=sha(OLD_LAB / 'results/offline-gate.json'), + runtime_handoff_sha256=sha(lab / 'results/runtime-handoff.json'))) + copy_exact(OLD_LAB / 'results/independent-review.json', lab / 'results/independent-review.json') + exclusive(lab / 'results/preparation.json', dict(status='PASS', lab=str(lab), fixture=fixture, run_reserved=False)) + return lab + + +def preflight(lab, authorization, review=True): + """Pure checks; review=False is only for offline tests and review preparation. + + CONTRACT: dispatch always uses review=True. The original authorize() is + mandatory and runs before any ticket-owned action can be consumed. + """ + lab = Path(lab).resolve() + need(not sys.flags.optimize, 'assertions-required') + auth = authorize(lab, authorization) + need(auth['ticket'] == TICKET and auth['registered_lab'] == str(lab), 'current-ticket-and-lab') + need(lab.stat().st_uid == os.getuid() and stat.S_IMODE(lab.stat().st_mode) == 0o700, 'lab-local-owner-mode') + regular(REGISTRY, 0o600) + regular(REGISTRATION, 0o600) + regular(lab / 'campaign-owner', 0o600) + need(Path(auth['owner_registry']) == REGISTRY and REGISTRY.read_text().strip() == str(lab) and + (lab / 'campaign-owner').read_text().strip() == str(REGISTRY), 'exclusive-owner-identity') + need(not SPENT.exists() and not (lab / 'run').exists() and not (lab / 'run.lock').exists(), 'fresh-unconsumed-run') + need(not any(stat.S_ISSOCK(p.lstat().st_mode) for p in lab.rglob('*')), 'no-preexisting-sockets') + need(auth['binary'] == str(BINARY) and auth['binary_sha256'] == BINARY_SHA and + auth['wlroots_sha256'] == WLROOTS_SHA and auth['uid'] == os.getuid(), 'authorized-identities') + need(sha(BINARY) == sha(lab / 'swayfx-mosaic') == BINARY_SHA and sha(WLROOTS) == WLROOTS_SHA, 'actual-binary-wlroots') + regular(lab / 'swayfx-mosaic', 0o755) + fixture = verify(lab / 'results/fixture-freeze-sha256.txt') + registration = read(REGISTRATION) + expected = dict(ticket=TICKET, lab=str(lab), uid=os.getuid(), owner_registry=str(REGISTRY), binary=str(BINARY), + binary_sha256=BINARY_SHA, wlroots_sha256=WLROOTS_SHA, fixture_sha256=fixture['sha256'], non_reusable=True) + need(registration == read(lab / 'results/registration.json'), 'original-registration-copy') + if AMENDMENT.exists(): + # WHY: a rejected preparation candidate is never silently accepted as + # final. CONTRACT: preserve original registration and append exactly one + # pre-runtime amendment; ownership and all budget counters are retained. + regular(AMENDMENT, 0o600) + amendment = read(AMENDMENT) + original_identity = {k: v for k, v in registration.items() if k != 'fixture_sha256'} + need(original_identity == {k: v for k, v in expected.items() if k != 'fixture_sha256'}, 'amendment-identity-unchanged') + need(amendment == dict(registration=expected, previous_registration_sha256=sha(REGISTRATION), + superseded_fixture_sha256=registration['fixture_sha256'], reason='pre-runtime test exception-class correction', + runtime_reserved=False, budget_reset=False), 'append-only-registration-amendment') + need(read(lab / 'results/registration-amendment.json') == amendment, 'amendment-copy') + else: + need(registration == expected, 'durable-registration-bindings') + need(auth['fixture_sha256'] == fixture['sha256'], 'authorization-fixture-binding') + proof = accepted() + need(read(lab / 'results/accepted-offline-provenance.json') == proof, 'accepted-offline-provenance') + handoff = read(lab / 'results/runtime-handoff.json') + need(handoff['ticket'] == TICKET and handoff['destination_lab'] == str(lab) and handoff['fixture'] == fixture and + handoff['registration_sha256'] == sha(REGISTRATION) and handoff['authorization_sha256'] == sha(authorization) and + handoff['provenance_sha256'] == sha(lab / 'results/accepted-offline-provenance.json'), 'handoff-bindings') + if AMENDMENT.exists(): + need(handoff['registration_amendment_sha256'] == sha(AMENDMENT) and + auth['registration_amendment_sha256'] == sha(AMENDMENT), 'explicit-amendment-binding') + for item in handoff['transfers']: + need(sha(item['source']) == sha(item['destination']) == item['sha256'], 'exact-transfer: ' + item['source']) + gate = read(lab / 'results/offline-gate.json') + need(gate == dict(status='READY_FOR_NEW_RUNTIME', marker=MARKER, fixture_sha256=fixture['sha256'], + markers=proof['aggregate']['markers'], blockers=[], scope='DERIVED_TRANSFER_OF_ACCEPTED_OFFLINE_EVIDENCE', + historical_gate_sha256=sha(OLD_LAB / 'results/offline-gate.json'), + runtime_handoff_sha256=sha(lab / 'results/runtime-handoff.json')), 'derived-gate-bindings') + need(sha(lab / 'results/independent-review.json') == REVIEW_SHA, 'historical-review-copy') + need(read(lab / 'results/binary-identity.json')['status'] == 'PASS', 'binary-receipt') + clients = read(lab / 'results/client-provenance.json') + for item in clients['runtime_installation']: + name = Path(item['destination']).name + need(name in ('nested-client', 'pointer-daemon', 'static-reference') and + item['destination'] == str(lab / name) and item['source'] == str(lab / 'client-build' / name), 'client-installation-path') + regular(lab / name, 0o755) + need(sha(item['source']) == sha(item['destination']) == item['sha256'], 'client-installation-bytes') + need({Path(i['destination']).name for i in clients['runtime_installation']} == + {'nested-client', 'pointer-daemon', 'static-reference'}, 'all-runtime-clients') + budget = valid(read(lab / 'results/budget.json')) + need(not any(budget['used'].values()) and not budget['events'], 'unused-ledger-required') + if review: + checks = read(lab / 'results/preflight-tests.json') + need(checks['status'] == 'PASS' and checks['tests'] >= 13 and checks['source_sha256'] == sha(HERE / 'tests_offline.py'), 'negative-preflight-tests') + reviewed = read(lab / 'results/handoff-review.json') + need(reviewed['status'] == 'ACCEPTED' and reviewed['independent'] is True and reviewed['read_only'] is True and + reviewed['scope'] == 'STATIC_NESTED_RUNTIME_HANDOFF' and reviewed['fixture_sha256'] == fixture['sha256'] and + reviewed['runtime_handoff_sha256'] == sha(lab / 'results/runtime-handoff.json') and + reviewed['authorization_sha256'] == sha(authorization) and reviewed['registration_sha256'] == sha(REGISTRATION), 'independent-handoff-acceptance') + need(reviewed['source_sha256'] == {p.name: sha(p) for p in HERE.iterdir() if p.is_file()}, 'reviewed-handoff-sources') + if AMENDMENT.exists(): + need(reviewed['registration_amendment_sha256'] == sha(AMENDMENT), 'reviewed-registration-amendment') + need(sha(reviewed['report_file']) == reviewed['report_sha256'] and not reviewed['blockers'], 'handoff-review-report') + return dict(status='PASS', marker='STATIC_NESTED_RUNTIME_PREFLIGHT=PASS', fixture=fixture, + authorization='PASS', handoff='PASS' if review else 'PENDING_INDEPENDENT_REVIEW', run_reserved=False, + budget=budget, ticket=TICKET, lab=str(lab)) + + +if __name__ == '__main__': + import argparse + p = argparse.ArgumentParser() + p.add_argument('action', choices=('prepare', 'preflight')) + p.add_argument('lab', nargs='?', type=Path) + a = p.parse_args() + if a.action == 'prepare': + prepare() + else: + result = preflight(a.lab, a.lab / 'results/authorization.json') + exclusive(a.lab / 'results/preflight.json', result) + print(result['marker']) diff --git a/tooling/probes/step20c-static-nested-runtime/launch.py b/tooling/probes/step20c-static-nested-runtime/launch.py new file mode 100644 index 0000000..05d73a0 --- /dev/null +++ b/tooling/probes/step20c-static-nested-runtime/launch.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""One durable dispatch; never rebuild, retry, refund or reset. + +WHY: a new directory must not reopen this ticket's single launch entitlement. +CONTRACT: full reviewed preflight precedes consumption; runner owns its ledger. +INVARIANT: even a dispatch/runner failure leaves the exclusive spent token. +""" +import json +import os +from pathlib import Path +import subprocess +import sys +from handoff import preflight, exclusive, REGISTRY, SPENT, TICKET, sha, environment + + +def launch(lab): + lab = Path(lab).resolve() + auth = lab / 'results/authorization.json' + result = preflight(lab, auth) + existing = json.loads((lab / 'results/preflight.json').read_text()) + if existing != result: + raise ValueError('published-preflight-must-match') + # O_EXCL consumption also arbitrates concurrent dispatches. No action can + # execute if another dispatcher won, even before runner ledger locking. + exclusive(SPENT, dict(ticket=TICKET, lab=str(lab), uid=os.getuid(), owner_registry=str(REGISTRY), + fixture_sha256=result['fixture']['sha256'], authorization_sha256=sha(auth))) + env = environment() + for key in ('LD_DEBUG', 'LD_DEBUG_OUTPUT'): + env.pop(key, None) + argv = [sys.executable, str(lab / 'fixture/run.py'), str(lab), '--future-authorization', str(auth)] + with (lab / 'raw/runner.stdout').open('x') as out, (lab / 'raw/runner.stderr').open('x') as err: + proc = subprocess.run(argv, env=env, stdout=out, stderr=err) + exclusive(lab / 'results/dispatch.json', dict(argv=argv, exit_code=proc.returncode, launch_consumed=True, + runner_sha256=sha(lab / 'fixture/run.py'))) + return proc.returncode + + +if __name__ == '__main__': + raise SystemExit(launch(Path(sys.argv[1]))) diff --git a/tooling/probes/step20c-static-nested-runtime/tests_offline.py b/tooling/probes/step20c-static-nested-runtime/tests_offline.py new file mode 100644 index 0000000..a078d21 --- /dev/null +++ b/tooling/probes/step20c-static-nested-runtime/tests_offline.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +"""File-backed authorization attacks and real ledger tests, never runtime. + +CONTRACT: immutable LAB/historical files are never edited by negative tests. +Alternate records model received corrupt inputs; actual verification functions +still execute. Manifest attacks use real disposable files and real SHA checks. +""" +import copy +import json +from pathlib import Path +import tempfile +import unittest +from unittest.mock import patch +import handoff as h + +LAB = None + + +class Gate(unittest.TestCase): + def auth_attack(self, key, value): + auth = h.read(LAB / 'results/authorization.json') + auth[key] = value + with tempfile.TemporaryDirectory(prefix='nested-auth-negative-') as tmp: + p = Path(tmp) / 'authorization.json' + p.write_text(json.dumps(auth)) + with self.assertRaises((ValueError, AssertionError, OSError, KeyError)): + h.preflight(LAB, p, review=False) + + def record_attack(self, name, change): + actual = h.read + def read(path): + value = actual(path) + if Path(path) == LAB / 'results' / name: + value = copy.deepcopy(value) + change(value) + return value + with patch.object(h, 'read', read): + with self.assertRaises((ValueError, AssertionError, OSError, KeyError)): + h.preflight(LAB, LAB / 'results/authorization.json', review=False) + + def test_positive_real_preflight_without_review(self): + r = h.preflight(LAB, LAB / 'results/authorization.json', review=False) + self.assertEqual(r['handoff'], 'PENDING_INDEPENDENT_REVIEW') + self.assertFalse(r['run_reserved']) + + def test_no_authorization(self): + with self.assertRaises(ValueError): + h.preflight(LAB, None, review=False) + + def test_old_ticket(self): + self.auth_attack('ticket', 'SWAYFX_MOSAIC_STEP20C_NESTED_OFFLINE_FREEZE_AND_RECEIPT_INTEGRITY_V1') + + def test_wrong_lab(self): + self.auth_attack('registered_lab', str(h.OLD_LAB)) + + def test_exhausted_historical_lab(self): + auth = h.read(LAB / 'results/authorization.json') + old = Path('/home/fy59/swayfx-mosaic-static-nested-grab-m0a5e2qh') + auth['registered_lab'] = str(old) + with tempfile.TemporaryDirectory(prefix='nested-old-lab-negative-') as tmp: + p = Path(tmp) / 'authorization.json' + p.write_text(json.dumps(auth)) + with self.assertRaises(ValueError): + h.preflight(old, p, review=False) + + def test_wrong_owner(self): + self.auth_attack('owner_registry', '/tmp/nonexistent-old-campaign-owner') + + def test_wrong_uid(self): + self.auth_attack('uid', h.os.getuid() + 1) + + def test_wrong_binary(self): + self.auth_attack('binary', '/usr/bin/sway') + + def test_wrong_binary_hash(self): + self.auth_attack('binary_sha256', '0' * 64) + + def test_wrong_wlroots(self): + self.auth_attack('wlroots_sha256', '0' * 64) + + def test_wrong_fixture_hash(self): + self.auth_attack('fixture_sha256', '0' * 64) + + def test_review_not_accepted_original_gate(self): + # The unchanged authorize() reads the actual file-backed gate/review. + # Preserve original accepted bytes in an isolated negative-test LAB, + # then downgrade its review; no accepted review is invented. + with tempfile.TemporaryDirectory(prefix='nested-review-negative-') as tmp: + lab = Path(tmp) + (lab / 'results').mkdir() + auth = h.read(LAB / 'results/authorization.json') + auth['registered_lab'] = str(lab) + (lab / 'auth.json').write_text(json.dumps(auth)) + (lab / 'results/offline-gate.json').write_bytes((LAB / 'results/offline-gate.json').read_bytes()) + review = h.read(LAB / 'results/independent-review.json') + review['status'] = 'CORRECTION_REQUIRED' + (lab / 'results/independent-review.json').write_text(json.dumps(review)) + with self.assertRaises(ValueError): + h.authorize(lab, lab / 'auth.json') + + def test_offline_gate_not_ready_original_gate(self): + with tempfile.TemporaryDirectory(prefix='nested-ready-negative-') as tmp: + lab = Path(tmp) + (lab / 'results').mkdir() + auth = h.read(LAB / 'results/authorization.json') + auth['registered_lab'] = str(lab) + (lab / 'auth.json').write_text(json.dumps(auth)) + gate = h.read(LAB / 'results/offline-gate.json') + gate['marker'] = 'INCONCLUSIVE' + (lab / 'results/offline-gate.json').write_text(json.dumps(gate)) + (lab / 'results/independent-review.json').write_bytes((LAB / 'results/independent-review.json').read_bytes()) + with self.assertRaises(ValueError): + h.authorize(lab, lab / 'auth.json') + + def test_missing_manifest(self): + actual = h.verify + with tempfile.TemporaryDirectory(prefix='nested-manifest-negative-') as tmp: + missing = Path(tmp) / 'absent' + def verify(path): + return actual(missing if Path(path) == LAB / 'results/fixture-freeze-sha256.txt' else path) + with patch.object(h, 'verify', verify), self.assertRaises(OSError): + h.preflight(LAB, LAB / 'results/authorization.json', review=False) + + def test_changed_manifest_entry(self): + actual = h.verify + with tempfile.TemporaryDirectory(prefix='nested-manifest-negative-') as tmp: + manifest = Path(tmp) / 'fixture-freeze-sha256.txt' + rows = (LAB / 'results/fixture-freeze-sha256.txt').read_text().splitlines(True) + rows[0] = '0' * 64 + rows[0][64:] + manifest.write_text(''.join(rows)) + def verify(path): + return actual(manifest if Path(path) == LAB / 'results/fixture-freeze-sha256.txt' else path) + with patch.object(h, 'verify', verify), self.assertRaises(AssertionError): + h.preflight(LAB, LAB / 'results/authorization.json', review=False) + + def test_run_already_consumed(self): + def consume(b): + b['used']['run'] = 1 + b['events'] = [dict(seq=0, kind='run', ns=1)] + self.record_attack('budget.json', consume) + + def test_dispatch_already_consumed(self): + with tempfile.TemporaryDirectory(prefix='nested-consumed-negative-') as tmp: + p = Path(tmp) / 'spent' + p.write_text('consumed') + with patch.object(h, 'SPENT', p), self.assertRaises(ValueError): + h.preflight(LAB, LAB / 'results/authorization.json', review=False) + + def test_durable_exclusive_consumption(self): + with tempfile.TemporaryDirectory(prefix='nested-exclusive-test-') as tmp: + p = Path(tmp) / 'spent' + h.exclusive(p, dict(ticket=h.TICKET)) + before = p.read_bytes() + with self.assertRaises(FileExistsError): + h.exclusive(p, dict(ticket='second')) + self.assertEqual(p.read_bytes(), before) + + def test_ledger_exhaustion_and_atomic_bundle(self): + from ledger import initial_for_offline_test, reserve + with tempfile.TemporaryDirectory(prefix='geometry-ledger-test-') as tmp: + p = Path(tmp) / 'budget.json' + initial_for_offline_test(p) + reserve(p, 'run') + before = p.read_bytes() + with self.assertRaises(ValueError): + reserve(p, 'run', 'click') + self.assertEqual(p.read_bytes(), before) + self.assertEqual(h.valid(h.read(p))['used']['run'], 1) + + +if __name__ == '__main__': + import sys + LAB = Path(sys.argv[1]).resolve() + result = unittest.TextTestRunner(verbosity=2).run(unittest.defaultTestLoader.loadTestsFromTestCase(Gate)) + h.exclusive(LAB / 'results/preflight-tests.json', dict(status='PASS' if result.wasSuccessful() else 'FAIL', + tests=result.testsRun, failures=len(result.failures), errors=len(result.errors), + source_sha256=h.sha(Path(__file__)), run_reserved=False, runtime_runs=0)) + raise SystemExit(0 if result.wasSuccessful() else 1)