docs(mosaic): preserve static nested grab geometry fixture failure evidence
This commit is contained in:
parent
d0fa8c5c9e
commit
899c007c37
16 changed files with 1623 additions and 0 deletions
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
### STEP20C experimental evidence
|
||||
|
||||
- Preserved static nested-grab fixture failure: sole unchanged-binary run confirms identity/output/camera/tiled clip and exact whole-H2 pre-popup references, then fails before any click/grab on a native-SHM versus painted-height assertion. INCONCLUSIVE / UNDECIDED; additional IPC-y/titlebar assumption invalid, offline assertion audit insufficient. Raw runner exit1 and compositor exit0 retained; popup/standard gates NOT TESTED, no retry/build/production change. Historical runner and sealed artifacts preserved.
|
||||
|
||||
- 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.
|
||||
|
|
|
|||
|
|
@ -430,3 +430,16 @@ 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.
|
||||
|
||||
The preserved [static nested valid-grab probe](design/STEP20C_STATIC_NESTED_VALID_GRAB_PROBE.md)
|
||||
is **EXPERIMENTAL / INCONCLUSIVE**, architecture UNDECIDED. The sole unchanged-
|
||||
static-binary run verified actual identity, outputs, applied camera/tiled clip
|
||||
and two exact whole-H2 references, then stopped before any click or popup on
|
||||
an invalid painted-height assertion (573 vs native SHM320). An additional
|
||||
IPC-y/titlebar assertion was also invalid. The recorded offline test PASS had
|
||||
an insufficient source-assertion audit; raw runner exit1 and all evidence remain
|
||||
immutable. Root/nested grabs, geometry, popup pixels/input/dismissal and splith
|
||||
are NOT TESTED in this campaign. No algorithm/production/UX/package change or
|
||||
retry; budget exhausted. Correct/replay geometry predicates offline before a
|
||||
separately authorized fresh static popup gate. Historical failure evidence is frozen;
|
||||
STEP20C NEXT, production multi-output NOT YET SUPPORTED.
|
||||
|
|
|
|||
|
|
@ -371,3 +371,16 @@ 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.
|
||||
|
||||
The preserved [static nested valid-grab probe](design/STEP20C_STATIC_NESTED_VALID_GRAB_PROBE.md)
|
||||
is **EXPERIMENTAL / INCONCLUSIVE**, architecture UNDECIDED. The sole unchanged-
|
||||
static-binary run verified actual identity, outputs, applied camera/tiled clip
|
||||
and two exact whole-H2 references, then stopped before any click or popup on
|
||||
an invalid painted-height assertion (573 vs native SHM320). An additional
|
||||
IPC-y/titlebar assertion was also invalid. The recorded offline test PASS had
|
||||
an insufficient source-assertion audit; raw runner exit1 and all evidence remain
|
||||
immutable. Root/nested grabs, geometry, popup pixels/input/dismissal and splith
|
||||
are NOT TESTED in this campaign. No algorithm/production/UX/package change or
|
||||
retry; budget exhausted. Correct/replay geometry predicates offline before a
|
||||
separately authorized fresh static popup gate. Historical failure evidence is frozen;
|
||||
STEP20C NEXT, production multi-output NOT YET SUPPORTED.
|
||||
|
|
|
|||
|
|
@ -289,3 +289,16 @@ 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.
|
||||
|
||||
The preserved [static nested valid-grab probe](design/STEP20C_STATIC_NESTED_VALID_GRAB_PROBE.md)
|
||||
is **EXPERIMENTAL / INCONCLUSIVE**, architecture UNDECIDED. The sole unchanged-
|
||||
static-binary run verified actual identity, outputs, applied camera/tiled clip
|
||||
and two exact whole-H2 references, then stopped before any click or popup on
|
||||
an invalid painted-height assertion (573 vs native SHM320). An additional
|
||||
IPC-y/titlebar assertion was also invalid. The recorded offline test PASS had
|
||||
an insufficient source-assertion audit; raw runner exit1 and all evidence remain
|
||||
immutable. Root/nested grabs, geometry, popup pixels/input/dismissal and splith
|
||||
are NOT TESTED in this campaign. No algorithm/production/UX/package change or
|
||||
retry; budget exhausted. Correct/replay geometry predicates offline before a
|
||||
separately authorized fresh static popup gate. Historical failure evidence is frozen;
|
||||
STEP20C NEXT, production multi-output NOT YET SUPPORTED.
|
||||
|
|
|
|||
|
|
@ -375,3 +375,16 @@ 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.
|
||||
|
||||
The preserved [static nested valid-grab probe](design/STEP20C_STATIC_NESTED_VALID_GRAB_PROBE.md)
|
||||
is **EXPERIMENTAL / INCONCLUSIVE**, architecture UNDECIDED. The sole unchanged-
|
||||
static-binary run verified actual identity, outputs, applied camera/tiled clip
|
||||
and two exact whole-H2 references, then stopped before any click or popup on
|
||||
an invalid painted-height assertion (573 vs native SHM320). An additional
|
||||
IPC-y/titlebar assertion was also invalid. The recorded offline test PASS had
|
||||
an insufficient source-assertion audit; raw runner exit1 and all evidence remain
|
||||
immutable. Root/nested grabs, geometry, popup pixels/input/dismissal and splith
|
||||
are NOT TESTED in this campaign. No algorithm/production/UX/package change or
|
||||
retry; budget exhausted. Correct/replay geometry predicates offline before a
|
||||
separately authorized fresh static popup gate. Historical failure evidence is frozen;
|
||||
STEP20C NEXT, production multi-output NOT YET SUPPORTED.
|
||||
|
|
|
|||
389
docs/design/STEP20C_STATIC_NESTED_VALID_GRAB_PROBE.md
Normal file
389
docs/design/STEP20C_STATIC_NESTED_VALID_GRAB_PROBE.md
Normal file
|
|
@ -0,0 +1,389 @@
|
|||
# STEP20C static root/nested valid-grab continuity probe
|
||||
|
||||
**EXPERIMENTAL / INCONCLUSIVE; architecture UNDECIDED. Frozen failure evidence.** The protocol below was written before fixture implementation
|
||||
and graphical launch; completed observations are appended separately.
|
||||
Canonical [architecture](../ARCHITECTURE.md), [UX](../UX.md),
|
||||
[limitations](../KNOWN_LIMITATIONS.md) and [roadmap](../ROADMAP.md) govern.
|
||||
STEP20A DONE, STEP20B EXPERIMENTAL, STEP20C NEXT, STEP20D–G PLANNED;
|
||||
production multi-output NOT YET SUPPORTED. No algorithm or UX change.
|
||||
|
||||
## Prior evidence and exact question
|
||||
|
||||
Phase A froze the [static tiled RGB/input/standard evidence](STEP20C_STATIC_RGB_INPUT_STANDARD_RETEST.md)
|
||||
in commit d0fa8c5c9e6f878a0cf30c16c5ce006392838791, verified directly on
|
||||
Forgejo and GitHub with clean worktree. Its scientific verdict is FEASIBLE
|
||||
only for its tested tiled matrix; its runner exit 1 / runtime INCONCLUSIVE
|
||||
and compositor exit 0 remain immutable. No historical run is repeated.
|
||||
|
||||
The [popup ownership](STEP20C_POPUP_OWNERSHIP_PROBE.md),
|
||||
[valid-grab hidden-owner](STEP20C_VALID_GRAB_HIDDEN_OWNER_PROBE.md),
|
||||
[nested-grab](STEP20C_NESTED_VALID_GRAB_PROBE.md),
|
||||
[vertical](STEP20C_VERTICAL_OUTPUT_NESTED_GRAB_PROBE.md) and
|
||||
[R90 partial-popup](STEP20C_ROTATED_PARTIAL_POPUP_CLIP_PROBE.md) reports
|
||||
establish sampled shared-private behavior and controls, not static continuity.
|
||||
They distinguish invisible hits from legitimate outside-grab dismissal.
|
||||
No new pristine control run is authorized; comparison uses those retained
|
||||
controls. Static/shared exhaustive equivalence is NOT PROVEN.
|
||||
|
||||
Reuse only static candidate
|
||||
`/home/fy59/swayfx-mosaic-static-runtime-hm5kbdkt/stage-static/usr/bin/swayfx-mosaic`,
|
||||
SHA-256 `f82557c1a9978a5e9665b9b4291b74880253725359e3df1c5b8d554921f7603b`,
|
||||
build ID `942db64269640778ee433e5970ba1100bc60f94c`. Verify private identical
|
||||
0755 copy, ELF64 x86-64, ordinary clip definitions, no SceneFX DT_NEEDED,
|
||||
no missing relocation, and actual loaded system wlroots 0.20.2 SHA-256
|
||||
`7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837`.
|
||||
No compositor/library builds, binary modifications, system install or daily IPC.
|
||||
|
||||
## Client and XDG contract
|
||||
|
||||
Historical `step20c-nested-grab/nested-client.c` must remain byte-identical,
|
||||
SHA-256 `2e40c98af72a0df8b316810589a0be8f4425a763db1de448b49a7813956b6f7d`.
|
||||
Retain it separately from the adapted copy and private placement patch.
|
||||
Reuse the exact valid-grab pointer daemon and static-reference/saved-client
|
||||
sources. Reuse manifest-verified generated protocol files; retain XML hashes
|
||||
and verify regeneration byte-for-byte. Compile only clients as C17 with
|
||||
-Werror (-Wno-unused-parameter for existing listener signatures).
|
||||
|
||||
The [XDG protocol](https://raw.githubusercontent.com/wayland-mirror/wayland-protocols/main/stable/xdg-shell/xdg-shell.xml)
|
||||
and installed XML require a user-action serial, mapped parent, grabbing popup
|
||||
parent for B, pre-map grab, bufferless initial commit, configure/ACK before
|
||||
buffer attachment, and reverse destruction. Client binds wm_base v3. A uses
|
||||
the exact TOPLEVEL left press serial; B uses a new POPUP_A left press serial.
|
||||
Both logs and WAYLAND_DEBUG requests must agree; nonzero serial alone fails.
|
||||
A/B have distinct wl_surface, xdg_surface and popup roles, with B.parent=A.xdg.
|
||||
No arbitrary serial, forced grab or protocol workaround is permitted.
|
||||
|
||||
The only private C delta adds placement `static_cross`: A anchor (100,80),
|
||||
size 350×170; B anchor (100,60), size 300×130. Both 1×1 anchors are inside
|
||||
parent effective SHM geometry (TOPLEVEL 420×320; A 350×170). Anchor top-left,
|
||||
gravity bottom-right, default constraint adjustment none. No draw/grab/serial/
|
||||
ACK/map/dismissal/destruction change. The toplevel keeps historical 420×320
|
||||
buffer; assigned container content is measured separately. Source constants
|
||||
and actual wl_surface.attach/protocol sizes qualify drawn dimensions.
|
||||
|
||||
For applied top content near (-318,25), predicted A=(-218,105,350,170),
|
||||
B=(-118,165,300,130). Predictions are not evidence. Parse actual role configure,
|
||||
measure owner scene and content offset, then independently correlate solid
|
||||
paint and pointer-local coordinates. Require A.left<0<A.right and
|
||||
B.left<0<B.right. Never vary anchors empirically after launch.
|
||||
|
||||
## Scene contract and observability
|
||||
|
||||
Exact candidate source parents root popup wrapper under root->layers.popup,
|
||||
positions it from the applied view content tree, and derives its local clip
|
||||
from current workspace's already-applied tiling clip and output intersection.
|
||||
B's scene wrapper descends from A's XDG tree, retaining the same owner.
|
||||
Source trace PI_OWNER distinguishes logical/current rectangle from scene;
|
||||
PI_SCENE reports applied tiling clip; PI_POPUP reports root wrapper clip;
|
||||
PROBE_POPUP_CLIP records owner pointer/local clip. Root wrapper origin differs
|
||||
from A's configured painted origin. Correlate root local clip + applied content
|
||||
origin with global [-800,0,800,600]. Child effective clip is inherited, backed
|
||||
by exact source hierarchy and measured B pixels/input; no new child-node or
|
||||
atomic-generation instrumentation may be claimed. Standard root clip removal
|
||||
must appear in PI_POPUP, and B standard rendering/input must exercise inheritance
|
||||
removal. Missing direct child trace is documented, not manufactured.
|
||||
|
||||
## Offline preflight and resource ledger
|
||||
|
||||
Before run reservation: source/protocol manifests, C17 syntax/build, binary/
|
||||
loader identity, AST/shell/links, source audit (XDG hierarchy, input mapping,
|
||||
configure/ACK/map, child-first destruction, clipping and shutdown), deterministic
|
||||
color/coordinate/geometry/button predicates and negative cases, dismissal
|
||||
classification and exit-classification tests all PASS. Live predicates are the
|
||||
same functions used offline. Record `STATIC_NESTED_GRAB_FIXTURE_PREFLIGHT=PASS`.
|
||||
No graphical launch to discover an offline-detectable error.
|
||||
|
||||
One exclusive persistent campaign owner file and one LAB outside Git. Stable
|
||||
flock serializes fsync/atomic ledger updates; reservations persist before run,
|
||||
chain, grab, click, pointer positioning, PNG, camera and transition. Failures
|
||||
consume reservations; no refunds/reset/second LAB. Limits: run 1, pristine 0,
|
||||
SceneFX/Mosaic builds 0, chains 2, grabs 4, camera 2 (at most one per chain),
|
||||
Mosaic→splith 1, complete clicks 9, PNG 16, positioning/injection actions 24,
|
||||
run duration 150 seconds. Planned 8 clicks and 15 PNGs; every click includes
|
||||
and consumes one positioning action. No hide/return, scale/rotation/resize/
|
||||
migration or additional campaign. Watchdog bounds owned process groups/logs;
|
||||
all command timeouts and cleanup fit inside 150 seconds.
|
||||
|
||||
## Ordered runtime protocol
|
||||
|
||||
One headless compositor, private short runtime mode 0700, private Wayland and
|
||||
explicit swaymsg -s socket. Clear inherited display/IPC/library/session overrides.
|
||||
H1=(-800,0,800,600); H2=(0,0,800,600); scale1/normal. focus_follows_mouse no.
|
||||
Create static H2 reference, discover PID/con_id, apply only its `[con_id=ID]
|
||||
border none. Validate empty deco_rect, 800×600 content, visible XDG identity,
|
||||
output/workspace; stored border width need not be zero. H1 filler may share
|
||||
App ID, but never identity/PID/ancestor. Establish Mosaic, owner and H1 focus
|
||||
before two H2 baselines separated by >=500 ms. Park private pointer and require
|
||||
all 480,000 RGB pixels #303038 and exact equality. Stop dependents if unstable.
|
||||
|
||||
E1: reserve a fresh chain, map owner; compute signed relative camera delta from
|
||||
actual PI_OWNER/content rectangle to center assigned content at x=0. Reserve
|
||||
at most one correction; validate applied camera, same owner/workspace and clip.
|
||||
Capture actual blue pixels, choose interior point, reserve grab and full left
|
||||
click, open A using received press. Require configure/ACK/map; capture A, select
|
||||
actual yellow interior, reserve second grab/left click to open B. Capture H1/H2;
|
||||
require both crossings, positive A/B paint H1, zero A/B H2 and whole-H2 equality.
|
||||
Choose B visible point from actual green pixels and inject right; require B pair,
|
||||
no reference/filler event, no prior popup_done. Choose H2 point within measured
|
||||
B continuation and actual reference, inject right; no invisible A/B button may
|
||||
arrive. Observe reference pair and topmost-first popup_done separately; legitimate
|
||||
dismissal is not an invisible hit. Verify clean H2, close E1 owner normally.
|
||||
|
||||
E2: new process, owner IDs and serials in the same compositor. Reserve chain,
|
||||
verify Mosaic workspace, recompute sole possible camera correction, open A/B
|
||||
with new actual left presses. Capture clipped state and whole H2, then send
|
||||
`[con_id=E2] layout splith` without exterior click. Verify same workspace,
|
||||
actual applied splith, both still mapped, no prior popup_done, tile/root clip
|
||||
absent. Capture both outputs: require actual A/B crossings and positive A/B
|
||||
colors H2, not empty-reference equality during intentional popup spill. Right
|
||||
click actual green H2 interior must go to B. Send historical `close_b` command;
|
||||
require B destruction, A survives, capture H2 and right click actual yellow
|
||||
interior to A. Close owner normally; final H2 must fully equal baseline with
|
||||
zero A/B residue. No stale IDs or points are reused across stages.
|
||||
|
||||
## Closure, classification and preservation
|
||||
|
||||
Record completion of all scientific gates before terminal exit. Audit
|
||||
sway/commands/exit.c, ipc-server.c and main.c: termination may close socket before
|
||||
queued writable reply. Keep swaymsg stdout, stderr and real code separately;
|
||||
wait for compositor code and shutdown log, clients and residual groups/sockets.
|
||||
Nonzero exit IPC is nonblocking transport only when all prior measures pass,
|
||||
server handled exit, compositor=0, no crash/protocol error or rescue cleanup.
|
||||
Never force code0 or claim IPC success when transport failed. Earlier functional
|
||||
failure remains fatal to scientific qualification, regardless of shutdown.
|
||||
|
||||
FEASIBLE requires all listed identity/reference/XDG/grab/crossing/render/input/
|
||||
dismissal/standard/cleanup gates in this matrix. PARTIALLY_FEASIBLE retains a
|
||||
working main chain with missing secondary measurements; INCONCLUSIVE means
|
||||
fixture attribution, geometry or protocol sequence not exercised. BLOCKED needs
|
||||
actual visual/input leak, grab violation, runtime ABI/crash or standard regression.
|
||||
Architecture is STATIC_NESTED_VALID_GRAB_FEASIBLE_FOR_TESTED_MATRIX,
|
||||
STATIC_NESTED_VALID_GRAB_REQUIRES_REDESIGN, or UNDECIDED respectively.
|
||||
|
||||
Retain raw exact source/patch/runner/config, WAYLAND_DEBUG, timestamps/injections,
|
||||
IPC/scene/client logs, actual codes, PNGs, dependency maps and source/evidence
|
||||
SHA-256 inventories. Seal only after collection; verify every entry read-only.
|
||||
Available damage extents and stabilized capture changes are partial evidence:
|
||||
no intermediate-frame/minimal-damage/native-buffer/GPU-texture guarantee.
|
||||
Hidden owner, scaling/rotation, migration/resize, saved buffers, effects,
|
||||
complete damage/transactions and exhaustive ABI are NOT TESTED in this campaign.
|
||||
Synchronize this report, fixture README, architecture, limitations, roadmap,
|
||||
document index and changelog. B stays local and uncommitted, never pushed.
|
||||
|
||||
## Offline preparation completed before run reservation
|
||||
|
||||
LAB `/home/fy59/swayfx-mosaic-static-nested-grab-m0a5e2qh`, exclusive owner
|
||||
`/home/fy59/swayfx-mosaic-static-nested-grab-v1.owner`. Exact candidate/0755/
|
||||
ELF/build ID/ordinary symbols/ldd -r/system wlroots 0.20.2 pass. Three client-only
|
||||
C17 syntax and executable builds pass with -Werror; both protocol regenerations
|
||||
are byte-identical to manifest-verified inputs. Original nested SHA matches.
|
||||
Private placement patch SHA-256
|
||||
`e0e87fb0cb3d6e7dafb1cd59ccb50f323f2934e65247526199da1695487d3e4a`;
|
||||
adapted C SHA-256
|
||||
`6eafb8ad63b0b06b8297e7e72711056a6d42bdf33ccf795793eeda77f98221c7`.
|
||||
Exact frozen live runner SHA-256
|
||||
`e2bde767036096e4633f9dd4fe3deb1b147f6415dc550a46802a6a90c4bd6486`.
|
||||
|
||||
First offline check stopped on a negative with the original button deliberately
|
||||
removed: the shared predicate correctly rejected it via ValueError, but the
|
||||
test harness initially counted only assertion/runtime errors. Correct only
|
||||
that harness in `LAB/preflight-correction/check.py`; retain immutable original
|
||||
`LAB/fixture/check.py` and the incident JSON. No live predicate/runner change,
|
||||
no budget reservation or graphical launch occurred. The corrected checker
|
||||
then passes retained actual standard-control protocol, actual historical
|
||||
border-none/stored-width2 reference, labelled synthetic geometry/paint/input,
|
||||
16 negatives, ledger concurrency/exhaustion/corruption and terminal outcome
|
||||
positives/five negatives. AST/shell/links and exact frozen inventory pass.
|
||||
All live predicates are the same frozen functions exercised offline.
|
||||
|
||||
`STATIC_NESTED_GRAB_FIXTURE_PREFLIGHT=PASS`.
|
||||
Runtime remains NOT TESTED at this point; reserve the sole run only afterward.
|
||||
|
||||
|
||||
## Completed single runtime — 2026-10-13
|
||||
|
||||
`SWAYFX_MOSAIC_STATIC_NESTED_VALID_GRAB_PROBE=INCONCLUSIVE`.
|
||||
Architecture: **UNDECIDED**. This is a fixture geometry failure before the first
|
||||
popup, not a demonstrated clipping, grab, ABI or standard-layout defect.
|
||||
The sole compositor run is consumed; no retry or second laboratory. The
|
||||
intended two-chain experiment was **not completed** and must not be described
|
||||
as popup evidence. No new Mosaic/SceneFX build, binary/algorithm/shader change,
|
||||
production/UX/package change or installation occurred.
|
||||
|
||||
### Actual identity, output, focus and reference
|
||||
|
||||
Original and private executable retain SHA-256
|
||||
`f82557c1a9978a5e9665b9b4291b74880253725359e3df1c5b8d554921f7603b`,
|
||||
build ID `942db64269640778ee433e5970ba1100bc60f94c`, mode0755, ELF64 x86-64.
|
||||
Ordinary clip definitions and ldd -r pass; no SceneFX DT_NEEDED/RPATH/RUNPATH
|
||||
or loaded shared SceneFX. /proc/exe resolves to the private copy; actual /proc
|
||||
maps show system wlroots 0.20.2 SHA-256
|
||||
`7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837`.
|
||||
All protected production/system/package/UX/AGENTS/binary hashes pass afterward.
|
||||
|
||||
Actual outputs match H1=(-800,0,800,600), H2=(0,0,800,600), scale1/normal.
|
||||
Reference ID7/PID1737643 belongs to workspace2 ID5/H2; only its discovered
|
||||
con_id receives border none. Stored width2 persists, deco_rect empty,
|
||||
rect/window_rect 800×600, visible xdg_shell, correct app_id/PID/output.
|
||||
Filler PID1737649 is distinct despite the same app_id. E1 owner ID9/PID1737655
|
||||
belongs to workspace1 ID6/H1, focused and mapped. IDs were discovered, not
|
||||
hard-coded. Only one chain instance was reserved/mapped.
|
||||
|
||||
After E1 map, applied camera was 480. PI_OWNER measured scene(-640,0,640,600),
|
||||
content x=-638, assigned content636×573. Desired content x=-318 yields the
|
||||
sole relative correction **-320**, resulting in camera160, applied owner
|
||||
scene(-320,0,640,600), content x=-318. PI_SCENE reports local tile(-160,0),
|
||||
clip(160,0,800,600); with H1 origin this gives [-800,0)×[0,600).
|
||||
H2 tiling clip is absent. No popup root or child clip was measured.
|
||||
|
||||
Two independent 800×600 reference PNGs were captured after setup/H1 focus and
|
||||
pointer parking, >=500ms apart (requested extra delay600ms). Every RGB pixel
|
||||
is #303038; zero differences, no mask or tolerance. Both PNG SHA-256:
|
||||
`4471e93ccb6cf3ff4970f61fce405431a228af598d2f1f030a3b7586962ecd7a`.
|
||||
This establishes pre-popup reference stability only. No popup-phase whole-H2
|
||||
capture exists. The third PNG, E1-top, has 182,214 actual blue pixels, box
|
||||
[482,25,800,598) in H1 local coordinates, giving global visible x[-318,0),
|
||||
y[25,598). It does not establish popup clipping or popup geometry.
|
||||
|
||||
### Runtime fixture failure and source-based diagnosis
|
||||
|
||||
The frozen runner stops at `E1-open-A`, **before any click/grab reservation**.
|
||||
Line182 asserts painted height320 from the native SHM constants. The actual
|
||||
client creates a 420×320 SHM buffer, then receives and ACKs configure636×573
|
||||
without redrawing (historical client behavior). SwayFX
|
||||
`output_configure_scene` in the exact candidate's sway/desktop/output.c:331
|
||||
sets the tiled scene buffer destination from applied content width/height.
|
||||
The captured blue height is573, consistent with this source path, not320.
|
||||
The failed assertion is therefore an invalid fixture assumption.
|
||||
|
||||
An additional assertion at line183 was not reached: it requires painted y
|
||||
= owner scene y + IPC window_rect.y. Exact sway/ipc-json.c intentionally
|
||||
sets window_rect.y=0 when a titlebar is enabled; actual IPC has that0,
|
||||
deco height25 and painted y25. The prior static retest already distinguished
|
||||
native buffer y from window_rect.y. This source-visible error should also
|
||||
have been caught before the run. No rendered-buffer/IPC equivalence contract
|
||||
is invented to make the fixture pass.
|
||||
|
||||
The recorded preflight marker remains PASS for its actually executed tests.
|
||||
Its assertion/source audit was **INSUFFICIENT / retrospective FAIL** for these
|
||||
two live assertions; it did not satisfy the full intended offline gate.
|
||||
No retrospective rewrite of offline-gate.json, runtime.json, frozen runner,
|
||||
protocol or budget is made. Preparation's missing-origin negative-case harness
|
||||
incident and this runtime geometry incident are separate. The full client
|
||||
protocol buffer/configure/ACK/map, IPC, PI_OWNER/PI_SCENE and PNG are retained.
|
||||
`geometry_audit.py` reproduces both invalid assumptions read-only, verifies
|
||||
actual reference/camera/clip/paint/codes and confirms zero popup creation,
|
||||
zero pointer button and zero grab. This offline diagnosis is not graphical
|
||||
qualification. The original runner remains byte-identical and is preserved
|
||||
as failed experimental evidence; it is not a ready replay fixture.
|
||||
|
||||
### Unexercised protocol, geometry and input
|
||||
|
||||
The historical nested C source remains byte-identical; the sole private delta
|
||||
is the legal static_cross placement. Original C and adapted C/patch are retained
|
||||
with distinct hashes. The source hierarchy and historical control/replay show
|
||||
correct XDG parentage, real button serial use and child-first destruction.
|
||||
In this new runtime, however, **A and B were never created**. No A/B configure,
|
||||
ACK, MAP, grab request, serial, enter/button, popup_done or destruction exists.
|
||||
Neither predicted crossing is a measurement. E2 was never reserved or spawned;
|
||||
no standard transition, standard popup pixels/input or popup residue cleanup
|
||||
sample was exercised. All those matrix cells are NOT TESTED.
|
||||
|
||||
### Terminal exit and owned cleanup
|
||||
|
||||
Runtime duration5.485246415s; actual runner exit1, runtime INCONCLUSIVE,
|
||||
measurements_completed=false, watchdog=false. The finally path explicitly sends
|
||||
exit on /tmp/msng-jfnc_v3o's private IPC socket; stdout empty, stderr
|
||||
`Unable to receive IPC response`, actual swaymsg code1. Server log records
|
||||
Handling command exit and Shutting down sway; compositor exits0. E1, filler,
|
||||
reference and pointer all exit0, with normal owned cleanup and no rescue signal.
|
||||
No wl_display.error/protocol-error event appears. Ordinary startup backend
|
||||
errors/noise are retained; no compositor crash is observed.
|
||||
|
||||
The command's queued response may have been lost at socket closure, compatible
|
||||
with audited source. It is **not accepted as a nonblocking scientific exception**:
|
||||
the earlier geometry failure occurred before measurements completed. The
|
||||
terminal classifier correctly retains scientific_cleanup_pass=false. Composite
|
||||
shutdown/cleanup success does not turn this campaign into FEASIBLE.
|
||||
All owned PIDs/process groups are gone; short runtime removed, no sockets left.
|
||||
No daily compositor/socket/process was touched.
|
||||
|
||||
### Budget, damage and retained inventories
|
||||
|
||||
| Resource | Consumed / ceiling |
|
||||
| --- | --- |
|
||||
| Static compositor runs | 1/1 |
|
||||
| Pristine / SceneFX build / Mosaic build | 0/0 each |
|
||||
| Chain instances | 1/2; E1 toplevel only |
|
||||
| Valid-grab reservations | 0/4 |
|
||||
| Camera corrections | 1/2; E1 only |
|
||||
| Mosaic→splith | 0/1 |
|
||||
| Complete clicks | 0/9 |
|
||||
| Pointer positioning | 1/24 |
|
||||
| PNGs | 3/16 |
|
||||
| Runtime seconds | 5.485246415/150 |
|
||||
|
||||
Unused chain/click/PNG counters cannot authorize another compositor run.
|
||||
The durable ledger remains consumed, with no refund/reset/retry. Available
|
||||
TEMPORAL_DAMAGE extents cover only setup, camera, captures and shutdown;
|
||||
A/B appearance, partial popup clipping, outside dismissal/destruction, standard
|
||||
return and disappearance damage are NOT TESTED. Only stabilized reference
|
||||
RGB is compared. No all-frame/minimal-damage/native-PNG/GPU guarantee.
|
||||
|
||||
Frozen pre-run fixture and source inventories remain immutable. Collector and
|
||||
read-only diagnostic are preserved separately in LAB/postrun-fixture; corrected
|
||||
offline harness in LAB/preflight-correction. Both are included in source sealing.
|
||||
Source:30 entries, SHA-256
|
||||
`9b76aae162faa631bf1930677d484359dfc3e29ace237b9aee2daa3d5cd68dc4`.
|
||||
Evidence:96 entries, SHA-256
|
||||
`5750d1e74636c2d276800f795ed0214c3e12999908149855af0b382a2da9bfb4`.
|
||||
Every entry is verified read-only; reports and closeout receipts live outside
|
||||
the sealed inventories to avoid circular hashes. No generated binary/PNG/JSON/
|
||||
protocol/build/socket/log is added to Git.
|
||||
|
||||
### Architectural answers for this campaign
|
||||
|
||||
| Question | Evidence-backed answer |
|
||||
| --- | --- |
|
||||
| Q1 A/B real button serial? | NOT TESTED; zero runtime buttons or grabs. Historical source/control passed offline. |
|
||||
| Q2 distinct compliant grabs? | NOT TESTED; no A/B objects in the new run. |
|
||||
| Q3 hierarchy correct? | Correct in exact source and retained historical control; runtime A→B NOT TESTED. |
|
||||
| Q4 both really cross x=0? | NOT EXERCISED. Requested anchors and predicted boxes are not configured geometry. |
|
||||
| Q5 clip from applied owner? | Tiled applied viewport measured; popup root/child NOT TESTED. |
|
||||
| Q6 permitted A/B pixels? | NOT TESTED; only toplevel blue182214 measured. |
|
||||
| Q7 forbidden popup pixels absent H2? | NOT TESTED; two pre-popup H2 references stable. |
|
||||
| Q8 visible B input? | NOT TESTED. |
|
||||
| Q9 invisible B events? | NOT TESTED; no popup/input exists, not proof of exclusion. |
|
||||
| Q10 outside grab behavior? | NOT TESTED; historical controls distinguish dismissal from hit. |
|
||||
| Q11 popup_done distinct from invisible button? | Yes as protocol/measurement categories; no new dismissal or button sample. |
|
||||
| Q12 A/B survive splith E2? | NOT TESTED; E2 and transition not executed. |
|
||||
| Q13 clips removed? | NOT TESTED for popup or standard transition. |
|
||||
| Q14 standard popup colors on H2? | NOT TESTED. |
|
||||
| Q15 standard A/B buttons? | NOT TESTED. |
|
||||
| Q16 popup residues after closure? | NOT TESTED because none opened; process/socket cleanup PASS separately. |
|
||||
| Q17 static regression versus private shared controls? | UNDECIDED for popups; no valid static comparison was obtained. |
|
||||
| Q18 algorithm change justified? | No. Confirmed errors are fixture assertions; no clipping defect demonstrated. |
|
||||
| Q19 matrices open? | Static hidden-owner, scale, rotation, resize/migration, saved-buffer/effects, full damage/transactions and exhaustive ABI. Historical sampled evidence is not replayed or promoted. |
|
||||
| Q20 smallest next gate? | First correct and replay all geometry predicates offline against actual native/configured/painted/IPC observations, including titlebar and standard states; then a separately authorized fresh one-run static two-chain gate. Current run budget is exhausted. |
|
||||
|
||||
Validation PASS: original nested hash, placement-only delta, C17/client builds,
|
||||
protocol byte identity, binary/loader/maps, outputs/focus/camera/tiled clip,
|
||||
reference whole RGB, actual paint diagnosis, owned codes/cleanup, protected
|
||||
hashes, manifests, AST/shell/links and diff. FAIL: full preflight assertion audit
|
||||
and the exact runtime geometry assertion; runner code1. NOT RUN/NOT TESTED:
|
||||
new pristine/compositor/library builds and all new popup/input/standard gates.
|
||||
No subagent review is claimed; final local delta/source/evidence audit is by the
|
||||
parent. This historical report and source fixture are now frozen for publication
|
||||
in the failure-freeze phase. A separate offline geometry gate remains local
|
||||
and cannot change this historical verdict or authorize another run.
|
||||
STEP20C remains NEXT, production multi-output NOT YET SUPPORTED.
|
||||
|
||||
## Failure evidence freeze — 2026-10-13
|
||||
|
||||
Read-only revalidation verifies all24/30/96 entries of the historical inventories
|
||||
and reproduces both invalid assertions without launching a compositor. AST,
|
||||
Shell blocks, Markdown links, source-only Git content and exact distinctions
|
||||
between executed, corrected-checker and postrun sources pass. The executed
|
||||
runner SHA and raw runtime/terminal codes remain unchanged. The single commit
|
||||
for this freeze preserves INCONCLUSIVE / UNDECIDED and all NOT TESTED cells.
|
||||
No historical run, ledger, manifest, client source or binary is modified.
|
||||
87
tooling/probes/step20c-static-nested-grab/README.md
Normal file
87
tooling/probes/step20c-static-nested-grab/README.md
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
# Static nested valid-grab fixture
|
||||
|
||||
EXPERIMENTAL / FROZEN FAILURE EVIDENCE. No production integration.
|
||||
Read the [protocol](../../../docs/design/STEP20C_STATIC_NESTED_VALID_GRAB_PROBE.md).
|
||||
One exclusive laboratory, one existing immutable static binary, client-only
|
||||
C17 builds; no SceneFX/Mosaic build or pristine run. Original nested source is
|
||||
retained separately; `placement.patch` adds only legal static_cross anchors.
|
||||
|
||||
`prepare.py` verifies identity/provenance and compiles clients outside Git.
|
||||
`check.py` exercises actual historical observations and labelled synthetic
|
||||
negative fixtures, ledger concurrency/exhaustion/corruption, lifecycle and exit
|
||||
classification before run reservation. `run.py` shares those predicates,
|
||||
consumes reservations before every action, and preserves real terminal codes.
|
||||
`collect.py` records only measured states and seals inventories once.
|
||||
|
||||
```sh
|
||||
PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-static-nested-grab/prepare.py
|
||||
# Use only the LAB printed and registered by prepare; no second LAB or reset.
|
||||
PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-static-nested-grab/check.py "$LAB"
|
||||
# Only after STATIC_NESTED_GRAB_FIXTURE_PREFLIGHT=PASS:
|
||||
PYTHONDONTWRITEBYTECODE=1 python "$LAB/fixture/run.py" "$LAB"
|
||||
PYTHONDONTWRITEBYTECODE=1 python "$LAB/fixture/collect.py" "$LAB"
|
||||
```
|
||||
|
||||
Budgets: 1 run, 2 chains, 4 grabs, 2 calculated camera corrections (one per
|
||||
chain), 1 splith transition, 9 complete clicks, 16 PNGs, 24 positions, 150 s.
|
||||
Failed actions consume budget. Private sockets/groups only, no desktop IPC.
|
||||
Generated protocol/client binaries/logs/JSON/PNG/build files stay outside Git.
|
||||
Fixture preflight success is not graphical evidence. Results appended after
|
||||
collection; every unexecuted matrix cell remains NOT TESTED.
|
||||
|
||||
|
||||
## Completed campaign: INCONCLUSIVE, do not rerun
|
||||
|
||||
LAB `/home/fy59/swayfx-mosaic-static-nested-grab-m0a5e2qh`; run exhausted1/1.
|
||||
Only E1 toplevel mapped: two stable full #303038 H2 images, computed camera
|
||||
480→160 by -320, measured applied tiled clip, H1 blue182214 pixels. No popup,
|
||||
click or grab occurred. Runner exits1 on its painted-height320 assertion;
|
||||
actual height573. A further IPC window_rect.y=0/titlebar y25 assertion was
|
||||
invalid but unreached. The original run.py here and LAB/fixture remain exact
|
||||
failed evidence, **not a qualified replay runner**. Before any fresh separately
|
||||
authorized campaign, repair/replay all geometric assertions offline. No runtime
|
||||
retry is permitted by this ticket; unused chain/click/capture capacity is not
|
||||
a new run authorization. The recipes above describe the preparation/run that
|
||||
was attempted, not permission to repeat it.
|
||||
|
||||
`check.py` now counts ValueError from a deliberately removed-button negative
|
||||
as rejection; the initial immutable checker is retained in LAB/fixture and the
|
||||
corrected preflight checker in LAB/preflight-correction. Recorded preflight PASS
|
||||
covers its tests, but the full geometric source/assertion audit was insufficient.
|
||||
No historical gate/runtime status or manifest is rewritten. `geometry_audit.py`
|
||||
rechecks actual SHM/configure/ACK/map, scene/IPC/paint, reference RGB, zero
|
||||
popup/button/grab and terminal codes without launching a graphical process.
|
||||
Post-run collector/diagnostic copies are in LAB/postrun-fixture, separate from
|
||||
the executed fixture. Source/evidence inventories include these distinctions.
|
||||
|
||||
Terminal swaymsg actual1 (response receive failure), compositor0, all four
|
||||
clients/injector0, no rescue or residual process/socket. Earlier measurement
|
||||
failure is preserved; shutdown success cannot qualify scientific success.
|
||||
Budget: run1, chain1, grab0, camera1, transition0, click0, move1, PNG3,
|
||||
5.485246415 seconds. New Mosaic/SceneFX builds0, pristine runs0.
|
||||
Source30 entries SHA256
|
||||
`9b76aae162faa631bf1930677d484359dfc3e29ace237b9aee2daa3d5cd68dc4`;
|
||||
evidence96 entries SHA256
|
||||
`5750d1e74636c2d276800f795ed0214c3e12999908149855af0b382a2da9bfb4`.
|
||||
Both are sealed once and verified. Root/nested input, crossings, dismissal,
|
||||
standard-return/input/residues and related damage remain NOT TESTED.
|
||||
|
||||
Read-only completed-lab checks (never rerun collect or regenerate manifests):
|
||||
|
||||
```sh
|
||||
PYTHONDONTWRITEBYTECODE=1 python "$LAB/postrun-fixture/geometry_audit.py" "$LAB"
|
||||
PYTHONDONTWRITEBYTECODE=1 python - "$LAB" <<'PYCODE'
|
||||
import sys
|
||||
from pathlib import Path
|
||||
lab = Path(sys.argv[1])
|
||||
sys.path.insert(0, str(lab / 'postrun-fixture'))
|
||||
from support import verify
|
||||
for name in ['fixture-freeze', 'source', 'evidence']:
|
||||
print(name, verify(lab / 'results' / (name + '-sha256.txt')))
|
||||
PYCODE
|
||||
```
|
||||
|
||||
Verdict SWAYFX_MOSAIC_STATIC_NESTED_VALID_GRAB_PROBE=INCONCLUSIVE.
|
||||
Architecture UNDECIDED. STEP20C NEXT; multi-output NOT YET SUPPORTED.
|
||||
This historical failure fixture is frozen for publication. The separate new
|
||||
offline geometry-review fixture remains local, uncommitted and unpublished.
|
||||
124
tooling/probes/step20c-static-nested-grab/check.py
Normal file
124
tooling/probes/step20c-static-nested-grab/check.py
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Offline protocol/pixel/ledger/terminal tests; never starts a compositor.
|
||||
|
||||
Historical observations and synthetic negative cases are explicitly labelled.
|
||||
"""
|
||||
import ast
|
||||
import concurrent.futures
|
||||
import copy
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from PIL import Image
|
||||
from support import initial,reserve,LIMITS,save,sha,verify,OWNER,REPO,NESTED_SHA
|
||||
from predicates import protocol,geometry,pair,dismissal,terminal,point,reference,root_clip,popup_pixels,Defect
|
||||
from ipc_predicates import validate_reference,validate_outputs
|
||||
|
||||
def attempt(args):
|
||||
lab,k=args
|
||||
try:reserve(lab,k);return True
|
||||
except RuntimeError:return False
|
||||
|
||||
def rejected(name,func):
|
||||
try:func()
|
||||
except (AssertionError,Defect,RuntimeError,ValueError):return name
|
||||
raise AssertionError('negative accepted: '+name)
|
||||
|
||||
if __name__=='__main__':
|
||||
lab=Path(sys.argv[1]);assert OWNER.read_text().strip()==str(lab)
|
||||
verify(lab/'results/fixture-freeze-sha256.txt')
|
||||
assert json.loads((lab/'results/budget.json').read_text())['used']==dict.fromkeys(LIMITS,0)
|
||||
for p in (lab/'fixture').glob('*.py'):ast.parse(p.read_text(),filename=str(p))
|
||||
for p in [lab/'protocol-before-run.md',lab/'fixture/README.md']:
|
||||
for s in re.findall(r'```(?:sh|bash)\n(.*?)```',p.read_text(),re.S):subprocess.run(['sh','-n'],input=s,text=True,check=True)
|
||||
with tempfile.TemporaryDirectory(prefix='static-nested-ledger-offline-') as tmp:
|
||||
test=Path(tmp);(test/'results').mkdir();initial(test)
|
||||
with concurrent.futures.ProcessPoolExecutor(max_workers=4) as pool:
|
||||
assert sum(pool.map(attempt,[(test,'capture')]*24))==16
|
||||
for k,n in LIMITS.items():
|
||||
if k=='capture':continue
|
||||
for _ in range(n):reserve(test,k)
|
||||
assert not attempt((test,k))
|
||||
before=(test/'results/budget.json').read_bytes()
|
||||
assert not attempt((test,'run')) and (test/'results/budget.json').read_bytes()==before
|
||||
state=json.loads(before);state['used']['run']=-1
|
||||
(test/'results/budget.json').write_text(json.dumps(state));assert not attempt((test,'run'))
|
||||
# Use the retained actual unmodified nested control, stopping while mapped.
|
||||
old=Path('/tmp/swayfx-mosaic-nested-grab-If8ofeUu/run-control-open-visible/client.log')
|
||||
text=old.read_text().split('TOPLEVEL DESTROY')[0]
|
||||
cuts=[text.find(s) for s in ['POPUP_B DESTROY','POPUP_B POPUP_DONE'] if s in text]
|
||||
if cuts:text=text[:min(cuts)]
|
||||
good=protocol(text);negative=[]
|
||||
for name,wrong in [('serial',text.replace('used=61','used=62')),
|
||||
('parent',text.replace('parent_xdg=12','parent_xdg=9')),
|
||||
('button origin',text.replace('target=TOPLEVEL serial=61 button=272 state=1','target=OTHER serial=61 button=272 state=1')),
|
||||
('protocol error',text+'\nwl_display#1.error(1, 1, "bad")'),
|
||||
('premature dismissal',text+'\nPOPUP_B POPUP_DONE role=17')]:
|
||||
negative.append(rejected(name,lambda t=wrong:protocol(t)))
|
||||
snap=json.loads((Path('/home/fy59/swayfx-mosaic-static-rgb-input-retest-hn67lnhq/results/preflight-snapshot.json')).read_text())
|
||||
ref=validate_reference(snap,7,1700453);validate_outputs(snap['outputs']);assert ref['stored_border_width']==2
|
||||
negative.append(rejected('wrong reference identity',lambda:validate_reference(snap,8,1700459)))
|
||||
synthetic='POPUP_A ROLE_CONFIGURE x=100 y=80 w=350 h=170\nPOPUP_B ROLE_CONFIGURE x=100 y=60 w=300 h=130'
|
||||
g=geometry(synthetic,[-318,25]);assert g['A']==[-218,105,350,170] and g['B']==[-118,165,300,130]
|
||||
negative.append(rejected('no crossing',lambda:geometry(synthetic,[-700,25])))
|
||||
# Labelled synthetic color regions challenge exact rectangle/occlusion predicates.
|
||||
from PIL import ImageDraw
|
||||
baseline=Image.new('RGB',(800,600),(48,48,56))
|
||||
def synthetic_pixels(mosaic):
|
||||
images=[]
|
||||
for ox in [-800,0]:
|
||||
im=baseline.copy();draw=ImageDraw.Draw(im)
|
||||
if not mosaic or ox==-800:
|
||||
for key,color in [('A',(224,192,32)),('B',(32,208,160))]:
|
||||
x,y,w,h=g[key];draw.rectangle((x-ox,y,x+w-ox-1,y+h-1),fill=color)
|
||||
images.append(im)
|
||||
return images
|
||||
left,right=synthetic_pixels(True);popup_pixels(left,right,g,True,baseline)
|
||||
badright=right.copy();badright.putpixel((1,200),(32,208,160))
|
||||
negative.append(rejected('actual forbidden color',lambda:popup_pixels(left,badright,g,True,baseline)))
|
||||
lstd,rstd=synthetic_pixels(False);popup_pixels(lstd,rstd,g,False,baseline)
|
||||
negative.append(rejected('standard B missing',lambda:popup_pixels(lstd,right,g,False,baseline)))
|
||||
assert point(left,'POPUP_B',-800)[0]<0
|
||||
# Initial synthetic protocol must reject missing/early ACK and buffer attachment.
|
||||
cfg=good['grabs'][0]['ack'];role=good['grabs'][0]['ids'][1]
|
||||
negative.append(rejected('missing real ACK',lambda:protocol(text.replace(f'xdg_surface#{role}.ack_configure({cfg})',f'xdg_surface#{role}.ack_configure(999)'))))
|
||||
assert 100+1<=420 and 80+1<=320 and 100+1<=350 and 60+1<=170
|
||||
assert -800+800==0 and 799+800==1599 # Signed global -> verified output-layout fraction.
|
||||
im=Image.new('RGB',(800,600),(48,48,56));reference(im,im.copy())
|
||||
bad=im.copy();bad.putpixel((799,599),(48,48,57))
|
||||
negative.append(rejected('one changed RGB',lambda:reference(im,bad)))
|
||||
negative.append(rejected('sparse paint point',lambda:point(im,'POPUP_B',0)))
|
||||
lines=['POINTER_BUTTON target=POPUP_B serial=70 button=273 state=1','POINTER_BUTTON target=POPUP_B serial=71 button=273 state=0']
|
||||
pair(lines,'POPUP_B')
|
||||
negative.append(rejected('wrong delivered target',lambda:pair(lines,'POPUP_A')))
|
||||
d='POPUP_B POPUP_DONE role=17\nPOPUP_A POPUP_DONE role=14\nPOPUP_B DESTROY role=17\nPOPUP_A DESTROY role=14'
|
||||
dismissal(d,[])
|
||||
negative.append(rejected('invisible delivery',lambda:dismissal(d,lines)))
|
||||
negative.append(rejected('wrong dismissal order',lambda:dismissal(d.replace('POPUP_B POPUP_DONE','POPUP_A POPUP_DONE'),[])))
|
||||
sc='PI_POPUP output=HEADLESS-1 ns=1 local_index=1 tree=0x123 enabled=1 clip_valid=1 clip=-482,-25,800,600'
|
||||
root_clip(sc,[-318,25],True)
|
||||
negative.append(rejected('wrong applied clip',lambda:root_clip(sc,[-317,25],True)))
|
||||
log="Handling command 'exit'\nShutting down sway"
|
||||
assert terminal(True,1,0,log,False,False)['transport_incident']
|
||||
for args in [(False,1,0,log,False,False),(True,1,1,log,False,False),(True,1,0,'',False,False),(True,1,0,log,True,False),(True,1,0,log,False,True)]:
|
||||
assert not terminal(*args)['scientific_cleanup_pass']
|
||||
src=(lab/'historical-source/nested-client.c').read_text();assert sha(lab/'historical-source/nested-client.c')==NESTED_SHA
|
||||
patched=(lab/'client-source/nested-client.c').read_text()
|
||||
# CONTRACT: remove precisely the sole new placement branch, recover original bytes.
|
||||
expected=src.replace(' if (!strcmp(placement,"visible")) {',' /* EXPERIMENTAL: only placement changes; serial/grab/lifecycle stay historical.\n * Both 1x1 anchors lie inside their parent SHM window geometry. */\n if (!strcmp(placement,"static_cross")) {\n a.anchor_x=100; a.anchor_y=80; a.width=350;\n b.anchor_x=100; b.anchor_y=60; b.width=300;\n } else if (!strcmp(placement,"visible")) {')
|
||||
assert patched==expected,'unauthorized client behavior change'
|
||||
for tokens in ['xdg_popup_grab(s->popup,seat,serial)','parent->xdg','if (!s->mapped && !s->done) draw(s)','destroy_popup(&b); destroy_popup(&a);']:
|
||||
assert tokens in src
|
||||
candidate=Path('/home/fy59/swayfx-mosaic-static-runtime-hm5kbdkt/mosaic-source')
|
||||
token_files={'sway/desktop/transaction.c':['con->current.workspace','wlr_scene_tree_get_clip(ws->layers.tiling','wlr_scene_tree_set_clip(popup_tree, NULL)'],
|
||||
'sway/desktop/xdg_shell.c':['popup->xdg_surface_tree, popup->image_capture_tree','popup->desc.relative = &view->content_tree->node'],
|
||||
'sway/commands/exit.c':['sway_terminate(0)'],
|
||||
'sway/main.c':['wl_display_terminate(server.wl_display)','Shutting down sway','return exit_value'],
|
||||
'sway/ipc-server.c':['WL_EVENT_WRITABLE','ipc_send_reply']}
|
||||
for file,tokens in token_files.items():assert all(t in (candidate/file).read_text() for t in tokens),(file,tokens)
|
||||
save(lab,'offline-gate',dict(marker='STATIC_NESTED_GRAB_FIXTURE_PREFLIGHT=PASS',status='PASS',fixture_sha256=sha(lab/'results/fixture-freeze-sha256.txt'),historical_control=str(old),historical_protocol=good,synthetic_geometry=g,negative_cases=negative,ledger='PASS concurrency/exhaustion/corruption',terminal='PASS synthetic success and five failures',client_delta='placement only',AST='PASS',shell='PASS',source_audit='PASS',C17='PASS from preparation',runtime='NOT TESTED'))
|
||||
print('STATIC_NESTED_GRAB_FIXTURE_PREFLIGHT=PASS')
|
||||
print('PASS historical control, reference width2, labelled synthetic predicates,',len(negative),'negative cases, ledger and terminal classification')
|
||||
92
tooling/probes/step20c-static-nested-grab/collect.py
Normal file
92
tooling/probes/step20c-static-nested-grab/collect.py
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Seal retained one-run observations without manufacturing missing evidence.
|
||||
|
||||
CONTRACT: runtime failures and real IPC codes stay immutable. No graphical replay.
|
||||
INVARIANT: source/evidence manifests are created once and checked read-only.
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
from PIL import Image
|
||||
from support import OWNER,sha,save,verify,inventory
|
||||
from predicates import protocol,mapped,geometry,popup_pixels,paint,pair,dismissal,root_clip,equal
|
||||
from ipc_predicates import nodes,workspace,validate_outputs,validate_reference,scene_from_log
|
||||
from geometry_audit import diagnose
|
||||
p=argparse.ArgumentParser();p.add_argument('lab',type=Path);lab=p.parse_args().lab.resolve()
|
||||
assert OWNER.read_text().strip()==str(lab)
|
||||
assert not (lab/'results/evidence-sha256.txt').exists(),'already sealed'
|
||||
verify(lab/'results/fixture-freeze-sha256.txt')
|
||||
def read(n):return json.loads((lab/'results'/(n+'.json')).read_text())
|
||||
run=lab/'run';runtime=read('runtime');terminal=read('terminal-exit');cleanup=read('cleanup')
|
||||
protected=read('protected-before');assert all(sha(p)==h for p,h in protected.items()),'protected source/system change'
|
||||
save(lab,'protected-after',dict(status='PASS',hashes=protected))
|
||||
assert cleanup['runtime_removed'] and not Path(cleanup['runtime']).exists()
|
||||
assert all(not Path('/proc',str(p['pid'])).exists() for p in cleanup['owned_processes'])
|
||||
text=(run/'compositor.log').read_text();events=[json.loads(s) for s in (run/'events.jsonl').read_text().splitlines()]
|
||||
full=runtime['status']=='PASS' and runtime['measurements_completed'] and terminal['scientific_cleanup_pass']
|
||||
independent={}
|
||||
if not full and runtime['stage']=='E1-open-A':
|
||||
diagnostic=diagnose(lab);save(lab,'geometry-fixture-diagnostic',diagnostic)
|
||||
save(lab,'applied-owner-geometry',dict(status='PARTIAL',E1=diagnostic['applied_owner'],painted_content_origin=diagnostic['painted_content_origin'],SHM_size=diagnostic['SHM_size'],assigned_content=diagnostic['assigned_content'],painted=diagnostic['painted_TOPLEVEL'],popups='NOT TESTED'))
|
||||
save(lab,'applied-clips',dict(status='PARTIAL',tiled=diagnostic['tiled_clip'],root='NOT TESTED',child='NOT TESTED'))
|
||||
save(lab,'mosaic-pixels',dict(status='PARTIAL',toplevel_H1=diagnostic['painted_TOPLEVEL'],H2=diagnostic['H2'],root='NOT TESTED',nested='NOT TESTED'))
|
||||
if full:
|
||||
baseline=Image.open(run/'H2-reference-1.png').convert('RGB')
|
||||
owners=read('applied-owner-geometry');clips=read('applied-clips');grab_serials=[]
|
||||
for name in ['E1','E2']:
|
||||
client=(run/(name+'.log')).read_text();cut=client.index('POPUP_B MAP ');prefix=client[:client.index('\n',cut)+1]
|
||||
pr=protocol(prefix);grab_serials += [g['serial'] for g in pr['grabs']]
|
||||
g=geometry(prefix,owners[name]['content_origin'])
|
||||
independent[name]=dict(protocol=pr,geometry=g,pixels=popup_pixels(Image.open(run/(name+'-mosaic-H1.png')).convert('RGB'),Image.open(run/(name+'-mosaic-H2.png')).convert('RGB'),g,True,baseline))
|
||||
assert clips[name]['root']['raw'] in text
|
||||
root_clip(clips[name]['root']['raw'],owners[name]['content_origin'],True)
|
||||
assert len(set(grab_serials))==4 and grab_serials==sorted(grab_serials)
|
||||
for file in ['H2-reference-2.png','E1-mosaic-H2.png','E1-dismissed-H2.png','E2-mosaic-H2.png','final-H2.png']:
|
||||
assert equal(baseline,Image.open(run/file).convert('RGB'))['equal'],('full H2',file)
|
||||
returns=read('standard-return');assert returns['root_clip']['raw'] in text
|
||||
root_clip(returns['root_clip']['raw'],returns['geometry']['origin'],False)
|
||||
stdsnap=read('standard-snapshot');ws=workspace(stdsnap['tree'],'1');assert ws['id']==owners['E2']['workspace'] and ws['layout']=='splith'
|
||||
validate_reference(stdsnap,read('reference-stability')['reference']['con_id'],read('reference-stability')['reference']['pid'])
|
||||
std=popup_pixels(Image.open(run/'standard-H1.png').convert('RGB'),Image.open(run/'standard-H2.png').convert('RGB'),returns['geometry'],False,baseline)
|
||||
client=(run/'E2.log').read_text();cut=next(e['ns'] for e in events if e['kind']=='measurements-completed')
|
||||
# Delivery must exist in actual retained recipient logs, not just summary JSON.
|
||||
for row in read('clicks'):
|
||||
for name,lines in row['events'].items():
|
||||
actual=(run/(name+'.log')).read_text()
|
||||
assert all(line in actual for line in lines)
|
||||
for name,target in [('E1-visible-B','POPUP_B'),('E2-standard-B','POPUP_B'),('E2-standard-A','POPUP_A')]:
|
||||
row=next(r for r in read('clicks') if r['tag']==name);who='E1' if name.startswith('E1') else 'E2'
|
||||
pair(row['events'][who],target)
|
||||
assert not row['events']['reference'] and not row['events']['filler']
|
||||
forbidden=read('forbidden-input');dismissal((run/'E1.log').read_text(),forbidden['popup_events'])
|
||||
assert client.index('POPUP_B DESTROY ')<client.index('COMMAND close_b DONE')<client.index('POPUP_A DESTROY ')
|
||||
assert not re.search(r'wl_display[^\n]*\.error\(|protocol error',client,re.I)
|
||||
assert runtime['duration_seconds']<=150 and not runtime['rescue_cleanup']
|
||||
independent['standard']=std
|
||||
save(lab,'independent-validation',dict(status='PASS',historical_status_unchanged=True,actual=independent,serials=grab_serials))
|
||||
# Trace extents + capture diffs are retained as partial damage observations.
|
||||
damage=[s for s in text.splitlines() if 'TEMPORAL_DAMAGE' in s]
|
||||
save(lab,'damage-analysis',dict(status='PARTIAL',trace_count=len(damage),raw_extents=damage,
|
||||
capture_windows=[e for e in events if e['kind']=='capture'],
|
||||
changed_regions={n:independent[n]['pixels'] for n in ['E1','E2'] if n in independent},
|
||||
limitations=['not every intermediate frame','not minimal damage','no native buffer/PNG identity','no exhaustive GPU lifetime']))
|
||||
source=Path('/home/fy59/swayfx-mosaic-static-runtime-hm5kbdkt/mosaic-source/sway/desktop/xdg_shell.c')
|
||||
save(lab,'scene-parentage',dict(status='PASS source-backed' if full else 'NOT TESTED runtime',source=str(source),source_sha256=sha(source),root='global popup layer; positioned from applied view content tree',nested='B wrapper under A xdg_surface_tree; same view',direct_child_trace='NOT AVAILABLE in immutable binary',runtime_XDG=read('xdg-protocol-order') if (lab/'results/xdg-protocol-order.json').exists() else None))
|
||||
required=['applied-owner-geometry','popup-root-geometry','popup-nested-geometry','grab-serials','xdg-protocol-order','applied-clips','mosaic-pixels','mosaic-input','forbidden-input','dismissal','standard-return','standard-popup-pixels','standard-popup-input','reference-stability']
|
||||
for n in required:
|
||||
if not (lab/'results'/(n+'.json')).exists():save(lab,n,dict(status='NOT TESTED',stopping_stage=runtime['stage'],reason=runtime['failure']))
|
||||
verdict='FEASIBLE' if full else (runtime.get('failure') or {}).get('verdict','INCONCLUSIVE')
|
||||
architecture='STATIC_NESTED_VALID_GRAB_FEASIBLE_FOR_TESTED_MATRIX' if full else 'STATIC_NESTED_VALID_GRAB_REQUIRES_REDESIGN' if verdict=='BLOCKED' else 'UNDECIDED'
|
||||
matrix={n:'PASS' if full else 'NOT TESTED: inspect individual retained observations' for n in required}
|
||||
if not full:
|
||||
matrix.update(binary_identity='PASS',loaded_libraries='PASS',client_provenance='PASS',C17='PASS',outputs='PASS',H1_focus='PASS',applied_camera='PASS relative -320, 480 to 160',H2_reference='PASS whole RGB; before any popup',tiled_clip='PASS global [-800,0,800,600]',toplevel_paint='PASS measured 182214 pixels; no popup',root_grab='NOT TESTED',nested_grab='NOT TESTED',E2='NOT TESTED',terminal_IPC='FAIL actual exit1; completed=false, no scientific exception',compositor_exit='PASS actual code0',cleanup='PASS no rescue',offline_gate='PASS recorded tests; INSUFFICIENT assertion audit, see diagnostic')
|
||||
matrix.update(damage='PARTIAL',direct_child_clip_trace='NOT AVAILABLE; runtime NOT TESTED' if not full else 'NOT AVAILABLE; inherited source + pixels/input',hidden_owner='NOT TESTED',scale='NOT TESTED',rotation='NOT TESTED',output_migration='NOT TESTED',saved_buffer='NOT TESTED',effects='NOT TESTED',atomic_generation='NOT PROVEN',exhaustive_ABI='NOT TESTED',new_pristine='NOT RUN; historical controls only')
|
||||
save(lab,'matrix',matrix)
|
||||
save(lab,'summary',dict(verdict=verdict,architecture=architecture,runtime=runtime,terminal=terminal,budget=read('budget'),marker='SWAYFX_MOSAIC_STATIC_NESTED_VALID_GRAB_PROBE='+verdict,STEP20C='NEXT',production_multi_output='NOT YET SUPPORTED',new_Mosaic_builds=0,new_SceneFX_builds=0,pristine_runs=0))
|
||||
sources=[p for root in ['fixture','preflight-correction','postrun-fixture','client-source','historical-source'] for p in (lab/root).rglob('*') if p.is_file()]+[lab/'protocol-before-run.md']
|
||||
evidence=[p for root in ['raw','run','results'] for p in (lab/root).rglob('*') if p.is_file()]+[lab/n for n in ['swayfx-mosaic','nested-client','static-reference','pointer-daemon','campaign-owner']]
|
||||
# Both lists fixed before writing either manifest, avoiding circular digests.
|
||||
for name,paths in [('source',sources),('evidence',evidence)]:
|
||||
print(name,inventory(lab/'results'/(name+'-sha256.txt'),paths))
|
||||
print('SWAYFX_MOSAIC_STATIC_NESTED_VALID_GRAB_PROBE='+verdict)
|
||||
80
tooling/probes/step20c-static-nested-grab/geometry_audit.py
Normal file
80
tooling/probes/step20c-static-nested-grab/geometry_audit.py
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Read-only post-run diagnosis of failed geometry assertions; no graphical retry.
|
||||
|
||||
WHY: SHM/native geometry, rendered destination and IPC window_rect differ.
|
||||
CONTRACT: preserve the executed runner and diagnose against actual PNG/protocol.
|
||||
INVARIANT: a successful diagnosis cannot qualify uncreated popup grabs or input.
|
||||
"""
|
||||
import argparse
|
||||
import ast
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
from PIL import Image
|
||||
from predicates import paint,equal,reference
|
||||
from ipc_predicates import validate_reference,measure_owner,scene_from_log
|
||||
from support import sha,save,verify
|
||||
|
||||
def diagnose(lab):
|
||||
lab=Path(lab);run=lab/'run'
|
||||
runtime=json.loads((lab/'results/runtime.json').read_text());budget=json.loads((lab/'results/budget.json').read_text())
|
||||
assert runtime['status']=='INCONCLUSIVE' and runtime['stage']=='E1-open-A' and not runtime['measurements_completed']
|
||||
assert budget['used']==dict(run=1,chain=1,grab=0,click=0,capture=3,camera=1,transition=0,move=1)
|
||||
client=(run/'E1.log').read_text();assert 'GRAB_REQUEST' not in client and 'POPUP_A CREATE' not in client and 'POINTER_BUTTON' not in client
|
||||
assert 'create_buffer(new id wl_buffer#12, 0, 420, 320, 1680, 1)' in client
|
||||
assert 'TOPLEVEL ROLE_CONFIGURE w=636 h=573' in client
|
||||
assert client.index('xdg_surface#9.ack_configure(20)')<client.index('wl_surface#3.attach(')<client.index('TOPLEVEL MAP ')
|
||||
snap=json.loads((lab/'results/E1-applied-snapshot.json').read_text())
|
||||
cal=json.loads((lab/'results/camera-calibration.json').read_text())[-1];owner=cal['after_owner']['IPC']
|
||||
measure=measure_owner((run/'compositor.log').read_text(),owner)
|
||||
# Use the recorded applied sample before shutdown, never the later teardown scene.
|
||||
assert cal['after_owner']['raw'] in (run/'compositor.log').read_text()
|
||||
applied=measure_owner(cal['after_owner']['raw'],owner)
|
||||
scene=scene_from_log(cal['scene']['raw'],'HEADLESS-1')
|
||||
assert scene['global_clip']==[-800,0,800,600] and scene['clip_valid'] and scene['scroll']==160
|
||||
assert cal['before']==480 and cal['delta']==-320 and cal['after']==160
|
||||
value=paint(Image.open(run/'E1-top.png').convert('RGB'),'TOPLEVEL');assert value==dict(count=182214,bbox=(482,25,800,598))
|
||||
x1,y1,x2,y2=value['bbox'];assert x1-800==applied['horizontal_content'][0]==-318
|
||||
assert value['count']==(x2-x1)*(y2-y1) and y2-y1==owner['window_rect']['height']==573
|
||||
assert owner['geometry']==dict(x=0,y=0,width=420,height=320)
|
||||
old_height_ok=(y2-y1==320)
|
||||
old_y_ok=(y1==applied['scene'][1]+owner['window_rect']['y'])
|
||||
assert not old_height_ok and not old_y_ok, 'must reproduce both invalid assumptions'
|
||||
assert owner['window_rect']['y']==0 and owner['deco_rect']['height']==25 and applied['scene'][1]==0
|
||||
ref=json.loads((lab/'results/reference-stability.json').read_text())['reference']
|
||||
validate_reference(snap,ref['con_id'],ref['pid'])
|
||||
a=Image.open(run/'H2-reference-1.png').convert('RGB');b=Image.open(run/'H2-reference-2.png').convert('RGB');reference(a,b)
|
||||
assert paint(a,'TOPLEVEL')['count']==0 and paint(b,'TOPLEVEL')['count']==0
|
||||
events=[json.loads(s) for s in (run/'events.jsonl').read_text().splitlines()]
|
||||
cap=[e for e in events if e['kind']=='capture']
|
||||
assert len(cap)==3 and cap[1]['start_ns']-cap[0]['start_ns']>=500000000
|
||||
exact=Path('/home/fy59/swayfx-mosaic-static-runtime-hm5kbdkt/mosaic-source')
|
||||
output=(exact/'sway/desktop/output.c').read_text();ipc=(exact/'sway/ipc-json.c').read_text();trans=(exact/'sway/desktop/transaction.c').read_text()
|
||||
assert 'wlr_scene_buffer_set_dest_size(buffer, MAX(content_width, 0), MAX(content_height, 0))' in output
|
||||
assert 'has_titlebar ? 0 : c->pending.content_y - c->pending.y' in ipc
|
||||
assert 'border_left, border_top' in trans
|
||||
runner=(lab/'fixture/run.py').read_text();assert 'topbox[3]-topbox[1]==320' in runner
|
||||
assert "origin[1]==after['scene'][1]+owner['window_rect']['y']" in runner
|
||||
terminal=json.loads((lab/'results/terminal-exit.json').read_text())
|
||||
assert terminal['ipc_exit']==1 and terminal['compositor_exit']==0 and not terminal['scientific_cleanup_pass']
|
||||
assert 'Unable to receive IPC response' in terminal['stderr']
|
||||
text=(run/'compositor.log').read_text();assert "Handling command 'exit'" in text and 'Shutting down sway' in text
|
||||
assert all(row['exit']==0 and not Path('/proc',str(row['pid'])).exists() for row in terminal['owned_process_codes'])
|
||||
assert not re.search(r'wl_display[^\n]*\.error\(|protocol error',client,re.I)
|
||||
return dict(status='PASS offline diagnosis; runtime remains INCONCLUSIVE',runner_sha256=sha(lab/'fixture/run.py'),
|
||||
executed_assertion_line=182,first_failure='rendered blue height 573 != immutable SHM height 320',
|
||||
additional_unreached_assertion_line=183,additional_failure='titlebar-enabled IPC window_rect.y=0 does not give painted/content y=25',
|
||||
applied_owner=applied,painted_TOPLEVEL=value,SHM_size=[420,320],assigned_content=[636,573],painted_content_origin=[-318,25],
|
||||
applied_camera=dict(before=480,relative_delta=-320,after=160),tiled_clip=scene,
|
||||
H2=dict(whole_RGB='PASS two references',color='#303038',changed_pixels=0,PNG_sha256=sha(run/'H2-reference-1.png')),
|
||||
source_locations={'destination':'sway/desktop/output.c:331','window_rect_y':'sway/ipc-json.c:712','content_origin':'sway/desktop/transaction.c:705'},
|
||||
origin_diagnosis='actual paint supplies y; IPC window_rect excludes enabled titlebar; rendered destination is separate from native SHM',
|
||||
offline_preflight_limit='recorded PASS did not audit these two live assertions against the source/available prior IPC; it was insufficient',
|
||||
popup_geometry='NOT EXERCISED',grabs='NOT TESTED',input='NOT TESTED',standard='NOT TESTED',new_runs=0,
|
||||
correction_scope='offline fixture geometry revalidation required; no clipping algorithm change justified')
|
||||
|
||||
if __name__=='__main__':
|
||||
p=argparse.ArgumentParser();p.add_argument('lab',type=Path);p.add_argument('--record',action='store_true');a=p.parse_args()
|
||||
verify(a.lab/'results/fixture-freeze-sha256.txt');result=diagnose(a.lab)
|
||||
if a.record:save(a.lab,'geometry-fixture-diagnostic',result)
|
||||
print('PASS reproduce failed height assertion and additional invalid IPC-y assumption; no popup evidence manufactured')
|
||||
139
tooling/probes/step20c-static-nested-grab/ipc_predicates.py
Normal file
139
tooling/probes/step20c-static-nested-grab/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'
|
||||
16
tooling/probes/step20c-static-nested-grab/placement.patch
Normal file
16
tooling/probes/step20c-static-nested-grab/placement.patch
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
--- a/nested-client.c
|
||||
+++ b/nested-client.c
|
||||
@@ -205,7 +205,12 @@
|
||||
int main(int argc,char **argv) {
|
||||
signal(SIGTERM,stop);
|
||||
const char *placement=argc>1?argv[1]:"b_cross";
|
||||
- if (!strcmp(placement,"visible")) {
|
||||
+ /* EXPERIMENTAL: only placement changes; serial/grab/lifecycle stay historical.
|
||||
+ * Both 1x1 anchors lie inside their parent SHM window geometry. */
|
||||
+ if (!strcmp(placement,"static_cross")) {
|
||||
+ a.anchor_x=100; a.anchor_y=80; a.width=350;
|
||||
+ b.anchor_x=100; b.anchor_y=60; b.width=300;
|
||||
+ } else if (!strcmp(placement,"visible")) {
|
||||
a.anchor_x=50; a.anchor_y=80; a.width=250;
|
||||
b.anchor_x=20; b.anchor_y=60; b.width=170;
|
||||
} else if (!strcmp(placement,"both_cross")) {
|
||||
146
tooling/probes/step20c-static-nested-grab/predicates.py
Normal file
146
tooling/probes/step20c-static-nested-grab/predicates.py
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
"""Shared offline/live predicates: facts precede scientific classification.
|
||||
|
||||
CONTRACT: full RGB comparisons have no mask/tolerance; grabs require real logs.
|
||||
INVARIANT: dismissal, client destruction, clipping and layout remain distinct.
|
||||
"""
|
||||
import re
|
||||
from PIL import Image,ImageChops
|
||||
COLORS=dict(TOPLEVEL=(35,68,204),POPUP_A=(224,192,32),POPUP_B=(32,208,160))
|
||||
|
||||
class Defect(RuntimeError):
|
||||
"""A directly measured scientific contract failure, distinct from fixture ambiguity."""
|
||||
|
||||
def paint(im,target):
|
||||
im=im.convert('RGB');assert im.size==(800,600)
|
||||
mask=Image.new('L',im.size);mask.putdata([255 if p==COLORS[target] else 0 for p in im.get_flattened_data()])
|
||||
return dict(count=sum(mask.histogram()[1:]),bbox=mask.getbbox())
|
||||
|
||||
def point(im,target,origin):
|
||||
box=paint(im,target)['bbox'];assert box,('no measured paint',target)
|
||||
# WHY: A may be occluded by B; bbox center alone is not a valid hit point.
|
||||
x1,y1,x2,y2=box
|
||||
for y in range(y1+10,y2-10,5):
|
||||
for x in range(x1+10,x2-10,5):
|
||||
if all(im.getpixel((xx,yy))==COLORS[target] for yy in range(y-8,y+9) for xx in range(x-8,x+9)):
|
||||
return [x+origin,y]
|
||||
raise AssertionError('no solid measured interior '+target)
|
||||
|
||||
def equal(a,b):
|
||||
assert a.size==b.size==(800,600)
|
||||
rows=[(i%800,i//800,x,y) for i,(x,y) in enumerate(zip(a.get_flattened_data(),b.get_flattened_data())) if x!=y]
|
||||
return dict(equal=not rows,changed=len(rows),bbox=ImageChops.difference(a,b).getbbox(),pixels=rows)
|
||||
|
||||
def reference(a,b):
|
||||
result=equal(a,b)
|
||||
assert result['equal'] and all(c==(48,48,56) for c in a.get_flattened_data()),'unstable reference'
|
||||
return result
|
||||
|
||||
def configured(text,name):
|
||||
rows=re.findall(rf'{name} ROLE_CONFIGURE x=(-?\d+) y=(-?\d+) w=(\d+) h=(\d+)',text)
|
||||
assert rows,(name,'no configure')
|
||||
return list(map(int,rows[-1]))
|
||||
|
||||
def geometry(text,origin):
|
||||
a=configured(text,'POPUP_A');b=configured(text,'POPUP_B')
|
||||
A=[origin[0]+a[0],origin[1]+a[1],a[2],a[3]]
|
||||
B=[A[0]+b[0],A[1]+b[1],b[2],b[3]]
|
||||
assert all(r[0]<0<r[0]+r[2] for r in [A,B]),'popup crossing NOT EXERCISED'
|
||||
return dict(A=A,B=B,configured_A=a,configured_B=b,origin=origin)
|
||||
|
||||
def mapped(text):
|
||||
for name in ['POPUP_A','POPUP_B']:
|
||||
assert text.count(name+' MAP ')==1 and name+' POPUP_DONE ' not in text and name+' DESTROY ' not in text,(name,'not mapped')
|
||||
|
||||
def protocol(text):
|
||||
assert not re.search(r'wl_display[^\n]*\.error\(|protocol error|invalid_popup_parent|not_the_topmost_popup',text,re.I),'protocol error'
|
||||
mapped(text)
|
||||
ids={}
|
||||
for name in ['TOPLEVEL','POPUP_A','POPUP_B']:
|
||||
m=re.search(rf'{name} CREATE wl=(\d+) xdg=(\d+) role=(\d+)',text);assert m
|
||||
ids[name]=list(map(int,m.groups()))
|
||||
assert len({n for v in ids.values() for n in v})==9,'object identities reused'
|
||||
grabs=[]
|
||||
for name,parent in [('POPUP_A','TOPLEVEL'),('POPUP_B','POPUP_A')]:
|
||||
create=re.search(rf'{name} CREATE .*parent={parent} parent_xdg=(\d+)',text);assert create
|
||||
assert int(create[1])==ids[parent][1], 'wrong XDG parent'
|
||||
m=re.search(rf'{name} GRAB_REQUEST origin=wl_pointer.button target={parent} serial=(\d+) used=(\d+)',text);assert m
|
||||
serial,used=map(int,m.groups());assert serial==used and 0<serial<=0xffffffff
|
||||
button=f'POINTER_BUTTON target={parent} serial={serial} button=272 state=1'
|
||||
assert text.index(button)<m.start(),'grab serial not from earlier button'
|
||||
role=text.index(name+' ROLE_CONFIGURE ');cfg=re.search(rf'{name} XDG_CONFIGURE serial=(\d+) ack',text);assert cfg
|
||||
mp=text.index(name+' MAP ')
|
||||
assert m.start()<role<cfg.start()<mp,'configure ACK map order'
|
||||
popup_role=ids[name][2];xdg=ids[name][1];wl=ids[name][0]
|
||||
# WAYLAND_DEBUG is independent corroboration, not just fixture claims.
|
||||
request=re.search(rf'xdg_popup#{popup_role}\.grab\(wl_seat#\d+, {serial}\)',text);assert request
|
||||
ack=re.search(rf'xdg_surface#{xdg}\.ack_configure\({cfg[1]}\)',text);assert ack
|
||||
attach=re.search(rf'wl_surface#{wl}\.attach\(',text);assert attach
|
||||
commits=list(re.finditer(rf'wl_surface#{wl}\.commit\(\)',text));assert len(commits)>=2
|
||||
assert request.start()<commits[0].start()<role<ack.start()<attach.start()<commits[1].start()<mp
|
||||
if parent=='POPUP_A':assert text.index('POPUP_A MAP ')<text.index(button)
|
||||
grabs.append(dict(popup=name,parent=parent,serial=serial,used=used,origin='wl_pointer.button',ids=ids[name],ack=int(cfg[1])))
|
||||
assert grabs[0]['serial']<grabs[1]['serial'], 'distinct ordered serials'
|
||||
return dict(ids=ids,grabs=grabs,hierarchy='TOPLEVEL -> POPUP_A -> POPUP_B',status='PASS')
|
||||
|
||||
def pair(lines,target):
|
||||
events=[]
|
||||
for line in lines:
|
||||
m=re.fullmatch(r'POINTER_BUTTON target=(\w+) serial=(\d+) button=(\d+) state=(\d+)',line);assert m
|
||||
events.append([m[1],*map(int,m.groups()[1:])])
|
||||
if len(events)!=2 or any(e[0]!=target for e in events) or [e[2:] for e in events]!=[[273,1],[273,0]]:
|
||||
raise Defect('wrong actual button recipient/pair: '+str(events))
|
||||
assert 0<events[0][1]<events[1][1]<=0xffffffff
|
||||
return events
|
||||
|
||||
def dismissal(text,buttons):
|
||||
if any('target=POPUP_A ' in b or 'target=POPUP_B ' in b for b in buttons):
|
||||
raise Defect('invisible popup received actual button')
|
||||
done=re.findall(r'(POPUP_[AB]) POPUP_DONE ',text)
|
||||
destroyed=re.findall(r'(POPUP_[AB]) DESTROY ',text)
|
||||
assert done==['POPUP_B','POPUP_A'] and destroyed==['POPUP_B','POPUP_A'],('dismissal order',done,destroyed)
|
||||
return dict(done=done,destroyed=destroyed,invisible_buttons=buttons,status='PASS',meaning='outside dismissal distinct from invisible hit')
|
||||
|
||||
def root_clip(text,origin,mosaic):
|
||||
rows=[s for s in text.splitlines() if 'PI_POPUP output=HEADLESS-1 ' in s]
|
||||
assert rows,'no root clip trace'
|
||||
row=rows[-1]
|
||||
m=re.search(r'local_index=(\d+) tree=(\S+) enabled=(\d+) clip_valid=(\d+) clip=(-?\d+),(-?\d+),(\d+),(\d+)',row);assert m
|
||||
index,tree,enabled,valid,*box=m.groups();box=list(map(int,box))
|
||||
assert int(index)==1 and int(enabled)==1
|
||||
if bool(int(valid))!=mosaic:raise Defect('root clip state mismatch')
|
||||
if mosaic:
|
||||
assert [origin[0]+box[0],origin[1]+box[1],box[2],box[3]]==[-800,0,800,600], 'root/owner applied clip relationship'
|
||||
return dict(tree=tree,clip_valid=bool(int(valid)),local=box,origin=origin,raw=row,child='inherited; source hierarchy plus measured B render/input')
|
||||
|
||||
def popup_pixels(left,right,g,mosaic,baseline):
|
||||
result={side:{n:paint(im,n) for n in COLORS} for side,im in [('H1',left),('H2',right)]}
|
||||
for key,name in [('A','POPUP_A'),('B','POPUP_B')]:
|
||||
rect=g[key]
|
||||
assert result['H1'][name]['count']>0,'permitted popup color absent'
|
||||
if mosaic and result['H2'][name]['count']:
|
||||
raise Defect('forbidden popup pixels '+name)
|
||||
if not mosaic and not result['H2'][name]['count']:
|
||||
raise AssertionError('standard popup continuation absent '+name)
|
||||
# CONTRACT: compare the exact color region against configured geometry,
|
||||
# subtracting B's occlusion of A, not against a predicted painted bbox.
|
||||
for side,im,ox in [('H1',left,-800),('H2',right,0)]:
|
||||
for y in range(600):
|
||||
for x in range(800):
|
||||
gx=x+ox
|
||||
inside=rect[0]<=gx<rect[0]+rect[2] and rect[1]<=y<rect[1]+rect[3]
|
||||
if key=='A':
|
||||
B=g['B'];inside=inside and not (B[0]<=gx<B[0]+B[2] and B[1]<=y<B[1]+B[3])
|
||||
expected=inside and (not mosaic or side=='H1')
|
||||
if (im.getpixel((x,y))==COLORS[name])!=expected:
|
||||
raise AssertionError(('paint/configured position mismatch',name,side,x,y))
|
||||
if mosaic:
|
||||
result['whole_H2']=equal(baseline,right)
|
||||
if not result['whole_H2']['equal']:raise Defect('whole-H2 changed under Mosaic')
|
||||
return result
|
||||
|
||||
def terminal(measures,ipc_code,compositor_code,log,protocol_error,rescue):
|
||||
ok=bool(measures and compositor_code==0 and "Handling command 'exit'" in log
|
||||
and 'Shutting down sway' in log and not protocol_error and not rescue)
|
||||
return dict(scientific_cleanup_pass=ok,ipc_exit=ipc_code,compositor_exit=compositor_code,
|
||||
ipc_success=ipc_code==0,transport_incident=bool(ok and ipc_code!=0),
|
||||
interpretation=('compatible queued-response loss, not proven packet causality' if ok and ipc_code!=0 else 'actual recorded IPC outcome'))
|
||||
77
tooling/probes/step20c-static-nested-grab/prepare.py
Normal file
77
tooling/probes/step20c-static-nested-grab/prepare.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Create one exclusive LAB; copy an immutable compositor and build clients only.
|
||||
|
||||
CONTRACT: never launch a graphical process from preparation. No LAB retry/reset.
|
||||
INVARIANT: retained historical source and manifests are read-only.
|
||||
"""
|
||||
import ast
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import tempfile
|
||||
from support import REPO,OWNER,BINARY,BINARY_SHA,WLROOTS_SHA,NESTED_SHA,sha,save,command,initial,inventory,verify
|
||||
|
||||
fd=os.open(OWNER,os.O_CREAT|os.O_EXCL|os.O_WRONLY,0o600)
|
||||
with os.fdopen(fd,'w') as out:
|
||||
lab=Path(tempfile.mkdtemp(prefix='swayfx-mosaic-static-nested-grab-',dir=Path.home()))
|
||||
out.write(str(lab)+'\n');out.flush();os.fsync(out.fileno())
|
||||
for n in ['raw','results','client-source','historical-source','fixture']:(lab/n).mkdir()
|
||||
(lab/'campaign-owner').write_text(str(OWNER)+'\n');initial(lab)
|
||||
prior=Path('/home/fy59/swayfx-mosaic-static-rgb-input-retest-hn67lnhq')
|
||||
receipts={n:verify(prior/'results'/(n+'-sha256.txt')) for n in ['fixture-freeze','source','evidence']}
|
||||
assert sha(BINARY)==BINARY_SHA and not BINARY.is_symlink() and stat.S_IMODE(BINARY.stat().st_mode)==0o755
|
||||
shutil.copy2(BINARY,lab/'swayfx-mosaic');assert sha(lab/'swayfx-mosaic')==BINARY_SHA
|
||||
elf=command(lab,'ELF',['readelf','-hW','-nW','-dW',lab/'swayfx-mosaic'])
|
||||
assert 'ELF64' in elf and 'Advanced Micro Devices X86-64' in elf and '942db64269640778ee433e5970ba1100bc60f94c' in elf
|
||||
assert 'libscenefx' not in elf and '(RPATH)' not in elf and '(RUNPATH)' not in elf
|
||||
symbols=command(lab,'symbols',['nm',lab/'swayfx-mosaic'])
|
||||
assert all(' T '+s in symbols for s in ['wlr_scene_tree_set_clip','wlr_scene_tree_get_clip'])
|
||||
loader=command(lab,'loader',['ldd','-r',lab/'swayfx-mosaic'])
|
||||
assert not any(s in loader for s in ['undefined symbol','not found','libscenefx'])
|
||||
wl=Path('/usr/lib/libwlroots-0.20.so').resolve();assert sha(wl)==WLROOTS_SHA and str(wl) in loader
|
||||
version=command(lab,'wlroots-version',['pkg-config','--modversion','wlroots-0.20']).strip();assert version=='0.20.2'
|
||||
save(lab,'binary-identity',dict(status='PASS',sha256=BINARY_SHA,build_id='942db64269640778ee433e5970ba1100bc60f94c',mode='0755',ELF='ELF64 x86-64',SceneFX_dynamic=False,wlroots_sha256=WLROOTS_SHA,wlroots_version=version,source=str(BINARY),copy=str(lab/'swayfx-mosaic')))
|
||||
src=lab/'client-source';inputs=[]
|
||||
for name,rel in [('nested-client.c','step20c-nested-grab/nested-client.c'),('pointer-daemon.c','step20c-valid-grab/pointer-daemon.c'),('static-reference.c','step20c-saved-target-timeout-input/static-reference.c'),('saved-client.c','step20c-saved-target-timeout-input/saved-client.c')]:
|
||||
p=REPO/'tooling/probes'/rel;shutil.copy2(p,src/name);shutil.copy2(p,lab/'historical-source'/name)
|
||||
inputs.append(dict(source=str(p),sha256=sha(p),copy=str(src/name)))
|
||||
assert sha(src/'nested-client.c')==NESTED_SHA
|
||||
patch=REPO/'tooling/probes/step20c-static-nested-grab/placement.patch'
|
||||
command(lab,'placement-check',['git','apply','--check',patch],cwd=src)
|
||||
command(lab,'placement-apply',['git','apply',patch],cwd=src)
|
||||
# Protocol byte identity is independently checked by regeneration, outside Git.
|
||||
for name in ['xdg-shell-client.h','xdg-shell-protocol.c','virtual-pointer-client.h','virtual-pointer-protocol.c']:
|
||||
shutil.copy2(prior/'client-source'/name,src/name)
|
||||
xml=Path('/usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml');shutil.copy2(xml,src/'xdg-shell.xml')
|
||||
vxml=REPO/'tooling/probes/step20c-valid-grab/wlr-virtual-pointer-unstable-v1.xml';shutil.copy2(vxml,src/'virtual-pointer.xml')
|
||||
assert sha(vxml)=='3ff6d540be0bc5228195bf072bde42117ea17945a5c2061add5d3cf97d6bb524'
|
||||
for stem in ['xdg-shell','virtual-pointer']:
|
||||
for mode,suffix in [('client-header','-client.h'),('private-code','-protocol.c')]:
|
||||
dest=lab/'raw'/(stem+suffix)
|
||||
command(lab,stem+'-'+mode,['wayland-scanner',mode,src/(stem+'.xml'),dest])
|
||||
assert dest.read_bytes()==(src/(stem+suffix)).read_bytes(),'generated protocol differs'
|
||||
flags=command(lab,'wayland-flags',['pkg-config','--cflags','--libs','wayland-client']).split()
|
||||
builds=[]
|
||||
for name,proto in [('nested-client','xdg-shell'),('static-reference','xdg-shell'),('pointer-daemon','virtual-pointer')]:
|
||||
args=['cc','-std=c17','-Wall','-Wextra','-Werror','-Wno-unused-parameter','-I'+str(src),src/(name+'.c'),src/(proto+'-protocol.c'),*flags]
|
||||
command(lab,name+'-syntax',args+['-fsyntax-only']);command(lab,name+'-build',args+['-o',lab/name])
|
||||
builds.append(dict(name=name,source_sha256=sha(src/(name+'.c')),binary_sha256=sha(lab/name)))
|
||||
save(lab,'client-provenance',dict(inputs=inputs,historical_nested_sha256=NESTED_SHA,adapted_nested_sha256=sha(src/'nested-client.c'),patch_sha256=sha(patch),protocol_regeneration='byte-identical',protocols={p.name:sha(p) for p in src.glob('*') if p.suffix in ['.h','.xml'] or p.name.endswith('-protocol.c')},C17='PASS',builds=builds,prior_manifests=receipts))
|
||||
for p in Path(__file__).parent.iterdir():
|
||||
assert p.suffix in ['.py','.md','.patch'],('generated repository file',p)
|
||||
shutil.copy2(p,lab/'fixture'/p.name)
|
||||
if p.suffix=='.py':ast.parse(p.read_text(),filename=str(p))
|
||||
shutil.copy2(REPO/'docs/design/STEP20C_STATIC_NESTED_VALID_GRAB_PROBE.md',lab/'protocol-before-run.md')
|
||||
protected=[REPO/p for p in ['AGENTS.md','docs/UX.md','docs/PACKAGING.md','PKGBUILD','patches/0001-mosaic-layout-viewport.patch']]
|
||||
protected+=[Path('/usr/bin/sway'),wl,Path('/usr/lib/libscenefx-0.5.so').resolve(),BINARY]
|
||||
protected+=list((REPO/'sway').rglob('*.c'))+list((REPO/'include').rglob('*.h'))
|
||||
save(lab,'protected-before',{str(p):sha(p) for p in protected})
|
||||
source=Path('/home/fy59/swayfx-mosaic-static-runtime-hm5kbdkt/mosaic-source')
|
||||
files=['sway/desktop/transaction.c','sway/desktop/xdg_shell.c','sway/desktop/output.c','sway/tree/view.c','sway/commands/exit.c','sway/ipc-server.c','sway/main.c','sway/commands/layout.c']
|
||||
save(lab,'candidate-source-audit',{str(source/p):sha(source/p) for p in files})
|
||||
freeze=[p for root in ['fixture','client-source','historical-source'] for p in (lab/root).rglob('*') if p.is_file()]+[lab/'protocol-before-run.md']
|
||||
save(lab,'fixture-inventory',inventory(lab/'results/fixture-freeze-sha256.txt',freeze))
|
||||
print('LAB='+str(lab))
|
||||
319
tooling/probes/step20c-static-nested-grab/run.py
Normal file
319
tooling/probes/step20c-static-nested-grab/run.py
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
#!/usr/bin/env python3
|
||||
"""One immutable-static headless process, two genuinely grabbed XDG chains.
|
||||
|
||||
WHY: outside dismissal must not preclude standard-return measurements.
|
||||
CONTRACT: every mutation/injection/capture consumes a persistent reservation.
|
||||
INVARIANT: only owned groups/sockets; actual codes survive terminal IPC failure.
|
||||
"""
|
||||
import argparse
|
||||
import fcntl
|
||||
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
|
||||
from support import OWNER,BINARY,BINARY_SHA,WLROOTS_SHA,LIMITS,sha,save,reserve,environment,verify
|
||||
from ipc_predicates import nodes,workspace,validate_outputs,validate_reference,measure_owner,scene_from_log,validate_camera
|
||||
from predicates import Defect,COLORS,paint,point,equal,reference,protocol,geometry,mapped,pair,dismissal,root_clip,popup_pixels,terminal
|
||||
|
||||
p=argparse.ArgumentParser();p.add_argument('lab',type=Path);lab=p.parse_args().lab.resolve()
|
||||
assert not __import__('sys').flags.optimize,'assertions required'
|
||||
assert OWNER.read_text().strip()==str(lab) and (lab/'campaign-owner').read_text().strip()==str(OWNER)
|
||||
lock=(lab/'run.lock').open('a+');fcntl.flock(lock,fcntl.LOCK_EX|fcntl.LOCK_NB)
|
||||
freeze=verify(lab/'results/fixture-freeze-sha256.txt');gate=json.loads((lab/'results/offline-gate.json').read_text())
|
||||
assert gate['marker']=='STATIC_NESTED_GRAB_FIXTURE_PREFLIGHT=PASS' and gate['fixture_sha256']==freeze['sha256']
|
||||
assert json.loads((lab/'results/binary-identity.json').read_text())['status']=='PASS'
|
||||
assert sha(BINARY)==sha(lab/'swayfx-mosaic')==BINARY_SHA
|
||||
assert not (lab/'run').exists(),'run path already used'
|
||||
reserve(lab,'run')
|
||||
run=lab/'run';run.mkdir();runtime=Path(tempfile.mkdtemp(prefix='msng-',dir='/tmp'));runtime.chmod(0o700)
|
||||
assert len(os.fsencode(str(runtime/'sway-ipc.4294967295.2147483647.sock')))<108
|
||||
(run/'runtime-path').write_text(str(runtime)+'\n')
|
||||
config='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'
|
||||
(run/'config').write_text(config)
|
||||
env=environment();env.update(XDG_RUNTIME_DIR=str(runtime),WLR_BACKENDS='headless',WLR_HEADLESS_OUTPUTS='2',WLR_RENDERER='pixman')
|
||||
processes=[];files=[];comp=None;pointer=None;failure=None;stage='startup';measures=False;rescue=False
|
||||
started=time.monotonic();finished=threading.Event();expired=threading.Event();chains={};clips={};captures=[];clicks=[];owners={};camera_rows=[]
|
||||
|
||||
def event(kind,**data):
|
||||
with (run/'events.jsonl').open('a') as out:out.write(json.dumps(dict(ns=time.monotonic_ns(),kind=kind,**data))+'\n')
|
||||
|
||||
def spawn(name,argv,debug=False):
|
||||
assert not expired.is_set()
|
||||
out=(run/(name+'.log')).open('w');files.append(out)
|
||||
proc=subprocess.Popen(list(map(str,argv)),stdin=subprocess.PIPE,stdout=out,stderr=subprocess.STDOUT,
|
||||
text=True,start_new_session=True,env={**env,**({'WAYLAND_DEBUG':'1'} if debug else {})})
|
||||
processes.append((name,proc));event('spawn',name=name,pid=proc.pid,argv=list(map(str,argv)));return proc
|
||||
|
||||
def stop_owned(sig=signal.SIGTERM):
|
||||
for name,proc in list(processes):
|
||||
try:os.killpg(proc.pid,sig)
|
||||
except ProcessLookupError:pass
|
||||
|
||||
def watchdog():
|
||||
while not finished.wait(.1):
|
||||
if time.monotonic()-started>150 or any(p.stat().st_size>32*1024*1024 for p in run.glob('*.log')):
|
||||
expired.set();stop_owned();return
|
||||
|
||||
thread=threading.Thread(target=watchdog,daemon=True);thread.start()
|
||||
|
||||
def log(name):return (run/(name+'.log')).read_text(errors='replace')
|
||||
|
||||
def wait(fn,seconds=6):
|
||||
end=min(time.monotonic()+seconds,started+143)
|
||||
while time.monotonic()<end:
|
||||
if expired.is_set():raise RuntimeError('deadline/log bound exceeded')
|
||||
if comp is not None and comp.poll() is not None:raise RuntimeError('unexpected compositor exit '+str(comp.returncode))
|
||||
value=fn()
|
||||
if value:return value
|
||||
time.sleep(.025)
|
||||
raise RuntimeError('fixture wait timeout at '+stage)
|
||||
|
||||
def ipc(*args):
|
||||
assert sock.is_relative_to(runtime) and sock.is_socket()
|
||||
r=subprocess.run(['swaymsg','-s',str(sock),*args],env=env,capture_output=True,text=True,timeout=4)
|
||||
event('ipc',args=args,exit=r.returncode,stdout=r.stdout,stderr=r.stderr)
|
||||
assert r.returncode==0,('IPC failure',args,r.returncode)
|
||||
return json.loads(r.stdout)
|
||||
|
||||
def cmd(text):
|
||||
assert all(x['success'] for x in ipc(text)),text
|
||||
|
||||
def snapshot(tag):
|
||||
tree=ipc('-t','get_tree');outputs=ipc('-t','get_outputs');ws=ipc('-t','get_workspaces')
|
||||
data=dict(ns=time.monotonic_ns(),tree=tree,outputs=outputs,workspaces=ws,focused=[n['id'] for n in nodes(tree) if n.get('focused')])
|
||||
save(lab,tag+'-snapshot',data);return data
|
||||
|
||||
def find(app,pid):
|
||||
candidates=[n for n in nodes(ipc('-t','get_tree')) if n.get('app_id')==app and n.get('pid')==pid]
|
||||
assert len(candidates)<=1
|
||||
return candidates[0] if candidates else None
|
||||
|
||||
def capture(tag,output):
|
||||
reserve(lab,'capture');time.sleep(.20)
|
||||
path=run/(tag+'.png');assert not path.exists()
|
||||
before=time.monotonic_ns()
|
||||
r=subprocess.run(['grim','-s','1','-o',output,str(path)],env=env,capture_output=True,text=True,timeout=4)
|
||||
event('capture',file=path.name,output=output,start_ns=before,exit=r.returncode,stderr=r.stderr)
|
||||
assert r.returncode==0
|
||||
im=Image.open(path).convert('RGB');assert im.size==(800,600)
|
||||
captures.append(dict(file=path.name,output=output,sha256=sha(path),ns=time.monotonic_ns()))
|
||||
save(lab,'captures',captures);return im
|
||||
|
||||
def inject(action,xy,grab=False):
|
||||
assert action in ['left','right','move'] and len(xy)==2
|
||||
x,y=xy;assert type(x) is int and type(y) is int and -800<=x<800 and 0<=y<600
|
||||
kinds=['move'] if action=='move' else ['click','move']+(['grab'] if grab else [])
|
||||
reserve(lab,*kinds)
|
||||
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(.18)
|
||||
event('injection',action=action,global_point=xy,sent=[x+800,y],extent=[1600,600],grab=grab)
|
||||
|
||||
def buttons(name):
|
||||
return [s for s in log(name).splitlines() if s.startswith(('POINTER_BUTTON ','BUTTON '))]
|
||||
|
||||
def click(tag,name,xy,grab=False,action='right'):
|
||||
before={n:len(buttons(n)) for n in [name,'reference','filler']}
|
||||
t0=time.monotonic_ns();inject(action,xy,grab)
|
||||
row=dict(tag=tag,global_point=xy,action=action,start_ns=t0,end_ns=time.monotonic_ns(),events={n:buttons(n)[before[n]:] for n in before})
|
||||
clicks.append(row);save(lab,'clicks',clicks);return row
|
||||
|
||||
def delivery(row,name,target):
|
||||
assert not row['events']['reference'] and not row['events']['filler'],'other client received visible popup button'
|
||||
return pair(row['events'][name],target)
|
||||
|
||||
def correlate_pointer(name,xy,rect,target):
|
||||
# WHY: an injection request alone does not identify applied popup origin.
|
||||
rows=re.findall(rf'POINTER_(?:ENTER|MOTION) target={target} .*?x=([\d.-]+) y=([\d.-]+)',log(name))
|
||||
assert rows,'missing pointer local coordinates'
|
||||
local=list(map(float,rows[-1]));expected=[xy[0]-rect[0],xy[1]-rect[1]]
|
||||
assert local==expected,('pointer/global geometry mismatch',target,local,expected)
|
||||
return dict(global_point=xy,local=local,rectangle=rect)
|
||||
|
||||
def close_client(proc,name,value):
|
||||
proc.stdin.write(value+'\n');proc.stdin.flush()
|
||||
code=proc.wait(timeout=3);assert code==0,(name,code)
|
||||
assert 'TOPLEVEL DESTROY' in log(name)
|
||||
wait(lambda:find('org.mosaic.NestedGrabProbe',proc.pid) is None)
|
||||
|
||||
# CONTRACT: applied owner, paint and XDG surface size are distinct measurements.
|
||||
def owner_state(name,proc,tag):
|
||||
snap=snapshot(tag);ws=workspace(snap['tree'],'1');owner=next(n for n in nodes(ws) if n.get('pid')==proc.pid)
|
||||
assert owner['app_id']=='org.mosaic.NestedGrabProbe' and owner['shell']=='xdg_shell' and owner['visible']
|
||||
assert ws['output']=='HEADLESS-1' and owner['focused'] and snap['focused']==[owner['id']]
|
||||
measured=measure_owner(log('compositor'),owner);scene=scene_from_log(log('compositor'),'HEADLESS-1')
|
||||
assert scene['workspace']==ws['id']
|
||||
return snap,ws,owner,measured,scene
|
||||
|
||||
def open_chain(name):
|
||||
global stage
|
||||
reserve(lab,'chain');cmd('workspace 1');proc=spawn(name,[lab/'nested-client','static_cross'],debug=True)
|
||||
wait(lambda:find('org.mosaic.NestedGrabProbe',proc.pid) and 'TOPLEVEL MAP ' in log(name));time.sleep(.6)
|
||||
snap,ws,owner,before,sc=owner_state(name,proc,name+'-before-camera')
|
||||
assert ws['layout']=='mosaic'
|
||||
current=ws['mosaic']['rows'][0]['scroll_x'];x,w=before['horizontal_content'];desired=-w//2;delta=int(x-desired)
|
||||
if delta:
|
||||
reserve(lab,'camera');cmd(f'[con_id={owner["id"]}] layout mosaic scroll {delta}');time.sleep(.6)
|
||||
snap,ws,owner,after,scene=owner_state(name,proc,name+'-applied')
|
||||
validate_camera(current,ws['mosaic']['rows'][0]['scroll_x'],delta,scene)
|
||||
assert scene['clip_valid'] and scene['global_clip']==[-800,0,800,600]
|
||||
assert not scene_from_log(log('compositor'),'HEADLESS-2')['clip_valid']
|
||||
refstate=validate_reference(snap,refid,ref.pid)
|
||||
camera_rows.append(dict(chain=name,before=current,delta=delta,after=scene['scroll'],before_owner=before,after_owner=after,scene=scene,reference=refstate))
|
||||
save(lab,'camera-calibration',camera_rows)
|
||||
if name=='E1':
|
||||
stage='reference';inject('move',[-790,590]);time.sleep(.5)
|
||||
first=capture('H2-reference-1','HEADLESS-2');time.sleep(.6)
|
||||
second=capture('H2-reference-2','HEADLESS-2');cmp=reference(first,second)
|
||||
save(lab,'reference-stability',dict(status='PASS',comparison=cmp,reference=refstate,interval='>=600ms',focus=snap['focused']))
|
||||
global baseline
|
||||
baseline=first
|
||||
stage=name+'-open-A';topim=capture(name+'-top','HEADLESS-1')
|
||||
topbox=paint(topim,'TOPLEVEL')['bbox'];assert topbox
|
||||
# Tiled scene+window offset is checked against actual painted blue origin.
|
||||
origin=[after['horizontal_content'][0],topbox[1]]
|
||||
assert topbox[0]-800==origin[0] and topbox[3]-topbox[1]==320
|
||||
assert origin[1]==after['scene'][1]+owner['window_rect']['y']
|
||||
owners[name]=dict(id=owner['id'],pid=proc.pid,workspace=ws['id'],logical_rect=owner['rect'],applied=after,drawn_size=[420,320],paint=paint(topim,'TOPLEVEL'),content_origin=origin)
|
||||
save(lab,'applied-owner-geometry',owners)
|
||||
cp=click(name+'-open-A',name,point(topim,'TOPLEVEL',-800),True,'left')
|
||||
wait(lambda:'POPUP_A MAP ' in log(name));time.sleep(.25)
|
||||
aim=capture(name+'-A','HEADLESS-1');ap=point(aim,'POPUP_A',-800)
|
||||
stage=name+'-open-B';click(name+'-open-B',name,ap,True,'left')
|
||||
wait(lambda:'POPUP_B MAP ' in log(name));time.sleep(.3)
|
||||
prot=protocol(log(name));g=geometry(log(name),origin)
|
||||
assert g['configured_A']==[100,80,350,170] and g['configured_B']==[100,60,300,130]
|
||||
correlate_pointer(name,ap,g['A'],'POPUP_A')
|
||||
left=capture(name+'-mosaic-H1','HEADLESS-1');right=capture(name+'-mosaic-H2','HEADLESS-2')
|
||||
rgb=popup_pixels(left,right,g,True,baseline)
|
||||
clip=root_clip(log('compositor'),origin,True)
|
||||
chains[name]=dict(protocol=prot,geometry=g,pixels=rgb,owner=owners[name])
|
||||
clips[name]=dict(tile=scene,root=clip,child=clip['child'])
|
||||
save(lab,'grab-serials',{n:v['protocol']['grabs'] for n,v in chains.items()})
|
||||
save(lab,'xdg-protocol-order',{n:v['protocol'] for n,v in chains.items()})
|
||||
save(lab,'popup-root-geometry',{n:v['geometry']['A'] for n,v in chains.items()})
|
||||
save(lab,'popup-nested-geometry',{n:v['geometry']['B'] for n,v in chains.items()})
|
||||
save(lab,'applied-clips',clips);save(lab,'mosaic-pixels',{n:v['pixels'] for n,v in chains.items()})
|
||||
return proc,owner,g,left
|
||||
|
||||
try:
|
||||
comp=spawn('compositor',[lab/'swayfx-mosaic','-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(WAYLAND_DISPLAY=display.name,SWAYSOCK=str(sock))
|
||||
assert Path(f'/proc/{comp.pid}/exe').resolve()==lab/'swayfx-mosaic'
|
||||
maps=Path(f'/proc/{comp.pid}/maps').read_text();(run/'proc-maps.txt').write_text(maps)
|
||||
paths=sorted({s.split()[-1] for s in maps.splitlines() if len(s.split())>=6 and s.split()[-1].startswith('/')})
|
||||
wl=Path('/usr/lib/libwlroots-0.20.so').resolve()
|
||||
assert [Path(x).resolve() for x in paths if 'libwlroots' in x]==[wl] and sha(wl)==WLROOTS_SHA
|
||||
assert not any('libscenefx' in x for x in paths)
|
||||
save(lab,'dependency-mappings',dict(status='PASS',paths=paths,wlroots=str(wl),wlroots_sha256=sha(wl),SceneFX_shared=False))
|
||||
outputs=ipc('-t','get_outputs');validate_outputs(outputs);save(lab,'output-geometry',outputs)
|
||||
pointer=spawn('pointer',[lab/'pointer-daemon']);wait(lambda:'POINTER_READY' in log('pointer'))
|
||||
cmd('workspace 2');ref=spawn('reference',[lab/'static-reference'],debug=True)
|
||||
rn=wait(lambda:find('org.mosaic.StaticReference',ref.pid));refid=rn['id'];cmd(f'[con_id={refid}] border none')
|
||||
cmd('workspace 1');filler=spawn('filler',[lab/'static-reference'],debug=True)
|
||||
wait(lambda:find('org.mosaic.StaticReference',filler.pid));cmd('layout mosaic')
|
||||
E1,owner1,g1,im1=open_chain('E1')
|
||||
stage='E1-visible-B';xy=point(im1,'POPUP_B',-800);row=click(stage,'E1',xy)
|
||||
result=delivery(row,'E1','POPUP_B');coords=correlate_pointer('E1',xy,g1['B'],'POPUP_B');mapped(log('E1'))
|
||||
save(lab,'mosaic-input',dict(status='PASS',pair=result,coordinates=coords,click=row))
|
||||
stage='E1-forbidden-B';B=g1['B'];xy=[int((max(0,B[0])+min(800,B[0]+B[2]))//2),B[1]+30]
|
||||
assert B[0]<=xy[0]<B[0]+B[2] and 8<=xy[0]<792
|
||||
cut=len(log('E1'));row=click(stage,'E1',xy);time.sleep(.3)
|
||||
ds=dismissal(log('E1')[cut:],row['events']['E1']);save(lab,'dismissal',ds)
|
||||
refpair=row['events']['reference'];assert len(refpair) in [0,2]
|
||||
save(lab,'forbidden-input',dict(status='PASS',click=row,popup_events=row['events']['E1'],reference_events=refpair,dismissal=ds))
|
||||
inject('move',[-790,590]);clean=capture('E1-dismissed-H2','HEADLESS-2')
|
||||
assert equal(baseline,clean)['equal'],'E1 dismissal residue'
|
||||
close_client(E1,'E1','close_owner')
|
||||
E2,owner2,g2,im2=open_chain('E2');assert owner2['id']!=owner1['id'] and E2.pid!=E1.pid
|
||||
s1=[g['serial'] for g in chains['E1']['protocol']['grabs']];s2=[g['serial'] for g in chains['E2']['protocol']['grabs']]
|
||||
assert len(set(s1+s2))==4 and max(s1)<min(s2),'serial reuse across chains'
|
||||
stage='E2-standard';mapped(log('E2'));reserve(lab,'transition');cmd(f'[con_id={owner2["id"]}] layout splith');time.sleep(.6)
|
||||
snap,ws,std_owner,std_measure,std_scene=owner_state('E2',E2,'standard')
|
||||
assert ws['id']==owners['E2']['workspace'] and ws['layout']=='splith'
|
||||
if std_scene['clip_valid'] or std_scene['layout_enum']!=1:raise Defect('standard tiled clip retained')
|
||||
mapped(log('E2'))
|
||||
stdleft=capture('standard-H1','HEADLESS-1');stdright=capture('standard-H2','HEADLESS-2')
|
||||
blue=paint(stdleft,'TOPLEVEL')['bbox'];assert blue
|
||||
stdorigin=[std_measure['horizontal_content'][0],blue[1]];assert blue[0]-800==stdorigin[0]
|
||||
stdg=geometry(log('E2'),stdorigin);stdclip=root_clip(log('compositor'),stdorigin,False)
|
||||
stdrgb=popup_pixels(stdleft,stdright,stdg,False,baseline)
|
||||
save(lab,'standard-return',dict(status='PASS',same_workspace=ws['id'],owner=std_owner,applied=std_measure,scene=std_scene,root_clip=stdclip,child_clip='no independent clip in exact source; standard B pixels/input exercise removed inherited boundary',geometry=stdg,mapped_before_and_after=True,no_prior_popup_done=True))
|
||||
save(lab,'standard-popup-pixels',stdrgb)
|
||||
stage='E2-standard-B';xy=point(stdright,'POPUP_B',0);row=click(stage,'E2',xy)
|
||||
Bpair=delivery(row,'E2','POPUP_B');Bcoords=correlate_pointer('E2',xy,stdg['B'],'POPUP_B');mapped(log('E2'))
|
||||
E2.stdin.write('close_b\n');E2.stdin.flush();wait(lambda:'COMMAND close_b DONE' in log('E2'))
|
||||
assert 'POPUP_B DESTROY ' in log('E2') and 'POPUP_A DESTROY ' not in log('E2') and 'POPUP_A POPUP_DONE ' not in log('E2')
|
||||
stage='E2-standard-A';Aimage=capture('standard-A-H2','HEADLESS-2');assert paint(Aimage,'POPUP_B')['count']==0
|
||||
xy=point(Aimage,'POPUP_A',0);rowA=click(stage,'E2',xy);Apair=delivery(rowA,'E2','POPUP_A')
|
||||
Acoords=correlate_pointer('E2',xy,stdg['A'],'POPUP_A')
|
||||
save(lab,'standard-popup-input',dict(status='PASS',B=Bpair,A=Apair,B_coordinates=Bcoords,A_coordinates=Acoords,explicit_B_close=True,A_survived=True))
|
||||
stage='E2-cleanup';close_client(E2,'E2','close_owner');time.sleep(.35)
|
||||
final=capture('final-H2','HEADLESS-2');assert equal(baseline,final)['equal'],'final H2 residue'
|
||||
assert not any(paint(final,n)['count'] for n in ['POPUP_A','POPUP_B'])
|
||||
snap=snapshot('final');validate_reference(snap,refid,ref.pid)
|
||||
assert filler.poll() is None and ref.poll() is None
|
||||
measures=True;event('measurements-completed',status='PASS');stage='completed'
|
||||
except Exception as exc:
|
||||
failure=dict(type=type(exc).__name__,message=str(exc),stage=stage,verdict='BLOCKED' if isinstance(exc,Defect) else 'INCONCLUSIVE')
|
||||
event('failure',**failure)
|
||||
finally:
|
||||
# WHY: shutdown transport outcome is not folded into check=True scientific gates.
|
||||
exit_data=None
|
||||
if comp is not None and comp.poll() is None and 'sock' in globals() and sock.is_socket():
|
||||
try:
|
||||
r=subprocess.run(['swaymsg','-s',str(sock),'exit'],env=env,capture_output=True,text=True,timeout=3)
|
||||
(run/'terminal-exit.stdout').write_text(r.stdout);(run/'terminal-exit.stderr').write_text(r.stderr)
|
||||
comp_code=comp.wait(timeout=3)
|
||||
bad_protocol=any(re.search(r'wl_display[^\n]*\.error\(|protocol error',log(n),re.I) for n,_ in processes if n not in ['compositor','pointer'])
|
||||
exit_data=terminal(measures,r.returncode,comp_code,log('compositor'),bad_protocol,expired.is_set())
|
||||
exit_data.update(stdout=r.stdout,stderr=r.stderr,measurements_completed=measures)
|
||||
except Exception as exc:
|
||||
exit_data=dict(scientific_cleanup_pass=False,error=repr(exc),measurements_completed=measures)
|
||||
if exit_data is None:exit_data=dict(scientific_cleanup_pass=False,compositor_exit=comp.poll() if comp else None,measurements_completed=measures)
|
||||
codes=[]
|
||||
for name,proc in reversed(processes):
|
||||
if proc.poll() is None:
|
||||
try:proc.wait(timeout=.7)
|
||||
except subprocess.TimeoutExpired:
|
||||
if name=='pointer':proc.stdin.close() # normal EOF destroys virtual pointer
|
||||
else:
|
||||
rescue=True
|
||||
try:os.killpg(proc.pid,signal.SIGTERM)
|
||||
except ProcessLookupError:pass
|
||||
try:proc.wait(timeout=.7)
|
||||
except subprocess.TimeoutExpired:
|
||||
rescue=True
|
||||
try:os.killpg(proc.pid,signal.SIGKILL)
|
||||
except ProcessLookupError:pass
|
||||
proc.wait(timeout=.7)
|
||||
codes.append(dict(name=name,pid=proc.pid,exit=proc.returncode))
|
||||
if proc.stdin and not proc.stdin.closed:proc.stdin.close()
|
||||
finished.set();thread.join(timeout=1)
|
||||
for out in files:out.close()
|
||||
for name,proc in processes:
|
||||
try:os.killpg(proc.pid,0)
|
||||
except ProcessLookupError:pass
|
||||
else:rescue=True;os.killpg(proc.pid,signal.SIGKILL)
|
||||
assert all(not Path('/proc',str(row['pid'])).exists() for row in codes),'owned process residual'
|
||||
private_sockets=[str(p) for p in runtime.iterdir() if p.is_socket()]
|
||||
shutil.rmtree(runtime)
|
||||
exit_data.update(rescue_cleanup=rescue,owned_process_codes=codes)
|
||||
if rescue:exit_data['scientific_cleanup_pass']=False
|
||||
if measures and (not exit_data['scientific_cleanup_pass'] or any(r['exit']!=0 for r in codes)):
|
||||
failure=dict(type='TerminalCleanupFailure',message='terminal/process evidence failed',stage='completed',verdict='INCONCLUSIVE')
|
||||
save(lab,'terminal-exit',exit_data)
|
||||
save(lab,'cleanup',dict(status='PASS' if not rescue else 'RESCUE',runtime=str(runtime),runtime_removed=not runtime.exists(),owned_processes=codes,sockets_before_owned_directory_removal=private_sockets,private_socket_residual=False))
|
||||
save(lab,'runtime',dict(status='PASS' if failure is None else failure['verdict'],failure=failure,stage=stage,measurements_completed=measures,duration_seconds=time.monotonic()-started,watchdog=expired.is_set(),rescue_cleanup=rescue,runner_exit=0 if failure is None else 1))
|
||||
verify(lab/'results/fixture-freeze-sha256.txt')
|
||||
print('RUNTIME', 'PASS' if failure is None else failure)
|
||||
raise SystemExit(0 if failure is None else 1)
|
||||
100
tooling/probes/step20c-static-nested-grab/support.py
Normal file
100
tooling/probes/step20c-static-nested-grab/support.py
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
"""Private probe ownership, durable budgets and bounded argv execution.
|
||||
|
||||
WHY: an exhausted experiment must not be restarted by replacing its LAB.
|
||||
CONTRACT: stable exclusive owner and lock files; fsync precedes every action.
|
||||
INVARIANT: failures consume all reservations, never refund or reset counters.
|
||||
"""
|
||||
import fcntl
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
REPO = Path('/home/fy59/Documents/Development/swayfx-mosaic')
|
||||
OWNER = Path.home() / 'swayfx-mosaic-static-nested-grab-v1.owner'
|
||||
BINARY = Path('/home/fy59/swayfx-mosaic-static-runtime-hm5kbdkt/stage-static/usr/bin/swayfx-mosaic')
|
||||
BINARY_SHA = 'f82557c1a9978a5e9665b9b4291b74880253725359e3df1c5b8d554921f7603b'
|
||||
WLROOTS_SHA = '7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837'
|
||||
NESTED_SHA = '2e40c98af72a0df8b316810589a0be8f4425a763db1de448b49a7813956b6f7d'
|
||||
LIMITS = dict(run=1, chain=2, grab=4, click=9, capture=16, camera=2, transition=1, move=24)
|
||||
|
||||
def sha(path):
|
||||
return hashlib.sha256(Path(path).read_bytes()).hexdigest()
|
||||
|
||||
def save(lab, name, data):
|
||||
(Path(lab)/'results'/(name+'.json')).write_text(json.dumps(data,indent=2)+'\n')
|
||||
|
||||
def durable(path, data):
|
||||
fd,tmp=tempfile.mkstemp(prefix='.ledger-',dir=path.parent)
|
||||
with os.fdopen(fd,'w') as out:
|
||||
json.dump(data,out,indent=2);out.write('\n');out.flush();os.fsync(out.fileno())
|
||||
os.replace(tmp,path)
|
||||
fd=os.open(path.parent,os.O_RDONLY|os.O_DIRECTORY)
|
||||
try:os.fsync(fd)
|
||||
finally:os.close(fd)
|
||||
|
||||
def initial(lab):
|
||||
path=Path(lab)/'results/budget.json'
|
||||
with path.open('x') as out:
|
||||
json.dump(dict(limits=LIMITS,used=dict.fromkeys(LIMITS,0),events=[]),out,indent=2)
|
||||
out.write('\n');out.flush();os.fsync(out.fileno())
|
||||
fd=os.open(path.parent,os.O_RDONLY|os.O_DIRECTORY)
|
||||
try:os.fsync(fd)
|
||||
finally:os.close(fd)
|
||||
|
||||
def reserve(lab, *kinds):
|
||||
lab=Path(lab)
|
||||
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())
|
||||
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('seq')==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 not kinds or any(k not in LIMITS for k in kinds):
|
||||
raise RuntimeError('corrupt/invalid ledger')
|
||||
if any(state['used'][k]+kinds.count(k)>LIMITS[k] for k in kinds):
|
||||
raise RuntimeError('budget exhausted')
|
||||
for k in kinds:
|
||||
state['used'][k]+=1
|
||||
state['events'].append(dict(seq=len(state['events']),kind=k,ns=time.monotonic_ns()))
|
||||
durable(path,state)
|
||||
return state
|
||||
|
||||
def environment():
|
||||
# WHY: inherited desktop/loader settings must not select a daily socket.
|
||||
excluded={'DISPLAY','WAYLAND_DISPLAY','SWAYSOCK','I3SOCK','XDG_RUNTIME_DIR',
|
||||
'WAYLAND_DEBUG','DBUS_SESSION_BUS_ADDRESS','XDG_SESSION_ID','XDG_SEAT',
|
||||
'XDG_VTNR','XDG_SESSION_TYPE','XDG_CURRENT_DESKTOP','XDG_SESSION_DESKTOP',
|
||||
'LD_LIBRARY_PATH','LD_PRELOAD','LD_AUDIT','PKG_CONFIG_PATH','WLR_BACKENDS',
|
||||
'WLR_RENDERER','WLR_HEADLESS_OUTPUTS','WLR_DRM_DEVICES','WLR_LIBINPUT_NO_DEVICES'}
|
||||
env={k:v for k,v in os.environ.items() if k not in excluded and not k.startswith('WLR_')}
|
||||
env.update(LC_ALL='C',PYTHONDONTWRITEBYTECODE='1')
|
||||
return env
|
||||
|
||||
def command(lab,label,argv,cwd=None):
|
||||
result=subprocess.run(list(map(str,argv)),cwd=cwd,env=environment(),capture_output=True,text=True,timeout=30)
|
||||
(lab/'raw'/(label+'.stdout')).write_text(result.stdout)
|
||||
(lab/'raw'/(label+'.stderr')).write_text(result.stderr)
|
||||
with (lab/'raw/commands.jsonl').open('a') as out:
|
||||
out.write(json.dumps(dict(label=label,argv=list(map(str,argv)),exit=result.returncode,cwd=str(cwd)))+'\n')
|
||||
assert result.returncode==0,(label,result.returncode,result.stderr)
|
||||
return result.stdout
|
||||
|
||||
def verify(manifest):
|
||||
seen=set()
|
||||
for row in Path(manifest).read_text().splitlines():
|
||||
digest,p=row.split(' ',1)
|
||||
assert p not in seen and sha(p)==digest,(manifest,p)
|
||||
seen.add(p)
|
||||
assert seen
|
||||
return dict(entries=len(seen),sha256=sha(manifest))
|
||||
|
||||
def inventory(path, files):
|
||||
with Path(path).open('x') as out:
|
||||
for p in sorted(files):out.write(f'{sha(p)} {p}\n')
|
||||
return verify(path)
|
||||
Loading…
Reference in a new issue