diff --git a/CHANGELOG.md b/CHANGELOG.md index c733852..30a2ef2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 26772fc..8756e12 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -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. diff --git a/docs/KNOWN_LIMITATIONS.md b/docs/KNOWN_LIMITATIONS.md index 4d9e7c3..8e39521 100644 --- a/docs/KNOWN_LIMITATIONS.md +++ b/docs/KNOWN_LIMITATIONS.md @@ -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. diff --git a/docs/PACKAGING.md b/docs/PACKAGING.md index aa7b451..5ce821e 100644 --- a/docs/PACKAGING.md +++ b/docs/PACKAGING.md @@ -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. diff --git a/docs/README.md b/docs/README.md index bae7b3b..bbffb5a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 7f40ee6..8bf5ff7 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -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. diff --git a/docs/design/STEP20C_SCENEFX_STATIC_DISTRIBUTION_CONTRACT.md b/docs/design/STEP20C_SCENEFX_STATIC_DISTRIBUTION_CONTRACT.md new file mode 100644 index 0000000..d8b67ad --- /dev/null +++ b/docs/design/STEP20C_SCENEFX_STATIC_DISTRIBUTION_CONTRACT.md @@ -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`. diff --git a/tooling/probes/step20c-scenefx-static-contract/README.md b/tooling/probes/step20c-scenefx-static-contract/README.md new file mode 100644 index 0000000..ab6051d --- /dev/null +++ b/tooling/probes/step20c-scenefx-static-contract/README.md @@ -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. diff --git a/tooling/probes/step20c-scenefx-static-contract/audit.py b/tooling/probes/step20c-scenefx-static-contract/audit.py new file mode 100644 index 0000000..69e3ec6 --- /dev/null +++ b/tooling/probes/step20c-scenefx-static-contract/audit.py @@ -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') diff --git a/tooling/probes/step20c-scenefx-static-contract/verify-freeze.py b/tooling/probes/step20c-scenefx-static-contract/verify-freeze.py new file mode 100644 index 0000000..9071e08 --- /dev/null +++ b/tooling/probes/step20c-scenefx-static-contract/verify-freeze.py @@ -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'