From 5e2961d05b7b33252718aabc4dbf787724f79ef0 Mon Sep 17 00:00:00 2001 From: fy59 Date: Tue, 13 Oct 2026 07:23:41 +0200 Subject: [PATCH] docs(mosaic): preserve static runtime and package contract evidence --- AGENTS.md | 10 +- CHANGELOG.md | 2 + docs/ARCHITECTURE.md | 37 ++++ docs/DEVELOPMENT.md | 28 +++ docs/KNOWN_LIMITATIONS.md | 37 ++++ docs/PACKAGING.md | 33 +++ docs/README.md | 37 ++++ docs/ROADMAP.md | 37 ++++ docs/UPSTREAM.md | 28 +++ ...20C_STATIC_RUNTIME_AND_PACKAGE_CONTRACT.md | 205 ++++++++++++++++++ .../probes/step20c-static-runtime/README.md | 79 +++++++ .../probes/step20c-static-runtime/audit.py | 89 ++++++++ .../probes/step20c-static-runtime/clients.py | 29 +++ .../probes/step20c-static-runtime/collect.py | 63 ++++++ .../probes/step20c-static-runtime/prepare.py | 69 ++++++ tooling/probes/step20c-static-runtime/run.py | 169 +++++++++++++++ tooling/probes/step20c-static-runtime/seal.py | 33 +++ .../probes/step20c-static-runtime/stage.py | 32 +++ .../probes/step20c-static-runtime/support.py | 84 +++++++ .../step20c-static-runtime/test-budget.py | 25 +++ 20 files changed, 1124 insertions(+), 2 deletions(-) create mode 100644 docs/design/STEP20C_STATIC_RUNTIME_AND_PACKAGE_CONTRACT.md create mode 100644 tooling/probes/step20c-static-runtime/README.md create mode 100644 tooling/probes/step20c-static-runtime/audit.py create mode 100644 tooling/probes/step20c-static-runtime/clients.py create mode 100644 tooling/probes/step20c-static-runtime/collect.py create mode 100644 tooling/probes/step20c-static-runtime/prepare.py create mode 100644 tooling/probes/step20c-static-runtime/run.py create mode 100644 tooling/probes/step20c-static-runtime/seal.py create mode 100644 tooling/probes/step20c-static-runtime/stage.py create mode 100644 tooling/probes/step20c-static-runtime/support.py create mode 100644 tooling/probes/step20c-static-runtime/test-budget.py diff --git a/AGENTS.md b/AGENTS.md index 174895e..0ae1ba0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -115,7 +115,11 @@ Resolve contradictory canonical documents **before** implementing behavior. Mosaic-modified checkout. For documentation-only changes, validate links, consistency, and `git diff --check`; do not claim runtime tests were run. 6. For packaging tasks, use **DESTDIR staging only** and verify the complete - manifest contains exactly `/usr/bin/swayfx-mosaic`. No live install, sudo, + manifest contains exactly `/usr/bin/swayfx-mosaic` for the current package. + The approved private future STEP20C candidate may additionally stage only + justified notices under `/usr/share/licenses/swayfx-mosaic/`. This exception + does not authorize PKGBUILD changes or real installation; coexistence with + standard SwayFX is mandatory. No live install, sudo, `pacman`, session switch or system library replacement without explicit user authorization. @@ -125,7 +129,9 @@ Resolve contradictory canonical documents **before** implementing behavior. `fd71a6bdc061bd633b488ae7b83e8a6981d22f86`, wlroots 0.20 and SceneFX 0.5. Never assume an upstream update is compatible. - The ordinary SwayFX package owns `/usr/bin/sway`; this project may stage - only `/usr/bin/swayfx-mosaic`. No `provides`, `conflicts` or `replaces` + only `/usr/bin/swayfx-mosaic` for the current package. The private future + STEP20C candidate may also stage justified separate license notices as above. + No `provides`, `conflicts` or `replaces` against sway/SwayFX. Do not alter or block normal SwayFX upgrades. - Remotes: `origin` = canonical Forgejo; `github` = public mirror; `upstream` = SwayFX fetch-only. Never push Mosaic to `upstream`. diff --git a/CHANGELOG.md b/CHANGELOG.md index 30a2ef2..dfb81a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### STEP20C experimental evidence +- Approved private static candidate decisions (2026-10-13): separate notices, project maintenance and exact-pair qualification. Current package unchanged; private real `/usr` static build/model/staging/library identity/headless startup pass. Whole-H2 reference fixture failed after a focus change; input and splith return NOT TESTED. PARTIALLY_FEASIBLE / UNDECIDED, preserved historical evidence; the original manifests and fixture bytes remain unchanged. + - Preserved static distribution contract: one hash-qualified static executable staged through private Meson metadata at /usr/bin/swayfx-mosaic, exact-one-file shape PASS without recompilation. Existing scene interposition is characterized, version/security/notice obligations and an unsubmitted upstream API dossier are explicit. PARTIALLY_FEASIBLE, contract decision required; no recipe, packaging contract, production or system change. Historical verdict retained; later user decisions authorize a separate private gate only. - Preserved SceneFX/wlroots ABI/package audit: current stock build and exact-one-file staging PASS; experimental shared clip functions absent from stock despite common SONAME. Private static build/loader passes, but wlroots interposition, notices, security and version-maintenance gates remain. PARTIALLY_FEASIBLE; architectural decision required. No integration or system change; the historical ABI evidence is published separately from any later distribution-contract investigation. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 8756e12..40c82d4 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -368,3 +368,40 @@ passes mechanically; legal distribution compliance is not approved. The upstream API dossier is preserved and not submitted. Production strategy: **NONE — USER DECISION REQUIRED**. Packaging/UX contracts are unchanged; STEP20C stays NEXT and production multi-output NOT YET SUPPORTED. + +## Approved private STEP20C static candidate — 2026-10-13 + +The user approved four decisions for documentation and private prototypes only: +private static SceneFX; separate complete notices under +`/usr/share/licenses/swayfx-mosaic/`; project responsibility for embedded +SceneFX maintenance; and qualification of each exact SceneFX/wlroots pair. +See the [decision register and bounded runtime gate](design/STEP20C_STATIC_RUNTIME_AND_PACKAGE_CONTRACT.md). +The current PKGBUILD, system SceneFX dependency, one-binary package, production +sources and cumulative patch remain unchanged. The future private stage may +contain only `/usr/bin/swayfx-mosaic` and justified regular notice files below +the license directory. No private shared library, session, service, config or +Sway symlink is allowed. No real installation is authorized. + +Mosaic owns monitoring, impact review, rebuild and requalification of embedded +SceneFX fixes, including renderer/shaders/dependencies/ABI/buffer lifetime/clip/ +damage changes. Before public distribution, a release maintainer and security +reviewer must be designated; no names or SLA are assigned. Preserve exact source, +patch, compiler/options/dependency, notice, test and artifact manifests. +A new relevant dependency build requires header/symbol/shared-layout inspection, +rebuild, relocation/model/staging/headless checks and graphical requalification +of relevant paths, then a separate publication decision. No automatic update +subsystem is added. Ordinary SwayFX upgrades remain independent and standard +SwayFX remains the fallback. All other pairs are NOT QUALIFIED. + +These decisions do not settle popup UX or close STEP20C production gates. +STEP20A DONE; STEP20B EXPERIMENTAL; STEP20C NEXT; STEP20D–G PLANNED. +Production multi-output remains NOT YET SUPPORTED. + +The [private static runtime gate](design/STEP20C_STATIC_RUNTIME_AND_PACKAGE_CONTRACT.md) +is **EXPERIMENTAL / PARTIALLY_FEASIBLE**, architecture **UNDECIDED**. Real `/usr` +static build/model/stage/notices/loaded-library identity/headless startup pass. +Sampled H1 target pixels survive with zero target colors on H2, but whole-H2 +reference equality fails through a focus/decor mismatch; input and splith return +are NOT TESTED after the single run stopped. No rerun or algorithm modification +occurred. Distribution notice closure, named release/security roles and broader +ABI/graphics gates remain open. STEP20C NEXT, multi-output NOT YET SUPPORTED. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 1f3fac7..709693f 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -116,3 +116,31 @@ Then review the patch and run the package checks in [Packaging](PACKAGING.md). ` 7. Publish only after the new base is validated. The normal SwayFX package continues updating independently. See [Packaging](PACKAGING.md) and [Upstream](UPSTREAM.md) for release invariants. Desktop key bindings belong to user dotfiles and are not installed here. + +## Approved private STEP20C static candidate — 2026-10-13 + +The user approved four decisions for documentation and private prototypes only: +private static SceneFX; separate complete notices under +`/usr/share/licenses/swayfx-mosaic/`; project responsibility for embedded +SceneFX maintenance; and qualification of each exact SceneFX/wlroots pair. +See the [decision register and bounded runtime gate](design/STEP20C_STATIC_RUNTIME_AND_PACKAGE_CONTRACT.md). +The current PKGBUILD, system SceneFX dependency, one-binary package, production +sources and cumulative patch remain unchanged. The future private stage may +contain only `/usr/bin/swayfx-mosaic` and justified regular notice files below +the license directory. No private shared library, session, service, config or +Sway symlink is allowed. No real installation is authorized. + +Mosaic owns monitoring, impact review, rebuild and requalification of embedded +SceneFX fixes, including renderer/shaders/dependencies/ABI/buffer lifetime/clip/ +damage changes. Before public distribution, a release maintainer and security +reviewer must be designated; no names or SLA are assigned. Preserve exact source, +patch, compiler/options/dependency, notice, test and artifact manifests. +A new relevant dependency build requires header/symbol/shared-layout inspection, +rebuild, relocation/model/staging/headless checks and graphical requalification +of relevant paths, then a separate publication decision. No automatic update +subsystem is added. Ordinary SwayFX upgrades remain independent and standard +SwayFX remains the fallback. All other pairs are NOT QUALIFIED. + +These decisions do not settle popup UX or close STEP20C production gates. +STEP20A DONE; STEP20B EXPERIMENTAL; STEP20C NEXT; STEP20D–G PLANNED. +Production multi-output remains NOT YET SUPPORTED. diff --git a/docs/KNOWN_LIMITATIONS.md b/docs/KNOWN_LIMITATIONS.md index 8e39521..f7e52c5 100644 --- a/docs/KNOWN_LIMITATIONS.md +++ b/docs/KNOWN_LIMITATIONS.md @@ -309,3 +309,40 @@ passes mechanically; legal distribution compliance is not approved. The upstream API dossier is preserved and not submitted. Production strategy: **NONE — USER DECISION REQUIRED**. Packaging/UX contracts are unchanged; STEP20C stays NEXT and production multi-output NOT YET SUPPORTED. + +## Approved private STEP20C static candidate — 2026-10-13 + +The user approved four decisions for documentation and private prototypes only: +private static SceneFX; separate complete notices under +`/usr/share/licenses/swayfx-mosaic/`; project responsibility for embedded +SceneFX maintenance; and qualification of each exact SceneFX/wlroots pair. +See the [decision register and bounded runtime gate](design/STEP20C_STATIC_RUNTIME_AND_PACKAGE_CONTRACT.md). +The current PKGBUILD, system SceneFX dependency, one-binary package, production +sources and cumulative patch remain unchanged. The future private stage may +contain only `/usr/bin/swayfx-mosaic` and justified regular notice files below +the license directory. No private shared library, session, service, config or +Sway symlink is allowed. No real installation is authorized. + +Mosaic owns monitoring, impact review, rebuild and requalification of embedded +SceneFX fixes, including renderer/shaders/dependencies/ABI/buffer lifetime/clip/ +damage changes. Before public distribution, a release maintainer and security +reviewer must be designated; no names or SLA are assigned. Preserve exact source, +patch, compiler/options/dependency, notice, test and artifact manifests. +A new relevant dependency build requires header/symbol/shared-layout inspection, +rebuild, relocation/model/staging/headless checks and graphical requalification +of relevant paths, then a separate publication decision. No automatic update +subsystem is added. Ordinary SwayFX upgrades remain independent and standard +SwayFX remains the fallback. All other pairs are NOT QUALIFIED. + +These decisions do not settle popup UX or close STEP20C production gates. +STEP20A DONE; STEP20B EXPERIMENTAL; STEP20C NEXT; STEP20D–G PLANNED. +Production multi-output remains NOT YET SUPPORTED. + +The [private static runtime gate](design/STEP20C_STATIC_RUNTIME_AND_PACKAGE_CONTRACT.md) +is **EXPERIMENTAL / PARTIALLY_FEASIBLE**, architecture **UNDECIDED**. Real `/usr` +static build/model/stage/notices/loaded-library identity/headless startup pass. +Sampled H1 target pixels survive with zero target colors on H2, but whole-H2 +reference equality fails through a focus/decor mismatch; input and splith return +are NOT TESTED after the single run stopped. No rerun or algorithm modification +occurred. Distribution notice closure, named release/security roles and broader +ABI/graphics gates remain open. STEP20C NEXT, multi-output NOT YET SUPPORTED. diff --git a/docs/PACKAGING.md b/docs/PACKAGING.md index 5ce821e..98d4f5b 100644 --- a/docs/PACKAGING.md +++ b/docs/PACKAGING.md @@ -63,3 +63,36 @@ are technically extractable in a disposable copy; complete/legal delivery is unqualified. A separate installed notice (N3) would need an explicit user decision changing the exact-one-file rule. PARTIALLY_FEASIBLE; no production strategy adopted. The historical evidence is preserved; it does not authorize production integration. + +## Approved private STEP20C static candidate — 2026-10-13 + +The user approved four decisions for documentation and private prototypes only: +private static SceneFX; separate complete notices under +`/usr/share/licenses/swayfx-mosaic/`; project responsibility for embedded +SceneFX maintenance; and qualification of each exact SceneFX/wlroots pair. +See the [decision register and bounded runtime gate](design/STEP20C_STATIC_RUNTIME_AND_PACKAGE_CONTRACT.md). +The current PKGBUILD, system SceneFX dependency, one-binary package, production +sources and cumulative patch remain unchanged. The future private stage may +contain only `/usr/bin/swayfx-mosaic` and justified regular notice files below +the license directory. No private shared library, session, service, config or +Sway symlink is allowed. No real installation is authorized. + +Mosaic owns monitoring, impact review, rebuild and requalification of embedded +SceneFX fixes, including renderer/shaders/dependencies/ABI/buffer lifetime/clip/ +damage changes. Before public distribution, a release maintainer and security +reviewer must be designated; no names or SLA are assigned. Preserve exact source, +patch, compiler/options/dependency, notice, test and artifact manifests. +A new relevant dependency build requires header/symbol/shared-layout inspection, +rebuild, relocation/model/staging/headless checks and graphical requalification +of relevant paths, then a separate publication decision. No automatic update +subsystem is added. Ordinary SwayFX upgrades remain independent and standard +SwayFX remains the fallback. All other pairs are NOT QUALIFIED. + +These decisions do not settle popup UX or close STEP20C production gates. +STEP20A DONE; STEP20B EXPERIMENTAL; STEP20C NEXT; STEP20D–G PLANNED. +Production multi-output remains NOT YET SUPPORTED. + +Measured private candidate staging: one real `/usr` binary plus four justified +notice files, STATIC_STAGE_WITH_LICENSES=PASS. This does not change the current +recipe, certify legal distribution closure or qualify runtime input/standard +return. See the linked private gate for complete failed/untested cells. diff --git a/docs/README.md b/docs/README.md index bbffb5a..b9bf52b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -227,3 +227,40 @@ STEP20C stays NEXT and production multi-output NOT YET SUPPORTED. The [static-contract source fixture](../tooling/probes/step20c-scenefx-static-contract/README.md) preserves the bounded audit/staging procedure; all generated results stay external. + +## Approved private STEP20C static candidate — 2026-10-13 + +The user approved four decisions for documentation and private prototypes only: +private static SceneFX; separate complete notices under +`/usr/share/licenses/swayfx-mosaic/`; project responsibility for embedded +SceneFX maintenance; and qualification of each exact SceneFX/wlroots pair. +See the [decision register and bounded runtime gate](design/STEP20C_STATIC_RUNTIME_AND_PACKAGE_CONTRACT.md). +The current PKGBUILD, system SceneFX dependency, one-binary package, production +sources and cumulative patch remain unchanged. The future private stage may +contain only `/usr/bin/swayfx-mosaic` and justified regular notice files below +the license directory. No private shared library, session, service, config or +Sway symlink is allowed. No real installation is authorized. + +Mosaic owns monitoring, impact review, rebuild and requalification of embedded +SceneFX fixes, including renderer/shaders/dependencies/ABI/buffer lifetime/clip/ +damage changes. Before public distribution, a release maintainer and security +reviewer must be designated; no names or SLA are assigned. Preserve exact source, +patch, compiler/options/dependency, notice, test and artifact manifests. +A new relevant dependency build requires header/symbol/shared-layout inspection, +rebuild, relocation/model/staging/headless checks and graphical requalification +of relevant paths, then a separate publication decision. No automatic update +subsystem is added. Ordinary SwayFX upgrades remain independent and standard +SwayFX remains the fallback. All other pairs are NOT QUALIFIED. + +These decisions do not settle popup UX or close STEP20C production gates. +STEP20A DONE; STEP20B EXPERIMENTAL; STEP20C NEXT; STEP20D–G PLANNED. +Production multi-output remains NOT YET SUPPORTED. + +The [private static runtime gate](design/STEP20C_STATIC_RUNTIME_AND_PACKAGE_CONTRACT.md) +is **EXPERIMENTAL / PARTIALLY_FEASIBLE**, architecture **UNDECIDED**. Real `/usr` +static build/model/stage/notices/loaded-library identity/headless startup pass. +Sampled H1 target pixels survive with zero target colors on H2, but whole-H2 +reference equality fails through a focus/decor mismatch; input and splith return +are NOT TESTED after the single run stopped. No rerun or algorithm modification +occurred. Distribution notice closure, named release/security roles and broader +ABI/graphics gates remain open. STEP20C NEXT, multi-output NOT YET SUPPORTED. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 8bf5ff7..6cf0fba 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -313,3 +313,40 @@ passes mechanically; legal distribution compliance is not approved. The upstream API dossier is preserved and not submitted. Production strategy: **NONE — USER DECISION REQUIRED**. Packaging/UX contracts are unchanged; STEP20C stays NEXT and production multi-output NOT YET SUPPORTED. + +## Approved private STEP20C static candidate — 2026-10-13 + +The user approved four decisions for documentation and private prototypes only: +private static SceneFX; separate complete notices under +`/usr/share/licenses/swayfx-mosaic/`; project responsibility for embedded +SceneFX maintenance; and qualification of each exact SceneFX/wlroots pair. +See the [decision register and bounded runtime gate](design/STEP20C_STATIC_RUNTIME_AND_PACKAGE_CONTRACT.md). +The current PKGBUILD, system SceneFX dependency, one-binary package, production +sources and cumulative patch remain unchanged. The future private stage may +contain only `/usr/bin/swayfx-mosaic` and justified regular notice files below +the license directory. No private shared library, session, service, config or +Sway symlink is allowed. No real installation is authorized. + +Mosaic owns monitoring, impact review, rebuild and requalification of embedded +SceneFX fixes, including renderer/shaders/dependencies/ABI/buffer lifetime/clip/ +damage changes. Before public distribution, a release maintainer and security +reviewer must be designated; no names or SLA are assigned. Preserve exact source, +patch, compiler/options/dependency, notice, test and artifact manifests. +A new relevant dependency build requires header/symbol/shared-layout inspection, +rebuild, relocation/model/staging/headless checks and graphical requalification +of relevant paths, then a separate publication decision. No automatic update +subsystem is added. Ordinary SwayFX upgrades remain independent and standard +SwayFX remains the fallback. All other pairs are NOT QUALIFIED. + +These decisions do not settle popup UX or close STEP20C production gates. +STEP20A DONE; STEP20B EXPERIMENTAL; STEP20C NEXT; STEP20D–G PLANNED. +Production multi-output remains NOT YET SUPPORTED. + +The [private static runtime gate](design/STEP20C_STATIC_RUNTIME_AND_PACKAGE_CONTRACT.md) +is **EXPERIMENTAL / PARTIALLY_FEASIBLE**, architecture **UNDECIDED**. Real `/usr` +static build/model/stage/notices/loaded-library identity/headless startup pass. +Sampled H1 target pixels survive with zero target colors on H2, but whole-H2 +reference equality fails through a focus/decor mismatch; input and splith return +are NOT TESTED after the single run stopped. No rerun or algorithm modification +occurred. Distribution notice closure, named release/security roles and broader +ABI/graphics gates remain open. STEP20C NEXT, multi-output NOT YET SUPPORTED. diff --git a/docs/UPSTREAM.md b/docs/UPSTREAM.md index 936b4c4..2994c43 100644 --- a/docs/UPSTREAM.md +++ b/docs/UPSTREAM.md @@ -33,3 +33,31 @@ The maintained production delta is `patches/0001-mosaic-layout-viewport.patch` o 7. Update [known limitations](KNOWN_LIMITATIONS.md), [roadmap](ROADMAP.md), and release notes with the observed result. A failed patch check or changed compositor contract blocks that candidate release until resolved. It does not justify modifying the ordinary SwayFX package or using an ABI-incompatible binary workaround. The normal SwayFX package must remain independently updatable. + +## Approved private STEP20C static candidate — 2026-10-13 + +The user approved four decisions for documentation and private prototypes only: +private static SceneFX; separate complete notices under +`/usr/share/licenses/swayfx-mosaic/`; project responsibility for embedded +SceneFX maintenance; and qualification of each exact SceneFX/wlroots pair. +See the [decision register and bounded runtime gate](design/STEP20C_STATIC_RUNTIME_AND_PACKAGE_CONTRACT.md). +The current PKGBUILD, system SceneFX dependency, one-binary package, production +sources and cumulative patch remain unchanged. The future private stage may +contain only `/usr/bin/swayfx-mosaic` and justified regular notice files below +the license directory. No private shared library, session, service, config or +Sway symlink is allowed. No real installation is authorized. + +Mosaic owns monitoring, impact review, rebuild and requalification of embedded +SceneFX fixes, including renderer/shaders/dependencies/ABI/buffer lifetime/clip/ +damage changes. Before public distribution, a release maintainer and security +reviewer must be designated; no names or SLA are assigned. Preserve exact source, +patch, compiler/options/dependency, notice, test and artifact manifests. +A new relevant dependency build requires header/symbol/shared-layout inspection, +rebuild, relocation/model/staging/headless checks and graphical requalification +of relevant paths, then a separate publication decision. No automatic update +subsystem is added. Ordinary SwayFX upgrades remain independent and standard +SwayFX remains the fallback. All other pairs are NOT QUALIFIED. + +These decisions do not settle popup UX or close STEP20C production gates. +STEP20A DONE; STEP20B EXPERIMENTAL; STEP20C NEXT; STEP20D–G PLANNED. +Production multi-output remains NOT YET SUPPORTED. diff --git a/docs/design/STEP20C_STATIC_RUNTIME_AND_PACKAGE_CONTRACT.md b/docs/design/STEP20C_STATIC_RUNTIME_AND_PACKAGE_CONTRACT.md new file mode 100644 index 0000000..27f78b4 --- /dev/null +++ b/docs/design/STEP20C_STATIC_RUNTIME_AND_PACKAGE_CONTRACT.md @@ -0,0 +1,205 @@ +# STEP20C private static runtime and package contract + +**EXPERIMENTAL / PRESERVED HISTORICAL EVIDENCE.** No production integration or install. +Protocol established before builds and runtime. Historical verdicts remain +unchanged; [static distribution evidence](STEP20C_SCENEFX_STATIC_DISTRIBUTION_CONTRACT.md) +was published separately. [Packaging](../PACKAGING.md) governs file ownership; +[Architecture](../ARCHITECTURE.md) preserves compositor authority. + +## Decision register — 2026-10-13 + +| Decision | Motivation and authorized scope | Prohibited / residual risks / revision gates | +| --- | --- | --- | +| D1 private static SceneFX | Historical ELF resolves both private clips without system SceneFX replacement; build/run private candidate | No production integration, algorithm change or installed library replacement; semantic interposition and full graphical matrix remain open | +| D2 separate notices | Preserve full incorporated-code attributions and permissions without artificial one-file constraint; private future stage only | Current recipe unchanged; exact notice inventory must follow link inputs and generated protocols; legal clearance not asserted | +| D3 project maintenance | Embedded code does not inherit system SceneFX fixes; project monitors/reviews/rebuilds/requalifies | No named owner/SLA; release maintainer/security reviewer required before distribution; no automatic updater | +| D4 strict pair qualification | Unstable shared scene/renderer types and 85 overlaps require exact build evidence | No inference to later versions, no blocking stock upgrades; inspect headers/symbols/layouts, rebuild, relocations/model/stage/headless/graphics, separate release decision | + +Current package: unchanged PKGBUILD, system SceneFX, only `/usr/bin/swayfx-mosaic`, +no STEP20C implementation. Future experimental candidate: static SceneFX, shared +wlroots, system libraries intact, binary plus inventoried notices only. +Long-term direction remains SCENEFX_UPSTREAM_MAINTAINED_CLIP_API; reuse the +historical unsubmitted dossier. Ownership/errors/bounds/lifetime/hit/damage/ +versioning need a maintained API; popup ownership remains Mosaic-specific. +No contact or upstream adoption is presumed. + +## Bounded protocol + +One external LAB, one SceneFX static build, one Mosaic static build with +`--prefix=/usr --bindir=bin -Dmosaic-package=true`, one Meson DESTDIR stage, +one notice addition and one ELF/dependency audit. Reconstruct pinned Mosaic +`cd550382b311efdbfe3ed892f4e1d281d852e754`, SceneFX +`37ccd723bef49e6891156ffafce8f549f01446cc`, SwayFX +`fd71a6bdc061bd633b488ae7b83e8a6981d22f86` and wlroots +`d783533489e1f75d6886c2ab5c5960090ef268f8`; reuse the historical ordered +eight private patches unchanged, verify source equivalence. wlroots stays shared. + +A persistent exclusively locked ledger reserves every operation before execution; +failures consume reservations. Test the ledger offline first. One principal run, +optional one indispensable pristine control, at most eight PNGs, one calculated +camera correction, no hide/return pair, 150 seconds per run. Explicit private +mode-0700 runtime/Wayland/IPC sockets; staged executable only. Verify process +mappings before clients or clipping: no SceneFX .so, exact system wlroots and +no historical LAB. Wrong identity stops the run. + +HEADLESS-1=(-800,0), HEADLESS-2=(0,0), each 800×600, scale 1, normal. +Use verified historical ordinary SHM client/reference and virtual pointer; verify +actual target crossing x=0, retained H1 pixels, excluded H2 pixels and measured +hits. Return to splith and inspect clip removal. Root valid-grab popup may be +reused only without widening protocol; otherwise NOT TESTED. No nested campaign. +Damage extents are sampled and never establish every-frame coverage. Stop +concerned scenario on crash/ABI/clip/hit leak; no algorithm repairs. Historical +pristine evidence can provide reference semantics, without fresh campaign. + +License inventory follows actual linker/compile/generated inputs, preserved +full notices and attribution/provenance/hash/destination. Dynamic system-library +licenses are not indiscriminately copied. Unclosed inventory prevents a ready +distribution verdict. Reproducible procedure is distinct from bit reproducibility. + +Results and raw commands/exit codes live outside Git. All untested matrix cells +are NOT TESTED. FEASIBLE is limited to the tested matrix; PARTIALLY_FEASIBLE +retains qualification limits; INCONCLUSIVE reflects unreliable/missing runtime +evidence; BLOCKED requires a demonstrated incompatibility/crash/forbidden leak. +STEP20C remains NEXT; production multi-output NOT YET SUPPORTED. + +## Completed private campaign + +LAB: `/home/fy59/swayfx-mosaic-static-runtime-hm5kbdkt`. +The preceding static-distribution freeze commit was +`4f4f3fefcdcf979f8be126d6671062ecb1d65b50`, published on both remotes. +The runtime campaign below was phase B of that earlier ticket. Its evidence +is now preserved by the separate static-runtime freeze ticket; it is not the +new focus/input/standard-return campaign. + +`SWAYFX_MOSAIC_STATIC_RUNTIME_PACKAGE_GATE=PARTIALLY_FEASIBLE` + +Architecture: **UNDECIDED**. The single runtime comparison scenario is +**INCONCLUSIVE**; no reproduced ABI crash or forbidden target hit is established. + +| Gate | Observation | +| --- | --- | +| Source/build | Eight unchanged patches applied; full private source equivalence PASS; one static SceneFX and one Mosaic build, model 1/1 PASS | +| Prefix | Real Meson `/usr`, bindir `bin`, compiled sysconfdir `/etc` (compiler spelling `//etc`); no copied `/usr/local` binary | +| Staging | STATIC_STAGE_WITH_LICENSES=PASS; five regular files; user-owned private DESTDIR | +| ELF | ELF64 x86-64, both private clip T definitions and direct calls, no SceneFX DT_NEEDED, no RPATH/RUNPATH, ldd -r without unresolved symbols | +| Runtime | Staged executable actually loaded; exact system wlroots and no shared SceneFX in /proc maps; two prescribed outputs, IPC and ordinary clients mapped | +| Renderer | Actual SceneFX FX / OpenGL ES 3.2 Mesa 26.2.4, AMD Radeon 780M; headless backend, no physical display session | +| Interposition | 34 observed runtime wlroots scene bindings: 31 into executable, three wlroots helper providers unchanged | +| Applied clipping | +160 sole camera correction, owner scene x=-320,width=640; local inherited clip=(160,0,800,600), both set/get paths observed through retained traces | +| Pixels | 182,214 H1 target-color pixels, zero H2 target colors; H2 content crop identical | +| Reference gate | Whole H2 FAIL: 23,807 changed pixels outside IPC content bounds, coherent with focused→unfocused decorations; cannot claim whole-output equality | +| Input/standard return | NOT TESTED: run stopped at reference gate before button scoring and splith | +| Popup | Root/nested grabbed popup NOT TESTED; historical controls retained, no fresh pristine run | +| Damage | Native TEMPORAL_DAMAGE extents retained; complete frame coverage NOT TESTED | +| Budget | scene=1,mosaic=1,stage=1,notices=1,ELF=1,principal run=1,control=0,camera=1,PNG=3; ~3.999 seconds, no retries/refunds | +| Cleanup | Compositor exit 0 on owned SIGTERM cleanup, clients exit 0; pointer terminated -15; all owned groups reaped and private runtime removed | + +The build/stage/executed binary SHA-256 is +`f82557c1a9978a5e9665b9b4291b74880253725359e3df1c5b8d554921f7603b`, +size 6,760,304 bytes. Different bytes/size from the historical binary are not +a regression claim; prefix/source/build paths differ. +Qualified shared wlroots build: Arch `wlroots0.20 0.20.2-1`, ELF SHA-256 +`7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837`, +Build ID `2cd3a56136647a9da32b9a682606911aa2498393`. Installed nongenerated +headers match the pinned archive; selected historical layouts are retained, +without rerunning the old ABI campaign. Other pairs NOT QUALIFIED. This pair's +input, standard return and wider graphical behavior remain unqualified. + +### Notice manifest and distribution boundary + +Meson produced only `/usr/bin/swayfx-mosaic`. The future-package simulation +added four 0644 files under `/usr/share/licenses/swayfx-mosaic/`: +`SwayFX-LICENSE`, `SceneFX-LICENSE`, `wlroots-LICENSE`, +`INCORPORATED-NOTICES`. Root texts are byte-identical to pinned sources; +compiled generated protocol copyright/permission/warranty comments are included +verbatim with their source provenance. wlroots notice covers copied code in +SceneFX, not a dump of every dynamic system dependency. The inventory records +compile commands, source hashes, protocol comments, destinations and root +attribution. Notice hashes and the complete five-file manifest are in results. +The technical stage passes; exact unused archive-member extraction, all copied +header/shader notice closure and final legal review remain distribution gates. +No definitive legal compliance is asserted. + +### Failed fixture checks and reproducibility limits + +The first offline multiprocessing ledger test failed for missing `__main__` +guard on Python 3.14; corrected and PASS before runtime. The ELF audit completed +its one diagnostic batch and notice addition, then failed an incorrect assertion +looking for `/usr` in config.h. Stage validation instead verifies actual Meson +options and compiler SYSCONFDIR. That erroneous assertion was corrected locally, +without rerunning the audit batch or repeating staging. Initial direct loader +trace yielded no scene bindings; real runtime LD_DEBUG provides the 31/3 result. +The local audit fixture now uses diagnostic ldd -r for future reproduction; this +correction has NOT been executed on a second binary. + +The principal run compared H2 while focused at baseline and unfocused in Mosaic. +The whole-output assertion stopped the scenario. Offline IPC/color comparison +localizes all differences outside the unchanged content rectangle; this supports +a reference-fixture mismatch, not a demonstrated forbidden clip leak. No new +run, capture, algorithm fix or budget reset follows. The collector's initial +crop double-counted decoration height; its corrected offline crop follows IPC +rect/window_rect and passes. Failures are retained in the correction record. +The initial audit source reconstruction is documented as reconstructed after +correction, not contemporaneously hashed. Current fixture corrections are not +additional executed evidence. Bit reproducibility needs independent builds and +remains NOT TESTED under this one-build budget. + +Next smallest justified ticket: establish a reference focus state before its +baseline capture, then authorize one bounded same-static-binary run for measured +clipped input and splith return. Keep notices/maintainer designation/full ABI and +existing STEP20C graphics/generation/lifetime gates separate before any real +PKGBUILD change. No integration approval follows from this partial result. + +The [source-only fixture](../../tooling/probes/step20c-static-runtime/README.md) +records commands, budgets and failed-check limitations. The run's field named +`source_box` used applied owner scene x plus IPC window offsets; its y=0 was an +estimate missing title decoration. Offline color bounds independently locate +visible content at H1 local `(482,25)..(800,598)`, consistent with global +x=-318 crossing. No claim of exact raw buffer y identity is made from that field. +The final parent audit checks contracts, delta, raw provenance and limitations; +no independent subagent review was invoked. + +## Sealed integrity receipt + +Source manifest: 1,397 entries, SHA-256 +`ed55bd418595ca977c022fa98917a899072cf3b6f0fb15457db823081046e797`. +Evidence manifest: 146 entries, SHA-256 +`e260ce5793ffc9a29ee4bfa2189663feeb6dc025bd46d3f5ddebccd225f3eef1`. +Both were created once and all entries reverified. This report and full delta +are outside the manifest set to avoid circular digests. Historical 1,892/143 +manifests still verify through the pre-freeze snapshot mapping. No manifest +was regenerated or overwritten. + +Final AST, shell/recipe syntax, local Markdown links, source-only fixture, +whitespace, stage identity/modes/hash, budget, protected system/source/recipe/ +patch checks and owned-process/socket cleanup PASS. Runtime reference gate +FAIL is retained; input/standard return and broader tests NOT RUN / NOT TESTED. +No B commit, push, production edit or installation occurred. + +## Static-runtime evidence freeze — 2026-10-13 + +The new freeze audit starts at main `4f4f3fefcdcf979f8be126d6671062ecb1d65b50`, +with both directly queried remote main references equal. Only the expected +documentation and source-only static-runtime fixture are included. The historical +PARTIALLY_FEASIBLE verdict and UNDECIDED architecture are unchanged. + +All 1,397 source and 146 evidence entries verify against the original manifests. +The ten fixture files, including its README, remain byte-identical because their +absolute paths are sealed in the source manifest. Its LOCAL/UNCOMMITTED wording +and this report's earlier A/B labels describe the historical campaign at closure, +not the later publication state. No corrected runner is executed during freeze. + +Independent offline RGB recount retains 23,807 changed H2 pixels and the complete +image bounding box; the reference interior is identical, 182,214 target pixels +remain on H1, and no target color appears on H2. These findings support a focus/ +decoration fixture explanation without replacing the failed whole-H2 comparison. +The original scene-derived source_box is not an exact native buffer rectangle. +Input, splith return and popups remain NOT TESTED for this historical run. + +D1–D4 remain approved only for documentation and private experiments. Static +linkage does not close semantic ABI, legal notice inventory, release/security +ownership or wider graphical gates. The next separately bounded gate reuses +the exact binary, normalizes the H2 reference before comparison, and measures +three button deliveries and standard clip removal. No new build or old-LAB run +is part of this freeze. STEP20C stays NEXT; production multi-output remains +NOT YET SUPPORTED. diff --git a/tooling/probes/step20c-static-runtime/README.md b/tooling/probes/step20c-static-runtime/README.md new file mode 100644 index 0000000..801f1e5 --- /dev/null +++ b/tooling/probes/step20c-static-runtime/README.md @@ -0,0 +1,79 @@ +# Private static Mosaic runtime fixture + +**EXPERIMENTAL / LOCAL / UNCOMMITTED; no production integration.** +Read the [protocol and decisions](../../../docs/design/STEP20C_STATIC_RUNTIME_AND_PACKAGE_CONTRACT.md). +Current package, production patch and installed desktop remain unchanged. + +## Reproduction and one-laboratory ownership + +The completed laboratory is `/home/fy59/swayfx-mosaic-static-runtime-hm5kbdkt`. +Its run reservation is consumed. **Never rerun prepare/audit/run in that LAB, +reset its budget, create a replacement LAB or execute the old campaigns.** +Future independent reproduction requires a new scoped authorization/budget. +Generated sources, build trees, binaries, PNGs, sockets and logs stay outside Git. + +Dependencies: Git, Meson/Ninja, cc C17, pkg-config, readelf/nm/objdump/ldd, +wayland-scanner, swaymsg, grim, Python/Pillow, installed pinned build libraries. +No installation or downloads are performed by these scripts. Local SceneFX +object store must contain the pinned commit. Historical wlroots archive and +private source/patch hashes are verified through the preserved manifests +before prepare; archive provenance remains the sealed ABI campaign. + +For a separately authorized fresh LAB, the ordered commands are: + +```sh +PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-static-runtime/test-budget.py +PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-static-runtime/prepare.py "$TASKLAB" \ + --scenefx-git "$SCENE_STORE" --historical "$VERIFIED_ABI_LAB" +PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-static-runtime/audit.py "$TASKLAB" +PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-static-runtime/stage.py "$TASKLAB" +PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-static-runtime/clients.py "$TASKLAB" +PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-static-runtime/run.py "$TASKLAB" +PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-static-runtime/collect.py "$TASKLAB" +``` + +Prepare reconstructs only the static chain, applies eight unchanged private +patches and checks source equivalence. Mosaic is genuinely compiled with +`--prefix=/usr --bindir=bin -Dmosaic-package=true`; only SceneFX is archived. +Meson installation is DESTDIR only. Audit inventories exact root license texts +and compiled/generated notice comments; exact unused archive-member extraction +and legal clearance remain separate distribution gates. Stage refuses links, +foreign files and modes; exactly one binary and four inventoried notice files +were retained. `collect.py` recalculates structured observations offline. +`seal.py` creates exclusive manifests once or verifies them read-only. + +The budget uses a stable exclusive flock, atomic ledger replacement and fsync. +Build/stage/notice/audit/run/capture/camera failures retain their reservations. +No refund/reset exists. Offline tests exercise concurrent/exhausted reservations. +Each runtime is mode 0700, with explicit private Wayland and swaymsg sockets, +owned process groups, a 150-second watchdog and 64 MiB per-log stop bound. +The compositor executes only the staged binary. /proc executable/maps identity +is checked before any clients. No live desktop socket is used. + +## Completed evidence and limitations + +Build, model, real `/usr` prefix, stage, ELF, library identity, headless outputs, +IPC and ordinary client mapping passed. Runtime measured the same 31 wlroots +scene bindings into the executable and three retained wlroots helpers. The +actual renderer was SceneFX FX / OpenGL ES 3.2, AMD Radeon 780M / Mesa 26.2.4; +`WLR_RENDERER=pixman` did not select pixman in this FX path. + +One correction of +160 produced an applied target scene x=-320, width=640; +its measured H1 colored region retained 182,214 pixels, and H2 contained zero +target-color pixels. The full H2 comparison failed: 23,807 decoration pixels +changed after the reference lost focus. The IPC-derived content crop remained +exact. This supports a fixture focus-state mismatch, without replacing the +failed whole-output gate with a PASS. The run stopped before scored input and +splith return; both and root/nested popups are NOT TESTED. No algorithm changed, +no second run, no pristine run, three PNGs, one camera correction, ~4 seconds. +All owned processes were reaped and the private runtime was removed. + +Overall PARTIALLY_FEASIBLE; runtime comparison scenario INCONCLUSIVE; +architectural conclusion UNDECIDED. The run itself does not demonstrate an +ABI crash or forbidden target hit. Complete license closure, named release/ +security owners, broader graphical/ABI qualification and bit reproducibility +remain open. Current package unchanged, STEP20C NEXT, production multi-output +NOT YET SUPPORTED. See the retained fixture-corrections record for failed +offline checks and corrected, unexecuted diagnostic behavior. A future ticket +should first fix reference focus preflight, then explicitly authorize the +minimal input and standard-return qualification. diff --git a/tooling/probes/step20c-static-runtime/audit.py b/tooling/probes/step20c-static-runtime/audit.py new file mode 100644 index 0000000..bb4ebac --- /dev/null +++ b/tooling/probes/step20c-static-runtime/audit.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +"""Single ELF audit and justified notice stage; never launches compositor code. + +WHY: loader resolution, interposition and notice closure are separate gates. +CONTRACT: only SceneFX is archived; system wlroots remains a shared provider. +INVARIANT: each notice is verbatim, attributed and traced to a compiled input. +""" +import argparse +import json +from pathlib import Path +import re +import stat +from support import command,reserve,save,sha + +p=argparse.ArgumentParser();p.add_argument('lab',type=Path);a=p.parse_args();lab=a.lab.resolve() +binary=lab/'stage-static/usr/bin/swayfx-mosaic';reserve(lab,'elf') +raw={} +for label,argv in [('elf-header',['readelf','-hW']),('elf-dynamic',['readelf','-dW']),('elf-symbols',['readelf','-Ws']),('elf-relocations',['readelf','-rW']),('elf-versions',['readelf','-VW']),('elf-notes',['readelf','-nW']),('elf-nm',['nm','--format=posix']),('elf-objdump',['objdump','-d']),('elf-ldd',['ldd','-r'])]: + raw[label]=command(lab,label,[*argv,binary],timeout=60) +assert 'libscenefx' not in raw['elf-dynamic'] and 'libscenefx' not in raw['elf-ldd'] +assert 'undefined symbol:' not in raw['elf-ldd'] and 'not found' not in raw['elf-ldd'] +assert 'libwlroots-0.20.so' in raw['elf-dynamic'] +assert '(RPATH)' not in raw['elf-dynamic'] and '(RUNPATH)' not in raw['elf-dynamic'] +clips=['wlr_scene_tree_set_clip','wlr_scene_tree_get_clip'] +for name in clips: + assert re.search(r'^'+name+r' T ',raw['elf-nm'],re.M) + assert re.search(r'call\s+[^\n]*<'+name+r'>',raw['elf-objdump']), 'static call site absent' +needed=re.findall(r'\(NEEDED\).*\[([^]]+)\]',raw['elf-dynamic']) +wl=Path('/usr/lib/libwlroots-0.20.so').resolve() +wl_header=command(lab,'wlroots-elf',['readelf','-nW','-dW','-VW',wl],timeout=60) +save(lab,'binary-elf',{'status':'PASS','path':str(binary),'sha256':sha(binary),'bytes':binary.stat().st_size,'DT_NEEDED':needed,'RPATH':[],'RUNPATH':[],'build_id':re.findall(r'Build ID: (\w+)',raw['elf-notes']),'header':raw['elf-header'],'versions_file':'raw/elf-versions.log'}) +save(lab,'static-symbols',{'status':'PASS','clips':clips,'definitions':'ordinary T executable symbols','call_sites':'raw/elf-objdump.log','semantic_ABI':'not established by ELF'}) +# Glibc diagnostic mode exits before the compositor entry point; no run debit. +bindings=command(lab,'loader-bindings',['ldd','-r',binary],extra={'LD_DEBUG':'bindings'},timeout=60) +records=[] +for line in bindings.splitlines(): + m=re.search(r"binding file (.*?) \[0\] to (.*?) \[0\].*symbol `([^']+)'",line) + if m and 'libwlroots' in m[1] and m[3].startswith('wlr_scene_'): + rec={'consumer':m[1],'provider':m[2],'symbol':m[3]} + if rec not in records:records.append(rec) +save(lab,'dependency-resolution',{'status':'PASS','ldd_exit':0,'records':records,'executable_scene_bindings':sum(x['provider']==str(binary) for x in records),'wlroots_scene_bindings':sum('libwlroots' in x['provider'] for x in records),'runtime_identity':'must still be checked from /proc maps'}) +headers=[] +for path in (lab/'wlroots-source/include').rglob('*.h'): + rel=path.relative_to(lab/'wlroots-source/include');installed=Path('/usr/include/wlroots-0.20')/rel + if installed.is_file():headers.append({'path':str(rel),'source_sha256':sha(path),'installed_sha256':sha(installed),'match':sha(path)==sha(installed)}) +assert headers and all(x['match'] for x in headers) +metadata=list(Path('/var/lib/pacman/local').glob('wlroots0.20-*/desc')) +save(lab,'wlroots-qualified-pair',{'SceneFX':'37ccd723bef49e6891156ffafce8f549f01446cc','wlroots_source':'d783533489e1f75d6886c2ab5c5960090ef268f8','wlroots_pkg_config':command(lab,'wlroots-pkg-config',['pkg-config','--modversion','--cflags','--libs','wlroots-0.20']),'library':str(wl),'sha256':sha(wl),'build_id':re.findall(r'Build ID: (\w+)',wl_header),'package_metadata':[{'path':str(x),'text':x.read_text()} for x in metadata],'headers':headers,'features_header':Path('/usr/include/wlroots-0.20/wlr/config.h').read_text(),'struct_layouts':'retained historical 17 structs/6 offsets only; no old ABI probe rerun','other_pairs':'NOT QUALIFIED'}) +# Compile inputs conservatively cover all SceneFX archive members. Some unused +# members may be over-included; no dynamic system-library license dump is used. +inputs=[];notice_records=[];payloads={} +for tree in ('mosaic','scene'): + build=lab/(tree+'-build');commands=json.loads((build/'compile_commands.json').read_text()) + for rec in commands: + src=(build/rec['file']).resolve() + if not src.is_file():continue + inputs.append({'source':str(src),'sha256':sha(src),'object':rec.get('output'),'component':tree,'command':rec['command']}) + text=src.read_text(errors='strict') + comments=[c for c in re.findall(r'/\*.*?\*/',text,re.S) if re.search(r'copyright|permission|redistribution|SPDX',c,re.I)] + for comment in comments: + # SPDX alone names a license; root license text is staged separately. + if not re.search(r'copyright|permission|redistribution',comment,re.I):continue + key=__import__('hashlib').sha256(comment.encode()).hexdigest() + payloads.setdefault(key,comment) + notice_records.append({'source':str(src),'source_sha256':sha(src),'notice_sha256':key,'component':tree,'generated':src.is_relative_to(build)}) +roots=[('SwayFX','swayfx-source/LICENSE'),('SceneFX','scene-source/LICENSE'),('wlroots','wlroots-source/LICENSE')] +root_records=[] +for name,rel in roots: + src=lab/rel;root_records.append({'component':name,'source':str(src),'sha256':sha(src),'destination':f'/usr/share/licenses/swayfx-mosaic/{name}-LICENSE','reason':'compositor code' if name=='SwayFX' else 'static scene/renderer archive including copied wlroots code' if name=='SceneFX' else 'copied scene/renderer code within SceneFX; system wlroots stays dynamic'}) +assert notice_records and any(x['generated'] for x in notice_records) +reserve(lab,'notices');dest=lab/'stage-static/usr/share/licenses/swayfx-mosaic';dest.mkdir(parents=True) +for row in root_records: + path=lab/'stage-static'/row['destination'].lstrip('/');path.write_bytes(Path(row['source']).read_bytes());path.chmod(0o644) +# Each full comment is retained once with all provenance paths, including +# generated protocol supporting-documentation and attribution clauses. +notice_path=dest/'INCORPORATED-NOTICES' +blocks=[] +for key,text in payloads.items(): + sources=[r['source'] for r in notice_records if r['notice_sha256']==key] + blocks.append('Sources:\n'+'\n'.join(sources)+'\n\n'+text) +notice_path.write_text('\n\n'.join(blocks)+'\n');notice_path.chmod(0o644) +save(lab,'license-inventory',{'status':'PARTIAL DISTRIBUTION QUALIFICATION; compiled/root/generated notices inventoried; legal/header/shader/exact extraction closure remains open','roots':root_records,'compiled_inputs':inputs,'source_notices':notice_records,'extra_archive_members':'conservative coverage of archive compile inputs; exact extraction of every unused member not claimed','dynamic_libraries':'not indiscriminately copied','notice_destination':'/usr/share/licenses/swayfx-mosaic/INCORPORATED-NOTICES'}) +save(lab,'license-files',[{'path':str(x.relative_to(lab/'stage-static')),'sha256':sha(x),'bytes':x.stat().st_size} for x in sorted(dest.iterdir())]) +# Compiled source provenance verifies actual config paths as well as Meson opts. +config=(lab/'mosaic-build/include/config.h').read_text();(lab/'raw/compiled-config.h').write_text(config) +compiled=(lab/'mosaic-build/compile_commands.json').read_text() +assert '/usr/local' not in compiled and 'SYSCONFDIR' in compiled +save(lab,'compiled-paths',{'prefix':'/usr','bindir':'bin','sysconfdir':'/etc (compiler spelling //etc)','source':'Meson build options, intro-installed.json and compile_commands.json'}) +print('PASS ELF, clip definitions/calls, relocations, header identity, notices; bindings',len(records)) diff --git a/tooling/probes/step20c-static-runtime/clients.py b/tooling/probes/step20c-static-runtime/clients.py new file mode 100644 index 0000000..1282479 --- /dev/null +++ b/tooling/probes/step20c-static-runtime/clients.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +"""Compile byte-verified historical clients and the preserved pointer adaptation. + +CONTRACT: no held transaction or new popup behavior is added to these fixtures. +""" +import argparse +from pathlib import Path +import shutil +import subprocess +from support import REPO,command,save,sha +p=argparse.ArgumentParser();p.add_argument('lab',type=Path);a=p.parse_args();lab=a.lab.resolve();src=lab/'client-source';src.mkdir() +paths={'saved-client.c':'step20c-saved-target-timeout-input/saved-client.c','static-reference.c':'step20c-saved-target-timeout-input/static-reference.c','pointer-daemon.c':'step20c-valid-grab/pointer-daemon.c','virtual-pointer.xml':'step20c-valid-grab/wlr-virtual-pointer-unstable-v1.xml'} +inventory=[] +for name,rel in paths.items(): + origin=REPO/'tooling/probes'/rel;shutil.copy2(origin,src/name);assert sha(origin)==sha(src/name) + inventory.append({'source':str(origin),'sha256':sha(origin),'destination':str(src/name)}) +patch=REPO/'tooling/probes/step20c-vertical-grab/pointer-extents.patch' +command(lab,'pointer-check',['git','apply','--check',patch],src);command(lab,'pointer-apply',['git','apply',patch],src) +for stem,xml in [('xdg-shell',Path('/usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml')),('virtual-pointer',src/'virtual-pointer.xml')]: + command(lab,stem+'-header',['wayland-scanner','client-header',xml,src/(stem+'-client.h')]) + command(lab,stem+'-code',['wayland-scanner','private-code',xml,src/(stem+'-protocol.c')]) +# The historical pointer include uses its original generated filename. +shutil.copy2(src/'virtual-pointer-client.h',src/'virtual-pointer.h') +flags=subprocess.check_output(['pkg-config','--cflags','--libs','wayland-client'],text=True).split() +for name,proto in [('saved-client','xdg-shell'),('static-reference','xdg-shell'),('pointer-daemon','virtual-pointer')]: + argv=['cc','-std=c17','-Wall','-Wextra','-Werror','-Wno-unused-parameter','-I'+str(src),src/(name+'.c'),src/(proto+'-protocol.c'),*flags] + command(lab,name+'-syntax',[*argv,'-fsyntax-only']) + command(lab,name+'-compile',[*argv,'-o',lab/name]) +save(lab,'client-provenance',{'inputs':inventory,'pointer_patch':{'source':str(patch),'sha256':sha(patch)},'adapted_pointer_sha256':sha(src/'pointer-daemon.c'),'C17':'PASS'}) diff --git a/tooling/probes/step20c-static-runtime/collect.py b/tooling/probes/step20c-static-runtime/collect.py new file mode 100644 index 0000000..c8eccb5 --- /dev/null +++ b/tooling/probes/step20c-static-runtime/collect.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +"""Recalculate bounded results from retained raw evidence, without new execution. + +WHY: a fixture stop must not be upgraded to a demonstrated compositor defect. +CONTRACT: retain failed raw gates and explicit NOT TESTED downstream cells. +INVARIANT: never alter old results/manifests or refund experiment reservations. +""" +import argparse +from collections import Counter +import json +from pathlib import Path +import re +from PIL import Image,ImageChops +from support import REPO,save,sha +p=argparse.ArgumentParser();p.add_argument('lab',type=Path);a=p.parse_args();lab=a.lab.resolve() +assert not (lab/'results/evidence-sha256.txt').exists(),'sealed results are immutable' +run=lab/'run';text=(run/'compositor.log').read_text();runtime=json.loads((lab/'results/static-runtime.json').read_text()) +rows=[] +for line in text.splitlines(): + m=re.search(r"binding file (.*?) \[0\] to (.*?) \[0\].*symbol `([^']+)'",line) + if m and 'libwlroots' in m[1] and m[3].startswith('wlr_scene_'): + row={'consumer':m[1],'provider':m[2],'symbol':m[3]} + if row not in rows:rows.append(row) +binary=lab/'stage-static/usr/bin/swayfx-mosaic';own=[r for r in rows if r['provider']==str(binary)];wl=[r for r in rows if 'libwlroots' in r['provider']] +assert len(rows)==34 and len(own)==31 and len(wl)==3 +expected={'wlr_scene_subsurface_tree_create','wlr_scene_surface_try_from_buffer','wlr_scene_surface_create'} +assert {r['symbol'] for r in wl}==expected +save(lab,'runtime-bindings',{'status':'PASS','records':rows,'executable':31,'wlroots':3,'historical_pattern':'same 31/3 suppliers; full semantic ABI still unproven'}) +renderer=[s for s in text.splitlines() if re.search(r'\[render/fx_renderer/fx_renderer.c:\d+\].*(Using OpenGL|GL vendor:|GL renderer:)',s)] +save(lab,'renderer',{'observed':renderer,'requested_env':'WLR_RENDERER=pixman','actual':'SceneFX FX / OpenGL ES, AMD Radeon 780M; headless outputs, not pixman renderer'}) +reference=Image.open(run/'h2-reference.png').convert('RGB');mosaic=Image.open(run/'h2-mosaic.png').convert('RGB');left=Image.open(run/'h1-mosaic.png').convert('RGB') +# This fixed interior matches the mapped reference's IPC client bounds. The +# comparison is supplemental evidence; it never replaces failed whole-H2 gate. +events=[json.loads(x) for x in (run/'events.jsonl').read_text().splitlines()] +trees=[e['result'] for e in events if e['kind']=='ipc' and e['args']==['-t','get_tree']] +def nodes(n): + yield n + for child in n.get('nodes',[])+n.get('floating_nodes',[]):yield from nodes(child) +refs=[n for n in nodes(trees[-1]) if n.get('app_id')=='org.mosaic.StaticReference' and n['rect']['x']>=0];assert len(refs)==1 +ref=refs[0];rect=ref['rect'];win=ref['window_rect'];deco=ref['deco_rect'] +# IPC rect/window_rect describe the standard reference content, while +# applied Mosaic target geometry is retained separately from scene traces. +x=rect['x']+win['x'];y=rect['y']+win['y'];box=(x,y,x+win['width'],y+win['height']) +assert 0<=box[0]150 or any(x.stat().st_size>64*1024*1024 for x in run.glob('*.log')): + expired.set();stop_owned();return + +thread=threading.Thread(target=watchdog,daemon=True);thread.start() + +def spawn(name,argv,stdin=False,extra=None): + assert not expired.is_set() + out=(run/(name+'.log')).open('w');files.append(out) + proc=subprocess.Popen(list(map(str,argv)),env={**env,**(extra or {})},stdin=subprocess.PIPE if stdin else subprocess.DEVNULL,stdout=out,stderr=subprocess.STDOUT,text=True,start_new_session=True) + processes.append(proc);event('spawn',name=name,pid=proc.pid,argv=list(map(str,argv)));return proc + +def wait(predicate,seconds=8): + end=time.monotonic()+seconds + while time.monotonic()before) + time.sleep(.15);event('pointer',action=action,global_point=[x,y],target_tail=log('target').splitlines()[-6:] if (run/'target.log').exists() else []) + +def capture(tag,output): + reserve(lab,'capture');path=run/(tag+'.png') + subprocess.run(['grim','-s','1','-o',output,str(path)],env=env,check=True,timeout=5,stdout=subprocess.DEVNULL) + im=Image.open(path).convert('RGB');assert im.size==(800,600) + event('capture',file=path.name,output=output,sha256=sha(path));return im + +def measure(owner): + rows=[x for x in log('compositor').splitlines() if 'PI_OWNER output=HEADLESS-1 ' in x and f' id={owner["id"]} ' in x] + assert rows,'applied owner trace unavailable' + line=rows[-1];m=re.search(r'scene_valid=1 scene=(-?\d+),(-?\d+) applied_size=(\d+),(\d+)',line);assert m,line + x,y,w,h=map(int,m.groups());win=owner['window_rect'] + return {'id':owner['id'],'scene':[x,y,w,h],'window_rect':win,'source_box':[x+win['x'],y+win['y'],win['width'],win['height']],'raw':line} + +try: + compositor=spawn('compositor',[binary,'-d','-c',run/'config'],extra={'LD_DEBUG':'bindings','LD_BIND_NOW':'1'}) + sock=wait(lambda:next(iter(runtime.glob('sway-ipc.*.sock')),None)) + display=wait(lambda:next((x for x in runtime.glob('wayland-*') if x.is_socket()),None)) + env.update(SWAYSOCK=str(sock),WAYLAND_DISPLAY=display.name) + maps=Path(f'/proc/{compositor.pid}/maps').read_text();(run/'proc-maps.txt').write_text(maps) + exe=Path(f'/proc/{compositor.pid}/exe').resolve();assert exe==binary and sha(exe)==sha(binary),'wrong executable' + paths=sorted({line.split()[-1] for line in maps.splitlines() if len(line.split())>=6 and line.split()[-1].startswith('/')}) + pair=json.loads((lab/'results/wlroots-qualified-pair.json').read_text());wl=Path(pair['library']) + assert not any('libscenefx' in x for x in paths),'wrong SceneFX shared mapping' + loaded_wl=[Path(x) for x in paths if 'libwlroots' in x];assert len(loaded_wl)==1 and loaded_wl[0].resolve()==wl and sha(wl)==pair['sha256'],'wrong wlroots mapping' + assert not any('/swayfx-mosaic-' in x and Path(x)!=binary for x in paths),'old LAB dependency' + save(lab,'loader-mappings',{'status':'PASS','pid':compositor.pid,'exe':str(exe),'sha256':sha(exe),'paths':paths,'wlroots_sha256':sha(wl),'SceneFX_shared':False}) + outputs=ipc('-t','get_outputs');actual={o['name']:(o['rect']['x'],o['rect']['y'],o['rect']['width'],o['rect']['height'],o['scale'],o['transform']) for o in outputs if o['active']} + assert actual=={'HEADLESS-1':(-800,0,800,600,1.0,'normal'),'HEADLESS-2':(0,0,800,600,1.0,'normal')},actual + save(lab,'headless-outputs',outputs) + pointer=spawn('pointer',[lab/'pointer-daemon','1600','600'],stdin=True);wait(lambda:'POINTER_READY' in log('pointer')) + cmd('workspace 2');ref=spawn('reference',[lab/'static-reference'],stdin=True);wait(lambda:find('org.mosaic.StaticReference')) + time.sleep(.4);inject('move',-790,590);baseline=capture('h2-reference','HEADLESS-2') + cmd('workspace 1');filler=spawn('filler',[lab/'static-reference'],stdin=True);wait(lambda:len([n for n in nodes(ipc('-t','get_tree')) if n.get('app_id')=='org.mosaic.StaticReference'])==2) + target=spawn('target',[lab/'saved-client'],stdin=True);wait(lambda:find('org.mosaic.SavedBufferProbe')) + cmd('layout mosaic');time.sleep(.5) + owner=find('org.mosaic.SavedBufferProbe');before=measure(owner);x,y,w,h=before['source_box'];desired=-w//2;delta=x-desired + event('camera-calculation',before=before,desired_left=desired,delta=delta) + if delta:reserve(lab,'camera');cmd(f'layout mosaic scroll {delta}') + time.sleep(.5);owner=find('org.mosaic.SavedBufferProbe');after=measure(owner);x,y,w,h=after['source_box'] + assert x<00 and right_count==0 and diff is None,'forbidden pixel leakage/reference difference' + visible_x=max(-780,min(-20,x+max(20,w//4)));visible_y=min(550,y+80) + target_before=log('target').count('BUTTON ');inject('right',visible_x,visible_y);visible_buttons=log('target').count('BUTTON ')-target_before + assert visible_buttons>=2,'visible target input missing' + target_before=log('target').count('BUTTON ');ref_before=log('reference').count('BUTTON ') + clipped_x=max(20,min(x+w-20,200));inject('right',clipped_x,visible_y) + invisible_buttons=log('target').count('BUTTON ')-target_before;reference_buttons=log('reference').count('BUTTON ')-ref_before + save(lab,'input-analysis',{'visible_point':[visible_x,visible_y],'visible_target_button_events':visible_buttons,'forbidden_point':[clipped_x,visible_y],'forbidden_target_button_events':invisible_buttons,'reference_button_events':reference_buttons}) + assert invisible_buttons==0 and reference_buttons>=2,'forbidden target hit/reference missing' + inject('move',-790,590);repeat=capture('h2-mosaic-repeat','HEADLESS-2');assert ImageChops.difference(baseline,repeat).getbbox() is None,'unstable H2 reference' + cmd('layout splith');time.sleep(.5);inject('move',-790,590) + standard_left=capture('h1-standard','HEADLESS-1');standard_right=capture('h2-standard','HEADLESS-2') + scene_lines=[s for s in log('compositor').splitlines() if 'PI_SCENE output=HEADLESS-1' in s] + assert scene_lines and 'clip_valid=0' in scene_lines[-1],'residual Mosaic clip' + standard_owner=find('org.mosaic.SavedBufferProbe');standard_measure=measure(standard_owner) + sx,sy,sw,sh=standard_measure['source_box'];before_buttons=log('target').count('BUTTON ');inject('right',sx+sw//2,sy+min(80,sh//2));standard_buttons=log('target').count('BUTTON ')-before_buttons + assert standard_buttons>=2 and ImageChops.difference(baseline,standard_right).getbbox() is None,'standard regression' + save(lab,'standard-layout',{'status':'PASS sampled only','scene_trace':scene_lines[-1],'owner':standard_measure,'target_button_events':standard_buttons,'H2_equal':True}) + save(lab,'popup-ownership',{'status':'NOT TESTED','reason':'root-grab fixture not added to the single minimal tiled scenario; historical pristine/grab evidence retained'}) + cmd('exit');compositor.wait(timeout=5);assert compositor.returncode==0 +except Exception as exc: + failure=f'{type(exc).__name__}: {exc}';event('failure',message=failure) +finally: + finished.set();thread.join(timeout=1);stop_owned() + for proc in processes: + try:proc.wait(timeout=3) + except subprocess.TimeoutExpired: + os.killpg(proc.pid,signal.SIGKILL);proc.wait(timeout=3) + try:os.killpg(proc.pid,signal.SIGKILL) + except ProcessLookupError:pass + for out in files:out.close() + elapsed=time.monotonic()-started + save(lab,'static-runtime',{'status':'PASS' if failure is None else 'INCONCLUSIVE','failure':failure,'duration_seconds':elapsed,'binary':str(binary),'sha256':sha(binary),'process_exit_codes':[{'pid':x.pid,'argv':x.args,'exit':x.returncode} for x in processes],'runtime_removed':str(runtime),'cleanup':'all owned groups reaped'}) + shutil.rmtree(runtime) + damage=[s for s in log('compositor').splitlines() if 'TEMPORAL_DAMAGE ' in s] + save(lab,'damage-analysis',{'status':'OBSERVED' if damage else 'NOT TESTED','extents':damage,'all_frames':'NOT TESTED'}) +print('STATIC_RUNTIME',failure or 'PASS') +if failure:raise SystemExit(1) diff --git a/tooling/probes/step20c-static-runtime/seal.py b/tooling/probes/step20c-static-runtime/seal.py new file mode 100644 index 0000000..aaa8d0c --- /dev/null +++ b/tooling/probes/step20c-static-runtime/seal.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +"""Seal this completed LAB once, or verify every immutable manifest entry. + +CONTRACT: exclusive creation; source files and raw/generated evidence separate. +INVARIANT: no rewriting/refunds; report/diff stay outside sealed inventories. +""" +import argparse +from pathlib import Path +from support import REPO,sha +p=argparse.ArgumentParser();p.add_argument('lab',type=Path);p.add_argument('--verify',action='store_true');a=p.parse_args();lab=a.lab.resolve() +assert lab.is_relative_to(Path.home()) and not lab.is_relative_to(REPO) +results=lab/'results' +if not a.verify: + assert not any((results/(n+'-sha256.txt')).exists() for n in ['source','evidence']) + source=set(x for x in Path(__file__).parent.iterdir() if x.is_file()) + for tree in ['mosaic-source','scene-source','swayfx-source','wlroots-source','client-source','patches']: + source.update(x for x in (lab/tree).rglob('*') if x.is_file()) + evidence=set(x for tree in ['raw','run','results','stage-static'] for x in (lab/tree).rglob('*') if x.is_file() and x.name not in ['source-sha256.txt','evidence-sha256.txt']) + for build in ['mosaic-build','scene-build']: + for name in ['build.ninja','compile_commands.json']: + evidence.add(lab/build/name) + evidence.update(x for x in (lab/build/'meson-info').glob('*.json')) + evidence.update(x for x in (lab/build/'meson-logs').glob('*.txt')) + evidence.add(lab/'scene-build/libscenefx-0.5.a') + evidence.add(lab/'mosaic-build/sway/swayfx-mosaic') + for name,files in [('source',source),('evidence',evidence)]: + with (results/(name+'-sha256.txt')).open('x') as out: + for path in sorted(files):out.write(f'{sha(path)} {path.resolve()}\n') +for name in ['source','evidence']: + path=results/(name+'-sha256.txt');lines=path.read_text().splitlines() + for line in lines: + digest,file=line.split(' ',1);assert sha(file)==digest,file + print(name,len(lines),sha(path),'PASS') diff --git a/tooling/probes/step20c-static-runtime/stage.py b/tooling/probes/step20c-static-runtime/stage.py new file mode 100644 index 0000000..98a3252 --- /dev/null +++ b/tooling/probes/step20c-static-runtime/stage.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +"""Read-only stage verification; notices are added only by the one-shot audit. + +CONTRACT: regular binary plus exactly inventoried regular notice files only. +INVARIANT: links, devices, libraries, configs and unlisted files are rejected. +""" +import argparse +import json +from pathlib import Path +import stat +from support import save,sha +p=argparse.ArgumentParser();p.add_argument('lab',type=Path);a=p.parse_args();lab=a.lab.resolve();stage=lab/'stage-static' +licenses=json.loads((lab/'results/license-files.json').read_text()) +allowed={'usr/bin/swayfx-mosaic':sha(lab/'mosaic-build/sway/swayfx-mosaic')} +allowed.update({x['path']:x['sha256'] for x in licenses});entries=[];seen=set() +for path in sorted(stage.rglob('*')): + s=path.lstat();rel=str(path.relative_to(stage));mode=stat.S_IMODE(s.st_mode) + if stat.S_ISDIR(s.st_mode): + assert any(x.startswith(rel+'/') for x in allowed),rel + assert mode==0o755,(rel,mode) + continue + assert stat.S_ISREG(s.st_mode) and rel in allowed,rel + assert sha(path)==allowed[rel] and mode==(0o755 if rel=='usr/bin/swayfx-mosaic' else 0o644),(rel,mode) + if rel!='usr/bin/swayfx-mosaic':assert path.read_text().strip() + seen.add(rel);entries.append({'path':'/'+rel,'type':'regular','mode':oct(mode),'uid':s.st_uid,'gid':s.st_gid,'bytes':s.st_size,'sha256':sha(path),'origin':'meson install' if rel=='usr/bin/swayfx-mosaic' else 'future package simulation'}) +assert seen==set(allowed) +options=json.loads((lab/'mosaic-build/meson-info/intro-buildoptions.json').read_text());values={x['name']:x['value'] for x in options} +assert values['prefix']=='/usr' and values['bindir']=='bin' +compiled=(lab/'mosaic-build/compile_commands.json').read_text();assert '/usr/local' not in compiled +save(lab,'compiled-paths',{'prefix':values['prefix'],'bindir':values['bindir'],'sysconfdir':values['sysconfdir'],'compiler_definition':'SYSCONFDIR=//etc; canonical /etc','compiled_command_provenance':'mosaic-build/compile_commands.json'}) +save(lab,'static-stage-manifest',{'status':'PASS','marker':'STATIC_STAGE_WITH_LICENSES=PASS','files':entries,'installation':'private user-owned DESTDIR; no package installed'}) +print('STATIC_STAGE_WITH_LICENSES=PASS',len(entries),'regular files') diff --git a/tooling/probes/step20c-static-runtime/support.py b/tooling/probes/step20c-static-runtime/support.py new file mode 100644 index 0000000..e6bbd3c --- /dev/null +++ b/tooling/probes/step20c-static-runtime/support.py @@ -0,0 +1,84 @@ +"""Private probe support; no desktop environment or global process ownership. + +WHY: failures must consume the same persistent experiment budget as successes. +CONTRACT: a stable flock file serializes durable atomic ledger updates. +INVARIANT: no reset/refund; only argv execution and owned process groups. +""" +import fcntl +import hashlib +import json +import os +from pathlib import Path +import signal +import subprocess +import tempfile +import time + +REPO = Path(__file__).resolve().parents[3] +LIMITS = {'scene_build': 1, 'mosaic_build': 1, 'stage': 1, 'notices': 1, + 'elf': 1, 'run': 1, 'control': 1, 'capture': 8, 'camera': 1} + +def sha(path): + return hashlib.sha256(Path(path).read_bytes()).hexdigest() + +def save(lab, name, value): + (Path(lab)/'results'/(name+'.json')).write_text(json.dumps(value, indent=2)+'\n') + +def reserve(lab, kind): + lab = Path(lab) + if kind not in LIMITS: + raise ValueError(kind) + with (lab/'budget.lock').open('a+') as lock: + fcntl.flock(lock, fcntl.LOCK_EX) + path = lab/'results/budget.json' + state = json.loads(path.read_text()) if path.exists() else { + 'limits': LIMITS, 'used': dict.fromkeys(LIMITS, 0), 'events': []} + if state['limits'] != LIMITS or state['used'][kind] >= LIMITS[kind]: + raise RuntimeError('budget exhausted or changed: '+kind) + state['used'][kind] += 1 + state['events'].append({'sequence': len(state['events']), 'kind': kind}) + fd, temporary = tempfile.mkstemp(prefix='.budget-', dir=path.parent) + with os.fdopen(fd, 'w') as out: + json.dump(state, out, indent=2); out.write('\n'); out.flush(); os.fsync(out.fileno()) + os.replace(temporary, path) + fd = os.open(path.parent, os.O_RDONLY | os.O_DIRECTORY) + try: + os.fsync(fd) + finally: + os.close(fd) + return state + +def environment(): + env = {k:v for k,v in os.environ.items() if k not in ( + 'SWAYSOCK','I3SOCK','WAYLAND_DISPLAY','DISPLAY','XDG_RUNTIME_DIR', + 'LD_LIBRARY_PATH','LD_PRELOAD','PKG_CONFIG_PATH')} + env.update(LC_ALL='C', PYTHONDONTWRITEBYTECODE='1', MESON_ROOT_CMD='/bin/false') + return env + +def command(lab, label, argv, cwd=None, extra=None, timeout=600, allow_failure=False): + lab=Path(lab); path=lab/'raw'/(label+'.log') + assert not path.exists(), 'diagnostic already recorded: '+label + with path.open('wb') as out: + proc=subprocess.Popen(list(map(str,argv)),cwd=cwd,env={**environment(),**(extra or {})}, + stdout=out,stderr=subprocess.STDOUT,start_new_session=True) + try: + deadline=time.monotonic()+timeout + while proc.poll() is None: + if time.monotonic()>=deadline or path.stat().st_size>64*1024*1024: + raise subprocess.TimeoutExpired(argv,timeout) + time.sleep(.1) + except subprocess.TimeoutExpired: + os.killpg(proc.pid,signal.SIGTERM) + try: proc.wait(timeout=3) + except subprocess.TimeoutExpired: + os.killpg(proc.pid,signal.SIGKILL);proc.wait() + try: os.killpg(proc.pid,signal.SIGKILL) + except ProcessLookupError: pass + raise + with (lab/'raw/commands.jsonl').open('a') as out: + out.write(json.dumps({'label':label,'argv':list(map(str,argv)), 'cwd':str(cwd), + 'env':extra or {},'exit':proc.returncode})+'\n') + assert path.stat().st_size < 64*1024*1024, 'output bound exceeded' + if not allow_failure and proc.returncode: + raise RuntimeError(f'{label} exit={proc.returncode}; inspect {path}') + return path.read_text(errors='replace') diff --git a/tooling/probes/step20c-static-runtime/test-budget.py b/tooling/probes/step20c-static-runtime/test-budget.py new file mode 100644 index 0000000..271083d --- /dev/null +++ b/tooling/probes/step20c-static-runtime/test-budget.py @@ -0,0 +1,25 @@ +"""Offline exhaustion and cross-process reservation tests, outside campaign LAB.""" +import concurrent.futures +import json +from pathlib import Path +import tempfile +from support import LIMITS, reserve + +def attempt(args): + lab, kind = args + try: reserve(lab,kind); return True + except RuntimeError: return False + +if __name__ == '__main__': + with tempfile.TemporaryDirectory(prefix='static-budget-test-') as tmp: + lab=Path(tmp);(lab/'results').mkdir() + with concurrent.futures.ProcessPoolExecutor(max_workers=4) as pool: + assert sum(pool.map(attempt,[(lab,'capture')]*20))==8 + for kind,limit in LIMITS.items(): + if kind=='capture':continue + for _ in range(limit):assert attempt((lab,kind)) + assert not attempt((lab,kind)) + state=json.loads((lab/'results/budget.json').read_text()) + assert state['used']==LIMITS + assert not attempt((lab,'run')) + print('PASS offline ledger: concurrent reservations, exhaustion, durable failures, no refund')