From bb3a112550f45667e501d05d5a08aa8f7d0c02c6 Mon Sep 17 00:00:00 2001 From: fy59 Date: Tue, 13 Oct 2026 06:22:28 +0200 Subject: [PATCH] docs(mosaic): preserve SceneFX ABI and packaging feasibility evidence --- CHANGELOG.md | 2 + docs/ARCHITECTURE.md | 11 + docs/KNOWN_LIMITATIONS.md | 11 + docs/PACKAGING.md | 11 + docs/README.md | 14 + docs/ROADMAP.md | 11 + ...STEP20C_SCENEFX_ABI_PACKAGE_FEASIBILITY.md | 441 ++++++++++++++++++ .../step20c-scenefx-abi-package/README.md | 85 ++++ .../step20c-scenefx-abi-package/audit.py | 207 ++++++++ .../step20c-scenefx-abi-package/prepare.py | 134 ++++++ .../step20c-scenefx-abi-package/seal.py | 36 ++ .../step20c-scenefx-abi-package/summarize.py | 77 +++ 12 files changed, 1040 insertions(+) create mode 100644 docs/design/STEP20C_SCENEFX_ABI_PACKAGE_FEASIBILITY.md create mode 100644 tooling/probes/step20c-scenefx-abi-package/README.md create mode 100644 tooling/probes/step20c-scenefx-abi-package/audit.py create mode 100644 tooling/probes/step20c-scenefx-abi-package/prepare.py create mode 100644 tooling/probes/step20c-scenefx-abi-package/seal.py create mode 100644 tooling/probes/step20c-scenefx-abi-package/summarize.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 341fc89..c733852 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### STEP20C experimental evidence +- 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. + - Preserved optimized-edge gate: four completed controls reproduce the same boundary differences in pristine SceneFX, private splith and Mosaic without OPT traces. Same-binary Mosaic trace-off/on and pristine/private standard match full RGB at six states; central freshness, H2 stability and sampled clipping hold. PARTIALLY_FEASIBLE, architecture UNDECIDED: reduced-domain FBO history has source/model support but no native causal observation. Separate ABI/package audit justified; optional bounded GPU study remains unexecuted. No production change; historical graphical campaign closed. - Preserved optimized blur cache probe: private splith/Mosaic used a real background source and output-scoped GPU cache, naturally reconstructed A/B/A and restored exact central filtered pixels with stable whole H2 RGB. Boundary equivalence still differs by up to six RGB levels in both private modes. Pristine C0 failed before captures; its duplicate initial image was charged after observation, with no retry or numerical budget overrun. PARTIALLY_FEASIBLE, architecture UNDECIDED; neutrality and whole-cache correctness unqualified. No production change; the historical campaign is closed. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 329f9cf..26772fc 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -342,3 +342,14 @@ extents cover stabilized differences only. No clipping regression or required invariant violation is established. A separate SceneFX ABI/package audit is now justified, retaining this shared rendering limitation; optional selective GPU observation requires a new scoped ticket. This evidence is preserved without a production change. STEP20C stays NEXT, production multi-output NOT YET SUPPORTED. + +The preserved [SceneFX ABI/package audit](design/STEP20C_SCENEFX_ABI_PACKAGE_FEASIBILITY.md) +is **PARTIALLY_FEASIBLE**, architecture +**SCENEFX_ABI_PACKAGE_REQUIRES_ARCHITECTURAL_DECISION**. The unchanged current +package builds with stock SceneFX and stages exactly /usr/bin/swayfx-mosaic. +The shared experimental binary needs two clip exports absent from stock +despite the same SONAME. Static embedding builds and resolves without a +SceneFX shared dependency, but shared wlroots interposition, updates, security +maintenance and license notices remain gates. A distinct private shared library +would require a new file-ownership decision; upstream API acceptance is unknown. +No integration or packaging contract is approved. The historical audit is frozen as evidence; STEP20C stays NEXT, production multi-output NOT YET SUPPORTED. diff --git a/docs/KNOWN_LIMITATIONS.md b/docs/KNOWN_LIMITATIONS.md index 3fe7a82..4d9e7c3 100644 --- a/docs/KNOWN_LIMITATIONS.md +++ b/docs/KNOWN_LIMITATIONS.md @@ -283,3 +283,14 @@ extents cover stabilized differences only. No clipping regression or required invariant violation is established. A separate SceneFX ABI/package audit is now justified, retaining this shared rendering limitation; optional selective GPU observation requires a new scoped ticket. This evidence is preserved without a production change. STEP20C stays NEXT, production multi-output NOT YET SUPPORTED. + +The preserved [SceneFX ABI/package audit](design/STEP20C_SCENEFX_ABI_PACKAGE_FEASIBILITY.md) +is **PARTIALLY_FEASIBLE**, architecture +**SCENEFX_ABI_PACKAGE_REQUIRES_ARCHITECTURAL_DECISION**. The unchanged current +package builds with stock SceneFX and stages exactly /usr/bin/swayfx-mosaic. +The shared experimental binary needs two clip exports absent from stock +despite the same SONAME. Static embedding builds and resolves without a +SceneFX shared dependency, but shared wlroots interposition, updates, security +maintenance and license notices remain gates. A distinct private shared library +would require a new file-ownership decision; upstream API acceptance is unknown. +No integration or packaging contract is approved. The historical audit is frozen as evidence; STEP20C stays NEXT, production multi-output NOT YET SUPPORTED. diff --git a/docs/PACKAGING.md b/docs/PACKAGING.md index dfd19c8..aa7b451 100644 --- a/docs/PACKAGING.md +++ b/docs/PACKAGING.md @@ -41,3 +41,14 @@ The repository contains full source for development. The exporter constructs the 6. If Mosaic fails, use the normal SwayFX session. Do not overwrite `/usr/bin/sway` to recover. See [upstream maintenance](UPSTREAM.md) and [development](DEVELOPMENT.md) for the operational procedure. + +## Preserved experimental ABI/package audit + +The [STEP20C audit](design/STEP20C_SCENEFX_ABI_PACKAGE_FEASIBILITY.md) separately +validates the unchanged current package's stock build and exact-one-file stage. +The experimental shared binary cannot resolve its two private clip functions +with stock SceneFX. A private static build removes SceneFX DT_NEEDED, but does +not establish shared-wlroots update safety or a one-file notice policy. +A distinct private dynamic library would add an owned file and require a new +explicit packaging decision. No option is adopted; the contract above remains +authoritative and unchanged. The historical evidence is frozen, PARTIALLY_FEASIBLE. diff --git a/docs/README.md b/docs/README.md index 6abd089..bae7b3b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -195,3 +195,17 @@ GPU observation requires a new scoped ticket. This evidence is preserved without The [preserved edge source fixture](../tooling/probes/step20c-optimized-blur-edge/README.md) records pinned reconstruction, offline checks and the exhausted four-slot ledger. + +The preserved [SceneFX ABI/package audit](design/STEP20C_SCENEFX_ABI_PACKAGE_FEASIBILITY.md) +is **PARTIALLY_FEASIBLE**, architecture +**SCENEFX_ABI_PACKAGE_REQUIRES_ARCHITECTURAL_DECISION**. The unchanged current +package builds with stock SceneFX and stages exactly /usr/bin/swayfx-mosaic. +The shared experimental binary needs two clip exports absent from stock +despite the same SONAME. Static embedding builds and resolves without a +SceneFX shared dependency, but shared wlroots interposition, updates, security +maintenance and license notices remain gates. A distinct private shared library +would require a new file-ownership decision; upstream API acceptance is unknown. +No integration or packaging contract is approved. The historical audit is frozen as evidence; STEP20C stays NEXT, production multi-output NOT YET SUPPORTED. + +The [ABI/package source fixture](../tooling/probes/step20c-scenefx-abi-package/README.md) +records build-only reconstruction and non-graphical diagnostic commands. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 85eaf00..7f40ee6 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -287,3 +287,14 @@ extents cover stabilized differences only. No clipping regression or required invariant violation is established. A separate SceneFX ABI/package audit is now justified, retaining this shared rendering limitation; optional selective GPU observation requires a new scoped ticket. This evidence is preserved without a production change. STEP20C stays NEXT, production multi-output NOT YET SUPPORTED. + +The preserved [SceneFX ABI/package audit](design/STEP20C_SCENEFX_ABI_PACKAGE_FEASIBILITY.md) +is **PARTIALLY_FEASIBLE**, architecture +**SCENEFX_ABI_PACKAGE_REQUIRES_ARCHITECTURAL_DECISION**. The unchanged current +package builds with stock SceneFX and stages exactly /usr/bin/swayfx-mosaic. +The shared experimental binary needs two clip exports absent from stock +despite the same SONAME. Static embedding builds and resolves without a +SceneFX shared dependency, but shared wlroots interposition, updates, security +maintenance and license notices remain gates. A distinct private shared library +would require a new file-ownership decision; upstream API acceptance is unknown. +No integration or packaging contract is approved. The historical audit is frozen as evidence; STEP20C stays NEXT, production multi-output NOT YET SUPPORTED. diff --git a/docs/design/STEP20C_SCENEFX_ABI_PACKAGE_FEASIBILITY.md b/docs/design/STEP20C_SCENEFX_ABI_PACKAGE_FEASIBILITY.md new file mode 100644 index 0000000..0ee0331 --- /dev/null +++ b/docs/design/STEP20C_SCENEFX_ABI_PACKAGE_FEASIBILITY.md @@ -0,0 +1,441 @@ +# STEP20C SceneFX / wlroots ABI and package feasibility + +**EXPERIMENTAL — PRESERVED HISTORICAL EVIDENCE.** +Protocol recorded before builds. A freeze gate passed at +`7c22b4020edc2d9ab6779d789159937523aae75a` on local, Forgejo and GitHub, +with a clean worktree. STEP20A DONE, STEP20B EXPERIMENTAL, STEP20C NEXT, +STEP20D–G PLANNED; production multi-output NOT YET SUPPORTED. + +## Authority and problem + +[Packaging](../PACKAGING.md) remains authoritative: exactly one installed +file `/usr/bin/swayfx-mosaic`, no Sway replacement, library overwrite, +conflicts/provides/replaces, personal configuration or service. +The current production patch has no STEP20C clip dependency. The private +SceneFX adds `wlr_scene_tree_set_clip` and `wlr_scene_tree_get_clip` while +retaining the stock SONAME. A shared SONAME does not supply missing symbols. +No strategy is preapproved; conclusions are evidence, not integration authority. + +## Pinned sources and comparison + +SwayFX 0.6 `fd71a6bdc061bd633b488ae7b83e8a6981d22f86`; private Mosaic +`cd550382b311efdbfe3ed892f4e1d281d852e754`; SceneFX 0.5 +`37ccd723bef49e6891156ffafce8f549f01446cc`; wlroots 0.20.2 +`d783533489e1f75d6886c2ab5c5960090ef268f8`. +Compare installed SceneFX, rebuilt pristine/private SceneFX, retained pristine +SwayFX, freshly reconstructed production Mosaic and rebuilt private Mosaic. +Preserve ELF headers/dynamic tags, defined/undefined/versioned symbols, +pkg-config flags/features, actual compiler headers and loader resolution. +Compare all public SceneFX headers and public struct size/alignment/offsets +where relevant. Audit the sidecar lifetime and wlroots shared structures. +Installed package versions alone never prove exact source provenance or ABI. + +## Budget and isolation + +One stock production reconstruction, one DESTDIR staging, one private +SceneFX/Mosaic shared build chain. One optional static SceneFX/Mosaic build +only after source audit establishes a bounded Meson method. No compositor +launch, graphics, system install, package creation, algorithm or UX change. +All source/build/stage/raw evidence stays in a home-directory LAB outside Git. +No inherited session sockets, global library path or loader configuration. +Loader diagnostics use `ld-linux --list` or `ldd -r` on trusted private +artifacts only; these are not compositor execution or graphics tests. +Stop implementing any option needing a public ABI, package-contract change, +new package or architecture migration; record the required future decision. + +## Scenarios and decision criteria + +S0: apply the unchanged cumulative patch on pinned pristine, prove source +equivalence and PKGBUILD checksums, compile with stock dependencies, run only +Meson model tests, stage once and inspect the complete manifest. +S1: private binary with private versus stock SceneFX; inspect required clip +symbols, same SONAME, RUNPATH/RPATH, transitive needs and missing relocations. +S2: compare upstream API, private static embedding, isolated distinct-SONAME +shared library, and an architecture without a new SceneFX API. Prioritize +standard SwayFX coexistence, independent updates, explicit ownership, security +maintenance, symbol interposition, licenses and versioned wlroots constraints. + +FEASIBLE requires S0 staging plus characterized incompatibilities and a +sufficiently demonstrated candidate without identified ABI risk in the matrix. +PARTIALLY_FEASIBLE retains technical/contract/decision gates. INCONCLUSIVE +means insufficient reliable comparison. BLOCKED requires an established +coexistence violation, not the expected missing private functions in stock. +Architecture conclusion remains UNDECIDED until evidence is assessed. + +## Limits + +No graphical, GPU lifetime/cache, all-frame, rotated/scaled/migrated output, +installation, update or rollback test. Those are NOT TESTED. The optimized +edge property remains a separate shared rendering limitation; native FBO +causality remains unproved. No stock-package provenance assertion beyond +available metadata/source comparison. All optional strategies require a new +explicit integration decision before production work. + +## Completed non-graphical audit (2026-10-13) + +`SWAYFX_MOSAIC_SCENEFX_ABI_PACKAGE_PROBE=PARTIALLY_FEASIBLE` + +Architecture: **SCENEFX_ABI_PACKAGE_REQUIRES_ARCHITECTURAL_DECISION**. +No production strategy is selected. The current package passes its separate +stock build/stage gate. The naive experimental/stock-library combination is +INCOMPATIBLE_WITH_STOCK_SCENEFX. Static embedding is technically demonstrated +but retains interposition, shared-wlroots, security and notice gates. + +LAB: `/home/fy59/swayfx-mosaic-abi-package-tp22xfwm/`. +Raw commands/stdout/stderr/return codes are in `raw/`; derived evidence is +in `results/`. All build artifacts remain external to Git. Sources are fresh +archives of verified fixed Git objects, not copies of dirty historical source +trees. SceneFX and wlroots object stores were read-only inputs. The pristine +SwayFX executable is explicitly reused from the sealed A laboratory, with +SHA-256 checked against A's identity; it was not rebuilt or launched. + +## Installed dependencies and source identity + +Installed package metadata: SceneFX `0.5-1`, wlroots0.20 `0.20.2-1`, SwayFX +`0.6-1`. pkg-config reports SceneFX **0.5.0** as `scenefx-0.5`, wlroots +**0.20.2** as `wlroots-0.20`; generated installed headers and a tiny observer +calling native `wlr_version_get_*` agree on runtime wlroots 0.20.2. +The `.pc` files reside in `/usr/lib/pkgconfig`, with include roots +`/usr/include/scenefx-0.5` and `/usr/include/wlroots-0.20`. +All ten queried wlroots build features are true: DRM, X11, libinput, Xwayland, +GLES2, Vulkan, GBM, udmabuf, session and color management. + +The 121 non-generated installed wlroots headers exactly match the pinned +0.20.2 source archive. Installed generated config/version headers are retained +separately. No wlroots source was patched. Header equality and runtime version +do not recover the complete distro binary build provenance, patch set or flags; +that exact provenance remains **NOT ESTABLISHED**. Other 0.20 builds and future +updates are **NOT TESTED**. Compiler dependency output and Meson compile +commands/dependencies record the actual headers and library paths used, rather +than relying only on declared package versions. + +## S0: unchanged current production package + +`PKGBUILD` remains byte-identical to A. Version `0.6+mosaic.1-1`, x86_64, +MIT, SwayFX fixed Git pin, no conflicts/provides/replaces. +Depends: cairo, gdk-pixbuf2, libevdev, libinput, json-c, systemd-libs, wayland, +libxcb, libxkbcommon, pango, pcre2, scenefx, ttf-font, wlroots0.20. +Makedepends: git, meson, ninja, pkgconf, python, wayland-protocols, libdrm, pixman. +Checkdepends: kitty, grim. The declared graphical check was deliberately not +executed; this ticket forbids compositor launches. `makepkg --printsrcinfo` +passed; no package build/install command or dependency installation was used. + +Checksums recomputed without changing the recipe: + +| Input | Expected and actual SHA-256 | Result | +| --- | --- | --- | +| cumulative production patch | `a04cd06a6ef4fc58e3b40333a53226849e82d9a4757b9ccee58ad7b59a23ff4b` | PASS | +| headless IPC source | `0a16c887ca6a87f0392c2eef498b4fad78b9f32afaf7d2a6c0e5e0e37c3c1744` | PASS | +| upstream Git source | recipe SKIP plus exact commit check/archive | PASS pinned source; no archive checksum invented | + +The cumulative patch passed forward application on a pristine archive of +`fd71a6bdc061bd633b488ae7b83e8a6981d22f86`. Full source equivalence passed +for include/, sway/, meson.build, meson_options.txt and tests/mosaic-model.c. +The build used the recipe's Meson package flags, prefix `/usr`, bindir `bin`, +stock SceneFX and wlroots, `-j 8`. Meson mosaic-model passed 1/1. + +Exactly one `meson install --destdir LAB/stage-current --no-rebuild` ran. +Complete non-directory manifest (including symlinks): + +```text +/usr/bin/swayfx-mosaic +``` + +**CURRENT_PACKAGE_STAGE_PASS**. One regular executable ELF64 x86-64, no +symlinks, second binary, library, config, dotfile, service or session file. +The staged binary has no clip references, no RPATH or RUNPATH; its SceneFX +and wlroots references resolve against `/usr/lib`, with no missing relocations. +This qualifies only S0 in the measured build/loader/stage matrix. It is not +a full Arch release package, graphical test, or STEP20C qualification. + +## ELF comparison + +All compared objects are ELF64 x86-64. Every ELF header, dynamic table, dynamic +symbol table, version table, nm listing and objdump output is retained. + +| Object | SONAME or SceneFX dependency | Clip symbols | RPATH / RUNPATH | +| --- | --- | --- | --- | +| installed SceneFX | `libscenefx-0.5.so` | neither defined | absent | +| rebuilt pristine SceneFX | same | neither defined | absent | +| rebuilt private SceneFX | same | both defined | absent | +| retained pristine SwayFX | DT_NEEDED `libscenefx-0.5.so` | neither required | historical pristine build RUNPATH retained | +| staged current Mosaic | DT_NEEDED `libscenefx-0.5.so` | neither required | absent | +| shared experimental Mosaic | DT_NEEDED `libscenefx-0.5.so` | both strong undefined | new private SceneFX build RUNPATH | +| static experimental Mosaic | no SceneFX DT_NEEDED | both present in ordinary symbol table | absent | +| installed wlroots | `libwlroots-0.20.so` | neither defined | absent | + +Exact artifact digests and sizes: + +| Object | Bytes | SHA-256 | +| --- | ---: | --- | +| stock-binary-elf | 6312256 | `479c55bdbf2cc10e72f4ed8633fc7287d15358ed3f451dd2e6bb350671985af0` | +| experimental-binary-elf | 6324344 | `058e51366a71f54ff7824f9a166e3d5cd6797c14f39968ba03587c30bb07bb05` | +| static-binary-elf | 6760600 | `ccd47ce5b74c132e19bcd46d022a80e90e8a944acc3db521a89427ee7913c614` | +| system-scenefx-symbols | 182216 | `63586ffbe268c6830b5782d5327e8ca3624dbd9b9c9b0e93e591e94ca28a6e62` | +| pristine-scenefx-symbols | 493216 | `4e342bbd832ae6e6cfb62938af5bf1fb6b46d30ec50c8b4eea50b652892af19e` | +| private-scenefx-symbols | 504792 | `b9582b6c7dd2f903f6de740534f71356261f3e0b84143a24f666f64575b1d91c` | +| system-wlroots-elf | 1378496 | `7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837` | +| pristine-sway-elf | 6297064 | `8ac865481bc0487344210cd0e16d8e6c7655b29db887cd58ddc11b0ca22fe494` | + +Installed and rebuilt pristine SceneFX have exactly the same exported symbol +set. Private SceneFX adds precisely `wlr_scene_tree_set_clip` and +`wlr_scene_tree_get_clip`; no stock export is removed. The new functions are +unversioned. ELF version requirements for dependency symbols are retained; +there is no versioned clip API contract. A common SONAME selects a library +identity for DT_NEEDED; it is not evidence that this library implements every +undefined function required by the consumer. + +## S1: actual runtime search and relocations + +`ld-linux-x86-64.so.2 --list` inspected library selection without running +application main. `ldd -r` on these trusted generated executables checked +relocations; return code alone is insufficient because missing-symbol +diagnostics can be emitted with status zero. The audit explicitly checks +stdout for missing symbols. No command launched the compositor. + +| Consumer / provider | Selected library | Result | +| --- | --- | --- | +| current staged Mosaic / default | `/usr/lib/libscenefx-0.5.so` | PASS selection/relocations | +| experimental / default | LAB/scene-private-build/libscenefx-0.5.so via build RUNPATH | PASS selection/relocations only | +| experimental / private process path | same private library | PASS selection/relocations only | +| same experimental / stock process path | `/usr/lib/libscenefx-0.5.so` | two missing strong symbols | +| static experimental / default | no SceneFX loaded; stock wlroots still loaded | PASS selection/relocations only | + +The stock counterfactual uses the loader's process-local `--library-path +/usr/lib` and a separately recorded `LD_LIBRARY_PATH=/usr/lib` only for +`ldd -r`. DT_RUNPATH is overridden in this tested setup. No global environment +was modified. Build RUNPATH is an absolute temporary path and is not a +distribution policy. Moving/removing that private library can expose the +stock fallback and its missing functions. No universal loader claim follows +for preload, secure execution, another RUNPATH or a changed library graph. +The documented search semantics are in [ld.so](https://man7.org/linux/man-pages/man8/ld.so.8.html). + +## Public headers, structures and lifetime + +All seven installed public SceneFX headers match pristine source bytes. The +private difference is only two declarations in types/wlr_scene.h. All +existing typedefs, enums, callbacks, public struct members and ownership +comments in these headers remain byte-identical. Private instrumentation +headers are internal, not installed public API. No wlroots header is changed. + +C17 `-Wall -Wextra -Werror` observers report identical size/alignment for 17 +SceneFX/wlroots structs in stock/pristine/private configurations, plus six +selected member offsets. Examples: scene_node 112/8, scene_tree 128/8, scene +304/8, scene_buffer 472/8, scene_output 344/8, wlr_output 624/8, output_state +208/8, surface 968/8. Full results are in struct-abi-audit.json. This is a +measured layout subset; complete semantic ABI compatibility is not proven. + +The retained working patch stores clips in a file-local linked sidecar list +in types/scene/wlr_scene.c. Each record holds a tree pointer, copied wlr_box +and destroy listener; clearing or node destruction removes the listener and +frees the record. Set copies a tree-local rectangle, NULL clears, nonnegative +dimensions are asserted; get copies or returns false. No clip field is added +to a public struct. Linear lookup, unchecked coordinate addition and silent +allocation failure remain private-prototype production design gates. This +ticket does not repair them or freeze these API semantics. + +The historical first discarded candidate added a public tree field and +crashed; the [spike report](STEP20C_SCENEFX_CLIPPING_SPIKE.md) retains that +separate evidence. It must not be conflated with the preserved sidecar patch. +No additional current ABI failure beyond the two absent stock functions was +demonstrated in this non-graphical matrix. That is not proof of absence of +semantic, allocation, concurrency, GPU or future-version defects. + +## Transitive dependencies and symbol ownership + +SceneFX stock/pristine/private share the same direct dependency list: +wlroots, wayland-server, libm, libdrm, pixman, EGL, GBM, GLESv2, lcms2 and +libc. wlroots additionally depends on Vulkan, libinput, libseat, libudev, +display-info, liftoff, Wayland client and XCB components, among others. +All DT_NEEDED entries and recursive loader paths are retained; 72 resolved +system-library paths map to 50 installed package metadata/license records. +No library package was installed. + +SceneFX and wlroots define **85 common dynamic symbols**, including scene, +EGL, matrix and color functions. This overlap already exists with stock +SceneFX and does not by itself demonstrate a new clipping regression. +Controlled `LD_DEBUG=bindings` with relocation tracing shows 34 wlroots +scene bindings in each stock/private/static case: 31 go to SceneFX (or the +static executable), three remain in wlroots. Thus distinct SONAME or static +embedding alone does not isolate the process-wide symbol namespace. +The complete binding records distinguish actual provider from matching names. +A future design must preserve all shared-struct and function semantics across +this graph; hiding/renaming every symbol blindly could break wlroots's scene +helper integration. No symbol renaming or visibility experiment is authorized +or performed here. + +## S2: static feasibility and license constraints + +The pinned [SceneFX Meson target](https://github.com/wlrfx/scenefx/blob/37ccd723bef49e6891156ffafce8f549f01446cc/meson.build) +is a native library() honoring `default_library=static`. One private static +SceneFX build and one Mosaic link use its uninstalled pkg-config file; no +production Meson edit is needed for this laboratory demonstration. + +The static executable is 6,760,600 bytes versus shared experimental 6,324,344 +(+436,256 bytes, about 6.90%, both unstripped local builds). It contains both +clip implementations as ordinary `T` symbols, not new dynamic exports. +DT_NEEDED no longer names SceneFX; EGL/GBM/GLESv2/lcms2 are direct dependencies +instead. wlroots remains dynamic, and 85 common exports remain observable. +Model and loader checks pass. There is no second staging, strip, package build +or graphical run; the one-file ELF shape is demonstrated, a static release +package is **NOT TESTED**. + +SwayFX, pinned SceneFX and wlroots use MIT notices. The source license texts +and transitive package license metadata are retained. Static copying requires +preservation of copyright/permission notices, per the +[SceneFX license](https://github.com/wlrfx/scenefx/blob/37ccd723bef49e6891156ffafce8f549f01446cc/LICENSE). +A separately installed notice would expand the current exact-one-file manifest. +An embedded notice strategy is possible in principle but **NOT TESTED** and +requires explicit packaging/legal review; this audit asserts no blanket legal +clearance. Embedded SceneFX security fixes require Mosaic rebuilds and an +identified maintenance owner. Static embedding does not freeze shared wlroots +or make future system updates ABI-safe. + +## Comparative architecture matrix + +| Criterion | A: upstream clipping API | B: static private SceneFX | C: distinct private shared library | D: no new SceneFX API | +| --- | --- | --- | --- | --- | +| Technical evidence | prototype only; acceptance unknown | build/model/relocations PASS | private lookup PASS; distinct identity NOT TESTED | architecture unknown | +| System libraries intact | normal upstream update only | yes | yes if isolated | yes | +| /usr/bin/sway intact | yes | yes | yes | yes | +| Coexistence | supported upstream version required | file ownership preserved; symbol/version qualification pending | extra file and complete lookup/binding contract required | unproved implementation | +| Stable clip API | absent today | private | private | no new API, mechanism unproved | +| ABI | future release unestablished | measured layouts/relocations pass; semantic/future ABI unestablished | same SONAME naive stock incompatible; isolated design unestablished | unestablished | +| Exactly one package file | possible after adoption | ELF shape demonstrated; notices/stage pending | no | possible, not demonstrated | +| Upgrade risk | medium shared-version transition | high until wlroots/security policy | medium/high; shared wlroots persists | unknown | +| SceneFX maintenance | upstream if accepted | Mosaic embedded-code owner needed | private-library owner needed | stock upstream; alternate mechanism owned by Mosaic | +| wlroots maintenance | qualified version pair | shared 0.20 ABI/interposition | shared 0.20 ABI/interposition | stock APIs plus larger Mosaic implementation | +| Reproducibility | fixed prototype inputs only | commands/sources retained, bit identity untested | laboratory lookup only | not tested | +| Human decision | yes, no submission approved | yes, bundling/security/license contract | yes, new file/SONAME/package ownership | yes, new architecture | + +A is the conditional long-term maintenance direction: one maintained generic +primitive rather than perpetual private API patches. It needs maintainer +agreement, an ownership/error/bounds contract, version policy and closure of +remaining graphical gates. No upstream contact/submission occurred and no +acceptance is presumed. +B is the most concretely demonstrated candidate that avoids owning a second +SceneFX file and removes dependency on the system SceneFX's clip symbols. It +does not yet establish safe independent wlroots upgrades or a distributable +one-file notice strategy. C separates filesystem ownership and library identity +but requires a packaging decision and still shares a symbol namespace. +D cannot be called viable: surface crops/output scissor alone do not establish +popup-owner rendering, topmost hit traversal, damage, effects, saved buffers, +scale/rotation/migration or applied transaction consistency. No alternate +clipping engine is developed. + +## Upgrade and hypothetical rollback + +S0 can coexist by file ownership with standard SwayFX. No recipe conflict or +replacement blocks its updates. That does not guarantee an old Mosaic binary +will load after every shared SceneFX/wlroots upgrade. Requalify and rebuild +Mosaic separately; do not pin or replace system libraries to force it to work. +Static SceneFX makes Mosaic independent of system SceneFX updates, but an +embedded security fix requires its own rebuild and shared wlroots remains a +constraint. A private shared library likewise needs its own security/rebase +policy and explicit transitive compatibility. + +Hypothetical removal of S0 deletes only /usr/bin/swayfx-mosaic and leaves +/usr/bin/sway, SceneFX and wlroots untouched. C would additionally remove only +its approved private library path. Real install, upgrade, uninstall and +rollback are NOT TESTED and were not attempted. + +## Evidence-backed answers Q1–Q18 + +| Question | Answer | +| --- | --- | +| Q1 current stock build/stage? | Yes: clean pinned source/patch equivalence, stock Meson build and single DESTDIR stage PASS. | +| Q2 exact manifest? | Yes, exactly one regular ELF /usr/bin/swayfx-mosaic, no symlink. | +| Q3 added symbols? | Exactly wlr_scene_tree_set_clip and wlr_scene_tree_get_clip, no removed export. | +| Q4 required? | Both strong undefined in the shared private Mosaic binary; both ordinary definitions in static. | +| Q5 SONAME? | Names a provider, does not certify its export set; stock selection proves the two functions missing. | +| Q6 public layouts? | No member/type/enum/callback change in retained SceneFX headers, no wlroots source change; 17 sizes/alignments and six offsets identical. | +| Q7 further ABI incompatibility? | None newly demonstrated in current non-graphical matrix; 85 overlaps and mixed scene bindings are observed constraints, not proof of a new crash. Historical discarded struct-field candidate did crash separately. | +| Q8 stock with private binary? | No: missing strong clip relocations; unmodified stock cannot satisfy the tested binary. | +| Q9 static? | Build/model/loader shape passes with no SceneFX DT_NEEDED; one-file distribution still needs notice, security and wlroots decisions. | +| Q10 isolated shared? | Separate file/SONAME/RUNPATH could isolate library selection; process-wide symbol interactions remain. Distinct-SONAME build NOT TESTED. | +| Q11 package changes? | A needs approved supported API/version dependency; B approved embedding and notices/security/build policy; C extra file/identity/lookup contract; D architecture decision without necessarily another file. None applied. | +| Q12 update independence? | B removes shared SceneFX dependency and preserves file independence most concretely here; shared wlroots still prevents unconditional update-safety claims. | +| Q13 long-term maintenance? | A conditionally, if officially accepted/maintained; no acceptance evidence. Otherwise B/C need explicit project owners for security and rebases. | +| Q14 upstream preferable? | Conditional maintenance inference from one shared maintained primitive; error/bounds/ABI policy, ownership and graphical gates still missing. No submission authorized. | +| Q15 GPU/caches/version risks? | Not measured here; all-frame damage, references, effects and cache semantics must be separately qualified across actual version pairs. | +| Q16 shared edge phenomenon? | Separate rendering limitation carried across all options; static linkage does not repair it or prove native causality. | +| Q17 choose now? | Evidence supports candidate comparison only; explicit user architecture/packaging decision required before integration. | +| Q18 smallest next work? | Non-graphical contract dossier for static symbol binding/ownership, version qualification, security owner and one-file notices, plus an upstream API proposal dossier without submission. No new renderer or production patch. | + +## Budget, validation and limits + +PASS: one stock reconstruction, one stock stage, one private shared chain, +one optional static chain, one rebuilt pristine SceneFX reference. All Meson +setup/compile commands returned zero; mosaic-model 1/1 in stock, private +shared and static builds. C17 layout observers pass. AST, shell syntax, patch +application/equivalence, recipe checksums, export/symbol/header/loader and +license inventories are checked independently of graphical evidence. + +Zero compositor launches, graphical runs, installations and packages generated. +NOT RUN: graphical headless IPC suite, GPU readback, physical backend/scanout, +new geometry/caches/lifetimes, actual update/rollback, another wlroots binary +version, bit-reproducible builds, static staging, distinct-SONAME build or +upstream submission. Preparation guards were tightened after the single build +chain; LAB/prepare-executed.py preserves the prior executed version and command +logs. No build was repeated for that non-behavioral fixture hardening. + +The report and source fixture remain local and uncommitted. No source Mosaic +production, PKGBUILD, cumulative patch, UX, AGENTS, source pin or installed +compositor/library was changed. Final integrity and manifest details follow. + +## Final integrity and local handoff + +Source inventory: **1,463 entries**, SHA-256 +`a27fa33d57d3423e09f02430f04cfed643762d876099b7f88489229115c4a1e5`. +Evidence inventory: **204 entries**, SHA-256 +`86f7654c8ad962053644ce2c4c13eb9f026d6618b171e0cc3a8fc3034eb380d5`. +Both were sealed once and every entry independently checked again. +Original A source/evidence manifests remain byte-identical and pass with the +explicit historical README snapshot mapping. + +Final normal checks PASS: AST for all four B Python scripts; shell syntax +for the existing exporter and PKGBUILD; C17 observers; all recorded builds and +three model tests; patch application/source equivalence; recipe digests; +Markdown local links; complete tracked/new-file whitespace/source-only audit; +protected production/UX/AGENTS/package/pin paths; installed sway/SceneFX/wlroots +hash equality; absence of LAB processes and sockets. The deep closeout review +was performed by the parent, not by an independent subagent; its findings and +fixture hardening are recorded in results/closeout-review.json. + +Local HEAD and directly queried origin/main and github/main remain +`7c22b4020edc2d9ab6779d789159937523aae75a`. A was published; B has no staged +files, commit or push. The final worktree intentionally contains only the B +report/fixture and synchronized Architecture, Known Limitations, Roadmap, +README, Packaging audit section and Changelog. Packaging authority is unchanged. +Complete B diff, including untracked files, is retained as LAB/complete-phase-b.diff. +This report/diff is outside the sealed scientific inventory to avoid digest cycles. + +No system install, package distribution, library replacement, live-session IPC, +QuickShell/dotfile/Codex change or STEP20D work occurred. No experimental process +or private socket remains. STEP20C NEXT; production multi-output NOT YET SUPPORTED. + +## ABI/package evidence freeze — new ticket phase A + +The preceding local handoff records the historical campaign state and its +A/B labels. This freeze publishes those ABI/package findings, without +reclassification, new builds, staging or compositor execution. The new static +distribution-contract phase B is separate and is not part of this commit. + +All 1,463 source and 204 evidence entries were verified before changing the +fixture README's publication wording. Historical manifests are immutable. +For later verification, map only the historical fixture README path to: + +`/home/fy59/swayfx-mosaic-abi-freeze-i6x1osil/historical-fixture-README.md` + +The snapshot retains the hash in the original manifest; all other paths must +be checked directly. The four Python scripts are unchanged. Freeze audit +commands and historical-result verification are retained in `/home/fy59/swayfx-mosaic-abi-freeze-i6x1osil`. +Current package remains CURRENT_PACKAGE_STAGE_PASS; private shared with stock +SceneFX remains INCOMPATIBLE_WITH_STOCK_SCENEFX; static remains build/loader +evidence with no historical static staging or graphical qualification. + +`SWAYFX_MOSAIC_SCENEFX_ABI_PACKAGE_PROBE=PARTIALLY_FEASIBLE` + +`SCENEFX_ABI_PACKAGE_REQUIRES_ARCHITECTURAL_DECISION` + +No distribution architecture or production integration is authorized. diff --git a/tooling/probes/step20c-scenefx-abi-package/README.md b/tooling/probes/step20c-scenefx-abi-package/README.md new file mode 100644 index 0000000..d305fca --- /dev/null +++ b/tooling/probes/step20c-scenefx-abi-package/README.md @@ -0,0 +1,85 @@ +# SceneFX ABI/package audit fixture + +**EXPERIMENTAL / PRESERVED EVIDENCE / no production integration.** +Read the [protocol](../../../docs/design/STEP20C_SCENEFX_ABI_PACKAGE_FEASIBILITY.md). +Private sources/builds/results must stay outside Git. No script may launch a +compositor, use desktop IPC, install system files or change package contracts. +Budgets: one stock reconstruction/stage, one private shared chain, optionally +one static chain after source inspection. ELF checks are diagnostics only. +Generated artifacts and manifests stay in LAB. + +## Build-only reproduction + +Use a full Mosaic clone containing the fixed commits, installed stock build +prerequisites and local Git object stores containing pinned SceneFX and wlroots. +`prepare.py` does not fetch or install missing dependencies. It archives exact +objects, applies retained private patches in new copies, and logs each bounded +build command. Create a new LAB under your home directory, outside this clone. +A LAB can be prepared only once; a failed preparation consumes that attempt. +Do not bypass the ticket's build budget with another directory. + +```sh +python tooling/probes/step20c-scenefx-abi-package/prepare.py LAB \ + --scenefx-git /path/to/scenefx-object-store \ + --wlroots-git /path/to/wlroots-object-store --static +python tooling/probes/step20c-scenefx-abi-package/audit.py LAB +python tooling/probes/step20c-scenefx-abi-package/summarize.py LAB +``` + +`--static` is optional and requires the source-audit decision documented in +this ticket. `--pristine-binary` optionally accepts only the A campaign's +SHA-256-qualified retained pristine executable; otherwise its ELF comparison +is NOT TESTED. A newly reproduced baseline binary needs its own provenance +qualification, not substitution under that historical identity. +The retained completed LAB is `/home/fy59/swayfx-mosaic-abi-package-tp22xfwm`. +Its optional static trial was exercised once; no new run/build is authorized +by these instructions for this completed ticket. + +`audit.py` checks complete stage entries including symlinks, recipe digests, +ELF headers/symbols/version requirements, loader paths and relocations, public +header differences, C17 struct-layout observers and compiler dependencies. +It runs only diagnostic loader modes and small non-graphical layout/version +observers. It never runs a compositor entry point. `ldd -r` diagnostics are +checked even when its status is zero. Its stock-versus-private process library +paths are temporary and recorded. Required tools: cc, Meson/Ninja, pkg-config, +readelf, nm, objdump, the native x86-64 glibc loader, ldd and read-only Arch +package metadata (`pacman -Qi`; no package installation). Other platforms are +NOT TESTED, rather than silently inferred compatible. + +`summarize.py` records measured +S0/S1 results separately from inferred architectural options. Static loader +success does not establish graphics, semantic ABI, future updates or licensing +clearance. All generated JSON/logs/source archives/builds remain in LAB. + +## Completed result + +Current package: **CURRENT_PACKAGE_STAGE_PASS**, exactly one regular ELF +`/usr/bin/swayfx-mosaic`, stock dynamic dependencies, no clip references. +Shared private binary with stock SceneFX: **INCOMPATIBLE_WITH_STOCK_SCENEFX**, +two missing strong clip symbols despite the common SONAME. Shared private +resolution and static embedding pass non-graphical build/model/loader checks. +Seventeen layouts and six selected offsets match; 85 exports overlap wlroots. +Stock/private/static wlroots scene bindings are retained, including the mix +between SceneFX providers and wlroots helpers. Distinct private SONAME, +future-version safety, GPU lifetimes and graphical behavior are NOT TESTED. + +Overall **PARTIALLY_FEASIBLE**; architecture +**SCENEFX_ABI_PACKAGE_REQUIRES_ARCHITECTURAL_DECISION**. No packaging option is +adopted. The historical campaign is closed; STEP20C NEXT and production multi-output +NOT YET SUPPORTED. `seal.py LAB` refuses existing manifests; `seal.py LAB +--verify` verifies all entries without regenerating. Derive all evidence and +finalize fixtures before sealing. Report and complete diff are captured outside +the sealed inventory to avoid self-referential digest dependencies. + +## ABI evidence freeze + +The October 2026 freeze verifies the original 1,463 source entries and 204 +evidence entries without rerunning the campaign. Their manifest hashes remain +`a27fa33d57d3423e09f02430f04cfed643762d876099b7f88489229115c4a1e5` and +`86f7654c8ad962053644ce2c4c13eb9f026d6618b171e0cc3a8fc3034eb380d5`. +This README's publication wording changed after verification. To verify the +sealed historical source manifest subsequently, map only its README entry to +the exact pre-freeze snapshot recorded in the report. Do not regenerate the +manifest or substitute other source files. Python fixture bytes are unchanged. +The old report's A/B labels refer to its earlier ticket, not the new static +distribution-contract phase. That later phase must remain local. diff --git a/tooling/probes/step20c-scenefx-abi-package/audit.py b/tooling/probes/step20c-scenefx-abi-package/audit.py new file mode 100644 index 0000000..5da3ddc --- /dev/null +++ b/tooling/probes/step20c-scenefx-abi-package/audit.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +"""Read-only ELF/header/package audit of prepared trusted private artifacts. + +WHY: record missing relocations, symbol overlap and header provenance separately. +CONTRACT: no compositor entry point executes; loader commands only inspect ELF. +INVARIANT: results may be derived again before sealing, never after sealing. +""" +import argparse +import difflib +import hashlib +import json +import os +from pathlib import Path +import re +import shlex +import subprocess + +p = argparse.ArgumentParser(description=__doc__) +p.add_argument('lab', type=Path) +a = p.parse_args(); lab = a.lab.resolve(); repo = Path(__file__).resolve().parents[3] +assert lab.is_dir() and lab.is_relative_to(Path.home()) and lab != Path.home() +assert not lab.is_relative_to(repo) +assert not (lab/'results/evidence-sha256.txt').exists(), 'sealed evidence; verify, never rewrite' +results = lab/'results'; raw = lab/'raw' +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['LC_ALL'] = 'C' + +def sha(path): return hashlib.sha256(path.read_bytes()).hexdigest() +def save(name, value): (results/(name+'.json')).write_text(json.dumps(value, indent=2)+'\n') +def run(label, args, extra=None): + proc = subprocess.run(list(map(str, args)), env={**env, **(extra or {})}, + capture_output=True, text=True, timeout=60) + entry = {'args': list(map(str, args)), 'env_override': extra or {}, + 'returncode': proc.returncode, 'stdout': proc.stdout, 'stderr': proc.stderr} + (raw/(label+'.json')).write_text(json.dumps(entry, indent=2)+'\n') + return entry + +def elf(label, path): + path = path.resolve(); parts = {} + assert path.stat().st_size <= 128*1024*1024, 'ELF input exceeds audit bound' + for tag, args in [('header', ['readelf','-hW']), ('dynamic',['readelf','-dW']), + ('symbols',['readelf','--dyn-syms','-W']), ('versions',['readelf','-VW']), + ('nm',['nm','-D','--format=posix']), ('nm-all',['nm','--format=posix']), ('objdump',['objdump','-p'])]: + parts[tag] = run(label+'-'+tag, [*args, path]) + assert parts[tag]['returncode']==0, (label,tag) + defs = []; undefs = []; weak = [] + for line in parts['symbols']['stdout'].splitlines(): + fields = line.split() + if len(fields) >= 8 and fields[0].rstrip(':').isdigit(): + name = fields[7]; base = name.split('@')[0] + if fields[6]=='UND': + (weak if fields[4]=='WEAK' else undefs).append(base) + elif fields[4] in ('GLOBAL','WEAK'): defs.append(base) + d = parts['dynamic']['stdout'] + result = {'path':str(path), 'sha256':sha(path), 'bytes':path.stat().st_size, + 'class':re.search(r'Class:\s+(.*)',parts['header']['stdout'])[1], + 'machine':re.search(r'Machine:\s+(.*)',parts['header']['stdout'])[1], + 'SONAME':re.findall(r'\(SONAME\).*?\[(.*?)\]',d), + 'DT_NEEDED':re.findall(r'\(NEEDED\).*?\[(.*?)\]',d), + 'RPATH':re.findall(r'\(RPATH\).*?\[(.*?)\]',d), + 'RUNPATH':re.findall(r'\(RUNPATH\).*?\[(.*?)\]',d), + 'defined':sorted(set(defs)), 'undefined':sorted(set(undefs)), + 'weak_undefined':sorted(set(weak)), + 'clip_defined':[x for x in defs if x in clips], + 'clip_undefined':[x for x in undefs if x in clips]} + save(label,result);return result +clips = ['wlr_scene_tree_set_clip','wlr_scene_tree_get_clip'] +versions = {} +for pkg in ['scenefx-0.5','wlroots-0.20','wayland-server','libdrm','pixman-1']: + versions[pkg] = {op:run('pkg-'+pkg+'-'+op,['pkg-config','--'+op,pkg])['stdout'].strip() + for op in ('modversion','cflags','libs')} +for pkg in ['scenefx-0.5','wlroots-0.20']: + path=Path('/usr/lib/pkgconfig')/(pkg+'.pc');sh=(raw/(pkg+'.pc'));sh.write_bytes(path.read_bytes()) +features = {name:run('feature-'+name,['pkg-config','--variable=have_'+name,'wlroots-0.20'])['stdout'].strip() + for name in ['drm_backend','x11_backend','libinput_backend','xwayland', + 'gles2_renderer','vulkan_renderer','gbm_allocator','udmabuf_allocator','session','color_management']} +save('dependency-versions',{'packages':versions,'wlroots_features':features}) +# Metadata is evaluated in a non-installing shell: functions are defined but not called. +meta = run('pkgbuild-metadata',['bash','-c','startdir=$2; source "$1"; declare -p pkgname pkgver pkgrel arch license depends makedepends checkdepends source sha256sums _upstream_commit; declare -p conflicts provides replaces 2>/dev/null || true','audit',repo/'PKGBUILD',repo]) +assert meta['returncode']==0 +save('pkgbuild-metadata',meta) +pkgtext=(repo/'PKGBUILD').read_text() +expected=re.findall(r"'([a-f0-9]{64})'",pkgtext) +paths=[repo/'patches/0001-mosaic-layout-viewport.patch',repo/'tests/headless-ipc.py'] +checks=[{'path':str(path),'expected':exp,'actual':sha(path),'PASS':sha(path)==exp} for path,exp in zip(paths,expected)] +assert len(checks)==2 and all(x['PASS'] for x in checks) +save('package-source-checksums',checks) +assert not re.search(r'^\s*(conflicts|provides|replaces)=',pkgtext,re.M) +save('package-contract',{'authority':'docs/PACKAGING.md','expected':['/usr/bin/swayfx-mosaic'], + 'conflicts_provides_replaces':False,'stock_scene_dependency':True,'STEP20C':False}) +stage=lab/'stage-current' +entries=[] +for path in sorted(stage.rglob('*')): + if path.is_symlink() or not path.is_dir(): + entries.append({'path':'/'+str(path.relative_to(stage)), 'symlink':path.is_symlink(), + 'executable':os.access(path,os.X_OK),'sha256':sha(path) if path.is_file() else None}) +assert len(entries)==1 and entries[0]['path']=='/usr/bin/swayfx-mosaic' and not entries[0]['symlink'] and entries[0]['executable'] +save('stock-stage-manifest',{'status':'CURRENT_PACKAGE_STAGE_PASS','entries':entries}) +paths={'system-scenefx-symbols':Path('/usr/lib/libscenefx-0.5.so'), + 'system-wlroots-elf':Path('/usr/lib/libwlroots-0.20.so'), + 'pristine-scenefx-symbols':lab/'scene-pristine-build/libscenefx-0.5.so', + 'private-scenefx-symbols':lab/'scene-private-build/libscenefx-0.5.so', + 'stock-binary-elf':stage/'usr/bin/swayfx-mosaic', + 'experimental-binary-elf':lab/'private-build/sway/swayfx-mosaic'} +if (lab/'retained-pristine-sway').exists():paths['pristine-sway-elf']=lab/'retained-pristine-sway' +if (lab/'static-build/sway/swayfx-mosaic').exists():paths['static-binary-elf']=lab/'static-build/sway/swayfx-mosaic' +elves={label:elf(label,path) for label,path in paths.items()} +stockdefs=set(elves['system-scenefx-symbols']['defined']);privdefs=set(elves['private-scenefx-symbols']['defined']) +wlrootsdefs=set(elves['system-wlroots-elf']['defined']) +save('symbol-comparison',{'private_added':sorted(privdefs-stockdefs),'private_removed':sorted(stockdefs-privdefs), + 'scene_wlroots_overlap':sorted(privdefs & wlrootsdefs), + 'pristine_system_added':sorted(set(elves['pristine-scenefx-symbols']['defined'])-stockdefs), + 'pristine_system_removed':sorted(stockdefs-set(elves['pristine-scenefx-symbols']['defined']))}) +assert set(clips)<=privdefs and not set(clips)&stockdefs +assert set(clips)<=set(elves['experimental-binary-elf']['undefined']) +save('unresolved-symbols',{'stock_missing':clips,'experimental_requires':clips,'classification':'INCOMPATIBLE_WITH_STOCK_SCENEFX'}) +# ld-linux --list never enters the application. Stock --library-path overrides +# DT_RUNPATH on this artifact; verify the selected absolute library in raw output. +loader='/usr/lib/ld-linux-x86-64.so.2';loads={} +for label,path in [('stock',paths['stock-binary-elf']),('private',paths['experimental-binary-elf'])]: + loads[label+'-default']=run('loader-'+label+'-default',[loader,'--list',path]) +for label,directory in [('private',lab/'scene-private-build'),('stock',Path('/usr/lib'))]: + loads['experimental-'+label]=run('loader-experimental-'+label,[loader,'--library-path',directory,'--list',paths['experimental-binary-elf']]) + loads['relocations-'+label]=run('relocations-'+label,['ldd','-r',paths['experimental-binary-elf']],{'LD_LIBRARY_PATH':str(directory)}) +loads['stock-bindings']=run('stock-bindings',['ldd','-r',paths['stock-binary-elf']],{'LD_DEBUG':'bindings'}) +loads['stock-relocations']=run('stock-relocations',['ldd','-r',paths['stock-binary-elf']]) +if 'static-binary-elf' in paths: + loads['static-default']=run('loader-static-default',[loader,'--list',paths['static-binary-elf']]) + loads['static-relocations']=run('static-relocations',['ldd','-r',paths['static-binary-elf']]) +assert '/usr/lib/libscenefx-0.5.so' in loads['experimental-stock']['stdout'] +assert all('undefined symbol: '+c in loads['relocations-stock']['stdout'] for c in clips) +assert 'undefined symbol:' not in loads['relocations-private']['stdout'] +assert 'undefined symbol:' not in loads['stock-relocations']['stdout'] +if 'static-relocations' in loads: assert 'undefined symbol:' not in loads['static-relocations']['stdout'] +if 'static-binary-elf' in paths: + loads['static-bindings']=run('static-bindings',['ldd','-r',paths['static-binary-elf']],{'LD_DEBUG':'bindings'}) +loads['private-bindings']=run('private-bindings',['ldd','-r',paths['experimental-binary-elf']],{'LD_DEBUG':'bindings','LD_LIBRARY_PATH':str(lab/'scene-private-build')}) +run('installed-package-metadata',['pacman','-Qi','scenefx','wlroots0.20','swayfx']) +save('loader-resolution',loads) +bindings={} +for name in ['stock-bindings','private-bindings','static-bindings']: + if name in loads: + lines=[line.strip() for line in loads[name]['stdout'].splitlines() + if 'binding file /usr/lib/libwlroots' in line and 'wlr_scene_' in line] + bindings[name]={'total':len(lines), + 'external_provider':sum(' to /usr/lib/libwlroots' not in line for line in lines), + 'self_wlroots':sum(' to /usr/lib/libwlroots' in line for line in lines), + 'lines':lines} +save('scene-symbol-bindings',bindings) +# All installed public headers are compared; generated config headers are reported +# separately rather than mistaken for source changes. +stockheaders=Path('/usr/include/scenefx-0.5/scenefx') +headeraudit=[] +for installed in sorted(stockheaders.rglob('*.h')): + rel=installed.relative_to(stockheaders);pristine=lab/'scene-pristine/include/scenefx'/rel;private=lab/'scene-private/include/scenefx'/rel + if not pristine.exists(): + headeraudit.append({'path':str(rel),'generated_or_missing_source':True,'installed_sha256':sha(installed)});continue + diff=''.join(difflib.unified_diff(pristine.read_text().splitlines(True),private.read_text().splitlines(True),fromfile='pristine/'+str(rel),tofile='private/'+str(rel))) + headeraudit.append({'path':str(rel),'system_pristine_equal':installed.read_bytes()==pristine.read_bytes(), + 'pristine_private_equal':pristine.read_bytes()==private.read_bytes(),'diff':diff, + 'sha256':{'installed':sha(installed),'pristine':sha(pristine),'private':sha(private)}}) +save('public-header-diff',headeraudit) +# Compile tiny C17 layout observers, not scene/compositor instances. Size/offset +# equality is a measured necessary condition, never a full ABI guarantee. +structs=['wlr_scene_node','wlr_scene_tree','wlr_scene','wlr_scene_surface','wlr_scene_rect','wlr_scene_shadow','wlr_scene_blur','wlr_scene_optimized_blur','wlr_scene_buffer','wlr_scene_output','wlr_scene_timer','wlr_scene_layer_surface_v1','wlr_output','wlr_output_state','wlr_surface','wlr_buffer','wlr_renderer'] +source='#define WLR_USE_UNSTABLE\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nint main(void) {\nprintf("wlroots_runtime %d.%d.%d\\n",wlr_version_get_major(),wlr_version_get_minor(),wlr_version_get_micro());\n' +for struct in structs:source+=f'printf("{struct} %zu %zu\\n", sizeof(struct {struct}), _Alignof(struct {struct}));\n' +for struct,field in [('wlr_scene_tree','children'),('wlr_scene','tree'),('wlr_scene','outputs'),('wlr_scene_node','parent'),('wlr_scene_buffer','buffer'),('wlr_scene_output','output')]: + source+=f'printf("offset:{struct}.{field} %zu\\n", offsetof(struct {struct},{field}));\n' +source+='return 0; }\n'; (lab/'struct-layout.c').write_text(source) +flags=shlex.split(run('layout-pkg-cflags',['pkg-config','--cflags','wlroots-0.20','wayland-server','pixman-1'])['stdout']) +layouts={} +for name,inc in [('system',Path('/usr/include/scenefx-0.5')),('pristine',lab/'scene-pristine/include'),('private',lab/'scene-private/include')]: + exe=lab/('layout-'+name) + build=run('layout-compile-'+name,['cc','-std=c17','-Wall','-Wextra','-Werror','-I'+str(inc),*flags,lab/'struct-layout.c','-o',exe,'-lwlroots-0.20']) + assert build['returncode']==0,build['stderr'] + layouts[name]=run('layout-run-'+name,[exe])['stdout'] +assert len(set(layouts.values()))==1 +save('struct-abi-audit',{'layouts':layouts,'size_alignment_offsets':'IDENTICAL in 17 structs / 6 offsets', + 'complete_ABI':'NOT ESTABLISHED; layout equality alone insufficient', + 'private_storage':'destroy-listener-owned sidecar; no public field additions'}) +wlheaders=[] +for src in sorted((lab/'wlroots-source/include/wlr').rglob('*.h')): + rel=src.relative_to(lab/'wlroots-source/include/wlr');installed=Path('/usr/include/wlroots-0.20/wlr')/rel + wlheaders.append({'path':str(rel),'installed':installed.exists(),'equal':installed.exists() and src.read_bytes()==installed.read_bytes(), 'source_sha256':sha(src),'installed_sha256':sha(installed) if installed.exists() else None}) +required={label:sorted(set(info['undefined']) & wlrootsdefs) for label,info in elves.items() if label!='system-wlroots-elf'} +save('wlroots-compatibility',{'pkg_config':'wlroots-0.20','version':'0.20.2','source_pin':'d783533489e1f75d6886c2ab5c5960090ef268f8','headers':wlheaders,'required_symbols_provided_by_installed_wlroots':required,'exact_installed_binary_source_provenance':'NOT ESTABLISHED by version/headers alone','future_0_20_ABI':'NOT ESTABLISHED'}) +# Compiler dependency files contain headers actually read, not inferred flags. +for build in ['stock-build','private-build','static-build','scene-private-build']: + if (lab/build).exists():run('compiler-headers-'+build,['ninja','-C',lab/build,'-t','deps']) +static=elves.get('static-binary-elf') +save('static-link-feasibility',{'status':'BUILD_AND_LOADER_PASS' if static else 'NOT TESTED', + 'scene_DT_NEEDED_absent':static is not None and 'libscenefx-0.5.so' not in static['DT_NEEDED'], + 'wlroots_still_dynamic':static is not None and 'libwlroots-0.20.so' in static['DT_NEEDED'], + 'clip_dynamic_defined':static['clip_defined'] if static else [], + 'clip_static_symbols':[line for line in run('static-clip-symbols',['nm','--format=posix',paths['static-binary-elf']])['stdout'].splitlines() if line.split()[0] in clips] if static else [], + 'overlapping_dynamic_exports':sorted(set(static['defined']) & wlrootsdefs) if static else [], + 'unique_file_possible':'ELF shape demonstrated; no second stage performed', + 'licenses':'MIT notices must accompany substantial copied code; embedded notice strategy NOT TESTED', + 'update_safety':'NOT ESTABLISHED; executable interposes shared wlroots scene symbols'}) +save('private-shared-feasibility',{'status':'PROCESS_LOCAL_LOADER_PASS; distinct SONAME packaging NOT TESTED', + 'same_SONAME':'unsafe naive substitution; default build RUNPATH is private LAB, not distributable', + 'future_requirements':['distinct SONAME and DT_NEEDED','private installation path','controlled RUNPATH','audit all transitive scene consumers and interposition','security/version owner'], + 'current_one_file_contract':'would require new decision for additional library artifact'}) +print('PASS stock stage, ELF/loader comparisons, missing clip proof, C17 17-struct layouts and header audit') diff --git a/tooling/probes/step20c-scenefx-abi-package/prepare.py b/tooling/probes/step20c-scenefx-abi-package/prepare.py new file mode 100644 index 0000000..f4fae7d --- /dev/null +++ b/tooling/probes/step20c-scenefx-abi-package/prepare.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +"""One-shot private source/build/stage reconstruction; never runs a compositor. + +CONTRACT: empty external LAB, explicit local Git object stores at fixed commits. +INVARIANT: every command is logged; no install except stock DESTDIR staging. +WHY: a successful experimental link must never stand in for stock packaging. +""" +import argparse +import hashlib +import json +import os +from pathlib import Path +import shutil +import signal +import subprocess +import tarfile + +p = argparse.ArgumentParser(description=__doc__) +p.add_argument('lab', type=Path) +p.add_argument('--scenefx-git', type=Path, required=True) +p.add_argument('--wlroots-git', type=Path, required=True) +p.add_argument('--pristine-binary', type=Path) +p.add_argument('--static', action='store_true') +a = p.parse_args() +lab = a.lab.resolve() +repo = Path(__file__).resolve().parents[3] +assert lab.is_dir() and lab.is_relative_to(Path.home()) and lab != Path.home() +assert not lab.is_relative_to(repo) and not (lab/'stock').exists() +assert not (lab/'prepare-started.json').exists(), 'budget consumed; inspect logs, never restart' +(lab/'prepare-started.json').write_text(json.dumps(vars(a), default=str, indent=2)+'\n') +raw = lab/'raw'; raw.mkdir(exist_ok=True) +results = lab/'results'; results.mkdir(exist_ok=True) +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', 'OPT_TRACE')} +env['PYTHONDONTWRITEBYTECODE'] = '1' +commands = [] + +def run(args, cwd=None, extra=None): + i = len(commands) + entry = {'args': list(map(str, args)), 'cwd': str(cwd or repo), 'env_override': extra or {}} + commands.append(entry) + with (raw/f'build-{i:03}.log').open('wb') as out: + proc = subprocess.Popen(entry['args'], cwd=cwd or repo, env={**env, **(extra or {})}, + stdout=out, stderr=subprocess.STDOUT, start_new_session=True) + try: + proc.wait(timeout=600) + except subprocess.TimeoutExpired: + # Own only this command's group; compiler children must not outlive + # a failed reproduction. No process-name or desktop-wide cleanup. + os.killpg(proc.pid, signal.SIGTERM) + try: + proc.wait(timeout=5) + except subprocess.TimeoutExpired: + os.killpg(proc.pid, signal.SIGKILL) + proc.wait() + try: + os.killpg(proc.pid, signal.SIGKILL) + except ProcessLookupError: + pass + entry['timed_out'] = True + entry['returncode'] = proc.returncode + (results/'build-commands.json').write_text(json.dumps(commands, indent=2)+'\n') + if proc.returncode: + raise RuntimeError(f'command {i} failed; inspect {raw}/build-{i:03}.log') + +def archive(git, rev, name): + target = lab/name + target.mkdir() + arc = lab/(name+'.tar') + with arc.open('wb') as out: + subprocess.run(['git', '-C', str(git), 'archive', rev], stdout=out, check=True, env=env) + with tarfile.open(arc) as tf: + tf.extractall(target, filter='data') + return target + +pins = {'SwayFX': 'fd71a6bdc061bd633b488ae7b83e8a6981d22f86', + 'Mosaic': 'cd550382b311efdbfe3ed892f4e1d281d852e754', + 'SceneFX': '37ccd723bef49e6891156ffafce8f549f01446cc', + 'wlroots': 'd783533489e1f75d6886c2ab5c5960090ef268f8'} +stock = archive(repo, pins['SwayFX'], 'stock') +archive(a.scenefx_git, pins['SceneFX'], 'scene-pristine') +archive(a.scenefx_git, pins['SceneFX'], 'scene-private') +archive(a.wlroots_git, pins['wlroots'], 'wlroots-source') +archive(repo, pins['Mosaic'], 'mosaic-private') +patchdir = lab/'patches'; patchdir.mkdir() +patch = patchdir/'production.patch'; shutil.copyfile(repo/'patches/0001-mosaic-layout-viewport.patch', patch) +run(['git', 'apply', '--check', patch], stock); run(['git', 'apply', patch], stock) +for rel in ('include', 'sway', 'meson.build', 'meson_options.txt', 'tests/mosaic-model.c'): + run(['git', 'diff', '--no-index', '--exit-code', stock/rel, repo/rel]) +chains = {'scene-private': ['step20c-valid-grab/scenefx-popup-plus-base.patch', + 'step20c-temporal-damage/scenefx-timing.patch', + 'step20c-optimized-blur-cache/scenefx-cache-trace.patch'], + 'mosaic-private': ['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']} +for tree, chain in chains.items(): + for rel in chain: + dst = patchdir/Path(rel).name; shutil.copyfile(repo/'tooling/probes'/rel, dst) + run(['git', 'apply', '--check', dst], lab/tree); run(['git', 'apply', dst], lab/tree) +flags = ['-Dwerror=false', '-Dsd-bus-provider=libsystemd', '-Dmosaic-package=true', + '-Dswaybar=false', '-Dswaynag=false', '-Dman-pages=disabled', '-Ddefault-wallpaper=false'] +run(['meson', 'setup', lab/'stock-build', stock, '--prefix=/usr', '--bindir=bin', *flags]) +run(['meson', 'compile', '-C', lab/'stock-build', '-j', '8']) +run(['meson', 'test', '-C', lab/'stock-build', 'mosaic-model', '--print-errorlogs']) +run(['meson', 'install', '-C', lab/'stock-build', '--destdir', lab/'stage-current', '--no-rebuild']) +for name in ('pristine', 'private'): + run(['meson', 'setup', lab/('scene-'+name+'-build'), lab/('scene-'+name), '-Dexamples=false', '-Dwerror=false']) + run(['meson', 'compile', '-C', lab/('scene-'+name+'-build'), '-j', '8']) +private_env = {'PKG_CONFIG_PATH': str(lab/'scene-private-build/meson-uninstalled')} +run(['meson', 'setup', lab/'private-build', lab/'mosaic-private', *flags], extra=private_env) +run(['meson', 'compile', '-C', lab/'private-build', '-j', '8'], extra=private_env) +run(['meson', 'test', '-C', lab/'private-build', 'mosaic-model', '--print-errorlogs']) +if a.static: + # Source inspection establishes a native Meson library() target honoring this + # option. Only SceneFX is embedded; wlroots and other dependencies stay shared. + run(['meson', 'setup', lab/'scene-static-build', lab/'scene-private', + '-Ddefault_library=static', '-Dexamples=false', '-Dwerror=false']) + run(['meson', 'compile', '-C', lab/'scene-static-build', '-j', '8']) + static_env = {'PKG_CONFIG_PATH': str(lab/'scene-static-build/meson-uninstalled')} + run(['meson', 'setup', lab/'static-build', lab/'mosaic-private', *flags], extra=static_env) + run(['meson', 'compile', '-C', lab/'static-build', '-j', '8'], extra=static_env) + run(['meson', 'test', '-C', lab/'static-build', 'mosaic-model', '--print-errorlogs']) +if a.pristine_binary: + # Retained pristine binary is tied to the sealed A campaign identity. + assert hashlib.sha256(a.pristine_binary.read_bytes()).hexdigest() == '8ac865481bc0487344210cd0e16d8e6c7655b29db887cd58ddc11b0ca22fe494' + # Copy/hash only, never launch it. + shutil.copyfile(a.pristine_binary, lab/'retained-pristine-sway') + pins['pristine_binary_origin'] = str(a.pristine_binary.resolve()) + pins['pristine_binary_sha256'] = hashlib.sha256(a.pristine_binary.read_bytes()).hexdigest() +(results/'source-pins.json').write_text(json.dumps(pins, indent=2)+'\n') +print('PASS private builds/model tests and single stock staging; zero compositor launches') diff --git a/tooling/probes/step20c-scenefx-abi-package/seal.py b/tooling/probes/step20c-scenefx-abi-package/seal.py new file mode 100644 index 0000000..6de81df --- /dev/null +++ b/tooling/probes/step20c-scenefx-abi-package/seal.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +"""Seal completed source/evidence inventories once, or verify existing bytes. + +WHY: derived reports cannot silently overwrite the evidence they cite. +CONTRACT: fixtures finalized first; report/diff are captured outside inventory. +INVARIANT: no manifest replacement, no binaries/build trees in source inventory. +""" +import argparse +import hashlib +from pathlib import Path + +p=argparse.ArgumentParser(description=__doc__);p.add_argument('lab',type=Path) +p.add_argument('--verify',action='store_true');a=p.parse_args();lab=a.lab.resolve() +fixture=Path(__file__).resolve().parent;repo=fixture.parents[2] +assert lab.is_relative_to(Path.home()) and lab!=Path.home() and not lab.is_relative_to(repo) +r=lab/'results' +def sha(path):return hashlib.sha256(path.read_bytes()).hexdigest() +if not a.verify: + assert not any((r/(name+'-sha256.txt')).exists() for name in ['source','evidence']) + source=set(p for p in fixture.iterdir() if p.is_file()) + for tree in ['stock','scene-pristine','scene-private','wlroots-source','mosaic-private']: + source.update(p for p in (lab/tree).rglob('*') if p.is_file() and 'subprojects' not in p.relative_to(lab/tree).parts) + source.update(p for p in (lab/'patches').iterdir() if p.is_file()) + source.update(p for p in lab.iterdir() if p.is_file() and p.suffix in ['.py','.c','.md']) + evidence=set(p for p in (lab/'raw').iterdir() if p.is_file()) + evidence.update(p for p in r.iterdir() if p.is_file() and not p.name.endswith('-sha256.txt')) + for name,files in [('source',source),('evidence',evidence)]: + manifest=r/(name+'-sha256.txt') + # Exclusive creation, never truncate a prior result on interrupted runs. + with manifest.open('x') as out: + for path in sorted(files):out.write(f'{sha(path)} {path}\n') +for name in ['source','evidence']: + path=r/(name+'-sha256.txt');lines=path.read_text().splitlines() + for line in lines: + expected,filename=line.split(' ',1);assert sha(Path(filename))==expected,filename + print(name,len(lines),sha(path),'PASS') diff --git a/tooling/probes/step20c-scenefx-abi-package/summarize.py b/tooling/probes/step20c-scenefx-abi-package/summarize.py new file mode 100644 index 0000000..13bae97 --- /dev/null +++ b/tooling/probes/step20c-scenefx-abi-package/summarize.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +"""Summarize measured evidence without turning a candidate into an approval. + +CONTRACT: successful S0 and expected incompatible naive S1 are distinct. +INVARIANT: no ABI inference from SONAME, compile success, or struct size alone. +""" +import argparse +import json +from pathlib import Path + +p=argparse.ArgumentParser(description=__doc__);p.add_argument('lab',type=Path) +a=p.parse_args();lab=a.lab.resolve();r=lab/'results' +assert not (r/'evidence-sha256.txt').exists(), 'already sealed' +def read(name):return json.loads((r/(name+'.json')).read_text()) +def save(name,value):(r/(name+'.json')).write_text(json.dumps(value,indent=2)+'\n') +assert read('stock-stage-manifest')['status']=='CURRENT_PACKAGE_STAGE_PASS' +assert read('unresolved-symbols')['classification']=='INCOMPATIBLE_WITH_STOCK_SCENEFX' +options={ + 'A_upstream':{'technical':'UNKNOWN acceptance; API source prototype demonstrated', + 'system_library_intact':'normal maintained upstream upgrade required; no private overwrite', + 'sway_intact':True,'coexistence':'conditional on supported upstream API/version', + 'stable_API':'NOT AVAILABLE in pinned stock','ABI':'NOT ESTABLISHED for future upstream release', + 'one_file':'possible after adoption, NOT TESTED','upgrade_risk':'medium; shared dependency transition', + 'SceneFX_maintenance':'upstream if accepted; no commitment obtained','wlroots':'version-paired qualification', + 'reproducibility':'private prototype only','human_decision':True, + 'remaining':['accepted ownership/error/bounds contract','upstream maintainer agreement','public ABI/version policy','graphical feasibility gates']}, + 'B_static':{'technical':'shared-source static build and loader PASS', + 'system_library_intact':True,'sway_intact':True,'coexistence':'conditional; no filesystem overlap', + 'stable_API':'private','ABI':'17-struct layout and current relocations PASS; future safety NOT ESTABLISHED', + 'one_file':'binary dependency shape demonstrated, static stage NOT TESTED', + 'upgrade_risk':'high until explicit wlroots interposition/update policy', + 'SceneFX_maintenance':'Mosaic must own all embedded security fixes; no owner committed', + 'wlroots':'still shared libwlroots-0.20.so; overlapping exports must be qualified', + 'reproducibility':'fixed sources/patches/commands retained; bit reproducibility NOT TESTED', + 'human_decision':True,'remaining':['symbol binding contract','wlroots version matrix','MIT notices in one-file distribution','security/rebase owner','static package contract acceptance','graphical tests separately authorized']}, + 'C_private_shared':{'technical':'same-SONAME private process resolution PASS; distinct-SONAME candidate NOT TESTED', + 'system_library_intact':True,'sway_intact':True,'coexistence':'conditional on separate file/SONAME and complete binding audit', + 'stable_API':'private','ABI':'future isolated distribution NOT ESTABLISHED', + 'one_file':False,'upgrade_risk':'medium/high; shared wlroots still imposes constraints', + 'SceneFX_maintenance':'Mosaic-private library owner required','wlroots':'shared consumers/interposition remain', + 'reproducibility':'laboratory lookup only','human_decision':True, + 'remaining':['new owned library file and packaging decision','distinct SONAME/DT_NEEDED','private RUNPATH/transitive lookup','no stock/private duplication within process','security owner']}, + 'D_no_new_API':{'technical':'UNKNOWN; no implementation authorized', + 'system_library_intact':True,'sway_intact':True,'coexistence':'conditional on valid compositor architecture', + 'stable_API':'existing unstable scene API only','ABI':'new dependency avoided, architecture unproved', + 'one_file':'possible, NOT TESTED','upgrade_risk':'unknown, broad renderer maintenance possible', + 'SceneFX_maintenance':'normal upstream for stock; Mosaic owns alternate mechanism', + 'wlroots':'same version-paired graphical contracts','reproducibility':'NOT TESTED','human_decision':True, + 'remaining':['popup/global stacking','hit traversal','damage/visibility','effects and saved buffers','rotation/scaling/migration','transaction state consistency']}} +save('architectural-options',options) +save('coexistence-matrix',{'S0':'no filesystem conflict; stock dynamic dependency', + 'S1_naive_stock':'missing two symbols; cannot be distributed as stock-compatible', + 'S2_static':'no SceneFX file, 85 overlapping exports with shared wlroots', + 'S2_shared':'extra private library file requires new package ownership contract', + 'standard_sway':'no modification in any experiment'}) +save('upgrade-rollback-risks',{'current_upgrade':'no conflicts/provides/replaces prevents package-manager block; shared SONAME/API changes can still break Mosaic and require separate rebuild', + 'static_upgrade':'SceneFX system upgrade does not update embedded code; shared wlroots remains an ABI constraint', + 'private_shared_upgrade':'private file protects library ownership; shared wlroots plus symbol semantics still require qualification', + 'rollback':'hypothetical remove only /usr/bin/swayfx-mosaic in S0; stock sway/libraries untouched; actual removal NOT TESTED', + 'forbidden':'pinning shared system dependencies to block stock SwayFX updates'}) +save('decision-gates',{'public_API':'new decision before implementation', + 'package_contract':'unchanged; static bundling or extra private library requires explicit decision', + 'GPU_lifetime_and_cache':'NOT TESTED here; separate from linker evidence', + 'recommended_next':'bounded non-graphical design of symbol/ownership/update and license contracts for static candidate; prepare upstream API dossier without submission', + 'integration_authorized':False}) +save('matrix',{'S0_build':'PASS','S0_model':'PASS','S0_stage':'CURRENT_PACKAGE_STAGE_PASS', + 'S1_private_loader':'PASS relocations only','S1_stock_loader':'INCOMPATIBLE_WITH_STOCK_SCENEFX', + 'S2_static_build':'PASS','S2_static_loader':'PASS relocations only','distinct_SONAME_build':'NOT TESTED', + 'graphics':'NOT RUN / FORBIDDEN','system_install':'NOT RUN / FORBIDDEN','bit_reproducibility':'NOT TESTED'}) +save('summary',{'verdict':'PARTIALLY_FEASIBLE','architecture':'SCENEFX_ABI_PACKAGE_REQUIRES_ARCHITECTURAL_DECISION', + 'current_package':'CURRENT_PACKAGE_STAGE_PASS','experimental_stock':'INCOMPATIBLE_WITH_STOCK_SCENEFX', + 'candidate':'static embedding demonstrated technically; unresolved maintenance, symbol and license gates', + 'long_term_direction':'upstream maintained API preferred conditionally, no acceptance presumed', + 'budget':{'stock_rebuild':1,'stock_stage':1,'private_shared_chain':1,'optional_static_chain':1, + 'pristine_SceneFX_reference_build':1,'compositor_launches':0,'graphical_runs':0,'installs':0}, + 'phase_B_commit_push':False}) +print(json.dumps(read('summary'),indent=2))