From fb0293cd855a2ec917c14df3ecf3504c064499fc Mon Sep 17 00:00:00 2001 From: fy59 Date: Tue, 13 Oct 2026 07:47:38 +0200 Subject: [PATCH] docs(mosaic): preserve static focus input fixture failure evidence --- CHANGELOG.md | 2 + docs/ARCHITECTURE.md | 11 + docs/KNOWN_LIMITATIONS.md | 11 + docs/README.md | 11 + docs/ROADMAP.md | 11 + ...ATIC_RUNTIME_FOCUS_INPUT_STANDARD_PROBE.md | 320 ++++++++++++++++++ .../step20c-static-runtime-followup/README.md | 76 +++++ .../collect.py | 62 ++++ .../step20c-static-runtime-followup/pixels.py | 31 ++ .../prepare.py | 46 +++ .../step20c-static-runtime-followup/run.py | 249 ++++++++++++++ .../support.py | 84 +++++ .../test-budget.py | 25 ++ .../test-offline.py | 18 + 14 files changed, 957 insertions(+) create mode 100644 docs/design/STEP20C_STATIC_RUNTIME_FOCUS_INPUT_STANDARD_PROBE.md create mode 100644 tooling/probes/step20c-static-runtime-followup/README.md create mode 100644 tooling/probes/step20c-static-runtime-followup/collect.py create mode 100644 tooling/probes/step20c-static-runtime-followup/pixels.py create mode 100644 tooling/probes/step20c-static-runtime-followup/prepare.py create mode 100644 tooling/probes/step20c-static-runtime-followup/run.py create mode 100644 tooling/probes/step20c-static-runtime-followup/support.py create mode 100644 tooling/probes/step20c-static-runtime-followup/test-budget.py create mode 100644 tooling/probes/step20c-static-runtime-followup/test-offline.py diff --git a/CHANGELOG.md b/CHANGELOG.md index dfb81a9..0998064 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### STEP20C experimental evidence +- Preserved static focus/input/standard fixture failure: exact static binary and loaded pair verified; one run stopped before captures/input on an incorrect border-width fixture assertion. INCONCLUSIVE / UNDECIDED; camera and clip traces retained, RGB/clicks/splith NOT TESTED, no retry or production change. + - Approved private static candidate decisions (2026-10-13): separate notices, project maintenance and exact-pair qualification. Current package unchanged; private real `/usr` static build/model/staging/library identity/headless startup pass. Whole-H2 reference fixture failed after a focus change; input and splith return NOT TESTED. PARTIALLY_FEASIBLE / UNDECIDED, preserved historical evidence; the original manifests and fixture bytes remain unchanged. - Preserved static distribution contract: one hash-qualified static executable staged through private Meson metadata at /usr/bin/swayfx-mosaic, exact-one-file shape PASS without recompilation. Existing scene interposition is characterized, version/security/notice obligations and an unsubmitted upstream API dossier are explicit. PARTIALLY_FEASIBLE, contract decision required; no recipe, packaging contract, production or system change. Historical verdict retained; later user decisions authorize a separate private gate only. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 40c82d4..571db5b 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -405,3 +405,14 @@ reference equality fails through a focus/decor mismatch; input and splith return are NOT TESTED after the single run stopped. No rerun or algorithm modification occurred. Distribution notice closure, named release/security roles and broader ABI/graphics gates remain open. STEP20C NEXT, multi-output NOT YET SUPPORTED. + +The [static focus/input/standard follow-up](design/STEP20C_STATIC_RUNTIME_FOCUS_INPUT_STANDARD_PROBE.md) +is **EXPERIMENTAL / INCONCLUSIVE**, architecture **UNDECIDED**. One run reused +the exact static binary, verified loaded wlroots/output geometry, calculated +camera and observed the applied clip. A fixture incorrectly required stored +current_border_width=0 under border none; actual IPC retained thickness 2 with +empty decoration and full-size client rectangles. The run stopped before any +PNG, click or splith transition. Those gates are NOT TESTED; no clipping defect +or need for an algorithm change is demonstrated. No retry/build/production +change occurred. A separately authorized corrected-fixture gate is required. +STEP20C stays NEXT; production multi-output remains NOT YET SUPPORTED. diff --git a/docs/KNOWN_LIMITATIONS.md b/docs/KNOWN_LIMITATIONS.md index f7e52c5..3234ef8 100644 --- a/docs/KNOWN_LIMITATIONS.md +++ b/docs/KNOWN_LIMITATIONS.md @@ -346,3 +346,14 @@ reference equality fails through a focus/decor mismatch; input and splith return are NOT TESTED after the single run stopped. No rerun or algorithm modification occurred. Distribution notice closure, named release/security roles and broader ABI/graphics gates remain open. STEP20C NEXT, multi-output NOT YET SUPPORTED. + +The [static focus/input/standard follow-up](design/STEP20C_STATIC_RUNTIME_FOCUS_INPUT_STANDARD_PROBE.md) +is **EXPERIMENTAL / INCONCLUSIVE**, architecture **UNDECIDED**. One run reused +the exact static binary, verified loaded wlroots/output geometry, calculated +camera and observed the applied clip. A fixture incorrectly required stored +current_border_width=0 under border none; actual IPC retained thickness 2 with +empty decoration and full-size client rectangles. The run stopped before any +PNG, click or splith transition. Those gates are NOT TESTED; no clipping defect +or need for an algorithm change is demonstrated. No retry/build/production +change occurred. A separately authorized corrected-fixture gate is required. +STEP20C stays NEXT; production multi-output remains NOT YET SUPPORTED. diff --git a/docs/README.md b/docs/README.md index b9bf52b..7a40490 100644 --- a/docs/README.md +++ b/docs/README.md @@ -264,3 +264,14 @@ reference equality fails through a focus/decor mismatch; input and splith return are NOT TESTED after the single run stopped. No rerun or algorithm modification occurred. Distribution notice closure, named release/security roles and broader ABI/graphics gates remain open. STEP20C NEXT, multi-output NOT YET SUPPORTED. + +The [static focus/input/standard follow-up](design/STEP20C_STATIC_RUNTIME_FOCUS_INPUT_STANDARD_PROBE.md) +is **EXPERIMENTAL / INCONCLUSIVE**, architecture **UNDECIDED**. One run reused +the exact static binary, verified loaded wlroots/output geometry, calculated +camera and observed the applied clip. A fixture incorrectly required stored +current_border_width=0 under border none; actual IPC retained thickness 2 with +empty decoration and full-size client rectangles. The run stopped before any +PNG, click or splith transition. Those gates are NOT TESTED; no clipping defect +or need for an algorithm change is demonstrated. No retry/build/production +change occurred. A separately authorized corrected-fixture gate is required. +STEP20C stays NEXT; production multi-output remains NOT YET SUPPORTED. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 6cf0fba..9254cde 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -350,3 +350,14 @@ reference equality fails through a focus/decor mismatch; input and splith return are NOT TESTED after the single run stopped. No rerun or algorithm modification occurred. Distribution notice closure, named release/security roles and broader ABI/graphics gates remain open. STEP20C NEXT, multi-output NOT YET SUPPORTED. + +The [static focus/input/standard follow-up](design/STEP20C_STATIC_RUNTIME_FOCUS_INPUT_STANDARD_PROBE.md) +is **EXPERIMENTAL / INCONCLUSIVE**, architecture **UNDECIDED**. One run reused +the exact static binary, verified loaded wlroots/output geometry, calculated +camera and observed the applied clip. A fixture incorrectly required stored +current_border_width=0 under border none; actual IPC retained thickness 2 with +empty decoration and full-size client rectangles. The run stopped before any +PNG, click or splith transition. Those gates are NOT TESTED; no clipping defect +or need for an algorithm change is demonstrated. No retry/build/production +change occurred. A separately authorized corrected-fixture gate is required. +STEP20C stays NEXT; production multi-output remains NOT YET SUPPORTED. diff --git a/docs/design/STEP20C_STATIC_RUNTIME_FOCUS_INPUT_STANDARD_PROBE.md b/docs/design/STEP20C_STATIC_RUNTIME_FOCUS_INPUT_STANDARD_PROBE.md new file mode 100644 index 0000000..834eb8a --- /dev/null +++ b/docs/design/STEP20C_STATIC_RUNTIME_FOCUS_INPUT_STANDARD_PROBE.md @@ -0,0 +1,320 @@ +# STEP20C static runtime focus, input and standard-return probe + +**EXPERIMENTAL / INCONCLUSIVE. Historical campaign frozen as evidence.** +Phase A published `5e2961d05b7b33252718aabc4dbf787724f79ef0` to both remotes, +with a clean worktree. The original phase B was local and unpublished; the later evidence freeze publishes its sources and findings without replay. +[Architecture](../ARCHITECTURE.md), [UX](../UX.md), [Packaging](../PACKAGING.md) +and [Roadmap](../ROADMAP.md) remain authoritative. STEP20A DONE, STEP20B +EXPERIMENTAL, STEP20C NEXT, STEP20D–G PLANNED; production multi-output +NOT YET SUPPORTED. No production, algorithm, shader, package or UX change. + +## Historical evidence and exact candidate + +The [previous runtime](STEP20C_STATIC_RUNTIME_AND_PACKAGE_CONTRACT.md) remains +PARTIALLY_FEASIBLE / UNDECIDED. It captured h2-reference.png while workspace 2 +was focused, then h2-mosaic.png after workspace 1/client setup and Mosaic. +The full RGB comparison failed by 23,807 pixels, bounding box (0,0,800,600). +Changes outside the unchanged reference interior support focus/title/border +variation. No target colors appeared on H2; 182,214 survived on H1. Neither +cropping borders nor a new successful run can retroactively pass that comparison. + +Reuse only the historical staged binary from +`/home/fy59/swayfx-mosaic-static-runtime-hm5kbdkt/stage-static/usr/bin/swayfx-mosaic`: +SHA-256 `f82557c1a9978a5e9665b9b4291b74880253725359e3df1c5b8d554921f7603b`, +ELF build ID `942db64269640778ee433e5970ba1100bc60f94c`. +Verify original/copy hash, executable regular-file mode, ELF dependencies and +system wlroots SHA-256 `7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837`. +No SceneFX DT_NEEDED or loaded SceneFX shared mapping is allowed. Stop before +launch if identity is unavailable. No SceneFX/Mosaic build or binary alteration. + +Historical C17 saved-client and static-reference plus the extent-adapted virtual +pointer are reused from manifest-verified source. Their historical executable +files are not entries of those manifests: rebuild only these clients from the +verified C/generated-protocol sources, record commands and resulting hashes. +No client algorithm changes. Target alternates two opaque colors on configure; +reference remains RGB (48,48,56) across focus/configure changes. Pointer uses +1600×600 logical extent and global-x + 800 normalization. + +## Single-run protocol and stop gates + +One new private LAB, mode-0700 runtime, explicit private Wayland display and IPC +socket, WLR_BACKENDS=headless. Every swaymsg uses -s. Only owned process groups +are stopped; preserve exit codes and verify no residual process/socket. +A 150-second deadline and bounded logs apply. No pristine run, no popup. +HEADLESS-1=(-800,0,800,600), HEADLESS-2=(0,0,800,600), scale 1, normal. + +Create reference on workspace 2, then filler and target on workspace 1. Both +reference and filler have org.mosaic.StaticReference App ID. Therefore do not +use the broad for_window App ID rule: obtain reference con_id while it is the +only reference and apply `[con_id=ID] border none` in private IPC. Check reference +PID, workspace/output, border none and zero border width; filler keeps its normal +border. Keep focus_follows_mouse no. No daily configuration changes. + +Activate Mosaic, read real applied owner/tiling traces and IPC scroll. Compute +one signed camera delta from horizontal content offset and configured width, +then reread scene/scroll. The historical +160 is not a constant. No further +camera command or hide/return. Focus must initially remain target/workspace 1. +If later normal focus changes move the camera, retain that observation and stop +any assertion needing unchanged Mosaic geometry; do not correct it. + +PI_OWNER gives an applied scene owner, not a buffer. IPC rect/window_rect/deco_rect +and the client's last real COMMIT size are separate evidence. Use the actual H1 +painted color rectangle to identify vertical content and pick an interior point. +Correlate measured paint with horizontal scene offset and client width; never +label the old source_box y estimate a native buffer coordinate. PI_SCENE local +clip plus output-parent origin and tiled translation gives global viewport. + +After complete Mosaic setup and sole calibration, park pointer at (-790,590), +wait for stabilization, capture H2 twice at least 500 ms apart. Check 800×600, +full-frame reference color, border, focus/workspace and exact whole RGB equality. +Store PNG hashes and snapshots. No ROI, no tolerance. Failure stops before input. + +V1: capture H1/H2, retain positive target-color count on H1, zero on H2, and +whole-H2 equality to preflight. Require content crossing x=0 and clip ending +there. Save every differing pixel coordinate/color on failure; stop dependent tests. + +V2: choose a point at least 20 pixels inside the actually painted H1 rectangle, +outside decorations and borders. One right press/release; require actual target +BUTTON 273 states 1 then 0 with recorded serials, no reference delivery. A move +alone is never input proof. Preserve available passive scene hit and Wayland logs. + +V3: choose an H2 point within the geometrical continuation of the target content +and the full-frame reference interior. One right press/release; require zero +new target BUTTON events and two real reference events. Record focus after click, +park pointer, stabilize and recapture whole H2 with exact equality. Do not infer +PASS from neither client receiving input. + +V4: address the target con_id with layout splith (after V3 global focus is H2; +a bare layout command could affect the wrong workspace). Verify workspace identity, +applied splith, target still mapped, applied owner and PI_SCENE clip_valid=0 on +H1 and ordinary H2. Capture H1/H2. Choose a fresh interior target-colored point +from the standard image, inject one click and require actual target press/release. +Park pointer and recapture H2 after focus transition. Require stable H2 and no +residual clip. Do not reuse the Mosaic click point blindly. + +## Budget, analysis and classification + +Persistent ledger, exclusive flock, atomic durable reservation before run, +each PNG, each click, camera and bounded pointer movement. Failed reservations +remain consumed; no reset/refund or alternate LAB. Offline tests must precede run. +Limits: run 1, pristine 0, SceneFX/Mosaic builds 0, camera adjustment 1, each of +three named clicks 1, PNG 12, pointer moves 8, duration 150 seconds. Expected +PNG count 8. All generated artifacts stay outside Git. Seal source and evidence +manifests once after collecting results, then verify every entry without rewriting. + +Keep client damage requests, scene/output damage extents and stabilized PNGs +separate. No claim of complete intermediate frames or native output-buffer/PNG +identity. Root/nested grabs, other geometry, migration, saved buffers/effects, +full semantic ABI, other wlroots pairs, legal notice closure and named release/ +security owners are NOT TESTED or remain open. D1–D4 stay approved privately. + +FEASIBLE requires every V1–V4 assertion and conformant binary/dependencies, +no crash or blocking defect in this matrix. PARTIALLY_FEASIBLE retains demonstrated +principal properties with unqualified input/return/pixels. INCONCLUSIVE means an +unreliable fixture. BLOCKED requires demonstrated pixel/input leakage, runtime +ABI defect or standard regression. Focus/calibration error alone is not a clipping +failure. Architecture: STATIC_MOSAIC_FOCUS_INPUT_STANDARD_FEASIBLE_FOR_TESTED_MATRIX, +STATIC_MOSAIC_FOCUS_INPUT_STANDARD_REQUIRES_REDESIGN, or UNDECIDED respectively. + +## Independent future reconstruction (not authorized in this campaign) + +A later campaign can archive pinned Mosaic cd550382b311efdbfe3ed892f4e1d281d852e754, +SceneFX 37ccd723bef49e6891156ffafce8f549f01446cc and wlroots +d783533489e1f75d6886c2ab5c5960090ef268f8, without the old LAB. Apply the ordered +three SceneFX and five Mosaic patches listed in the +[static distribution report](STEP20C_SCENEFX_STATIC_DISTRIBUTION_CONTRACT.md). +Use SceneFX default_library=static/examples=false, its uninstalled pkg-config +provider and Mosaic --prefix=/usr --bindir=bin with mosaic-package=true, +swaybar/swaynag=false, man-pages=disabled, default-wallpaper=false, +sd-bus-provider=libsystemd, werror=false. Build in independent directories, +qualify exact shared wlroots, model, ELF, notices/stage and new authorized +runtime budgets. Preserve toolchain/generated protocols/patch hashes. This is +source reconstruction, not a promise of byte-reproducibility or permission to +substitute another binary in this ticket. + +The [fixture](../../tooling/probes/step20c-static-runtime-followup/README.md) +provides the bounded runner, offline tests and evidence collector. + +## Completed campaign — retained fixture failure + +`SWAYFX_MOSAIC_STATIC_FOCUS_INPUT_STANDARD_PROBE=INCONCLUSIVE` + +Architecture: **UNDECIDED**. LAB: +`/home/fy59/swayfx-mosaic-static-followup-nhwejz6u`. One run, 2.661722 seconds. +The unique runtime stopped before the first screenshot, movement or button. +No restart, second LAB, binary/algorithm repair or budget refund occurred. + +### Identity, setup and measured state + +The copied executable retains SHA-256 +`f82557c1a9978a5e9665b9b4291b74880253725359e3df1c5b8d554921f7603b`, +ELF build ID `942db64269640778ee433e5970ba1100bc60f94c`, mode 0755. +Readelf and ldd -r pass; process maps resolve the qualified system wlroots +0.20.2 binary with SHA-256 +`7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837`. +No shared SceneFX or historical LAB dependency appears in the runtime mappings. +Meson/model evidence is reused from A; no new compositor/library build. + +Three C17 client-only builds from manifest-verified sources passed syntax and +compilation and produced the same bytes as the historical executables: + +| Client | SHA-256 | +| --- | --- | +| saved-client | `3d5d0c5e446b995c210006a5bafe0d86dd5f32a228e1b7137d12af6ec9e217da` | +| static-reference | `11166ea1bb7cb3bb08f10ab93e091c5c5ea8687649b5fb0705288c7ca91680e8` | +| pointer-daemon | `76ad0f093f2b7a97359920b7bd456cfa93a1581c73875547a86eb911f2ed2d7a` | + +These hashes are newly recorded; the historical executable files themselves +were not sealed. Historical C/generated protocol inputs were sealed and verified. +The new retained source/command/binary chain supplies explicit provenance. + +Both outputs matched the requested negative-origin 800×600/scale-1/normal +matrix. Private configuration retained focus_follows_mouse no. The command +`[con_id=7] border none` succeeded for reference PID 1674352 on workspace 2 +(ID 5), H2. Filler ID 8, with the same App ID, kept normal border and width 2. +Target ID 9/PID 1674361 on workspace 1 (ID 6), H1 remained focused; reference +was unfocused. No broad App ID rule affected the filler. + +Applied camera read 0 before calibration. The target's horizontal content offset +was -158 with configured width 636; desired left -318 gave signed delta +160. +IPC and scene both read 160 afterward. Applied owner scene became (-320,0), +size 640×600. IPC window offset x=2 and actual client COMMIT size 636×573 +support horizontal content continuation [-318,318), crossing x=0. They do not +prove native buffer y or actually visible pixels without a capture. +PI_SCENE records tile-local (-160,0), clip (160,0,800,600), translated through +H1 origin (-800,0) to global clip (-800,0,800,600). H2 records clip_valid=0. +This proves the observed applied clip state, not the unperformed pixel/input gates. + +### Exact stopping assertion and source diagnosis + +The executed runner required both border none and current_border_width==0. +Reference IPC instead reports: + +```json +{"border":"none","current_border_width":2, + "rect":{"x":0,"y":0,"width":800,"height":600}, + "deco_rect":{"x":0,"y":0,"width":0,"height":0}, + "window_rect":{"x":0,"y":0,"width":800,"height":600}} +``` + +This failed the stored-width predicate before writing owner-geometry.json or +reference-preflight.json and before any capture reservation. Offline collection +reconstructs the actual owner/reference observations from retained IPC/traces +and explicitly labels all unexecuted results NOT TESTED. + +Source diagnosis: `sway/commands/border.c:cmd_border` calls set_border(B_NONE); +it changes border_thickness only with a second argument. `sway/ipc-json.c` +publishes current.border_thickness unconditionally. `sway/desktop/transaction.c` +sets the effective border width/top to zero for B_NONE. Therefore stored width 2 +is compatible with no border. The runner confused stored configuration with +effective decoration geometry. No rendered border or Mosaic leak is demonstrated. +The initial protocol's zero-width expectation above is retained as the failed +hypothesis; it is not a new product contract. The exact executed runner remains +unchanged, and no corrected runner is represented as executed. + +### Matrix, damage and cleanup + +| Gate | Result | +| --- | --- | +| Binary, loader, exact wlroots | PASS | +| Output geometry, client mapping, focus, targeted border mode | PASS IPC/source evidence | +| Calculated camera and applied clip | OBSERVED; no pixel qualification | +| Two H2 reference captures / whole RGB equality | NOT TESTED; zero PNG | +| H1 retained pixels / H2 forbidden colors | NOT TESTED in B | +| Visible Mosaic button | NOT TESTED | +| Invisible Mosaic non-delivery / reference H2 delivery | NOT TESTED | +| H2 focus-transition RGB stability | NOT TESTED | +| splith return / clip removal / standard button | NOT TESTED | +| Root/nested popup or grab | NOT TESTED, deliberately outside scope | +| Damage | Setup/client/scene/output extents retained; no PNG correlation or intermediate-frame coverage | +| Crash/protocol error | No crash; each ordinary client exited 0 with error=0 | +| Cleanup | PASS; compositor exit 0, three clients exit 0, owned pointer terminated -15; PIDs and private runtime absent | + +The owned runtime was `/tmp/msr-330t08c8`, removed after all owned process groups +were reaped. No daily IPC, installation, system dependency replacement, package, +production patch/source, UX, dotfile, QuickShell, Codex or STEP20D change occurred. + +Budget: SceneFX build 0, Mosaic build 0, binary modification 0, run 1/1, pristine +0, camera 1/1, captures 0/12, each named click 0/1, moves 0/8. The consumed run +prevents further execution despite unused PNG/click capacity. The persistent +owner registration and ledger are retained and never reset. + +The future smallest justified gate is a separately authorized single run after +correcting the fixture's border-semantic predicate and testing it offline against +this retained border-none/width-2 snapshot. Keep the same focus/RGB/input/splith +objectives and exact candidate identity if still available. No SceneFX or clip +algorithm modification is justified by this fixture failure. This ticket does +not close the static runtime continuity gate or authorize STEP20C integration. + +Q1–Q2 reference stability and full RGB: NOT TESTED. Q3 forbidden pixels: NOT +TESTED in B; A's zero target colors remain historical only. Q4–Q9 button delivery, +invisible input, focus RGB, clip removal and standard input: NOT TESTED. Q10 +static/shared behavioral equivalence: not established by this run. Q11 algorithm +change justified: no demonstrated runtime anomaly. Q12 wider geometry, lifecycle, +full damage/effects/saved buffers, applied-generation and exhaustive ABI remain +unqualified, as do this run's intended principal gates. Q13 preparation for +integration: the static build/load candidate is established, but the requested +continuity evidence remains open. Q14 next technical gate: correct the fixture +and obtain a newly authorized bounded RGB/input/standard-return observation. +D1–D4 persist; STEP20C NEXT, production multi-output NOT YET SUPPORTED. + +## Validation and integrity receipt + +Python AST, shell/recipe syntax, concurrent/exhausted persistent-ledger tests, +exact-RGB and actual-button offline tests, retained border snapshot/source +semantics, local Markdown links, source-only fixture, protected source/system +hashes, unchanged executed runner and owned-process/socket cleanup: PASS. +Runtime scenario: FAIL at fixture assertion, scientific verdict INCONCLUSIVE. +Meson/model: historical qualified results reused, NOT RUN anew. Standard test +suite/headless campaign: NOT RUN, would exceed the authorized runtime scope. +No subagent review was requested; the parent performed the bounded source, +contract, provenance and final evidence audit. No independent review is claimed. + +188 TEMPORAL_DAMAGE trace lines are retained from setup. They establish no +post-click, standard-return or every-frame damage result. + +B source manifest: 24 entries, SHA-256 +`5f9ab2b6d1ca23ef7743623d1e544237c8b70b62fe6de8164a46f1342e5a8ee2`. +B evidence manifest: 46 entries, SHA-256 +`41cc5e3d305d89e83314723e67d7116c36d1503dd35647894bc898c49343e1a5`. +Both were created once and every entry verified. Executed fixture and later +collector/final fixture are distinct source directories. The report and final +Git diff are outside these inventories to avoid circular digests. A's original +manifests still verify without rewriting or snapshot remapping. + +Final tracked B delta: this report plus Architecture, Known Limitations, +Roadmap, documentation index and Changelog; source-only follow-up fixture added. +No AGENTS, UX, Packaging, PKGBUILD, cumulative patch or production source change +in B. Local main and both remotes remain the A publication commit +`5e2961d05b7b33252718aabc4dbf787724f79ef0`; B remains unstaged and uncommitted. + +## Static focus fixture evidence freeze — 2026-10-13 + +This new freeze task starts at main +`5e2961d05b7b33252718aabc4dbf787724f79ef0`. Direct `git ls-remote` +queries to canonical Forgejo and GitHub both returned that exact commit. +The expected local documentation and source-only fixture were audited in full. +No foreign change was found. This preservation commit does not alter the +historical executed runner, either historical manifest, or any raw observation. +The earlier LOCAL/UNCOMMITTED statements describe the original campaign's state, +not the publication state of this frozen evidence. + +All 24 source and 46 evidence entries pass their original SHA-256 inventories; +the manifest digests above are unchanged. The Python files match the sealed +final-fixture byte for byte, and run.py matches the actually executed fixture. +Python AST, offline RGB/button tests, concurrent/exhausted ledger tests, +read-only collector verification, shell recipe syntax, local Markdown links +and git diff --check pass. Production source, package recipe and cumulative +patch are unchanged. No graphical test, compositor/library build or historical +budget reservation is performed by this freeze. Run 1/1 and camera 1/1 remain +consumed; unused captures/clicks/moves cannot authorize another historical run. + +The source audit also reads the pristine pinned SwayFX border command: +`fd71a6bdc061bd633b488ae7b83e8a6981d22f86:sway/commands/border.c`. +Its B_NONE handling and optional second-argument thickness assignment match +the retained candidate semantics. IPC thickness is stored metadata; effective +border/decor geometry is separate. The failure remains INCONCLUSIVE / UNDECIDED. +The separately authorized RGB/input/splith ticket must supply a corrected pure +predicate, replay actual IPC and audit the entire preflight before its own run. + +`SWAYFX_MOSAIC_STATIC_FOCUS_FIXTURE_FREEZE=PASS` diff --git a/tooling/probes/step20c-static-runtime-followup/README.md b/tooling/probes/step20c-static-runtime-followup/README.md new file mode 100644 index 0000000..5cdbbd4 --- /dev/null +++ b/tooling/probes/step20c-static-runtime-followup/README.md @@ -0,0 +1,76 @@ +# Static focus/input/standard-return fixture + +EXPERIMENTAL / FROZEN HISTORICAL FAILURE. No production integration. +Read the [protocol](../../../docs/design/STEP20C_STATIC_RUNTIME_FOCUS_INPUT_STANDARD_PROBE.md). +The previous static campaign remains sealed and is never replayed. + +This fixture creates one campaign-owned LAB outside Git, copies the hash-qualified +static compositor unchanged and rebuilds only three historical C17 clients from +manifest-verified source/generated protocols. The old client executables were +not manifest entries, so their observed hashes are contextual only. An exclusive +`~/swayfx-mosaic-static-followup-v1.owner` file binds the campaign to one LAB. +Never delete/reset it, its ledger, or replace the LAB to retry. + +Offline checks before launch: + +```sh +PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-static-runtime-followup/test-budget.py +PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-static-runtime-followup/test-offline.py +``` + +Historical commands, preserved for provenance only — DO NOT execute prepare.py +or run.py again; the historical budget is exhausted: + +```sh +PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-static-runtime-followup/prepare.py +# Use the printed LAB path; execute the retained exact fixture copy there. +PYTHONDONTWRITEBYTECODE=1 python "$LAB/fixture/run.py" "$LAB" +PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-static-runtime-followup/collect.py "$LAB" +``` + +The flock ledger reserves run, PNG, named click, camera and pointer moves before +execution. No refund/reset. Runtime is private mode 0700; all swaymsg use explicit +-s, child environments discard inherited desktop sockets. Only owned process +groups are stopped. The deadline is 150 seconds, per-log bound 64 MiB. Sources, +raw protocol/client/scene logs, PNGs, IPC snapshots, timestamps, serials, exit +codes and result manifests stay external. The collector creates manifests once; +verification is read-only with `collect.py "$LAB" --verify`. + +Reference border none is applied to its measured con_id, never to the shared +App ID (which also identifies the H1 filler). H2 full RGB must be identical +before input; no crop/tolerance. Clicks use measured painted interiors. The +standard-return command explicitly targets the H1 con_id after H2 gains focus. +Popup tests and native output-buffer/PNG identity remain NOT TESTED. + +The protocol describes independent source reconstruction for a future authorized +campaign if historical binaries disappear. Do not silently rebuild or substitute +the compositor in this campaign. Historical LAB paths identify retained evidence, +not a permanent production dependency. + +## Actual execution — no retry permitted + +LAB `/home/fy59/swayfx-mosaic-static-followup-nhwejz6u`, run consumed. +Verdict INCONCLUSIVE, architecture UNDECIDED. The runner stopped before the +first PNG because it incorrectly required current_border_width=0 after border +none. Actual IPC had border none, stored width 2, empty deco_rect and 800×600 +window_rect. The filler retained normal decorations; target/workspace 1 were +focused. The source confirms that border none changes the border enum, not +stored thickness. This is a fixture defect, not an observed rendered border. + +The executed runner is intentionally unchanged, including the erroneous predicate. +It is historical experimental source, not a validated replay harness. A future +authorized campaign must correct that semantic check and add its regression +before a separately budgeted run; this campaign may not be resumed or repeated. +No PNG, click or splith command was executed. Camera 0→160 and applied clip were +observed. All principal RGB/input/standard gates remain NOT TESTED. The collector +records those omissions explicitly and verifies owned-process/socket cleanup. + +## Freeze receipt + +The preservation task verified every entry of the original 24-source and +46-evidence manifests without rewriting them. All Python files match the +sealed final-fixture; run.py matches the historical executed fixture, including +the invalid width assertion. Offline tests and --verify remain read-only +checks of this exhausted campaign. Publication preserves INCONCLUSIVE / +UNDECIDED, zero PNG and zero clicks; it does not convert the fixture into a +validated runner. A new campaign uses a separate directory and budget. diff --git a/tooling/probes/step20c-static-runtime-followup/collect.py b/tooling/probes/step20c-static-runtime-followup/collect.py new file mode 100644 index 0000000..8a60fec --- /dev/null +++ b/tooling/probes/step20c-static-runtime-followup/collect.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Offline collection of the single stopped run; never creates runtime evidence. + +WHY: an IPC metadata predicate is not a pixel or input failure. +CONTRACT: retain the executed runner unchanged and label unexercised cells. +INVARIANT: seal once; --verify never rewrites results or manifests. +""" +import argparse, json, re, shutil +from pathlib import Path +from support import save, sha +p=argparse.ArgumentParser();p.add_argument('lab',type=Path);p.add_argument('--verify',action='store_true');a=p.parse_args();lab=a.lab.resolve() +assert (Path.home()/'swayfx-mosaic-static-followup-v1.owner').read_text().strip()==str(lab) +results=lab/'results' +if not a.verify: + assert not (results/'evidence-sha256.txt').exists() + runtime=json.loads((results/'runtime.json').read_text()) + snap=json.loads((results/'preflight-snapshot.json').read_text()) + def nodes(n): + yield n + for c in n.get('nodes',[])+n.get('floating_nodes',[]):yield from nodes(c) + ns=list(nodes(snap['tree'])) + ref=next(n for n in ns if n.get('app_id')=='org.mosaic.StaticReference' and n['rect']['x']==0) + target=next(n for n in ns if n.get('app_id')=='org.mosaic.SavedBufferProbe') + filler=next(n for n in ns if n.get('app_id')=='org.mosaic.StaticReference' and n['id']!=ref['id']) + assert ref['border']=='none' and ref['current_border_width']==2 + assert ref['deco_rect']==dict(x=0,y=0,width=0,height=0) + assert ref['window_rect']==dict(x=0,y=0,width=800,height=600) + assert filler['border']=='normal' and target['focused'] and not ref['focused'] + assert runtime['status']=='INCONCLUSIVE' and runtime['failure']=='AssertionError: ' + budget=json.loads((results/'budget.json').read_text()) + assert budget['used']==dict(run=1,capture=0,camera=1,visible_click=0,forbidden_click=0,standard_click=0,move=0) + assert not list((lab/'run').glob('*.png')) and runtime['duration_seconds']<150 + for name in ['target','reference','filler']: + text=(lab/'run'/(name+'.log')).read_text() + assert not any(s.startswith('BUTTON ') for s in text.splitlines()) + assert 'error=0' in text + for rec in runtime['process_exit_codes']:assert not Path('/proc',str(rec['pid'])).exists() + assert not Path(runtime['runtime_removed']).exists() + compositor=(lab/'run/compositor.log').read_text() + owner=[s for s in compositor.splitlines() if 'PI_OWNER output=HEADLESS-1 ' in s and f' id={target["id"]} ' in s][-1] + clips=[s for s in compositor.splitlines() if 'PI_SCENE output=' in s] + save(lab,'fixture-incident',{'status':'CONFIRMED FIXTURE ERROR','failed_predicate':'reference current_border_width == 0','actual':ref,'filler':filler,'reason':'border none sets enum B_NONE without resetting stored thickness; IPC reports stored thickness even when rendered border is disabled','source_sites':['sway/commands/border.c:cmd_border','sway/ipc-json.c:ipc_json_describe_container','sway/desktop/transaction.c:arrange_container'],'runner_modified_after_run':False,'rerun':False}) + save(lab,'owner-geometry',{'status':'OBSERVED scene and IPC only','target':target,'scene_trace':owner,'client_commits':[s for s in (lab/'run/target.log').read_text().splitlines() if s.startswith('COMMIT ')],'painted_content':'NOT TESTED; no PNG','native_buffer_rectangle':'NOT ESTABLISHED'}) + save(lab,'clip-observations',{'status':'OBSERVED applied traces only','traces':clips,'pixel_validation':'NOT TESTED'}) + for name in ['reference-preflight','reference-pixel-analysis','permitted-pixels','forbidden-pixels','target-input','reference-input','standard-return']: + save(lab,name,{'status':'NOT TESTED','reason':'run stopped at incorrect stored-border-width assertion before first capture or injection; no retry'}) + matrix={'binary_identity':'PASS','loaded_libraries':'PASS','output_geometry':'PASS','reference_border_mode':'PASS IPC none; stored thickness 2','reference_visible_pixels':'NOT TESTED','initial_focus':'PASS target/workspace 1','camera':'PASS calculated +160, applied 0 to 160','applied_clip':'OBSERVED global [-800,0,800,600]','H2_preflight':'NOT TESTED','H1_pixels':'NOT TESTED','H2_pixels':'NOT TESTED','whole_H2_RGB':'NOT TESTED','visible_click':'NOT TESTED','forbidden_click':'NOT TESTED','reference_delivery':'NOT TESTED','focus_RGB_stability':'NOT TESTED','splith_return':'NOT TESTED','clip_removal':'NOT TESTED','standard_click':'NOT TESTED','popups':'NOT TESTED','damage':'available setup extents only; no PNG or every-frame proof','cleanup':'PASS'} + save(lab,'matrix',matrix) + save(lab,'summary',{'verdict':'INCONCLUSIVE','architecture':'UNDECIDED','reason':'incorrect fixture interpretation of current_border_width under border none stopped the only run before RGB/input gates','budget':budget['used'],'builds':{'SceneFX':0,'Mosaic':0,'historical_clients_only':3},'STEP20C':'NEXT','production_multi_output':'NOT YET SUPPORTED','reruns':0,'new_labs':1}) + # Keep the executed fixture separate from the later offline collector. + shutil.copytree(Path(__file__).parent,lab/'final-fixture') + source=sorted(p for root in ['fixture','final-fixture','client-source'] for p in (lab/root).rglob('*') if p.is_file()) + evidence=sorted(p for root in ['raw','run','results'] for p in (lab/root).rglob('*') if p.is_file()) + evidence += [lab/name for name in ['swayfx-mosaic','saved-client','static-reference','pointer-daemon','campaign-owner']] + for name,files in [('source',source),('evidence',evidence)]: + with (results/(name+'-sha256.txt')).open('x') as out: + for f in files:out.write(f'{sha(f)} {f}\n') +for name in ['source','evidence']: + manifest=results/(name+'-sha256.txt');lines=manifest.read_text().splitlines() + for line in lines: + digest,path=line.split(' ',1);assert sha(path)==digest,path + print(name,len(lines),sha(manifest),'PASS') diff --git a/tooling/probes/step20c-static-runtime-followup/pixels.py b/tooling/probes/step20c-static-runtime-followup/pixels.py new file mode 100644 index 0000000..1eb2039 --- /dev/null +++ b/tooling/probes/step20c-static-runtime-followup/pixels.py @@ -0,0 +1,31 @@ +"""Exact whole-image comparisons and measured interior click selection. + +CONTRACT: no mask or tolerance may turn a whole-output difference into PASS. +INVARIANT: coordinates of every differing pixel are retained on failure. +""" +from PIL import Image, ImageChops +import re +COLORS = {(35,68,204),(32,208,160)} +def analyze(im): + mask=Image.new('L',im.size) + values=[255 if c in COLORS else 0 for c in im.get_flattened_data()] + mask.putdata(values) + return {'count':sum(v!=0 for v in values),'bbox':mask.getbbox()} +def compare(a,b): + assert a.size==b.size + points=[{'x':i%a.width,'y':i//a.width,'before':x,'after':y} + for i,(x,y) in enumerate(zip(a.get_flattened_data(),b.get_flattened_data())) if x!=y] + return {'equal':not points,'changed':len(points),'bbox':ImageChops.difference(a,b).getbbox(),'pixels':points} +def interior(im): + box=analyze(im)['bbox'];assert box + x1,y1,x2,y2=box;assert x2-x1>40 and y2-y1>40 + x,y=(x1+x2)//2,(y1+y2)//2 + assert all(im.getpixel((xx,yy)) in COLORS for xx in range(x-20,x+21) for yy in range(y-20,y+21)) + return x,y + +def button_pair(lines): + events=[re.search(r'BUTTON ns=(\d+) serial=(\d+) button=(\d+) state=(\d+)',x) for x in lines] + events=[tuple(map(int,x.groups())) for x in events if x] + assert len(events)==2 and [x[2:] for x in events]==[(273,1),(273,0)],events + assert all(x[1]>0 for x in events) + return [{'ns':x[0],'serial':x[1],'button':x[2],'state':x[3]} for x in events] diff --git a/tooling/probes/step20c-static-runtime-followup/prepare.py b/tooling/probes/step20c-static-runtime-followup/prepare.py new file mode 100644 index 0000000..a9fdff3 --- /dev/null +++ b/tooling/probes/step20c-static-runtime-followup/prepare.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +"""Copy exact candidate and compile verified historical C17 clients only. + +CONTRACT: exclusive campaign registration prevents another LAB/reset/retry. +WHY: historical client executable hashes were not sealed; source provenance was. +""" +import json, os, shutil, stat, subprocess, tempfile +from pathlib import Path +from support import sha, command, save +OLD=Path('/home/fy59/swayfx-mosaic-static-runtime-hm5kbdkt') +OWNER=Path.home()/'swayfx-mosaic-static-followup-v1.owner' +# Reserve campaign ownership before creating the only LAB, even on failure. +fd=os.open(OWNER,os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600) +with os.fdopen(fd,'w') as f: + lab=Path(tempfile.mkdtemp(prefix='swayfx-mosaic-static-followup-',dir=Path.home())) + f.write(str(lab)+'\n');f.flush();os.fsync(f.fileno()) +for name in ['results','raw']:(lab/name).mkdir() +(lab/'campaign-owner').write_text(str(OWNER)+'\n') +for name,digest in [('source','ed55bd418595ca977c022fa98917a899072cf3b6f0fb15457db823081046e797'),('evidence','e260ce5793ffc9a29ee4bfa2189663feeb6dc025bd46d3f5ddebccd225f3eef1')]: + manifest=OLD/'results'/(name+'-sha256.txt');assert sha(manifest)==digest + for line in manifest.read_text().splitlines(): + h,p=line.split(' ',1);assert sha(p)==h,p +binary=OLD/'stage-static/usr/bin/swayfx-mosaic' +expected='f82557c1a9978a5e9665b9b4291b74880253725359e3df1c5b8d554921f7603b' +assert sha(binary)==expected and stat.S_IMODE(binary.stat().st_mode)==0o755 +shutil.copy2(binary,lab/'swayfx-mosaic');assert sha(lab/'swayfx-mosaic')==expected +elf=command(lab,'binary-elf',['readelf','-hW','-nW','-dW',lab/'swayfx-mosaic']) +assert 'libscenefx' not in elf and '942db64269640778ee433e5970ba1100bc60f94c' in elf +assert '(RPATH)' not in elf and '(RUNPATH)' not in elf +loader=command(lab,'dependencies',['ldd','-r',lab/'swayfx-mosaic']) +assert 'not found' not in loader and 'undefined symbol' not in loader and 'libscenefx' not in loader +wl=Path('/usr/lib/libwlroots-0.20.so').resolve() +assert sha(wl)=='7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837' +assert str(wl) in loader +save(lab,'binary-identity',{'status':'PASS','source':str(binary),'copy':str(lab/'swayfx-mosaic'),'sha256':expected,'build_id':'942db64269640778ee433e5970ba1100bc60f94c','mode':'0755','wlroots':str(wl),'wlroots_sha256':sha(wl),'new_compositor_builds':0}) +shutil.copytree(OLD/'client-source',lab/'client-source') +flags=subprocess.check_output(['pkg-config','--cflags','--libs','wayland-client'],text=True).split() +clients=[] +for name,proto in [('saved-client','xdg-shell'),('static-reference','xdg-shell'),('pointer-daemon','virtual-pointer')]: + src=lab/'client-source';argv=['cc','-std=c17','-Wall','-Wextra','-Werror','-Wno-unused-parameter','-I'+str(src),src/(name+'.c'),src/(proto+'-protocol.c'),*flags] + command(lab,name+'-syntax',[*argv,'-fsyntax-only']) + command(lab,name+'-compile',[*argv,'-o',lab/name]) + clients.append({'name':name,'source_sha256':sha(src/(name+'.c')),'binary_sha256':sha(lab/name),'historical_binary_sha256_unsealed':sha(OLD/name)}) +save(lab,'client-provenance',{'historical':json.loads((OLD/'results/client-provenance.json').read_text()),'source_manifest_verified':True,'clients':clients}) +shutil.copytree(Path(__file__).parent,lab/'fixture') +print('LAB='+str(lab)) diff --git a/tooling/probes/step20c-static-runtime-followup/run.py b/tooling/probes/step20c-static-runtime-followup/run.py new file mode 100644 index 0000000..8d293a2 --- /dev/null +++ b/tooling/probes/step20c-static-runtime-followup/run.py @@ -0,0 +1,249 @@ +#!/usr/bin/env python3 +"""One budgeted follow-up of the unchanged verified static compositor. + +WHY: resolved relocations do not demonstrate real compositor execution. +CONTRACT: verify /proc executable/library identity before mapping any client. +INVARIANT: owned sockets/groups only, one camera correction, durable captures. +""" +import argparse +import json +import os +from pathlib import Path +import re +import shutil +import signal +import subprocess +import tempfile +import threading +import time +from PIL import Image,ImageChops +from support import environment,reserve,save,sha +from pixels import analyze, compare, interior, button_pair + +p=argparse.ArgumentParser();p.add_argument('lab',type=Path);a=p.parse_args();lab=a.lab.resolve() +assert json.loads((lab/'results/binary-identity.json').read_text())['status']=='PASS' +assert sha(lab/'swayfx-mosaic')=='f82557c1a9978a5e9665b9b4291b74880253725359e3df1c5b8d554921f7603b' +assert (Path.home()/'swayfx-mosaic-static-followup-v1.owner').read_text().strip()==str(lab) +reserve(lab,'run');run=lab/'run';run.mkdir();runtime=Path(tempfile.mkdtemp(prefix='msr-',dir='/tmp')) +runtime.chmod(0o700);(run/'runtime-path').write_text(str(runtime)+'\n') +(run/'config').write_text('output HEADLESS-1 resolution 800x600 scale 1 transform normal position -800 0\noutput HEADLESS-2 resolution 800x600 scale 1 transform normal position 0 0\nworkspace 1 output HEADLESS-1\nworkspace 2 output HEADLESS-2\nfocus_follows_mouse no\nseat seat0 hide_cursor 100\n') +env=environment();env.update(XDG_RUNTIME_DIR=str(runtime),WLR_BACKENDS='headless',WLR_HEADLESS_OUTPUTS='2',WLR_RENDERER='pixman') +binary=lab/'swayfx-mosaic';processes=[];files=[];failure=None;compositor=None;events=[] +started=time.monotonic();finished=threading.Event();expired=threading.Event() + +def event(kind,**data): + row={'ns':time.monotonic_ns(),'kind':kind,**data};events.append(row) + with (run/'events.jsonl').open('a') as out:out.write(json.dumps(row)+'\n') + +def stop_owned(): + for proc in list(processes): + try:os.killpg(proc.pid,signal.SIGTERM) + except ProcessLookupError:pass + +def watchdog(): + while not finished.wait(.1): + if time.monotonic()-started>150 or any(x.stat().st_size>64*1024*1024 for x in run.glob('*.log')): + expired.set();stop_owned();return + +thread=threading.Thread(target=watchdog,daemon=True);thread.start() + +def spawn(name,argv,stdin=False,extra=None): + assert not expired.is_set() + out=(run/(name+'.log')).open('w');files.append(out) + proc=subprocess.Popen(list(map(str,argv)),env={**env,**(extra or {})},stdin=subprocess.PIPE if stdin else subprocess.DEVNULL,stdout=out,stderr=subprocess.STDOUT,text=True,start_new_session=True) + processes.append(proc);event('spawn',name=name,pid=proc.pid,argv=list(map(str,argv)));return proc + +def wait(predicate,seconds=8): + end=time.monotonic()+seconds + while time.monotonic()before) + time.sleep(.15);event('pointer',action=action,global_point=[x,y],target_tail=log('target').splitlines()[-6:] if (run/'target.log').exists() else []) + +def capture(tag,output): + reserve(lab,'capture');path=run/(tag+'.png') + subprocess.run(['grim','-s','1','-o',output,str(path)],env=env,check=True,timeout=5,stdout=subprocess.DEVNULL) + im=Image.open(path).convert('RGB');assert im.size==(800,600) + event('capture',file=path.name,output=output,sha256=sha(path));return im + +def measure(owner): + rows=[x for x in log('compositor').splitlines() if 'PI_OWNER output=HEADLESS-1 ' in x and f' id={owner["id"]} ' in x] + assert rows,'applied owner trace unavailable' + line=rows[-1];m=re.search(r'scene_valid=1 scene=(-?\d+),(-?\d+) applied_size=(\d+),(\d+)',line);assert m,line + x,y,w,h=map(int,m.groups());win=owner['window_rect'] + return {'id':owner['id'],'scene':[x,y,w,h],'window_rect':win,'horizontal_content':[x+win['x'],win['width']],'raw':line,'IPC':owner} + +# WHY: global focus becomes H2 after the forbidden-region click. Workspace and +# owner IDs, rather than global command context, bind the standard-return test. +focus_states=[] +def snapshot(tag): + tree=ipc('-t','get_tree');outputs=ipc('-t','get_outputs');workspaces=ipc('-t','get_workspaces') + row={'tag':tag,'ns':time.monotonic_ns(),'tree':tree,'outputs':outputs,'workspaces':workspaces, + 'focused':[n['id'] for n in nodes(tree) if n.get('focused')]} + focus_states.append(row);save(lab,'focus-states',focus_states) + save(lab,tag+'-snapshot',row) + return tree + +def workspace(tree,name): + return next(n for n in nodes(tree) if n['type']=='workspace' and n['name']==name) + +def scene(output): + rows=[s for s in log('compositor').splitlines() if 'PI_SCENE output='+output+' ' in s] + assert rows,'scene trace absent';line=rows[-1] + m=re.search(r'workspace=(\d+) layout=(\d+) scroll=(-?[\d.]+) tile_scene=(-?\d+),(-?\d+) clip_valid=(\d+) clip=(-?\d+),(-?\d+),(\d+),(\d+)',line);assert m,line + v=list(map(float,m.groups())) + return {'workspace':int(v[0]),'layout_enum':int(v[1]),'scroll':v[2], + 'tile_local':v[3:5],'clip_valid':bool(v[5]),'clip_local':v[6:10], + 'global_clip':[(-800 if output=='HEADLESS-1' else 0)+v[3]+v[6],v[4]+v[7],v[8],v[9]],'raw':line} + +def camera(tree):return workspace(tree,'1')['mosaic']['rows'][0]['scroll_x'] +def buttons(name):return [x for x in log(name).splitlines() if x.startswith('BUTTON ')] +def measured_click(kind,point): + before={n:len(buttons(n)) for n in ['target','reference','filler']} + inject('right',*point,kind=kind) + row={'global_point':point,'events':{n:buttons(n)[before[n]:] for n in before}, + 'tails':{n:log(n).splitlines()[-15:] for n in before},'ns':time.monotonic_ns()} + save(lab,kind,row);return row + +def whole(tag,baseline,im): + value=compare(baseline,im);save(lab,tag,value);assert value['equal'],'whole H2 differs: '+tag + return value + +try: + compositor=spawn('compositor',[binary,'-d','-c',run/'config']) + sock=wait(lambda:next(iter(runtime.glob('sway-ipc.*.sock')),None)) + display=wait(lambda:next((x for x in runtime.glob('wayland-*') if x.is_socket()),None)) + env.update(SWAYSOCK=str(sock),WAYLAND_DISPLAY=display.name) + maps=Path(f'/proc/{compositor.pid}/maps').read_text();(run/'proc-maps.txt').write_text(maps) + exe=Path(f'/proc/{compositor.pid}/exe').resolve();assert exe==binary and sha(exe)==sha(binary) + paths=sorted({line.split()[-1] for line in maps.splitlines() if len(line.split())>=6 and line.split()[-1].startswith('/')}) + wl=Path('/usr/lib/libwlroots-0.20.so').resolve() + assert not any('libscenefx' in x for x in paths) + assert [Path(x).resolve() for x in paths if 'libwlroots' in x]==[wl] + assert sha(wl)=='7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837' + assert not any('/swayfx-mosaic-' in x and Path(x)!=binary for x in paths) + save(lab,'dependency-mappings',{'status':'PASS','exe':str(exe),'paths':paths,'wlroots_sha256':sha(wl),'SceneFX_shared':False}) + outputs=ipc('-t','get_outputs');actual={o['name']:(o['rect']['x'],o['rect']['y'],o['rect']['width'],o['rect']['height'],o['scale'],o['transform']) for o in outputs if o['active']} + assert actual=={'HEADLESS-1':(-800,0,800,600,1.0,'normal'),'HEADLESS-2':(0,0,800,600,1.0,'normal')},actual + save(lab,'output-geometry',outputs) + pointer=spawn('pointer',[lab/'pointer-daemon','1600','600'],stdin=True);wait(lambda:'POINTER_READY' in log('pointer')) + cmd('workspace 2');ref=spawn('reference',[lab/'static-reference'],stdin=True,extra={'WAYLAND_DEBUG':'1'}) + reference=wait(lambda:find('org.mosaic.StaticReference'));refid=reference['id'] + cmd(f'[con_id={refid}] border none') + cmd('workspace 1');filler=spawn('filler',[lab/'static-reference'],stdin=True) + wait(lambda:len([n for n in nodes(ipc('-t','get_tree')) if n.get('app_id')=='org.mosaic.StaticReference'])==2) + target=spawn('target',[lab/'saved-client'],stdin=True,extra={'WAYLAND_DEBUG':'1'}) + wait(lambda:find('org.mosaic.SavedBufferProbe')) + cmd('layout mosaic');time.sleep(.6) + tree=snapshot('before-camera');owner=find('org.mosaic.SavedBufferProbe');targetid=owner['id'];wsid=workspace(tree,'1')['id'] + before=measure(owner);sc_before=scene('HEADLESS-1');scroll_before=camera(tree) + assert sc_before['scroll']==scroll_before + x,w=before['horizontal_content'];desired=-w//2;delta=x-desired + reserve(lab,'camera');cmd(f'layout mosaic scroll {delta}') + time.sleep(.6);owner=find('org.mosaic.SavedBufferProbe');after=measure(owner) + tree=snapshot('preflight');scroll_after=camera(tree);sc_after=scene('HEADLESS-1') + save(lab,'camera-calibration',{'before':scroll_before,'delta':delta,'desired_left':desired,'after':scroll_after,'before_scene':sc_before,'after_scene':sc_after}) + assert scroll_after==scroll_before+delta and sc_after['scroll']==scroll_after + x,w=after['horizontal_content'];assert x<00 and forbidden['count']==0 + whole('v1-H2-comparison',baseline,right) + bx,by,bw,bh=visible['bbox'];assert bx-800==x and bw==800 + last_commit=[s for s in log('target').splitlines() if s.startswith('COMMIT ')][-1] + dimensions=re.search(r'size=(\d+)x(\d+)',last_commit);assert dimensions + assert int(dimensions[1])==w and bh-by==int(dimensions[2]) + # Paint proves vertical position; only horizontal continuation is inferred + # from the applied scene, IPC offset and actual configured client width. + px,py=interior(left);permitted=(px-800,py) + forbidden_point=(min(100,int(x+w)-30),py);assert forbidden_point[0]>=20 + assert x= LIMITS[kind]: + raise RuntimeError('budget exhausted or changed: '+kind) + state['used'][kind] += 1 + state['events'].append({'sequence': len(state['events']), 'kind': kind}) + fd, temporary = tempfile.mkstemp(prefix='.budget-', dir=path.parent) + with os.fdopen(fd, 'w') as out: + json.dump(state, out, indent=2); out.write('\n'); out.flush(); os.fsync(out.fileno()) + os.replace(temporary, path) + fd = os.open(path.parent, os.O_RDONLY | os.O_DIRECTORY) + try: + os.fsync(fd) + finally: + os.close(fd) + return state + +def environment(): + env = {k:v for k,v in os.environ.items() if k not in ( + 'SWAYSOCK','I3SOCK','WAYLAND_DISPLAY','DISPLAY','XDG_RUNTIME_DIR', + 'LD_LIBRARY_PATH','LD_PRELOAD','PKG_CONFIG_PATH')} + env.update(LC_ALL='C', PYTHONDONTWRITEBYTECODE='1', MESON_ROOT_CMD='/bin/false') + return env + +def command(lab, label, argv, cwd=None, extra=None, timeout=600, allow_failure=False): + lab=Path(lab); path=lab/'raw'/(label+'.log') + assert not path.exists(), 'diagnostic already recorded: '+label + with path.open('wb') as out: + proc=subprocess.Popen(list(map(str,argv)),cwd=cwd,env={**environment(),**(extra or {})}, + stdout=out,stderr=subprocess.STDOUT,start_new_session=True) + try: + deadline=time.monotonic()+timeout + while proc.poll() is None: + if time.monotonic()>=deadline or path.stat().st_size>64*1024*1024: + raise subprocess.TimeoutExpired(argv,timeout) + time.sleep(.1) + except subprocess.TimeoutExpired: + os.killpg(proc.pid,signal.SIGTERM) + try: proc.wait(timeout=3) + except subprocess.TimeoutExpired: + os.killpg(proc.pid,signal.SIGKILL);proc.wait() + try: os.killpg(proc.pid,signal.SIGKILL) + except ProcessLookupError: pass + raise + with (lab/'raw/commands.jsonl').open('a') as out: + out.write(json.dumps({'label':label,'argv':list(map(str,argv)), 'cwd':str(cwd), + 'env':extra or {},'exit':proc.returncode})+'\n') + assert path.stat().st_size < 64*1024*1024, 'output bound exceeded' + if not allow_failure and proc.returncode: + raise RuntimeError(f'{label} exit={proc.returncode}; inspect {path}') + return path.read_text(errors='replace') diff --git a/tooling/probes/step20c-static-runtime-followup/test-budget.py b/tooling/probes/step20c-static-runtime-followup/test-budget.py new file mode 100644 index 0000000..072291a --- /dev/null +++ b/tooling/probes/step20c-static-runtime-followup/test-budget.py @@ -0,0 +1,25 @@ +"""Offline exhaustion and cross-process reservation tests, outside campaign LAB.""" +import concurrent.futures +import json +from pathlib import Path +import tempfile +from support import LIMITS, reserve + +def attempt(args): + lab, kind = args + try: reserve(lab,kind); return True + except RuntimeError: return False + +if __name__ == '__main__': + with tempfile.TemporaryDirectory(prefix='static-budget-test-') as tmp: + lab=Path(tmp);(lab/'results').mkdir() + with concurrent.futures.ProcessPoolExecutor(max_workers=4) as pool: + assert sum(pool.map(attempt,[(lab,'capture')]*20))==12 + for kind,limit in LIMITS.items(): + if kind=='capture':continue + for _ in range(limit):assert attempt((lab,kind)) + assert not attempt((lab,kind)) + state=json.loads((lab/'results/budget.json').read_text()) + assert state['used']==LIMITS + assert not attempt((lab,'run')) + print('PASS offline ledger: concurrent reservations, exhaustion, durable failures, no refund') diff --git a/tooling/probes/step20c-static-runtime-followup/test-offline.py b/tooling/probes/step20c-static-runtime-followup/test-offline.py new file mode 100644 index 0000000..f4c81b9 --- /dev/null +++ b/tooling/probes/step20c-static-runtime-followup/test-offline.py @@ -0,0 +1,18 @@ +"""Exercise scoring against edge differences, actual paint and button events.""" +from PIL import Image +from pixels import analyze, compare, interior, button_pair +x=Image.new('RGB',(800,600),(48,48,56));y=x.copy() +assert compare(x,y)['equal'];y.putpixel((799,599),(48,48,57)) +r=compare(x,y);assert not r['equal'] and r['changed']==1 and r['pixels'][0]['x']==799 +assert analyze(x)['count']==0 +paint=Image.new('RGB',(800,600),(0,0,0)) +for xx in range(482,800): + for yy in range(25,598):paint.putpixel((xx,yy),(35,68,204)) +assert analyze(paint)=={'count':182214,'bbox':(482,25,800,598)} +assert interior(paint)==(641,311) +assert len(button_pair(['BUTTON ns=1 serial=3 button=273 state=1','BUTTON ns=2 serial=4 button=273 state=0']))==2 +for lines in [[],['ENTER ns=1 x=0 y=0'],['BUTTON ns=1 serial=3 button=273 state=1']]: + try:button_pair(lines) + except AssertionError:pass + else:raise AssertionError('missing delivery accepted') +print('PASS exact RGB edge rejection, painted interior and real press/release scoring')