docs(mosaic): preserve static runtime and package contract evidence

This commit is contained in:
fy59 2026-10-13 07:23:41 +02:00
parent 4f4f3fefcd
commit 5e2961d05b
20 changed files with 1124 additions and 2 deletions

View file

@ -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`.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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))

View file

@ -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'})

View file

@ -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]<box[2]<=800 and 0<=box[1]<box[3]<=600
points=[(i%800,i//800,a,b) for i,(a,b) in enumerate(zip(reference.get_flattened_data(),mosaic.get_flattened_data())) if a!=b]
interior_equal=ImageChops.difference(reference.crop(box),mosaic.crop(box)).getbbox() is None
colors={(35,68,204),(32,208,160)}
mask=Image.new('L',left.size);mask.putdata([255 if v in colors else 0 for v in left.get_flattened_data()]);target_box=mask.getbbox()
save(lab,'pixel-reference-diagnostic',{'whole_H2_changed_pixels':len(points),'whole_H2_bbox':ImageChops.difference(reference,mosaic).getbbox(),'reference_IPC':ref,'measured_interior_box':box,'interior_equal':interior_equal,'all_differences_outside_interior':all(not(box[0]<=x<box[2] and box[1]<=y<box[3]) for x,y,_,_ in points),'color_changes':[{'before':a,'after':b,'count':c} for (a,b),c in Counter((a,b) for _,_,a,b in points).most_common(8)],'H1_target_color_bbox':target_box,'interpretation':'focus/decor changes supported by IPC and color changes; no whole-output PASS; no rerun'})
for name in ['input-analysis','popup-ownership','standard-layout']:
if not (lab/'results'/(name+'.json')).exists():save(lab,name,{'status':'NOT TESTED','reason':'single run stopped at whole-H2 reference gate before downstream measurement'})
decisions=[{'id':'D1','decision':'private static SceneFX for immediate experimental STEP20C candidate'}, {'id':'D2','decision':'separate full notices for incorporated components in future private stage'}, {'id':'D3','decision':'project owns embedded SceneFX monitoring/review/rebuild/requalification; people and SLA unassigned'}, {'id':'D4','decision':'strict exact SceneFX/wlroots build pairs; stock updates independent; standard fallback'}]
save(lab,'decisions',{'date':'2026-10-13','authority':'explicit user ticket','decisions':decisions,'scope':'documentation/private prototypes; current package unchanged','production':'not authorized'})
save(lab,'security-maintenance-policy',{'responsible':'swayfx-mosaic project','individual_release_owner':None,'security_reviewer':None,'SLA':None,'required_before_public_distribution':'designate release maintainer and security reviewer','record':['SceneFX commit/provenance/checksums','ordered private patches','toolchain/options/dependencies','incorporated code/notices','qualified wlroots builds','tests','published binary hashes'],'monitor':['security fixes','renderer','shaders','dependencies','ABI','buffer lifetime','clipping','damage'],'workflow':['detect upstream changes/advisories','review relevance and exposure','review patch and clip interaction','rebuild','requalify pair','record evidence','separate publication decision'],'automatic_updates':False,'fallback':'ordinary SwayFX; never downgrade/replace system libraries for rollback'})
save(lab,'upstream-direction',{'direction':'SCENEFX_UPSTREAM_MAINTAINED_CLIP_API','historical_dossier':str(REPO/'docs/design/STEP20C_SCENEFX_STATIC_DISTRIBUTION_CONTRACT.md'),'static':'transitory experimental preference','maintained_API':['ownership','errors','bounds','lifetime','hit testing','damage','versioning'],'popup_ownership':'Mosaic specific','contact':False,'adoption':'not presumed'})
pair=json.loads((lab/'results/wlroots-qualified-pair.json').read_text());pair['qualification_scope']='build/model/stage/loader/headless startup and sampled tiled pixels; input/standard return unqualified';pair['required_after_update']=['identify exact pair','headers/symbols','shared layouts','rebuild','relocations','model tests','private stage/notices','authorized headless','relevant graphical paths','separate release decision'];save(lab,'wlroots-qualified-pair',pair)
# Legal/source closure is intentionally distinguished from successful file staging.
licenses=json.loads((lab/'results/license-inventory.json').read_text());licenses['status']='PARTIAL DISTRIBUTION QUALIFICATION; root/generated/source comments staged, exact archive extraction and final legal review remain open';save(lab,'license-inventory',licenses)
not_tested=['root valid grab popup','nested popup','measured clipped input','standard layout return','all-frame damage','vertical/scaled/rotated geometry','migration/resize','saved buffers/effects/optimized blur','atomic applied generation','other wlroots versions','bit reproducibility','install/upgrade/rollback','final legal compliance']
matrix={'build':'PASS','real_usr_prefix':'PASS','stage_with_notices':'PASS','ELF_and_relocations':'PASS','runtime_library_identity':'PASS','headless_startup_IPC_client_mapping':'PASS','static_clips_set_and_get':'OBSERVED','sampled_tiled_pixels':'retained H1 / zero target colors H2; reference interior identical','whole_H2_reference':'FAIL fixture gate; focus/decor mismatch','input':'NOT TESTED','standard_return':'NOT TESTED','runtime_scenario':runtime['status'],'other_cells':{x:'NOT TESTED' for x in not_tested}}
save(lab,'matrix',matrix)
save(lab,'summary',{'verdict':'PARTIALLY_FEASIBLE','architecture':'UNDECIDED','runtime_scenario':runtime['status'],'reason':'static build/stage/loader/headless function; reference fixture gate prevents input/standard return qualification; notice/security/full ABI gates remain','budget':json.loads((lab/'results/budget.json').read_text())['used'],'not_tested':not_tested,'reruns':0,'production':'NOT YET SUPPORTED','STEP20C':'NEXT'})
print('PARTIALLY_FEASIBLE; runtime scenario INCONCLUSIVE; bindings 31/3; H2 interior equal',interior_equal)

View file

@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""Reconstruct only the approved static chain in the one external LAB.
CONTRACT: unchanged historical patches, real /usr prefix, no live install.
INVARIANT: reserve before each build/stage; failures are never retried.
WHY: a staged historical /usr/local binary cannot qualify compiled /usr paths.
"""
import argparse
import json
from pathlib import Path
import shutil
import subprocess
import tarfile
from support import REPO,command,environment,reserve,save,sha
p=argparse.ArgumentParser(description=__doc__)
p.add_argument('lab',type=Path);p.add_argument('--scenefx-git',type=Path,required=True)
p.add_argument('--historical',type=Path,required=True)
a=p.parse_args();lab=a.lab.resolve();old=a.historical.resolve()
assert lab.is_relative_to(Path.home()) and not lab.is_relative_to(REPO)
assert not (lab/'prepare-started.json').exists()
(lab/'prepare-started.json').write_text(json.dumps({'historical':str(old),'scene_store':str(a.scenefx_git)})+'\n')
pins={'SwayFX':'fd71a6bdc061bd633b488ae7b83e8a6981d22f86',
'Mosaic':'cd550382b311efdbfe3ed892f4e1d281d852e754',
'SceneFX':'37ccd723bef49e6891156ffafce8f549f01446cc',
'wlroots':'d783533489e1f75d6886c2ab5c5960090ef268f8'}
for name,store,rev in [('mosaic-source',REPO,pins['Mosaic']),('scene-source',a.scenefx_git,pins['SceneFX']),('swayfx-source',REPO,pins['SwayFX'])]:
arc=lab/(name+'.tar')
with arc.open('wb') as out:subprocess.run(['git','-C',str(store),'archive',rev],stdout=out,check=True,env=environment())
(lab/name).mkdir()
with tarfile.open(arc) as tf:tf.extractall(lab/name,filter='data')
# The sealed wlroots archive was hash-verified at A; copying never changes it.
shutil.copy2(old/'wlroots-source.tar',lab/'wlroots-source.tar')
(lab/'wlroots-source').mkdir()
with tarfile.open(lab/'wlroots-source.tar') as tf:tf.extractall(lab/'wlroots-source',filter='data')
chains={'scene-source':['step20c-valid-grab/scenefx-popup-plus-base.patch','step20c-temporal-damage/scenefx-timing.patch','step20c-optimized-blur-cache/scenefx-cache-trace.patch'],
'mosaic-source':['step20c-valid-grab/mosaic-popup-plus-base.patch','step20c-valid-grab/hidden-guard.patch','step20c-temporal-damage/mosaic-output-timing.patch','step20c-presentation-identity/presentation-events.patch','step20c-optimized-blur-cache/sway-source-trace.patch']}
(lab/'patches').mkdir();inventory=[]
for tree,chain in chains.items():
for i,rel in enumerate(chain):
src=REPO/'tooling/probes'/rel;dst=lab/'patches'/src.name
assert sha(src)==sha(old/'patches'/src.name)
shutil.copy2(src,dst);inventory.append({'source':str(src),'sha256':sha(src),'tree':tree,'order':i})
command(lab,f'{tree}-patch-{i}-check',['git','apply','--check',dst],lab/tree)
command(lab,f'{tree}-patch-{i}-apply',['git','apply',dst],lab/tree)
historical=old/('scene-private' if tree=='scene-source' else 'mosaic-private')
files={str(x.relative_to(historical)):sha(x) for x in historical.rglob('*') if x.is_file()}
reconstructed={str(x.relative_to(lab/tree)):sha(x) for x in (lab/tree).rglob('*') if x.is_file()}
assert files==reconstructed, 'historical source equivalence'
save(lab,'source-versions',{'pins':pins,'patches':inventory,'source_equivalence':'PASS','archives':{x.name:sha(x) for x in lab.glob('*.tar')}})
command(lab,'compiler',['cc','--version']);command(lab,'meson-version',['meson','--version'])
command(lab,'dependencies',['pkg-config','--modversion','wlroots-0.20','scenefx-0.5','wayland-server','wayland-client','wayland-protocols','libdrm','xkbcommon','pixman-1'])
reserve(lab,'scene_build')
command(lab,'scene-setup',['meson','setup',lab/'scene-build',lab/'scene-source','-Ddefault_library=static','-Dexamples=false','-Dwerror=false'])
command(lab,'scene-build',['meson','compile','-C',lab/'scene-build','-j','8'])
reserve(lab,'mosaic_build')
extra={'PKG_CONFIG_PATH':str(lab/'scene-build/meson-uninstalled')}
flags=['--prefix=/usr','--bindir=bin','-Dwerror=false','-Dsd-bus-provider=libsystemd','-Dmosaic-package=true','-Dswaybar=false','-Dswaynag=false','-Dman-pages=disabled','-Ddefault-wallpaper=false']
command(lab,'mosaic-setup',['meson','setup',lab/'mosaic-build',lab/'mosaic-source',*flags],extra=extra)
command(lab,'mosaic-build',['meson','compile','-C',lab/'mosaic-build','-j','8'],extra=extra)
command(lab,'mosaic-model',['meson','test','-C',lab/'mosaic-build','mosaic-model','--print-errorlogs'])
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'
save(lab,'static-build',{'status':'PASS','flags':flags,'options':options,'archive_sha256':sha(lab/'scene-build/libscenefx-0.5.a'),'binary_sha256':sha(lab/'mosaic-build/sway/swayfx-mosaic'),'bit_reproducibility':'NOT TESTED; one build per contract'})
reserve(lab,'stage')
command(lab,'meson-stage',['meson','install','-C',lab/'mosaic-build','--no-rebuild','--destdir',lab/'stage-static'])
assert sha(lab/'stage-static/usr/bin/swayfx-mosaic')==sha(lab/'mosaic-build/sway/swayfx-mosaic')
save(lab,'meson-stage',{'files':[str(x.relative_to(lab/'stage-static')) for x in (lab/'stage-static').rglob('*') if not x.is_dir()]})
print('PASS static builds, exact source equivalence, /usr prefix, model and single stage')

View file

@ -0,0 +1,169 @@
#!/usr/bin/env python3
"""One headless run of staged static compositor with measured tiled crossing.
WHY: resolved relocations do not demonstrate real compositor execution.
CONTRACT: verify /proc executable/library identity before mapping any client.
INVARIANT: owned sockets/groups only, one camera correction, durable captures.
"""
import argparse
import json
import os
from pathlib import Path
import re
import shutil
import signal
import subprocess
import tempfile
import threading
import time
from PIL import Image,ImageChops
from support import environment,reserve,save,sha
p=argparse.ArgumentParser();p.add_argument('lab',type=Path);a=p.parse_args();lab=a.lab.resolve()
assert json.loads((lab/'results/static-stage-manifest.json').read_text())['status']=='PASS'
assert json.loads((lab/'results/binary-elf.json').read_text())['status']=='PASS'
reserve(lab,'run');run=lab/'run';run.mkdir();runtime=Path(tempfile.mkdtemp(prefix='msr-',dir='/tmp'))
runtime.chmod(0o700);(run/'runtime-path').write_text(str(runtime)+'\n')
(run/'config').write_text('output HEADLESS-1 resolution 800x600 scale 1 transform normal position -800 0\noutput HEADLESS-2 resolution 800x600 scale 1 transform normal position 0 0\nworkspace 1 output HEADLESS-1\nworkspace 2 output HEADLESS-2\nfocus_follows_mouse no\nseat seat0 hide_cursor 100\n')
env=environment();env.update(XDG_RUNTIME_DIR=str(runtime),WLR_BACKENDS='headless',WLR_HEADLESS_OUTPUTS='2',WLR_RENDERER='pixman')
binary=lab/'stage-static/usr/bin/swayfx-mosaic';processes=[];files=[];failure=None;compositor=None;events=[]
started=time.monotonic();finished=threading.Event();expired=threading.Event()
def event(kind,**data):
row={'ns':time.monotonic_ns(),'kind':kind,**data};events.append(row)
with (run/'events.jsonl').open('a') as out:out.write(json.dumps(row)+'\n')
def stop_owned():
for proc in list(processes):
try:os.killpg(proc.pid,signal.SIGTERM)
except ProcessLookupError:pass
def watchdog():
while not finished.wait(.1):
if time.monotonic()-started>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()<end:
if expired.is_set():raise RuntimeError('deadline/log bound exceeded')
if compositor and compositor.poll() is not None:raise RuntimeError('compositor exited '+str(compositor.returncode))
value=predicate()
if value:return value
time.sleep(.025)
raise RuntimeError('fixture wait timed out')
def log(name):return (run/(name+'.log')).read_text(errors='replace')
def ipc(*args):
assert sock.is_relative_to(runtime) and sock.is_socket()
result=subprocess.run(['swaymsg','-s',str(sock),*args],env=env,capture_output=True,text=True,timeout=5,check=True)
data=json.loads(result.stdout);event('ipc',args=args,result=data);return data
def cmd(value):
data=ipc(value);assert all(x.get('success') for x in data),(value,data)
def nodes(node):
yield node
for child in node.get('nodes',[])+node.get('floating_nodes',[]):yield from nodes(child)
def find(app):return next((n for n in nodes(ipc('-t','get_tree')) if n.get('app_id')==app),None)
def inject(action,x,y):
assert -800<=x<800 and 0<=y<600
marker=f'INJECT_DONE {action} {x+800},{y}';before=log('pointer').count(marker)
pointer.stdin.write(f'{action} {x+800} {y}\n');pointer.stdin.flush()
wait(lambda:log('pointer').count(marker)>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<0<x+w and -800<x,'real source crossing absent'
save(lab,'scene-clips',{'before':before,'after':after,'delta':delta,'clips':[s for s in log('compositor').splitlines() if 'PI_SCENE output=HEADLESS-1' in s][-4:]})
inject('move',-790,590);left=capture('h1-mosaic','HEADLESS-1');right=capture('h2-mosaic','HEADLESS-2')
colors={(35,68,204),(32,208,160)};left_count=sum(v in colors for v in left.get_flattened_data());right_count=sum(v in colors for v in right.get_flattened_data())
diff=ImageChops.difference(baseline,right).getbbox()
save(lab,'pixel-analysis',{'retained_h1_target_pixels':left_count,'forbidden_h2_target_pixels':right_count,'whole_H2_difference_bbox':diff,'source_crossing':after['source_box']})
assert left_count>0 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)

View file

@ -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')

View file

@ -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')

View file

@ -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')

View file

@ -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')