docs(mosaic): preserve SceneFX static distribution contract evidence

This commit is contained in:
fy59 2026-10-13 06:55:24 +02:00
parent bb3a112550
commit 4f4f3fefcd
10 changed files with 1125 additions and 0 deletions

View file

@ -4,6 +4,8 @@
### STEP20C experimental evidence
- 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.
- 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.

View file

@ -353,3 +353,18 @@ 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 preserved [static distribution contract dossier](design/STEP20C_SCENEFX_STATIC_DISTRIBUTION_CONTRACT.md)
is **EXPERIMENTAL / PARTIALLY_FEASIBLE**, conclusion
**STATIC_DISTRIBUTION_REQUIRES_CONTRACT_DECISION**. One private Meson DESTDIR
stage of the hash-qualified static executable contains exactly
`/usr/bin/swayfx-mosaic`; this proves file shape only. The 85 overlapping
exports and 31/34 wlroots scene bindings into the executable remain explicit
paired-version constraints, not automatically defects. Only the pinned
SceneFX 0.5 / observed wlroots 0.20.2 pair has non-graphical evidence.
Notices (including generated protocols), security ownership, upgrades, rollback
and graphical semantics remain gates. A disposable notice-section extraction
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.

View file

@ -294,3 +294,18 @@ 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 preserved [static distribution contract dossier](design/STEP20C_SCENEFX_STATIC_DISTRIBUTION_CONTRACT.md)
is **EXPERIMENTAL / PARTIALLY_FEASIBLE**, conclusion
**STATIC_DISTRIBUTION_REQUIRES_CONTRACT_DECISION**. One private Meson DESTDIR
stage of the hash-qualified static executable contains exactly
`/usr/bin/swayfx-mosaic`; this proves file shape only. The 85 overlapping
exports and 31/34 wlroots scene bindings into the executable remain explicit
paired-version constraints, not automatically defects. Only the pinned
SceneFX 0.5 / observed wlroots 0.20.2 pair has non-graphical evidence.
Notices (including generated protocols), security ownership, upgrades, rollback
and graphical semantics remain gates. A disposable notice-section extraction
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.

View file

@ -52,3 +52,14 @@ 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.
## Preserved static distribution contract evidence
The [static dossier](design/STEP20C_SCENEFX_STATIC_DISTRIBUTION_CONTRACT.md)
records STATIC_STAGE_FILE_SHAPE_PASS from one private stage, with no library
or notice file installed. It proposes dependency/security/version/notice
policies, without changing this package contract or PKGBUILD. Embedded notices
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.

View file

@ -209,3 +209,21 @@ No integration or packaging contract is approved. The historical audit is frozen
The [ABI/package source fixture](../tooling/probes/step20c-scenefx-abi-package/README.md)
records build-only reconstruction and non-graphical diagnostic commands.
The preserved [static distribution contract dossier](design/STEP20C_SCENEFX_STATIC_DISTRIBUTION_CONTRACT.md)
is **EXPERIMENTAL / PARTIALLY_FEASIBLE**, conclusion
**STATIC_DISTRIBUTION_REQUIRES_CONTRACT_DECISION**. One private Meson DESTDIR
stage of the hash-qualified static executable contains exactly
`/usr/bin/swayfx-mosaic`; this proves file shape only. The 85 overlapping
exports and 31/34 wlroots scene bindings into the executable remain explicit
paired-version constraints, not automatically defects. Only the pinned
SceneFX 0.5 / observed wlroots 0.20.2 pair has non-graphical evidence.
Notices (including generated protocols), security ownership, upgrades, rollback
and graphical semantics remain gates. A disposable notice-section extraction
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.
The [static-contract source fixture](../tooling/probes/step20c-scenefx-static-contract/README.md)
preserves the bounded audit/staging procedure; all generated results stay external.

View file

@ -298,3 +298,18 @@ 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 preserved [static distribution contract dossier](design/STEP20C_SCENEFX_STATIC_DISTRIBUTION_CONTRACT.md)
is **EXPERIMENTAL / PARTIALLY_FEASIBLE**, conclusion
**STATIC_DISTRIBUTION_REQUIRES_CONTRACT_DECISION**. One private Meson DESTDIR
stage of the hash-qualified static executable contains exactly
`/usr/bin/swayfx-mosaic`; this proves file shape only. The 85 overlapping
exports and 31/34 wlroots scene bindings into the executable remain explicit
paired-version constraints, not automatically defects. Only the pinned
SceneFX 0.5 / observed wlroots 0.20.2 pair has non-graphical evidence.
Notices (including generated protocols), security ownership, upgrades, rollback
and graphical semantics remain gates. A disposable notice-section extraction
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.

View file

@ -0,0 +1,600 @@
# STEP20C SceneFX static distribution and upstream contract
**EXPERIMENTAL / PRESERVED HISTORICAL EVIDENCE / no production integration.**
Protocol recorded before new tests. Phase A frozen and published at
`bb3a112550f45667e501d05d5a08aa8f7d0c02c6`; both remotes equal, clean worktree.
STEP20A DONE; STEP20B EXPERIMENTAL; STEP20C NEXT; STEP20D–G PLANNED.
Production multi-output NOT YET SUPPORTED.
## Authority, protocol and decision boundary
[Packaging](../PACKAGING.md), [Architecture](../ARCHITECTURE.md),
[Upstream](../UPSTREAM.md), [Development](../DEVELOPMENT.md),
[Known limitations](../KNOWN_LIMITATIONS.md) and [Roadmap](../ROADMAP.md)
remain authoritative. No UX, production source, recipe, pin, cumulative patch
or system dependency change is authorized. Production strategy:
**NONE — USER DECISION REQUIRED**.
Reuse [historical ABI evidence](STEP20C_SCENEFX_ABI_PACKAGE_FEASIBILITY.md):
current stock package CURRENT_PACKAGE_STAGE_PASS; private shared with stock
SceneFX INCOMPATIBLE_WITH_STOCK_SCENEFX; static ELF resolves without SceneFX
DT_NEEDED, with shared wlroots. Historical verdict PARTIALLY_FEASIBLE.
85 common symbols are not 85 defects; 34 actual wlroots scene bindings have
31 external providers and three wlroots providers. No visibility policy is
authorized. Sizes/offsets and loader success do not establish full semantic ABI.
Pins: SwayFX 0.6 `fd71a6bdc061bd633b488ae7b83e8a6981d22f86`;
experimental Mosaic `cd550382b311efdbfe3ed892f4e1d281d852e754`;
SceneFX 0.5 `37ccd723bef49e6891156ffafce8f549f01446cc`;
wlroots 0.20.2 `d783533489e1f75d6886c2ab5c5960090ef268f8`.
No newer source is substituted.
Read-only historical laboratory:
`/home/fy59/swayfx-mosaic-abi-package-tp22xfwm`.
New laboratory: `/home/fy59/swayfx-mosaic-static-contract-7_vlvpjw`. Commands, return codes and all generated results
remain there. Historical manifests are verified, never regenerated.
### Planned non-graphical checks
Map source references and ELF definitions into A public used SceneFX, B private
clip, C overlap with wlroots, D remaining dynamic and E executable-local
symbols. Record visibility, version, actual retained binding, owner and risk.
Inspect exported/dynamic tables, relocations and transitive package owners.
Read pinned headers, implementations, renderer/build/protocol contracts.
One static staging uses the qualified binary, not a newly built compositor.
Historical static prefix `/usr/local` requires a fresh private Meson install
plan on identical source bytes with `/usr` prefix; reuse the verified binary
without rebuild/strip, normal DESTDIR installation only. Inspect every entry
including directories, regular files, links, mode, uid/gid and hashes.
One-file shape says nothing about graphics, legal compliance or release readiness.
Audit exact source license texts and source-level notices for incorporated
components. Compare N1 embedded/extractable notice, N2 package metadata and
N3 separate installed notice. An optional disposable ELF-section roundtrip
tests N1 mechanics only. Never evade a license condition to retain one file.
Propose explicit dependency/version gates, manual security ownership and
reproducible source/patch/build provenance plus qualified rollback. Prepare
an upstream API dossier without contact, submission or public-field changes.
Compare maintained upstream API, private static, distinct private shared and
no new SceneFX primitive. Recommendations are not authorizations.
### Classification
FEASIBLE requires staging and relevant binding characterization plus explicit
dependency/maintenance contracts. PARTIALLY_FEASIBLE retains unresolved notice,
maintenance, version or ownership questions. INCONCLUSIVE means missing
essential staging/binding evidence. BLOCKED means a reproducible invariant
failure without validated candidate in scope. Unchosen license or packaging
policy is not automatically a code defect. No graphical campaign is allowed.
## Completed campaign and staging
`SWAYFX_MOSAIC_STATIC_DISTRIBUTION_CONTRACT_PROBE=PARTIALLY_FEASIBLE`
Architecture: **STATIC_DISTRIBUTION_REQUIRES_CONTRACT_DECISION**.
Immediate experimental candidate: private static SceneFX. Conditional long-term
maintenance direction: an upstream-maintained clipping API. Production strategy:
**NONE — USER DECISION REQUIRED**. No option is integrated.
`STATIC_STAGE_FILE_SHAPE_PASS` is measured, not inferred. One normal Meson
DESTDIR install returned 0. Complete non-directory manifest:
| Path | Kind | Mode | Actual uid:gid | Bytes | SHA-256 |
| --- | --- | --- | --- | --- | --- |
| `/usr/bin/swayfx-mosaic` | regular ELF | 0755 | 1000:1000 | 6,760,600 | `ccd47ce5b74c132e19bcd46d022a80e90e8a944acc3db521a89427ee7913c614` |
Directories `/usr` and `/usr/bin` are recorded in the complete JSON manifest.
No symlink, library, header, config, service, notice or second regular file.
The future package would own this path as root:root; no ownership change was
attempted. This is not an Arch package or legal distribution qualification.
The historical build has prefix `/usr/local`, bindir `bin`, debug build,
strip=false, mosaic-package=true, swaybar/swaynag=false, man-pages=disabled,
default-wallpaper=false. SceneFX alone has default_library=static; the
compositor's default_library=shared does not make its explicit archive dynamic.
The private new plan used the same package flags and static uninstalled
pkg-config provider, changing only prefix to `/usr` for staging. Sources were
copied and byte-verified against the sealed historical tree. The exact
qualified binary was copied into that plan's build-relative target location.
Meson setup performs its normal dependency/compiler configuration probes;
**no SceneFX or compositor source was compiled or linked again**. Install used
`--no-rebuild`, without tags or manual placement into DESTDIR. The staged hash
still matches the historical binary. Its embedded `/usr/local` configuration
strings were not rebuilt into `/usr`: runtime path suitability is NOT TESTED.
This intentional limitation is why this result qualifies file shape only.
Preparation initially failed because the fixture assumed a `subprojects`
directory absent from the historical archive. That Python failure preceded
Meson setup/install. The fixture was corrected to copy only existing inputs;
the retained preparation was resumed. Exactly one install attempt occurred.
The persistent `stage-install-started.json` prohibits a second attempt; the
historical laboratory and manifests were not changed.
## ELF loading and the five symbol groups
The private binary is ELF64, little-endian x86-64. No RPATH or RUNPATH.
Both clipping functions are FUNC/GLOBAL/DEFAULT, unversioned, in `.symtab`;
`nm` reports ordinary `T` definitions. They are absent from `.dynsym` and
have no unresolved dynamic relocation. They are executable definitions,
**not ELF LOCAL symbols**. Their call sites include private transaction tiled
and popup clipping, with get calls also in private output instrumentation.
No public structure field was introduced by the preserved sidecar patch.
SceneFX is absent from DT_NEEDED and the retained recursive loader graph;
`libwlroots-0.20.so` remains shared. Retained `ldd -r` returned 0 with no
undefined-symbol diagnostics. Strong undefined imports in the ELF are normal
shared-library references, not 891 loader failures. Weak optional imports are
reported separately. New readelf header/dynamic/full-symbol/relocation/version
and nm outputs are preserved; actual provider records are reused from the
sealed trusted loader diagnostics. No compositor entry point was run.
The standalone historical graph contains no SceneFX shared library alongside
the embedded archive. A future plugin, preload or additional dependency could
introduce one; that graph is NOT TESTED. Static embedding does not isolate the
process namespace. No export, rename, hidden-visibility or linker-policy
change was made or recommended without a separate experiment/authorization.
| Group | Definition and evidence | Current owner/provider | Convention and upgrade risk |
| --- | --- | --- | --- |
| A | 73 public SceneFX symbols required as strong imports by shared experimental compositor; static definitions and source references recorded individually | SceneFX implementation embedded in static executable; this set covers compiled SwayFX/Mosaic, not only newly added layout code | Unversioned C scene/effects/renderer functions, unstable public types; new header/body semantics need audit |
| B | `wlr_scene_tree_set_clip`, `wlr_scene_tree_get_clip`; strong private shared imports, executable ordinary definitions | Private SceneFX sidecar; direct static call resolution | No official upstream API/version/error guarantee; private prototype only |
| C | 85 names defined by pristine SceneFX and wlroots and exported by the static executable | Scene, EGL, color and matrix functions; actual providers separately recorded | Matching names alone establish overlap; only observed bindings prove selection; unobserved routes remain uncertain |
| D | 891 distinct strong undefined dynamic symbol base names in static executable, with versions/visibility preserved | Shared wlroots, Wayland, rendering and other system providers selected by retained loader records | ELF version requirements and SONAME do not prove semantic compatibility; all recursive versions matter |
| E | 1,275 ELF LOCAL definitions, including clip sidecar helpers and compositor internals; full table retained | Executable internal implementations | Local helpers have no dynamic ABI; they do not isolate the 85 shared scene/renderer exports |
`scene-symbol-ownership.json` includes groups, source locations, definitions/
reference candidates, full ELF records, consumer/provider pairs and risk per
symbol. A source occurrence is explicitly evidence of a reference candidate;
ELF tables determine definition/import status. Missing runtime records mean
NOT OBSERVED, not an invented provider. `exported-symbols.json` retains the
complete table, including actual symbol versions. Public WLR/SceneFX scene
functions have no ELF symbol-version compatibility namespace in this pair.
Versioned libc/etc imports are retained rather than relabeled unversioned.
Some helpers such as `wlr_scene_subsurface_tree_set_clip` remain dynamic
wlroots imports; that existing surface/subsurface primitive is distinct from
the two private inherited generic tree functions.
### Interposition: intentional integration versus unresolved semantics
The preserved stock/private/static diagnostics each observe **34 wlroots scene
bindings**. The exact same three remain in wlroots:
- `wlr_scene_subsurface_tree_create`
- `wlr_scene_surface_try_from_buffer`
- `wlr_scene_surface_create`
The other 31 bind to system/private SceneFX in shared controls and to the
static executable in the candidate. Their exact names follow below. This
pattern is coherent with ordinary SceneFX integration, already present in
stock; it is not a newly demonstrated clipping failure.
Pinned `wlroots-source/types/scene/xdg_shell.c:66`,
`layer_shell_v1.c:128`, `drag_icon.c:49`, `subsurface_tree.c:246` and
`surface.c:432` show why mixed ownership matters: wlroots helpers construct
SceneFX tree/buffer objects through interposed constructors, then manage
surface listeners and protocol lifecycle. Renderer/color/EGL structs and
callbacks similarly cross library boundaries. Preserving compatible object
and function semantics is necessary for these routes. This necessity follows
from source paths; **no hide/rename counterfactual was tested**. The minimum
safe export set is therefore NOT ESTABLISHED.
The 31 measured bindings are intentional/coherent for the tested pair at the
loader level, and helper construction routes substantiate their integration
role. Complete semantic necessity of every binding and all remaining overlap
paths is uncertain. Unobserved EGL/color/matrix routes must not be called
qualified merely because the names match. Any other wlroots version or distro
build is NOT QUALIFIED. A hypothetical distinct SONAME also does not fix this
process-wide behavior by itself.
```text
wlr_scene_buffer_create
wlr_scene_buffer_from_node
wlr_scene_buffer_send_frame_done
wlr_scene_buffer_set_buffer
wlr_scene_buffer_set_buffer_with_options
wlr_scene_buffer_set_color_encoding
wlr_scene_buffer_set_color_range
wlr_scene_buffer_set_dest_size
wlr_scene_buffer_set_opacity
wlr_scene_buffer_set_opaque_region
wlr_scene_buffer_set_primaries
wlr_scene_buffer_set_source_box
wlr_scene_buffer_set_transfer_function
wlr_scene_buffer_set_transform
wlr_scene_get_scene_output
wlr_scene_node_coords
wlr_scene_node_destroy
wlr_scene_node_place_above
wlr_scene_node_place_below
wlr_scene_node_set_enabled
wlr_scene_node_set_position
wlr_scene_output_build_state
wlr_scene_output_create
wlr_scene_output_destroy
wlr_scene_output_needs_frame
wlr_scene_output_send_frame_done
wlr_scene_output_set_position
wlr_scene_rect_from_node
wlr_scene_timer_finish
wlr_scene_tree_create
wlr_scene_tree_from_node
```
## Dependency contract and conditional future recipe
The static executable's complete direct DT_NEEDED list is:
```text
libcairo.so.2 libdrm.so.2 libjson-c.so.5 libevdev.so.2 libinput.so.10
libudev.so.1 libm.so.6 libpango-1.0.so.0 libgobject-2.0.so.0 libglib-2.0.so.0
libpcre2-8.so.0 libpixman-1.so.0 libwlroots-0.20.so libwayland-server.so.0
libxkbcommon.so.0 libEGL.so.1 libgbm.so.1 libGLESv2.so.2 liblcms2.so.2
libxcb.so.1 libxcb-icccm.so.4 libpangocairo-1.0.so.0 libc.so.6
```
Only SceneFX is statically embedded. EGL/GBM/GLESv2/lcms2 become direct
DT_NEEDED instead of arriving through SceneFX. libdrm and pixman were already
direct ELF needs, but are only makedepends in the current recipe. Static
embedding must not rely on removing SceneFX while accidentally losing runtime
providers previously brought in by it. The retained recursive graph also has
Wayland client, Vulkan, seatd/libseat, display-info, liftoff, libudev, XCB
extensions and renderer/allocator libraries. Their ownership remains with
shared providers; GPU driver modules loaded later are outside loader-only proof.
| Provider in observed system | Candidate future runtime declaration | Reason |
| --- | --- | --- |
| wlroots0.20 | retain, qualify exact build/version matrix | still shared; cannot freeze ordinary SwayFX upgrades |
| wayland | retain | server direct, client/transitive and protocol generation |
| libglvnd | add explicit runtime/build availability | direct EGL and GLESv2 |
| mesa | add explicit runtime/build availability | observed libgbm provider; actual renderer/driver gates remain |
| lcms2 | add explicit runtime/build availability when enabled | direct color-management provider in this build |
| libdrm, pixman | promote to runtime depends | direct DT_NEEDED, currently only makedepends |
| glib2, glibc, xcb-util-wm | make direct ownership explicit in proposed recipe review | GObject/GLib, libc/libm, libxcb-icccm; presently supplied transitively/base system |
| cairo, json-c, libevdev, libinput, systemd-libs, libxcb, libxkbcommon, pango, pcre2 | retain | direct providers and current enabled features |
| gdk-pixbuf2, ttf-font | retain pending runtime feature audit | absence in DT_NEEDED alone does not authorize removal of feature/data requirements |
| scenefx | conditionally remove runtime dependency only after static architecture approval | no SceneFX DT_NEEDED; replace with verified embedded-source build provenance |
| git, meson, ninja, pkgconf, python, wayland-protocols | retain build tools/inputs | source/patch/build/protocol generation |
| kitty, grim | retain future check requirements | not executed in this non-graphical ticket |
This is a proposal, not an applied recipe. Package names describe observed
Arch owners, not universal providers. Requalify clean dependency closure,
feature toggles, strip/notice retention, reproducibility, staging and separate
graphics checks before changing the recipe. No PKGBUILD byte changed.
## Version and shared-structure contract
Only pinned SceneFX 0.5 source plus **observed wlroots 0.20.2-1 binary** is
qualified for these non-graphical checks. Installed headers match the pinned
wlroots source for 121 non-generated files; runtime/version metadata agree.
That does not reconstruct the entire distro build patch set and toolchain.
The Meson range `>=0.20.0,<0.21.0` is a dependency acceptance expression,
not a demonstrated compatibility range. 0.20.0, 0.20.1, another 0.20.2 build,
later 0.20 releases and 0.21 are **NOT TESTED**.
WLR_USE_UNSTABLE exposes public scene nodes, tree, buffers, output, output_state,
surfaces, renderer/buffer implementations, listeners, signals, color transform,
EGL and render-pass conventions. The historical observer measures identical
size/alignment for 17 selected structs and six offsets across installed,
pristine and private headers. Examples: scene_node 112/8, tree 128/8,
scene_buffer 472/8, output 624/8, surface 968/8; tree.children offset 112,
node.parent 8, buffer.buffer 112. It does **not** show equality between native
wlroots scene layouts and extended SceneFX structures, nor exhaustive layouts,
callbacks, enum tags, ownership or semantics.
SceneFX requires Wayland server >=1.24, protocols >=1.41, libdrm >=2.4.129,
xkbcommon >=1.8 and pixman >=0.43; compositor requirements differ in places.
Generated XDG and other Wayland protocol code and scanner inputs are part of
reproduction. The observed wlroots enables DRM, X11, libinput, Xwayland,
GLES2, Vulkan, GBM, udmabuf, session and color management. SceneFX selects its
FX/GLES renderer and observed lcms2 path. Build flags, generated config headers,
protocol versions and actual compiler dependencies must accompany source pins.
No additional renderer or protocol combination was tested here.
Candidate qualification matrix for **each** new shared wlroots build:
| Gate | Required evidence | This ticket |
| --- | --- | --- |
| 1 header/source | API, callbacks, enum and lifecycle diffs; exact distro patches | pinned pair only, subset retained |
| 2 symbols | definitions/imports, versions, all consumer/provider bindings | pinned pair only; visibility isolation untested |
| 3 struct layouts | sizes/alignments/offsets for every crossing type and callback ABI | historical 17 structs/six offsets only |
| 4 clean build | fixed SceneFX/Mosaic sources and patch order, toolchain/features | historical build PASS; no new compositor build |
| 5 loader | complete recursive graph, strong/weak relocations, no duplicate SceneFX | historical trusted graph PASS |
| 6 unit/model | actual production model and library contracts | historical mosaic-model 1/1 in three builds; new version NOT TESTED |
| 7 private stage | complete manifest, notices/strip and provenance | current candidate file shape PASS only |
| 8 separately authorized graphics | standard/Mosaic/control, popups, effects, saved buffers, damage, input, scale/rotation/migration/generation | NOT TESTED for static candidate in this ticket |
Do not pin global wlroots to prevent normal SwayFX upgrades. If Mosaic's pair
becomes unqualified, withhold its candidate release, rebuild/requalify or use
standard SwayFX. File independence is established; update independence is not.
## Candidate security ownership and reproducibility
Bundled SceneFX no longer receives fixes when the system scenefx package is
updated. The **Mosaic distributor/maintainer must explicitly accept ownership**
for monitoring and rebuilding embedded code, with a named reviewer and release
owner. Nobody is assigned by this ticket; an unowned policy is an open gate.
Upstream remains a source of fixes, not the security updater of this executable.
Proposed manual workflow, requiring adoption before distribution:
1. Record SceneFX commit/tag provenance, Git object identity and SHA-256 source
inventory; list exact private patches in order and their digests. Preserve
SwayFX/Mosaic/wlroots pins, actual system package/build identities, scanner,
protocols, toolchain, flags, generated config and link commands.
2. An assigned maintainer reviews upstream releases/commits and security
advisories affecting SceneFX and copied wlroots scene/renderer code; assess
relevance and exposure. A second reviewer checks the fix and private clip
interaction. Set a reporting channel and triage/release response expectations.
3. Review a candidate source revision and rebase private patches explicitly;
no silent cherry-pick, ABI substitution or automatic update. A fix may alter
renderer paths, layouts or interposition and require redesign.
4. Rebuild only in a controlled private environment with fixed dependency
sources/build metadata and no fallback downloads. Recheck symbol/layout/
loader/dependency gates, model tests, staging/notices and separately authorized
graphics. Two clean builds with path/timestamp controls are needed before
claiming bit reproducibility; not performed here.
5. Record artifact/source/notices hashes, review approvals and matrix results.
Publish only under a separate release authorization. Retain the previous
qualified artifact and full build manifest for rollback, including compatible
dependency identities. No updater or release package is created here.
The cost is recurring SceneFX plus copied-wlroots security triage, patch rebases,
shared-wlroots qualification, legal-notice inventory and graphics/release testing.
It is more than rebuilding one compositor after a source change. A reproducible
procedure is proposed; bit-identical artifacts and security response are NOT
ESTABLISHED. Source provenance is necessary but does not itself certify security.
Rollback must never restore system SceneFX/wlroots or overwrite `/usr/bin/sway`.
Replacing/removing only Mosaic preserves ordinary package ownership. An older
Mosaic artifact may fail with newer shared wlroots/renderer providers; it is
not automatically a working rollback. Retain/requalify compatible manifests,
or fall back to the separately maintained standard SwayFX session. Actual
install/update/uninstall/rollback remain NOT TESTED.
## Licenses, incorporated components and notice options
Pinned SwayFX, SceneFX and wlroots root licenses contain MIT copyright,
permission and warranty notices. SceneFX's root notice names Drew DeVault and
Jari Vetoniemi; wlroots additionally names its contributors. Preserve complete
texts, not merely `license=('MIT')`. The licenses and source notice candidates
are retained verbatim in private `license-notices.json`; no notice dump enters Git.
Compiled-source provenance, generated protocol comments, archive symbol input,
compile commands and linker plan are retained separately. The proof does not
claim every archive member is extracted into the final executable.
Important additional condition: generated protocol code carries its own
copyright/permission text. XDG includes Kristian Høgsberg, Rafael Antognolli,
Jasper St. Pierre, Intel, Samsung and Red Hat notices. Generated wlr-layer-shell
and output-power code use permission/warranty language that includes supporting
documentation and restrictions on advertising with copyright-holder names.
An umbrella MIT label alone does not cover this inventory. The existing asset
license and tinywl example notices appear in the broad source inventory but
are not thereby proven incorporated: wallpaper/examples are disabled. Shared
EGL, GBM, GLESv2, lcms2, Wayland, pixman/libdrm and wlroots are not newly copied
as static libraries; their package license metadata is contextual evidence,
not a declaration that all their source is embedded.
The disposable `.mosaic_notices` objcopy probe includes exact root license and
generated protocol texts. A byte-for-byte section extraction passed. This is
an oversized candidate inventory, not a completed incorporated-code notice
closure, public notice interface or approved compliance solution. The copied
binary was never executed or staged. Original and staged qualified binaries
are unchanged. Strip/signing/package transformations and accessible notice
delivery still need tests. No public/source API or visibility policy changes.
| Option | Feasibility/accessibility | One-file contract | Risk and required decision |
| --- | --- | --- | --- |
| N1 embedded section/metadata | add/extract byte identity PASS on disposable ELF; document extraction with binutils without compositor execution | structurally compatible | complete notice closure, end-user discoverability, strip/retention and legal sufficiency not established; user adoption/review required |
| N2 package metadata/source archive | normal `license` metadata identifies a license; it does not automatically deliver its full notices | structurally compatible installed manifest | raw binary transfer and package/source separation can lose notices; actual complete notice-delivery design and legal review required; metadata label alone insufficient |
| N3 separate installed license/notice | conventional readable file, no specialized ELF extraction | incompatible with current exact-one-file rule | requires explicit user packaging-contract decision plus complete notices/review; preferred practical distribution direction if rule can be changed |
Do not drop notice obligations to preserve one installed file. A credible N1
candidate exists technically; a sufficiently solid one-file compliance solution
is **NOT YET QUALIFIED**. Conditional recommendation: allow a distinct installed
notice file (N3) for clarity and discoverability if the user authorizes that
contract change. This ticket neither changes the contract nor asserts final
legal compliance. The protocol-specific supporting-documentation condition
needs assessment for any N1/N2 route.
Primary references inspected: [pinned SceneFX license](https://github.com/wlrfx/scenefx/blob/37ccd723bef49e6891156ffafce8f549f01446cc/LICENSE),
[Meson DESTDIR/no-rebuild](https://mesonbuild.com/Installing.html),
[GNU objcopy sections](https://sourceware.org/binutils/docs/binutils/objcopy.html)
and [Arch PKGBUILD license metadata](https://man.archlinux.org/man/PKGBUILD.5.en).
Local pinned source texts and retained generated comments govern the inventory.
This is a technical/legal-obligation dossier, not legal clearance.
## Separate upstream API dossier — not submitted
An API could express a **generic inherited scene-tree clip**, not Mosaic popup
ownership or UX. Known experimental signatures, retained only as candidates:
```c
void wlr_scene_tree_set_clip(struct wlr_scene_tree *tree,
const struct wlr_box *clip);
bool wlr_scene_tree_get_clip(struct wlr_scene_tree *tree,
struct wlr_box *clip);
```
No signature is frozen. In particular a void setter cannot report the private
sidecar's allocation failure. A maintained API may need a result/error enum or
another upstream-owned storage/lifecycle design. No such code, fields or ABI
change is implemented here. Public structures must not grow without maintainer
agreement on ABI/version policy and all cross-library consumers.
| Topic | Demonstrated private/source behavior | Proposed maintained contract / required tests |
| --- | --- | --- |
| coordinates | copied tree-local integer wlr_box; descendant traversal intersects ancestor clips | logical tree-local half-open rectangle, explicit conversion to output/buffer; checked addition/narrowing/overflow |
| ownership | sidecar owns copied box; tree pointer borrowed; destroy listener frees sidecar | caller may discard box after successful set; tree owns clip lifetime; main scene-thread use only, no independent worker authority |
| NULL | setter clears, absent clear is no-op; getter asserts non-NULL output | NULL rectangle removes own clip; invalid tree/out pointer must have documented error/assertion policy; get absent returns false and define untouched out value |
| empty/negative | zero dimensions accepted; negative dimensions asserted | zero area excludes descendant pixels/hits; negative sizes rejected explicitly; no silent signed overflow or allocation failure |
| update/idempotence | same box memcmp returns early; clear/update invokes scene_node_update | compare semantic coordinates rather than struct padding; same effective clip idempotent; allocation error preserves old state and reported status |
| moving/reparenting | traversal follows parent chain and local→global coordinates | own clip moves with tree; inherited effective clip recomputed on reparent; old/new output damage invalidated; cyclic/invalid reparent rejected by existing ownership rules |
| destruction | listener removes and frees sidecar | no references/listeners after tree destruction; recursive destruction, clear-before-destroy, failed allocation and destruction during callbacks tested |
| inheritance | intersections in private descendant render/hit traversal | getter distinguishes own clip from effective inherited clip; empty ancestor dominates; nested trees and negative origins tested |
| rendering/hit | sampled historical tiled/popup render/hit behavior only | same applied effective bounds for render and hit; other node types, occlusion and buffer-local coordinates must agree |
| damage | private node update and sampled damage; complete proof absent | invalidate exposed/vacated regions on set/clear/move/reparent/output changes; all-frame stale-pixel coverage and cache invalidation |
| effects | sampled shadows/blur; shared edge limitation unresolved | define whether clip limits final coverage versus blur source sampling, shadows/rounded corners/optimized cache; preserve ordinary effects semantics |
| saved buffers | sampled historical held/timeout paths, no complete GPU lifetime proof | consistent applied geometry for saved nodes; destroy/release/ACK/timeout and cross-output lifetime tests |
| rotation/scaling | separate sampled historical probes, not static-candidate graphics | output-transform and fractional rounding policy, damage expansion, conservative raster bounds with exact logical hit contract |
| performance | private linear global sidecar lookup | scalable lookup/storage, bounded per-frame work, no unbounded allocator churn; benchmark deep trees/many clips, cache mutation coherency |
| ABI/version | two private functions, unchanged measured public header layouts | maintained feature/version exposure, compile/link guards, cross-wlroots compatibility policy; exact paired-version qualification |
Mosaic must separately retain tiled-owner identity, root/nested popup hierarchy,
applied workspace/output viewport and owner visibility. The private empty clip
for a fully hidden owner is **Mosaic-owned policy evidence**, not a generic
SceneFX requirement to know workspaces or XDG grab semantics. Offscreen hide,
dismiss or reposition remains undecided UX. No universal click-through promise:
valid grabs still have their ordinary dismissal behavior, compared with stock.
SceneFX cannot alone assign a Sway transaction generation or popup owner.
Questions for a future authorized maintainer discussion: is inherited tree clip
an acceptable primitive? Which coordinate and empty/error policy? Can own/effective
queries remain separate? Where may storage live without incompatible public
layouts? Which consumers must retain interposed providers? What damage/effects/
saved-buffer semantics can be guaranteed? How should capabilities, ABI and
wlroots pairs be versioned? What tests/benchmarks belong upstream? Would an
error-reporting setter be preferable? No contact, issue, PR or submission occurs.
Required future tests include C API syntax/invalid arguments, box overflow and
allocation failures, idempotence, nested clips, reparent and destruction,
render/hit agreement, complete damage and effects/cache/saved-buffer coverage,
scaling/rotation/migration, shared-object crossings and performance. A successful
compile or signature proposal does not close those tests.
## Architectural decision matrix
| Dimension | A maintained upstream API | B private static SceneFX | C distinct private dynamic identity | D no SceneFX extension |
| --- | --- | --- | --- | --- |
| evidence | private primitive only; upstream acceptance unknown | pinned build/ELF/bindings and one stage PASS | private same-SONAME selection observed; distinct identity NOT TESTED | alternative clipping mechanism not demonstrated |
| maintenance | upstream primitive if accepted; Mosaic owner/viewport still ours | embedded source/security/rebase/version owner required | private library/security/rebase/version owner required | broader Mosaic rendering/input maintenance possible |
| system dependencies | supported maintained SceneFX/wlroots pair | shared wlroots/graphics, no shared SceneFX need | shared wlroots plus private SceneFX file | existing SceneFX/wlroots, unknown new needs |
| SwayFX coexistence | standard packages intact, normal upstream transition | standard binary/libraries intact; file shape PASS | extra private path must be approved | possible filesystem independence, architecture unproved |
| ABI/interposition | official contract still to negotiate; existing integration persists | 85 overlaps; 31 measured wlroots routes into executable | distinct SONAME separates lookup, not namespace/struct semantics | no new clip exports, existing unstable APIs still matter |
| upgrades | paired versions and release qualification | no automatic SceneFX fixes, wlroots upgrades unqualified | private fixes plus shared wlroots qualification | unknown forward-port cost |
| packaging | possible one binary, notices remain | one binary staged; notice/security contracts open | extra library and notices require contract change | unknown final shape |
| licenses/security | preserve notices; upstream plus Mosaic release owners | complete embedded notices and project security owner | notices plus private-library security owner | inventory remains; project owns alternate mechanism |
| rollback | old release only with compatible dependencies; stock fallback | old embedded code may be vulnerable/incompatible; stock fallback | must roll binary/private library together, shared graph still matters | unqualified alternate rollback |
| delivery complexity | external agreement, maintained release and integration | controlled source/archive/patch/provenance/notices builds | private identity/path/RUNPATH/transitive binding contract | largest unexplored implementation risk |
| residual risks | API acceptance and graphical gates | semantics, versions, notices, ownership and graphics | same plus double library/lookup risk | popup stacking/hits/effects/damage/saved buffers/generation |
| minimum next step | authorize discussion after local API/notice review | user contract decisions, then separately scoped version/notice tests | user new-file contract then bounded identity/binding prototype | architecture study; no renderer implementation authorized |
B is the **best demonstrated immediate experimental candidate**: no new owned
SceneFX library file, no absent system clip symbol dependency, explicit ELF
and staging evidence. A is the **conditional best long-term maintenance
orientation**, because a maintained generic primitive could avoid perpetual
private patches. This is an engineering recommendation, not evidence of
upstream commitment. C is not qualified by the existing same-SONAME build;
D is not established by output scissor or surface cropping alone.
Required user decisions: candidate architecture or continued investigation;
N1/N2/N3 notice route and any packaging-contract change; named security/release
owner and response policy; supported exact wlroots build qualification policy
without blocking standard upgrades; acceptable rollback/support boundary;
permission for any upstream contact; scope of separately authorized graphics,
version, reproducibility and delivery tests. No production choice is applied.
## Reproduction, validation and unclosed gates
Historical source/evidence manifests remain unchanged and fully verified with
the explicit README snapshot mapping from phase A. This campaign reuses ELF
and actual bindings, without rerunning historical builds or loader tracing.
The new audit fixture preserves its own command/return-code/raw records. It
allows repeat static analyses before sealing, but has an exclusive staging
attempt marker and refuses sealed results.
For an independent future reconstruction, archive exact pinned Mosaic and
SceneFX objects; apply preserved patches in order:
```text
SceneFX: step20c-valid-grab/scenefx-popup-plus-base.patch
step20c-temporal-damage/scenefx-timing.patch
step20c-optimized-blur-cache/scenefx-cache-trace.patch
Mosaic: 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
```
Use the historical fixture's exact Meson static-chain flags, setting Mosaic
prefix `/usr`/bindir `bin` initially, with the static SceneFX uninstalled
pkg-config directory only for that build. Keep wlroots shared and archive its
pinned source for audits. Verify each patch/digest and qualified installed
header/build inputs. No stock/shared chain is needed. A future reproduction
requires its own authorization and provenance rather than expecting the old
laboratory to exist or silently accepting a different library build. Do not
run historical prepare.py just to analyze symbols: it builds unnecessary chains.
The qualified-source regeneration path is independent of historical binaries;
bit identity and reproducibility across machines remain NOT TESTED.
Current result files include complete staging/ELF/export/ownership/interposition,
transitive dependencies, versions, security, licenses/notice options, upstream
API, architectural matrix, unresolved gates and summary. Private source/evidence
manifests seal the final inventories, excluding themselves and this report's
integrity appendix to avoid cyclic digests. No generated file enters Git.
NOT TESTED: static-candidate graphics/GPU/readback, another wlroots version or
build, distinct-SONAME/private-path delivery, all-frame damage/effects/cache/
saved-buffer lifetimes, applied-generation consistency, upgrade/rollback,
strip/sign/package notice retention, complete legal compliance and bit
reproducibility. Historical graphical subset results are not transferred into
a new static distribution qualification. STEP20C remains NEXT.
Final ELF count: 88 dynamic definitions, comprising the 85 common SceneFX/
wlroots exports plus libc-related `optarg`, `optind`, `stderr` data records.
These data records are not additional SceneFX functions. Seven generated
protocol notice records are preserved from compiled inputs.
Final checks PASS: Python AST, existing shell/recipe syntax, local Markdown
links, source-only fixture/whitespace, complete stage/hash, retained ELF/bindings,
structured policy dossiers, protected sources/recipe/patch/UX/AGENTS, unchanged
canonical packaging contract, installed sway/SceneFX/wlroots hashes and no LAB
processes/sockets. Meson model results are historical, not reexecuted.
The parent performed one closeout dossier/fixture review; this is not an
independent subagent review. Failures and corrections are explicitly retained.
## Final inventory integrity and local state
Source manifest: **1892 entries**, SHA-256
`5ad64eaf0aec2290689c46b7754eb4563980396ba2a06e852d1c8e903086f138`.
Evidence manifest: **143 entries**, SHA-256
`85d034f4075436bbdda302afdfa94955365aa51adcfb7e8b04ed6bb28377f649`.
Both new manifests are sealed and reverified; original 1,463/204 historical
manifests remain unchanged. The report itself and complete diff are excluded
from those inventories to avoid cyclic digests. Phase A HEAD remains
`bb3a112550f45667e501d05d5a08aa8f7d0c02c6` on local, origin and github.
Phase B remains unstaged/uncommitted/unpublished: report/fixture plus
Architecture, Known Limitations, Packaging evidence appendix, Roadmap, README
and Changelog only. No protected production/system/session change, installation,
package creation/publication, upstream submission or STEP20D work occurred.
Recommended next smallest step: user review of notice route (N3 recommended
conditionally), security/release ownership and exact wlroots qualification
policy, then a separately scoped test authorization. No production integration
is authorized by this dossier.
`SWAYFX_MOSAIC_STATIC_DISTRIBUTION_CONTRACT_PROBE=PARTIALLY_FEASIBLE`
`STATIC_DISTRIBUTION_REQUIRES_CONTRACT_DECISION`
## Subsequent user decisions — 2026-10-13
After this campaign, the user approved private static SceneFX for the immediate
STEP20C candidate, separate complete license notices for a future candidate,
project responsibility for embedded SceneFX maintenance, and strict qualification
of each SceneFX/wlroots pair. These decisions authorize documentation and private
prototypes only; the current package, production patch and source remain unchanged.
No individual owner or response SLA is assigned. The historical verdict and
architectural conclusion above are unchanged. A separate runtime/package gate
must build a real `/usr` binary and qualify loaded libraries and sampled clipping.
The evidence-freeze audit verified all 1,892 source and 143 evidence entries
before changing publication wording. Original manifests were neither regenerated
nor overwritten. For later verification, map the seven updated documentation
entries through `/home/fy59/swayfx-mosaic-static-contract-freeze-v1/snapshot-map.json`;
all other entries use their original paths. Snapshot bytes are retained outside
Git, with original hashes verified by `verify-freeze.py`.

View file

@ -0,0 +1,115 @@
# Static SceneFX distribution contract fixture
**EXPERIMENTAL / PRESERVED HISTORICAL EVIDENCE / not production or a package recipe.**
Read the [contract dossier](../../../docs/design/STEP20C_SCENEFX_STATIC_DISTRIBUTION_CONTRACT.md).
All builds, binaries, notices, logs and JSON inventories stay in an external LAB.
No compositor entry point, desktop IPC, installation, package publication,
system library modification or symbol visibility experiment is allowed.
## Protocol before execution
Verify immutable historical manifests, using the exact historical README
snapshot identified by the ABI freeze report. Reuse trusted qualified static
binary after SHA-256 and Meson-option checks. The old build prefix is
`/usr/local`: never change its sealed sources or build metadata. Prepare a
new private Meson installation plan from byte-verified copies of the same
source tree with `--prefix=/usr --bindir=bin` and identical package options.
Reuse the original qualified executable at its expected build-relative path
with identical hash. Run exactly one `meson install --no-rebuild --destdir
LAB/stage-static`. No compilation, linking, stripping or retesting of old
compositor builds is needed. Record the metadata adaptation separately.
If metadata setup fails, retain its failure; do not improvise manual staging
or rerun the historical chain. A future independent reproduction may build
only the static chain from pinned sources and private patches listed in the
[ABI fixture](../step20c-scenefx-abi-package/README.md), with `/usr` prefix
from the beginning. The historical laboratory is not a required future input.
Audit readelf/nm outputs, retained actual loader bindings and source call
sites; never infer provider selection from a matching symbol name.
Notice embedding, if exercised, uses a disposable copy and objcopy only;
byte-for-byte extraction establishes technical feasibility, not legal approval.
Do not stage that altered copy or add a notice policy to production.
Budget: one static DESTDIR stage, zero graphical runs, zero shared builds,
at most one necessary static reconstruction (not required if reuse succeeds).
## Completed local campaign
LAB: `/home/fy59/swayfx-mosaic-static-contract-7_vlvpjw`.
Historical README mapping:
`/home/fy59/swayfx-mosaic-abi-freeze-i6x1osil/historical-fixture-README.md`.
PARTIALLY_FEASIBLE; STATIC_DISTRIBUTION_REQUIRES_CONTRACT_DECISION;
production strategy NONE — USER DECISION REQUIRED. Phase B stays local.
One stage passed with exactly one regular `/usr/bin/swayfx-mosaic`, no symlink,
library or notice. SHA-256
`ccd47ce5b74c132e19bcd46d022a80e90e8a944acc3db521a89427ee7913c614`.
The normal Meson plan uses `/usr`; the reused executable retains its historical
build strings/options. Runtime path/graphics qualification is not claimed.
Initial Python preparation failed on an absent optional `subprojects` directory
before setup/staging. The source-copy assumption was corrected and preparation
resumed; one setup and one install were executed, with zero compositor rebuilds.
```sh
python tooling/probes/step20c-scenefx-static-contract/audit.py LAB \
--historical HISTORICAL_LAB --historical-readme HISTORICAL_README --stage
```
This historical-reuse mode verifies exact sealed manifest identities and trusted
binaries, records commands/return codes, inventories symbols/provider consumers,
dependencies/source notice candidates, and stages once. `--stage` must never
be run again after `stage-install-started.json` exists. Omitting `--stage`
allows repeated static analysis before sealing only. The sole notice probe
modifies a disposable copy with objcopy, not production or the staged executable.
After sealing, only `--verify` is permitted; it rechecks every new inventory
entry without writing. Historical manifests use their README snapshot mapping.
## Independent reproduction without the old laboratory
Use local Git object stores with the four exact pins in the report. Materialize
new SceneFX and Mosaic sources with `git archive` at their respective commits;
archive pinned wlroots for comparison. Apply the eight private patches in the
report's ordered list with `git apply --check` before each application. Use the
same installed wlroots/header/build identities, compiler/scanner/protocol and
renderer features; otherwise mark the new matrix unqualified, not equivalent.
The report is reproducible from these source inputs, not dependent on retaining
the old executable or `/home/fy59` paths. Future execution needs its own budget
and authorization; these commands were **not run in this ticket**:
```sh
meson setup "$TASKLAB/scene-build" "$TASKLAB/scene-source" \
-Ddefault_library=static -Dexamples=false -Dwerror=false
meson compile -C "$TASKLAB/scene-build" -j 8
PKG_CONFIG_PATH="$TASKLAB/scene-build/meson-uninstalled" \
meson setup "$TASKLAB/mosaic-build" "$TASKLAB/mosaic-source" \
--prefix=/usr --bindir=bin -Dwerror=false -Dsd-bus-provider=libsystemd \
-Dmosaic-package=true -Dswaybar=false -Dswaynag=false \
-Dman-pages=disabled -Ddefault-wallpaper=false
PKG_CONFIG_PATH="$TASKLAB/scene-build/meson-uninstalled" \
meson compile -C "$TASKLAB/mosaic-build" -j 8
meson test -C "$TASKLAB/mosaic-build" mosaic-model --print-errorlogs
meson install -C "$TASKLAB/mosaic-build" --no-rebuild \
--destdir "$TASKLAB/stage-static"
readelf -hW -dW -Ws -rW -VW "$TASKLAB/mosaic-build/sway/swayfx-mosaic"
nm --format=posix "$TASKLAB/mosaic-build/sway/swayfx-mosaic"
```
On trusted binaries only, retain loader `--list` and relocation/binding
inspection in diagnostic mode (no compositor entry point), as in the preserved
ABI fixture. Inspect complete stage entries with lstat, not just regular-file
counts. Capture actual consumer/provider pairs; preserve package owners and
complete root/generated-protocol notices. The existing historical-reuse audit
intentionally refuses a different sealed identity; a new reproduction needs
its own validated manifests and may use these standard diagnostics directly.
Never execute the historical prepare.py full stock/shared chain just to obtain
static symbol evidence. No bit reproducibility or legal clearance is implied.
## Publication freeze
The historical verdict is unchanged. Subsequent decisions on private static
linking, separate notices, project maintenance and exact-pair qualification
authorize a distinct private runtime gate, not production integration.
Verify the sealed manifests with `verify-freeze.py LAB --snapshot-map MAP`
after publication wording changes. MAP is the external pre-freeze snapshot
mapping recorded in the dossier; it must contain only entries whose exact
original bytes still match the sealed manifest. No manifests are rewritten.

View file

@ -0,0 +1,301 @@
#!/usr/bin/env python3
"""Bounded non-graphical audit and single Meson DESTDIR stage of trusted artifacts.
WHY: source, ELF visibility and actual binding evidence answer different questions.
CONTRACT: historical LAB is read-only; only new external LAB receives results.
INVARIANT: never run the compositor; never rebuild, install live, or change symbols.
"""
import argparse
import hashlib
import json
import os
from pathlib import Path
import re
import shutil
import signal
import stat
import subprocess
p = argparse.ArgumentParser(description=__doc__)
p.add_argument('lab', type=Path)
p.add_argument('--historical', type=Path, required=True)
p.add_argument('--historical-readme', type=Path, required=True)
p.add_argument('--stage', action='store_true')
p.add_argument('--verify', action='store_true')
a = p.parse_args()
lab, old = a.lab.resolve(), a.historical.resolve()
repo = Path(__file__).resolve().parents[3]
assert not old.is_relative_to(lab) and not lab.is_relative_to(old)
assert lab.is_dir() and old.is_dir() and lab != old
assert lab.is_relative_to(Path.home()) and not lab.is_relative_to(repo)
if a.verify:
assert not a.stage
for kind in ['source', 'evidence']:
manifest = lab/'results'/(kind+'-sha256.txt')
lines = manifest.read_text().splitlines()
for line in lines:
digest, filename = line.split(' ', 1)
assert hashlib.sha256(Path(filename).read_bytes()).hexdigest() == digest, filename
print(kind, len(lines), hashlib.sha256(manifest.read_bytes()).hexdigest(), 'PASS')
raise SystemExit(0)
assert not (lab/'results/evidence-sha256.txt').exists(), 'sealed: verify only'
raw, results = lab/'raw', lab/'results'
raw.mkdir(exist_ok=True); 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')}
os.environ["PYTHONDONTWRITEBYTECODE"] = "1"
env.update(LC_ALL='C', PYTHONDONTWRITEBYTECODE='1', MESON_ROOT_CMD='/bin/false')
commands = json.loads((results/'commands.json').read_text()) if (results/'commands.json').exists() else []
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 read(name):
return json.loads((old/'results'/(name+'.json')).read_text())
def run(label, args, extra=None):
# Only explicitly requested diagnostics, Meson setup/install and objcopy.
# Command output is file-backed, with bounded execution and owned group.
label = f'{len(commands):03}-'+label
entry = {'args': list(map(str, args)), 'env_override': extra or {}, 'label': label}
with (raw/(label+'.out')).open('wb') as out, (raw/(label+'.err')).open('wb') as err:
proc = subprocess.Popen(entry['args'], env={**env, **(extra or {})},
stdout=out, stderr=err, start_new_session=True)
try:
proc.wait(timeout=120)
except subprocess.TimeoutExpired:
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
commands.append(entry); save('commands', commands)
assert proc.returncode == 0, (label, entry)
assert (raw/(label+'.out')).stat().st_size < 32*1024*1024
output = (raw/(label+'.out')).read_text()
(raw/(label.split('-', 1)[1]+'.out')).write_text(output)
return output
integrity = {}
for kind, count, digest in [
('source', 1463, 'a27fa33d57d3423e09f02430f04cfed643762d876099b7f88489229115c4a1e5'),
('evidence', 204, '86f7654c8ad962053644ce2c4c13eb9f026d6618b171e0cc3a8fc3034eb380d5')]:
manifest = old/'results'/(kind+'-sha256.txt')
assert sha(manifest) == digest
lines = manifest.read_text().splitlines(); assert len(lines) == count
for line in lines:
expected, filename = line.split(' ', 1)
path = Path(filename)
if path == repo/'tooling/probes/step20c-scenefx-abi-package/README.md':
path = a.historical_readme
assert sha(path) == expected, filename
integrity[kind] = {'count': count, 'sha256': digest, 'verified': True}
save('historical-integrity', integrity)
elf = read('static-binary-elf'); binary = Path(elf['path'])
assert sha(binary) == elf['sha256'] and binary.stat().st_size < 128*1024*1024
for name in ['static-binary-elf', 'experimental-binary-elf', 'system-wlroots-elf',
'pristine-scenefx-symbols']:
item = read(name); assert sha(Path(item['path'])) == item['sha256'], name
for tag, args in [('header', ['readelf', '-hW']), ('dynamic', ['readelf', '-dW']),
('symbols', ['readelf', '-Ws']), ('relocations', ['readelf', '-rW']),
('versions', ['readelf', '-VW']), ('nm', ['nm', '--format=posix'])]:
run('static-'+tag, [*args, binary])
# Copy retained diagnostic records, do not reexecute the historical ABI campaign.
for name in ['static-bindings', 'static-relocations', 'loader-static-default']:
shutil.copyfile(old/'raw'/(name+'.json'), raw/('historical-'+name+'.json'))
load = read('loader-resolution')
assert load['static-relocations']['returncode'] == 0
assert 'undefined symbol:' not in load['static-relocations']['stdout']
assert 'libscenefx' not in load['static-default']['stdout']
save('static-elf', {**elf, 'relocations': 'PASS historical trusted loader diagnostics',
'graphics': 'NOT TESTED', 'loader_records_reused': True})
# Full ELF tables distinguish local/global/default visibility from dynsym membership.
def symbols(path):
out = {}
for line in path.read_text().splitlines():
f = line.split()
if len(f) >= 8 and f[0].rstrip(':').isdigit():
name = f[7].split('@')[0]
rec = dict(type=f[3], bind=f[4], visibility=f[5], index=f[6],
version=f[7].split('@', 1)[1] if '@' in f[7] else None)
out.setdefault(name, []).append(rec)
return out
sym = symbols(raw/'static-symbols.out')
scene = set(read('pristine-scenefx-symbols')['defined'])
wl = set(read('system-wlroots-elf')['defined'])
shared = read('experimental-binary-elf')
clip = {'wlr_scene_tree_set_clip', 'wlr_scene_tree_get_clip'}
local_names = {n for n, records in sym.items() if any(x['bind']=='LOCAL' and x['index']!='UND' for x in records)}
scan_names = set(sym) | scene | clip
source_refs = {}
for path in sorted((old/'mosaic-private/sway').rglob('*.c')):
for lineno, line in enumerate(path.read_text().splitlines(), 1):
for name in set(re.findall(r'\b[A-Za-z_][A-Za-z0-9_]*\b', line)) & scan_names:
source_refs.setdefault(name, []).append({'file': str(path.relative_to(old)),
'line': lineno, 'text': line.strip()})
definition_refs = {}
actual_definitions = {}
for tree in ['scene-private', 'wlroots-source', 'mosaic-private']:
for path in sorted((old/tree).rglob('*.c')):
content = path.read_text()
for match in re.finditer(r'(?m)^[ \t]*(?:[A-Za-z_]\w*[\s*]+)+([A-Za-z_]\w*)\s*\([^;{}]*\)\s*\{', content):
name = match[1]
if name in scan_names:
actual_definitions.setdefault(name, []).append({'file': str(path.relative_to(old)), 'line': content[:match.start()].count('\n')+1})
for lineno, line in enumerate(path.read_text().splitlines(), 1):
for name in set(re.findall(r'\b[A-Za-z_][A-Za-z0-9_]*\b', line)) & scan_names:
definition_refs.setdefault(name, []).append({'file': str(path.relative_to(old)),
'line': lineno, 'text': line.strip()})
bindings = {}
for mode in ['stock-bindings', 'private-bindings', 'static-bindings']:
data = read('loader-resolution')[mode]
for line in (data['stdout']+'\n'+data['stderr']).splitlines():
match = re.search(r"binding file (.*?) \[0\] to (.*?) \[0\].*symbol `([^']+)'", line)
if not match:
continue
rec = {'consumer': match[1], 'provider': match[2]}
rows = bindings.setdefault(match[3], {}).setdefault(mode, [])
if rec not in rows:
rows.append(rec)
public = sorted((set(shared['undefined']) & scene) - clip)
common = sorted(scene & wl)
assert len(common) == 85
ownership = []
local_names = {n for n, records in sym.items() if any(x['bind']=='LOCAL' and x['index']!='UND' for x in records)}
for name in sorted(set(public) | set(common) | clip | set(elf['undefined']) | local_names):
actual = bindings.get(name, {})
ownership.append({'name': name, 'groups': [g for g, yes in [
('A', name in public), ('B', name in clip), ('C', name in common),
('D', name in elf['undefined']), ('E', name in local_names)] if yes],
'definitions': actual_definitions.get(name, []),
'definition_reference_source_candidates': definition_refs.get(name, []),
'compositor_source_references': source_refs.get(name, []),
'elf': sym.get(name, []), 'dynamic_export': name in elf['defined'],
'expected_owner': 'SceneFX private clip' if name in clip else 'SceneFX scene/renderer' if name in scene else 'shared provider from retained loader graph' if name in elf['undefined'] else 'executable internal implementation',
'observed_providers': actual,
'selection': 'direct executable definition (non-dynsym)' if name in clip else ('MEASURED retained bindings (includes callers other than wlroots; see scene counts)' if actual else 'NOT OBSERVED at runtime; ELF definition/reference only'),
'abi_convention': 'unversioned WLR_USE_UNSTABLE C ABI and shared scene/renderer types' if name in scene or name in clip or name.startswith('wlr_') else 'ELF table version recorded; no new ABI assertion',
'classification': 'private prototype only; ABI not maintained' if name in clip else 'provider records retained; scene helper necessity inferred from source, not isolation experiment' if actual and name in common else 'current definition/reference characterized; full semantic ABI NOT ESTABLISHED',
'upgrade_risk': 'any new wlroots version/build NOT QUALIFIED'})
save('scene-symbol-ownership', {'group_A_scope': 'link-required public SceneFX functions across compiled SwayFX/Mosaic; per-source references distinguish Mosaic-specific paths', 'groups': {'A': public, 'B': sorted(clip), 'C': common,
'D': elf['undefined'], 'E': sorted(n for n, records in sym.items() if any(x['bind']=='LOCAL' and x['index']!='UND' for x in records))}, 'symbols': ownership})
save('exported-symbols', {'dynamic_definitions': elf['defined'], 'dynamic_undefined': elf['undefined'], 'full_table': sym})
save('interposition-matrix', {'observed': bindings, 'scene_wlroots_counts': read('scene-symbol-bindings'),
'overlap': common, 'visibility_changes': 'NONE', 'new_version': 'NOT TESTED',
'warning': 'matching names are not defects; hiding them may break wlroots helper delegation'})
transitive = read('transitive-dependencies')
stock = read('stock-binary-elf')
save('transitive-dependencies', {'static_DT_NEEDED': elf['DT_NEEDED'],
'stock_DT_NEEDED': stock['DT_NEEDED'], 'new_direct': sorted(set(elf['DT_NEEDED'])-set(stock['DT_NEEDED'])),
'removed_direct': sorted(set(stock['DT_NEEDED'])-set(elf['DT_NEEDED'])),
'historical_recursive_package_owners': transitive,
'note': 'historical union includes SceneFX for shared cases; static graph comes from historical loader-static-default.json',
'proposal_only': 'keep existing runtime dependencies except scenefx conditionally; add libglvnd, mesa, lcms2; promote libdrm/pixman to runtime; review glib2, glibc, xcb-util-wm direct owners; no PKGBUILD change'})
# Exact root licenses plus source-level notice candidates remain external.
notices = []
for tree in ['stock', 'scene-private', 'wlroots-source']:
for path in sorted((old/tree).rglob('*')):
if not path.is_file() or 'subprojects' in path.relative_to(old/tree).parts:
continue
if path.name.upper().startswith(('LICENSE', 'COPYING', 'NOTICE')):
notices.append({'path': str(path.relative_to(old)), 'sha256': sha(path), 'text': path.read_text(errors='replace'), 'kind': 'license_file'})
elif path.suffix in ['.c', '.h', '.frag', '.vert', '.xml']:
text = path.read_text(errors='replace')
if re.search(r'copyright|SPDX-License|permission is hereby|redistribution and use', text, re.I):
notices.append({'path': str(path.relative_to(old)), 'sha256': sha(path), 'text': text, 'kind': 'source_notice_candidate; inclusion needs link-object provenance review'})
# Link-input provenance also includes generated protocol code with its own notices.
# Root MIT files alone are insufficient for a complete distribution notice bundle.
incorporated = []
for build_name in ['static-build', 'scene-static-build']:
build_path = old/build_name
compile_commands = json.loads((build_path/'compile_commands.json').read_text())
shutil.copyfile(build_path/'compile_commands.json', raw/(build_name+'-compile-commands.json'))
shutil.copyfile(build_path/'build.ninja', raw/(build_name+'-build.ninja'))
for command in compile_commands:
path = (build_path/command['file']).resolve()
if not path.is_file():
continue
incorporated.append({'path': str(path), 'sha256': sha(path),
'object': command.get('output'), 'build': build_name,
'note': 'compile input, final archive extraction not proven by this alone'})
if path.is_relative_to(build_path) and path.suffix == '.c':
text = path.read_text()
comments = re.findall(r'/\*.*?\*/', text, re.S)
payload = '\n'.join(c for c in comments if re.search(r'copyright|permission|redistribution', c, re.I))
if payload:
notices.append({'path': str(path.relative_to(old)), 'sha256': sha(path),
'text': payload, 'kind': 'generated_protocol_notice'})
save('incorporated-source-inventory', incorporated)
run('archive-symbols', ['nm', '-A', '--defined-only', old/'scene-static-build/libscenefx-0.5.a'])
save('license-notices', {'root_licenses': read('licenses')['source_licenses'],
'source_notice_candidates': notices, 'system_packages': read('licenses'),
'incorporation': 'only SceneFX archive embedded; wlroots shared; SwayFX/common/protocol code included; no static system dependency claimed',
'compliance': 'NOT APPROVED; exact source notices retained, final object/notice closure still required'})
# Probe N1 solely on a disposable executable; never staged or executed.
notice_file = lab/'notice-candidate.txt'
notice_file.write_text('\n\n'.join(x['path']+'\n'+x['text'] for x in notices if x['kind'] in ['license_file', 'generated_protocol_notice']))
notice_bin = lab/'notice-copy'
run('notice-add', ['objcopy', '--add-section', '.mosaic_notices='+str(notice_file),
'--set-section-flags', '.mosaic_notices=readonly,contents', binary, notice_bin])
extracted = lab/'notice-extracted.txt'
run('notice-extract', ['objcopy', '--dump-section', '.mosaic_notices='+str(extracted), notice_bin, lab/'notice-extract-copy'])
assert extracted.read_bytes() == notice_file.read_bytes()
save('notice-distribution-options', {'N1': {'mechanics': 'PASS objcopy/extraction roundtrip on disposable copy', 'sha256': sha(extracted), 'staged': False, 'strip_retention': 'NOT TESTED', 'legal': 'NOT APPROVED', 'one_file': True, 'remaining': 'complete notice inventory, accessible extraction without executing compositor, retention through strip/sign/package, legal review'},
'N2': {'mechanics': 'license metadata identifies license; does not contain complete notices automatically', 'one_file': True, 'risk': 'package/source separation or raw executable transfer loses notices', 'legal': 'NOT APPROVED'},
'N3': {'mechanics': 'conventional installed notice file', 'one_file': False, 'decision': 'USER packaging contract change required', 'legal': 'complete inventory/review still required'}})
if a.stage:
# One-shot marker persists even on failure. Historical build remains untouched.
marker = lab/'stage-started.json'
assert not (lab/'stage-install-started.json').exists(), 'staging attempt already consumed'
if not marker.exists():
with marker.open('x') as out:
json.dump({'budget': 1, 'binary': str(binary), 'sha256': sha(binary)}, out)
source, build = lab/'stage-source', lab/'stage-plan'
if not source.exists():
shutil.copytree(old/'mosaic-private', source)
# Git archives need not contain a subprojects directory; never assume one.
original = old/'mosaic-private'
for path in original.rglob('*'):
if path.is_file() and 'subprojects' not in path.relative_to(original).parts:
assert sha(path) == sha(source/path.relative_to(original))
flags = ['--prefix=/usr', '--bindir=bin', '-Dwerror=false', '-Dsd-bus-provider=libsystemd',
'-Dmosaic-package=true', '-Dswaybar=false', '-Dswaynag=false', '-Dman-pages=disabled', '-Ddefault-wallpaper=false']
run('stage-plan-setup', ['meson', 'setup', build, source, *flags],
{'PKG_CONFIG_PATH': str(old/'scene-static-build/meson-uninstalled')})
plan = json.loads((build/'meson-info/intro-installed.json').read_text())
assert plan == {str(build/'sway/swayfx-mosaic'): '/usr/bin/swayfx-mosaic'}, plan
target = build/'sway/swayfx-mosaic'; shutil.copy2(binary, target)
assert sha(target) == elf['sha256']
with (lab/'stage-install-started.json').open('x') as out:
json.dump({'budget': 1}, out)
run('stage-static', ['meson', 'install', '-C', build, '--destdir', lab/'stage-static', '--no-rebuild'])
entries = []
for path in sorted((lab/'stage-static').rglob('*')):
s = path.lstat(); kind = 'symlink' if stat.S_ISLNK(s.st_mode) else 'directory' if stat.S_ISDIR(s.st_mode) else 'regular' if stat.S_ISREG(s.st_mode) else 'other'
entries.append({'path': '/'+str(path.relative_to(lab/'stage-static')), 'kind': kind,
'mode': oct(stat.S_IMODE(s.st_mode)), 'uid': s.st_uid, 'gid': s.st_gid,
'bytes': s.st_size, 'sha256': sha(path) if kind=='regular' else None,
'target': os.readlink(path) if kind=='symlink' else None})
files = [x for x in entries if x['kind']!='directory']
passed = len(files)==1 and files[0]['path']=='/usr/bin/swayfx-mosaic' and files[0]['kind']=='regular' and files[0]['sha256']==elf['sha256']
save('static-stage-manifest', {'status': 'STATIC_STAGE_FILE_SHAPE_PASS' if passed else 'STATIC_STAGE_FILE_SHAPE_FAIL',
'entries': entries, 'complete_non_directory_manifest': files, 'plan': plan,
'build_options': json.loads((build/'meson-info/intro-buildoptions.json').read_text()),
'metadata_adaptation_only': True, 'compositor_compiles': 0, 'intended_package_owner': 'swayfx-mosaic',
'intended_install_uid_gid': 'root:root under future packaging; actual staging remains user-owned',
'notices_present': False, 'libraries_installed': False})
assert passed
print('PASS static analysis, retained binding verification and notice mechanics; stage executed' if a.stage else 'PASS static analysis only; no new staging')

View file

@ -0,0 +1,33 @@
#!/usr/bin/env python3
"""Verify immutable historical manifests after publication wording changes.
WHY: documentation snapshots preserve evidence without rewriting sealed hashes.
CONTRACT: explicit mappings replace only files recorded in the source manifest.
INVARIANT: verification is read-only; every original digest must still match.
"""
import argparse
import hashlib
import json
from pathlib import Path
p = argparse.ArgumentParser(description=__doc__)
p.add_argument('lab', type=Path)
p.add_argument('--snapshot-map', type=Path, required=True)
a = p.parse_args()
mapping = json.loads(a.snapshot_map.read_text())
seen = set()
for kind, count, expected in [
('source', 1892, '5ad64eaf0aec2290689c46b7754eb4563980396ba2a06e852d1c8e903086f138'),
('evidence', 143, '85d034f4075436bbdda302afdfa94955365aa51adcfb7e8b04ed6bb28377f649')]:
manifest = a.lab/'results'/(kind+'-sha256.txt')
assert hashlib.sha256(manifest.read_bytes()).hexdigest() == expected
lines = manifest.read_text().splitlines()
assert len(lines) == count
for line in lines:
digest, name = line.split(' ', 1)
if kind == 'source' and name in mapping:
seen.add(name)
name = mapping[name]
assert hashlib.sha256(Path(name).read_bytes()).hexdigest() == digest, name
print(kind, count, expected, 'PASS')
assert seen == set(mapping), 'mapping must contain only sealed source entries'