test(mosaic): qualify static nested standard return and integration readiness
E2 is FEASIBLE for the measured matrix. The initial private integration candidate is BUILDABLE; its accepted comment delta is NOT REBUILT. STEP20C remains NEXT and production multi-output remains NOT YET SUPPORTED.
This commit is contained in:
parent
c5f96a34e3
commit
4ef16a4019
30 changed files with 3025 additions and 0 deletions
21
CHANGELOG.md
21
CHANGELOG.md
|
|
@ -106,3 +106,24 @@ uncommitted and unpublished, and readiness only permits requesting a separate
|
|||
future authorization. No compositor/runtime authorization,
|
||||
production/UX/package change or STEP20D work follows. Architecture UNDECIDED;
|
||||
STEP20C NEXT; production multi-output NOT YET SUPPORTED.
|
||||
|
||||
|
||||
## Static E2 completion and private integration evidence — 2026-10-13
|
||||
|
||||
- Recorded the independently accepted shared applied-standard-transition gate:
|
||||
104 targeted offline tests PASS, historical replay and 277 classified assertion
|
||||
sites, with the separate older-suite 145 PASS/61 setup errors preserved.
|
||||
- Recorded the sole unchanged-static-binary E2 run as EXPERIMENTAL / FEASIBLE
|
||||
for two genuine grabs, Mosaic pixels, applied splith return, standard B/A
|
||||
input, explicit B close/A survival and normal owner close/full-H2 restoration.
|
||||
Raw terminal IPC1, startup entries and historical INCONCLUSIVE findings remain.
|
||||
- Recorded a private initial integration candidate BUILDABLE from one SceneFX
|
||||
build, one Mosaic build/model/ELF check and one exact five-file DESTDIR stage.
|
||||
Its distinct binary is graphically NOT TESTED. Independent P2 comment repair
|
||||
is accepted as COMMENT-ONLY / NOT REBUILT; initial recipe/build evidence stays
|
||||
unchanged, recipe execution NOT RUN and legal compliance NOT APPROVED.
|
||||
- Added the [completion report](docs/design/STEP20C_STATIC_E2_TRANSITION_AND_COMPLETION.md)
|
||||
and [current fixture results](tooling/probes/step20c-static-e2-completion/README_RESULTS.md),
|
||||
preserving the original README as a sealed pre-execution protocol. Final batch
|
||||
audit is PASS / ACCEPTED with no BLOCKING finding. No production source, UX, package, public ABI
|
||||
or cumulative patch change; STEP20C NEXT, multi-output NOT YET SUPPORTED.
|
||||
|
|
|
|||
|
|
@ -488,3 +488,31 @@ compositor/client exits0 remain intact. No rerun or algorithm change; historical
|
|||
is preserved for publication by the E2 completion batch. Architecture UNDECIDED, STEP20C NEXT,
|
||||
production multi-output NOT YET SUPPORTED; broader damage/generation/output/
|
||||
effects/ABI/distribution gates remain open.
|
||||
|
||||
|
||||
## Static E2 completion and private integration evidence — 2026-10-13
|
||||
|
||||
The [E2 completion report](design/STEP20C_STATIC_E2_TRANSITION_AND_COMPLETION.md)
|
||||
records independent B/C/D acceptance. The shared applied-transition predicate
|
||||
passes 104 targeted offline tests; its 277-site source audit is classification,
|
||||
not graphical execution proof. The sole unchanged-static-binary C run is
|
||||
**EXPERIMENTAL / FEASIBLE for the tested matrix**: two real grabbed popups,
|
||||
owner-derived Mosaic pixels, ordered current/applied splith return with clips
|
||||
absent, standard B input, explicit B close/A survival, standard A input and normal
|
||||
owner close with exact whole-H2 restoration. This correlates one live root and
|
||||
owner/workspace/output; it does not establish an atomic transaction generation.
|
||||
The historical runtime remains INCONCLUSIVE with its missing gates unchanged.
|
||||
|
||||
The conditional private D candidate is **BUILDABLE for its initial build**,
|
||||
model/ELF and five-file DESTDIR stage. Its distinct binary is graphically
|
||||
**NOT TESTED**. The accepted four-file comment correction is
|
||||
**COMMENT-ONLY / NOT REBUILT**; the initial recipe/sources/manifests/binary remain
|
||||
unchanged. Characterized85 overlap symbols and 17 local structs/six offsets do
|
||||
not establish maintained semantic ABI. Legal notice closure and named release/
|
||||
security roles remain open. No production source, public ABI, library dependency,
|
||||
packaging or popup UX contract changes. Final batch audit is PASS / ACCEPTED; documentation and fixtures are approved
|
||||
for normal publication.
|
||||
[Current fixture results](../tooling/probes/step20c-static-e2-completion/README_RESULTS.md)
|
||||
supersede the original README's sealed pre-execution status only.
|
||||
Architecture remains UNDECIDED for production integration; STEP20C NEXT,
|
||||
production multi-output NOT YET SUPPORTED.
|
||||
|
|
|
|||
|
|
@ -429,3 +429,34 @@ compositor/client exits0 remain intact. No rerun or algorithm change; historical
|
|||
is preserved for publication by the E2 completion batch. Architecture UNDECIDED, STEP20C NEXT,
|
||||
production multi-output NOT YET SUPPORTED; broader damage/generation/output/
|
||||
effects/ABI/distribution gates remain open.
|
||||
|
||||
|
||||
## Static E2 completion support boundary — 2026-10-13
|
||||
|
||||
The [completion report](design/STEP20C_STATIC_E2_TRANSITION_AND_COMPLETION.md)
|
||||
and [current fixture results](../tooling/probes/step20c-static-e2-completion/README_RESULTS.md)
|
||||
record B offline PASS, C **FEASIBLE for the tested E2 matrix** and D initial
|
||||
**BUILDABLE** with an accepted **COMMENT-ONLY / NOT REBUILT** delta. C closes
|
||||
its own sampled standard-return B/A input, explicit B-close/A-survival and normal
|
||||
owner-close/full-H2 restoration gates. It does not rewrite the historical
|
||||
INCONCLUSIVE campaign, whose corresponding gates remain NOT TESTED.
|
||||
|
||||
C ran once on normal scale1 outputs at x=-800/0 with the unchanged static binary.
|
||||
New static hidden-owner, saved-buffer/effects, vertical/scaled/rotated and
|
||||
migration/resize matrices remain unqualified; historical private evidence retains
|
||||
its own limits. Settled full-H2 equality and 256 sampled damage traces do not
|
||||
qualify every intermediate frame, complete damage/stale pixels or atomic
|
||||
transaction/render/input generation. Universal click-through and final offscreen
|
||||
popup UX remain UNDECIDED.
|
||||
|
||||
D's new binary has **NOT TESTED** graphics. Initial builds/model/ELF/stage pass;
|
||||
annotated sources are NOT REBUILT and the future recipe is syntax-only, NOT RUN.
|
||||
The exact five-file private stage includes the binary and four license notices;
|
||||
semantic ABI maintenance is NOT ESTABLISHED, legal compliance NOT APPROVED,
|
||||
and release/security names remain required. Raw terminal IPC1 and startup
|
||||
EGL_BAD_ALLOC entries remain preserved despite runner/compositor/client0 and
|
||||
completed C observations. A fresh old 206-suite setup retained 145 PASS/61 setup
|
||||
errors, separate from B's accepted104 tests and the historical206 result.
|
||||
Final batch audit is PASS / ACCEPTED. Publication identities are recorded in
|
||||
Git history and the private closeout receipt. STEP20C remains NEXT, production
|
||||
multi-output NOT YET SUPPORTED; no daily-driver qualification follows.
|
||||
|
|
|
|||
|
|
@ -347,3 +347,19 @@ compositor/client exits0 remain intact. No rerun or algorithm change; historical
|
|||
is preserved for publication by the E2 completion batch. Architecture UNDECIDED, STEP20C NEXT,
|
||||
production multi-output NOT YET SUPPORTED; broader damage/generation/output/
|
||||
effects/ABI/distribution gates remain open.
|
||||
|
||||
|
||||
[Static E2 completion and private integration results](design/STEP20C_STATIC_E2_TRANSITION_AND_COMPLETION.md)
|
||||
and [current fixture results](../tooling/probes/step20c-static-e2-completion/README_RESULTS.md):
|
||||
EXPERIMENTAL. B 104 targeted offline tests PASS with independent acceptance;
|
||||
C FEASIBLE for the sole static nested-popup standard-return/input/normal-close
|
||||
matrix; D initial candidate BUILDABLE, accepted comment delta COMMENT-ONLY /
|
||||
NOT REBUILT, distinct candidate graphics NOT TESTED. The original fixture
|
||||
[README](../tooling/probes/step20c-static-e2-completion/README.md) is the sealed
|
||||
pre-execution protocol, preserved byte-for-byte; current status is in
|
||||
README_RESULTS. Historical INCONCLUSIVE verdicts remain unchanged. Final batch
|
||||
audit is PASS / ACCEPTED, with publication identities recorded in Git history
|
||||
and the private closeout receipt. Complete damage,
|
||||
atomic generation, wider static graphics/output coverage, maintained ABI and
|
||||
legal/maintenance gates remain open. STEP20C NEXT, production multi-output
|
||||
NOT YET SUPPORTED; no production/UX/packaging contract change.
|
||||
|
|
|
|||
|
|
@ -433,3 +433,29 @@ compositor/client exits0 remain intact. No rerun or algorithm change; historical
|
|||
is preserved for publication by the E2 completion batch. Architecture UNDECIDED, STEP20C NEXT,
|
||||
production multi-output NOT YET SUPPORTED; broader damage/generation/output/
|
||||
effects/ABI/distribution gates remain open.
|
||||
|
||||
|
||||
## Static E2 completion and conditional private integration — 2026-10-13
|
||||
|
||||
The [completion report](design/STEP20C_STATIC_E2_TRANSITION_AND_COMPLETION.md)
|
||||
records B offline PASS with accepted final handoff, C independent ACCEPTED /
|
||||
**FEASIBLE for the tested matrix**, and conditional D initial **BUILDABLE**.
|
||||
C measures two genuine grabs, applied standard return, B then A standard input,
|
||||
explicit child close/A survival and normal owner close with exact whole-H2 RGB.
|
||||
Historical INCONCLUSIVE evidence is unchanged. D passes its one SceneFX build,
|
||||
one Mosaic build/model/ELF check and one exact five-file stage with zero graphics
|
||||
or installations. The accepted comment delta is **COMMENT-ONLY / NOT REBUILT**;
|
||||
C does not qualify the different D binary. The original fixture README remains
|
||||
a sealed pre-execution protocol; use
|
||||
[README_RESULTS](../tooling/probes/step20c-static-e2-completion/README_RESULTS.md).
|
||||
|
||||
Final batch audit is PASS / ACCEPTED with no BLOCKING finding. The accompanying
|
||||
documentation/fixture publication and its verified remote identities are recorded
|
||||
in Git history and the private closeout receipt.
|
||||
A separate bounded authorization is required for an annotated rebuild or D's
|
||||
exact-binary graphical qualification. Complete damage, atomic generation, static
|
||||
hidden-owner/effects/saved-buffer continuity, wider output/migration matrices,
|
||||
maintained ABI, notice/legal closure and named maintenance roles remain gates.
|
||||
This is STEP20C evidence, not implementation of roadmap STEP20D. STEP20A DONE;
|
||||
STEP20B EXPERIMENTAL; STEP20C NEXT; STEP20D–G PLANNED. Production multi-output
|
||||
remains NOT YET SUPPORTED. No production/UX/packaging contract changes.
|
||||
|
|
|
|||
338
docs/design/STEP20C_STATIC_E2_TRANSITION_AND_COMPLETION.md
Normal file
338
docs/design/STEP20C_STATIC_E2_TRANSITION_AND_COMPLETION.md
Normal file
|
|
@ -0,0 +1,338 @@
|
|||
# STEP20C static E2 transition and completion
|
||||
|
||||
**EXPERIMENTAL. Phase B PASS; Phase C FEASIBLE for the tested matrix;
|
||||
Phase D initial candidate BUILDABLE, comment delta ACCEPTED / NOT REBUILT.**
|
||||
Independent B/C/D reviews accept these bounded findings. Phase E final deep audit is PASS / ACCEPTED, with no BLOCKING finding.
|
||||
Publication identities are recorded in Git history and the private closeout
|
||||
receipt; this report does not self-reference its publication commit.
|
||||
Ticket: `SWAYFX_MOSAIC_STEP20C_E2_COMPLETION_AND_PRIVATE_INTEGRATION_BATCH_V1`.
|
||||
Historical freeze: `c5f96a34e3924275179365938d02f3a318c0c4bf`, verified on
|
||||
Forgejo and GitHub. The historical runtime remains INCONCLUSIVE.
|
||||
|
||||
## Accepted bounded experiment contract
|
||||
|
||||
Canonical [architecture](../ARCHITECTURE.md), [UX](../UX.md),
|
||||
[limitations](../KNOWN_LIMITATIONS.md), [roadmap](../ROADMAP.md),
|
||||
[development](../DEVELOPMENT.md), [packaging](../PACKAGING.md) and
|
||||
[upstream](../UPSTREAM.md) govern. This experiment changes no production or UX
|
||||
contract. All historical LAB bytes remain unchanged. Historical fixture scripts are
|
||||
unchanged, and the repository fixtures remain byte-identical to the Phase A
|
||||
publication baseline `c5f96a34` during B–E. The Phase A README status update
|
||||
and its earlier runtime-inventory binding are explicitly documented below.
|
||||
|
||||
The derived fixture under `tooling/probes/step20c-static-e2-completion/` must
|
||||
share `validate_applied_standard_transition(...)` between offline replay and
|
||||
runtime. It verifies ordered applied Mosaic, targeted accepted splith IPC,
|
||||
applied standard and stabilized standard observations, with exact owner/root/
|
||||
workspace/output attribution, absent tiled/root clips and live mapped popups.
|
||||
Pending state is insufficient. Multiple roots or ambiguous epochs are
|
||||
INCONCLUSIVE. Independent observations do not establish atomic generation.
|
||||
Negative tests cover all seventeen specified temporal/provenance failures,
|
||||
legitimate omissions and empty PASS records. The entire E2 assertion path and
|
||||
handoff must be independently accepted before any runtime reservation.
|
||||
|
||||
## Sole authorized runtime, conditional on offline gates
|
||||
|
||||
One fresh private LAB, ticket-specific durable owner and exclusive consumed
|
||||
launch token. Unchanged static binary SHA256
|
||||
`f82557c1a9978a5e9665b9b4291b74880253725359e3df1c5b8d554921f7603b`,
|
||||
build ID `942db64269640778ee433e5970ba1100bc60f94c`; actual mapped wlroots
|
||||
SHA256 `7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837`.
|
||||
No SceneFX dynamic mapping, compositor rebuild or pristine control.
|
||||
Private headless H1(-800,0), H2(0,0), each800x600, scale1/normal; runtime0700,
|
||||
clean environment and explicit private IPC socket on every command.
|
||||
Limits: run1, chain1, grabs2, complete clicks4, camera1, transition1,
|
||||
PNG12, positions16, maximum150 seconds. Failed runs are never refunded/retried.
|
||||
|
||||
Unique static H2 reference #303038, border none by actual con_id, full content.
|
||||
Two stabilized full-RGB references must be identical without mask/tolerance.
|
||||
A and B open from distinct real button serials with TOPLEVEL->A->B hierarchy,
|
||||
configure/ACK/map/protocol corroboration. Applied camera/owner geometry and
|
||||
root/tiled clips must explain measured Mosaic H1 pixels and whole-H2 equality.
|
||||
Targeted splith preserves workspace and mapped owner/A/B; shared temporal
|
||||
predicate qualifies observed clip removal. Standard H2 overflow is expected.
|
||||
B right press/release comes from a measured solid green interior. Explicit
|
||||
client close of B must be distinguished from popup_done; A retains its XDG
|
||||
object, map, pixels and input. Recalculate A right-click point from a new
|
||||
capture after B disappears. Normal owner close destroys popups; final whole
|
||||
H2 RGB must exactly equal the initial reference, with no A/B residual color.
|
||||
Preserve raw runner, terminal IPC, compositor/client codes and protocol logs,
|
||||
including cleanup/rescue. Terminal IPC1 alone does not undo completed science.
|
||||
|
||||
## Conditional private integration and publication
|
||||
|
||||
Only runtime FEASIBLE plus accepted review permits a private integration
|
||||
candidate outside production Git. Pinned source/patch provenance is mandatory;
|
||||
limits are one SceneFX static build, one Mosaic build, one DESTDIR stage, zero
|
||||
graphical runs/installations. Stage only `/usr/bin/swayfx-mosaic` and justified
|
||||
notices under `/usr/share/licenses/swayfx-mosaic/`. BUILDABLE is not production
|
||||
readiness, and the candidate binary has no graphical qualification from E2.
|
||||
Reviewed documentation/fixture source may be committed and pushed normally;
|
||||
no production source, real PKGBUILD, cumulative patch, binary, PNG or build file.
|
||||
|
||||
STEP20A DONE; STEP20B EXPERIMENTAL; STEP20C NEXT; STEP20D–G PLANNED.
|
||||
Production multi-output NOT YET SUPPORTED.
|
||||
|
||||
|
||||
## Recorded completion — 2026-10-13
|
||||
|
||||
This is the current results record for the ticket. The original fixture
|
||||
[README](../../tooling/probes/step20c-static-e2-completion/README.md) is the
|
||||
**sealed pre-execution protocol**, preserved byte-for-byte in the accepted
|
||||
runtime manifest; its PREPARATION/NOT RUN wording describes that earlier state.
|
||||
Use [README_RESULTS](../../tooling/probes/step20c-static-e2-completion/README_RESULTS.md)
|
||||
for the synchronized current status. Historical reports and fixtures retain
|
||||
their original INCONCLUSIVE verdicts. This report adds evidence, not production
|
||||
architecture, final popup UX, a packaging change or STEP20D implementation.
|
||||
|
||||
### Phase A — historical preservation
|
||||
|
||||
Initial batch HEAD was `54392cc0`; historical publication was verified at
|
||||
`c5f96a34e3924275179365938d02f3a318c0c4bf`, equal to origin/main and github/main
|
||||
at that gate with a clean tracked tree. This identifies the historical
|
||||
publication only; the completion batch has no recorded publication SHA yet.
|
||||
The 60-entry historical source manifest SHA256 is
|
||||
`263f5dbee5e96f2c49619802d063115f792718f6aeb71fa41c58c73b83eb8c91`;
|
||||
the 178-entry historical evidence manifest is
|
||||
`e8b2307612ce2402ff9b31e68b50a47d666ca38cd8cce1d67096297f8129034a`.
|
||||
All entries, thirteen PNGs and raw terminal codes were verified. Historical
|
||||
runner/dispatch1, IPC1 and compositor/clients0 remain unchanged. Historical
|
||||
standard input, explicit B close/A survival and final H2 restoration remain
|
||||
NOT TESTED in that campaign.
|
||||
|
||||
The Phase A publication updated only historical repository documentation
|
||||
status, including the runtime fixture README. Its old 92-entry pre-execution
|
||||
inventory still records that README at SHA256
|
||||
`12d324b8cc971565c1e1f59b370cc7ada5df2567f6c6573f8b4f6cf60b8e56d8`;
|
||||
the published README is now
|
||||
`9574f2ab2cd538d78f9bf57bf98484dd5fdd66203013658709ddeb02ba5332ed`.
|
||||
The inventory and LAB copies remain intact. No new PASS92 is claimed: the
|
||||
final source60/evidence178 seals used here and by replay verify every entry.
|
||||
The final reviewer classified this documentation provenance note NON_BLOCKING.
|
||||
|
||||
### Phase B — shared applied-transition validation, offline only
|
||||
|
||||
`validate_applied_standard_transition(...)` is shared by replay and runner.
|
||||
It requires source-backed applied/current Mosaic, a targeted accepted splith
|
||||
request, applied standard and at least two coherent stabilized standard
|
||||
observations. Exact owner/root/workspace/output, live XDG hierarchy, protocol
|
||||
and capture identity are correlated, and observed clip reactivation is rejected.
|
||||
Pending state or ambiguous roots/epochs cannot qualify the gate. This bounded
|
||||
correlation does not establish atomic transaction generation.
|
||||
|
||||
The old request-start timestamp was absent. Offline replay uses the explicit
|
||||
actual interval `[181922658327071,181923034516066]`, reply
|
||||
`181923035203192`, retained old root `181923034904618` and cleared standard roots
|
||||
`181923236944243` and `181923848675032`. No old row is discarded. The historical
|
||||
standard PNG has content origin `(-398,25)`, A `[-298,105,350,170]`,
|
||||
B `[-198,165,300,130]`, and H2 A3120/B13260 pixels. This is **OFFLINE replay**,
|
||||
not a new runtime or retroactive qualification of missing historical input.
|
||||
|
||||
The final derived fixture corrects old-configure mixing and the pre-root
|
||||
opening window. All 104 targeted tests pass, including seventeen temporal/
|
||||
provenance negatives and fifteen explicitly synthetic handoff tests. Nonempty
|
||||
executed receipts are required; the 277 static assertion inventory classifies
|
||||
source sites and is not graphical execution proof. The independent tester
|
||||
reproduced 104 PASS, C17 syntax for four clients and nineteen Python AST checks.
|
||||
The independent reviewer reran 88 pure tests and verified all 104 actual
|
||||
receipts, accepting B. All B gate markers pass before dispatch.
|
||||
|
||||
The attempted fresh setup of the older 206-test suite retained 145 PASS and
|
||||
61 errors from missing preflight native-SHM evidence and dependent source-audit
|
||||
setup. Its 145 preflight checks and replay passed. This setup limitation is
|
||||
separate from the new 104-test gate and does not rewrite the historical qualified
|
||||
206 result. No synthetic execution receipts were substituted. Early failed
|
||||
bounded tester attempts are retained privately; the final C validation has
|
||||
28 PASS.
|
||||
|
||||
### Phase C — sole new runtime, FEASIBLE for this E2 matrix
|
||||
|
||||
Private LAB: `/home/fy59/swayfx-mosaic-e2-completion-dk1hq9an`.
|
||||
The exclusive launch token was consumed once; post-run refusal was verified.
|
||||
There was no E1/pristine-control build or runtime retry. Binary SHA256 remains
|
||||
`f82557c1a9978a5e9665b9b4291b74880253725359e3df1c5b8d554921f7603b`,
|
||||
build ID `942db64269640778ee433e5970ba1100bc60f94c`. Actual mapped wlroots is
|
||||
`7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837`.
|
||||
No dynamic SceneFX mapping was observed. Actual rendering used FX/GLES;
|
||||
an environment renderer request is not evidence of Pixman execution.
|
||||
Outputs were H1 `(-800,0)` and H2 `(0,0)`, 800×600, scale1, normal transform.
|
||||
|
||||
Reference container7/PID2130252 was solid `#303038` with empty decoration and
|
||||
full client size. Stored border width2 is valid under the observed border-none
|
||||
state. Owner9/PID2130263 stayed in workspace6. One camera scroll -320 changed
|
||||
480→160; applied scene was `(-320,0,640,600)`, content origin `(-318,25)`.
|
||||
Native SHM420×320 remains distinct from assigned content636×573. Root
|
||||
`0x55b8d482ea30` carried local clip `[-482,-25,800,600]`, global
|
||||
`[-800,0,800,600]`.
|
||||
|
||||
Real left serial33/ACK34 opened A; real left serial42/ACK43 opened B.
|
||||
The wire chain was TOPLEVEL XDG9 → A XDG12 → B XDG15. Configure/ACK/map and
|
||||
wire/client observations corroborate both valid grabs. Mosaic A was
|
||||
`[-218,105,350,170]`, B `[-118,165,300,130]`.
|
||||
|
||||
| Settled capture | H1 A | H1 B | H2 A | H2 B | Whole H2 reference equality |
|
||||
| --- | ---: | ---: | ---: | ---: | --- |
|
||||
| Mosaic | 24080 | 15340 | 0 | 0 | Exact |
|
||||
| Standard A+B | 28880 | 25740 | 3120 | 13260 | Ordinary overflow expected |
|
||||
| Standard after explicit B close | 50660 | 0 | 8840 | 0 | Ordinary A overflow expected |
|
||||
| Final owner close | 0 | 0 | 0 | 0 | Exact |
|
||||
|
||||
Targeted `[con_id=9] layout splith` request `191234782728967` received accepted
|
||||
reply `191234785087227`. First applied standard root was `191234987463354`,
|
||||
stabilized root `191235601727206`; four more standard callbacks through B input
|
||||
retained absent tiled/root clips. Owner/root/workspace/output and mapped popups
|
||||
were preserved. Standard content origin became `(-398,25)` with the same A/B
|
||||
rectangles as the old standard replay. Standard H2 popup pixels are expected
|
||||
ordinary overflow, not a reference-equality failure.
|
||||
|
||||
Measured B point `(10,175)` delivered right pair53/54 at local `(208,10)`.
|
||||
Explicit `close_b` produced exact wire destruction and `COMMAND close_b DONE`,
|
||||
with no `popup_done`. A retained the same mapped objects and pixels. A new
|
||||
post-close capture selected point `(10,115)` and right pair59/60 at local
|
||||
`(308,10)`. Normal `close_owner` destroyed A before TOPLEVEL and exited0.
|
||||
Final H1/H2 contain zero A/B colors. Reference1, reference2, Mosaic H2 and final
|
||||
H2 all have PNG SHA256
|
||||
`4471e93ccb6cf3ff4970f61fce405431a228af598d2f1f030a3b7586962ecd7a`;
|
||||
all 480000 RGB pixels match without mask or tolerance. Twelve captures and
|
||||
six exact A/B geometry/occlusion masks were independently verified.
|
||||
|
||||
Runtime was 13.05365723601426 seconds of 150: run1, chain1, grabs2, complete
|
||||
clicks4, captures12, camera1, transition1, positions5/16. Runner and dispatch0;
|
||||
terminal IPC1 retained stderr `Unable to receive IPC response`; compositor,
|
||||
E2, filler, reference and pointer0. No rescue/watchdog was needed; private
|
||||
runtime `/tmp/msng-znx8zrw4` disappeared and owned PIDs were reaped. Response
|
||||
loss during normal shutdown is compatible with these observations, not causally
|
||||
proven. Two startup EGL_BAD_ALLOC enumeration entries are retained; they did
|
||||
not prevent the measured matrix or normal exits. The 256 damage traces are
|
||||
sampled coverage, not complete damage or atomic-generation proof.
|
||||
Independent C review ACCEPTED the FEASIBLE finding; final tester C has 28 PASS.
|
||||
|
||||
### Phase D — private initial candidate BUILDABLE, annotated delta NOT REBUILT
|
||||
|
||||
C's accepted FEASIBLE gate enabled the conditional private integration dossier
|
||||
at `LAB/integration-candidate/`. Pinned sources: SwayFX
|
||||
`fd71a6bdc061bd633b488ae7b83e8a6981d22f86`, Mosaic
|
||||
`cd550382b311efdbfe3ed892f4e1d281d852e754`, SceneFX
|
||||
`37ccd723bef49e6891156ffafce8f549f01446cc`, wlroots
|
||||
`d783533489e1f75d6886c2ab5c5960090ef268f8`. Archive reconstruction and eight
|
||||
historical patches passed applicability and source-equivalence checks.
|
||||
One SceneFX static build, one Mosaic build, one model test1/1 and one DESTDIR
|
||||
stage passed. Zero graphical runs and zero installations occurred.
|
||||
The private initial binary is
|
||||
`616aa504334d88aada5b1596df624957f39b0f1ae975a1a04105fbf73aea6dd0`,
|
||||
build ID `a0a46860f3b0f1b9dcc7f9044acca7f8489cceec`; it differs from C and
|
||||
is graphically **NOT TESTED**. Private cumulative SwayFX patch SHA256 is
|
||||
`8acde9a9755e0db16fee4c49c479649414bd23f0dab399e4e6a4aa2e1053b1d3`,
|
||||
SceneFX `768372773f8755083aa02be5547863b1269ef9facb2131b0a7acc5574c822856`.
|
||||
These private patches are not published in the fixture or production patch.
|
||||
No additional Meson delta was needed beyond the existing private static
|
||||
pkg-config selection. `PKGBUILD.future` syntax passed; recipe execution is
|
||||
NOT RUN and the production PKGBUILD remains unchanged.
|
||||
|
||||
The complete stage has exactly five regular owned files:
|
||||
|
||||
- `/usr/bin/swayfx-mosaic`;
|
||||
- `/usr/share/licenses/swayfx-mosaic/INCORPORATED-NOTICES`;
|
||||
- `/usr/share/licenses/swayfx-mosaic/SceneFX-LICENSE`;
|
||||
- `/usr/share/licenses/swayfx-mosaic/SwayFX-LICENSE`;
|
||||
- `/usr/share/licenses/swayfx-mosaic/wlroots-LICENSE`.
|
||||
|
||||
The one-binary Meson manifest is the intermediate stage before notice addition;
|
||||
the final five-file manifest is complete. An initial tester concern about absent
|
||||
notices was corrected against that final manifest. Relocation checks pass;
|
||||
85 overlapping symbols are characterized, 17 structs/six offsets were locally
|
||||
checked with C17 and 71 transitive libraries inventoried. Semantic ABI maintenance
|
||||
is **NOT ESTABLISHED**. Conservative notice inventory covers 895 inputs,
|
||||
599 headers,12 shaders,7 XML,16 generated inputs and282 full notice blocks.
|
||||
Exact extraction/header obligations and legal release clearance remain open:
|
||||
legal compliance **NOT APPROVED**. Named release/security roles remain required.
|
||||
|
||||
The initial independent D review was CORRECTION_REQUIRED on P2 source comments.
|
||||
One bounded `comment-repair/` derivation annotated four files in two private
|
||||
patches: SceneFX
|
||||
`21f057ff4180bb5dd35af35070aa4a95923be83f81abb864793e5bfb93643b01`,
|
||||
Mosaic `7fee137101abc0cd5024cea172c5904268ddf995bc5855c570e4dfabd643c970`.
|
||||
Lexical checks preserve all active C tokens; exact patch application and parent/
|
||||
annotated hashes pass. Delta review ACCEPTED / P2 RESOLVED. This is
|
||||
**COMMENT-ONLY / NOT REBUILT**: the original sources/builds/binary/manifests are
|
||||
unchanged; inserted lines may affect `__LINE__` diagnostics. The initial recipe
|
||||
was neither amended nor executed for these annotated copies. A future annotated
|
||||
recipe must add both patches after the eight historical patches and requires
|
||||
separate rebuild authorization. D BUILDABLE means initial build evidence plus
|
||||
resolved comment obligation, never a compiled annotated candidate.
|
||||
The sealed `results/final-gate.json` retains its original review-PENDING
|
||||
snapshot. The authoritative appended `/batch/phase-d-gate.json` records the
|
||||
accepted final D gate without rewriting that snapshot.
|
||||
|
||||
### Provenance and independent review bindings
|
||||
|
||||
`/batch` below means `/home/fy59/swayfx-mosaic-e2-batch-hyrd8qm1`; `LAB` means
|
||||
the sole C laboratory above. All referenced private manifests and entries were
|
||||
verified; artifacts, raw logs, PNGs, builds and integration patches remain
|
||||
outside Git. The 23 accepted fixture source files, including the original README,
|
||||
remain byte-identical. The current results README is a documentation append,
|
||||
not a modification of the reviewed runtime inputs.
|
||||
|
||||
| Record | Entries / status | SHA256 |
|
||||
| --- | --- | --- |
|
||||
| B/C source handoff | 73 verified entries | `b90bd1564acfdd78276843a5fe5ddb2ba1b5fb75fdd5f097fa64f073d0a89cbc` |
|
||||
| C evidence | 66 verified entries | `1f3723db89a6a5dca05f094d8e7c370feebcc640c922838b7abecb99167b6067` |
|
||||
| D initial source | 1003 verified entries | `fca097b0376694fd563b33fb49923086a19d1a5a9cfe5ba9fc045707b07bc61f` |
|
||||
| D initial evidence | 172 verified entries | `4be20af63e0ee4f4b5c2187584bbb7087ece1be5cd9d5576246ae5a62eda2957` |
|
||||
| `/batch/review-b-report.md` | ACCEPTED | `eddefb3dfbde5410c32b1addf940977d0b2a176c91009597bf4130ba4929ef82` |
|
||||
| `/batch/review-c-report.md` | ACCEPTED / FEASIBLE | `e506b42e64a18fd237cea5199f67d49745436ee9a10f01798f07eb2072050f68` |
|
||||
| `/batch/review-d-delta.md` | ACCEPTED / P2 RESOLVED | `6a7299e530ce3abde51997572b0bccccc61c5d88180c15925dcecc6cb76a32d7` |
|
||||
|
||||
The original D review `/batch/review-d-initial.md`, earlier tester failures,
|
||||
raw terminal failures and historical fixtures remain available unchanged.
|
||||
|
||||
### Consolidated boundary and next gate
|
||||
|
||||
| Domain | Evidence now recorded | Remaining qualification |
|
||||
| --- | --- | --- |
|
||||
| Static tiled clip | Prior static RGB/input/standard retest | Wider graphics/daily use NOT QUALIFIED |
|
||||
| Static grabbed popups in Mosaic | Historical E1 and new C E2 sampled pixels/hierarchy/grabs | Other popup families/geometries NOT TESTED by C |
|
||||
| Static standard return/input/lifecycle | C E2 FEASIBLE, B then A input and explicit normal close | Universal click-through/final popup UX UNDECIDED |
|
||||
| Hidden-owner guard | Historical private evidence | New static C hidden-owner matrix NOT TESTED |
|
||||
| Saved-buffer continuity | Separate historical saved-target evidence | Static C continuity NOT TESTED; lifetime gate open |
|
||||
| Blur/shadow/effects | Historical PARTIALLY_FEASIBLE with retained limits | New static C effects NOT TESTED |
|
||||
| Scale/rotation/vertical geometry | Historical bounded private probes | Generalized static pair NOT QUALIFIED |
|
||||
| Output migration/resize | Historical bounded ownership evidence | Static C/D migration NOT QUALIFIED |
|
||||
| Damage/stale pixels | Settled restoration and 256 sampled C traces | Complete/intermediate-frame coverage NOT QUALIFIED |
|
||||
| Transaction generation | Ordered applied observations | Atomic transaction/render/input identity NOT ESTABLISHED |
|
||||
| ABI and maintenance | D local header/symbol/relocation characterization | Maintained semantic ABI, upgrades/security gates open |
|
||||
| Private static packaging | D initial BUILDABLE, exact five-file stage | Annotated source NOT REBUILT; recipe NOT RUN; legal NOT APPROVED |
|
||||
| Production integration/distribution | No source/package/UX change | STEP20C NEXT, production multi-output NOT YET SUPPORTED |
|
||||
|
||||
Phase E documentation prepares the authorized second publication with intended
|
||||
message `test(mosaic): qualify static nested standard return and integration readiness`.
|
||||
The final deep batch audit is PASS / ACCEPTED, with no BLOCKING finding.
|
||||
Normal publication identities are recorded by the accompanying Git commit and
|
||||
`/batch/closeout.json`; no self-referential commit SHA is embedded here. Any graphical qualification of the D binary or annotated rebuild
|
||||
requires a separate bounded authorization; C's result cannot qualify D.
|
||||
STEP20A DONE; STEP20B EXPERIMENTAL; STEP20C NEXT; STEP20D–G PLANNED.
|
||||
|
||||
## Final audit and gate record
|
||||
|
||||
The independent Astra final review is preserved at `/batch/final-review.md`,
|
||||
SHA256 `088f1b0e9d32095d20c842400d865a3e880cf122ce51b952cbf342c99ee3fe3f`.
|
||||
It reran 88 pure tests, recalculated all claimed A/C/D manifest entries and
|
||||
C's actual geometry/RGB/input/lifecycle evidence, and accepted publication.
|
||||
No additional runtime, build or staging was executed during closeout.
|
||||
|
||||
```text
|
||||
STATIC_NESTED_RUNTIME_FREEZE=PASS
|
||||
E2_TEMPORAL_REPLAY=PASS
|
||||
E2_TEMPORAL_NEGATIVES=PASS
|
||||
E2_ASSERTION_AUDIT=PASS
|
||||
E2_OFFLINE_TESTS=PASS
|
||||
E2_INDEPENDENT_REVIEW=ACCEPTED
|
||||
STATIC_E2_RUNTIME_PREPARATION=PASS
|
||||
STATIC_E2_RUNTIME_HANDOFF=PASS
|
||||
SWAYFX_MOSAIC_STATIC_E2_COMPLETION=FEASIBLE
|
||||
STEP20C_PRIVATE_INTEGRATION_CANDIDATE=BUILDABLE
|
||||
```
|
||||
|
||||
BUILDABLE refers to the initial D build; the accepted annotated source delta
|
||||
remains COMMENT-ONLY / NOT REBUILT. Final remote identities and publication
|
||||
outcome are recorded separately in `/batch/closeout.json` after direct checks.
|
||||
105
tooling/probes/step20c-static-e2-completion/README.md
Normal file
105
tooling/probes/step20c-static-e2-completion/README.md
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
# Static E2 completion fixture
|
||||
|
||||
**EXPERIMENTAL / PREPARATION; runtime NOT RUN, review PENDING.**
|
||||
Ticket `SWAYFX_MOSAIC_STEP20C_E2_COMPLETION_AND_PRIVATE_INTEGRATION_BATCH_V1`.
|
||||
No production source, UX, packaging or cumulative patch change. STEP20C stays
|
||||
NEXT; production multi-output is NOT YET SUPPORTED.
|
||||
|
||||
`derivation.json` identifies initial exact copies from the frozen
|
||||
`step20c-static-nested-geometry-replay` fixture at
|
||||
`c5f96a34e3924275179365938d02f3a318c0c4bf`. Model, IPC adapters, observations,
|
||||
pixels, predicates and core geometry/protocol/terminal tests are retained.
|
||||
`reusable_tests.py` preserves the identified AST method bodies. The historical
|
||||
readiness aggregate and its ACCEPTED review grant no acceptance here.
|
||||
|
||||
`transition.py` is shared by raw historical replay and the new E2-only runner.
|
||||
It examines the entire selected H1 callback window: old applied Mosaic,
|
||||
targeted accepted splith, first coherent applied standard, then at least two
|
||||
coherent standard observations. It verifies source-backed `current` layout,
|
||||
actual owner scene, exact workspace/output/XDG identities, unique enabled root,
|
||||
clip removal, capture intervals, and no reactivation through standard B input.
|
||||
PI_POPUP lacks owner IDs and reports the global popup root on both outputs.
|
||||
Unique live XDG hierarchy plus exact IPC ancestry supplies bounded correlation;
|
||||
independent observations do **not** establish an atomic transaction generation.
|
||||
|
||||
Historical request-start time was not recorded. `replay.py` uses the previous
|
||||
actual capture event and first monotonic trace after actual command handling as
|
||||
an explicit request interval. It preserves the old applied root after handling
|
||||
and before reply, followed by two cleared roots; no row is discarded. The
|
||||
historical cleanup was compositor shutdown, corroborated by seat capabilities0,
|
||||
then destruction. It does not qualify normal owner close. Replay cuts only at
|
||||
that explicit shutdown boundary, retaining any earlier dismissal/destruction.
|
||||
|
||||
The new runner records exact IPC request start and actual reply separately.
|
||||
Its transition window starts at a complete source-backed Mosaic commit after
|
||||
A/B are fully mapped and before the request; earlier pre-root callbacks stay
|
||||
in separate opening evidence. Absence of an eligible callback is INCONCLUSIVE.
|
||||
Only E2 runs: real left TOPLEVEL opens A, real left A opens B, right B in standard,
|
||||
explicit client close B, then a fresh measured A right click. Each point comes
|
||||
from a measured solid interior with an8px margin. Every click records fresh
|
||||
client protocol deltas, full button pairs and wire/callback coordinates.
|
||||
Configured/applied destinations remain distinct from native420x320 SHM; an old
|
||||
Mosaic observation is not reparsed with a later standard configure. Client
|
||||
close requests are recorded before flushing stdin. B destruction and COMMAND
|
||||
close_b DONE must match exact objects, without popup_done. A preserves map/IDs,
|
||||
exact pixels on both outputs, zero B colors and input. Normal owner close must
|
||||
be corroborated by command, child-first destruction and wire destroy requests.
|
||||
Final whole H2 RGB equals the initial #303038 reference without mask/tolerance;
|
||||
final H1 has no popup colors. Raw terminal IPC/compositor/client codes and rescue
|
||||
are retained separately. Terminal IPC1 alone does not undo completed science.
|
||||
|
||||
Budget: run1, chain1, grabs2, complete clicks4, camera1, transition1, PNG12,
|
||||
positions16. Maximum150s; active work watchdog cuts at143s to leave cleanup
|
||||
margin. Two full H2 references are identical. Runtime is0700, private two-output
|
||||
headless H1(-800,0)/H2(0,0), 800x600 scale1 normal, with explicit private swaymsg
|
||||
socket. No pristine control, compositor/SceneFX build or desktop operation.
|
||||
|
||||
## Offline preparation
|
||||
|
||||
All outputs below are in a **new** directory outside Git. Commands never reserve
|
||||
a real runtime budget, launch a client/compositor or change historical LAB bytes.
|
||||
|
||||
```sh
|
||||
F=tooling/probes/step20c-static-e2-completion
|
||||
O=/home/fy59/swayfx-mosaic-e2-batch-hyrd8qm1/implementation-final-offline
|
||||
mkdir "$O"
|
||||
PYTHONDONTWRITEBYTECODE=1 python "$F/tests_offline.py" --output "$O/test-receipts.json" > "$O/tests.log" 2>&1
|
||||
PYTHONDONTWRITEBYTECODE=1 python "$F/replay.py" --output "$O/replay.json"
|
||||
PYTHONDONTWRITEBYTECODE=1 python "$F/audit.py" "$O/source-audit.json"
|
||||
PYTHONDONTWRITEBYTECODE=1 python "$F/gate.py" "$O"
|
||||
```
|
||||
|
||||
The gate requires nonempty exact test inventory, actual executed PASS receipts,
|
||||
every temporal negative rejection, final source hashes, raw replay equality and
|
||||
an exhaustive current static assertion inventory. Audit PASS means classified
|
||||
source inventory; graphical execution coverage remains NOT TESTED.
|
||||
Negative handoff tests simulate records only in temporary isolated directories;
|
||||
these are explicitly synthetic and never mint a real independent review.
|
||||
|
||||
`handoff.py prepare "$O"` creates the sole fresh registered LAB, exact source
|
||||
manifest and prospective handoff. It reuses the historical exact C17 clients
|
||||
and their actual adapted static_cross/protocol sources byte-for-byte; no build
|
||||
or client execution is required. Static binary identity remains
|
||||
f82557c1a9978a5e9665b9b4291b74880253725359e3df1c5b8d554921f7603b,
|
||||
build ID942db64269640778ee433e5970ba1100bc60f94c, mapped wlroots identity
|
||||
7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837.
|
||||
The original source directory is part of the frozen runtime manifest, alongside
|
||||
installed fixture, binaries/clients, actual private trace source and receipts.
|
||||
|
||||
## Independent review before dispatch
|
||||
|
||||
The implementation author does not generate acceptance. A genuine independent
|
||||
read-only reviewer must produce a report and `results/handoff-review.json` with:
|
||||
status ACCEPTED, independent=true, read_only=true,
|
||||
scope STATIC_E2_COMPLETION_FINAL_BYTES, blockers=[], fixture_sha256,
|
||||
source_sha256 (all final source files), runtime_handoff_sha256,
|
||||
authorization_sha256, registration_sha256, gate_sha256, report_file and its
|
||||
report_sha256. These bind the actual final files, not a historical gate.
|
||||
|
||||
After parent approval, `handoff.py preflight LAB` publishes an exact preflight.
|
||||
Only then may the parent run `launch.py LAB` once. Exclusive O_EXCL consumed token
|
||||
precedes dispatch. Runner repeats reviewed source preflight and checks the exact
|
||||
consumed token before its lock or run debit. Wrong/old LAB, token, binary,
|
||||
wlroots, stale review, changed manifest/runner, existing run/socket and prior
|
||||
consumption fail closed. Failure never refunds the ticket; no automatic retry.
|
||||
No runtime dispatch has been performed during implementation.
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
# Static E2 completion — current results
|
||||
|
||||
**EXPERIMENTAL. B PASS; C FEASIBLE for the tested E2 matrix; D initial candidate
|
||||
BUILDABLE with accepted COMMENT-ONLY / NOT REBUILT delta.** Final batch audit
|
||||
is PASS / ACCEPTED, with no BLOCKING finding. Publication identities are
|
||||
recorded in Git history and the private closeout receipt.
|
||||
|
||||
The [original README](README.md) is the **sealed pre-execution protocol**.
|
||||
Its PREPARATION / runtime NOT RUN / review PENDING statements record the state
|
||||
before execution. It remains byte-identical, together with the 23 accepted
|
||||
fixture sources, to the 73-entry runtime handoff manifest SHA256
|
||||
`b90bd1564acfdd78276843a5fe5ddb2ba1b5fb75fdd5f097fa64f073d0a89cbc`.
|
||||
Use this results README and the
|
||||
[completion report](../../../docs/design/STEP20C_STATIC_E2_TRANSITION_AND_COMPLETION.md)
|
||||
for current evidence, provenance, exact hashes, failed attempts and open gates.
|
||||
Do not rerun the runtime commands: the sole launch token and run budget are
|
||||
consumed, and post-run refusal was verified. Historical fixtures/LABs remain
|
||||
unchanged and historical INCONCLUSIVE results are not retroactively upgraded.
|
||||
|
||||
The shared transition validator passes 104 targeted offline tests, including 17
|
||||
negative temporal/provenance cases and 15 synthetic handoff tests. Independent B
|
||||
review ACCEPTED; the 277-site static audit is not runtime execution proof. An
|
||||
attempted fresh setup of the old 206 suite retained 145 PASS/61 setup errors;
|
||||
that limitation is separate from the accepted104 gate and historical206 result.
|
||||
|
||||
C used the unchanged static binary
|
||||
`f82557c1a9978a5e9665b9b4291b74880253725359e3df1c5b8d554921f7603b`
|
||||
once in `/home/fy59/swayfx-mosaic-e2-completion-dk1hq9an`, on 800×600 scale1
|
||||
normal outputs at x=-800/0. Two genuine grabs formed TOPLEVEL→A→B. Mosaic
|
||||
retained H1 popup pixels and exact full H2 RGB. Ordered applied splith observations
|
||||
removed tiled/root clips while preserving live popups. Measured standard B input,
|
||||
explicit B destruction, A survival and measured A input passed; normal owner close
|
||||
restored all 480000 H2 RGB pixels. Standard H2 overflow is expected. Independent
|
||||
C review ACCEPTED and tester final28 PASS. Runner/dispatch0, terminal IPC1
|
||||
(`Unable to receive IPC response`), compositor/clients0 and two startup
|
||||
EGL_BAD_ALLOC enumeration entries remain recorded. Shutdown response-loss
|
||||
causality is compatible but NOT PROVEN. Runtime 13.053657s/150; run1, chain1,
|
||||
grabs2, clicks4, captures12, camera1, transition1, positions5/16; no rescue.
|
||||
|
||||
D is entirely private under that LAB's `integration-candidate/`. One SceneFX
|
||||
build, one Mosaic build, model1/1, relocation/header checks and one DESTDIR stage
|
||||
pass, with zero graphical runs/installations. Initial binary
|
||||
`616aa504334d88aada5b1596df624957f39b0f1ae975a1a04105fbf73aea6dd0`
|
||||
is distinct from C and graphically NOT TESTED. Stage contains exactly the binary
|
||||
and four full notices under `/usr/share/licenses/swayfx-mosaic/`. The initial
|
||||
future recipe has syntax PASS and execution NOT RUN; production PKGBUILD is
|
||||
unchanged. P2 comments were corrected in four private annotated copies with two
|
||||
patches, accepted independently as COMMENT-ONLY / NOT REBUILT. Initial builds,
|
||||
recipe, sources and manifests remain unchanged. A future annotated recipe must
|
||||
apply both comment patches after the eight historical patches, then undergo a
|
||||
separately authorized rebuild. The original review-PENDING D final-gate snapshot
|
||||
is sealed; `/home/fy59/swayfx-mosaic-e2-batch-hyrd8qm1/phase-d-gate.json` is the
|
||||
appended accepted gate. Integration patches are not copied into this fixture.
|
||||
|
||||
Complete damage/intermediate frames, atomic generation, broader output geometry,
|
||||
migration, hidden-owner/effects/saved-buffer static coverage, maintained semantic
|
||||
ABI and legal notices remain open. Legal compliance NOT APPROVED; release/security
|
||||
names remain required. No production/UX/packaging contract changes. STEP20A DONE;
|
||||
STEP20B EXPERIMENTAL; STEP20C NEXT; STEP20D–G PLANNED. Production multi-output
|
||||
NOT YET SUPPORTED. Raw logs, PNGs, binaries and builds remain outside Git.
|
||||
52
tooling/probes/step20c-static-e2-completion/audit.py
Normal file
52
tooling/probes/step20c-static-e2-completion/audit.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
"""Exhaustive assertion inventory for the derived E2 runtime, offline only.
|
||||
|
||||
PASS means classified source inventory, not executed runtime assertion coverage.
|
||||
Independent review must inspect these final bytes before dispatch.
|
||||
"""
|
||||
import ast
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
from model import require
|
||||
from support import sha
|
||||
HERE=Path(__file__).resolve().parent
|
||||
RUNTIME=('run.py','model.py','observations.py','ipc_predicates.py','predicates.py','pixels.py','transition.py','lifecycle.py','authorization.py','ledger.py','support.py','handoff.py','launch.py','gate.py')
|
||||
DOMAINS={'run.py':'runtime orchestration and raw outcomes','model.py':'typed coordinate and provenance contract',
|
||||
'observations.py':'native SHM vs applied destination geometry','ipc_predicates.py':'IPC identity and applied trace adapters',
|
||||
'predicates.py':'RGB/input/XDG protocol/unique-root/terminal observations','pixels.py':'exact RGB geometry',
|
||||
'transition.py':'current applied temporal transition','lifecycle.py':'explicit close and input lifecycle',
|
||||
'authorization.py':'reviewed dispatch before runtime','ledger.py':'nonrefundable action accounting',
|
||||
'support.py':'private environment/source integrity','handoff.py':'exclusive prospective source/review binding',
|
||||
'launch.py':'exclusive single dispatch','gate.py':'semantic offline receipts'}
|
||||
|
||||
def audit():
|
||||
records=[]
|
||||
for name in RUNTIME:
|
||||
source=HERE/name;tree=ast.parse(source.read_text())
|
||||
for n in ast.walk(tree):
|
||||
if isinstance(n,ast.Assert) or (isinstance(n,ast.Call) and isinstance(n.func,ast.Name) and n.func.id=='require'):
|
||||
records.append(dict(file=name,line=n.lineno,domain=DOMAINS[name],
|
||||
predicate=ast.unparse(n),ast_sha256=hashlib.sha256(ast.dump(n,include_attributes=False).encode()).hexdigest(),
|
||||
runtime_execution='NOT TESTED',review='PENDING_INDEPENDENT_REVIEW'))
|
||||
run=(HERE/'run.py').read_text();tree=ast.parse(run)
|
||||
require('E1' not in run,'E1-runtime-entirely-removed')
|
||||
calls=[n for n in ast.walk(tree) if isinstance(n,ast.Call) and isinstance(n.func,ast.Name)]
|
||||
require(sum(n.func.id=='open_chain' for n in calls)==1,'single-runtime-chain')
|
||||
require(sum(n.func.id=='click' for n in calls)==4,'four-complete-click-sites')
|
||||
require(sum(n.func.id=='validate_applied_standard_transition' for n in calls)==2,'transition-through-B-input')
|
||||
require(all(name in run for name in ('opening_pair','explicit_child_close','a_survival','normal_owner_close','final_restoration')),'E2-full-path-shared-predicates')
|
||||
require("global baseline" in run and "if name=='E2':" in run,'reference-relocated-to-E2')
|
||||
require('applied_model(owners' not in run,'old-configure-not-reparsed-as-current')
|
||||
require(bool(records),'nonempty-source-audit')
|
||||
return dict(status='PASS',scope='EXHAUSTIVE_STATIC_ASSERTION_INVENTORY; runtime NOT TESTED',
|
||||
sources={n:sha(HERE/n) for n in RUNTIME},records=records,
|
||||
reviewed_hazards=['native SHM420x320 vs configured/applied content; no painted-height=320 predicate',
|
||||
'IPC y is not content origin; scene+titlebar+window offset is explicit',
|
||||
'current PI_SCENE and actual clip/owner trees; pending and IPC reply insufficient',
|
||||
'old Mosaic geometry reused directly, not reparsed using later standard configure',
|
||||
'root traces have no owner ID; only bounded unique-root XDG/IPC correlation',
|
||||
'right input pairs and left opening serials are different predicates',
|
||||
'actual terminal IPC code and each owned process code stay separate'])
|
||||
|
||||
if __name__=='__main__':
|
||||
import json,sys
|
||||
with Path(sys.argv[1]).open('x') as out:json.dump(audit(),out,indent=2);out.write('\n')
|
||||
10
tooling/probes/step20c-static-e2-completion/authorization.py
Normal file
10
tooling/probes/step20c-static-e2-completion/authorization.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
"""Fail closed before any lock, ledger debit or subprocess."""
|
||||
import json
|
||||
from pathlib import Path
|
||||
from model import require
|
||||
|
||||
def authorize(lab,authorization):
|
||||
require(authorization is not None,'explicit-authorization-required')
|
||||
from handoff import preflight
|
||||
preflight(lab,authorization,review=True,consumed=True)
|
||||
return json.loads(Path(authorization).read_text())
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"status": "EXPERIMENTAL exact retained historical client/protocol byte contract",
|
||||
"source_receipt": "client-provenance.json in offline LAB; scanner must reproduce exact hashes",
|
||||
"adapted_nested_sha256": "6eafb8ad63b0b06b8297e7e72711056a6d42bdf33ccf795793eeda77f98221c7",
|
||||
"placement_sha256": "e0e87fb0cb3d6e7dafb1cd59ccb50f323f2934e65247526199da1695487d3e4a",
|
||||
"protocols": {
|
||||
"xdg-shell-client.h": "a09726137ea9fe1a23d58c95f1d33ab77a3fc692c63726bc82fbc91ab8e5c6fc",
|
||||
"xdg-shell-protocol.c": "9e12fcd5676d8c6b6cfd233c2eba563163c0ea7e586cb6e27f202a328323c7f0",
|
||||
"virtual-pointer-client.h": "0dcb6107cf948a007dcef0a6b2192e4f7b9166c0f57b499ca49b7aaf48adc56c",
|
||||
"virtual-pointer-protocol.c": "a72e9b38dd5ed0c49fae22aa2f01883aea76075ad33f6399d9b2033df4330675"
|
||||
},
|
||||
"native_nested_sha256": "2e40c98af72a0df8b316810589a0be8f4425a763db1de448b49a7813956b6f7d"
|
||||
}
|
||||
113
tooling/probes/step20c-static-e2-completion/derivation.json
Normal file
113
tooling/probes/step20c-static-e2-completion/derivation.json
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
{
|
||||
"status": "EXPERIMENTAL; historical acceptance does not apply",
|
||||
"base_commit": "c5f96a34e3924275179365938d02f3a318c0c4bf",
|
||||
"copies": [
|
||||
{
|
||||
"file": "model.py",
|
||||
"source": "tooling/probes/step20c-static-nested-geometry-replay/model.py",
|
||||
"sha256": "a578fc589fb092dae3ccdcf8c4eec12a97e515f721decc4c968078dedcb2367a",
|
||||
"kind": "identifiable initial copy; see final diff"
|
||||
},
|
||||
{
|
||||
"file": "observations.py",
|
||||
"source": "tooling/probes/step20c-static-nested-geometry-replay/observations.py",
|
||||
"sha256": "3a9193bb4221c2da00c3231d60bf90b51403833d06e0178b87f86dd44c9251db",
|
||||
"kind": "identifiable initial copy; see final diff"
|
||||
},
|
||||
{
|
||||
"file": "ipc_predicates.py",
|
||||
"source": "tooling/probes/step20c-static-nested-geometry-replay/ipc_predicates.py",
|
||||
"sha256": "4e0615d155183d841543131bf540056a2c2d49eb23f64997a6867d796633548c",
|
||||
"kind": "identifiable initial copy; see final diff"
|
||||
},
|
||||
{
|
||||
"file": "pixels.py",
|
||||
"source": "tooling/probes/step20c-static-nested-geometry-replay/pixels.py",
|
||||
"sha256": "d315ddad978238ffbf1c8e4d712d4608ba23564703332d814f627f57f7b6e7f5",
|
||||
"kind": "identifiable initial copy; see final diff"
|
||||
},
|
||||
{
|
||||
"file": "predicates.py",
|
||||
"source": "tooling/probes/step20c-static-nested-geometry-replay/predicates.py",
|
||||
"sha256": "1435832e61d9694fc80d429281d6b6a18b11f56a97d7ea4fee8d117e6953ba72",
|
||||
"kind": "identifiable initial copy; see final diff"
|
||||
},
|
||||
{
|
||||
"file": "support.py",
|
||||
"source": "tooling/probes/step20c-static-nested-geometry-replay/support.py",
|
||||
"sha256": "84687f5421ad8ef5d2e6bf1eb334fa5377ed9c59741a2cd593c043cf63c5fd76",
|
||||
"kind": "identifiable initial copy; see final diff"
|
||||
},
|
||||
{
|
||||
"file": "ledger.py",
|
||||
"source": "tooling/probes/step20c-static-nested-geometry-replay/ledger.py",
|
||||
"sha256": "3492ed714780d94fd72f9bbc0a7dc27586c62ee4955a2570ad15ec3809c5e12d",
|
||||
"kind": "identifiable initial copy; see final diff"
|
||||
},
|
||||
{
|
||||
"file": "run.py",
|
||||
"source": "tooling/probes/step20c-static-nested-geometry-replay/run.py",
|
||||
"sha256": "4382f2c0ffc8a41a6a65aecfca72dff762ded528f667288d179acbb354a9fcc9",
|
||||
"kind": "identifiable initial copy; see final diff"
|
||||
},
|
||||
{
|
||||
"file": "placement.patch",
|
||||
"source": "tooling/probes/step20c-static-nested-geometry-replay/placement.patch",
|
||||
"sha256": "e0e87fb0cb3d6e7dafb1cd59ccb50f323f2934e65247526199da1695487d3e4a",
|
||||
"kind": "identifiable initial copy; see final diff"
|
||||
},
|
||||
{
|
||||
"file": "client-contract.json",
|
||||
"source": "tooling/probes/step20c-static-nested-geometry-replay/client-contract.json",
|
||||
"sha256": "f330328c9f7965aed06cf73693054631847eceef39de18f2978b27120a645613",
|
||||
"kind": "identifiable initial copy; see final diff"
|
||||
}
|
||||
],
|
||||
"exact_reusable_test_methods": {
|
||||
"setUp": "a14a5a3abba8b66055da064bdfc63ae75653c3ca35aed826f981a801b85f72e8",
|
||||
"tearDown": "f311acf2dab71c6461a030efaec2062641116bb51e32fc49f8ee0b4643d21557",
|
||||
"reject": "32eb998c52f371a172b64e21ef149c0e9df16efec884244d4e7ce80961f249ce",
|
||||
"test_positive_geometry": "de5050007c1ae5833c57cfd8fa292f4b9e7b7e91ac63547ba6789c0c70bd7a69",
|
||||
"test_negative_native_height": "cb1b1da00973603eebc33464cfcb31dc0385263af45b54c876952d8e6f990625",
|
||||
"test_negative_scene_window_y": "124a051ce6e7e9d44efb10536c99fcedc9122255fe74f63fcda56483135866dd",
|
||||
"test_negative_titlebar": "59d2e6723e6aae2500f6ac290eeea0bdfca78da8ec9b23b66aa0678d44f90217",
|
||||
"test_negative_output_origin": "88d8c9c9a497f35f4db87085e15a32ea7c8c91e6ea5106b3a1f20058c518d84b",
|
||||
"test_negative_png_space": "52bfb9216cab05c0b75db3aba426610986ec0618d4146b9049023c8c303c01fa",
|
||||
"test_negative_png_bounds": "7096414bdf77bcf91a341cc2ed4884356090aa29f5ba5bffb6c9e1d6705bb981",
|
||||
"test_negative_assignment": "bdf5b8441835124a327b8108cd5d6c25a5d91de5106672d02d17ec33ca9605aa",
|
||||
"test_negative_workspace": "28a098ea5b234dbba4ca93dff5599e94bbff355893cca4d1074f87d71ff6b102",
|
||||
"test_negative_owner": "a06eff220c9af175bb7278bca28c8cd874305f846a673402f157304dc05b4719",
|
||||
"test_negative_old_camera": "2bdf861835107873f2a6ed9d9417da1213139c29ad502c09a6964ed6efe558db",
|
||||
"test_negative_local_global_clip": "187bab95f02f45a6de3b9dd0415c950690e3e947c04319a068a39997b7387ffa",
|
||||
"test_negative_buffer_identity": "81699b15d784ce23f1bbc4565a1b669afb49d98580ebb722949e0dc7c74b955c",
|
||||
"test_negative_pending": "602a0ec9d828adf7351b8012d958b84be5395eb754c01b9513753f28aa39f8f7",
|
||||
"test_negative_scale": "8833c6bc39c9b460ad1e9cd00fb0da1a02f2edbba1631081f2df98cedd1c107c",
|
||||
"test_negative_transform": "392ef3eb03a7938b038e1b2bf5db405950a7a0f4c6829e63d80dddc34ed111fc",
|
||||
"test_positive_popup": "c622548815bf95fac5b28f56d490e0f9c68955ef86c1009f3d3dc9940a2b92c3",
|
||||
"test_negative_parent": "62a71f2976d9e32acf870c03dd031fad8a3427366304662d78be19ee686f22f9",
|
||||
"test_negative_ack": "502d89e4e302adb14d3a52bfbdf6eca59cf46f75ef7825ad73bde90dc2d0b23a",
|
||||
"test_negative_map": "e73c12f816d8832d00d8498bcbb777f8d8a910a1fad1b9c72750180516ff9bb7",
|
||||
"test_negative_constraints": "d723171a9897f3dc316b4e4691a1ac99a3b3e47fac2d7f518f3b0f640e018830",
|
||||
"test_negative_pointer": "c8b574c028e4cac0ec7e4148c5a8fc52c97138e5bcce3d18be1b4bae486ceada",
|
||||
"test_negative_occlusion": "7bcbcf61ebb34e2196f296c4ca0000aaaf8070a7bdb68afb43473d4e0e19a956",
|
||||
"test_negative_standard_stale_origin": "0dc996c2c9684e446383637aa0b4c6eec4a5ee2c98deaf672a4b0df19a326e2c",
|
||||
"test_positive_standard_fresh": "2803ac768aad04f0af424876c04147e852b9351255ca4b92c40945475ac17d39",
|
||||
"test_negative_E2_identity": "2f9ce9a4afdd72e7e7c4cba0b7cdb9ecb64c98ef84aae863e7db8632806e9a0a",
|
||||
"test_negative_E2_serials": "c9950830e1c4244aec26a4fc3768c9db4e0d0b17fc24c3317c7f28e23f24ac65",
|
||||
"test_positive_E2": "e20673fbed4498af2b0698d32fb51b57e56137de37bddba70af5e5d6ddc3f655",
|
||||
"test_positive_terminal": "b37653a245fab18cc21af8a32c76827ba3ec8bda1e503822d6100274b52d9916",
|
||||
"test_negative_terminal_incomplete": "68f75b8803fea6d62cb28baef6de8a5a80ec9dd0d91386f0c2bb93b957c335e5",
|
||||
"test_negative_terminal_unhandled": "f7348d3486097e8ef162b7fe70931af3a668724de97de59fb1c106f0ec449547",
|
||||
"test_negative_terminal_compositor": "1d58c53f4dd048068e747d77174ccb27cc12791bf05f29909f1f939923cb5be0",
|
||||
"test_negative_terminal_protocol": "aa0fcdbe99b83282f6c7571e9d29b15be8140fc887bc35873905dd93cb9db878",
|
||||
"test_negative_terminal_rescue": "2de8c7b755bc94167a8d4de40d721030cc41a6aa75fb46f2d761cd747b79f4af",
|
||||
"test_negative_dismissal_hit": "d5ca0ee4a496a30072ea873683731b1e9a2ff6c3ed7a16259d43cd244ac0d942",
|
||||
"test_negative_dismissal_incomplete": "559d5ac3b5d46a93b7cbf9c2f4d9f42d801d8da0a87e12102aaf4173da49980d",
|
||||
"test_positive_dismissal": "8bc890b903eef26d0cab7a2c3c3248eed33f5ff58b64a5bd25bc79abd33a4bd1",
|
||||
"test_positive_button": "7a320d9fab94dfc9ab483fcb1ba5f584bf9e39d2ec99bb21e03f5e247e8fa6b5",
|
||||
"test_negative_button": "9511648c2dce6549036e1fa87f5d9b14cd920af0d3b7cf316bbcdbaa5e703726",
|
||||
"test_negative_root_ambiguous": "6aaf85c8ab2f4e40ecc99db1e5601372313d9fbbc64d27676881337211f2d7f5",
|
||||
"test_positive_root": "af8106e6e5264e21c9257f1af7fd591c2e5f2849033f9317ac6e6e35c4619312"
|
||||
},
|
||||
"test_source": "tooling/probes/step20c-static-nested-geometry-replay/tests_offline.py"
|
||||
}
|
||||
74
tooling/probes/step20c-static-e2-completion/gate.py
Normal file
74
tooling/probes/step20c-static-e2-completion/gate.py
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate a prospective gate only from actual nonempty executed receipts.
|
||||
|
||||
WHY: empty PASS and historical acceptance must not grant new readiness.
|
||||
CONTRACT: final source hashes + complete tests + actual raw replay + AST audit.
|
||||
INVARIANT: this gate is READY_FOR_REVIEW; independent acceptance is separate.
|
||||
"""
|
||||
import json
|
||||
from pathlib import Path
|
||||
from model import require
|
||||
from support import sha
|
||||
HERE=Path(__file__).resolve().parent
|
||||
|
||||
|
||||
def normalized(value):return json.loads(json.dumps(value))
|
||||
|
||||
|
||||
def sources():return {p.name:sha(p) for p in HERE.iterdir() if p.is_file()}
|
||||
|
||||
|
||||
def validate_test_receipts(receipt):
|
||||
from tests_offline import expected_tests
|
||||
expected=expected_tests()
|
||||
rows=receipt.get('tests')
|
||||
require(receipt.get('status')=='PASS' and isinstance(rows,list) and len(rows)==len(expected)
|
||||
and receipt.get('executed')==len(expected) and receipt.get('failures')==receipt.get('errors')==receipt.get('skips')==0,
|
||||
'complete-executed-test-receipts')
|
||||
require({r.get('id') for r in rows}==expected and all(r.get('status')=='PASS' for r in rows),'exact-test-inventory')
|
||||
require(receipt.get('sources')=={p.name:sha(p) for p in HERE.glob('*.py')},'tested-final-source-bytes')
|
||||
negative=['retained_clip','no_standard_epoch','wrong_layout','wrong_owner','wrong_root','wrong_output',
|
||||
'reversed_times','standard_before_application','clip_reactivation','missing_root_traces','multiple_roots',
|
||||
'wrong_capture_epoch','different_workspace','popup_disappears_early','ipc_failure','no_root_clip_removal','pending_presented_applied']
|
||||
indexed={r['id']:r for r in rows}
|
||||
for name in negative:
|
||||
row=indexed['tests_offline.Transition.test_'+name]
|
||||
require(bool(row.get('rejected_contracts') or row.get('rejected_exceptions')),'executed-negative-rejection:'+name)
|
||||
return receipt
|
||||
|
||||
|
||||
def validate_gate(path):
|
||||
from replay import replay
|
||||
from audit import audit
|
||||
path=Path(path);gate=json.loads(path.read_text())
|
||||
require(gate.get('status')=='READY_FOR_REVIEW' and gate.get('marker')=='E2_COMPLETION_OFFLINE=READY_FOR_REVIEW'
|
||||
and gate.get('source_sha256')==sources() and gate.get('blockers')==[], 'prospective-gate-final-source')
|
||||
receipts={}
|
||||
for name in ('test-receipts.json','replay.json','source-audit.json'):
|
||||
p=path.parent/name
|
||||
require(sha(p)==gate['receipts_sha256'][name],'actual-file-receipt:'+name)
|
||||
receipts[name]=json.loads(p.read_text())
|
||||
validate_test_receipts(receipts['test-receipts.json'])
|
||||
require(receipts['replay.json']==normalized(replay()),'real-raw-replay-receipt')
|
||||
require(receipts['source-audit.json']==normalized(audit()),'exhaustive-current-source-audit')
|
||||
require(gate.get('test_count')==len(receipts['test-receipts.json']['tests']),'gate-test-count')
|
||||
return gate
|
||||
|
||||
|
||||
def write(directory):
|
||||
from audit import audit
|
||||
from replay import replay
|
||||
directory=Path(directory)
|
||||
tests=json.loads((directory/'test-receipts.json').read_text());validate_test_receipts(tests)
|
||||
require(json.loads((directory/'source-audit.json').read_text())==normalized(audit()),'audit-current')
|
||||
require(json.loads((directory/'replay.json').read_text())==normalized(replay()),'real-raw-replay-before-writing')
|
||||
result=dict(status='READY_FOR_REVIEW',marker='E2_COMPLETION_OFFLINE=READY_FOR_REVIEW',source_sha256=sources(),
|
||||
receipts_sha256={n:sha(directory/n) for n in ('test-receipts.json','replay.json','source-audit.json')},
|
||||
test_count=len(tests['tests']),blockers=[],runtime='NOT RUN',independent_review='PENDING')
|
||||
path=directory/'offline-gate.json'
|
||||
with path.open('x') as out:json.dump(result,out,indent=2);out.write('\n')
|
||||
validate_gate(path);print(result['marker'])
|
||||
|
||||
if __name__=='__main__':
|
||||
import argparse
|
||||
p=argparse.ArgumentParser();p.add_argument('directory',type=Path);a=p.parse_args();write(a.directory)
|
||||
194
tooling/probes/step20c-static-e2-completion/handoff.py
Normal file
194
tooling/probes/step20c-static-e2-completion/handoff.py
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Prospective E2 handoff, derived from static-nested-runtime/handoff.py.
|
||||
|
||||
WHY: historical ACCEPTED cannot sign changed runner bytes.
|
||||
CONTRACT: genuine independent review binds final sources, receipts and owner.
|
||||
INVARIANT: preflight reads only; launch consumption is exclusive and durable.
|
||||
"""
|
||||
import ast
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import secrets
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from support import sha,verify,environment
|
||||
from model import require
|
||||
from ledger import LIMITS,valid
|
||||
from transition import source_bindings,SOURCE_FILES
|
||||
|
||||
HERE=Path(__file__).resolve().parent
|
||||
SOURCE_ROOT=Path('/home/fy59/Documents/Development/swayfx-mosaic/tooling/probes/step20c-static-e2-completion')
|
||||
TICKET='SWAYFX_MOSAIC_STEP20C_E2_COMPLETION_AND_PRIVATE_INTEGRATION_BATCH_V1'
|
||||
REGISTRY=Path('/home/fy59/swayfx-mosaic-e2-completion-v1.owner')
|
||||
REGISTRATION=REGISTRY.with_suffix('.registration.json')
|
||||
SPENT=REGISTRY.with_suffix('.launch-consumed.json')
|
||||
OLD_LAB=Path('/home/fy59/swayfx-mosaic-static-nested-runtime-pvn0osxm')
|
||||
BINARY=Path('/home/fy59/swayfx-mosaic-static-runtime-hm5kbdkt/stage-static/usr/bin/swayfx-mosaic')
|
||||
BINARY_SHA='f82557c1a9978a5e9665b9b4291b74880253725359e3df1c5b8d554921f7603b'
|
||||
BUILD_ID='942db64269640778ee433e5970ba1100bc60f94c'
|
||||
WLROOTS=Path('/usr/lib/libwlroots-0.20.so')
|
||||
WLROOTS_SHA='7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837'
|
||||
MARKER='E2_COMPLETION_OFFLINE=READY_FOR_REVIEW'
|
||||
|
||||
|
||||
def read(path):return json.loads(Path(path).read_text())
|
||||
|
||||
|
||||
def exclusive(path,data):
|
||||
path=Path(path)
|
||||
fd=os.open(path,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600)
|
||||
with os.fdopen(fd,'w') as out:
|
||||
out.write(data if isinstance(data,str) else json.dumps(data,indent=2)+'\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 regular(path,mode=None):
|
||||
p=Path(path);s=p.lstat()
|
||||
require(stat.S_ISREG(s.st_mode) and s.st_uid==os.getuid(),'owned-regular-file:'+str(p))
|
||||
if mode is not None:require(stat.S_IMODE(s.st_mode)==mode,'file-mode:'+str(p))
|
||||
|
||||
|
||||
def copy_exact(source,dest):
|
||||
regular(source)
|
||||
with Path(source).open('rb') as src,Path(dest).open('xb') as out:
|
||||
shutil.copyfileobj(src,out);out.flush();os.fsync(out.fileno())
|
||||
Path(dest).chmod(stat.S_IMODE(Path(source).stat().st_mode))
|
||||
require(sha(source)==sha(dest),'copy-exact')
|
||||
return dict(source=str(source),destination=str(dest),sha256=sha(source))
|
||||
|
||||
|
||||
def source_map():return {p.name:sha(p) for p in SOURCE_ROOT.iterdir() if p.is_file()}
|
||||
|
||||
|
||||
def immutable_paths(lab):
|
||||
paths=[p for root in ('fixture','client-source','offline') for p in (lab/root).iterdir() if p.is_file()]
|
||||
paths += [lab/n for n in ('swayfx-mosaic','nested-client','pointer-daemon','static-reference','campaign-owner')]
|
||||
paths += [lab/'results/binary-identity.json',*[lab/'raw'/(name+'.json') for name in ('ELF','symbols','relocations')],BINARY,WLROOTS.resolve(),REGISTRY]
|
||||
paths += [Path(p) for p in SOURCE_FILES.values()]
|
||||
paths += [p for p in SOURCE_ROOT.iterdir() if p.is_file()]
|
||||
require(len(paths)==len(set(paths)),'unique-manifest-paths')
|
||||
return sorted(paths)
|
||||
|
||||
|
||||
def prepare(offline):
|
||||
from gate import validate_gate
|
||||
offline=Path(offline).resolve();gate=validate_gate(offline/'offline-gate.json')
|
||||
require(not any(p.exists() for p in (REGISTRY,REGISTRATION,SPENT)),'ticket-already-registered')
|
||||
require(sha(BINARY)==BINARY_SHA and sha(WLROOTS)==WLROOTS_SHA,'binary-wlroots-identities')
|
||||
lab=Path(tempfile.mkdtemp(prefix='swayfx-mosaic-e2-completion-',dir='/home/fy59'));lab.chmod(0o700)
|
||||
for n in ('results','raw','fixture','client-source','offline'):(lab/n).mkdir()
|
||||
token=secrets.token_hex(32)
|
||||
exclusive(REGISTRY,str(lab)+'\n');exclusive(lab/'campaign-owner',str(REGISTRY)+'\n')
|
||||
transfers=[]
|
||||
for p in sorted(HERE.iterdir()):
|
||||
if p.is_file():transfers.append(copy_exact(p,lab/'fixture'/p.name))
|
||||
for name in ('offline-gate.json','test-receipts.json','replay.json','source-audit.json'):
|
||||
transfers.append(copy_exact(offline/name,lab/'offline'/name))
|
||||
for n in ('swayfx-mosaic','nested-client','pointer-daemon','static-reference'):
|
||||
transfers.append(copy_exact(BINARY if n=='swayfx-mosaic' else OLD_LAB/n,lab/n));regular(lab/n,0o755)
|
||||
# Actual prepared static_cross sources and generated protocols, not rebuilt.
|
||||
for p in sorted((OLD_LAB/'client-build').iterdir()):
|
||||
if p.suffix in ('.c','.h'):transfers.append(copy_exact(p,lab/'client-source'/p.name))
|
||||
contract=read(HERE/'client-contract.json')
|
||||
require(sha(lab/'client-source/nested-client.c')==contract['adapted_nested_sha256'],'actual-static-cross-source')
|
||||
for name,digest in contract['protocols'].items():require(sha(lab/'client-source'/name)==digest,'actual-protocol-source')
|
||||
identity={}
|
||||
for label,argv in [('ELF',['readelf','-hW','-nW','-dW',str(lab/'swayfx-mosaic')]),
|
||||
('symbols',['nm',str(lab/'swayfx-mosaic')]),('relocations',['ldd','-r',str(lab/'swayfx-mosaic')])]:
|
||||
r=subprocess.run(argv,env=environment(),capture_output=True,text=True,timeout=30)
|
||||
exclusive(lab/'raw'/(label+'.json'),dict(argv=argv,exit=r.returncode,stdout=r.stdout,stderr=r.stderr))
|
||||
require(r.returncode==0,'identity-command');identity[label]=r.stdout
|
||||
require(BUILD_ID in identity['ELF'] and 'ELF64' in identity['ELF'] and
|
||||
all(s not in identity['ELF'] for s in ('libscenefx','(RPATH)','(RUNPATH)')),'ELF-static-contract')
|
||||
require(all(' T '+n in identity['symbols'] for n in ('wlr_scene_tree_set_clip','wlr_scene_tree_get_clip')),'clip-symbols')
|
||||
require(str(WLROOTS.resolve()) in identity['relocations'] and
|
||||
all(s not in identity['relocations'] for s in ('libscenefx','undefined symbol','not found')),'resolved-libraries')
|
||||
exclusive(lab/'results/binary-identity.json',dict(status='PASS',build_id=BUILD_ID,binary_sha256=BINARY_SHA,
|
||||
wlroots_sha256=WLROOTS_SHA,raw={n:sha(lab/'raw'/(n+'.json')) for n in identity}))
|
||||
exclusive(lab/'results/fixture-freeze-sha256.txt',''.join(sha(p)+' '+str(p)+'\n' for p in immutable_paths(lab)))
|
||||
frozen=verify(lab/'results/fixture-freeze-sha256.txt')
|
||||
registration=dict(ticket=TICKET,lab=str(lab),owner_registry=str(REGISTRY),uid=os.getuid(),token=token,
|
||||
fixture_sha256=frozen['sha256'],binary=str(BINARY),binary_sha256=BINARY_SHA,wlroots_sha256=WLROOTS_SHA)
|
||||
exclusive(REGISTRATION,registration);exclusive(lab/'results/registration.json',registration)
|
||||
auth={**registration,'registered_lab':str(lab),'scope':'one-new-static-e2-completion-headless-run','explicit_user_authorization':True}
|
||||
exclusive(lab/'results/authorization.json',auth)
|
||||
exclusive(lab/'results/budget.json',dict(limits=LIMITS,used=dict.fromkeys(LIMITS,0),events=[]))
|
||||
exclusive(lab/'results/offline-gate.json',dict(marker=MARKER,status='READY_FOR_REVIEW',fixture_sha256=frozen['sha256'],
|
||||
offline_gate_sha256=sha(lab/'offline/offline-gate.json')))
|
||||
exclusive(lab/'results/runtime-handoff.json',dict(status='PREPARED_FOR_INDEPENDENT_REVIEW',ticket=TICKET,
|
||||
lab=str(lab),fixture=frozen,transfers=transfers,source_sha256=source_map(),source_contract=source_bindings(),
|
||||
gate_sha256=sha(lab/'offline/offline-gate.json'),authorization_sha256=sha(lab/'results/authorization.json'),
|
||||
registration_sha256=sha(REGISTRATION),historical_review='does not accept changed runner',run_reserved=False))
|
||||
print('LAB='+str(lab));return lab
|
||||
|
||||
|
||||
def preflight(lab,authorization,review=True,consumed=False):
|
||||
"""Read-only. review=False is prospective preparation, never dispatch."""
|
||||
from gate import validate_gate
|
||||
lab=Path(lab).resolve();authorization=Path(authorization)
|
||||
require(not sys.flags.optimize,'assertions-required')
|
||||
require(lab!=OLD_LAB and lab.name.startswith('swayfx-mosaic-e2-completion-'),'fresh-ticket-lab')
|
||||
require(lab.stat().st_uid==os.getuid() and stat.S_IMODE(lab.stat().st_mode)==0o700,'lab-owner-mode')
|
||||
for p in (REGISTRY,REGISTRATION,lab/'campaign-owner',authorization):regular(p,0o600)
|
||||
require(REGISTRY.read_text().strip()==str(lab) and (lab/'campaign-owner').read_text().strip()==str(REGISTRY),'exclusive-owner')
|
||||
auth=read(authorization);registration=read(REGISTRATION)
|
||||
require(registration==read(lab/'results/registration.json'),'durable-registration-copy')
|
||||
require(auth=={**registration,'registered_lab':str(lab),'scope':'one-new-static-e2-completion-headless-run',
|
||||
'explicit_user_authorization':True},'exact-authorization')
|
||||
require(auth['ticket']==TICKET and auth['lab']==str(lab) and auth['owner_registry']==str(REGISTRY)
|
||||
and auth['uid']==os.getuid() and len(auth['token'])==64,'ticket-lab-token')
|
||||
require(auth['binary']==str(BINARY) and auth['binary_sha256']==BINARY_SHA and auth['wlroots_sha256']==WLROOTS_SHA,
|
||||
'authorized-identities')
|
||||
require(sha(BINARY)==sha(lab/'swayfx-mosaic')==BINARY_SHA and sha(WLROOTS)==WLROOTS_SHA,'actual-identities')
|
||||
require(not (lab/'run').exists() and not (lab/'run.lock').exists(),'unconsumed-runtime')
|
||||
require(not any(stat.S_ISSOCK(p.lstat().st_mode) for p in lab.rglob('*')),'no-existing-sockets')
|
||||
manifest=lab/'results/fixture-freeze-sha256.txt'
|
||||
frozen=verify(manifest)
|
||||
require(manifest.read_text()==''.join(sha(p)+' '+str(p)+'\n' for p in immutable_paths(lab)),'complete-exact-manifest')
|
||||
require({p.name:sha(p) for p in (lab/'fixture').iterdir() if p.is_file()}==source_map(),'installed-final-fixture')
|
||||
require(auth['fixture_sha256']==frozen['sha256'],'authorization-manifest')
|
||||
handoff=read(lab/'results/runtime-handoff.json')
|
||||
require(handoff['ticket']==TICKET and handoff['lab']==str(lab) and handoff['fixture']==frozen and
|
||||
handoff['source_sha256']==source_map() and handoff['source_contract']==source_bindings() and
|
||||
handoff['authorization_sha256']==sha(authorization) and handoff['registration_sha256']==sha(REGISTRATION),
|
||||
'handoff-final-bindings')
|
||||
require(bool(handoff['transfers']) and len({row['destination'] for row in handoff['transfers']})==len(handoff['transfers']),'nonempty-unique-transfers')
|
||||
for row in handoff['transfers']:require(sha(row['source'])==sha(row['destination'])==row['sha256'],'transfer-changed')
|
||||
gate=validate_gate(lab/'offline/offline-gate.json')
|
||||
require(handoff['gate_sha256']==sha(lab/'offline/offline-gate.json'),'handoff-gate-binding')
|
||||
require(read(lab/'results/offline-gate.json')==dict(marker=MARKER,status='READY_FOR_REVIEW',fixture_sha256=frozen['sha256'],
|
||||
offline_gate_sha256=sha(lab/'offline/offline-gate.json')),'derived-gate')
|
||||
budget=valid(read(lab/'results/budget.json'));require(not any(budget['used'].values()) and not budget['events'],'ledger-unreserved')
|
||||
identity=read(lab/'results/binary-identity.json')
|
||||
require(identity['status']=='PASS' and identity['build_id']==BUILD_ID and identity['binary_sha256']==BINARY_SHA
|
||||
and identity['wlroots_sha256']==WLROOTS_SHA,'identity-receipt')
|
||||
for n,digest in identity['raw'].items():require(sha(lab/'raw'/(n+'.json'))==digest,'raw-identity-binding')
|
||||
expected_spent=dict(ticket=TICKET,lab=str(lab),token=auth['token'],fixture_sha256=frozen['sha256'],authorization_sha256=sha(authorization))
|
||||
if consumed:require(SPENT.exists() and read(SPENT)==expected_spent,'runner-exact-consumption')
|
||||
else:require(not SPENT.exists(),'ticket-launch-already-consumed')
|
||||
if review:
|
||||
reviewed=read(lab/'results/handoff-review.json')
|
||||
require(reviewed.get('status')=='ACCEPTED' and reviewed.get('independent') is True and reviewed.get('read_only') is True
|
||||
and reviewed.get('scope')=='STATIC_E2_COMPLETION_FINAL_BYTES' and reviewed.get('blockers')==[],
|
||||
'genuine-independent-review-required')
|
||||
require(reviewed['fixture_sha256']==frozen['sha256'] and reviewed['source_sha256']==source_map()
|
||||
and reviewed['runtime_handoff_sha256']==sha(lab/'results/runtime-handoff.json')
|
||||
and reviewed['authorization_sha256']==sha(authorization) and reviewed['registration_sha256']==sha(REGISTRATION)
|
||||
and reviewed['gate_sha256']==sha(lab/'offline/offline-gate.json'),'review-final-bindings')
|
||||
regular(reviewed['report_file']);require(sha(reviewed['report_file'])==reviewed['report_sha256'],'review-report-bytes')
|
||||
return dict(status='PASS',ticket=TICKET,lab=str(lab),fixture=frozen,token=auth['token'],
|
||||
review='ACCEPTED' if review else 'PENDING_INDEPENDENT_REVIEW',run_reserved=False)
|
||||
|
||||
if __name__=='__main__':
|
||||
import argparse
|
||||
p=argparse.ArgumentParser();p.add_argument('action',choices=['prepare','preflight']);p.add_argument('path',type=Path);a=p.parse_args()
|
||||
if a.action=='prepare':prepare(a.path)
|
||||
else:
|
||||
result=preflight(a.path,a.path/'results/authorization.json');exclusive(a.path/'results/preflight.json',result)
|
||||
print('E2_COMPLETION_PREFLIGHT=PASS')
|
||||
108
tooling/probes/step20c-static-e2-completion/handoff_tests.py
Normal file
108
tooling/probes/step20c-static-e2-completion/handoff_tests.py
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
"""Isolated preflight mutations. No real owner/review/token is created.
|
||||
|
||||
All simulated accepted records are confined to TemporaryDirectory; mocked gate
|
||||
validation avoids claiming historical or prospective runtime acceptance.
|
||||
"""
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
from contextlib import ExitStack
|
||||
import handoff as h
|
||||
from model import Invalid
|
||||
from ledger import LIMITS
|
||||
from support import sha
|
||||
|
||||
class Handoff(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp=tempfile.TemporaryDirectory(prefix='e2-handoff-tests-');self.root=Path(self.tmp.name)
|
||||
self.lab=self.root/'swayfx-mosaic-e2-completion-synthetic';self.lab.mkdir(mode=0o700)
|
||||
for n in ('results','raw','fixture','client-source','offline'):(self.lab/n).mkdir()
|
||||
self.stack=ExitStack()
|
||||
for name,value in [('REGISTRY',self.root/'owner'),('REGISTRATION',self.root/'registration.json'),('SPENT',self.root/'spent.json'),
|
||||
('BINARY',self.root/'binary'),('WLROOTS',self.root/'wlroots'),('SOURCE_FILES',{})]:
|
||||
self.stack.enter_context(patch.object(h,name,value))
|
||||
h.BINARY.write_bytes(b'synthetic binary');h.WLROOTS.write_bytes(b'synthetic wlroots')
|
||||
self.stack.enter_context(patch.object(h,'BINARY_SHA',sha(h.BINARY)));self.stack.enter_context(patch.object(h,'WLROOTS_SHA',sha(h.WLROOTS)))
|
||||
self.stack.enter_context(patch('gate.validate_gate',return_value={'status':'SYNTHETIC_TEST_ONLY'}))
|
||||
for p in h.HERE.iterdir():
|
||||
if p.is_file():(self.lab/'fixture'/p.name).write_bytes(p.read_bytes())
|
||||
for n in ('nested-client','pointer-daemon','static-reference'):(self.lab/n).write_bytes(b'synthetic client')
|
||||
(self.lab/'swayfx-mosaic').write_bytes(h.BINARY.read_bytes())
|
||||
for n in ('offline-gate.json','test-receipts.json','replay.json','source-audit.json'):(self.lab/'offline'/n).write_text('{}')
|
||||
h.exclusive(h.REGISTRY,str(self.lab)+'\n');h.exclusive(self.lab/'campaign-owner',str(h.REGISTRY)+'\n')
|
||||
for n in ('ELF','symbols','relocations'):(self.lab/'raw'/(n+'.json')).write_text('{}')
|
||||
self.write('binary-identity',dict(status='PASS',build_id=h.BUILD_ID,binary_sha256=h.BINARY_SHA,wlroots_sha256=h.WLROOTS_SHA,raw={}))
|
||||
manifest=self.lab/'results/fixture-freeze-sha256.txt'
|
||||
manifest.write_text(''.join(sha(p)+' '+str(p)+'\n' for p in h.immutable_paths(self.lab)))
|
||||
frozen=h.verify(manifest)
|
||||
reg=dict(ticket=h.TICKET,lab=str(self.lab),owner_registry=str(h.REGISTRY),uid=os.getuid(),token='a'*64,
|
||||
fixture_sha256=frozen['sha256'],binary=str(h.BINARY),binary_sha256=h.BINARY_SHA,wlroots_sha256=h.WLROOTS_SHA)
|
||||
h.exclusive(h.REGISTRATION,reg);self.write('registration',reg)
|
||||
self.auth={**reg,'registered_lab':str(self.lab),'scope':'one-new-static-e2-completion-headless-run','explicit_user_authorization':True}
|
||||
h.exclusive(self.lab/'results/authorization.json',self.auth)
|
||||
self.write('budget',dict(limits=LIMITS,used=dict.fromkeys(LIMITS,0),events=[]))
|
||||
self.write('offline-gate',dict(marker=h.MARKER,status='READY_FOR_REVIEW',fixture_sha256=frozen['sha256'],offline_gate_sha256=sha(self.lab/'offline/offline-gate.json')))
|
||||
self.write('runtime-handoff',dict(status='PREPARED_FOR_INDEPENDENT_REVIEW',ticket=h.TICKET,lab=str(self.lab),fixture=frozen,
|
||||
source_sha256=h.source_map(),source_contract=h.source_bindings(),authorization_sha256=sha(self.lab/'results/authorization.json'),
|
||||
registration_sha256=sha(h.REGISTRATION),gate_sha256=sha(self.lab/'offline/offline-gate.json'),
|
||||
transfers=[dict(source=str(h.BINARY),destination=str(self.lab/'swayfx-mosaic'),sha256=h.BINARY_SHA)]))
|
||||
self.report=self.root/'synthetic-review.md';self.report.write_text('SYNTHETIC TEST ONLY, not a genuine independent review')
|
||||
self.review=dict(status='ACCEPTED',independent=True,read_only=True,scope='STATIC_E2_COMPLETION_FINAL_BYTES',blockers=[],
|
||||
fixture_sha256=frozen['sha256'],source_sha256=h.source_map(),runtime_handoff_sha256=sha(self.lab/'results/runtime-handoff.json'),
|
||||
authorization_sha256=sha(self.lab/'results/authorization.json'),registration_sha256=sha(h.REGISTRATION),
|
||||
gate_sha256=sha(self.lab/'offline/offline-gate.json'),report_file=str(self.report),report_sha256=sha(self.report))
|
||||
self.write('handoff-review',self.review)
|
||||
def tearDown(self):self.stack.close();self.tmp.cleanup()
|
||||
def write(self,name,data):
|
||||
p=self.lab/'results'/(name+'.json');p.write_text(json.dumps(data));p.chmod(0o600)
|
||||
def mutate_auth(self,**updates):self.write('authorization',{**self.auth,**updates})
|
||||
def call(self,review=True):return h.preflight(self.lab,self.lab/'results/authorization.json',review=review)
|
||||
def test_prospective_no_review_pure(self):
|
||||
before={str(p):p.read_bytes() for p in self.root.rglob('*') if p.is_file()}
|
||||
self.assertEqual(self.call(review=False)['review'],'PENDING_INDEPENDENT_REVIEW')
|
||||
self.assertEqual(before,{str(p):p.read_bytes() for p in self.root.rglob('*') if p.is_file()})
|
||||
def test_wrong_lab(self):
|
||||
with self.assertRaises(Invalid):h.preflight(h.OLD_LAB,self.lab/'results/authorization.json')
|
||||
def test_wrong_token(self):
|
||||
self.mutate_auth(token='b'*64)
|
||||
with self.assertRaisesRegex(Invalid,'exact-authorization'):self.call()
|
||||
def test_wrong_ticket(self):
|
||||
self.mutate_auth(ticket='old-ticket')
|
||||
with self.assertRaises(Invalid):self.call()
|
||||
def test_wrong_binary(self):
|
||||
(self.lab/'swayfx-mosaic').write_bytes(b'changed')
|
||||
with self.assertRaisesRegex(Invalid,'actual-identities'):self.call()
|
||||
def test_wrong_wlroots(self):
|
||||
h.WLROOTS.write_bytes(b'changed')
|
||||
with self.assertRaisesRegex(Invalid,'actual-identities'):self.call()
|
||||
def test_missing_review(self):
|
||||
(self.lab/'results/handoff-review.json').unlink()
|
||||
with self.assertRaises(FileNotFoundError):self.call()
|
||||
def test_historical_review_rejected(self):
|
||||
self.write('handoff-review',{**self.review,'scope':'STATIC_NESTED_RUNTIME_HANDOFF'})
|
||||
with self.assertRaisesRegex(Invalid,'genuine-independent-review-required'):self.call()
|
||||
def test_stale_review(self):
|
||||
self.write('handoff-review',{**self.review,'fixture_sha256':'0'*64})
|
||||
with self.assertRaisesRegex(Invalid,'review-final-bindings'):self.call()
|
||||
def test_changed_manifest(self):
|
||||
(self.lab/'results/fixture-freeze-sha256.txt').write_text(sha(h.BINARY)+' '+str(h.BINARY)+'\n')
|
||||
with self.assertRaisesRegex(Invalid,'complete-exact-manifest'):self.call()
|
||||
def test_changed_runner(self):
|
||||
(self.lab/'fixture/run.py').write_text('changed runner')
|
||||
with self.assertRaises(AssertionError):self.call()
|
||||
def test_consumed_run(self):
|
||||
h.exclusive(h.SPENT,{'synthetic':'spent'})
|
||||
with self.assertRaisesRegex(Invalid,'ticket-launch-already-consumed'):self.call()
|
||||
def test_existing_run(self):
|
||||
(self.lab/'run').mkdir()
|
||||
with self.assertRaisesRegex(Invalid,'unconsumed-runtime'):self.call()
|
||||
def test_review_report_changed(self):
|
||||
self.report.write_text('changed synthetic report')
|
||||
with self.assertRaisesRegex(Invalid,'review-report-bytes'):self.call()
|
||||
def test_consumed_runner_wrong_token(self):
|
||||
h.exclusive(h.SPENT,dict(ticket=h.TICKET,lab=str(self.lab),token='wrong',fixture_sha256=self.auth['fixture_sha256'],authorization_sha256=sha(self.lab/'results/authorization.json')))
|
||||
with self.assertRaisesRegex(Invalid,'runner-exact-consumption'):h.preflight(self.lab,self.lab/'results/authorization.json',consumed=True)
|
||||
111
tooling/probes/step20c-static-e2-completion/ipc_predicates.py
Normal file
111
tooling/probes/step20c-static-e2-completion/ipc_predicates.py
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
"""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_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'
|
||||
22
tooling/probes/step20c-static-e2-completion/launch.py
Normal file
22
tooling/probes/step20c-static-e2-completion/launch.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Exclusive consumed ticket; full reviewed preflight before dispatch.
|
||||
|
||||
Derived from static-nested-runtime/launch.py. No retries, refunds or builds.
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
from handoff import preflight,read,exclusive,SPENT,TICKET,sha,environment
|
||||
|
||||
def launch(lab):
|
||||
lab=Path(lab).resolve();auth=lab/'results/authorization.json'
|
||||
result=preflight(lab,auth)
|
||||
if read(lab/'results/preflight.json')!=result:raise ValueError('published-preflight-mismatch')
|
||||
exclusive(SPENT,dict(ticket=TICKET,lab=str(lab),token=result['token'],fixture_sha256=result['fixture']['sha256'],authorization_sha256=sha(auth)))
|
||||
argv=[sys.executable,str(lab/'fixture/run.py'),str(lab),'--future-authorization',str(auth)]
|
||||
with (lab/'raw/runner.stdout').open('x') as out,(lab/'raw/runner.stderr').open('x') as err:
|
||||
proc=subprocess.run(argv,env=environment(),stdout=out,stderr=err)
|
||||
exclusive(lab/'results/dispatch.json',dict(argv=argv,exit_code=proc.returncode,launch_consumed=True,runner_sha256=sha(lab/'fixture/run.py')))
|
||||
return proc.returncode
|
||||
|
||||
if __name__=='__main__':raise SystemExit(launch(Path(sys.argv[1])))
|
||||
65
tooling/probes/step20c-static-e2-completion/ledger.py
Normal file
65
tooling/probes/step20c-static-e2-completion/ledger.py
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
"""Durable debits. Offline tests initialize temporary ledgers only.
|
||||
|
||||
The offline LAB never has a runtime ledger. Future registration is a separate
|
||||
explicitly authorized ticket; failed reservations/actions are never refunded.
|
||||
"""
|
||||
import fcntl
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import time
|
||||
from model import require
|
||||
|
||||
LIMITS = dict(run=1, chain=1, grab=2, click=4, capture=12, camera=1, transition=1, move=16)
|
||||
|
||||
|
||||
def durable(path, state):
|
||||
fd, tmp = tempfile.mkstemp(prefix='.debit-', dir=path.parent)
|
||||
try:
|
||||
with os.fdopen(fd, 'w') as out:
|
||||
json.dump(state, out); 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)
|
||||
finally:
|
||||
if os.path.exists(tmp):
|
||||
os.unlink(tmp)
|
||||
|
||||
|
||||
def initial_for_offline_test(path):
|
||||
"""No production initialization API: the caller proves isolated test storage."""
|
||||
path = Path(path)
|
||||
require(path.parent.name.startswith('geometry-ledger-test-'), 'test-ledger-only')
|
||||
with path.open('x') as out:
|
||||
json.dump(dict(limits=LIMITS, used=dict.fromkeys(LIMITS, 0), events=[]), out)
|
||||
out.flush(); os.fsync(out.fileno())
|
||||
|
||||
|
||||
def valid(state):
|
||||
require(state.get('limits') == LIMITS and set(state.get('used', {})) == set(LIMITS), 'ledger-schema')
|
||||
require(all(type(state['used'][k]) is int and 0 <= state['used'][k] <= LIMITS[k] for k in LIMITS), 'ledger-counts')
|
||||
events = state.get('events')
|
||||
require(isinstance(events, list) and len(events) == sum(state['used'].values()), 'ledger-events')
|
||||
require(all(isinstance(e, dict) and e.get('seq') == i and e.get('kind') in LIMITS and
|
||||
type(e.get('ns')) is int and e['ns'] > 0 for i, e in enumerate(events)), 'ledger-sequences')
|
||||
require(all(sum(e['kind'] == k for e in events) == state['used'][k] for k in LIMITS), 'ledger-accounting')
|
||||
return state
|
||||
|
||||
|
||||
def reserve(path, *kinds):
|
||||
path = Path(path)
|
||||
require(path.is_file() and not path.is_symlink(), 'existing-ledger-required')
|
||||
with path.with_suffix('.lock').open('a+') as lock:
|
||||
fcntl.flock(lock, fcntl.LOCK_EX)
|
||||
state = valid(json.loads(path.read_text()))
|
||||
require(bool(kinds) and all(k in LIMITS for k in kinds), 'ledger-debit-kinds')
|
||||
require(all(state['used'][k] + kinds.count(k) <= LIMITS[k] for k in kinds), 'ledger-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
|
||||
64
tooling/probes/step20c-static-e2-completion/lifecycle.py
Normal file
64
tooling/probes/step20c-static-e2-completion/lifecycle.py
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
"""Pure E2 input/lifecycle predicates shared with offline negative tests."""
|
||||
import re
|
||||
from model import require
|
||||
from predicates import COLORS, paint, equal, pair, point
|
||||
|
||||
|
||||
def opening_pair(lines,target,serial):
|
||||
actual=[]
|
||||
for line in lines:
|
||||
m=re.fullmatch(r'POINTER_BUTTON target=(\w+) serial=(\d+) button=(\d+) state=(\d+)',line)
|
||||
require(m is not None,'opening-button-format');actual.append([m[1],*map(int,m.groups()[1:])])
|
||||
require(len(actual)==2 and all(e[0]==target for e in actual) and
|
||||
[e[2:] for e in actual]==[[272,1],[272,0]] and actual[0][1]==serial and
|
||||
0<actual[0][1]<actual[1][1]<=0xffffffff,'real-left-opening-pair')
|
||||
return actual
|
||||
|
||||
|
||||
def explicit_child_close(before,after,ids,request_ns,done_ns):
|
||||
"""Client destroy must follow a recorded explicit request; no popup_done."""
|
||||
require(type(request_ns) is int and request_ns<done_ns,'explicit-close-request-order')
|
||||
require(after.startswith(before),'append-only-client-protocol')
|
||||
tail=after[len(before):]
|
||||
require('POPUP_DONE ' not in after,'close-must-not-be-dismissal')
|
||||
b=ids['POPUP_B'];a=ids['POPUP_A']
|
||||
destroy=f'POPUP_B DESTROY role={b[2]} xdg={b[1]} wl={b[0]}'
|
||||
require(tail.count(destroy)==1 and tail.count('COMMAND close_b DONE')==1 and
|
||||
tail.index(destroy)<tail.index('COMMAND close_b DONE'),'explicit-child-destroy-DONE')
|
||||
for obj,stem in [(b[2],'xdg_popup'),(b[1],'xdg_surface'),(b[0],'wl_surface')]:
|
||||
require(tail.count(f'{stem}#{obj}.destroy()')==1,'child-protocol-destroy')
|
||||
require(after.count(f'POPUP_A MAP wl={a[0]} xdg={a[1]}')==1 and
|
||||
'POPUP_A DESTROY ' not in after and 'TOPLEVEL DESTROY' not in after,'same-A-remains-mapped')
|
||||
return dict(status='PASS',request_ns=request_ns,done_ns=done_ns,B=b,A=a,
|
||||
meaning='explicit client close, no compositor popup_done')
|
||||
|
||||
|
||||
def a_survival(image,before,after,ids,buttons,global_point,rect):
|
||||
a=ids['POPUP_A']
|
||||
require(paint(image,'POPUP_B')['count']==0 and paint(image,'POPUP_A')['count']>0,'child-zero-residual-A-painted')
|
||||
require(before.startswith('') and after.startswith(before),'A-input-protocol-appended')
|
||||
require('POPUP_A DESTROY ' not in after and 'POPUP_A POPUP_DONE ' not in after and
|
||||
after.count(f'POPUP_A MAP wl={a[0]} xdg={a[1]}')==1,'same-A-live-after-input')
|
||||
require(rect[0]<=global_point[0]<rect[0]+rect[2] and rect[1]<=global_point[1]<rect[1]+rect[3], 'A-recomputed-point-bounds')
|
||||
return dict(status='PASS',pair=pair(buttons,'POPUP_A'),ids=a,point=global_point)
|
||||
|
||||
|
||||
def normal_owner_close(before,after,ids,code):
|
||||
require(code==0 and after.startswith(before),'normal-client-exit')
|
||||
tail=after[len(before):]
|
||||
require(tail.count('COMMAND close_owner')==1 and tail.count('TOPLEVEL DESTROY')==1 and
|
||||
'POPUP_DONE ' not in tail,'normal-owner-close-command')
|
||||
a=ids['POPUP_A'];top=ids['TOPLEVEL']
|
||||
require(tail.count(f'POPUP_A DESTROY role={a[2]} xdg={a[1]} wl={a[0]}')==1 and
|
||||
tail.index('COMMAND close_owner')<tail.index('POPUP_A DESTROY ')<tail.index('TOPLEVEL DESTROY'),
|
||||
'normal-owner-child-first-destruction')
|
||||
for obj,stem in [(a[2],'xdg_popup'),(a[1],'xdg_surface'),(a[0],'wl_surface'),
|
||||
(top[2],'xdg_toplevel'),(top[1],'xdg_surface'),(top[0],'wl_surface')]:
|
||||
require(tail.count(f'{stem}#{obj}.destroy()')==1,'owner-protocol-destroy')
|
||||
return dict(status='PASS',client_exit=code,ids=ids)
|
||||
|
||||
|
||||
def final_restoration(baseline,final):
|
||||
cmp=equal(baseline,final)
|
||||
require(cmp['equal'] and all(paint(final,n)['count']==0 for n in ('POPUP_A','POPUP_B')),'whole-H2-final-exact')
|
||||
return dict(status='PASS',comparison=cmp)
|
||||
194
tooling/probes/step20c-static-e2-completion/model.py
Normal file
194
tooling/probes/step20c-static-e2-completion/model.py
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
"""EXPERIMENTAL typed half-open geometry. Pure: no processes or sockets.
|
||||
|
||||
Source contracts are recorded in the offline gate, not inferred from RGB.
|
||||
Every conversion is explicit and keeps assignment/owner/generation provenance.
|
||||
"""
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
||||
class Invalid(ValueError):
|
||||
pass
|
||||
|
||||
class Space(str, Enum):
|
||||
NATIVE = 'NativeSHM'
|
||||
CONFIGURE = 'XDGConfigure'
|
||||
CONTENT = 'AppliedContent'
|
||||
GLOBAL = 'SceneGlobal'
|
||||
POPUP = 'PopupLocal'
|
||||
VIEWPORT = 'GlobalViewport'
|
||||
PNG = 'OutputPNG'
|
||||
|
||||
|
||||
def require(value, contract):
|
||||
if not value:
|
||||
raise Invalid(contract)
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Provenance:
|
||||
owner: int
|
||||
workspace: int
|
||||
generation: str
|
||||
evidence: str
|
||||
state: str = 'applied'
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Rect:
|
||||
space: Space
|
||||
x: int
|
||||
y: int
|
||||
width: int
|
||||
height: int
|
||||
provenance: Provenance
|
||||
|
||||
def __post_init__(self):
|
||||
require(isinstance(self.space, Space), 'tag-required')
|
||||
require(all(type(v) is int for v in (self.x, self.y, self.width, self.height)), 'integer-geometry')
|
||||
require(self.width >= 0 and self.height >= 0, 'nonnegative-size')
|
||||
require(bool(self.provenance.evidence), 'evidence-required')
|
||||
|
||||
@property
|
||||
def end(self):
|
||||
return (self.x + self.width, self.y + self.height)
|
||||
|
||||
def contains(self, xy):
|
||||
return self.x <= xy[0] < self.end[0] and self.y <= xy[1] < self.end[1]
|
||||
|
||||
def values(self):
|
||||
return [self.x, self.y, self.width, self.height]
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Native:
|
||||
rectangle: Rect
|
||||
buffer_id: int
|
||||
stride: int
|
||||
format: int
|
||||
commit_serial: int
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Applied:
|
||||
scene: Rect
|
||||
window: Rect
|
||||
configured: Rect
|
||||
native: Native
|
||||
border_left: int
|
||||
border_top: int
|
||||
titlebar: int
|
||||
configure_ack: int
|
||||
view_offset: tuple = (0, 0)
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Output:
|
||||
name: str
|
||||
rectangle: Rect
|
||||
scale: float
|
||||
transform: str
|
||||
|
||||
|
||||
def same(a, b):
|
||||
require(a.owner == b.owner, 'owner-identity')
|
||||
require(a.workspace == b.workspace, 'workspace-identity')
|
||||
require(a.generation == b.generation, 'applied-generation')
|
||||
require(a.state == b.state == 'applied', 'pending-not-applied')
|
||||
|
||||
|
||||
def content(applied):
|
||||
s, w, c, n = applied.scene, applied.window, applied.configured, applied.native
|
||||
require(s.space is Space.GLOBAL and w.space is Space.CONTENT and c.space is Space.CONFIGURE,
|
||||
'assignment-spaces')
|
||||
require(n.rectangle.space is Space.NATIVE, 'native-space')
|
||||
for r in (w, c, n.rectangle):
|
||||
same(s.provenance, r.provenance)
|
||||
require(n.buffer_id > 0 and n.stride == n.rectangle.width * 4 and n.format in (0, 1), 'native-buffer-identity')
|
||||
require(0 < n.commit_serial <= 0xffffffff, 'commit-serial')
|
||||
require(0 < applied.configure_ack <= 0xffffffff, 'assigned-configure-ack')
|
||||
require((w.width, w.height) == (c.width, c.height), 'configure-assignment')
|
||||
require(applied.border_left == w.x and applied.border_top == applied.titlebar + w.y,
|
||||
'titlebar-content-offset')
|
||||
require(w.width + 2 * applied.border_left == s.width and
|
||||
w.height + applied.border_top + applied.border_left == s.height, 'assigned-destination')
|
||||
return Rect(Space.GLOBAL, s.x + applied.border_left + applied.view_offset[0],
|
||||
s.y + applied.border_top + applied.view_offset[1], w.width, w.height, s.provenance)
|
||||
|
||||
|
||||
def intersect(rect, boundary):
|
||||
require(rect.space is Space.GLOBAL and boundary.space in (Space.GLOBAL, Space.VIEWPORT), 'global-intersection')
|
||||
same(rect.provenance, boundary.provenance)
|
||||
x, y = max(rect.x, boundary.x), max(rect.y, boundary.y)
|
||||
return Rect(Space.GLOBAL, x, y, max(0, min(rect.end[0], boundary.end[0]) - x),
|
||||
max(0, min(rect.end[1], boundary.end[1]) - y), rect.provenance)
|
||||
|
||||
|
||||
def to_png(rect, output):
|
||||
require(rect.space is Space.GLOBAL and output.rectangle.space is Space.GLOBAL, 'global-to-png-spaces')
|
||||
require(output.scale == 1.0, 'unmeasured-output-scale')
|
||||
require(output.transform == 'normal', 'unmeasured-output-transform')
|
||||
same(rect.provenance, output.rectangle.provenance)
|
||||
visible = intersect(rect, output.rectangle)
|
||||
return Rect(Space.PNG, min(max(visible.x - output.rectangle.x,0),output.rectangle.width), min(max(visible.y - output.rectangle.y,0),output.rectangle.height),
|
||||
visible.width, visible.height, rect.provenance)
|
||||
|
||||
|
||||
def from_png(rect, output):
|
||||
require(rect.space is Space.PNG and output.rectangle.space is Space.GLOBAL, 'png-to-global-spaces')
|
||||
require(output.scale == 1.0 and output.transform == 'normal', 'unmeasured-png-transform')
|
||||
same(rect.provenance, output.rectangle.provenance)
|
||||
require(0 <= rect.x <= rect.end[0] <= output.rectangle.width and
|
||||
0 <= rect.y <= rect.end[1] <= output.rectangle.height, 'png-bounds')
|
||||
return Rect(Space.GLOBAL, rect.x + output.rectangle.x, rect.y + output.rectangle.y,
|
||||
rect.width, rect.height, rect.provenance)
|
||||
|
||||
|
||||
def verify_paint(rect, output, bbox, count, clip=None):
|
||||
expected = to_png(intersect(rect, clip) if clip is not None else rect, output)
|
||||
actual = [] if bbox is None else list(bbox)
|
||||
box=[expected.x, expected.y, *expected.end] if expected.width and expected.height else []
|
||||
require(actual == box, 'paint-origin-intersection')
|
||||
require(count == expected.width * expected.height, 'paint-area')
|
||||
return expected
|
||||
|
||||
|
||||
def popup(parent, configured, parent_xdg, expected_parent_xdg, ack, mapped):
|
||||
require(parent.space is Space.GLOBAL and configured.space is Space.POPUP, 'popup-spaces')
|
||||
same(parent.provenance, configured.provenance)
|
||||
require(parent_xdg == expected_parent_xdg and expected_parent_xdg > 0, 'xdg-parent')
|
||||
require(0 < ack <= 0xffffffff, 'popup-ack')
|
||||
require(mapped is True, 'popup-map')
|
||||
return Rect(Space.GLOBAL, parent.x + configured.x, parent.y + configured.y,
|
||||
configured.width, configured.height, parent.provenance)
|
||||
|
||||
|
||||
def crossing(rect, boundary_x):
|
||||
require(rect.space is Space.GLOBAL, 'crossing-space')
|
||||
return 'EXERCISED' if rect.x < boundary_x < rect.end[0] else 'NOT EXERCISED'
|
||||
|
||||
|
||||
def local_clip_to_global(local, origin, provenance):
|
||||
require(local.space is Space.CONTENT, 'clip-local-space')
|
||||
same(local.provenance, provenance)
|
||||
return Rect(Space.VIEWPORT, local.x + origin[0], local.y + origin[1],
|
||||
local.width, local.height, provenance)
|
||||
|
||||
|
||||
def pointer_local(xy, rect, actual):
|
||||
require(rect.space is Space.GLOBAL and rect.contains(xy), 'pointer-global-bounds')
|
||||
require(list(actual) == [xy[0] - rect.x, xy[1] - rect.y], 'pointer-local-correlation')
|
||||
return list(actual)
|
||||
|
||||
|
||||
def fresh_chain(old, new):
|
||||
require(old['owner'] != new['owner'] and old['pid'] != new['pid'], 'fresh-E2-identity')
|
||||
require(len(set(old['serials'] + new['serials'])) == 4 and
|
||||
max(old['serials']) < min(new['serials']), 'fresh-E2-serials')
|
||||
|
||||
|
||||
def fresh_standard(mosaic, standard):
|
||||
require(mosaic.provenance.owner == standard.provenance.owner and
|
||||
mosaic.provenance.workspace == standard.provenance.workspace, 'standard-owner')
|
||||
require(mosaic.provenance.generation != standard.provenance.generation, 'fresh-standard-generation')
|
||||
return standard
|
||||
|
||||
|
||||
def constraints(flags, requested, configured):
|
||||
require(type(flags) is int and 0 <= flags <= 63, 'positioner-flags')
|
||||
require(flags != 0 or requested == configured, 'zero-flags-no-adjustment')
|
||||
return {'flags': flags, 'request': requested, 'actual': configured, 'prediction_only': True}
|
||||
59
tooling/probes/step20c-static-e2-completion/observations.py
Normal file
59
tooling/probes/step20c-static-e2-completion/observations.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
"""Adapters from independently recorded protocol/IPC/applied trace observations.
|
||||
|
||||
No native-buffer/frame identity is attributed to a screenshot. Generation is
|
||||
an observation-window label; atomic transaction-generation is NOT TESTED.
|
||||
"""
|
||||
import re
|
||||
from model import *
|
||||
|
||||
|
||||
def applied_model(measured, owner, text, generation):
|
||||
require(measured['id'] == owner['id'] and measured['IPC'] == owner, 'measured-owner-identity')
|
||||
workspace = measured.get('workspace')
|
||||
require(workspace is not None, 'measured-workspace-required')
|
||||
p = Provenance(owner['id'], workspace, generation, measured['raw'])
|
||||
buffers = re.findall(r'create_buffer\(new id wl_buffer#(\d+), \d+, (\d+), (\d+), (\d+), (\d+)\)', text)
|
||||
require(bool(buffers), 'native-shm-trace-required')
|
||||
# Top's first buffer is recorded before TOPLEVEL MAP; never choose popup buffer.
|
||||
first = re.search(r'create_buffer\(new id wl_buffer#(\d+), \d+, (\d+), (\d+), (\d+), (\d+)\)', text)
|
||||
require(first.start() < text.index('TOPLEVEL MAP '), 'toplevel-buffer-before-map')
|
||||
bid,w,h,stride,fmt = map(int, first.groups())
|
||||
top_id=int(re.search(r'TOPLEVEL CREATE wl=(\d+)',text)[1])
|
||||
require(re.search(rf'wl_surface#{top_id}\.attach\(wl_buffer#{bid}, 0, 0\)',text) is not None,'native-toplevel-attach')
|
||||
cfgs = re.findall(r'TOPLEVEL ROLE_CONFIGURE w=(\d+) h=(\d+)', text)
|
||||
configured = next((list(map(int,c)) for c in reversed(cfgs) if int(c[0]) > 0 and int(c[1]) > 0), None)
|
||||
require(configured is not None, 'toplevel-configure-required')
|
||||
assigned_ack = int(re.findall(r'TOPLEVEL XDG_CONFIGURE serial=(\d+) ack', text)[-1])
|
||||
require(f'ack_configure({assigned_ack})' in text, 'assigned-ack-trace')
|
||||
serial = int(re.search(r'TOPLEVEL XDG_CONFIGURE serial=(\d+) ack', text)[1])
|
||||
natural=owner['geometry']
|
||||
require(natural==dict(x=0,y=0,width=w,height=h),'native-natural-geometry')
|
||||
win = owner['window_rect']
|
||||
return Applied(Rect(Space.GLOBAL,*measured['scene'],p),
|
||||
Rect(Space.CONTENT,*[win[k] for k in ('x','y','width','height')],p),
|
||||
Rect(Space.CONFIGURE,0,0,*configured,p),
|
||||
Native(Rect(Space.NATIVE,0,0,w,h,p),bid,stride,fmt,serial),
|
||||
win['x'], owner['deco_rect']['height'] + win['y'], owner['deco_rect']['height'],assigned_ack)
|
||||
|
||||
|
||||
def output_model(snapshot, name, provenance):
|
||||
outputs = [o for o in snapshot['outputs'] if o['name'] == name and o['active']]
|
||||
require(len(outputs) == 1, 'unique-active-output')
|
||||
o = outputs[0]
|
||||
return Output(name,Rect(Space.GLOBAL,*[o['rect'][k] for k in ('x','y','width','height')],provenance),o['scale'],o['transform'])
|
||||
|
||||
|
||||
def viewport_model(scene, provenance):
|
||||
require(scene['workspace'] == provenance.workspace, 'viewport-workspace')
|
||||
require(scene['clip_valid'] is True, 'viewport-applied-valid')
|
||||
return Rect(Space.VIEWPORT,*map(int,scene['global_clip']),provenance)
|
||||
|
||||
|
||||
def verify_owner_occlusion(left,right,rect,g,mosaic):
|
||||
from pixels import color_geometry
|
||||
p=rect.provenance
|
||||
occluders=tuple(Rect(Space.GLOBAL,*g[k],p) for k in ('A','B'))
|
||||
for im,ox in ((left,-800),(right,0)):
|
||||
out=Output(str(ox),Rect(Space.GLOBAL,ox,0,800,600,p),1.0,'normal')
|
||||
clip=Rect(Space.VIEWPORT,-800,0,800,600,p) if mosaic else None
|
||||
color_geometry(im,'TOPLEVEL',rect,out,clip,occluders)
|
||||
21
tooling/probes/step20c-static-e2-completion/pixels.py
Normal file
21
tooling/probes/step20c-static-e2-completion/pixels.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
"""Exact binary RGB masks with explicit output origin, clip and occlusion.
|
||||
|
||||
Screenshots prove color/visible geometry only, never native-buffer identity.
|
||||
"""
|
||||
from PIL import Image,ImageChops,ImageDraw
|
||||
from model import *
|
||||
COLORS=dict(TOPLEVEL=(35,68,204),POPUP_A=(224,192,32),POPUP_B=(32,208,160))
|
||||
|
||||
|
||||
def color_geometry(im,target,rect,output,clip,occluders=()):
|
||||
require(im.size==(output.rectangle.width,output.rectangle.height),'replay-output-size')
|
||||
visible=intersect(rect,clip) if clip else rect
|
||||
expected=Image.new('L',im.size,0);draw=ImageDraw.Draw(expected)
|
||||
box=to_png(visible,output)
|
||||
if box.width and box.height:draw.rectangle((box.x,box.y,box.end[0]-1,box.end[1]-1),fill=255)
|
||||
for occ in occluders:
|
||||
ob=to_png(occ,output)
|
||||
if ob.width and ob.height:draw.rectangle((ob.x,ob.y,ob.end[0]-1,ob.end[1]-1),fill=0)
|
||||
actual=Image.new('L',im.size);actual.putdata([255 if px==COLORS[target] else 0 for px in im.get_flattened_data()])
|
||||
require(ImageChops.difference(expected,actual).getbbox() is None,'replay-paint-'+target)
|
||||
return dict(count=sum(actual.histogram()[1:]),bbox=actual.getbbox())
|
||||
16
tooling/probes/step20c-static-e2-completion/placement.patch
Normal file
16
tooling/probes/step20c-static-e2-completion/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")) {
|
||||
199
tooling/probes/step20c-static-e2-completion/predicates.py
Normal file
199
tooling/probes/step20c-static-e2-completion/predicates.py
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
"""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 model import Rect,Space,Provenance,Output,popup,crossing,local_clip_to_global,require
|
||||
from pixels import color_geometry,COLORS
|
||||
from PIL import Image,ImageChops
|
||||
|
||||
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,provenance=None):
|
||||
a=configured(text,'POPUP_A');b=configured(text,'POPUP_B')
|
||||
require(provenance is not None,'popup-owner-provenance-required')
|
||||
p=provenance
|
||||
top=Rect(Space.GLOBAL,*origin,0,0,p)
|
||||
ids={n:list(map(int,re.search(rf'{n} CREATE wl=(\d+) xdg=(\d+) role=(\d+)',text).groups())) for n in ('TOPLEVEL','POPUP_A','POPUP_B')}
|
||||
result={}
|
||||
parent=top
|
||||
for key,name,parent_name,c in [('A','POPUP_A','TOPLEVEL',a),('B','POPUP_B','POPUP_A',b)]:
|
||||
parent_id=int(re.search(rf'{name} CREATE .*parent_xdg=(\d+)',text)[1])
|
||||
ack=int(re.findall(rf'{name} XDG_CONFIGURE serial=(\d+) ack',text)[-1])
|
||||
parent=popup(parent,Rect(Space.POPUP,*c,p),parent_id,ids[parent_name][1],ack,name+' MAP ' in text)
|
||||
result[key]=parent.values()
|
||||
result.update(configured_A=a,configured_B=b,origin=origin,provenance=dict(owner=p.owner,workspace=p.workspace,generation=p.generation,evidence=p.evidence,state=p.state),
|
||||
crossing={key:crossing(Rect(Space.GLOBAL,*result[key],p),0) for key in ('A','B')})
|
||||
return result
|
||||
|
||||
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'
|
||||
debug_parent=re.search(rf'xdg_surface#{ids[name][1]}\.get_popup\(new id xdg_popup#{ids[name][2]}, xdg_surface#{ids[parent][1]}, xdg_positioner#\d+\)',text)
|
||||
require(debug_parent is not None,'debug-parent-request')
|
||||
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'
|
||||
debug_button=re.search(rf'wl_pointer#\d+\.button\({serial}, \d+, 272, 1\)',text)
|
||||
require(debug_button is not None and debug_button.start()<m.start(),'debug-real-button-serial')
|
||||
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
|
||||
actual=configured(text,name)
|
||||
require(re.search(rf'xdg_popup#{popup_role}\.configure\({actual[0]}, {actual[1]}, {actual[2]}, {actual[3]}\)',text) is not None,'debug-popup-configure')
|
||||
require(re.search(rf'xdg_surface#{xdg}\.configure\({cfg[1]}\)',text) is not None,'debug-xdg-configure')
|
||||
native_attach=re.search(rf'wl_surface#{wl}\.attach\(wl_buffer#(\d+), 0, 0\)',text[ack.start():mp])
|
||||
require(native_attach is not None,'popup-native-attach')
|
||||
native_buffer=int(native_attach[1])
|
||||
native_create=re.search(rf'create_buffer\(new id wl_buffer#{native_buffer}, 0, {actual[2]}, {actual[3]}, {actual[2]*4}, 1\)',text[role:mp])
|
||||
require(native_create is not None,'popup-native-configured-size')
|
||||
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]),native_buffer=native_buffer,native_size=actual[2:]))
|
||||
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,owner_id,start_ns,end_ns):
|
||||
# Traces lack popup owner IDs. Correlate the only live root in a bounded
|
||||
# owner-observed window; ambiguity is a fixture failure, never rows[-1].
|
||||
owners=[s for s in text.splitlines() if 'PI_OWNER output=HEADLESS-1 ' in s and f' id={owner_id} ' in s and start_ns<=int(re.search(r'ns=(\d+)',s)[1])<=end_ns]
|
||||
rows=[s for s in text.splitlines() if 'PI_POPUP output=HEADLESS-1 ' in s and start_ns<=int(re.search(r'ns=(\d+)',s)[1])<=end_ns]
|
||||
require(bool(owners) and bool(rows),'bounded-root-owner-observations')
|
||||
parsed=[]
|
||||
for row in rows:
|
||||
m=re.search(r'local_index=(\d+) tree=(\S+) enabled=(\d+) clip_valid=(\d+) clip=(-?\d+),(-?\d+),(\d+),(\d+)',row)
|
||||
require(m is not None,'root-trace-format')
|
||||
index,tree,enabled,valid,*box=m.groups()
|
||||
parsed.append((int(index),tree,int(enabled),bool(int(valid)),tuple(map(int,box))))
|
||||
require(len({r[1] for r in parsed})==1,'root-chain-unique')
|
||||
require(len(set(parsed))==1,'root-window-state-consistent')
|
||||
index,tree,enabled,valid,box=parsed[0]
|
||||
require(index==1 and enabled==1,'root-enabled-index')
|
||||
if valid!=mosaic:raise Defect('root clip state mismatch')
|
||||
if mosaic:
|
||||
p=Provenance(owner_id,1,'bounded-root-window','PI_OWNER + PI_POPUP')
|
||||
global_clip=local_clip_to_global(Rect(Space.CONTENT,*box,p),origin,p)
|
||||
require(global_clip.values()==[-800,0,800,600],'root-applied-global-clip')
|
||||
return dict(tree=tree,clip_valid=valid,local=list(box),origin=origin,raw=rows[0],owner=owner_id,window=[start_ns,end_ns],correlation='unique root and bounded owner observation; no atomic generation claim',child='inherited source hierarchy')
|
||||
|
||||
def popup_pixels(left,right,g,mosaic,baseline):
|
||||
# The runner and actual historical replay share exact typed mask conversion.
|
||||
require('provenance' in g,'popup-paint-provenance')
|
||||
p=Provenance(**g['provenance']);A=Rect(Space.GLOBAL,*g['A'],p);B=Rect(Space.GLOBAL,*g['B'],p)
|
||||
clip=Rect(Space.VIEWPORT,-800,0,800,600,p) if mosaic else None
|
||||
result={}
|
||||
for side,im,ox in [('H1',left,-800),('H2',right,0)]:
|
||||
output=Output(side,Rect(Space.GLOBAL,ox,0,800,600,p),1.0,'normal')
|
||||
result[side]={name:color_geometry(im,name,r,output,clip,(B,) if name=='POPUP_A' else ()) for name,r in [('POPUP_A',A),('POPUP_B',B)]}
|
||||
require(all(result['H1'][name]['count']>0 for name in ('POPUP_A','POPUP_B')),'permitted-popup-color')
|
||||
if mosaic:
|
||||
result['whole_H2']=equal(baseline,right)
|
||||
if not result['whole_H2']['equal']:raise Defect('whole-H2 changed under Mosaic')
|
||||
else:
|
||||
require(all(result['H2'][name]['count']>0 for name in ('POPUP_A','POPUP_B')),'standard-popup-continuation')
|
||||
return result
|
||||
|
||||
def terminal(measures,ipc_code,compositor_code,log,protocol_error,rescue,stdout='',stderr='',reaped=True):
|
||||
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)
|
||||
# Reaping is completed by the runner after recording actual terminal IPC.
|
||||
require(type(ipc_code) is int and type(compositor_code) is int,'actual-terminal-codes')
|
||||
return dict(scientific_cleanup_pass=ok,ipc_exit=ipc_code,compositor_exit=compositor_code,
|
||||
stdout=stdout,stderr=stderr,all_owned_reaped=reaped,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'))
|
||||
|
||||
|
||||
def terminal_exchange(execute, wait, argv, env, measures, compositor_log,
|
||||
protocol_error, rescue, record):
|
||||
"""CONTRACT: preserve an obtained IPC reply before fallible compositor wait."""
|
||||
data=dict(scientific_cleanup_pass=False,ipc_exit=None,stdout='',stderr='',
|
||||
compositor_exit=None,measurements_completed=measures)
|
||||
try:
|
||||
reply=execute(argv,env=env,capture_output=True,text=True,timeout=3)
|
||||
data.update(ipc_exit=reply.returncode,stdout=reply.stdout,stderr=reply.stderr)
|
||||
record(reply.stdout,reply.stderr)
|
||||
except Exception as exc:
|
||||
data['ipc_error']=repr(exc)
|
||||
return data
|
||||
try:
|
||||
data['compositor_exit']=wait(timeout=3)
|
||||
except Exception as exc:
|
||||
data['compositor_error']=repr(exc)
|
||||
return data
|
||||
try:
|
||||
data.update(terminal(measures,data['ipc_exit'],data['compositor_exit'],
|
||||
compositor_log(),protocol_error(),rescue(),stdout=data['stdout'],
|
||||
stderr=data['stderr'],reaped=False))
|
||||
except Exception as exc:
|
||||
data['terminal_error']=repr(exc)
|
||||
return data
|
||||
75
tooling/probes/step20c-static-e2-completion/replay.py
Normal file
75
tooling/probes/step20c-static-e2-completion/replay.py
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Read-only real E2 transition replay; raw PNGs/logs remain outside Git."""
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
from PIL import Image
|
||||
from transition import validate_applied_standard_transition, source_bindings
|
||||
from predicates import protocol, geometry, popup_pixels, reference
|
||||
from model import Provenance,require
|
||||
from support import sha,verify
|
||||
|
||||
HISTORICAL=Path('/home/fy59/swayfx-mosaic-static-nested-runtime-pvn0osxm')
|
||||
SEALS={'source-sha256.txt':(60,'263f5dbee5e96f2c49619802d063115f792718f6aeb71fa41c58c73b83eb8c91'),
|
||||
'evidence-sha256.txt':(178,'e8b2307612ce2402ff9b31e68b50a47d666ca38cd8cce1d67096297f8129034a')}
|
||||
|
||||
def historical_inputs():
|
||||
for name,(count,digest) in SEALS.items():
|
||||
p=HISTORICAL/'results'/name
|
||||
require(sha(p)==digest and verify(p)['entries']==count,'historical-seal')
|
||||
events=[json.loads(l) for l in (HISTORICAL/'run/events.jsonl').read_text().splitlines()]
|
||||
replies=[e for e in events if e.get('args')==['[con_id=10] layout splith']]
|
||||
require(len(replies)==1,'single-historical-transition');reply=replies[0]
|
||||
text=(HISTORICAL/'run/compositor.log').read_text()
|
||||
handling="Handling command 'layout splith'"
|
||||
require(text.count(handling)==1,'historical-command-handling')
|
||||
after=text.split(handling,1)[1]
|
||||
# Lower bound is previous actual runner event; upper bound is first actual
|
||||
# monotonic compositor record after handling. Exact request time was omitted.
|
||||
previous=max(e['ns'] for e in events if e['ns']<reply['ns'])
|
||||
upper=int(re.search(r'ns=(\d+)',after)[1])
|
||||
window=[previous,max(e['ns'] for e in events if e.get('kind')=='capture')]
|
||||
captures=[]
|
||||
for e in events:
|
||||
if e.get('file') in ('standard-H1.png','standard-H2.png'):
|
||||
captures.append(dict(file=e['file'],output=e['output'],start_ns=e['start_ns'],
|
||||
end_ns=e['ns'],exit=e['exit'],sha256=sha(HISTORICAL/'run'/e['file'])))
|
||||
client=(HISTORICAL/'run/E2.log').read_text()
|
||||
# Historical client log includes teardown after runner failure. Replay the
|
||||
# measured prefix, explicitly cut at cleanup command, never silently delete
|
||||
# a done/destroy during the measured window.
|
||||
# Historical shutdown is corroborated by seat capabilities(0), then
|
||||
# DESTROY records. No explicit owner-close measurement happened.
|
||||
shutdown=re.search(r'\{Default Queue\} wl_seat#\d+\.capabilities\(0\)',client)
|
||||
require(shutdown is not None and shutdown.start()<client.index('POPUP_B DESTROY '),'historical-shutdown-boundary')
|
||||
client=client[:shutdown.start()]
|
||||
snapshot=json.loads((HISTORICAL/'results/standard-snapshot.json').read_text())
|
||||
ids=protocol(client)['ids']
|
||||
return dict(request_ns=[previous,upper],ipc_response=reply,trace=text,
|
||||
owner_id=10,root_id='0x56292fbcc170',workspace_id=6,output='HEADLESS-1',
|
||||
applied_layout='splith',captures=captures,client_protocol=client,snapshot=snapshot,
|
||||
expected_ids=ids,window=window,source_contract=source_bindings())
|
||||
|
||||
|
||||
def replay():
|
||||
data=historical_inputs();result=validate_applied_standard_transition(**data)
|
||||
g=geometry(data['client_protocol'],[-398,25],Provenance(10,6,'historical-standard','actual PI_OWNER + IPC window'))
|
||||
require(g['A']==[-298,105,350,170] and g['B']==[-198,165,300,130],'historical-standard-geometry')
|
||||
images={n:Image.open(HISTORICAL/'run'/n).convert('RGB') for n in
|
||||
('H2-reference-1.png','H2-reference-2.png','standard-H1.png','standard-H2.png')}
|
||||
reference(images['H2-reference-1.png'],images['H2-reference-2.png'])
|
||||
pixels=popup_pixels(images['standard-H1.png'],images['standard-H2.png'],g,False,images['H2-reference-1.png'])
|
||||
require(pixels['H2']['POPUP_A']['count']==3120 and pixels['H2']['POPUP_B']['count']==13260,'historical-H2-counts')
|
||||
roots=[s for s in result['states'] if s['ns'] in (181923034904618,181923236944243,181923848675032)]
|
||||
require(len(roots)==3 and [s['root']['clip_valid'] for s in roots]==['1','0','0'],'raw-transition-chronology')
|
||||
return dict(status='PASS',transition=result,geometry=g,pixels=pixels,
|
||||
request_time='bounded omission; no exact request timestamp reconstructed',
|
||||
runtime_completion='NOT TESTED; historical run remains INCONCLUSIVE')
|
||||
|
||||
if __name__=='__main__':
|
||||
import argparse
|
||||
p=argparse.ArgumentParser();p.add_argument('--output',type=Path);a=p.parse_args()
|
||||
result=replay()
|
||||
if a.output:
|
||||
with a.output.open('x') as out:json.dump(result,out,indent=2);out.write('\n')
|
||||
print('E2_RAW_TRANSITION_REPLAY=PASS')
|
||||
171
tooling/probes/step20c-static-e2-completion/reusable_tests.py
Normal file
171
tooling/probes/step20c-static-e2-completion/reusable_tests.py
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
"""Exact reusable historical geometry/protocol/terminal core tests.
|
||||
Derived AST nodes from geometry-replay/tests_offline.py; no historical gate.
|
||||
"""
|
||||
import unittest
|
||||
import re
|
||||
from dataclasses import replace
|
||||
from PIL import Image
|
||||
from model import *
|
||||
from predicates import *
|
||||
from pixels import color_geometry
|
||||
ACTIVE=None
|
||||
class Case(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
global ACTIVE
|
||||
ACTIVE = self.id()
|
||||
self.p = Provenance(9, 6, 'applied-1', 'synthetic applied trace')
|
||||
self.a = Applied(Rect(Space.GLOBAL, -320, 0, 640, 600, self.p), Rect(Space.CONTENT, 2, 0, 636, 573, self.p), Rect(Space.CONFIGURE, 0, 0, 636, 573, self.p), Native(Rect(Space.NATIVE, 0, 0, 420, 320, self.p), 12, 1680, 1, 20), 2, 25, 25, 21)
|
||||
self.r = content(self.a)
|
||||
self.o = Output('H1', Rect(Space.GLOBAL, -800, 0, 800, 600, self.p), 1.0, 'normal')
|
||||
self.clip = Rect(Space.VIEWPORT, -800, 0, 800, 600, self.p)
|
||||
|
||||
def tearDown(self):
|
||||
global ACTIVE
|
||||
ACTIVE = None
|
||||
|
||||
def reject(self, contract, fn):
|
||||
with self.assertRaisesRegex(Invalid, '^' + re.escape(contract) + '$'):
|
||||
fn()
|
||||
|
||||
def test_positive_geometry(self):
|
||||
self.assertEqual(self.r.values(), [-318, 25, 636, 573])
|
||||
self.assertEqual(to_png(intersect(self.r, self.clip), self.o).values(), [482, 25, 318, 573])
|
||||
self.assertEqual(from_png(Rect(Space.PNG, 482, 25, 318, 573, self.p), self.o).values(), [-318, 25, 318, 573])
|
||||
verify_paint(self.r, self.o, [482, 25, 800, 598], 182214, self.clip)
|
||||
|
||||
def test_negative_native_height(self):
|
||||
self.reject('paint-origin-intersection', lambda: verify_paint(replace(self.r, height=320), self.o, [482, 25, 800, 598], 182214, self.clip))
|
||||
|
||||
def test_negative_scene_window_y(self):
|
||||
self.reject('paint-origin-intersection', lambda: verify_paint(replace(self.r, y=0), self.o, [482, 25, 800, 598], 182214, self.clip))
|
||||
|
||||
def test_negative_titlebar(self):
|
||||
self.reject('titlebar-content-offset', lambda: content(replace(self.a, titlebar=0)))
|
||||
|
||||
def test_negative_output_origin(self):
|
||||
self.reject('paint-origin-intersection', lambda: verify_paint(self.r, replace(self.o, rectangle=replace(self.o.rectangle, x=0)), [482, 25, 800, 598], 182214, self.clip))
|
||||
|
||||
def test_negative_png_space(self):
|
||||
self.reject('global-to-png-spaces', lambda: to_png(replace(self.r, space=Space.PNG), self.o))
|
||||
|
||||
def test_negative_png_bounds(self):
|
||||
self.reject('png-bounds', lambda: from_png(Rect(Space.PNG, -1, 25, 318, 573, self.p), self.o))
|
||||
|
||||
def test_negative_assignment(self):
|
||||
self.reject('configure-assignment', lambda: content(replace(self.a, configured=replace(self.a.configured, width=420))))
|
||||
|
||||
def test_negative_workspace(self):
|
||||
self.reject('workspace-identity', lambda: content(replace(self.a, window=replace(self.a.window, provenance=replace(self.p, workspace=7)))))
|
||||
|
||||
def test_negative_owner(self):
|
||||
self.reject('owner-identity', lambda: content(replace(self.a, window=replace(self.a.window, provenance=replace(self.p, owner=10)))))
|
||||
|
||||
def test_negative_old_camera(self):
|
||||
self.reject('applied-generation', lambda: intersect(self.r, replace(self.clip, provenance=replace(self.p, generation='old-camera'))))
|
||||
|
||||
def test_negative_local_global_clip(self):
|
||||
self.reject('global-intersection', lambda: intersect(self.r, replace(self.clip, space=Space.CONTENT)))
|
||||
|
||||
def test_negative_buffer_identity(self):
|
||||
self.reject('native-buffer-identity', lambda: content(replace(self.a, native=replace(self.a.native, buffer_id=0))))
|
||||
|
||||
def test_negative_pending(self):
|
||||
self.reject('pending-not-applied', lambda: content(replace(self.a, scene=replace(self.a.scene, provenance=replace(self.p, state='pending')))))
|
||||
|
||||
def test_negative_scale(self):
|
||||
self.reject('unmeasured-output-scale', lambda: to_png(self.r, replace(self.o, scale=2.0)))
|
||||
|
||||
def test_negative_transform(self):
|
||||
self.reject('unmeasured-output-transform', lambda: to_png(self.r, replace(self.o, transform='90')))
|
||||
|
||||
def test_positive_popup(self):
|
||||
A = popup(self.r, Rect(Space.POPUP, 100, 80, 350, 170, self.p), 9, 9, 22, True)
|
||||
B = popup(A, Rect(Space.POPUP, 100, 60, 300, 130, self.p), 12, 12, 23, True)
|
||||
self.assertEqual(A.values(), [-218, 105, 350, 170])
|
||||
self.assertEqual(B.values(), [-118, 165, 300, 130])
|
||||
self.assertEqual(crossing(B, 0), 'EXERCISED')
|
||||
self.assertEqual(crossing(replace(B, x=1), 0), 'NOT EXERCISED')
|
||||
pointer_local((-100, 175), B, (18, 10))
|
||||
constraints(0, [100, 80, 350, 170], [100, 80, 350, 170])
|
||||
self.assertEqual(local_clip_to_global(Rect(Space.CONTENT, -482, -25, 800, 600, self.p), (-318, 25), self.p).values(), [-800, 0, 800, 600])
|
||||
|
||||
def test_negative_parent(self):
|
||||
self.reject('xdg-parent', lambda: popup(self.r, Rect(Space.POPUP, 100, 80, 350, 170, self.p), 11, 9, 22, True))
|
||||
|
||||
def test_negative_ack(self):
|
||||
self.reject('popup-ack', lambda: popup(self.r, Rect(Space.POPUP, 100, 80, 350, 170, self.p), 9, 9, 0, True))
|
||||
|
||||
def test_negative_map(self):
|
||||
self.reject('popup-map', lambda: popup(self.r, Rect(Space.POPUP, 100, 80, 350, 170, self.p), 9, 9, 22, False))
|
||||
|
||||
def test_negative_constraints(self):
|
||||
self.reject('zero-flags-no-adjustment', lambda: constraints(0, [100, 80, 350, 170], [99, 80, 350, 170]))
|
||||
|
||||
def test_negative_pointer(self):
|
||||
self.reject('pointer-local-correlation', lambda: pointer_local((-308, 35), self.r, (0, 0)))
|
||||
|
||||
def test_negative_occlusion(self):
|
||||
im = Image.new('RGB', (800, 600))
|
||||
r = Rect(Space.GLOBAL, -800, 0, 1, 1, self.p)
|
||||
self.reject('replay-paint-POPUP_A', lambda: color_geometry(im, 'POPUP_A', r, self.o, None))
|
||||
|
||||
def test_negative_standard_stale_origin(self):
|
||||
self.reject('fresh-standard-generation', lambda: fresh_standard(self.r, self.r))
|
||||
|
||||
def test_positive_standard_fresh(self):
|
||||
self.assertEqual(fresh_standard(self.r, replace(self.r, x=-398, provenance=replace(self.p, generation='standard-2'))).x, -398)
|
||||
|
||||
def test_negative_E2_identity(self):
|
||||
self.reject('fresh-E2-identity', lambda: fresh_chain(dict(owner=9, pid=10, serials=[1, 2]), dict(owner=9, pid=11, serials=[3, 4])))
|
||||
|
||||
def test_negative_E2_serials(self):
|
||||
self.reject('fresh-E2-serials', lambda: fresh_chain(dict(owner=9, pid=10, serials=[1, 2]), dict(owner=10, pid=11, serials=[2, 3])))
|
||||
|
||||
def test_positive_E2(self):
|
||||
fresh_chain(dict(owner=9, pid=10, serials=[1, 2]), dict(owner=10, pid=11, serials=[3, 4]))
|
||||
|
||||
def test_positive_terminal(self):
|
||||
for ipc in (0, 1):
|
||||
self.assertTrue(terminal(True, ipc, 0, "Handling command 'exit'\nShutting down sway", False, False, stdout='actual reply', stderr='actual stderr')['scientific_cleanup_pass'])
|
||||
|
||||
def test_negative_terminal_incomplete(self):
|
||||
self.assertFalse(terminal(False, 1, 0, "Handling command 'exit'\nShutting down sway", False, False)['scientific_cleanup_pass'])
|
||||
|
||||
def test_negative_terminal_unhandled(self):
|
||||
self.assertFalse(terminal(True, 1, 0, 'Shutting down sway', False, False)['scientific_cleanup_pass'])
|
||||
|
||||
def test_negative_terminal_compositor(self):
|
||||
self.assertFalse(terminal(True, 1, 1, "Handling command 'exit'\nShutting down sway", False, False)['scientific_cleanup_pass'])
|
||||
|
||||
def test_negative_terminal_protocol(self):
|
||||
self.assertFalse(terminal(True, 1, 0, "Handling command 'exit'\nShutting down sway", True, False)['scientific_cleanup_pass'])
|
||||
|
||||
def test_negative_terminal_rescue(self):
|
||||
self.assertFalse(terminal(True, 1, 0, "Handling command 'exit'\nShutting down sway", False, True)['scientific_cleanup_pass'])
|
||||
|
||||
def test_negative_dismissal_hit(self):
|
||||
with self.assertRaisesRegex(Defect, 'invisible popup'):
|
||||
dismissal('POPUP_B POPUP_DONE \nPOPUP_A POPUP_DONE \nPOPUP_B DESTROY \nPOPUP_A DESTROY ', ['POINTER_BUTTON target=POPUP_B serial=22 button=273 state=1'])
|
||||
|
||||
def test_negative_dismissal_incomplete(self):
|
||||
with self.assertRaises(AssertionError):
|
||||
dismissal('POPUP_A POPUP_DONE ', [])
|
||||
|
||||
def test_positive_dismissal(self):
|
||||
self.assertEqual(dismissal('POPUP_B POPUP_DONE \nPOPUP_A POPUP_DONE \nPOPUP_B DESTROY \nPOPUP_A DESTROY ', [])['status'], 'PASS')
|
||||
|
||||
def test_positive_button(self):
|
||||
pair(['POINTER_BUTTON target=POPUP_B serial=22 button=273 state=1', 'POINTER_BUTTON target=POPUP_B serial=23 button=273 state=0'], 'POPUP_B')
|
||||
|
||||
def test_negative_button(self):
|
||||
with self.assertRaises(Defect):
|
||||
pair(['POINTER_BUTTON target=POPUP_A serial=22 button=273 state=1'], 'POPUP_B')
|
||||
|
||||
def test_negative_root_ambiguous(self):
|
||||
trace = 'PI_OWNER output=HEADLESS-1 ns=10 id=9 scene_valid=1\nPI_POPUP output=HEADLESS-1 ns=11 local_index=1 tree=0x1 enabled=1 clip_valid=1 clip=-482,-25,800,600\nPI_POPUP output=HEADLESS-1 ns=12 local_index=2 tree=0x2 enabled=1 clip_valid=1 clip=-482,-25,800,600'
|
||||
self.reject('root-chain-unique', lambda: root_clip(trace, [-318, 25], True, 9, 1, 20))
|
||||
|
||||
def test_positive_root(self):
|
||||
trace = 'PI_OWNER output=HEADLESS-1 ns=10 id=9 scene_valid=1\nPI_POPUP output=HEADLESS-1 ns=11 local_index=1 tree=0x1 enabled=1 clip_valid=1 clip=-482,-25,800,600'
|
||||
self.assertTrue(root_clip(trace, [-318, 25], True, 9, 1, 20)['clip_valid'])
|
||||
376
tooling/probes/step20c-static-e2-completion/run.py
Normal file
376
tooling/probes/step20c-static-e2-completion/run.py
Normal file
|
|
@ -0,0 +1,376 @@
|
|||
#!/usr/bin/env python3
|
||||
"""One immutable-static headless process, one genuinely grabbed E2 XDG chain.
|
||||
|
||||
WHY: one chain preserves both popups through 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 sha,save,reserve,environment,verify
|
||||
from model import *
|
||||
from authorization import authorize
|
||||
from transition import validate_applied_standard_transition, transition_window_start, source_bindings
|
||||
from lifecycle import opening_pair,explicit_child_close,a_survival,normal_owner_close,final_restoration
|
||||
from observations import applied_model,output_model,viewport_model,verify_owner_occlusion
|
||||
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,terminal_exchange
|
||||
|
||||
p=argparse.ArgumentParser();p.add_argument('lab',type=Path);p.add_argument('--future-authorization',type=Path)
|
||||
args=p.parse_args();lab=args.lab.resolve()
|
||||
# Fail CLOSED before owner/lock/ledger/runtime creation or any subprocess.
|
||||
auth=authorize(lab,args.future_authorization)
|
||||
OWNER=Path(auth['owner_registry']);BINARY=Path(auth['binary']);BINARY_SHA=auth['binary_sha256'];WLROOTS_SHA=auth['wlroots_sha256']
|
||||
require(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']=='E2_COMPLETION_OFFLINE=READY_FOR_REVIEW' 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={};root_boundary={};captures=[];clicks=[];owners={};camera_rows=[];fully_mapped_ns={}
|
||||
|
||||
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>143 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()
|
||||
event('ipc-request-start',args=args)
|
||||
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),start_ns=before,end_ns=time.monotonic_ns(),exit=r.returncode))
|
||||
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']}
|
||||
cuts={n:len(log(n)) for n in before}
|
||||
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},pointer_protocol={n:log(n)[cuts[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'
|
||||
result=pair(row['events'][name],target)
|
||||
for _,serial,button,state in result:
|
||||
require(re.search(rf'wl_pointer#\d+\.button\({serial}, \d+, {button}, {state}\)',row['pointer_protocol'][name]) is not None,'button-wire-corroboration')
|
||||
return result
|
||||
|
||||
def correlate_pointer(name,xy,rect,target,evidence):
|
||||
# WHY: an injection request alone does not identify applied popup origin.
|
||||
rows=re.findall(rf'POINTER_(?:ENTER|MOTION) target={target} .*?x=([\d.-]+) y=([\d.-]+)',evidence)
|
||||
assert rows,'missing pointer local coordinates'
|
||||
local=list(map(float,rows[-1]));expected=[xy[0]-rect[0],xy[1]-rect[1]]
|
||||
prov=Provenance(1,1,'pointer-sample','actual pointer log')
|
||||
pointer_local(xy,Rect(Space.GLOBAL,*rect,prov),local)
|
||||
wl=int(re.search(rf'{target} CREATE wl=(\d+)',log(name))[1])
|
||||
wire=re.findall(rf'wl_pointer#\d+\.(?:enter\(\d+, wl_surface#{wl},|motion\(\d+,) ([\d.-]+), ([\d.-]+)\)',evidence)
|
||||
require(any(list(map(float,v))==local for v in wire),'fresh-pointer-wire-coordinates')
|
||||
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']
|
||||
measured['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=='E2':
|
||||
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
|
||||
root_boundary[name]=time.monotonic_ns()
|
||||
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.
|
||||
applied=applied_model(after,owner,log(name),name+'-mosaic')
|
||||
content_rect=content(applied);origin=[content_rect.x,content_rect.y]
|
||||
out=output_model(snap,'HEADLESS-1',content_rect.provenance)
|
||||
verify_paint(content_rect,out,topbox,paint(topim,'TOPLEVEL')['count'],viewport_model(scene,content_rect.provenance))
|
||||
assert topbox[0]-800==origin[0]
|
||||
assert origin[1]==after['scene'][1]+owner['deco_rect']['height']+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)
|
||||
top_point=point(topim,'TOPLEVEL',-800)
|
||||
require(0 <= top_point[0]-origin[0] < applied.native.rectangle.width and 0 <= top_point[1]-origin[1] < applied.native.rectangle.height, 'opening-point-native-input-bounds')
|
||||
cp=click(name+'-open-A',name,top_point,True,'left')
|
||||
wait(lambda:'POPUP_A MAP ' in log(name));time.sleep(.25)
|
||||
correlate_pointer(name,top_point,content_rect.values(),'TOPLEVEL',cp['pointer_protocol'][name])
|
||||
aim=capture(name+'-A','HEADLESS-1');ap=point(aim,'POPUP_A',-800)
|
||||
stage=name+'-open-B';cpB=click(name+'-open-B',name,ap,True,'left')
|
||||
wait(lambda:'POPUP_B MAP ' in log(name));time.sleep(.3)
|
||||
prot=protocol(log(name))
|
||||
require(not cp['events']['reference'] and not cp['events']['filler'] and not cpB['events']['reference'] and not cpB['events']['filler'],'opening-other-recipient')
|
||||
opening_pair(cp['events'][name],'TOPLEVEL',prot['grabs'][0]['serial'])
|
||||
opening_pair(cpB['events'][name],'POPUP_A',prot['grabs'][1]['serial'])
|
||||
g=geometry(log(name),origin,content_rect.provenance)
|
||||
# Actual configure/ACK/map drives geometry; requests are predictions only.
|
||||
g['positioner_prediction']={'flags':0,'A':[100,80,350,170],'B':[100,60,300,130],'evidence':'source-backed prediction only'}
|
||||
require(all(value=='EXERCISED' for value in g['crossing'].values()),'popup crossing NOT EXERCISED')
|
||||
correlate_pointer(name,ap,g['A'],'POPUP_A',cpB['pointer_protocol'][name])
|
||||
fully_mapped_ns[name]=time.monotonic_ns();event('fully-mapped-chain',name=name,ids=prot['ids'])
|
||||
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,owner['id'],root_boundary[name],time.monotonic_ns())
|
||||
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')
|
||||
E2,owner2,g2,im2=open_chain('E2')
|
||||
stage='E2-standard';mapped(log('E2'));standard_boundary=time.monotonic_ns();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
|
||||
stdapplied=applied_model(std_measure,std_owner,log('E2'),'E2-standard')
|
||||
stdcontent=content(stdapplied);stdorigin=[stdcontent.x,stdcontent.y]
|
||||
fresh_standard(Rect(Space.GLOBAL,*owners['E2']['applied']['scene'],Provenance(owner2['id'],ws['id'],'E2-mosaic',owners['E2']['applied']['raw'])),stdapplied.scene)
|
||||
# Blue is occluded by A/B; full pixel mask checked against actual geometry below.
|
||||
assert blue[0]-800==stdorigin[0] and blue[1]==stdorigin[1]
|
||||
stdg=geometry(log('E2'),stdorigin,stdcontent.provenance)
|
||||
transition_end=time.monotonic_ns()
|
||||
events=[json.loads(l) for l in (run/'events.jsonl').read_text().splitlines()]
|
||||
request=[e for e in events if e['kind']=='ipc-request-start' and e.get('args')==[f'[con_id={owner2["id"]}] layout splith']]
|
||||
replies=[e for e in events if e['kind']=='ipc' and e.get('args')==[f'[con_id={owner2["id"]}] layout splith']]
|
||||
require(len(request)==len(replies)==1,'one-transition-request-reply')
|
||||
applied_transition_start=transition_window_start(log('compositor'),'HEADLESS-1',clips['E2']['root']['tree'],
|
||||
owner2['id'],ws['id'],fully_mapped_ns['E2'],request[0]['ns'])
|
||||
transition=validate_applied_standard_transition(request[0]['ns'],replies[0],log('compositor'),
|
||||
owner2['id'],clips['E2']['root']['tree'],ws['id'],'HEADLESS-1',ws['layout'],captures[-2:],
|
||||
log('E2'),snap,chains['E2']['protocol']['ids'],[applied_transition_start,transition_end],source_bindings())
|
||||
stdrgb=popup_pixels(stdleft,stdright,stdg,False,baseline)
|
||||
save(lab,'standard-return',dict(status='PASS',transition=transition,geometry=stdg,
|
||||
mapped_before_and_after=True,no_prior_popup_done=True))
|
||||
verify_owner_occlusion(stdleft,stdright,stdcontent,stdg,False)
|
||||
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',row['pointer_protocol']['E2']);mapped(log('E2'))
|
||||
# Re-examine every trace through the B input, including any late clip reactivation.
|
||||
transition=validate_applied_standard_transition(request[0]['ns'],replies[0],log('compositor'),
|
||||
owner2['id'],clips['E2']['root']['tree'],ws['id'],'HEADLESS-1',ws['layout'],captures[-2:],
|
||||
log('E2'),snap,chains['E2']['protocol']['ids'],[applied_transition_start,time.monotonic_ns()],source_bindings())
|
||||
save(lab,'standard-transition-through-B-input',transition)
|
||||
before_close=log('E2');request_close_ns=time.monotonic_ns()
|
||||
event('client-close-request',name='E2',command='close_b')
|
||||
E2.stdin.write('close_b\n');E2.stdin.flush();wait(lambda:'COMMAND close_b DONE' in log('E2'))
|
||||
close_proof=explicit_child_close(before_close,log('E2'),chains['E2']['protocol']['ids'],request_close_ns,time.monotonic_ns())
|
||||
save(lab,'explicit-child-close',close_proof)
|
||||
stage='E2-standard-A';Aleft=capture('standard-A-H1','HEADLESS-1');Aimage=capture('standard-A-H2','HEADLESS-2');assert paint(Aimage,'POPUP_B')['count']==0
|
||||
from pixels import color_geometry
|
||||
for im,ox in ((Aleft,-800),(Aimage,0)):
|
||||
require(paint(im,'POPUP_B')['count']==0,'no-child-residual-both-outputs')
|
||||
color_geometry(im,'POPUP_A',Rect(Space.GLOBAL,*stdg['A'],stdcontent.provenance),
|
||||
Output(str(ox),Rect(Space.GLOBAL,ox,0,800,600,stdcontent.provenance),1.,'normal'),None)
|
||||
xy=point(Aimage,'POPUP_A',0);before_A=log('E2');rowA=click(stage,'E2',xy);Apair=delivery(rowA,'E2','POPUP_A')
|
||||
Aproof=a_survival(Aimage,before_A,log('E2'),chains['E2']['protocol']['ids'],rowA['events']['E2'],xy,stdg['A'])
|
||||
Acoords=correlate_pointer('E2',xy,stdg['A'],'POPUP_A',rowA['pointer_protocol']['E2'])
|
||||
save(lab,'standard-popup-input',dict(status='PASS',B=Bpair,A=Apair,B_coordinates=Bcoords,A_coordinates=Acoords,explicit_B_close=close_proof,A_survived=Aproof))
|
||||
stage='E2-cleanup';before_owner_close=log('E2')
|
||||
event('client-close-request',name='E2',command='close_owner')
|
||||
close_client(E2,'E2','close_owner')
|
||||
ownerclose=normal_owner_close(before_owner_close,log('E2'),chains['E2']['protocol']['ids'],E2.returncode)
|
||||
save(lab,'normal-owner-close',ownerclose);time.sleep(.35)
|
||||
final=capture('final-H2','HEADLESS-2');save(lab,'final-restoration',final_restoration(baseline,final))
|
||||
assert not any(paint(final,n)['count'] for n in ['POPUP_A','POPUP_B'])
|
||||
finalleft=capture('final-H1','HEADLESS-1')
|
||||
require(not any(paint(finalleft,n)['count'] for n in ('POPUP_A','POPUP_B')),'final-H1-popup-residual')
|
||||
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():
|
||||
# WHY: record transport evidence before wait can raise or time out.
|
||||
def record_terminal(stdout,stderr):
|
||||
(run/'terminal-exit.stdout').write_text(stdout)
|
||||
(run/'terminal-exit.stderr').write_text(stderr)
|
||||
exit_data=terminal_exchange(subprocess.run,comp.wait,
|
||||
['swaymsg','-s',str(sock),'exit'],env,measures,
|
||||
lambda:log('compositor'),
|
||||
lambda:any(re.search(r'wl_display[^\n]*\.error\(|protocol error',log(n),re.I)
|
||||
for n,_ in processes if n not in ['compositor','pointer']),
|
||||
expired.is_set,record_terminal)
|
||||
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)
|
||||
exit_data['scientific_cleanup_pass']=bool(exit_data['scientific_cleanup_pass'] and not rescue and all(row['exit']==0 for row in codes))
|
||||
exit_data['all_owned_reaped']=all(not Path('/proc',str(row['pid'])).exists() for row in 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)
|
||||
70
tooling/probes/step20c-static-e2-completion/support.py
Normal file
70
tooling/probes/step20c-static-e2-completion/support.py
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
"""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(__file__).resolve().parents[3]
|
||||
# No immutable compositor is built/copied and no runtime budget is initialized.
|
||||
|
||||
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 reserve(lab, *kinds):
|
||||
from ledger import reserve as debit
|
||||
return debit(Path(lab)/'results/budget.json', *kinds)
|
||||
|
||||
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)
|
||||
220
tooling/probes/step20c-static-e2-completion/tests_offline.py
Normal file
220
tooling/probes/step20c-static-e2-completion/tests_offline.py
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Independent single mutations and executable receipts; never run compositor."""
|
||||
import ast
|
||||
import copy
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import tempfile
|
||||
from types import SimpleNamespace
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
import sys
|
||||
from PIL import Image,ImageDraw
|
||||
from model import Invalid,require
|
||||
from transition import validate_applied_standard_transition,transition_window_start
|
||||
from replay import historical_inputs,replay
|
||||
from predicates import COLORS,point,terminal_exchange,Defect
|
||||
from lifecycle import *
|
||||
from support import sha
|
||||
from ledger import initial_for_offline_test,reserve,valid,LIMITS
|
||||
from reusable_tests import Case as ReusableGeometry
|
||||
from handoff_tests import Handoff
|
||||
|
||||
HERE=Path(__file__).resolve().parent
|
||||
ACTIVE=None;OBSERVED={}
|
||||
|
||||
def trace(frame,event,arg):
|
||||
if ACTIVE and event=='return' and frame.f_code.co_name=='require' and not frame.f_locals.get('value'):
|
||||
OBSERVED.setdefault(ACTIVE,set()).add(frame.f_locals['contract'])
|
||||
if ACTIVE and event=='exception' and arg[0] is AssertionError and Path(frame.f_code.co_filename).parent==HERE:
|
||||
OBSERVED.setdefault(ACTIVE,set()).add('AssertionError:'+frame.f_code.co_name)
|
||||
return trace
|
||||
|
||||
class Transition(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):cls.original=historical_inputs()
|
||||
def setUp(self):self.data=copy.deepcopy(self.original)
|
||||
def reject(self,expected):
|
||||
with self.assertRaisesRegex(Invalid,'^'+re.escape(expected)+'$'):
|
||||
validate_applied_standard_transition(**self.data)
|
||||
def replace(self,old,new):
|
||||
self.assertIn(old,self.data['trace']);self.data['trace']=self.data['trace'].replace(old,new)
|
||||
def test_real_raw_replay(self):self.assertEqual(replay()['status'],'PASS')
|
||||
def test_retained_clip(self):
|
||||
self.replace('layout=1 scroll=-1 tile_scene=0,0 clip_valid=0','layout=1 scroll=-1 tile_scene=0,0 clip_valid=1');self.reject('standard-clips-absent')
|
||||
def test_no_standard_epoch(self):
|
||||
self.replace('layout=1 scroll=-1 tile_scene=0,0 clip_valid=0 clip=0,0,0,0','layout=5 scroll=-1 tile_scene=0,0 clip_valid=1 clip=0,0,800,600')
|
||||
self.data['trace']=re.sub(r'(PI_POPUP[^\n]*?)clip_valid=0 clip=0,0,0,0',r'\1clip_valid=1 clip=-402,-25,800,600',self.data['trace']);self.reject('two-standard-observations-required')
|
||||
def test_wrong_layout(self):
|
||||
self.replace('workspace=6 layout=1','workspace=6 layout=2');self.reject('unexpected-applied-layout')
|
||||
def test_wrong_owner(self):self.data['owner_id']=8;self.reject('targeted-accepted-splith')
|
||||
def test_wrong_root(self):self.data['root_id']='0x123';self.reject('unique-enabled-root')
|
||||
def test_wrong_output(self):self.data['output']='HEADLESS-2';self.reject('exact-workspace-output')
|
||||
def test_reversed_times(self):
|
||||
self.replace('ns=181923236944243','ns=181923034904617');self.reject('trace-monotonic-order')
|
||||
def test_standard_before_application(self):
|
||||
self.data['request_ns']=[181923236944244,181923236944244];self.data['ipc_response']['ns']=181923236944245
|
||||
self.reject('standard-before-request-application')
|
||||
def test_clip_reactivation(self):
|
||||
t=self.data['trace'];lines=t.splitlines();start=next(i for i,l in enumerate(lines) if 'PI_OUTPUT_COMMIT output=HEADLESS-1 commit_seq=56 ' in l)
|
||||
for i in range(start,len(lines)):
|
||||
if 'PI_SCENE output=HEADLESS-1 ' in lines[i]:lines[i]=lines[i].replace('layout=1','layout=5').replace('clip_valid=0 clip=0,0,0,0','clip_valid=1 clip=0,0,800,600')
|
||||
self.data['trace']='\n'.join(lines);self.reject('clip-reactivated-after-standard')
|
||||
def test_missing_root_traces(self):
|
||||
self.data['trace']='\n'.join(l for l in self.data['trace'].splitlines() if 'PI_POPUP output=HEADLESS-1 ' not in l);self.reject('unique-enabled-root')
|
||||
def test_multiple_roots(self):
|
||||
line=next(l for l in self.data['trace'].splitlines() if 'ns=181923236944243 ' in l)
|
||||
self.replace(line,line+'\n'+line.replace('181923236944243','181923236944244').replace('tree=0x56292fbcc170','tree=0x999').replace('local_index=1','local_index=2'));self.reject('unique-enabled-root')
|
||||
def test_wrong_capture_epoch(self):
|
||||
self.data['captures'][0]['start_ns']=181923035203193;self.reject('capture-applied-standard-epoch')
|
||||
def test_different_workspace(self):
|
||||
self.replace('workspace=6 layout=1','workspace=99 layout=1');self.reject('applied-current-scene-workspace')
|
||||
def test_popup_disappears_early(self):
|
||||
self.data['client_protocol']+='\nPOPUP_B DESTROY role=17 xdg=15 wl=13\n'
|
||||
with self.assertRaisesRegex(Invalid,'malformed-transition-evidence'):validate_applied_standard_transition(**self.data)
|
||||
def test_ipc_failure(self):self.data['ipc_response']['exit']=1;self.reject('targeted-accepted-splith')
|
||||
def test_no_root_clip_removal(self):
|
||||
self.replace('tree=0x56292fbcc170 enabled=1 clip_valid=0 clip=0,0,0,0','tree=0x56292fbcc170 enabled=1 clip_valid=1 clip=-482,-25,800,600');self.reject('standard-clips-absent')
|
||||
def test_pending_presented_applied(self):
|
||||
self.replace('scene_valid=1 scene=-400,0','scene_valid=0 scene=-400,0');self.reject('exact-applied-owner')
|
||||
def test_missing_second_observation(self):
|
||||
lines=self.data['trace'].splitlines();start=next(i for i,l in enumerate(lines) if 'PI_OUTPUT_COMMIT output=HEADLESS-1 commit_seq=56 ' in l)
|
||||
end=next((i for i in range(start+1,len(lines)) if 'PI_OUTPUT_COMMIT ' in lines[i]),len(lines))
|
||||
self.data['trace']='\n'.join(lines[:start]+lines[end:]);self.reject('two-standard-observations-required')
|
||||
def test_historical_request_omission_interval(self):
|
||||
proof=validate_applied_standard_transition(**self.data)
|
||||
self.assertEqual(proof['request_bounds'],[181922658327071,181923034516066])
|
||||
self.assertLess(proof['request_bounds'][1],181923034904618)
|
||||
def test_request_ambiguity(self):self.data['request_ns']=[181923035203193,181923035203194];self.reject('request-reply-window')
|
||||
def test_empty_captures(self):self.data['captures']=[];self.reject('two-standard-captures')
|
||||
def test_changed_xdg_identity(self):self.data['expected_ids']['POPUP_A'][1]=999;self.reject('exact-xdg-object-identities')
|
||||
def test_runtime_pre_root_epochs_window(self):
|
||||
# Real earlier callbacks contain no root before A maps. The transition
|
||||
# starts at a complete observed callback after A/B fully mapped.
|
||||
start=transition_window_start(self.data['trace'],'HEADLESS-1',self.data['root_id'],10,6,
|
||||
181922000000000,self.data['request_ns'][1])
|
||||
self.assertLess(start,self.data['request_ns'][0])
|
||||
self.data['window'][0]=start
|
||||
self.assertEqual(validate_applied_standard_transition(**self.data)['status'],'PASS')
|
||||
def test_runtime_mapped_window_missing_commit(self):
|
||||
with self.assertRaisesRegex(Invalid,'fully-mapped-mosaic-commit-before-request'):
|
||||
transition_window_start(self.data['trace'],'HEADLESS-1',self.data['root_id'],10,6,
|
||||
self.data['ipc_response']['ns'],self.data['request_ns'][1])
|
||||
def test_legitimate_missing_protocol_map_invalid(self):
|
||||
self.data['client_protocol']=self.data['client_protocol'].replace('POPUP_B MAP ','POPUP_B OMITTED ')
|
||||
with self.assertRaisesRegex(Invalid,'malformed-transition-evidence'):validate_applied_standard_transition(**self.data)
|
||||
def test_missing_reply_timestamp_invalid(self):
|
||||
del self.data['ipc_response']['ns']
|
||||
with self.assertRaisesRegex(Invalid,'malformed-transition-evidence'):validate_applied_standard_transition(**self.data)
|
||||
|
||||
class Lifecycle(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.data=historical_inputs();self.before=self.data['client_protocol'];self.ids=self.data['expected_ids']
|
||||
self.b=self.ids['POPUP_B'];self.a=self.ids['POPUP_A'];self.top=self.ids['TOPLEVEL']
|
||||
b=self.b
|
||||
self.after=self.before+f'\nPOPUP_B DESTROY role={b[2]} xdg={b[1]} wl={b[0]}\nxdg_popup#{b[2]}.destroy()\nxdg_surface#{b[1]}.destroy()\nwl_surface#{b[0]}.destroy()\nCOMMAND close_b DONE\n'
|
||||
self.image=Image.new('RGB',(800,600),(48,48,56));ImageDraw.Draw(self.image).rectangle((0,105,51,274),fill=COLORS['POPUP_A'])
|
||||
self.buttons=['POINTER_BUTTON target=POPUP_A serial=110 button=273 state=1','POINTER_BUTTON target=POPUP_A serial=111 button=273 state=0']
|
||||
def test_explicit_close(self):self.assertEqual(explicit_child_close(self.before,self.after,self.ids,1,2)['status'],'PASS')
|
||||
def test_popup_done_distinct(self):
|
||||
with self.assertRaisesRegex(Invalid,'close-must-not-be-dismissal'):explicit_child_close(self.before,self.after+'POPUP_B POPUP_DONE role=17\n',self.ids,1,2)
|
||||
def test_missing_close_command(self):
|
||||
with self.assertRaisesRegex(Invalid,'explicit-child-destroy-DONE'):explicit_child_close(self.before,self.after.replace('COMMAND close_b DONE',''),self.ids,1,2)
|
||||
def test_destroy_wrong_object(self):
|
||||
with self.assertRaisesRegex(Invalid,'child-protocol-destroy'):explicit_child_close(self.before,self.after.replace(f'xdg_popup#{self.b[2]}.destroy()','xdg_popup#999.destroy()'),self.ids,1,2)
|
||||
def test_A_destroyed(self):
|
||||
with self.assertRaisesRegex(Invalid,'same-A-remains-mapped'):explicit_child_close(self.before,self.after+'POPUP_A DESTROY role=14\n',self.ids,1,2)
|
||||
def test_A_input_and_recomputed_point(self):
|
||||
xy=point(self.image,'POPUP_A',0);self.assertEqual(a_survival(self.image,self.after,self.after,self.ids,self.buttons,xy,[-298,105,350,170])['status'],'PASS')
|
||||
def test_B_residual(self):
|
||||
self.image.putpixel((1,1),COLORS['POPUP_B'])
|
||||
with self.assertRaisesRegex(Invalid,'child-zero-residual-A-painted'):a_survival(self.image,self.after,self.after,self.ids,self.buttons,[10,120],[-298,105,350,170])
|
||||
def test_wrong_A_button(self):
|
||||
with self.assertRaises(Defect):a_survival(self.image,self.after,self.after,self.ids,[s.replace('POPUP_A','POPUP_B') for s in self.buttons],[10,120],[-298,105,350,170])
|
||||
def test_final_exact(self):
|
||||
im=Image.new('RGB',(800,600),(48,48,56));self.assertEqual(final_restoration(im,im.copy())['status'],'PASS')
|
||||
def test_final_no_tolerance(self):
|
||||
im=Image.new('RGB',(800,600),(48,48,56));b=im.copy();b.putpixel((799,599),(49,48,56))
|
||||
with self.assertRaisesRegex(Invalid,'whole-H2-final-exact'):final_restoration(im,b)
|
||||
def owner_tail(self):
|
||||
a,t=self.a,self.top
|
||||
return f'COMMAND close_owner\nPOPUP_A DESTROY role={a[2]} xdg={a[1]} wl={a[0]}\nxdg_popup#{a[2]}.destroy()\nxdg_surface#{a[1]}.destroy()\nwl_surface#{a[0]}.destroy()\nTOPLEVEL DESTROY\nxdg_toplevel#{t[2]}.destroy()\nxdg_surface#{t[1]}.destroy()\nwl_surface#{t[0]}.destroy()\n'
|
||||
def test_normal_owner_close(self):self.assertEqual(normal_owner_close(self.after,self.after+self.owner_tail(),self.ids,0)['status'],'PASS')
|
||||
def test_owner_close_without_command(self):
|
||||
with self.assertRaisesRegex(Invalid,'normal-owner-close-command'):normal_owner_close(self.after,self.after+self.owner_tail().replace('COMMAND close_owner',''),self.ids,0)
|
||||
def test_open_left_pair_real_serial(self):
|
||||
self.assertEqual(opening_pair(['POINTER_BUTTON target=TOPLEVEL serial=98 button=272 state=1','POINTER_BUTTON target=TOPLEVEL serial=99 button=272 state=0'],'TOPLEVEL',98)[0][1],98)
|
||||
def test_open_wrong_serial(self):
|
||||
with self.assertRaisesRegex(Invalid,'real-left-opening-pair'):opening_pair(['POINTER_BUTTON target=TOPLEVEL serial=98 button=272 state=1','POINTER_BUTTON target=TOPLEVEL serial=99 button=272 state=0'],'TOPLEVEL',97)
|
||||
|
||||
class TerminalLedger(unittest.TestCase):
|
||||
def test_actual_terminal_ipc_one(self):
|
||||
captured=[]
|
||||
r=terminal_exchange(lambda *a,**k:SimpleNamespace(returncode=1,stdout='raw out',stderr='raw err'),lambda **k:0,[],{},True,
|
||||
lambda:"Handling command 'exit'\nShutting down sway",lambda:False,lambda:False,lambda *a:captured.append(a))
|
||||
self.assertTrue(r['scientific_cleanup_pass']);self.assertEqual(captured,[('raw out','raw err')]);self.assertEqual(r['ipc_exit'],1)
|
||||
def test_terminal_wait_failure_retains_codes(self):
|
||||
captured=[]
|
||||
def fail(**k):raise TimeoutError('raw wait')
|
||||
r=terminal_exchange(lambda *a,**k:SimpleNamespace(returncode=1,stdout='out',stderr='err'),fail,[],{},True,lambda:'',lambda:False,lambda:False,lambda *a:captured.append(a))
|
||||
self.assertEqual(r['ipc_exit'],1);self.assertEqual(r['stdout'],'out');self.assertFalse(r['scientific_cleanup_pass'])
|
||||
def test_exact_budget_exhaustion(self):
|
||||
self.assertEqual(LIMITS,dict(run=1,chain=1,grab=2,click=4,capture=12,camera=1,transition=1,move=16))
|
||||
with tempfile.TemporaryDirectory(prefix='geometry-ledger-test-') as temp:
|
||||
p=Path(temp)/'budget.json';initial_for_offline_test(p)
|
||||
for kind,count in LIMITS.items():reserve(p,*([kind]*count))
|
||||
before=p.read_bytes()
|
||||
with self.assertRaisesRegex(Invalid,'ledger-exhausted'):reserve(p,'run')
|
||||
self.assertEqual(p.read_bytes(),before)
|
||||
def test_runner_single_chain_and_source_audit(self):
|
||||
from audit import audit
|
||||
result=audit();self.assertEqual(result['status'],'PASS');self.assertTrue(result['records'])
|
||||
|
||||
class Receipt(unittest.TestCase):
|
||||
def test_empty_PASS_rejected(self):
|
||||
from gate import validate_test_receipts
|
||||
with self.assertRaises(Invalid):validate_test_receipts({'status':'PASS','tests':[]})
|
||||
def test_skipped_rejected(self):
|
||||
from gate import validate_test_receipts
|
||||
with self.assertRaises(Invalid):validate_test_receipts({'status':'PASS','tests':[{'id':'test','status':'SKIP'}]})
|
||||
def test_incomplete_temporal_PASS_records_rejected(self):
|
||||
from gate import validate_test_receipts
|
||||
receipt=dict(status='PASS',tests=[dict(id=n,status='PASS',rejected_contracts=[]) for n in expected_tests()],
|
||||
executed=len(expected_tests()),failures=0,errors=0,skips=0,sources={p.name:sha(p) for p in HERE.glob('*.py')})
|
||||
with self.assertRaisesRegex(Invalid,'executed-negative-rejection'):
|
||||
validate_test_receipts(receipt)
|
||||
|
||||
class Result(unittest.TextTestResult):
|
||||
def startTest(self,test):
|
||||
global ACTIVE
|
||||
ACTIVE=test.id();super().startTest(test)
|
||||
def stopTest(self,test):
|
||||
global ACTIVE
|
||||
ACTIVE=None;super().stopTest(test)
|
||||
def addSuccess(self,test):
|
||||
super().addSuccess(test);self.receipts.append(dict(id=test.id(),status='PASS',rejected_contracts=sorted(OBSERVED.get(test.id(),[]))))
|
||||
def __init__(self,*a,**k):super().__init__(*a,**k);self.receipts=[]
|
||||
|
||||
|
||||
def expected_tests():
|
||||
suite=unittest.defaultTestLoader.loadTestsFromModule(sys.modules[__name__])
|
||||
def flatten(s):
|
||||
for item in s:
|
||||
if isinstance(item,unittest.TestSuite):yield from flatten(item)
|
||||
else:yield item
|
||||
return {test.id().replace('__main__.','tests_offline.') for test in flatten(suite)}
|
||||
|
||||
if __name__=='__main__':
|
||||
import argparse
|
||||
p=argparse.ArgumentParser();p.add_argument('--output',type=Path);a=p.parse_args()
|
||||
sys.settrace(trace)
|
||||
result=unittest.TextTestRunner(verbosity=2,resultclass=Result).run(unittest.defaultTestLoader.loadTestsFromModule(sys.modules[__name__]))
|
||||
sys.settrace(None)
|
||||
# Module identity is normalized so direct CLI and imported inventory agree.
|
||||
receipts=[{**r,'id':r['id'].replace('__main__.','tests_offline.')} for r in result.receipts]
|
||||
if a.output:
|
||||
with a.output.open('x') as out:json.dump(dict(status='PASS' if result.wasSuccessful() else 'FAIL',tests=receipts,
|
||||
executed=result.testsRun,failures=len(result.failures),errors=len(result.errors),skips=len(result.skipped),
|
||||
sources={p.name:sha(p) for p in HERE.glob('*.py')}),out,indent=2);out.write('\n')
|
||||
raise SystemExit(0 if result.wasSuccessful() else 1)
|
||||
173
tooling/probes/step20c-static-e2-completion/transition.py
Normal file
173
tooling/probes/step20c-static-e2-completion/transition.py
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
"""Observed applied transition; EXPERIMENTAL, no atomic generation claim.
|
||||
|
||||
WHY: an accepted layout request can precede the transaction applying it.
|
||||
CONTRACT: keep the old state, then require two coherent standard observations.
|
||||
INVARIANT: no first-row discard, last-row shortcut or pending-as-applied proof.
|
||||
PI_POPUP has no owner field: uniqueness + exact live XDG hierarchy + IPC ancestry
|
||||
provide bounded correlation only. The actual private output.c reads current.
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
from model import require
|
||||
from predicates import protocol
|
||||
from ipc_predicates import ancestry,nodes
|
||||
|
||||
SOURCE_FILES = {
|
||||
'output.c': '/home/fy59/swayfx-mosaic-static-runtime-hm5kbdkt/mosaic-source/sway/desktop/output.c',
|
||||
'transaction.c': '/home/fy59/swayfx-mosaic-static-runtime-hm5kbdkt/mosaic-source/sway/desktop/transaction.c',
|
||||
'xdg_shell.c': '/home/fy59/swayfx-mosaic-static-runtime-hm5kbdkt/mosaic-source/sway/desktop/xdg_shell.c',
|
||||
}
|
||||
|
||||
def fields(line):
|
||||
return dict(re.findall(r'(\w+)=([^\s]+)', line))
|
||||
|
||||
|
||||
def observed_epochs(text, output, start_ns, end_ns):
|
||||
"""Group the exact commit callback's ordered SCENE/OWNER/POPUP records.
|
||||
|
||||
Other output callbacks may contain the same global popup tree. We examine
|
||||
all callbacks for the selected output and never filter by desired owner.
|
||||
"""
|
||||
epochs=[]; active=None; last=0
|
||||
for line in text.splitlines():
|
||||
if not re.search(r'\bPI_(?:OUTPUT_COMMIT|SCENE|OWNER|POPUP)\b',line):continue
|
||||
f=fields(line)
|
||||
if f.get('output')!=output:continue
|
||||
ns=int(f['ns'])
|
||||
if not start_ns<=ns<=end_ns:continue
|
||||
require(ns>last,'trace-monotonic-order');last=ns
|
||||
if 'PI_OUTPUT_COMMIT ' in line:
|
||||
active={'commit':f,'scene':None,'owners':[],'roots':[],'raw':[line]};epochs.append(active)
|
||||
else:
|
||||
require(active is not None,'trace-without-commit')
|
||||
active['raw'].append(line)
|
||||
if 'PI_SCENE ' in line:
|
||||
require(active['scene'] is None,'duplicate-scene');active['scene']=f
|
||||
elif 'PI_OWNER ' in line:active['owners'].append(f)
|
||||
else:active['roots'].append(f)
|
||||
require(bool(epochs),'applied-observations-missing')
|
||||
return epochs
|
||||
|
||||
|
||||
def transition_window_start(trace,output,root_id,owner_id,workspace_id,mapped_after_ns,request_ns):
|
||||
"""Select an explicitly coherent mapped A/B Mosaic callback before request.
|
||||
|
||||
Earlier no-popup callbacks belong to opening evidence, not the transition.
|
||||
We require a complete commit header + current scene + owner + unique root;
|
||||
no unconditional trace-row deletion is allowed.
|
||||
"""
|
||||
epochs=observed_epochs(trace,output,0,request_ns)
|
||||
candidates=[]
|
||||
for epoch in epochs:
|
||||
c,s,roots=epoch['commit'],epoch['scene'],epoch['roots']
|
||||
if int(c['ns'])<mapped_after_ns:continue
|
||||
owners=[o for o in epoch['owners'] if o.get('id')==str(owner_id)]
|
||||
if s and s.get('workspace')==str(workspace_id) and s.get('layout')=='5' and s.get('clip_valid')=='1' and \
|
||||
len(owners)==1 and owners[0].get('scene_valid')=='1' and len(roots)==1 and \
|
||||
roots[0].get('tree')==root_id and roots[0].get('enabled')=='1' and roots[0].get('clip_valid')=='1':
|
||||
candidates.append(int(c['ns']))
|
||||
require(bool(candidates),'fully-mapped-mosaic-commit-before-request')
|
||||
return min(candidates)
|
||||
|
||||
|
||||
def _validate_applied_standard_transition(request_ns, ipc_response, trace,
|
||||
owner_id, root_id, workspace_id, output, applied_layout, captures,
|
||||
client_protocol, snapshot, expected_ids, window, source_contract):
|
||||
"""Return observed PASS or raise Invalid (INCONCLUSIVE).
|
||||
|
||||
request_ns is an exact int in runtime. Historical replay explicitly uses a
|
||||
lower/upper interval from raw event + first trace after Handling command;
|
||||
it cannot establish the omitted syscall-start timestamp. Captures contain
|
||||
real start_ns/end_ns and must begin after first coherent applied standard.
|
||||
"""
|
||||
require(source_contract==source_bindings(),'actual-private-source-contract')
|
||||
start,end=window
|
||||
bounds=[request_ns,request_ns] if type(request_ns) is int else request_ns
|
||||
require(isinstance(bounds,list) and len(bounds)==2 and all(type(v) is int for v in bounds)
|
||||
and start<=bounds[0]<=bounds[1]<ipc_response['ns']<end,'request-reply-window')
|
||||
require(ipc_response.get('args')==[f'[con_id={owner_id}] layout splith'] and
|
||||
ipc_response.get('exit')==0 and json.loads(ipc_response.get('stdout','null'))==[{'success':True}],
|
||||
'targeted-accepted-splith')
|
||||
require(applied_layout=='splith','standard-layout-required')
|
||||
path=ancestry(snapshot['tree'],owner_id)
|
||||
require(bool(path),'owner-ipc-absent')
|
||||
ws=[n for n in path if n.get('type')=='workspace'];out=[n for n in path if n.get('type')=='output']
|
||||
require(len(ws)==len(out)==1 and ws[0]['id']==workspace_id and ws[0]['layout']=='splith'
|
||||
and ws[0]['output']==output and out[0]['name']==output,'exact-workspace-output')
|
||||
require(path[-1].get('visible') is True and path[-1].get('app_id')=='org.mosaic.NestedGrabProbe',
|
||||
'mapped-owner-ipc')
|
||||
live=[n for n in nodes(snapshot['tree']) if n.get('app_id')=='org.mosaic.NestedGrabProbe' and n.get('visible')]
|
||||
require(len(live)==1 and live[0]['id']==owner_id,'one-live-popup-client-owner')
|
||||
prot=protocol(client_protocol)
|
||||
require(prot['ids']==expected_ids,'exact-xdg-object-identities')
|
||||
epochs=observed_epochs(trace,output,start,end)
|
||||
states=[];standard=[]
|
||||
for epoch in epochs:
|
||||
c,s,roots=epoch['commit'],epoch['scene'],epoch['roots']
|
||||
require(c.get('has_buffer')=='1' and s is not None and s.get('workspace')==str(workspace_id),
|
||||
'applied-current-scene-workspace')
|
||||
owners=[o for o in epoch['owners'] if o.get('id')==str(owner_id)]
|
||||
require(len(owners)==1 and owners[0].get('scene_valid')=='1','exact-applied-owner')
|
||||
require(len(roots)==1 and roots[0].get('tree')==root_id and roots[0].get('local_index')=='1'
|
||||
and roots[0].get('enabled')=='1','unique-enabled-root')
|
||||
o,r=owners[0],roots[0]
|
||||
t=int(r['ns']);layout=int(s['layout'])
|
||||
require(int(c['ns'])<int(s['ns'])<int(o['ns'])<t,'applied-trace-order')
|
||||
if layout==5:
|
||||
require(not standard,'clip-reactivated-after-standard')
|
||||
require(s['clip_valid']==r['clip_valid']=='1','old-mosaic-clips-required')
|
||||
# Signed local root box must explain the same global viewport as
|
||||
# the actual tiling tree. IPC supplies unchanged content offsets.
|
||||
win=path[-1]['window_rect'];deco=path[-1]['deco_rect']
|
||||
ox,oy=map(int,o['scene'].split(','));rx,ry,rw,rh=map(int,r['clip'].split(','))
|
||||
require([ox+win['x']+rx,oy+deco['height']+win['y']+ry,rw,rh]==[-800,0,800,600], 'old-root-global-viewport')
|
||||
tx,ty=map(int,s['tile_scene'].split(','));cx,cy,cw,ch=map(int,s['clip'].split(','))
|
||||
require([-800+tx+cx,ty+cy,cw,ch]==[-800,0,800,600],'old-tile-global-viewport')
|
||||
state='old-applied-Mosaic'
|
||||
elif layout==1:
|
||||
require(t>bounds[1],'standard-before-request-application')
|
||||
require(s['clip_valid']==r['clip_valid']=='0' and s['clip']==r['clip']=='0,0,0,0'
|
||||
and s['tile_scene']=='0,0','standard-clips-absent')
|
||||
state='applied-standard';standard.append(epoch)
|
||||
else:require(False,'unexpected-applied-layout')
|
||||
states.append({'ns':t,'state':state,'owner':o,'scene':s,'root':r,'commit':c})
|
||||
require(states[0]['state']=='old-applied-Mosaic' and states[0]['ns']<=ipc_response['ns'],
|
||||
'old-applied-mosaic-required')
|
||||
require(len(standard)>=2,'two-standard-observations-required')
|
||||
# Coherent stabilization retains actual owner geometry and root identity.
|
||||
stable=[{k:e['owners'][next(i for i,o in enumerate(e['owners']) if o['id']==str(owner_id))][k]
|
||||
for k in ('scene','applied_size','current')} for e in standard]
|
||||
require(all(v==stable[0] for v in stable),'standard-geometry-not-stabilized')
|
||||
first_ns=int(standard[0]['roots'][0]['ns'])
|
||||
require(isinstance(captures,list) and len(captures)==2 and
|
||||
{c.get('output') for c in captures}=={'HEADLESS-1','HEADLESS-2'},'two-standard-captures')
|
||||
for capture in captures:
|
||||
require(capture.get('exit')==0 and type(capture.get('start_ns')) is int and
|
||||
first_ns<capture['start_ns']<=capture['end_ns']<=end and
|
||||
capture.get('sha256') and capture.get('file'),'capture-applied-standard-epoch')
|
||||
require(snapshot['ns']>first_ns and snapshot['ns']<=end,'snapshot-applied-epoch')
|
||||
return dict(status='PASS',states=states,request_bounds=bounds,ipc_reply_ns=ipc_response['ns'],
|
||||
first_standard_ns=first_ns,stabilized_ns=int(standard[-1]['roots'][0]['ns']),
|
||||
owner=owner_id,root=root_id,workspace=workspace_id,output=output,ids=prot['ids'],
|
||||
captures=captures,source_contract=source_contract,
|
||||
limitation='bounded unique-root correlation; independent observations, not atomic transaction generation')
|
||||
|
||||
|
||||
EXPECTED_SOURCE = {'output.c':'b517c07655c6796a74fba731ab42d0f96ef53e104f6a9a4c0af256f26a25ded1',
|
||||
'transaction.c':'b04483d05100f57867560289d2a7db346e3e8925cec9fa4be35b23b1f6928165',
|
||||
'xdg_shell.c':'2a57ba553cacd82220bf16459debb167a3db5889c172af28d9067951f2df3610'}
|
||||
|
||||
def source_bindings():
|
||||
from support import sha
|
||||
observed={name:sha(path) for name,path in SOURCE_FILES.items()}
|
||||
require(observed==EXPECTED_SOURCE,'private-source-bytes-changed')
|
||||
return observed
|
||||
|
||||
|
||||
def validate_applied_standard_transition(*args,**kwargs):
|
||||
"""Malformed/omitted evidence is Invalid/INCONCLUSIVE, never accepted."""
|
||||
from model import Invalid
|
||||
try:return _validate_applied_standard_transition(*args,**kwargs)
|
||||
except Invalid:raise
|
||||
except (KeyError,IndexError,TypeError,ValueError,AttributeError,AssertionError) as exc:
|
||||
raise Invalid('malformed-transition-evidence:'+str(exc)) from exc
|
||||
Loading…
Reference in a new issue