docs(mosaic): preserve static RGB input and standard return evidence
This commit is contained in:
parent
fb0293cd85
commit
d0fa8c5c9e
17 changed files with 1569 additions and 0 deletions
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
### STEP20C experimental evidence
|
||||
|
||||
- Preserved independent static RGB/input/standard retest: corrected shared predicate and real replay pass; exact whole-H2 RGB, H1 retained/H2 forbidden pixels, real target/reference clicks and targeted splith clip removal/input pass. FEASIBLE for this matrix. Terminal exit IPC makes runner exit 1 after measured gates and compositor exit 0; raw failure retained and offline diagnosis passes. No new compositor/library build or production change.
|
||||
|
||||
- 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.
|
||||
|
|
|
|||
|
|
@ -416,3 +416,17 @@ 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.
|
||||
|
||||
The [static RGB/input/standard retest](design/STEP20C_STATIC_RGB_INPUT_STANDARD_RETEST.md)
|
||||
is **EXPERIMENTAL / FEASIBLE for the tested matrix**; its evidence is frozen
|
||||
for publication.
|
||||
The corrected shared IPC predicate/replay pass before the sole unchanged-static-
|
||||
binary run. Two full H2 references and all later H2 RGB are exact; H1 retains
|
||||
182,214 target pixels, H2 zero, actual visible/reference/standard button pairs
|
||||
are delivered only to the right client. Targeted splith removes the native clip
|
||||
and retains ordinary pixels/input. The runner exits 1 only on terminal exit IPC
|
||||
after completed measurements and compositor exit 0; that raw failure is retained
|
||||
and separately diagnosed/revalidated offline. No algorithm, build, production,
|
||||
package or UX change follows. Wider static popup/effect/lifecycle, complete
|
||||
damage/generation, ABI/maintenance/notices gates remain open. D1–D4 persist;
|
||||
STEP20C stays NEXT and production multi-output remains NOT YET SUPPORTED.
|
||||
|
|
|
|||
|
|
@ -357,3 +357,17 @@ 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.
|
||||
|
||||
The [static RGB/input/standard retest](design/STEP20C_STATIC_RGB_INPUT_STANDARD_RETEST.md)
|
||||
is **EXPERIMENTAL / FEASIBLE for the tested matrix**; its evidence is frozen
|
||||
for publication.
|
||||
The corrected shared IPC predicate/replay pass before the sole unchanged-static-
|
||||
binary run. Two full H2 references and all later H2 RGB are exact; H1 retains
|
||||
182,214 target pixels, H2 zero, actual visible/reference/standard button pairs
|
||||
are delivered only to the right client. Targeted splith removes the native clip
|
||||
and retains ordinary pixels/input. The runner exits 1 only on terminal exit IPC
|
||||
after completed measurements and compositor exit 0; that raw failure is retained
|
||||
and separately diagnosed/revalidated offline. No algorithm, build, production,
|
||||
package or UX change follows. Wider static popup/effect/lifecycle, complete
|
||||
damage/generation, ABI/maintenance/notices gates remain open. D1–D4 persist;
|
||||
STEP20C stays NEXT and production multi-output remains NOT YET SUPPORTED.
|
||||
|
|
|
|||
|
|
@ -275,3 +275,17 @@ 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.
|
||||
|
||||
The [static RGB/input/standard retest](design/STEP20C_STATIC_RGB_INPUT_STANDARD_RETEST.md)
|
||||
is **EXPERIMENTAL / FEASIBLE for the tested matrix**; its evidence is frozen
|
||||
for publication.
|
||||
The corrected shared IPC predicate/replay pass before the sole unchanged-static-
|
||||
binary run. Two full H2 references and all later H2 RGB are exact; H1 retains
|
||||
182,214 target pixels, H2 zero, actual visible/reference/standard button pairs
|
||||
are delivered only to the right client. Targeted splith removes the native clip
|
||||
and retains ordinary pixels/input. The runner exits 1 only on terminal exit IPC
|
||||
after completed measurements and compositor exit 0; that raw failure is retained
|
||||
and separately diagnosed/revalidated offline. No algorithm, build, production,
|
||||
package or UX change follows. Wider static popup/effect/lifecycle, complete
|
||||
damage/generation, ABI/maintenance/notices gates remain open. D1–D4 persist;
|
||||
STEP20C stays NEXT and production multi-output remains NOT YET SUPPORTED.
|
||||
|
|
|
|||
|
|
@ -361,3 +361,17 @@ 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.
|
||||
|
||||
The [static RGB/input/standard retest](design/STEP20C_STATIC_RGB_INPUT_STANDARD_RETEST.md)
|
||||
is **EXPERIMENTAL / FEASIBLE for the tested matrix**; its evidence is frozen
|
||||
for publication.
|
||||
The corrected shared IPC predicate/replay pass before the sole unchanged-static-
|
||||
binary run. Two full H2 references and all later H2 RGB are exact; H1 retains
|
||||
182,214 target pixels, H2 zero, actual visible/reference/standard button pairs
|
||||
are delivered only to the right client. Targeted splith removes the native clip
|
||||
and retains ordinary pixels/input. The runner exits 1 only on terminal exit IPC
|
||||
after completed measurements and compositor exit 0; that raw failure is retained
|
||||
and separately diagnosed/revalidated offline. No algorithm, build, production,
|
||||
package or UX change follows. Wider static popup/effect/lifecycle, complete
|
||||
damage/generation, ABI/maintenance/notices gates remain open. D1–D4 persist;
|
||||
STEP20C stays NEXT and production multi-output remains NOT YET SUPPORTED.
|
||||
|
|
|
|||
377
docs/design/STEP20C_STATIC_RGB_INPUT_STANDARD_RETEST.md
Normal file
377
docs/design/STEP20C_STATIC_RGB_INPUT_STANDARD_RETEST.md
Normal file
|
|
@ -0,0 +1,377 @@
|
|||
# STEP20C static RGB, input and standard-return retest
|
||||
|
||||
**EXPERIMENTAL / LOCAL / UNCOMMITTED. Protocol before fixture development.**
|
||||
Phase A froze the historical failure in
|
||||
`fb0293cd855a2ec917c14df3ecf3504c064499fc`, published to canonical Forgejo
|
||||
and GitHub with a clean worktree. This independent phase B is never committed
|
||||
or published. [Architecture](../ARCHITECTURE.md), [UX](../UX.md),
|
||||
[Roadmap](../ROADMAP.md) and [Packaging](../PACKAGING.md) remain authoritative.
|
||||
D1–D4 persist. STEP20A DONE; STEP20B EXPERIMENTAL; STEP20C NEXT; STEP20D–G
|
||||
PLANNED. Production multi-output remains NOT YET SUPPORTED.
|
||||
|
||||
## Historical error and mandatory offline gates
|
||||
|
||||
The [frozen follow-up](STEP20C_STATIC_RUNTIME_FOCUS_INPUT_STANDARD_PROBE.md)
|
||||
stopped on an invalid `current_border_width == 0` assertion, before any PNG,
|
||||
click or standard return. Its verdict remains INCONCLUSIVE / UNDECIDED and
|
||||
its run/camera budget is exhausted. Neither its runner nor manifests change.
|
||||
|
||||
Pinned SwayFX `fd71a6bdc061bd633b488ae7b83e8a6981d22f86` border.c sets
|
||||
B_NONE and only changes thickness with an optional second argument. The exact
|
||||
candidate's ipc-json.c reports stored current.border_thickness; transaction.c
|
||||
sets effective B_NONE border width/top to zero. Therefore width 2 is accepted
|
||||
as recorded metadata under border none. A shared pure predicate validates the
|
||||
actual IPC object, identity, ancestors, visibility, border mode, empty deco_rect,
|
||||
full rect/window_rect and exact H2 geometry. The runner imports the same function
|
||||
as the offline tests. No parallel implementation is permitted.
|
||||
|
||||
Read-only replay uses the sealed
|
||||
`/home/fy59/swayfx-mosaic-static-followup-nhwejz6u/results/preflight-snapshot.json`,
|
||||
scene traces, client logs and earlier real IPC observations. Historical IDs
|
||||
7/8/9 and PIDs are used only in replay. Negative tests alter explicit copies:
|
||||
wrong App ID, PID, ID, workspace, output, visibility, border, deco, absent view,
|
||||
window/content/output geometry, focus, camera and clip must be rejected.
|
||||
An actual earlier border-normal reference must also fail.
|
||||
|
||||
`STATIC_REFERENCE_IPC_REPLAY=PASS` and `STATIC_FIXTURE_PREFLIGHT_REPLAY=PASS`
|
||||
are mandatory before any run reservation. Historical PNGs from the older static
|
||||
runtime may validate pixel/coordinate code, clearly labelled HISTORICAL; the
|
||||
failed follow-up contains no PNG. Synthetic button fixtures are explicitly
|
||||
SYNTHETIC, never new input evidence. Tests check exact BTN_RIGHT=273 press/release,
|
||||
positive real-format serials, timestamps/order, event count, malformed lines,
|
||||
wrong recipient and absence in both other clients. No events may be silently
|
||||
discarded by the button parser.
|
||||
|
||||
## Assertion authority and remaining measurement
|
||||
|
||||
| Field / assertion | Source and meaning | Offline evidence / live requirement |
|
||||
| --- | --- | --- |
|
||||
| outputs rect/scale/transform | ipc-json output description, logical coordinates | actual snapshots, wrong-output negatives; reread live |
|
||||
| App ID/PID/con_id/ancestors | real view/node and tree ownership | actual reference/target/filler; live IDs discovered by PID |
|
||||
| visible/focused/workspaces | view_is_visible and default-seat focus; not pixel coverage | actual tree plus get_workspaces; sample live |
|
||||
| border/width | current border enum and stored thickness | B_NONE width 2 accepted; normal rejected |
|
||||
| rect/deco_rect/window_rect | node box with title removed; decoration and content-relative box | actual full H2, empty deco; pixels still require capture |
|
||||
| scroll_x | Mosaic row state, may precede applied scene | compare IPC with native PI_SCENE after settling |
|
||||
| PI_OWNER scene/applied_size | wlr_scene_node_coords, animation current size | parse actual target trace; never use IPC rect as buffer y |
|
||||
| PI_SCENE local/global clip | applied tiling tree, private get_clip | output origin + tiling translation + local clip equals viewport |
|
||||
| client COMMIT size/color | exact C17 SHM draw, configure/ACK loop | actual logs; target horizontal width compared with IPC content |
|
||||
| SHM colors | XRGB8888 target 0x2344cc/0x20d0a0; reference 0x303038 | source and historical PNG; live captures required |
|
||||
| capture coordinates | scale 1/normal, local x=global x-output origin | signed-coordinate tests and measured pixels; no approximate native y |
|
||||
| click point | 41×41 actual-color interior, margin 20; H2 continuation | historical image and negative sparse fixtures; recompute live |
|
||||
| BUTTON log | real pointer callback, ns/serial/button/state | positive/negative parser tests; live delivery mandatory |
|
||||
| splith criteria owner | layout.c uses matched container workspace; Mosaic clears parent target | exact candidate source audited; standard trace/pixels/input live |
|
||||
|
||||
The machine-readable assertion audit records each source location, historical
|
||||
input, replay result and uncertainty. Assertions depending on new RGB/input or
|
||||
standard-return observations remain explicitly live requirements; historical
|
||||
or synthetic success cannot qualify them. No presentation hypothesis is an
|
||||
invariant. True output/ownership/input/clip invariants remain required.
|
||||
|
||||
## Exact candidate, isolation and immutable runner
|
||||
|
||||
Reuse only
|
||||
`/home/fy59/swayfx-mosaic-static-runtime-hm5kbdkt/stage-static/usr/bin/swayfx-mosaic`:
|
||||
SHA-256 `f82557c1a9978a5e9665b9b4291b74880253725359e3df1c5b8d554921f7603b`,
|
||||
build ID `942db64269640778ee433e5970ba1100bc60f94c`.
|
||||
Check regular executable, ELF64 x86-64, private clip get/set ordinary ELF definitions (not necessarily .dynsym exports), DT_NEEDED,
|
||||
no SceneFX shared dependency, ldd -r and the exact system wlroots SHA-256
|
||||
`7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837`.
|
||||
Copy unchanged into one new campaign-owned laboratory outside Git. Compile
|
||||
only the three historical C17 clients from manifest-verified exact C/protocol
|
||||
sources; retain sources, commands and binary hashes. No SceneFX/Mosaic build.
|
||||
|
||||
The prepared laboratory has one exclusive owner file and persistent flock
|
||||
ledger. Offline gates, source audit, AST, C17, library identity, ledger tests,
|
||||
documentation and the exact runner inventory must all pass before reservation.
|
||||
Freeze hashes of the executed fixture and verify them immediately before run.
|
||||
Do not edit that copy afterward. Reports/collector receipts are separate.
|
||||
|
||||
Private runtime mode 0700, headless backend, private WAYLAND_DISPLAY and
|
||||
explicit private swaymsg -s only. Strip inherited desktop sockets/library
|
||||
overrides. Own process groups, bounded logs and 150-second watchdog. Retain
|
||||
logs/exit codes before removing only owned sockets/runtime. No daily IPC.
|
||||
|
||||
## One-run ordered protocol
|
||||
|
||||
HEADLESS-1=(-800,0,800,600), HEADLESS-2=(0,0,800,600), scale 1, normal.
|
||||
No popup, rotation, fractional scaling, resize, migration, saved-buffer scenario
|
||||
or added effect. Set focus_follows_mouse no. Create H2 static reference, discover
|
||||
its real PID/con_id, and apply only `[con_id=ID] border none`. H1 filler shares
|
||||
its App ID; never apply a global App ID rule. Create H1 target and establish
|
||||
Mosaic with target/workspace 1 focused before any H2 reference PNG.
|
||||
|
||||
Measure applied scene and client content width. Compute desired left=-width/2
|
||||
and signed camera delta from that actual state, reserve once and apply. Do not
|
||||
hard-code +160. Confirm IPC/applied scroll, owner crossing x=0, workspace identity
|
||||
and global clip [-800,0,800,600]. No second adjustment if later focus moves it.
|
||||
|
||||
Park pointer on H1 (-790,590), stabilize and capture H2 twice at least 500 ms
|
||||
apart. Require 800×600, fixed #303038 throughout and exact full RGB equality;
|
||||
retain PNG hashes. No tolerance, crop or excluded ROI. Difference stops all
|
||||
dependent tests and records every differing pixel.
|
||||
|
||||
Capture H1/H2 Mosaic. Require positive measured target paint on H1, continuation
|
||||
across x=0, zero target colors on H2 and full H2 RGB equality. Client COMMIT,
|
||||
painted bbox and scene-derived horizontal continuation are separate observations.
|
||||
Select visible click from actual target-colored H1 pixels with 20-pixel margins.
|
||||
Select H2 point in geometric continuation, within the actual full reference
|
||||
surface, at least 20 pixels from borders. One right press/release to visible
|
||||
target; exact client traces, no delivery to other clients. One right click to H2;
|
||||
zero new target/filler events, actual reference pair, record focus and camera.
|
||||
Park pointer on H1, recapture whole H2 and require exact equality after focus.
|
||||
|
||||
Send `[con_id=targetID] layout splith`, even when global focus is H2. Require
|
||||
same H1 workspace, actual splith, mapped target, absent H1/H2 tiling clips,
|
||||
ordinary target pixels and zero forbidden target pixels on H2. Capture both
|
||||
outputs and compare whole H2. Recompute standard target interior; third right
|
||||
click must deliver real target pair only. Park pointer, capture final H2 and
|
||||
check focus-transition RGB. No reuse of Mosaic point without measurement.
|
||||
|
||||
## Budget, classification and preservation
|
||||
|
||||
Limits: static run 1, pristine 0, Mosaic build 0, SceneFX build 0, camera 1,
|
||||
right clicks 3 (one of each named kind), movements 8, PNG 12, duration 150 s.
|
||||
Every click also moves the pointer and consumes a movement reservation. Reserve
|
||||
before run/capture/click/camera/move; failed work consumes reservations, no reset,
|
||||
refund or second laboratory. Expected PNG 8, movement 6 including click motions.
|
||||
|
||||
FEASIBLE requires all exact candidate/replay/reference/Mosaic/input/standard
|
||||
gates and no crash/protocol blocker in this precise matrix. PARTIALLY_FEASIBLE
|
||||
retains demonstrated properties with open paths. INCONCLUSIVE denotes fixture
|
||||
measurement failure. BLOCKED requires measured clipping/input/ABI/standard
|
||||
defect, not an invalid fixture assertion. Architecture is respectively
|
||||
STATIC_MOSAIC_RGB_INPUT_STANDARD_FEASIBLE_FOR_TESTED_MATRIX,
|
||||
STATIC_MOSAIC_RGB_INPUT_STANDARD_REQUIRES_REDESIGN, or UNDECIDED.
|
||||
|
||||
Retain exact fixture/client/config, raw PNG/IPC/protocol/client/scene logs,
|
||||
timestamps, serials, library mappings and exit codes outside Git. Produce all
|
||||
requested result JSON and seal source/evidence inventories once, excluding
|
||||
manifests themselves. Verify every entry read-only. Damage extents, client
|
||||
damage requests and final PNGs do not prove intermediate frames or native
|
||||
output-buffer identity. Wider geometry, popup/grabs, lifecycle/effects/buffers,
|
||||
applied-generation consistency, complete ABI/maintenance/notices remain open.
|
||||
No algorithm change or STEP20C integration follows automatically.
|
||||
|
||||
The [source-only fixture](../../tooling/probes/step20c-static-rgb-input-retest/README.md)
|
||||
contains replay, predicates, ledger, runner, pixel analysis and collector.
|
||||
|
||||
## Preparation incident before run reservation
|
||||
|
||||
The first preparation in the single new LAB stopped because the checker used
|
||||
`nm -D` for private static clip functions. The exact historical symbol receipt
|
||||
qualifies ordinary executable T definitions, and `nm` confirms both. This is
|
||||
a checker error, not a missing function or ABI failure. Preserve the original
|
||||
checker and dynamic-symbol log; continue only that same LAB's pre-run preparation,
|
||||
with no budget reservation/reset and no compositor launch. The corrected gate
|
||||
uses ordinary ELF symbols. The initial checker and incident are evidence files;
|
||||
the subsequently frozen/executed runner is explicitly distinct.
|
||||
|
||||
## Completed single campaign — 2026-10-13
|
||||
|
||||
`SWAYFX_MOSAIC_STATIC_RGB_INPUT_STANDARD_PROBE=FEASIBLE`
|
||||
|
||||
Architecture:
|
||||
**STATIC_MOSAIC_RGB_INPUT_STANDARD_FEASIBLE_FOR_TESTED_MATRIX**.
|
||||
This is a scientific verdict on the requested static, tiled, two-output matrix;
|
||||
it does not mean the runner command exited zero, nor close STEP20C production.
|
||||
LAB `/home/fy59/swayfx-mosaic-static-rgb-input-retest-hn67lnhq`.
|
||||
Run duration 9.142914 seconds, one run, no retry. No pristine run, no new
|
||||
SceneFX/Mosaic build, no binary/shader/algorithm/production/package/UX change.
|
||||
|
||||
### Candidate, clients and offline gates
|
||||
|
||||
The original and copied executable both retain SHA-256
|
||||
`f82557c1a9978a5e9665b9b4291b74880253725359e3df1c5b8d554921f7603b`
|
||||
and build ID `942db64269640778ee433e5970ba1100bc60f94c`, regular executable
|
||||
mode 0755, ELF64 x86-64. Ordinary ELF clip get/set T definitions pass; these
|
||||
private static definitions need not appear in .dynsym. No SceneFX DT_NEEDED,
|
||||
RPATH/RUNPATH, unresolved relocation or loaded shared SceneFX exists. Actual
|
||||
process maps identify the unchanged system wlroots with SHA-256
|
||||
`7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837`.
|
||||
The historical full source/evidence inventories and follow-up 24/46-entry
|
||||
inventories were verified read-only. No historical manifest was rewritten.
|
||||
|
||||
Three C17 client-only syntax checks and builds passed from exact verified
|
||||
historical C/generated-protocol inputs; commands are in raw/commands.jsonl:
|
||||
|
||||
| Client | Result executable SHA-256 |
|
||||
| --- | --- |
|
||||
| saved-client | `3d5d0c5e446b995c210006a5bafe0d86dd5f32a228e1b7137d12af6ec9e217da` |
|
||||
| static-reference | `11166ea1bb7cb3bb08f10ab93e091c5c5ea8687649b5fb0705288c7ca91680e8` |
|
||||
| pointer-daemon | `76ad0f093f2b7a97359920b7bd456cfa93a1581c73875547a86eb911f2ed2d7a` |
|
||||
|
||||
The corrected shared predicate accepted the actual historical H2 node with
|
||||
border none / stored width 2, full window_rect and zero deco_rect. Thirty-nine
|
||||
negative cases rejected wrong identities/ownership/geometry/focus/clip, the
|
||||
actual filler, an actual earlier normal-border reference, sparse/inaccessible
|
||||
pixel fixtures and invalid synthetic button sequences/recipients. Historical
|
||||
PNG and synthetic input observations remain explicitly labelled. Both required
|
||||
replay markers passed, with AST, shell recipes, links and durable concurrent/
|
||||
exhausted/corrupt-ledger tests before reservation.
|
||||
|
||||
The exact runner SHA-256 is
|
||||
`07ce1f98c9cb35ddb5219aff3991f19321759ac4edb26ab6d354096a853979d6`.
|
||||
The pre-run fixture inventory (10 source files plus protocol) has 11 entries,
|
||||
SHA-256 `294c001b5e88d7759255afb59646a386a59e2273f21e19c5a29512a4230f56a2`.
|
||||
The executed copy remains immutable. The live runner imports the same validators
|
||||
as replay. No width-zero assertion was relaxed after launching the compositor.
|
||||
|
||||
### Applied ownership, border, camera and clip
|
||||
|
||||
Both real outputs match (-800,0,800,600) / (0,0,800,600), scale 1, normal.
|
||||
Reference con_id 7/PID 1700453 belonged to workspace 2 (ID 5), H2. Filler ID 8/
|
||||
PID 1700459 kept normal border; target ID 9/PID 1700462 belonged to workspace 1
|
||||
(ID 6), H1. These IDs were discovered at runtime, never hard-coded in the runner.
|
||||
Target/workspace 1 initially focused; reference unfocused. Only `[con_id=7]
|
||||
border none` affected H2. IPC retained width 2, rect/window_rect 800×600 and
|
||||
zero deco_rect; full RGB then independently established absent rendered decoration.
|
||||
|
||||
Applied scroll initially 0. Actual target horizontal content left -158, width
|
||||
636, desired left -318 gave the sole signed correction +160. This was computed
|
||||
again from actual state, not borrowed as a constant. IPC and scene both read
|
||||
160. Owner scene (-320,0,640,600), content continuation [-318,318), actual client
|
||||
COMMIT 636×573. Tile-local translation (-160,0), local clip (160,0,800,600), H1
|
||||
origin (-800,0) yield global clip (-800,0,800,600). H2 tiling clip is absent.
|
||||
Native buffer y was never inferred from window_rect.y.
|
||||
|
||||
### Whole RGB and measured input
|
||||
|
||||
The two 800×600 H2 references, captured 600 ms apart after H1 focus/setup and
|
||||
pointer parking, are entirely #303038 and exact RGB equals. Every subsequent
|
||||
H2 PNG is also entirely equal: Mosaic, after H2 click/focus, standard layout,
|
||||
and after standard target click/focus. Six H2 PNGs share SHA-256
|
||||
`4471e93ccb6cf3ff4970f61fce405431a228af598d2f1f030a3b7586962ecd7a`.
|
||||
Five independent whole-frame comparisons each have zero changed pixels; no
|
||||
crop, excluded ROI, RGB tolerance or focus compensation.
|
||||
|
||||
H1 Mosaic contains 182,214 target-colored pixels, solid painted box local
|
||||
[482,25,800,598), global x [-318,0), against continuation [-318,318).
|
||||
H2 has zero target-colored pixels plus independently exact whole RGB.
|
||||
H1 Mosaic PNG SHA-256:
|
||||
`1ca64ee4aac80dd8377609528598608a412c1169d75bd4bce93b394292919ab8`.
|
||||
|
||||
| Actual injection | Recipient and real serials | Other client buttons | Focus afterward |
|
||||
| --- | --- | --- | --- |
|
||||
| Mosaic visible (-159,311) | target, BTN_RIGHT 273 press 35/release 36 | reference/filler none | target 9, workspace 1 |
|
||||
| Mosaic continuation on H2 (100,311) | reference, press 48/release 49 | target/filler none | reference 7, workspace 2 |
|
||||
| Standard recomputed (-200,311) | target, press 67/release 68 | reference/filler none | target 9, workspace 1 |
|
||||
|
||||
Each point uses measured captured pixels and a 41×41 interior neighborhood;
|
||||
H2 also lies within the un-clipped target's measured horizontal continuation.
|
||||
Client logs retain ns timestamps, real serials, enter/motion and protocol traces.
|
||||
All target logs contain exactly four button lines (visible + standard pairs),
|
||||
reference exactly two, filler zero. Camera remains 160 after both Mosaic clicks.
|
||||
Reference focus changes without any H2 RGB difference.
|
||||
|
||||
After reference focus, `[con_id=9] layout splith` keeps H1 workspace ID 6,
|
||||
changes its actual layout to splith and leaves the target mapped. commands.c
|
||||
binds handler context to matched container.pending.workspace; layout.c clears
|
||||
container targeting when that workspace is Mosaic. This source audit preceded
|
||||
execution. Native PI_SCENE reports layout enum 1, tile translation (0,0),
|
||||
clip_valid=0 on both H1 and H2. H1 standard has 226,908 target pixels, local
|
||||
[402,25,798,598), configured 396×573, with zero target pixels on H2. H1 standard
|
||||
PNG SHA-256 `195c4faea3020af31819214346772339f62a34c8a82386afb3176ddb9391ee1a`.
|
||||
The standard click point was recalculated from this image. Actual standard input,
|
||||
clip removal, layout, ownership and rendered pixels pass together.
|
||||
|
||||
### Terminal fixture incident, preserved raw status
|
||||
|
||||
The executed runner's final `swaymsg -s PRIVATE exit` returned 1 after all
|
||||
measurements and `standard-return.json` had been written. It therefore exited
|
||||
1 and wrote runtime.status=INCONCLUSIVE. Those bytes/statuses are preserved,
|
||||
not changed to PASS. The compositor log shows Handling command exit, orderly
|
||||
shutdown and compositor code 0. All three clients exited 0, logging errno 32
|
||||
(EPIPE) only after the final capture; pointer was owned and terminated -15.
|
||||
No wl_display.error protocol event or pre-measurement transport failure occurs.
|
||||
|
||||
Exact source is compatible with the terminal observation: cmd_exit invokes
|
||||
sway_terminate(0), which terminates the display event loop; ipc_send_reply queues
|
||||
its write for a later writable callback. Closing the compositor may remove the
|
||||
socket before swaymsg reads that reply. This is an observed terminal fixture
|
||||
incident, not a clipping/input/standard-layout failure. Loss of the queued IPC
|
||||
response is a compatible explanation, not an independently observed packet
|
||||
trace or a universal assertion about every exit command. Its exact response error
|
||||
text was not retained separately because subprocess.check=True raised; the
|
||||
CalledProcessError, command/code and compositor/client logs remain available.
|
||||
|
||||
A separate post-run validator rechecks actual PNGs, IPC/scene, recipient event
|
||||
lists, focus, clip absence, budget, library identity and cleanup without any
|
||||
runtime invocation. Negative post-run cases reject abnormal compositor exit,
|
||||
retained standard clip, invisible-target delivery and even one changed H2 pixel.
|
||||
Only all measured gates plus this narrow terminal-source/log diagnosis permit
|
||||
scientific FEASIBLE. The collector and validator are retained under postrun-fixture,
|
||||
separate from immutable fixture. No new run, workaround or budget refund.
|
||||
|
||||
### Budget, damage, integrity and validation
|
||||
|
||||
| Resource | Consumed / maximum |
|
||||
| --- | --- |
|
||||
| Static runs | 1/1 |
|
||||
| Pristine runs | 0/0 |
|
||||
| SceneFX / Mosaic builds | 0/0 each |
|
||||
| Camera correction | 1/1 |
|
||||
| Right clicks | 3/3, one per named kind |
|
||||
| Pointer motions, including click positioning | 6/8 |
|
||||
| PNGs | 8/12 |
|
||||
| Duration | 9.142914 / 150 seconds |
|
||||
|
||||
Persistent ledger remains consumed. Unused moves/PNGs never authorize another
|
||||
run. The single runtime `/tmp/msr-26spytfz` is removed; all owned process groups
|
||||
are reaped and PIDs absent. No private socket remains. Protected system binary,
|
||||
wlroots/SceneFX libraries, AGENTS/UX/package/patch hashes remain unchanged.
|
||||
No daily IPC/session/QuickShell/dotfile/Codex change or STEP20D work.
|
||||
|
||||
271 TEMPORAL_DAMAGE lines cover setup, transitions and cleanup. Actual clients
|
||||
request full SHM damage on commits, and stabilized PNGs are consistent. This
|
||||
is PARTIAL damage evidence: no complete intermediate-frame coverage, native
|
||||
output-buffer/PNG identity or atomic transaction/input generation is proved.
|
||||
Popups/grabs, scaling/rotation, resize/migration, extra effects and saved-buffer
|
||||
scenarios are NOT TESTED in this new campaign. Exhaustive semantic ABI, other
|
||||
library pairs, notice/legal closure and named release/security roles remain open.
|
||||
|
||||
Final source inventory: 31 entries, SHA-256
|
||||
`cb812ae3a27c9715efce2e1b2cfd36f315ea897d003c77944f688bc931861f1e`.
|
||||
Final evidence inventory: 93 entries, SHA-256
|
||||
`209196b2857381db7a856347dcce45cd3ee33c48c2db0c73777fc982d01ad568`.
|
||||
Both sealed once and every entry verified. Final report/documentation/Git diff
|
||||
receipts are outside those inventories to avoid circular digests. Repository
|
||||
README result additions are later documentation, not the executed source copy.
|
||||
|
||||
Validation: AST, shell, Markdown links, historical manifests, IPC reference/
|
||||
preflight replay, 39 negatives, ledger concurrency/exhaustion/corruption, C17
|
||||
sources/builds, exact binary/loader/maps, actual outputs/camera/clip/pixels/buttons/
|
||||
focus/splith, protected hashes and residual-process/socket checks PASS. Run
|
||||
command FAIL at terminal exit IPC only; scientific gates PASS after independent
|
||||
artifact revalidation. Meson compile/test, ordinary headless suite and pristine
|
||||
run NOT RUN (outside this ticket and prohibited new builds/runs). Parent source,
|
||||
contract and final-delta audit performed; no independent subagent review claimed.
|
||||
|
||||
### Architectural answers and next bounded gate
|
||||
|
||||
| Question | Evidence-backed answer |
|
||||
| --- | --- |
|
||||
| Q1 corrected none/width 2? | Yes, accepted in actual historical and live snapshots; mode/deco/content still checked. |
|
||||
| Q2 other assertions replayed? | Yes for available IPC/scene/COMMIT; pixel selector uses historical PNG; buttons synthetic negatives; live-only states then actually measured. |
|
||||
| Q3 two whole references identical? | Yes, 480,000 RGB pixels, zero differences and same PNG SHA. |
|
||||
| Q4 H2 unchanged during Mosaic? | Yes, whole RGB exact. |
|
||||
| Q5 permitted H1 pixels retained? | Yes, 182,214 actual target pixels. |
|
||||
| Q6 forbidden H2 pixels absent? | Yes, zero target colors and full reference equality. |
|
||||
| Q7 visible click correct? | Yes, actual target serials 35/36 only. |
|
||||
| Q8 invisible portion excluded? | Yes, no new target/filler event on the H2 click. |
|
||||
| Q9 H2 receives click? | Yes, reference serials 48/49. |
|
||||
| Q10 focus graphical change? | No H2 change across both actual focus transitions; no claim about all compositor decorations. |
|
||||
| Q11 splith removes clip? | Yes, actual H1 workspace splith and native H1/H2 clip_valid=0. |
|
||||
| Q12 standard input? | Yes, recalculated target point, actual serials 67/68 only. |
|
||||
| Q13 static retains shared-private behavior? | Tested tiled/render/input/standard continuity is supported; exhaustive static/shared equivalence and broader effects/popups are not proved. |
|
||||
| Q14 algorithm change justified? | No reproduced clipping defect; both incidents belong to fixture checks/terminal handling. |
|
||||
| Q15 remaining integration limits? | Static popup/grab/effects/buffer continuity, wider geometry/lifecycle, complete damage/generation, exhaustive ABI/maintenance/notices/release ownership. |
|
||||
|
||||
Smallest next justified experiment: a separately authorized same-static-binary
|
||||
root/nested valid-grab popup RGB/input/standard-return continuity gate. Do not
|
||||
extend this exhausted campaign or integrate clipping in production. STEP20C
|
||||
remains NEXT and production multi-output NOT YET SUPPORTED. At the start of the evidence-freeze ticket, local main, Forgejo and GitHub
|
||||
all pointed to fb0293cd855a2ec917c14df3ecf3504c064499fc. This report and the
|
||||
source-only fixture are frozen for a single evidence-publication commit. The
|
||||
separate new static nested-grab campaign may start only after that commit is
|
||||
verified on both remotes with a clean worktree, and remains uncommitted and
|
||||
unpublished. The historical runtime and three sealed inventories are immutable.
|
||||
95
tooling/probes/step20c-static-rgb-input-retest/README.md
Normal file
95
tooling/probes/step20c-static-rgb-input-retest/README.md
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
# Static RGB, input and standard-return retest fixture
|
||||
|
||||
EXPERIMENTAL / FROZEN EVIDENCE. No production integration.
|
||||
Read the [protocol](../../../docs/design/STEP20C_STATIC_RGB_INPUT_STANDARD_RETEST.md).
|
||||
The historical failed campaign is frozen and must never be rerun.
|
||||
|
||||
`ipc_predicates.py` supplies pure validators shared by replay and the real runner.
|
||||
`replay.py` uses actual sealed historical IPC/scene/client observations and
|
||||
clearly labelled historical PNGs or synthetic input fixtures. Negative tests
|
||||
must reject wrong identities/ownership/geometry/focus/clip and malformed input.
|
||||
`pixels.py` compares every RGB pixel, preserves differences, measures actual
|
||||
paint and selects points with a 20-pixel interior margin. `support.py` and
|
||||
`test-budget.py` implement/test durable exclusive no-refund reservations.
|
||||
|
||||
Offline preparation, client-only C17 checks and gates:
|
||||
|
||||
```sh
|
||||
PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-static-rgb-input-retest/test-budget.py
|
||||
PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-static-rgb-input-retest/prepare.py
|
||||
# LAB is the one newly printed path, registered in the exclusive owner file.
|
||||
PYTHONDONTWRITEBYTECODE=1 python "$LAB/fixture/replay.py" --lab "$LAB"
|
||||
```
|
||||
|
||||
Preparation copies the exact qualified static binary, verifies the unchanged
|
||||
historical manifests, builds only exact historical clients and freezes the
|
||||
fixture inventory. No Mosaic or SceneFX build is allowed. `replay.py` verifies
|
||||
source authority, syntax and ledger tests and records the two mandatory PASS
|
||||
markers. `run.py` verifies the immutable inventory and all gates before its
|
||||
first run reservation. Never replace the owner file, reset the ledger, modify
|
||||
the executed fixture or create a second lab to retry.
|
||||
|
||||
Only after every gate passes, execute the single retained runner:
|
||||
|
||||
```sh
|
||||
PYTHONDONTWRITEBYTECODE=1 python "$LAB/fixture/run.py" "$LAB"
|
||||
PYTHONDONTWRITEBYTECODE=1 python "$LAB/fixture/collect.py" "$LAB"
|
||||
PYTHONDONTWRITEBYTECODE=1 python "$LAB/fixture/verify.py" "$LAB"
|
||||
```
|
||||
|
||||
One static run, zero pristine runs/builds, one camera correction, three named
|
||||
right clicks, eight movements including click positioning, twelve PNGs and
|
||||
150 seconds. Reservations precede work and remain consumed on failure.
|
||||
Headless/private mode-0700 runtime only, explicit swaymsg -s, owned process
|
||||
groups, bounded logs and cleanup. No inherited daily IPC or library override.
|
||||
The collector seals source/evidence once, verification never rewrites them.
|
||||
Generated C protocols, clients/binaries/builds/logs/PNG/sockets stay outside Git.
|
||||
Actual observations and final verdict are recorded below after collection.
|
||||
|
||||
## Post-run collection distinction
|
||||
|
||||
The exact executed fixture remains immutable in LAB/fixture. The measurements
|
||||
completed, but its final `swaymsg exit` returned 1 after normal compositor exit.
|
||||
Raw runtime.json and runner code/status are preserved. `validate_results.py`
|
||||
independently rechecks actual PNG, IPC/scene, real recipient events, focus,
|
||||
layout/clip and cleanup, and rejects changed RGB, invisible delivery, retained
|
||||
clip or abnormal compositor exit. It accepts terminal transport only after all
|
||||
scientific measurements, explicit exit, compositor code 0 and terminal EPIPE.
|
||||
The post-run collector is retained separately in LAB/postrun-fixture, with its
|
||||
own source inventory. Run that collector/validator for this completed campaign;
|
||||
do not substitute it for the historical executed fixture or rerun the compositor.
|
||||
|
||||
## Measured result (one run, no retry)
|
||||
|
||||
LAB `/home/fy59/swayfx-mosaic-static-rgb-input-retest-hn67lnhq`.
|
||||
Scientific FEASIBLE for the exact matrix, architecture
|
||||
STATIC_MOSAIC_RGB_INPUT_STANDARD_FEASIBLE_FOR_TESTED_MATRIX. The executed runner
|
||||
exited 1 only on final exit IPC; compositor exited 0 after every measured gate.
|
||||
Raw failure is retained, never rewritten as a successful runner execution.
|
||||
271 damage traces are partial evidence, not every-frame qualification.
|
||||
|
||||
H2 six PNGs are whole-RGB identical; 182,214 target pixels on H1, zero H2.
|
||||
Actual target visible serials 35/36, reference 48/49, standard target 67/68;
|
||||
wrong clients have no button delivery. Real H1 splith/clip removal measured,
|
||||
standard H1 paint 226,908 pixels. Budget run 1, camera 1, PNG 8, clicks 3,
|
||||
movements 6 including click positioning, duration 9.142914 seconds. No second run.
|
||||
|
||||
Source manifest 31 entries SHA-256
|
||||
`cb812ae3a27c9715efce2e1b2cfd36f315ea897d003c77944f688bc931861f1e`;
|
||||
evidence 93 entries SHA-256
|
||||
`209196b2857381db7a856347dcce45cd3ee33c48c2db0c73777fc982d01ad568`.
|
||||
Every entry verifies; runner/pre-run inventory and post-run collector are
|
||||
separate retained sources. Result additions here are later documentation.
|
||||
|
||||
Read-only final validation (no recording into sealed results):
|
||||
|
||||
```sh
|
||||
PYTHONDONTWRITEBYTECODE=1 python "$LAB/postrun-fixture/validate_results.py" "$LAB"
|
||||
PYTHONDONTWRITEBYTECODE=1 python "$LAB/postrun-fixture/verify.py" "$LAB"
|
||||
```
|
||||
|
||||
No production readiness, broad static/shared equivalence, popup/effect/buffer
|
||||
qualification, full damage/generation or exhaustive ABI approval. STEP20C NEXT,
|
||||
production multi-output NOT YET SUPPORTED. The completed retest sources and report are preserved for publication.
|
||||
Any later static nested-grab campaign is separate and remains local, uncommitted
|
||||
and unpublished. No installation or system/session change.
|
||||
73
tooling/probes/step20c-static-rgb-input-retest/collect.py
Normal file
73
tooling/probes/step20c-static-rgb-input-retest/collect.py
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Collect actual one-run evidence and seal inventories once.
|
||||
|
||||
WHY: unexecuted cells remain NOT TESTED, never manufactured by collection.
|
||||
CONTRACT: verdict uses real gate observations; a fixture failure is not a leak.
|
||||
INVARIANT: source fixture and historical inventories remain untouched.
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
from support import save,sha
|
||||
from verify import verify_inventory
|
||||
from validate_results import check as check_actual_observations
|
||||
|
||||
p=argparse.ArgumentParser();p.add_argument('lab',type=Path);a=p.parse_args();lab=a.lab.resolve()
|
||||
assert (Path.home()/'swayfx-mosaic-static-rgb-input-retest-v1.owner').read_text().strip()==str(lab)
|
||||
assert not (lab/'results/evidence-sha256.txt').exists(), 'already sealed'
|
||||
verify_inventory(lab/'results/fixture-freeze-sha256.txt')
|
||||
runtime=json.loads((lab/'results/runtime.json').read_text());budget=json.loads((lab/'results/budget.json').read_text())
|
||||
for row in runtime['process_exit_codes']:assert not Path('/proc',str(row['pid'])).exists(), 'owned PID residual'
|
||||
assert not Path(runtime['runtime_removed']).exists(), 'private runtime residual'
|
||||
protected=json.loads((lab/'results/protected-before.json').read_text())
|
||||
assert all(sha(p)==digest for p,digest in protected.items()), 'protected file changed'
|
||||
save(lab,'protected-after',{'status':'PASS','hashes':protected})
|
||||
required=['binary-identity','source-provenance','ipc-replay','preflight-replay','assertion-audit',
|
||||
'output-geometry','focus-states','reference-state','reference-stability','camera-calibration',
|
||||
'scene-clips','painted-geometry','pixel-analysis','visible-input','forbidden-input','reference-input',
|
||||
'standard-return','damage-analysis']
|
||||
for name in required:
|
||||
if not (lab/'results'/(name+'.json')).exists():
|
||||
save(lab,name,{'status':'NOT TESTED','reason':runtime['failure'],'stopping_stage':runtime['stopping_stage']})
|
||||
def read(name):return json.loads((lab/'results'/(name+'.json')).read_text())
|
||||
qualification=check_actual_observations(lab)
|
||||
full=qualification['scientific_gates']=='ALL PASS'
|
||||
save(lab,'scientific-qualification',qualification)
|
||||
verdict='FEASIBLE' if full else 'INCONCLUSIVE'
|
||||
# Classification does not turn a metadata/parser failure into a compositor defect.
|
||||
for name in ['forbidden-pixels']:
|
||||
if (lab/'results'/(name+'.json')).exists() and read(name).get('count',0)>0:verdict='BLOCKED'
|
||||
if not full and verdict!='BLOCKED' and read('pixel-analysis').get('STATIC_MOSAIC_VISIBLE_PIXELS')=='PASS':
|
||||
verdict='PARTIALLY_FEASIBLE'
|
||||
architecture=('STATIC_MOSAIC_RGB_INPUT_STANDARD_FEASIBLE_FOR_TESTED_MATRIX' if full else
|
||||
'STATIC_MOSAIC_RGB_INPUT_STANDARD_REQUIRES_REDESIGN' if verdict=='BLOCKED' else 'UNDECIDED')
|
||||
matrix={
|
||||
'binary_identity':'PASS','source_provenance':'PASS exact historical C17 clients',
|
||||
'IPC_replay':'PASS','preflight_replay':'PASS','initial_focus':'PASS target/workspace 1',
|
||||
'effective_border':'PASS border none, stored width 2, empty deco and uniform full RGB',
|
||||
'outputs':'PASS negative-origin scale-1 normal', 'camera':'PASS calculated once and applied',
|
||||
'Mosaic_clip':'PASS applied local/global relationship plus RGB/input',
|
||||
'H2_reference':'PASS two full RGB images','H1_pixels':'PASS measured target paint',
|
||||
'H2_forbidden_pixels':0,'whole_H2_RGB':'PASS five complete comparisons',
|
||||
'visible_click':'PASS target real pair only','invisible_target':'PASS no event',
|
||||
'reference_click':'PASS reference real pair only','focus':'PASS H1 to H2 to H1',
|
||||
'standard_return':'PASS splith, clip absent, rendered pixels, real target pair',
|
||||
'damage':'PARTIAL client requests and output extents; intermediate frames NOT TESTED',
|
||||
'runner_exit':'FAIL exit 1 on terminal exit IPC; raw status preserved',
|
||||
'compositor_exit':'PASS code 0','terminal_transport':'observed EPIPE after completed measurements',
|
||||
'cleanup':'PASS','native_PNG_buffer_identity':'NOT PROVEN','popups':'NOT TESTED'}
|
||||
save(lab,'matrix',matrix)
|
||||
save(lab,'summary',{'verdict':verdict,'architecture':architecture,'runtime':runtime,'budget':budget,
|
||||
'scientific_qualification':qualification,'executed_runner_status_unchanged':True,
|
||||
'new_Mosaic_builds':0,'new_SceneFX_builds':0,'STEP20C':'NEXT','production_multi_output':'NOT YET SUPPORTED',
|
||||
'marker':'SWAYFX_MOSAIC_STATIC_RGB_INPUT_STANDARD_PROBE='+verdict})
|
||||
source=sorted(p for root in ['fixture','postrun-fixture','client-source'] for p in (lab/root).rglob('*') if p.is_file())
|
||||
source.append(lab/'protocol-before-run.md')
|
||||
evidence=sorted(p for root in ['raw','run','results'] for p in (lab/root).rglob('*') if p.is_file())
|
||||
evidence += [lab/n for n in ['swayfx-mosaic','saved-client','static-reference','pointer-daemon','campaign-owner']]
|
||||
# Capture both lists before writing either inventory, avoiding circular digests.
|
||||
for name,files in [('source',source),('evidence',evidence)]:
|
||||
with (lab/'results'/(name+'-sha256.txt')).open('x') as out:
|
||||
for path in files:out.write(f'{sha(path)} {path}\n')
|
||||
print(name,verify_inventory(lab/'results'/(name+'-sha256.txt')))
|
||||
print('SWAYFX_MOSAIC_STATIC_RGB_INPUT_STANDARD_PROBE='+verdict)
|
||||
139
tooling/probes/step20c-static-rgb-input-retest/ipc_predicates.py
Normal file
139
tooling/probes/step20c-static-rgb-input-retest/ipc_predicates.py
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
"""Pure validators for replay and live observations, never desktop IPC.
|
||||
|
||||
WHY: stored border thickness and container IPC geometry are not rendered pixels.
|
||||
CONTRACT: callers supply measured identities; validate ancestors and applied scene.
|
||||
INVARIANT: no width-zero predicate, hard-coded runtime IDs, or native buffer y.
|
||||
"""
|
||||
import re
|
||||
|
||||
OUTPUTS = {'HEADLESS-1': (-800, 0, 800, 600, 1.0, 'normal'),
|
||||
'HEADLESS-2': (0, 0, 800, 600, 1.0, 'normal')}
|
||||
ZERO = dict(x=0, y=0, width=0, height=0)
|
||||
FULL = dict(x=0, y=0, width=800, height=600)
|
||||
|
||||
|
||||
def nodes(node):
|
||||
yield node
|
||||
for child in node.get('nodes', []) + node.get('floating_nodes', []):
|
||||
yield from nodes(child)
|
||||
|
||||
|
||||
def workspace(tree, name):
|
||||
rows = [n for n in nodes(tree) if n.get('type') == 'workspace' and n.get('name') == name]
|
||||
assert len(rows) == 1, 'workspace identity'
|
||||
return rows[0]
|
||||
|
||||
|
||||
def ancestry(tree, con_id, path=()):
|
||||
if tree.get('id') == con_id:
|
||||
return path + (tree,)
|
||||
for child in tree.get('nodes', []) + tree.get('floating_nodes', []):
|
||||
found = ancestry(child, con_id, path + (tree,))
|
||||
if found:
|
||||
return found
|
||||
return ()
|
||||
|
||||
|
||||
def validate_outputs(outputs):
|
||||
actual = {o['name']: (*[o['rect'][k] for k in ('x', 'y', 'width', 'height')],
|
||||
o['scale'], o['transform']) for o in outputs if o['active']}
|
||||
assert actual == OUTPUTS, ('output geometry', actual)
|
||||
assert len([o for o in outputs if o['active']]) == 2, 'duplicate output'
|
||||
return actual
|
||||
|
||||
|
||||
def validate_reference(snapshot, con_id, pid, workspace_name='2', output_name='HEADLESS-2'):
|
||||
"""B_NONE may retain width 2; require actual empty decoration/full content.
|
||||
|
||||
IPC visibility means view_is_visible, not proof of SHM coverage. RGB is
|
||||
independently required by the runner. Width is returned only as metadata.
|
||||
"""
|
||||
validate_outputs(snapshot['outputs'])
|
||||
path = ancestry(snapshot['tree'], con_id)
|
||||
assert path, 'reference surface absent'
|
||||
ref = path[-1]
|
||||
ws = next((n for n in path if n.get('type') == 'workspace'), None)
|
||||
out = next((n for n in path if n.get('type') == 'output'), None)
|
||||
assert ref.get('type') == 'con' and ref.get('shell') == 'xdg_shell', 'reference view absent'
|
||||
assert ref.get('app_id') == 'org.mosaic.StaticReference', 'reference App ID'
|
||||
assert ref.get('pid') == pid and pid > 0 and ref['id'] == con_id, 'reference PID/ID'
|
||||
assert ws and ws['name'] == workspace_name and ws.get('output') == output_name, 'reference workspace'
|
||||
assert out and out['name'] == output_name and out['rect'] == FULL, 'reference output'
|
||||
assert ref.get('visible') is True, 'reference visibility'
|
||||
assert ref.get('border') == 'none', 'reference border mode'
|
||||
assert ref.get('rect') == FULL and ref.get('window_rect') == FULL, 'reference full content'
|
||||
assert ref.get('deco_rect') == ZERO, 'effective reference decoration'
|
||||
return {'status': 'PASS', 'con_id': con_id, 'pid': pid, 'workspace': ws['id'],
|
||||
'output': output_name, 'stored_border_width': ref.get('current_border_width'),
|
||||
'reference': ref}
|
||||
|
||||
|
||||
def scene_from_log(text, output):
|
||||
rows = [s for s in text.splitlines() if 'PI_SCENE output=' + output + ' ' in s
|
||||
and 'workspace=none' not in s]
|
||||
assert rows, 'applied 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, 'scene trace format'
|
||||
v = list(map(float, m.groups()))
|
||||
origin = OUTPUTS[output][:2]
|
||||
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': [origin[0] + v[3] + v[6], origin[1] + v[4] + v[7], v[8], v[9]],
|
||||
'raw': line}
|
||||
|
||||
|
||||
def measure_owner(text, owner):
|
||||
rows = [x for x in text.splitlines() if 'PI_OWNER output=HEADLESS-1 ' in x
|
||||
and f' id={owner["id"]} ' in x]
|
||||
assert rows, 'applied owner trace absent'
|
||||
line = rows[-1]
|
||||
m = re.search(r'scene_valid=1 scene=(-?\d+),(-?\d+) applied_size=(\d+),(\d+)', line)
|
||||
assert m, 'owner trace format'
|
||||
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}
|
||||
|
||||
|
||||
def client_commit(text):
|
||||
rows = [s for s in text.splitlines() if s.startswith('COMMIT ')]
|
||||
assert rows, 'no real client commit'
|
||||
m = re.fullmatch(r'COMMIT ns=(\d+) serial=(\d+) size=(\d+)x(\d+) color=([0-9a-f]{8})', rows[-1])
|
||||
assert m, 'client commit format'
|
||||
ns, serial, w, h = map(int, m.groups()[:4])
|
||||
assert ns > 0 and 0 < serial <= 0xffffffff and 0 < w <= 4096 and 0 < h <= 4096
|
||||
return {'ns': ns, 'serial': serial, 'width': w, 'height': h, 'color': m[5], 'raw': rows[-1]}
|
||||
|
||||
|
||||
def validate_preflight(snapshot, con_id, pid, target_id, target_pid, scene, h2_scene, measured, commit):
|
||||
reference = validate_reference(snapshot, con_id, pid)
|
||||
ws = workspace(snapshot['tree'], '1')
|
||||
path = ancestry(snapshot['tree'], target_id)
|
||||
assert path and ws in path, 'target workspace ownership'
|
||||
owner = path[-1]
|
||||
assert owner.get('app_id') == 'org.mosaic.SavedBufferProbe' and owner.get('pid') == target_pid
|
||||
assert owner.get('shell') == 'xdg_shell' and owner.get('visible') is True, 'target mapped'
|
||||
assert ws['output'] == 'HEADLESS-1' and ws['layout'] == 'mosaic', 'H1 Mosaic'
|
||||
assert owner.get('focused') is True and reference['reference']['focused'] is False, 'initial focus'
|
||||
assert [z['name'] for z in snapshot['workspaces'] if z['focused']] == ['1'], 'workspace focus'
|
||||
scroll = ws['mosaic']['rows'][0]['scroll_x']
|
||||
assert scene['workspace'] == ws['id'] and scene['layout_enum'] == 5, 'applied Mosaic owner'
|
||||
assert scene['scroll'] == scroll and scene['tile_local'] == [-scroll, 0], 'applied camera'
|
||||
assert scene['clip_valid'] and scene['global_clip'] == [-800, 0, 800, 600], 'viewport clip'
|
||||
assert scene['clip_local'] == [scroll, 0, 800, 600], 'local clip'
|
||||
h2ws = workspace(snapshot['tree'], '2')
|
||||
assert h2_scene['workspace'] == h2ws['id'] and not h2_scene['clip_valid'], 'H2 ordinary clip'
|
||||
assert measured['id'] == target_id and measured['IPC'] == owner, 'measured owner identity'
|
||||
x, w = measured['horizontal_content']
|
||||
assert -800 < x < 0 < x + w, 'target continuation crosses boundary'
|
||||
assert w == commit['width'] == owner['window_rect']['width'], 'configured content width'
|
||||
assert commit['height'] == owner['window_rect']['height'] and commit['color'] in ('002344cc', '0020d0a0')
|
||||
return {'status': 'PASS', 'reference': reference, 'workspace': ws['id'],
|
||||
'target': target_id, 'scroll': scroll, 'scene': scene, 'H2_scene': h2_scene,
|
||||
'measured': measured, 'commit': commit}
|
||||
|
||||
|
||||
def validate_camera(before_scroll, after_scroll, delta, scene):
|
||||
assert type(delta) is int and -2147483647 <= delta <= 2147483647
|
||||
assert after_scroll == before_scroll + delta and scene['scroll'] == after_scroll, 'camera correction applied'
|
||||
71
tooling/probes/step20c-static-rgb-input-retest/pixels.py
Normal file
71
tooling/probes/step20c-static-rgb-input-retest/pixels.py
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
"""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):
|
||||
# CONTRACT: malformed or extra client button lines must never be filtered out.
|
||||
events=[]
|
||||
for line in lines:
|
||||
m=re.fullmatch(r'BUTTON ns=(\d+) serial=(\d+) button=(\d+) state=(\d+)',line)
|
||||
assert m, ('malformed button',line)
|
||||
events.append(tuple(map(int,m.groups())))
|
||||
assert len(events)==2 and [x[2:] for x in events]==[(273,1),(273,0)],events
|
||||
assert all(x[0]>0 and 0<x[1]<=0xffffffff for x in events), 'timestamp/serial bounds'
|
||||
assert events[0][0]<events[1][0] and events[0][1]!=events[1][1], 'press/release order'
|
||||
return [{'ns':x[0],'serial':x[1],'button':x[2],'state':x[3]} for x in events]
|
||||
|
||||
|
||||
def painted_geometry(im, measured, commit):
|
||||
"""Paint supplies y; applied scene and actual COMMIT supply horizontal continuation.
|
||||
|
||||
WHY: window_rect.y deliberately excludes an enabled server titlebar.
|
||||
CONTRACT: require solid target paint to the viewport boundary, no guessed y.
|
||||
"""
|
||||
value = analyze(im);box = value['bbox'];assert box, 'no permitted target pixels'
|
||||
x1,y1,x2,y2 = box;x,w = measured['horizontal_content']
|
||||
assert x1-800 == x and x2 == 800, 'paint/scene horizontal correlation'
|
||||
assert value['count'] == (x2-x1)*(y2-y1), 'nonrectangular target paint'
|
||||
assert y2-y1 == commit['height'] and commit['width'] == w, 'painted configured dimensions'
|
||||
assert 20 <= y1+20 < y2-20 <= 600
|
||||
return value
|
||||
|
||||
|
||||
def click_points(left, right, measured):
|
||||
"""Select from captured target pixels; H2 must be inside continuation/reference."""
|
||||
px,py = interior(left);x,w = measured['horizontal_content']
|
||||
hx = min(100,int(x+w)-30)
|
||||
assert 20 <= hx < 780 and 20 <= py < 580, 'H2 point margin'
|
||||
assert x+20 <= hx < x+w-20, 'inside continued content'
|
||||
assert all(right.getpixel((xx,yy)) == (48,48,56)
|
||||
for xx in range(hx-20,hx+21) for yy in range(py-20,py+21)), 'H2 accessible reference interior'
|
||||
return {'permitted': (px-800,py), 'forbidden': (hx,py),
|
||||
'paint_bbox': analyze(left)['bbox'], 'horizontal_content': [x,w]}
|
||||
|
||||
|
||||
def delivery(events, recipient):
|
||||
"""No fabricated recipient: only a real pair in its named client log counts."""
|
||||
assert set(events) == {'target','reference','filler'}, 'client log set'
|
||||
pair=button_pair(events[recipient])
|
||||
assert all(not rows for name,rows in events.items() if name != recipient), 'wrong recipient delivery'
|
||||
return pair
|
||||
75
tooling/probes/step20c-static-rgb-input-retest/prepare.py
Normal file
75
tooling/probes/step20c-static-rgb-input-retest/prepare.py
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
#!/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, ast, argparse
|
||||
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-rgb-input-retest-v1.owner'
|
||||
p=argparse.ArgumentParser();p.add_argument('--resume-preparation',action='store_true');a=p.parse_args()
|
||||
if a.resume_preparation:
|
||||
# CONTRACT: only recover pre-run preparation in the same exclusive LAB.
|
||||
# No ledger/reset, second LAB, completed preparation or executed run is allowed.
|
||||
lab=Path(OWNER.read_text().strip()).resolve()
|
||||
assert lab.is_dir() and (lab/'campaign-owner').read_text().strip()==str(OWNER)
|
||||
assert not (lab/'run').exists() and not (lab/'results/budget.json').exists()
|
||||
assert not (lab/'results/fixture-freeze-sha256.txt').exists()
|
||||
else:
|
||||
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-rgb-input-retest-',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 binary.is_file() and not binary.is_symlink() and os.access(binary,os.X_OK)
|
||||
assert sha(binary)==expected and stat.S_IMODE(binary.stat().st_mode)==0o755
|
||||
if not (lab/'swayfx-mosaic').exists():shutil.copy2(binary,lab/'swayfx-mosaic')
|
||||
assert sha(lab/'swayfx-mosaic')==expected
|
||||
elf=((lab/'raw/binary-elf.log').read_text() if a.resume_preparation else
|
||||
command(lab,'binary-elf',['readelf','-hW','-nW','-dW',lab/'swayfx-mosaic']))
|
||||
assert 'libscenefx' not in elf and '942db64269640778ee433e5970ba1100bc60f94c' in elf
|
||||
assert 'ELF64' in elf and 'Advanced Micro Devices X86-64' in elf
|
||||
# WHY: private static clip definitions are ordinary ELF symbols, not exports.
|
||||
symbols=command(lab,'clip-symbols-static',['nm',lab/'swayfx-mosaic'])
|
||||
assert all(' T '+s in symbols for s in ['wlr_scene_tree_set_clip','wlr_scene_tree_get_clip'])
|
||||
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})
|
||||
save(lab,'source-provenance',{'clients':clients,'historical_manifests_verified':True,
|
||||
'source_manifest_sha256':sha(OLD/'results/source-sha256.txt'),
|
||||
'evidence_manifest_sha256':sha(OLD/'results/evidence-sha256.txt'),
|
||||
'new_Mosaic_builds':0,'new_SceneFX_builds':0})
|
||||
repo=Path(__file__).resolve().parents[3]
|
||||
shutil.copytree(Path(__file__).parent,lab/'fixture')
|
||||
(lab/'protocol-before-run.md').write_bytes((repo/'docs/design/STEP20C_STATIC_RGB_INPUT_STANDARD_RETEST.md').read_bytes())
|
||||
for p in (lab/'fixture').glob('*.py'):ast.parse(p.read_text(),filename=str(p))
|
||||
with (lab/'results/fixture-freeze-sha256.txt').open('x') as f:
|
||||
for p in sorted((lab/'fixture').rglob('*')):
|
||||
if p.is_file():f.write(f'{sha(p)} {p}\n')
|
||||
f.write(f'{sha(lab/"protocol-before-run.md")} {lab/"protocol-before-run.md"}\n')
|
||||
save(lab,'protected-before', {str(p):sha(p) for p in [Path('/usr/bin/sway'),
|
||||
Path('/usr/lib/libwlroots-0.20.so').resolve(),Path('/usr/lib/libscenefx-0.5.so').resolve(),
|
||||
repo/'PKGBUILD',repo/'patches/0001-mosaic-layout-viewport.patch',repo/'AGENTS.md',repo/'docs/UX.md']})
|
||||
print('LAB='+str(lab))
|
||||
154
tooling/probes/step20c-static-rgb-input-retest/replay.py
Normal file
154
tooling/probes/step20c-static-rgb-input-retest/replay.py
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Read-only historical replay and explicitly labelled synthetic negative tests.
|
||||
|
||||
CONTRACT: exactly the live pure functions are exercised, without compositor launch.
|
||||
INVARIANT: old manifests/snapshots are never written; historical IDs stay here.
|
||||
"""
|
||||
import argparse
|
||||
import ast
|
||||
import copy
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
from PIL import Image
|
||||
from support import save, sha
|
||||
from ipc_predicates import (nodes, validate_reference, validate_outputs, workspace,
|
||||
measure_owner, scene_from_log, client_commit, validate_preflight, validate_camera)
|
||||
from pixels import analyze, compare, interior, painted_geometry, click_points, delivery, button_pair
|
||||
|
||||
HIST = Path('/home/fy59/swayfx-mosaic-static-followup-nhwejz6u')
|
||||
OLD = Path('/home/fy59/swayfx-mosaic-static-runtime-hm5kbdkt')
|
||||
|
||||
|
||||
def check():
|
||||
from verify import verify_inventory
|
||||
for base,pairs in [(HIST,[('source','5f9ab2b6d1ca23ef7743623d1e544237c8b70b62fe6de8164a46f1342e5a8ee2'),('evidence','41cc5e3d305d89e83314723e67d7116c36d1503dd35647894bc898c49343e1a5')]),
|
||||
(OLD,[('source','ed55bd418595ca977c022fa98917a899072cf3b6f0fb15457db823081046e797'),('evidence','e260ce5793ffc9a29ee4bfa2189663feeb6dc025bd46d3f5ddebccd225f3eef1')])]:
|
||||
for name,digest in pairs:
|
||||
manifest=base/'results'/(name+'-sha256.txt');assert sha(manifest)==digest
|
||||
verify_inventory(manifest)
|
||||
snap=json.loads((HIST/'results/preflight-snapshot.json').read_text())
|
||||
text=(HIST/'run/compositor.log').read_text()
|
||||
ref=next(n for n in nodes(snap['tree']) if n.get('id')==7)
|
||||
target=next(n for n in nodes(snap['tree']) if n.get('id')==9)
|
||||
results=[]
|
||||
def rejected(name,fun):
|
||||
try:fun()
|
||||
except (AssertionError,KeyError,StopIteration,TypeError):results.append({'negative':name,'status':'REJECTED'})
|
||||
else:raise AssertionError('invalid fixture accepted: '+name)
|
||||
validate_reference(snap,7,ref['pid'])
|
||||
assert ref['border']=='none' and ref['current_border_width']==2
|
||||
for field,value in [('app_id','wrong'),('pid',1),('visible',False),('shell',None),
|
||||
('border','normal'),('rect',dict(x=1,y=0,width=800,height=600)),
|
||||
('window_rect',dict(x=0,y=0,width=799,height=600)),
|
||||
('deco_rect',dict(x=0,y=0,width=800,height=25))]:
|
||||
s=copy.deepcopy(snap);r=next(n for n in nodes(s['tree']) if n.get('id')==7);r[field]=value
|
||||
rejected(field,lambda s=s:validate_reference(s,7,ref['pid']))
|
||||
rejected('wrong con_id',lambda:validate_reference(snap,99,ref['pid']))
|
||||
rejected('wrong workspace',lambda:validate_reference(snap,7,ref['pid'],'1'))
|
||||
rejected('wrong output',lambda:validate_reference(snap,7,ref['pid'],'2','HEADLESS-1'))
|
||||
rejected('actual H1 filler',lambda:validate_reference(snap,8,next(n for n in nodes(snap['tree']) if n.get('id')==8)['pid']))
|
||||
s=copy.deepcopy(snap);workspace(s['tree'],'2')['nodes']=[]
|
||||
rejected('absent surface',lambda:validate_reference(s,7,ref['pid']))
|
||||
for field,value in [('rect',dict(x=0,y=0,width=801,height=600)),('scale',1.25),('transform','90')]:
|
||||
s=copy.deepcopy(snap);next(o for o in s['outputs'] if o['name']=='HEADLESS-2')[field]=value
|
||||
rejected('output '+field,lambda s=s:validate_reference(s,7,ref['pid']))
|
||||
# Earlier border-normal state is an actual IPC reply, not a manufactured state.
|
||||
early=None
|
||||
for line in (HIST/'run/events.jsonl').read_text().splitlines():
|
||||
e=json.loads(line)
|
||||
if e.get('kind')=='ipc' and list(e.get('args',[]))==['-t','get_tree']:
|
||||
r=next((n for n in nodes(e['result']) if n.get('id')==7),None)
|
||||
if r and r.get('border')=='normal':early=e['result'];break
|
||||
assert early is not None, 'real earlier border-normal observation missing'
|
||||
rejected('actual earlier normal reference',lambda:validate_reference({**snap,'tree':early},7,ref['pid']))
|
||||
scene=scene_from_log(text,'HEADLESS-1');h2=scene_from_log(text,'HEADLESS-2')
|
||||
measured=measure_owner(text,target);commit=client_commit((HIST/'run/target.log').read_text())
|
||||
good=validate_preflight(snap,7,ref['pid'],9,target['pid'],scene,h2,measured,commit)
|
||||
calibration=json.loads((HIST/'results/camera-calibration.json').read_text())
|
||||
validate_camera(calibration['before'],calibration['after'],calibration['delta'],scene)
|
||||
args=[snap,7,ref['pid'],9,target['pid'],scene,h2,measured,commit]
|
||||
for name,index,field,value in [('clip global',5,'global_clip',[0,0,800,600]),
|
||||
('clip absent',5,'clip_valid',False),('camera',5,'scroll',0),
|
||||
('scene owner',5,'workspace',999),('H2 clip',6,'clip_valid',True),
|
||||
('content width',8,'width',635)]:
|
||||
bad=copy.deepcopy(args);bad[index][field]=value
|
||||
rejected(name,lambda bad=bad:validate_preflight(*bad))
|
||||
for name,mutate in [('focus',lambda s:next(n for n in nodes(s['tree']) if n.get('id')==9).update(focused=False)),
|
||||
('layout',lambda s:workspace(s['tree'],'1').update(layout='splith'))]:
|
||||
bad=copy.deepcopy(args);mutate(bad[0]);rejected(name,lambda bad=bad:validate_preflight(*bad))
|
||||
rejected('camera correction',lambda:validate_camera(0,160,159,scene))
|
||||
historical=Image.open(OLD/'run/h1-mosaic.png').convert('RGB')
|
||||
# The older static campaign used exactly this measured content continuation.
|
||||
paint=painted_geometry(historical,measured,commit)
|
||||
fixed=Image.new('RGB',(800,600),(48,48,56))
|
||||
points=click_points(historical,fixed,measured)
|
||||
assert points['permitted']==(-159,311) and points['forbidden']==(100,311)
|
||||
assert analyze(fixed)['count']==0 and compare(fixed,fixed.copy())['equal']
|
||||
edge=fixed.copy();edge.putpixel((799,599),(48,48,57))
|
||||
diff=compare(fixed,edge);assert not diff['equal'] and diff['changed']==1
|
||||
rejected('no target paint',lambda:interior(fixed))
|
||||
sparse=historical.copy();sparse.putpixel((640,311),(0,0,0))
|
||||
rejected('sparse paint',lambda:painted_geometry(sparse,measured,commit))
|
||||
blocked=fixed.copy();blocked.putpixel((100,311),(0,0,0))
|
||||
rejected('H2 point inaccessible',lambda:click_points(historical,blocked,measured))
|
||||
# Positive and negatives below are SYNTHETIC, not new or historical delivery.
|
||||
pair=['BUTTON ns=1 serial=3 button=273 state=1','BUTTON ns=2 serial=4 button=273 state=0']
|
||||
assert delivery(dict(target=pair,reference=[],filler=[]),'target')
|
||||
for name,lines in [('none',[]),('missing release',pair[:1]),('extra',pair+pair[:1]),
|
||||
('malformed',pair+['BUTTON garbage']),('wrong code',[s.replace('273','272') for s in pair]),
|
||||
('wrong order',pair[::-1]),('zero serial',[pair[0].replace('serial=3','serial=0'),pair[1]]),
|
||||
('same serial',[pair[0],pair[1].replace('serial=4','serial=3')]),
|
||||
('backwards time',[pair[0].replace('ns=1','ns=3'),pair[1]])]:
|
||||
rejected('SYNTHETIC button '+name,lambda lines=lines:button_pair(lines))
|
||||
rejected('SYNTHETIC wrong recipient',lambda:delivery(dict(target=pair,reference=pair,filler=[]),'target'))
|
||||
src=OLD/'mosaic-source'
|
||||
# Source tokens are supporting semantic audit, not behavioral tests.
|
||||
checks=[('sway/commands/border.c',['set_border(container, B_NONE)','if (argc == 2)']),
|
||||
('sway/ipc-json.c',['json_object_new_int(c->current.border_thickness)','c->pending.content_width','view_is_visible']),
|
||||
('sway/commands/layout.c',['workspace && workspace->layout == L_MOSAIC','container = NULL;','workspace->layout = new_layout']),
|
||||
('sway/desktop/transaction.c',['border_width = 0;','wlr_scene_tree_set_clip(child->layers.tiling, NULL)']),
|
||||
('sway/desktop/output.c',['wlr_scene_node_coords','PI_SCENE output=','PI_OWNER output='])]
|
||||
sources=[]
|
||||
for name,tokens in checks:
|
||||
p=src/name;contents=p.read_text();assert all(t in contents for t in tokens),(p,tokens)
|
||||
sources.append({'file':str(p),'sha256':sha(p),'semantics_checked':tokens})
|
||||
fields=[('outputs rect/scale/transform','sway/ipc-json.c','actual IPC','PASS','live reread'),
|
||||
('view App ID/PID/con_id/ancestors/visible','sway/ipc-json.c','actual IPC','PASS','visibility is not pixels'),
|
||||
('workspace/focus/layout','sway/ipc-json.c; sway/commands/layout.c','actual IPC','PASS','later focus live'),
|
||||
('border/stored width/deco/window_rect','border.c; ipc-json.c; transaction.c','actual IPC plus earlier normal','PASS','effective RGB live'),
|
||||
('camera scroll_x/scene tree/clip local/global','transaction.c; output.c; ipc-json.c','actual traces + calibration','PASS','no atomic generation asserted'),
|
||||
('content dimensions/SHM colors','client-source/saved-client.c','actual COMMIT + historical PNG','PASS','new pixels live'),
|
||||
('capture coordinates/painted click selection','pixels.py; virtual pointer source','HISTORICAL H1 + SYNTHETIC H2','PASS','new accessible surface verified live'),
|
||||
('buttons/recipient/count/order/serial','saved-client.c:pointer_button; pointer-daemon.c','SYNTHETIC labelled cases','PASS','new actual client events required'),
|
||||
('targeted splith return','sway/commands/layout.c','exact candidate source','PASS source audit','layout/clip/render/input live; NOT TESTED offline')]
|
||||
audit=[dict(field=f,source=s,historical_data=h,replay=r,remaining_uncertainty=u,
|
||||
invariant=f+' remains required live') for f,s,h,r,u in fields]
|
||||
runtime_assertions=[]
|
||||
runner=Path(__file__).with_name('run.py').read_text()
|
||||
for node in ast.walk(ast.parse(runner)):
|
||||
if isinstance(node,ast.Assert):
|
||||
runtime_assertions.append({'line':node.lineno,'expression':ast.get_source_segment(runner,node.test),
|
||||
'authority':'see corresponding field/source table; operational assertions use owned files/processes',
|
||||
'offline':'shared predicates and historical/synthetic tests above; environment identity checked by prepare',
|
||||
'uncertainty':'live-only states require new observations; no retrospective PASS'})
|
||||
audit={'fields':audit,'runner_assertions':sorted(runtime_assertions,key=lambda x:x['line'])}
|
||||
return {'reference':{'marker':'STATIC_REFERENCE_IPC_REPLAY=PASS','snapshot_sha256':sha(HIST/'results/preflight-snapshot.json'),
|
||||
'accepted_width':2,'reference':good['reference'],'negatives':results},
|
||||
'preflight':{'marker':'STATIC_FIXTURE_PREFLIGHT_REPLAY=PASS','actual':good,'paint_HISTORICAL':paint,
|
||||
'points_HISTORICAL_SYNTHETIC':points,'synthetic_input_tests':'PASS'},
|
||||
'audit':audit,'source_audit':sources}
|
||||
|
||||
if __name__=='__main__':
|
||||
p=argparse.ArgumentParser();p.add_argument('--lab',type=Path);a=p.parse_args()
|
||||
result=check()
|
||||
for path in Path(__file__).parent.glob('*.py'):ast.parse(path.read_text(),filename=str(path))
|
||||
subprocess.run(['python',str(Path(__file__).with_name('test-budget.py'))],check=True)
|
||||
if a.lab:
|
||||
from verify import verify_inventory
|
||||
verify_inventory(a.lab/'results/fixture-freeze-sha256.txt')
|
||||
save(a.lab,'ipc-replay',result['reference']);save(a.lab,'preflight-replay',result['preflight'])
|
||||
save(a.lab,'assertion-audit',result['audit']);save(a.lab,'source-audit',result['source_audit'])
|
||||
save(a.lab,'offline-gate',{'reference':'PASS','preflight':'PASS','AST':'PASS','ledger':'PASS','source_audit':'PASS'})
|
||||
print(result['reference']['marker']);print(result['preflight']['marker'])
|
||||
print('PASS',len(result['reference']['negatives']),'negative cases; historical/synthetic evidence labelled')
|
||||
258
tooling/probes/step20c-static-rgb-input-retest/run.py
Normal file
258
tooling/probes/step20c-static-rgb-input-retest/run.py
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
#!/usr/bin/env python3
|
||||
"""One independent budgeted RGB/input/standard retest 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, painted_geometry, click_points, delivery
|
||||
from ipc_predicates import (nodes, workspace, validate_outputs, validate_reference,
|
||||
validate_preflight, measure_owner, scene_from_log, client_commit, validate_camera)
|
||||
from verify import verify_inventory
|
||||
|
||||
p=argparse.ArgumentParser();p.add_argument('lab',type=Path);a=p.parse_args();lab=a.lab.resolve()
|
||||
assert not __import__('sys').flags.optimize, 'Python assertions must be enabled'
|
||||
assert json.loads((lab/'results/binary-identity.json').read_text())['status']=='PASS'
|
||||
assert sha(lab/'swayfx-mosaic')=='f82557c1a9978a5e9665b9b4291b74880253725359e3df1c5b8d554921f7603b'
|
||||
assert (Path.home()/'swayfx-mosaic-static-rgb-input-retest-v1.owner').read_text().strip()==str(lab)
|
||||
# CONTRACT: run reservation is impossible until the exact frozen fixture passes.
|
||||
assert not __import__('sys').flags.optimize, 'Python assertions must be enabled'
|
||||
verify_inventory(lab/'results/fixture-freeze-sha256.txt')
|
||||
gate=json.loads((lab/'results/offline-gate.json').read_text())
|
||||
assert gate=={'reference':'PASS','preflight':'PASS','AST':'PASS','ledger':'PASS','source_audit':'PASS'}
|
||||
assert json.loads((lab/'results/ipc-replay.json').read_text())['marker']=='STATIC_REFERENCE_IPC_REPLAY=PASS'
|
||||
assert json.loads((lab/'results/preflight-replay.json').read_text())['marker']=='STATIC_FIXTURE_PREFLIGHT_REPLAY=PASS'
|
||||
reserve(lab,'run');run=lab/'run';run.mkdir();runtime=Path(tempfile.mkdtemp(prefix='msr-',dir='/tmp'))
|
||||
runtime.chmod(0o700);(run/'runtime-path').write_text(str(runtime)+'\n')
|
||||
(run/'config').write_text('output HEADLESS-1 resolution 800x600 scale 1 transform normal position -800 0\noutput HEADLESS-2 resolution 800x600 scale 1 transform normal position 0 0\nworkspace 1 output HEADLESS-1\nworkspace 2 output HEADLESS-2\nfocus_follows_mouse no\nseat seat0 hide_cursor 100\n')
|
||||
env=environment();env.update(XDG_RUNTIME_DIR=str(runtime),WLR_BACKENDS='headless',WLR_HEADLESS_OUTPUTS='2',WLR_RENDERER='pixman')
|
||||
binary=lab/'swayfx-mosaic';processes=[];files=[];failure=None;compositor=None;events=[];stage='startup'
|
||||
started=time.monotonic();finished=threading.Event();expired=threading.Event()
|
||||
|
||||
def event(kind,**data):
|
||||
row={'ns':time.monotonic_ns(),'kind':kind,**data}
|
||||
path=run/'events.jsonl'
|
||||
assert not path.exists() or path.stat().st_size<64*1024*1024, 'event output bound'
|
||||
with path.open('a') as out:out.write(json.dumps(row)+'\n')
|
||||
|
||||
def stop_owned():
|
||||
for proc in list(processes):
|
||||
try:os.killpg(proc.pid,signal.SIGTERM)
|
||||
except ProcessLookupError:pass
|
||||
|
||||
def watchdog():
|
||||
while not finished.wait(.1):
|
||||
if time.monotonic()-started>150 or any(x.stat().st_size>64*1024*1024 for x in run.glob('*.log')):
|
||||
expired.set();stop_owned();return
|
||||
|
||||
thread=threading.Thread(target=watchdog,daemon=True);thread.start()
|
||||
|
||||
def spawn(name,argv,stdin=False,extra=None):
|
||||
assert not expired.is_set()
|
||||
out=(run/(name+'.log')).open('w');files.append(out)
|
||||
proc=subprocess.Popen(list(map(str,argv)),env={**env,**(extra or {})},stdin=subprocess.PIPE if stdin else subprocess.DEVNULL,stdout=out,stderr=subprocess.STDOUT,text=True,start_new_session=True)
|
||||
processes.append(proc);event('spawn',name=name,pid=proc.pid,argv=list(map(str,argv)));return proc
|
||||
|
||||
def wait(predicate,seconds=8):
|
||||
end=time.monotonic()+seconds
|
||||
while time.monotonic()<end:
|
||||
if expired.is_set():raise RuntimeError('deadline/log bound exceeded')
|
||||
if compositor and compositor.poll() is not None:raise RuntimeError('compositor exited '+str(compositor.returncode))
|
||||
value=predicate()
|
||||
if value:return value
|
||||
time.sleep(.025)
|
||||
raise RuntimeError('fixture wait timed out')
|
||||
|
||||
def log(name):return (run/(name+'.log')).read_text(errors='replace')
|
||||
|
||||
def ipc(*args):
|
||||
assert sock.is_relative_to(runtime) and sock.is_socket()
|
||||
result=subprocess.run(['swaymsg','-s',str(sock),*args],env=env,capture_output=True,text=True,timeout=5,check=True)
|
||||
data=json.loads(result.stdout);event('ipc',args=args,result=data);return data
|
||||
|
||||
def cmd(value):
|
||||
data=ipc(value);assert all(x.get('success') for x in data),(value,data)
|
||||
|
||||
def find(app):return next((n for n in nodes(ipc('-t','get_tree')) if n.get('app_id')==app),None)
|
||||
|
||||
def inject(action,x,y,kind='move'):
|
||||
reserve(lab,kind)
|
||||
if kind!='move':reserve(lab,'move') # Clicks also contain one actual pointer motion.
|
||||
assert -800<=x<800 and 0<=y<600
|
||||
marker=f'INJECT_DONE {action} {x+800},{y}';before=log('pointer').count(marker)
|
||||
pointer.stdin.write(f'{action} {x+800} {y}\n');pointer.stdin.flush()
|
||||
wait(lambda:log('pointer').count(marker)>before)
|
||||
time.sleep(.15);event('pointer',action=action,global_point=[x,y],target_tail=log('target').splitlines()[-6:] if (run/'target.log').exists() else [])
|
||||
|
||||
def capture(tag,output):
|
||||
reserve(lab,'capture');path=run/(tag+'.png')
|
||||
subprocess.run(['grim','-s','1','-o',output,str(path)],env=env,check=True,timeout=5,stdout=subprocess.DEVNULL)
|
||||
im=Image.open(path).convert('RGB');assert im.size==(800,600)
|
||||
event('capture',file=path.name,output=output,sha256=sha(path));return im
|
||||
|
||||
def measure(owner):return measure_owner(log('compositor'),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 scene(output):return scene_from_log(log('compositor'),output)
|
||||
|
||||
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');validate_outputs(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'))
|
||||
stage='clients'
|
||||
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'))
|
||||
stage='preflight'
|
||||
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})
|
||||
validate_camera(scroll_before,scroll_after,delta,sc_after)
|
||||
x,w=after['horizontal_content'];assert x<0<x+w and -800<x
|
||||
assert sc_after['clip_valid'] and sc_after['global_clip']==[-800,0,800,600]
|
||||
assert sc_after['workspace']==wsid
|
||||
reference=next(n for n in nodes(tree) if n['id']==refid)
|
||||
commit=client_commit(log('target'))
|
||||
preflight=validate_preflight(focus_states[-1],refid,ref.pid,targetid,target.pid,
|
||||
sc_after,scene('HEADLESS-2'),after,commit)
|
||||
save(lab,'reference-state',preflight['reference'])
|
||||
save(lab,'live-preflight',preflight)
|
||||
save(lab,'owner-geometry',{'before':before,'after':after,'reference':reference,'last_client_commit':[s for s in log('target').splitlines() if s.startswith('COMMIT ')][-1]})
|
||||
save(lab,'scene-clips',{'mosaic':sc_after,'H2':scene('HEADLESS-2')})
|
||||
stage='reference'
|
||||
inject('move',-790,590);time.sleep(.6)
|
||||
baseline=capture('h2-reference-1','HEADLESS-2');time.sleep(.6);second=capture('h2-reference-2','HEADLESS-2')
|
||||
comparison=compare(baseline,second)
|
||||
save(lab,'reference-stability',{'H2_REFERENCE_STABLE':comparison['equal'],'H2_WHOLE_RGB_EQUAL':comparison['equal'],'reference':reference,'focus':focus_states[-1]['focused'],'hashes':{n:sha(run/n) for n in ['h2-reference-1.png','h2-reference-2.png']}})
|
||||
save(lab,'reference-pixel-analysis',comparison)
|
||||
assert comparison['equal'] and all(c==(48,48,56) for c in baseline.get_flattened_data()),'reference preflight failed'
|
||||
stage='mosaic'
|
||||
left=capture('h1-mosaic','HEADLESS-1');right=capture('h2-mosaic','HEADLESS-2')
|
||||
visible=analyze(left);forbidden=analyze(right)
|
||||
save(lab,'permitted-pixels',visible);save(lab,'forbidden-pixels',forbidden)
|
||||
assert visible['count']>0 and forbidden['count']==0
|
||||
whole('v1-H2-comparison',baseline,right)
|
||||
paint=painted_geometry(left,after,commit)
|
||||
points=click_points(left,right,after)
|
||||
permitted=points['permitted'];forbidden_point=points['forbidden']
|
||||
save(lab,'painted-geometry',{'paint':paint,'measured':after,'commit':commit,'points':points})
|
||||
save(lab,'pixel-analysis',{'STATIC_MOSAIC_VISIBLE_PIXELS':'PASS',
|
||||
'STATIC_MOSAIC_FORBIDDEN_PIXELS':forbidden['count'],'STATIC_MOSAIC_H2_WHOLE_RGB':'PASS',
|
||||
'H1':visible,'H2':forbidden})
|
||||
save(lab,'click-points',{'permitted':permitted,'forbidden':forbidden_point,'paint_bbox':visible['bbox'],'horizontal_content':[x,w]})
|
||||
stage='visible-input'
|
||||
click=measured_click('visible_click',permitted);events_visible=delivery(click['events'],'target')
|
||||
assert not click['events']['reference'] and not click['events']['filler']
|
||||
save(lab,'visible-input',{'status':'PASS','point':permitted,'events':events_visible})
|
||||
tree=snapshot('after-visible');assert camera(tree)==scroll_after
|
||||
assert measure(find('org.mosaic.SavedBufferProbe'))['horizontal_content']==[x,w]
|
||||
stage='forbidden-input'
|
||||
click=measured_click('forbidden_click',forbidden_point);events_reference=delivery(click['events'],'reference')
|
||||
assert not click['events']['target'] and not click['events']['filler'],'invisible target or wrong client input'
|
||||
save(lab,'forbidden-input',{'status':'PASS','target_received':False})
|
||||
save(lab,'reference-input',{'status':'PASS','point':forbidden_point,'events':events_reference,'invisible_target_events':click['events']['target']})
|
||||
tree=snapshot('after-reference');assert camera(tree)==scroll_after
|
||||
assert next(n for n in nodes(tree) if n['id']==refid)['focused']
|
||||
save(lab,'reference-after-focus',validate_reference(focus_states[-1],refid,ref.pid))
|
||||
inject('move',-790,590);time.sleep(.6)
|
||||
whole('post-click-H2-comparison',baseline,capture('h2-after-clicks','HEADLESS-2'))
|
||||
stage='standard-return'
|
||||
cmd(f'[con_id={targetid}] layout splith');time.sleep(.6)
|
||||
tree=snapshot('standard');standard_ws=workspace(tree,'1');assert standard_ws['id']==wsid and standard_ws['layout']=='splith'
|
||||
standard_owner=next(n for n in nodes(standard_ws) if n['id']==targetid);assert standard_owner['visible']
|
||||
standard_left=capture('h1-standard','HEADLESS-1');standard_right=capture('h2-standard','HEADLESS-2')
|
||||
save(lab,'standard-pixels',{'H1':analyze(standard_left),'H2':analyze(standard_right)})
|
||||
assert analyze(standard_right)['count']==0, 'standard target leaked to H2'
|
||||
whole('standard-H2-comparison',baseline,standard_right)
|
||||
standard_scene=scene('HEADLESS-1');standard_h2=scene('HEADLESS-2')
|
||||
assert standard_scene['workspace']==wsid and not standard_scene['clip_valid'] and not standard_h2['clip_valid']
|
||||
sx,sy=interior(standard_left);point=(sx-800,sy)
|
||||
click=measured_click('standard_click',point);events_standard=delivery(click['events'],'target')
|
||||
assert not click['events']['reference'] and not click['events']['filler']
|
||||
final_tree=snapshot('after-standard-click')
|
||||
assert next(n for n in nodes(final_tree) if n['id']==targetid)['focused']
|
||||
assert [z['name'] for z in focus_states[-1]['workspaces'] if z['focused']]==['1']
|
||||
save(lab,'reference-after-standard',validate_reference(focus_states[-1],refid,ref.pid))
|
||||
inject('move',-790,590);time.sleep(.6)
|
||||
whole('final-H2-comparison',baseline,capture('h2-standard-after-click','HEADLESS-2'))
|
||||
save(lab,'standard-return',{'status':'STATIC_STANDARD_RETURN=PASS','workspace':standard_ws,'owner':measure(standard_owner),'scene':standard_scene,'H2_scene':standard_h2,'point':point,'events':events_standard,'pixels':analyze(standard_left),'H2_equal':True})
|
||||
assert all(p.poll() is None for p in [target,ref,filler,pointer])
|
||||
stage='completed'
|
||||
cmd('exit');compositor.wait(timeout=5);assert compositor.returncode==0
|
||||
except Exception as exc:
|
||||
failure=f'{type(exc).__name__}: {exc}';event('failure',message=failure)
|
||||
finally:
|
||||
finished.set();thread.join(timeout=1);stop_owned()
|
||||
for proc in processes:
|
||||
try:proc.wait(timeout=3)
|
||||
except subprocess.TimeoutExpired:
|
||||
os.killpg(proc.pid,signal.SIGKILL);proc.wait(timeout=3)
|
||||
try:os.killpg(proc.pid,signal.SIGKILL)
|
||||
except ProcessLookupError:pass
|
||||
for out in files:out.close()
|
||||
shutil.rmtree(runtime)
|
||||
save(lab,'runtime',{'status':'PASS' if failure is None else 'INCONCLUSIVE','failure':failure,'stopping_stage':stage,'duration_seconds':time.monotonic()-started,'process_exit_codes':[{'pid':x.pid,'argv':x.args,'exit':x.returncode} for x in processes],'runtime_removed':str(runtime),'cleanup':'owned groups reaped, runtime removed'})
|
||||
damage=[s for s in log('compositor').splitlines() if 'TEMPORAL_DAMAGE ' in s] if (run/'compositor.log').exists() else []
|
||||
save(lab,'damage-analysis',{'extents':damage,'all_frames':'NOT TESTED','PNG_native_buffer_identity':'NOT PROVEN','client_damage':'wl_surface_damage_buffer full SHM at actual commits; see client source/protocol logs'})
|
||||
print('STATIC_RGB_INPUT_STANDARD',failure or 'PASS')
|
||||
if failure:raise SystemExit(1)
|
||||
90
tooling/probes/step20c-static-rgb-input-retest/support.py
Normal file
90
tooling/probes/step20c-static-rgb-input-retest/support.py
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
"""Private probe support; no desktop environment or global process ownership.
|
||||
|
||||
WHY: failures must consume the same persistent experiment budget as successes.
|
||||
CONTRACT: a stable flock file serializes durable atomic ledger updates.
|
||||
INVARIANT: no reset/refund; only argv execution and owned process groups.
|
||||
"""
|
||||
import fcntl
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import signal
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
REPO = Path(__file__).resolve().parents[3]
|
||||
LIMITS = {'run': 1, 'capture': 12, 'camera': 1, 'visible_click': 1,
|
||||
'forbidden_click': 1, 'standard_click': 1, 'move': 8}
|
||||
|
||||
def sha(path):
|
||||
return hashlib.sha256(Path(path).read_bytes()).hexdigest()
|
||||
|
||||
def save(lab, name, value):
|
||||
(Path(lab)/'results'/(name+'.json')).write_text(json.dumps(value, indent=2)+'\n')
|
||||
|
||||
def reserve(lab, kind):
|
||||
lab = Path(lab)
|
||||
if kind not in LIMITS:
|
||||
raise ValueError(kind)
|
||||
with (lab/'budget.lock').open('a+') as lock:
|
||||
fcntl.flock(lock, fcntl.LOCK_EX)
|
||||
path = lab/'results/budget.json'
|
||||
state = json.loads(path.read_text()) if path.exists() else {
|
||||
'limits': LIMITS, 'used': dict.fromkeys(LIMITS, 0), 'events': []}
|
||||
# CONTRACT: corruption cannot create negative/extra capacity or erase history.
|
||||
valid = (state.get('limits') == LIMITS and set(state.get('used', {})) == set(LIMITS)
|
||||
and all(type(state['used'][k]) is int and 0 <= state['used'][k] <= LIMITS[k] for k in LIMITS)
|
||||
and len(state.get('events', [])) == sum(state['used'].values())
|
||||
and all(e.get('sequence') == i and e.get('kind') in LIMITS for i,e in enumerate(state.get('events', [])))
|
||||
and all(sum(e['kind'] == k for e in state['events']) == state['used'][k] for k in LIMITS))
|
||||
if not valid or state['used'][kind] >= LIMITS[kind]:
|
||||
raise RuntimeError('budget exhausted or changed: '+kind)
|
||||
state['used'][kind] += 1
|
||||
state['events'].append({'sequence': len(state['events']), 'kind': kind})
|
||||
fd, temporary = tempfile.mkstemp(prefix='.budget-', dir=path.parent)
|
||||
with os.fdopen(fd, 'w') as out:
|
||||
json.dump(state, out, indent=2); out.write('\n'); out.flush(); os.fsync(out.fileno())
|
||||
os.replace(temporary, path)
|
||||
fd = os.open(path.parent, os.O_RDONLY | os.O_DIRECTORY)
|
||||
try:
|
||||
os.fsync(fd)
|
||||
finally:
|
||||
os.close(fd)
|
||||
return state
|
||||
|
||||
def environment():
|
||||
env = {k:v for k,v in os.environ.items() if k not in (
|
||||
'SWAYSOCK','I3SOCK','WAYLAND_DISPLAY','DISPLAY','XDG_RUNTIME_DIR',
|
||||
'LD_LIBRARY_PATH','LD_PRELOAD','PKG_CONFIG_PATH')}
|
||||
env.update(LC_ALL='C', PYTHONDONTWRITEBYTECODE='1', MESON_ROOT_CMD='/bin/false')
|
||||
return env
|
||||
|
||||
def command(lab, label, argv, cwd=None, extra=None, timeout=600, allow_failure=False):
|
||||
lab=Path(lab); path=lab/'raw'/(label+'.log')
|
||||
assert not path.exists(), 'diagnostic already recorded: '+label
|
||||
with path.open('wb') as out:
|
||||
proc=subprocess.Popen(list(map(str,argv)),cwd=cwd,env={**environment(),**(extra or {})},
|
||||
stdout=out,stderr=subprocess.STDOUT,start_new_session=True)
|
||||
try:
|
||||
deadline=time.monotonic()+timeout
|
||||
while proc.poll() is None:
|
||||
if time.monotonic()>=deadline or path.stat().st_size>64*1024*1024:
|
||||
raise subprocess.TimeoutExpired(argv,timeout)
|
||||
time.sleep(.1)
|
||||
except subprocess.TimeoutExpired:
|
||||
os.killpg(proc.pid,signal.SIGTERM)
|
||||
try: proc.wait(timeout=3)
|
||||
except subprocess.TimeoutExpired:
|
||||
os.killpg(proc.pid,signal.SIGKILL);proc.wait()
|
||||
try: os.killpg(proc.pid,signal.SIGKILL)
|
||||
except ProcessLookupError: pass
|
||||
raise
|
||||
with (lab/'raw/commands.jsonl').open('a') as out:
|
||||
out.write(json.dumps({'label':label,'argv':list(map(str,argv)), 'cwd':str(cwd),
|
||||
'env':extra or {},'exit':proc.returncode})+'\n')
|
||||
assert path.stat().st_size < 64*1024*1024, 'output bound exceeded'
|
||||
if not allow_failure and proc.returncode:
|
||||
raise RuntimeError(f'{label} exit={proc.returncode}; inspect {path}')
|
||||
return path.read_text(errors='replace')
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
"""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'))
|
||||
before=(lab/'results/budget.json').read_bytes()
|
||||
assert not attempt((lab,'capture'))
|
||||
assert (lab/'results/budget.json').read_bytes()==before, 'failed exhaustion rewrote budget'
|
||||
corrupted=json.loads(before);corrupted['used']['run']=-1
|
||||
(lab/'results/budget.json').write_text(json.dumps(corrupted))
|
||||
assert not attempt((lab,'run')), 'negative capacity accepted'
|
||||
print('PASS offline ledger: concurrent reservations, exhaustion, durable failures, no refund')
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Offline requalification of actual RGB/input/return artifacts after the sole run.
|
||||
|
||||
WHY: shutdown IPC may lose its reply after cmd_exit terminates the compositor.
|
||||
CONTRACT: never rewrite runtime.json or claim the executed runner exited zero.
|
||||
INVARIANT: all scientific gates must independently pass; terminal transport is
|
||||
accepted only with completed measurements, explicit exit and compositor code 0.
|
||||
"""
|
||||
import argparse
|
||||
import copy
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
from PIL import Image
|
||||
from support import sha,save
|
||||
from ipc_predicates import (nodes,workspace,validate_preflight,measure_owner,
|
||||
scene_from_log,client_commit,validate_reference,validate_outputs,validate_camera)
|
||||
from pixels import compare,analyze,painted_geometry,click_points,delivery,button_pair,interior
|
||||
|
||||
|
||||
def check(lab,overrides=None):
|
||||
lab=Path(lab);overrides=overrides or {}
|
||||
def read(name):return overrides.get(name,json.loads((lab/'results'/(name+'.json')).read_text()))
|
||||
def image(name):return overrides.get(name,Image.open(lab/'run'/name).convert('RGB'))
|
||||
text=(lab/'run/compositor.log').read_text()
|
||||
snap=read('preflight-snapshot');cal=read('camera-calibration');measured=read('owner-geometry')['after']
|
||||
ref=read('reference-state');tid=measured['id'];rid=ref['con_id']
|
||||
owner=next(n for n in nodes(snap['tree']) if n['id']==tid)
|
||||
assert measured['raw'] in text and cal['after_scene']['raw'] in text
|
||||
measured=measure_owner(measured['raw'],owner)
|
||||
scene=scene_from_log(cal['after_scene']['raw'],'HEADLESS-1')
|
||||
clips=read('scene-clips');assert clips['H2']['raw'] in text
|
||||
h2=scene_from_log(clips['H2']['raw'],'HEADLESS-2')
|
||||
commit=read('live-preflight')['commit'];assert commit['raw'] in (lab/'run/target.log').read_text()
|
||||
commit=client_commit(commit['raw'])
|
||||
validate_preflight(snap,rid,ref['pid'],tid,owner['pid'],scene,h2,measured,commit)
|
||||
validate_camera(cal['before'],cal['after'],cal['delta'],scene)
|
||||
baseline=image('h2-reference-1.png');assert baseline.size==(800,600)
|
||||
assert all(p==(48,48,56) for p in baseline.get_flattened_data())
|
||||
rgb={}
|
||||
for name in ['h2-reference-2.png','h2-mosaic.png','h2-after-clicks.png','h2-standard.png','h2-standard-after-click.png']:
|
||||
im=image(name);assert im.size==(800,600)
|
||||
value=compare(baseline,im);assert value['equal'], ('whole H2 RGB',name)
|
||||
assert analyze(im)['count']==0
|
||||
rgb[name]={'changed':0,'PNG_sha256':sha(lab/'run'/name)}
|
||||
paint=painted_geometry(image('h1-mosaic.png'),measured,commit)
|
||||
points=click_points(image('h1-mosaic.png'),image('h2-mosaic.png'),measured)
|
||||
logs={n:(lab/'run'/(n+'.log')).read_text() for n in ['target','reference','filler']}
|
||||
button_lines={n:[s for s in t.splitlines() if s.startswith('BUTTON ')] for n,t in logs.items()}
|
||||
pairs={}
|
||||
for name,recipient,point in [('visible_click','target',points['permitted']),
|
||||
('forbidden_click','reference',points['forbidden']),
|
||||
('standard_click','target',None)]:
|
||||
click=read(name);pairs[name]=delivery(click['events'],recipient)
|
||||
assert all(row in button_lines[recipient] for row in click['events'][recipient]), 'event not in actual log'
|
||||
if point:assert tuple(click['global_point'])==point
|
||||
assert button_lines['target']==read('visible_click')['events']['target']+read('standard_click')['events']['target']
|
||||
assert button_lines['reference']==read('forbidden_click')['events']['reference'] and not button_lines['filler']
|
||||
focus={}
|
||||
for name,expected in [('after-visible',tid),('after-reference',rid),('standard',rid),('after-standard-click',tid)]:
|
||||
s=read(name+'-snapshot');assert s['focused']==[expected]
|
||||
validate_reference(s,rid,ref['pid']);validate_outputs(s['outputs'])
|
||||
if name in ['after-visible','after-reference']:
|
||||
assert workspace(s['tree'],'1')['mosaic']['rows'][0]['scroll_x']==cal['after']
|
||||
focus[name]=expected
|
||||
standard=read('standard-return');assert standard['status']=='STATIC_STANDARD_RETURN=PASS'
|
||||
stdsnap=read('standard-snapshot');ws=workspace(stdsnap['tree'],'1')
|
||||
assert ws['id']==workspace(snap['tree'],'1')['id'] and ws['layout']=='splith'
|
||||
stdowner=next(n for n in nodes(ws) if n['id']==tid)
|
||||
assert stdowner['visible'] and stdowner['pid']==owner['pid']
|
||||
for output,key in [('HEADLESS-1','scene'),('HEADLESS-2','H2_scene')]:
|
||||
sc=standard[key];assert sc['raw'] in text
|
||||
parsed=scene_from_log(sc['raw'],output);assert not parsed['clip_valid'] and parsed['layout_enum']==1
|
||||
std=image('h1-standard.png');paint_std=analyze(std);assert paint_std['count']>0
|
||||
x1,y1,x2,y2=paint_std['bbox'];assert paint_std['count']==(x2-x1)*(y2-y1)
|
||||
stdpoint=interior(std);assert read('standard_click')['global_point']==[stdpoint[0]-800,stdpoint[1]]
|
||||
assert analyze(image('h2-standard.png'))['count']==0
|
||||
runtime=read('runtime');assert runtime['duration_seconds']<150
|
||||
assert [r['exit'] for r in runtime['process_exit_codes']]==[0,-15,0,0,0], 'unexpected owned process exit'
|
||||
assert not Path(runtime['runtime_removed']).exists()
|
||||
for row in runtime['process_exit_codes']:
|
||||
assert not Path('/proc',str(row['pid'])).exists(), 'owned process remains'
|
||||
budget=read('budget');assert budget['used']==dict(run=1,capture=8,camera=1,visible_click=1,forbidden_click=1,standard_click=1,move=6)
|
||||
assert len(list((lab/'run').glob('*.png')))==8
|
||||
injections=[s for s in (lab/'run/pointer.log').read_text().splitlines() if s.startswith('INJECT ')]
|
||||
assert len(injections)==6 and sum('INJECT right ' in s for s in injections)==3
|
||||
events=[json.loads(s) for s in (lab/'run/events.jsonl').read_text().splitlines()]
|
||||
adjustments=[e for e in events if e['kind']=='ipc' and e['args']==['layout mosaic scroll '+str(cal['delta'])]]
|
||||
assert len(adjustments)==1
|
||||
assert 'wl_display' not in '\n'.join(s for t in logs.values() for s in t.splitlines() if '.error(' in s)
|
||||
assert not re.search(r'protocol error|INVALID_|MISSING_GLOBAL', '\n'.join(logs.values()), re.I)
|
||||
shutdown_ns=min(int(re.search(r'EXIT ns=(\d+)',t)[1]) for t in logs.values())
|
||||
last_capture=max(e['ns'] for e in events if e['kind']=='capture')
|
||||
assert shutdown_ns>last_capture, 'client transport failure before final measurements'
|
||||
terminal=False
|
||||
if runtime['status']!='PASS':
|
||||
assert runtime['stopping_stage']=='completed'
|
||||
assert runtime['failure'].startswith('CalledProcessError: Command') and "'exit']' returned non-zero exit status 1." in runtime['failure']
|
||||
assert "Handling command 'exit'" in text and 'Shutting down sway' in text
|
||||
assert all('error=32' in t.splitlines()[-1] for t in logs.values()), 'different terminal transport error'
|
||||
terminal=True
|
||||
return {'status':'PASS','scientific_gates':'ALL PASS','verdict':'FEASIBLE',
|
||||
'architecture':'STATIC_MOSAIC_RGB_INPUT_STANDARD_FEASIBLE_FOR_TESTED_MATRIX',
|
||||
'RGB':rgb,'H1':paint,'standard_H1':paint_std,'pairs':pairs,'focus':focus,
|
||||
'terminal_transport_incident':terminal,'runner_exit':1 if terminal else 0,
|
||||
'original_runtime_status_preserved':runtime['status'],
|
||||
'terminal_diagnosis':'completed measurements precede explicit compositor exit 0; client errno 32 is terminal EPIPE, not a wl_display protocol error'}
|
||||
|
||||
if __name__=='__main__':
|
||||
p=argparse.ArgumentParser();p.add_argument('lab',type=Path);p.add_argument('--record',action='store_true');a=p.parse_args()
|
||||
result=check(a.lab)
|
||||
# Negative post-run classification tests do not write any original observation.
|
||||
negatives=[]
|
||||
for name in ['runtime','standard-return','forbidden_click']:
|
||||
value=json.loads((a.lab/'results'/(name+'.json')).read_text());bad=copy.deepcopy(value)
|
||||
if name=='runtime':bad['process_exit_codes'][0]['exit']=1
|
||||
elif name=='standard-return':bad['scene']['clip_valid']=True;bad['scene']['raw']=bad['scene']['raw'].replace('clip_valid=0','clip_valid=1')
|
||||
else:bad['events']['target']=['BUTTON ns=1 serial=3 button=273 state=1']
|
||||
try:check(a.lab,{name:bad})
|
||||
except AssertionError:negatives.append(name+' REJECTED')
|
||||
else:raise AssertionError('postrun bad state accepted: '+name)
|
||||
changed=Image.open(a.lab/'run/h2-mosaic.png').convert('RGB');changed.putpixel((799,599),(48,48,57))
|
||||
try:check(a.lab,{'h2-mosaic.png':changed})
|
||||
except AssertionError:negatives.append('whole H2 RGB REJECTED')
|
||||
else:raise AssertionError('postrun changed H2 accepted')
|
||||
result['negative_tests']=negatives
|
||||
if a.record:save(a.lab,'postrun-validation',result)
|
||||
print('PASS actual RGB, scene, all real buttons/focus, standard return, budget and terminal diagnosis')
|
||||
print('PASS negative postrun classification',negatives)
|
||||
19
tooling/probes/step20c-static-rgb-input-retest/verify.py
Normal file
19
tooling/probes/step20c-static-rgb-input-retest/verify.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Read-only integrity checks; no remapping, rewriting, or budget refund."""
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from support import sha
|
||||
|
||||
|
||||
def verify_inventory(manifest):
|
||||
rows=Path(manifest).read_text().splitlines();assert rows
|
||||
seen=set()
|
||||
for row in rows:
|
||||
digest,p=row.split(' ',1);assert p not in seen;seen.add(p)
|
||||
assert sha(p)==digest,('manifest mismatch',p)
|
||||
return {'entries':len(rows),'sha256':sha(manifest)}
|
||||
|
||||
if __name__=='__main__':
|
||||
p=argparse.ArgumentParser();p.add_argument('lab',type=Path);a=p.parse_args()
|
||||
for name in ['fixture-freeze','source','evidence']:
|
||||
print(name,verify_inventory(a.lab/'results'/(name+'-sha256.txt')),'PASS')
|
||||
Loading…
Reference in a new issue