test(mosaic): preserve reviewed CORE-C V2 headless fixtures
This commit is contained in:
parent
1053ab2a47
commit
c0a822a079
33 changed files with 5077 additions and 0 deletions
24
tooling/probes/step20c-corec-v2/BUILD_RECIPE.md
Normal file
24
tooling/probes/step20c-corec-v2/BUILD_RECIPE.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# Private CORE C V2 build recipe — EXPERIMENTAL
|
||||
|
||||
Run only after exact source/C independent review of the assembled source. This recipe creates no production install. Source reconstruction is exact; binary bit-for-bit reproducibility across compilers/timestamps is NOT PROVEN. Require >=20GiB filesystem free, new LAB solely below /var/tmp, redirected TMP/cache/data, no inherited display/session/loader routing, RLIMIT_CORE0, parallel jobs<=8. Use the assembled `scene` and `mosaic` directories, not a Mosaic checkout as pristine.
|
||||
|
||||
From an already owned LAB with sources assembled using the kit:
|
||||
|
||||
```sh
|
||||
meson setup "$LAB/build-scene" "$ASSEMBLED/scene" --prefix=/usr --wrap-mode=nodownload -Ddefault_library=static -Dexamples=false
|
||||
meson compile -C "$LAB/build-scene" -j8
|
||||
```
|
||||
|
||||
Supply PKG_CONFIG_PATH="$LAB/build-scene/meson-uninstalled" for Mosaic setup only. Its pkg-config executable wrapper must invoke the real pkg-config using argv and add --static to --libs requests so the static archive dependency closure is linked; do not modify system pkg-config or install SceneFX. Retain wrapper bytes/argv/raw statuses and resolved dependency paths. Use default compiler for both libraries; record version and headers. A shell wrapper equivalent for these controlled invocations is `exec /usr/bin/pkg-config --static "$@"`; the executed campaign used a Python execv wrapper which appended --static only for --libs. Check the actual Ninja link command for the private archive and native symbols; a successful setup alone is insufficient.
|
||||
|
||||
```sh
|
||||
PKG_CONFIG="$PRIVATE_PKG_CONFIG_WRAPPER" PKG_CONFIG_PATH="$LAB/build-scene/meson-uninstalled" meson setup "$LAB/build-mosaic" "$ASSEMBLED/mosaic" --prefix=/usr --bindir=bin --wrap-mode=nodownload -Dmosaic-package=true -Dsd-bus-provider=libsystemd -Dswaybar=false -Dswaynag=false -Dman-pages=disabled -Ddefault-wallpaper=false -Dc_args=-Wno-error=switch
|
||||
meson compile -C "$LAB/build-mosaic" -j8
|
||||
meson test -C "$LAB/build-mosaic" mosaic-model --print-errorlogs
|
||||
```
|
||||
|
||||
The private c_args compatibility exception keeps -Wswitch warnings visible. GCC16.2.1 plus the installed newer libinput enum SCROLL_CIRCULAR caused the original -Werror build failure at upstream ipc-json.c1175. scroll_method is initialized to unknown before that switch, so no source change was made. Original failure and final warning remain evidence. It is not a general permission to suppress compiler failures.
|
||||
|
||||
Verify readelf/nm/ldd-r, no SceneFX DT_NEEDED/RPATH/RUNPATH, exact wlroots SHA7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837 and actual factory binding from wlroots to the executable before runtime. Stage only Mosaic with `DESTDIR="$LAB/stage" meson install -C "$LAB/build-mosaic" --no-rebuild`; add the three exact pinned LICENSE texts and conservative INCORPORATED-NOTICES. Exactly binary0755 plus four notices0644 are allowed under the ticket paths. No symlinks, library or system package. LEGAL_NOT_APPROVED.
|
||||
|
||||
This recipe does not reserve a build/run or authorize bypassing budgets/publication/review. Every new build identity requires its own measured SHA/BuildID and reviewer receipt. Retained pristine is independently qualified, never built from these Mosaic sources. No compositor --version or live IPC is part of build validation.
|
||||
157
tooling/probes/step20c-corec-v2/graphics/README.md
Normal file
157
tooling/probes/step20c-corec-v2/graphics/README.md
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
# STEP20C CORE-C V2 graphics source fixture
|
||||
|
||||
EXPERIMENTAL. Source implementation and offline controls; graphics preflight is
|
||||
PENDING_INDEPENDENT_REVIEW. No compositor, Wayland client, IPC, screenshot or real
|
||||
ledger reservation has been executed while preparing this subtree. STEP20C NEXT;
|
||||
production multi-output NOT_YET_SUPPORTED; PRODUCTION_AUTHORIZED FALSE.
|
||||
|
||||
This is a new derivation, not recovery of old lab evidence. Surviving versioned
|
||||
core-recovery geometry, wire parser, input, lifecycle, saved validators, C clients
|
||||
and scenario code are reused with declared V2 changes. Original paths and hashes
|
||||
are in parent-provenance.json. Synthetic wire-fixture is solely a protocol parser
|
||||
control; it never qualifies a new binary. No historical replay or binary hash is
|
||||
an input. Protocol XML is source; generated protocol C/headers and binaries remain
|
||||
outside the kit. XML copyright/license headers are retained.
|
||||
|
||||
## Offline reproduction
|
||||
|
||||
Use a canonical, UID-owned direct `/var/tmp/<new-lab>` directory, its owned scratch,
|
||||
cache, data, pycache, results, logs, graphics-tests folders, and a wrapper redirecting
|
||||
TMPDIR/TMP/TEMP/XDG_CACHE_HOME/XDG_DATA_HOME/PYTHONPYCACHEPREFIX/CCACHE_DIR into LAB.
|
||||
Place this subtree as LAB/graphics-kit. Python3, Pillow, C17 compiler, pkg-config,
|
||||
wayland-client development files and wayland-scanner are prerequisites.
|
||||
|
||||
```sh
|
||||
LAB/run-env python LAB/graphics-kit/prepare.py --lab LAB
|
||||
LAB/run-env python LAB/graphics-kit/offline/test_graphics_v2.py
|
||||
LAB/run-env python LAB/graphics-kit/freeze.py --lab LAB
|
||||
```
|
||||
|
||||
Replace LAB with its absolute path. prepare.py creates graphics-tests/client-build
|
||||
exclusively, generates protocol bindings using system scanner and records versions,
|
||||
commands, raw statuses and source/output hashes in results/graphics-client-compile.json.
|
||||
It never executes a client. Tests create only graphics-tests/OFFLINE_TEST_ONLY
|
||||
ledger fixtures and mock transports for actual Session/run paths. SMX_TEST_LAB
|
||||
selects the lab when source is placed elsewhere inside a full kit. freeze.py writes
|
||||
results/graphics-source-freeze.json, never into source inputs. No old absolute lab
|
||||
or historical raw PNG is needed.
|
||||
|
||||
## Runtime admission contract — NOT RUN
|
||||
|
||||
Only the parent orchestrator initializes Ledger(lab) and spends real budget after
|
||||
independent build review, source-kit publication and per-scenario review. The
|
||||
single-case CLI accepts run ID and --lab/--manifest/--review/--publication. No all
|
||||
cases or retry mode exists. APIs: initialize/admit/action_debit/complete, no
|
||||
reset/refund. Reserved failures stay consumed. All action debits precede action.
|
||||
|
||||
Immutable manifest schema graphics-v2-frozen-inputs requires ticket, lab, author,
|
||||
source_root, files (ALL relative source SHA256), plans=plans.RUNS, limits=plans.LIMITS,
|
||||
identities, kit_manifest, kit_manifest_sha256. Exclude mutable captures, logs,
|
||||
ledgers, reviews, generated artifacts and self. The separately hashed full-kit
|
||||
manifest binds core recipes/patches/tests as well: ticket, source_root, files,
|
||||
source_commit. Addition/removal/modification invalidates admission.
|
||||
|
||||
Per-case review: decision ACCEPTED, independent true, reviewer different from
|
||||
author, manifest_sha256, authorized_runs. Identity: binary, binary_sha256, wlroots,
|
||||
wlroots_sha256, build_review ACCEPTED, source_qualification ACCEPTED; pristine also
|
||||
pristine_scene/pristine_scene_sha256. No old binary hash is assumed. Publication:
|
||||
ticket, decision VERIFIED, source_commit, kit_manifest_sha256, source_digest
|
||||
(gate.digest of graphics files map), origin_sha/github_sha both equal full-kit
|
||||
source_commit, offline_test_only false. Parent records direct git ls-remote
|
||||
observations and associates published commit to kit bytes; runner does no network.
|
||||
All bytes and input/review/publication hashes are rebound before each action.
|
||||
OFFLINE_TEST_ONLY cannot authorize a Session. Stable flock inode, no-follow,
|
||||
UID/link/type checks, atomic fsync replacements, token and monotonic duration
|
||||
protect against duplicate spend/stale use.
|
||||
|
||||
Environment allowlist clears inherited display/runtime/socket/loader/backend and
|
||||
activation. Only mode0700 /tmp/smx-unique holds sockets. Logs/PNG/source marker
|
||||
PNG/SHM/cache/temp stay in LAB. C clients check LAB/scratch lengths before mkstemp;
|
||||
launcher requires canonical owned scratch. Every swaymsg uses private explicit -s.
|
||||
Owned PID/groups, raw stdout/stderr, /proc/PID/exe SHA/maps, loaded wlroots exact
|
||||
7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837, SceneFX mapping,
|
||||
actual renderer and EGL startup log lines are recorded (empty EGL is NOT_OBSERVABLE).
|
||||
Watchdog kills only owned groups. Normal close/SHM release/Wayland error0 are
|
||||
scientific closure. Forced mechanical cleanup never qualifies it. Terminal IPC,
|
||||
client/compositor and outer runner raw exits remain distinct.
|
||||
|
||||
## Frozen intent and limits
|
||||
|
||||
| ID | Captures | Scenario/admission before independent review |
|
||||
| --- | ---: | --- |
|
||||
| P0 | 10 | pristine standard reduced homologous chain, B close/A survives |
|
||||
| P1 | 10 | NOT_ADMITTED: no comparable fullscreen offline certificate |
|
||||
| C1 | 18 | horizontal B/forbidden dismissal, new second chain, splith return |
|
||||
| C2 | 12 | vertical y600 actual root/nested configure geometry |
|
||||
| C3 | 12 | H1 physical1000x750 scale1.25 logical800x600 negative origin; marker |
|
||||
| C4 | 12 | H1 physical750x1000 scale1.25 transform90, IPC/PNG marker |
|
||||
| C5 | 12 | hidden motion only until return, same objects/grabs, fresh B input |
|
||||
| C6 | 12 | real SHM held configure, txn-timeout5000 argv, natural timeout/ACK |
|
||||
| C7 | 14 | 800→960→800, H1→H2→H1 stable IDs, input after each mutation |
|
||||
| C8 | 12 | NOT_ADMITTED: same fullscreen certificate missing as P1 |
|
||||
| O1 | 8 | NOT_ADMITTED: first-allocation diagnostic source/binary unavailable |
|
||||
| O2 | 8 | NOT_ADMITTED: second-allocation diagnostic source/binary unavailable |
|
||||
| G2 | 16 | NOT_ADMITTED: new GPU hook/observability unavailable |
|
||||
| R1 | 14 | NOT_ADMITTED: independent discriminant unspecified; never retry |
|
||||
|
||||
Caps sum170, global180, max14runs,18captures/run,150s/run. C7 spends opening6 plus
|
||||
four mutation pairs8; final post-close pixel capture explicitly NOT_TESTED.
|
||||
Legitimate popup_done is recorded; no dead popup gets clicked, its input is
|
||||
NOT_EXERCISED. All runtime results NOT_RUN; sampled success at most
|
||||
PARTIALLY_FEASIBLE, never production readiness. No final OOM/popup policy chosen.
|
||||
|
||||
C1 uses A350x170/B300x130 configured/native popup sizes and requires real crossing,
|
||||
not requested placement alone. P0 reuses mapped images to stay within10. Before
|
||||
and after second spawn, workspace1/H1/Mosaic tiled ancestry/PID are checked;
|
||||
second ownerID/PID must be fresh. floating=null is handled with actual tiled
|
||||
branch membership and nested ancestry. SHM dimensions, configure destination,
|
||||
decoration fields and current origins stay distinct. Fresh pointer-local input
|
||||
and paint corroborate current IPC candidates. Whole H2 RGB is exact zero tolerance
|
||||
and no mask, except expected standard overflow. Source marker PNG is separate from
|
||||
screenshot count; actual marker screenshot is debited. Root/nested configure ACK
|
||||
map and actual pointer press→grab serial are checked independently. Dismissal is
|
||||
not invisible delivery or universal click-through.
|
||||
|
||||
Core source retains three passive formats SPIKE_CLIP, PROBE_POPUP_CLIP and
|
||||
PROBE_POPUP_HIDDEN_OWNER; temporal neutrality NOT_PROVEN. NULL/zero both print0x0;
|
||||
no identity/generation is inferred. SPIKE_DAMAGE/PI/OPT absent. Native commit/present,
|
||||
complete damage, saved-tree identity, buffer identity, GPU completion and atomic
|
||||
generation remain NOT_OBSERVABLE/NOT_PROVEN absent distinct native measurement.
|
||||
evidence.native_present rejects fake/cross-run records; never manufactures one.
|
||||
evidence.fullscreen_certificate is offline-only and cannot itself enable P1/C8.
|
||||
|
||||
Offline handler quota controls exercise the actual sequencing with explicit fake
|
||||
I/O, not native scientific measurements: P0=10, C1=16, C2=8, C3/C4=8+1 startup
|
||||
marker screenshot, C5=12, C6=10, C7=14. All fake spawn/configuration/mutation/input/
|
||||
chain/camera actions are checked against the same local caps. Real protocol,
|
||||
serial, closure, source/publication and geometry adversarial tests are separate.
|
||||
Initial vertical quota fixture failure is retained in the lab log; its source
|
||||
fixture correction is disclosed in results/graphics-offline.json.
|
||||
|
||||
The swaybg marker helper is explicitly terminated/reaped immediately after the
|
||||
calibration capture. Its raw helper-exit status is retained separately; expected
|
||||
helper SIGTERM is never a scientific XDG close/SHM-release claim. A direct test
|
||||
exercises this actual helper retirement path without launching swaybg.
|
||||
|
||||
## Bounded R-runtime repair — OFFLINE ONLY
|
||||
|
||||
The watchdog owns TERM at elapsed143s and autonomous KILL no later than146s
|
||||
(or after a two-second grace for log-limit expiry). Main cleanup uses the same
|
||||
absolute146s deadline, with reaping bounded by147s. Process registration and
|
||||
watchdog expiry share a lock. The offline real Python child ignores TERM in its
|
||||
own session/group; KILL is observed without invoking main cleanup. This is a
|
||||
mechanical watchdog test, not native compositor cleanup evidence.
|
||||
|
||||
C7 now rejects a wrong reconstructed clamp at every resize/migration. The pinned
|
||||
mosaic.c row calloc leaves minimum_cell_width zero; no production setter exists;
|
||||
arrange.c and mosaic.c use640. This is independently corroborated by all three
|
||||
actual direct-child IPC widths640 and zero-gap x positions, at viewport widths
|
||||
below1920. The predicate rejects a different shape instead of guessing a policy.
|
||||
Expected scroll is min(previous scroll,1920-current viewport width). Every sampled
|
||||
mutation image rejects popup-colored pixels outside the current output viewport;
|
||||
H2 must additionally equal the whole static reference after both resizes and
|
||||
return toH1. Each surviving chain requires a newly sampled B interior and fresh
|
||||
pointer-local delivery; dismissal records NOT_EXERCISED input without clicking a
|
||||
destroyed popup. Source identity/publication binding and all original caps remain.
|
||||
These are pending IPC plus sampled paint/input predicates, not native applied
|
||||
state or generation/damage proof. Runtime remains NOT_RUN pending delta review.
|
||||
159
tooling/probes/step20c-corec-v2/graphics/cases.py
Normal file
159
tooling/probes/step20c-corec-v2/graphics/cases.py
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
"""Small campaign scenarios over the same reviewed Session; no historical runner copy."""
|
||||
import re,time
|
||||
from PIL import ImageChops
|
||||
from engine import *
|
||||
from geometry import *
|
||||
|
||||
def init_reference(s,prefix_fillers=1):
|
||||
s.cmd('workspace 2');s.launch_client('reference','static-reference');rid=s.find('reference')['id'];s.cmd(f'[con_id={rid}] border none')
|
||||
s.cmd('workspace 1')
|
||||
for i in range(prefix_fillers):s.launch_client('filler'+str(i),'static-reference')
|
||||
s.cmd('layout mosaic' if s.spec['mode']=='mosaic' else 'layout splith')
|
||||
return rid
|
||||
|
||||
def stable_reference(s):
|
||||
s.inject('move',[s.outputs[0]['logical'][0]+10,s.outputs[0]['logical'][1]+10]);time.sleep(.15)
|
||||
a=s.capture('H2-reference1','HEADLESS-2');time.sleep(.6);b=s.capture('H2-reference2','HEADLESS-2');exact(a,b)
|
||||
need(all(v==(48,48,56) for v in a.get_flattened_data()),'static uniform H2');s.save('reference-stability',dict(status='PASS',exact=exact(a,b)));return a
|
||||
|
||||
def root_state(s,state,origin,window_start,expect_hidden=False,previous=None,expected_tree=None):
|
||||
# WHY/CONTRACT/INVARIANT: no PI/native scene identity exists in CORE.
|
||||
# Pixel/input evidence qualifies sampled clipping only, never generation.
|
||||
return dict(status='NOT OBSERVABLE',native_tree_identity='NOT OBSERVABLE',
|
||||
applied_generation='NOT OBSERVABLE',owner_id=state['owner']['id'],
|
||||
workspace_id=state['workspace']['id'],window=[window_start,time.monotonic_ns()])
|
||||
|
||||
def open_chain(s,tail_fillers=0):
|
||||
|
||||
s.cmd('workspace 1');s.cmd('layout mosaic' if s.spec['mode']=='mosaic' else 'layout splith');s.expected_layout='mosaic' if s.spec['mode']=='mosaic' else 'splith'
|
||||
pre=s.snapshot('before-chain-'+str(sum(e['kind']=='spawn' and e['name']=='owner' for e in s.events)))
|
||||
validators.selected_workspace(pre,'1','HEADLESS-1',s.expected_layout)
|
||||
s.debit('chain');s.launch_client('owner',s.spec['client'],s.spec['placement'])
|
||||
s.owner('owner') # Reject wrong map before any click; never move mapped popup chains.
|
||||
for i in range(tail_fillers):s.launch_client('tail'+str(i),'static-reference')
|
||||
s.cmd(f'[con_id={s.find("owner")["id"]}] focus');state=s.calibrate('owner');baseline=s.reference if hasattr(s,'reference') else stable_reference(s);s.reference=baseline
|
||||
im=s.capture('owner-top-'+str(sum(e['kind']=='spawn' and e['name']=='owner' for e in s.events)),'HEADLESS-1');rect=[*state['origin'],state['owner']['window_rect']['width'],state['owner']['window_rect']['height']]
|
||||
xy=solid_point(im,COLORS['TOPLEVEL'],s.outputs[0],s.frames['HEADLESS-1'],rect)
|
||||
need(contains([*state['origin'],420,575 if s.spec['axis']==1 else 320],xy),'opening native bounds')
|
||||
ta,ba=s.click('owner',xy,'left',True);s.wait(lambda:'POPUP_A MAP ' in s.log('owner'));time.sleep(.2)
|
||||
aim=s.capture('popup-A-'+str(sum(e['kind']=='spawn' and e['name']=='owner' for e in s.events)),'HEADLESS-1');prov=Provenance(state['owner']['id'],state['workspace']['id'],'NOT OBSERVABLE',state['evidence'])
|
||||
xyA=solid_point(aim,COLORS['POPUP_A'],s.outputs[0],s.frames['HEADLESS-1'])
|
||||
tb,bb=s.click('owner',xyA,'left',True);s.wait(lambda:'POPUP_B MAP ' in s.log('owner'));time.sleep(.2)
|
||||
window=time.monotonic_ns();time.sleep(.15);p=validators.grab(s.log('owner'));g=geometry(s.log('owner'),state['origin'],prov)
|
||||
s.delivery('owner',rect,xy,ta,ba,'TOPLEVEL',p['grabs'][0]['serial']);s.delivery('owner',g['A'],xyA,tb,bb,'POPUP_A',p['grabs'][1]['serial'])
|
||||
[validators.crosses(g[k],s.spec['axis'],s.spec['boundary']) for k in ('A','B')]
|
||||
left,right=s.pair_capture('mapped-'+str(sum(e['kind']=='spawn' and e['name']=='owner' for e in s.events)))
|
||||
for name in ('POPUP_A','POPUP_B'):
|
||||
need(count(left,COLORS[name])>0,'permitted popup pixels missing')
|
||||
if s.spec['mode']=='pristine':need(count(right,COLORS[name])>0,'standard popup continuation missing')
|
||||
if s.spec['mode']=='mosaic':validators.rgb(baseline,right);root=root_state(s,state,state['origin'],window)
|
||||
else:root=dict(status='NOT OBSERVABLE',meaning='unmodified control has no private clip instrumentation')
|
||||
s.save('chain',dict(protocol=p,geometry=g,state=state,root=root,H2_exact=s.spec['mode']=='mosaic',color_counts={n:[count(left,c),count(right,c)] for n,c in COLORS.items()}))
|
||||
return state,baseline,p,g,left,right
|
||||
|
||||
def popup_campaign(s):
|
||||
init_reference(s);state,baseline,p,g,left,right=open_chain(s)
|
||||
xy=solid_point(left,COLORS['POPUP_B'],s.outputs[0],s.frames['HEADLESS-1'],g['B']);tails,buttons=s.click('owner',xy);s.delivery('owner',g['B'],xy,tails,buttons,'POPUP_B')
|
||||
forbidden=intersection(g['B'],s.outputs[1]['logical']);need(forbidden[2]>6 and forbidden[3]>6,'H2 crossing interior missing')
|
||||
xy=[forbidden[0]+forbidden[2]//2,forbidden[1]+forbidden[3]//2]
|
||||
if s.spec['mode']=='pristine':
|
||||
# The point must be real standard paint, not just a requested extent.
|
||||
xy=solid_point(right,COLORS['POPUP_B'],s.outputs[1],s.frames['HEADLESS-2'],g['B']);tails,buttons=s.click('owner',xy);s.delivery('owner',g['B'],xy,tails,buttons,'POPUP_B')
|
||||
else:
|
||||
tails,buttons=s.click('owner',xy);ds=dismissal(tails['owner'],buttons['owner']);s.save('dismissal',dict(proof=ds,reference_buttons=buttons.get('reference'),interpretation='outside grab dismissal; no universal click-through claim'))
|
||||
need(not any('target=POPUP_A ' in l or 'target=POPUP_B ' in l for v in buttons.values() for l in v),'forbidden popup delivery')
|
||||
s.close_client('owner');time.sleep(.2);final=s.pair_capture('final');exact(baseline,final[1]);need(not any(count(im,COLORS[n]) for im in final for n in ('POPUP_A','POPUP_B')),'popup residual')
|
||||
return 'PARTIALLY_FEASIBLE'
|
||||
|
||||
def hidden_campaign(s):
|
||||
init_reference(s,2);state,baseline,p,g,left,right=open_chain(s,2)
|
||||
xy=solid_point(left,COLORS['POPUP_B'],s.outputs[0],'logical',g['B']);tails,buttons=s.click('owner',xy);s.delivery('owner',g['B'],xy,tails,buttons,'POPUP_B')
|
||||
delta=state['scene'][0]+state['scene'][2]-s.outputs[0]['logical'][0]+16;start=time.monotonic_ns();s.scroll('owner',delta);hidden=s.owner('owner')
|
||||
need(intersection(hidden['scene'],s.outputs[0]['logical'])[2]==0,'owner not completely hidden')
|
||||
images=s.pair_capture('fully-hidden');need(not any(count(im,COLORS[n]) for im in images for n in ('POPUP_A','POPUP_B')),'hidden popup residual');exact(baseline,images[1])
|
||||
root=root_state(s,hidden,hidden['origin'],start,True,previous=state);cut=len(s.log('owner'));s.inject('move',[-790,590]);need('target=POPUP_A ' not in s.log('owner')[cut:] and 'target=POPUP_B ' not in s.log('owner')[cut:],'hidden popup pointer enter')
|
||||
validators.hidden_survival(s.log('owner'),p,[])
|
||||
return_start=time.monotonic_ns();s.scroll('owner',-delta);restored=s.owner('owner');need(restored['origin']==state['origin'],'same owner origin restored')
|
||||
images=s.pair_capture('returned');validators.hidden_survival(s.log('owner'),p,[e for e in s.events if e['kind']=='injection' and start<=e['ns']<=return_start]);exact(baseline,images[1]);returned_root=root_state(s,restored,restored['origin'],return_start,previous=hidden,expected_tree=root.get('tree'));fresh=validators.grab(s.log('owner'));need(fresh['ids']==p['ids'] and fresh['grabs']==p['grabs'],'unchanged live chain')
|
||||
g=geometry(s.log('owner'),restored['origin'],Provenance(restored['owner']['id'],restored['workspace']['id'],'NOT OBSERVABLE','fresh return geometry'));xy=solid_point(images[0],COLORS['POPUP_B'],s.outputs[0],'logical',g['B']);tails,buttons=s.click('owner',xy);s.delivery('owner',g['B'],xy,tails,buttons,'POPUP_B')
|
||||
s.save('hidden-return',dict(status='PASS',hidden=hidden,empty_root=root,returned=restored,returned_root=returned_root,ids=p['ids']))
|
||||
s.close_client('owner');final=s.pair_capture('final');exact(baseline,final[1]);return 'PARTIALLY_FEASIBLE'
|
||||
|
||||
def sample_surviving(s,images,tag):
|
||||
if 'POPUP_DONE ' in s.log('owner'):
|
||||
s.save(tag+'-input',dict(status='NOT_EXERCISED',reason='legitimate popup dismissal, no new chain reserved'));return
|
||||
state=s.owner('owner');prot=validators.grab(s.log('owner'));geo=geometry(s.log('owner'),state['origin'],Provenance(state['owner']['id'],state['workspace']['id'],'NOT OBSERVABLE','mutation paint+pointer'))
|
||||
index=next(i for i,o in enumerate(s.outputs) if o['name']==state['workspace']['output'])
|
||||
try:point=solid_point(images[index],COLORS['POPUP_B'],s.outputs[index],s.frames[s.outputs[index]['name']],geo['B'])
|
||||
except ValueError:
|
||||
s.save(tag+'-input',dict(status='NOT_EXERCISED',reason='B has no measured visible interior'));return
|
||||
tails,buttons=s.click('owner',point);s.delivery('owner',geo['B'],point,tails,buttons,'POPUP_B');s.save(tag+'-input',dict(status='PASS',point=point,protocol=prot))
|
||||
|
||||
def dynamic_geometry(snap,previous=None):
|
||||
# WHY: scroll IPC alone does not demonstrate the resize clamp.
|
||||
# CONTRACT: pinned arrange.c105-131 / mosaic.c125-153, zero inner gap,
|
||||
# three direct tiled children; calloc row state has no minimum-width setter
|
||||
# and uses640. Corroborate that minimum with every actual IPC child width.
|
||||
# INVARIANT: this is a pending IPC candidate, never native applied geometry.
|
||||
ws=workspace(snap['tree'],'1');row=ws['mosaic']['rows'][0]
|
||||
children=ws['nodes'];ids=[c['id'] for c in children]
|
||||
need(len(children)==3 and row['children']==ids,'dynamic direct row reconstruction')
|
||||
viewport=[ws['rect'][k] for k in ('x','y','width','height')]
|
||||
width=viewport[2];need(0<width<3*640,'dynamic minimum not distinguishable from fitting')
|
||||
need(all(c['rect']['width']==640 for c in children),'actual minimum-width reconstruction')
|
||||
need([c['rect']['x'] for c in children]==[viewport[0]+i*640 for i in range(3)],'actual zero-gap row reconstruction')
|
||||
maximum=3*640-width;scroll=row['scroll_x'];need(0<=scroll<=maximum,'dynamic camera outside clamp')
|
||||
result=dict(workspace_id=ws['id'],output=ws['output'],children=ids,viewport=viewport,
|
||||
minimum_width=640,content_width=1920,max_scroll=maximum,scroll=scroll,
|
||||
geometry_status='IPC_PENDING_CANDIDATE; native generation NOT OBSERVABLE')
|
||||
if previous is not None:
|
||||
need(ws['id']==previous['workspace_id'] and ids==previous['children'],'dynamic workspace/order changed')
|
||||
expected=min(previous['scroll'],maximum)
|
||||
need(scroll==expected,'reconstructed camera clamp mismatch')
|
||||
result.update(expected_scroll=expected,clamp='PASS')
|
||||
return result
|
||||
|
||||
def dynamic_pixels(images,outputs,frames,viewport,reference=None):
|
||||
# WHY/CONTRACT: each mutation must reject old-output and outside-clip paint.
|
||||
# INVARIANT: sampled RGB exclusion cannot prove complete native damage.
|
||||
for image,out in zip(images,outputs):
|
||||
for y in range(image.height):
|
||||
for x in range(image.width):
|
||||
if image.getpixel((x,y)) in (COLORS['POPUP_A'],COLORS['POPUP_B']):
|
||||
need(contains(viewport,png_global((x,y),out,frames[out['name']])),'dynamic popup pixel outside current viewport')
|
||||
if reference is not None:exact(reference,images[1])
|
||||
return dict(exclusion='PASS',whole_H2='PASS' if reference is not None else 'NOT_APPLICABLE: current owner workspace on H2',complete_damage='NOT OBSERVABLE')
|
||||
|
||||
def dynamic_campaign(s):
|
||||
init_reference(s,2);s.cmd('gaps inner 0');state,baseline,p,g,left,right=open_chain(s)
|
||||
owner_id=state['owner']['id'];previous=dynamic_geometry(state['snapshot']);stages=[]
|
||||
mutations=[('output HEADLESS-1 resolution 960x600 position -960 0','resized960','HEADLESS-1'),
|
||||
('output HEADLESS-1 resolution 800x600 position -800 0','resized-back','HEADLESS-1'),
|
||||
('move workspace to output HEADLESS-2','migrated-H2','HEADLESS-2'),
|
||||
('move workspace to output HEADLESS-1','migrated-back-H1','HEADLESS-1')]
|
||||
for command,tag,out in mutations:
|
||||
s.cmd('workspace 1');s.cmd(command,True);s.expected_output=out
|
||||
if tag=='resized960':s.outputs[0]={**s.outputs[0],'logical':[-960,0,960,600],'mode':[960,600]}
|
||||
elif tag=='resized-back':s.outputs[0]=dict(s.spec['outputs'][0])
|
||||
time.sleep(.35);snap=s.snapshot(tag);current=dynamic_geometry(snap,previous)
|
||||
need(current['output']==out and s.find('owner')['id']==owner_id,'dynamic exact owner/output')
|
||||
expected_viewport=next(o['logical'] for o in s.outputs if o['name']==out)
|
||||
need(current['viewport']==expected_viewport,'dynamic viewport/output reconstruction')
|
||||
pictures=s.pair_capture(tag)
|
||||
pixels=dynamic_pixels(pictures,s.outputs,s.frames,current['viewport'],baseline if out=='HEADLESS-1' else None)
|
||||
# An alive chain must supply fresh paint and local pointer input; an
|
||||
# actual XDG dismissal is recorded without injecting on destroyed popups.
|
||||
if 'POPUP_DONE ' not in s.log('owner'):
|
||||
fresh=s.owner('owner');geo=geometry(s.log('owner'),fresh['origin'],Provenance(owner_id,current['workspace_id'],'NOT OBSERVABLE','fresh mutation IPC + paint + pointer'))
|
||||
index=next(i for i,o in enumerate(s.outputs) if o['name']==out)
|
||||
point=solid_point(pictures[index],COLORS['POPUP_B'],s.outputs[index],s.frames[out],geo['B'])
|
||||
tails,buttons=s.click('owner',point);s.delivery('owner',geo['B'],point,tails,buttons,'POPUP_B')
|
||||
need(validators.grab(s.log('owner'))['ids']==p['ids'],'dynamic popup IDs changed')
|
||||
input_proof=dict(status='PASS',point=point)
|
||||
else:input_proof=dict(status='NOT_EXERCISED',reason='XDG dismissal; no injection on destroyed popup')
|
||||
stages.append(dict(tag=tag,geometry=current,pixels=pixels,input=input_proof));previous=current
|
||||
s.save('dynamic-ownership',dict(status='PASS',stages=stages,ids_before=p['ids'],atomic_generation='NOT PROVEN',complete_damage='NOT OBSERVABLE'))
|
||||
s.close_client('owner')
|
||||
for name in list(s.proc):
|
||||
if name.startswith('filler'):s.close_client(name,'close')
|
||||
s.save('dynamic-final-limit',dict(status='NOT_TESTED',reason='14 capture cap used by opening and four fresh mutation states; no post-close capture'));return 'PARTIALLY_FEASIBLE'
|
||||
298
tooling/probes/step20c-corec-v2/graphics/clients/nested-client.c
Normal file
298
tooling/probes/step20c-corec-v2/graphics/clients/nested-client.c
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <wayland-client.h>
|
||||
#include "xdg-shell-client.h"
|
||||
|
||||
/* EXPERIMENTAL CONTRACT: every popup owns distinct XDG objects. B's actual
|
||||
* parent is A's xdg_surface; logs name object IDs as well as colors. */
|
||||
struct surface {
|
||||
const char *name;
|
||||
struct wl_surface *wl;
|
||||
struct xdg_surface *xdg;
|
||||
struct xdg_popup *popup;
|
||||
bool configured, mapped, done;
|
||||
int width, height, anchor_x, anchor_y;
|
||||
uint32_t color;
|
||||
};
|
||||
static struct wl_display *display;
|
||||
static struct wl_compositor *compositor;
|
||||
static struct wl_shm *shm;
|
||||
static struct xdg_wm_base *wm;
|
||||
static struct wl_seat *seat;
|
||||
static struct wl_pointer *pointer;
|
||||
static struct xdg_toplevel *top_role;
|
||||
static struct surface top = {.name="TOPLEVEL", .width=420, .height=320, .color=0x002344cc};
|
||||
static struct surface a = {.name="POPUP_A", .width=300, .height=170, .color=0x00e0c020};
|
||||
static struct surface b = {.name="POPUP_B", .width=300, .height=130, .color=0x0020d0a0};
|
||||
static volatile sig_atomic_t running = 1;
|
||||
static struct surface *entered;
|
||||
|
||||
/* Observe client-side SHM release separately from native GPU retirement.
|
||||
* Keep protocol buffer alive until release; no backing memory is reused. */
|
||||
static unsigned shm_created, shm_released;
|
||||
static void buffer_release(void *data, struct wl_buffer *buffer) {
|
||||
(void)data;
|
||||
++shm_released;
|
||||
fprintf(stderr,"SHM_RELEASE buffer=%u released=%u created=%u\n",
|
||||
wl_proxy_get_id((struct wl_proxy *)buffer),shm_released,shm_created);
|
||||
wl_buffer_destroy(buffer);
|
||||
}
|
||||
static const struct wl_buffer_listener buffer_listener = {.release=buffer_release};
|
||||
static void draw(struct surface *s) {
|
||||
size_t bytes = (size_t)s->width * (size_t)s->height * 4;
|
||||
char path[4096];
|
||||
const char *scratch = getenv("TMPDIR");
|
||||
const char *lab = getenv("SMX_LAB");
|
||||
/* WHY: SHM belongs to LAB, never the short socket-only runtime.
|
||||
* CONTRACT: launcher supplies canonical LAB/scratch; fail before mkstemp. */
|
||||
char expected[4096];
|
||||
int expected_len = lab ? snprintf(expected, sizeof(expected), "%s/scratch", lab) : -1;
|
||||
int path_len = scratch ? snprintf(path, sizeof(path), "%s/smx-shm-XXXXXX", scratch) : -1;
|
||||
if (!lab || strncmp(lab, "/var/tmp/", 9) || strstr(lab, "/../") ||
|
||||
expected_len < 0 || expected_len >= (int)sizeof(expected) ||
|
||||
!scratch || strcmp(scratch, expected) || path_len < 0 ||
|
||||
path_len >= (int)sizeof(path)) {
|
||||
fprintf(stderr, "invalid LAB scratch for SHM\n");
|
||||
exit(1);
|
||||
}
|
||||
int fd = mkstemp(path);
|
||||
if (fd < 0 || ftruncate(fd, (off_t)bytes) < 0) { perror("buffer"); exit(1); }
|
||||
unlink(path);
|
||||
uint32_t *pixels = mmap(NULL, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (pixels == MAP_FAILED) { perror("mmap"); exit(1); }
|
||||
for (size_t i=0; i<bytes/4; ++i) pixels[i] = s->color;
|
||||
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, (int)bytes);
|
||||
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, s->width, s->height,
|
||||
s->width*4, WL_SHM_FORMAT_XRGB8888);
|
||||
++shm_created;
|
||||
wl_buffer_add_listener(buffer,&buffer_listener,NULL);
|
||||
wl_surface_attach(s->wl, buffer, 0, 0);
|
||||
wl_surface_damage_buffer(s->wl, 0, 0, s->width, s->height);
|
||||
wl_surface_commit(s->wl);
|
||||
wl_shm_pool_destroy(pool);
|
||||
munmap(pixels, bytes);
|
||||
close(fd);
|
||||
s->mapped = true;
|
||||
fprintf(stderr, "%s MAP wl=%u xdg=%u\n", s->name,
|
||||
wl_proxy_get_id((struct wl_proxy *)s->wl), wl_proxy_get_id((struct wl_proxy *)s->xdg));
|
||||
}
|
||||
static void on_ping(void *data, struct xdg_wm_base *base, uint32_t serial) {
|
||||
xdg_wm_base_pong(base, serial);
|
||||
}
|
||||
static const struct xdg_wm_base_listener wm_listener = {.ping=on_ping};
|
||||
static void on_xdg_configure(void *data, struct xdg_surface *xdg, uint32_t serial) {
|
||||
struct surface *s=data;
|
||||
fprintf(stderr, "%s XDG_CONFIGURE serial=%u ack\n", s->name, serial);
|
||||
xdg_surface_ack_configure(xdg, serial);
|
||||
s->configured=true;
|
||||
if (!s->mapped && !s->done) draw(s);
|
||||
}
|
||||
static const struct xdg_surface_listener xdg_listener = {.configure=on_xdg_configure};
|
||||
static void on_top_configure(void *data, struct xdg_toplevel *role, int32_t w,
|
||||
int32_t h, struct wl_array *states) {
|
||||
fprintf(stderr, "TOPLEVEL ROLE_CONFIGURE w=%d h=%d\n", w,h);
|
||||
}
|
||||
static void on_top_close(void *data, struct xdg_toplevel *role) {
|
||||
fprintf(stderr, "TOPLEVEL CLOSE_REQUEST\n"); running=0;
|
||||
}
|
||||
static const struct xdg_toplevel_listener top_listener = {
|
||||
.configure=on_top_configure, .close=on_top_close,
|
||||
};
|
||||
static void on_popup_configure(void *data, struct xdg_popup *role, int32_t x,
|
||||
int32_t y, int32_t w, int32_t h) {
|
||||
struct surface *s=data;
|
||||
fprintf(stderr, "%s ROLE_CONFIGURE x=%d y=%d w=%d h=%d\n",s->name,x,y,w,h);
|
||||
}
|
||||
static void on_popup_done(void *data, struct xdg_popup *role) {
|
||||
struct surface *s=data;
|
||||
s->done=true;
|
||||
s->mapped=false;
|
||||
fprintf(stderr, "%s POPUP_DONE role=%u\n",s->name,wl_proxy_get_id((struct wl_proxy *)role));
|
||||
}
|
||||
static const struct xdg_popup_listener popup_listener = {
|
||||
.configure=on_popup_configure, .popup_done=on_popup_done,
|
||||
};
|
||||
/* WHY: XDG requires child-first destruction even if the compositor sends
|
||||
* dismissals for multiple levels in one dispatch batch. */
|
||||
static void destroy_popup(struct surface *s) {
|
||||
if (!s->popup) return;
|
||||
fprintf(stderr,"%s DESTROY role=%u xdg=%u wl=%u\n",s->name,
|
||||
wl_proxy_get_id((struct wl_proxy *)s->popup),
|
||||
wl_proxy_get_id((struct wl_proxy *)s->xdg),
|
||||
wl_proxy_get_id((struct wl_proxy *)s->wl));
|
||||
xdg_popup_destroy(s->popup);
|
||||
xdg_surface_destroy(s->xdg);
|
||||
wl_surface_destroy(s->wl);
|
||||
s->popup=NULL; s->xdg=NULL; s->wl=NULL; s->mapped=false;
|
||||
if (entered==s) entered=NULL;
|
||||
}
|
||||
static void collect_dismissals(void) {
|
||||
if (b.done) destroy_popup(&b);
|
||||
if (a.done) {
|
||||
destroy_popup(&b);
|
||||
destroy_popup(&a);
|
||||
}
|
||||
}
|
||||
static void open_popup(struct surface *s, struct surface *parent, uint32_t serial) {
|
||||
if (s->popup || !parent->mapped || !serial) return;
|
||||
s->wl=wl_compositor_create_surface(compositor);
|
||||
s->xdg=xdg_wm_base_get_xdg_surface(wm,s->wl);
|
||||
xdg_surface_add_listener(s->xdg,&xdg_listener,s);
|
||||
struct xdg_positioner *pos=xdg_wm_base_create_positioner(wm);
|
||||
xdg_positioner_set_size(pos,s->width,s->height);
|
||||
xdg_positioner_set_anchor_rect(pos,s->anchor_x,s->anchor_y,1,1);
|
||||
xdg_positioner_set_anchor(pos,XDG_POSITIONER_ANCHOR_TOP_LEFT);
|
||||
xdg_positioner_set_gravity(pos,XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
|
||||
s->popup=xdg_surface_get_popup(s->xdg,parent->xdg,pos);
|
||||
xdg_popup_add_listener(s->popup,&popup_listener,s);
|
||||
fprintf(stderr,"%s CREATE wl=%u xdg=%u role=%u parent=%s parent_xdg=%u\n",
|
||||
s->name,wl_proxy_get_id((struct wl_proxy *)s->wl),
|
||||
wl_proxy_get_id((struct wl_proxy *)s->xdg),
|
||||
wl_proxy_get_id((struct wl_proxy *)s->popup),parent->name,
|
||||
wl_proxy_get_id((struct wl_proxy *)parent->xdg));
|
||||
fprintf(stderr,"%s GRAB_REQUEST origin=wl_pointer.button target=%s serial=%u used=%u\n",
|
||||
s->name,parent->name,serial,serial);
|
||||
xdg_popup_grab(s->popup,seat,serial);
|
||||
wl_surface_commit(s->wl); /* Initial bufferless commit requests configure. */
|
||||
xdg_positioner_destroy(pos);
|
||||
}
|
||||
static struct surface *which(struct wl_surface *wl) {
|
||||
if (wl==top.wl) return ⊤
|
||||
if (wl==a.wl && wl) return &a;
|
||||
if (wl==b.wl && wl) return &b;
|
||||
return NULL;
|
||||
}
|
||||
static void on_enter(void *data,struct wl_pointer *ptr,uint32_t serial,
|
||||
struct wl_surface *wl,wl_fixed_t x,wl_fixed_t y) {
|
||||
entered=which(wl);
|
||||
fprintf(stderr,"POINTER_ENTER target=%s wl=%u serial=%u x=%.2f y=%.2f\n",
|
||||
entered?entered->name:"OTHER",wl_proxy_get_id((struct wl_proxy *)wl),serial,
|
||||
wl_fixed_to_double(x),wl_fixed_to_double(y));
|
||||
}
|
||||
static void on_leave(void *data,struct wl_pointer *ptr,uint32_t serial,
|
||||
struct wl_surface *wl) {
|
||||
struct surface *s=which(wl);
|
||||
fprintf(stderr,"POINTER_LEAVE target=%s serial=%u\n",s?s->name:"OTHER",serial);
|
||||
if (entered==s) entered=NULL;
|
||||
}
|
||||
static void on_motion(void *data,struct wl_pointer *ptr,uint32_t time,
|
||||
wl_fixed_t x,wl_fixed_t y) {
|
||||
fprintf(stderr,"POINTER_MOTION target=%s x=%.2f y=%.2f\n",
|
||||
entered?entered->name:"OTHER",wl_fixed_to_double(x),wl_fixed_to_double(y));
|
||||
}
|
||||
static void on_button(void *data,struct wl_pointer *ptr,uint32_t serial,
|
||||
uint32_t time,uint32_t button,uint32_t state) {
|
||||
fprintf(stderr,"POINTER_BUTTON target=%s serial=%u button=%u state=%u\n",
|
||||
entered?entered->name:"OTHER",serial,button,state);
|
||||
if (state!=WL_POINTER_BUTTON_STATE_PRESSED || button!=272) return;
|
||||
if (entered==&top && !a.popup) open_popup(&a,&top,serial);
|
||||
else if (entered==&a && a.mapped && !b.popup) open_popup(&b,&a,serial);
|
||||
}
|
||||
static void on_axis(void *data,struct wl_pointer *ptr,uint32_t t,uint32_t axis,wl_fixed_t v) {}
|
||||
static void on_frame(void *data,struct wl_pointer *ptr) {}
|
||||
static const struct wl_pointer_listener pointer_listener={
|
||||
.enter=on_enter,.leave=on_leave,.motion=on_motion,.button=on_button,
|
||||
.axis=on_axis,.frame=on_frame,
|
||||
};
|
||||
static void on_caps(void *data,struct wl_seat *s,uint32_t caps) {
|
||||
if ((caps & WL_SEAT_CAPABILITY_POINTER) && !pointer) {
|
||||
pointer=wl_seat_get_pointer(s);
|
||||
wl_pointer_add_listener(pointer,&pointer_listener,NULL);
|
||||
}
|
||||
}
|
||||
static void on_seat_name(void *data,struct wl_seat *s,const char *name) {}
|
||||
static const struct wl_seat_listener seat_listener={.capabilities=on_caps,.name=on_seat_name};
|
||||
static void on_global(void *data,struct wl_registry *reg,uint32_t name,
|
||||
const char *interface,uint32_t version) {
|
||||
if (!strcmp(interface,wl_compositor_interface.name))
|
||||
compositor=wl_registry_bind(reg,name,&wl_compositor_interface,version<4?version:4);
|
||||
else if (!strcmp(interface,wl_shm_interface.name))
|
||||
shm=wl_registry_bind(reg,name,&wl_shm_interface,1);
|
||||
else if (!strcmp(interface,xdg_wm_base_interface.name))
|
||||
wm=wl_registry_bind(reg,name,&xdg_wm_base_interface,version<3?version:3);
|
||||
else if (!strcmp(interface,wl_seat_interface.name)) {
|
||||
seat=wl_registry_bind(reg,name,&wl_seat_interface,version<5?version:5);
|
||||
wl_seat_add_listener(seat,&seat_listener,NULL);
|
||||
}
|
||||
}
|
||||
static void on_removed(void *data,struct wl_registry *reg,uint32_t name) {}
|
||||
static const struct wl_registry_listener registry_listener={.global=on_global,.global_remove=on_removed};
|
||||
static void stop(int sig) { running=0; }
|
||||
int main(int argc,char **argv) {
|
||||
signal(SIGTERM,stop);
|
||||
const char *placement=argc>1?argv[1]:"b_cross";
|
||||
/* 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")) {
|
||||
a.anchor_x=350; a.anchor_y=80; a.width=350;
|
||||
b.anchor_x=160; b.anchor_y=60; b.width=300;
|
||||
} else if (!strcmp(placement,"b_cross")) {
|
||||
a.anchor_x=200; a.anchor_y=80; a.width=300;
|
||||
b.anchor_x=220; b.anchor_y=60; b.width=300;
|
||||
} else { fprintf(stderr,"invalid placement\n"); return 2; }
|
||||
display=wl_display_connect(NULL);
|
||||
if (!display) { fprintf(stderr,"cannot connect Wayland\n"); return 1; }
|
||||
struct wl_registry *registry=wl_display_get_registry(display);
|
||||
wl_registry_add_listener(registry,®istry_listener,NULL);
|
||||
wl_display_roundtrip(display); wl_display_roundtrip(display);
|
||||
if (!compositor || !shm || !wm || !seat || !pointer) {
|
||||
fprintf(stderr,"missing Wayland globals/pointer\n"); return 1;
|
||||
}
|
||||
xdg_wm_base_add_listener(wm,&wm_listener,NULL);
|
||||
top.wl=wl_compositor_create_surface(compositor);
|
||||
top.xdg=xdg_wm_base_get_xdg_surface(wm,top.wl);
|
||||
xdg_surface_add_listener(top.xdg,&xdg_listener,&top);
|
||||
top_role=xdg_surface_get_toplevel(top.xdg);
|
||||
xdg_toplevel_add_listener(top_role,&top_listener,NULL);
|
||||
xdg_toplevel_set_title(top_role,"Mosaic Nested Grab Probe");
|
||||
xdg_toplevel_set_app_id(top_role,"org.mosaic.NestedGrabProbe");
|
||||
fprintf(stderr,"TOPLEVEL CREATE wl=%u xdg=%u role=%u placement=%s\n",
|
||||
wl_proxy_get_id((struct wl_proxy *)top.wl),
|
||||
wl_proxy_get_id((struct wl_proxy *)top.xdg),
|
||||
wl_proxy_get_id((struct wl_proxy *)top_role),placement);
|
||||
wl_surface_commit(top.wl);
|
||||
while (running) {
|
||||
wl_display_flush(display);
|
||||
struct pollfd fds[2]={{.fd=wl_display_get_fd(display),.events=POLLIN},
|
||||
{.fd=STDIN_FILENO,.events=POLLIN}};
|
||||
int ready=poll(fds,2,500);
|
||||
if (ready<0) { if (errno==EINTR) continue; perror("poll"); break; }
|
||||
if (fds[0].revents & (POLLIN|POLLERR|POLLHUP)) {
|
||||
if (wl_display_dispatch(display)<0) break;
|
||||
collect_dismissals();
|
||||
}
|
||||
if (fds[1].revents & POLLIN) {
|
||||
char cmd[64];
|
||||
if (fgets(cmd,sizeof(cmd),stdin)) {
|
||||
if (!strncmp(cmd,"close_b",7)) { destroy_popup(&b); fprintf(stderr,"COMMAND close_b DONE\n"); }
|
||||
else if (!strncmp(cmd,"close_owner",11)) { fprintf(stderr,"CLOSE_RECEIVED\nCOMMAND close_owner\n"); running=0; }
|
||||
}
|
||||
}
|
||||
}
|
||||
destroy_popup(&b); destroy_popup(&a);
|
||||
fprintf(stderr,"TOPLEVEL DESTROY\n");
|
||||
xdg_toplevel_destroy(top_role); xdg_surface_destroy(top.xdg); wl_surface_destroy(top.wl);
|
||||
wl_display_roundtrip(display);
|
||||
fprintf(stderr,"SHM_CLOSURE created=%u released=%u\n",shm_created,shm_released);
|
||||
wl_display_flush(display);
|
||||
/* WHY/CONTRACT/INVARIANT: exit0 does not prove protocol error0. */
|
||||
fprintf(stderr,"EXIT error=%d\n",wl_display_get_error(display));
|
||||
wl_display_disconnect(display);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
#define _DEFAULT_SOURCE
|
||||
#include <linux/input-event-codes.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <wayland-client.h>
|
||||
#include "virtual-pointer-client.h"
|
||||
static struct zwlr_virtual_pointer_manager_v1 *manager;
|
||||
static void global(void *data, struct wl_registry *registry, uint32_t name,
|
||||
const char *interface, uint32_t version) {
|
||||
if (!strcmp(interface, zwlr_virtual_pointer_manager_v1_interface.name))
|
||||
manager=wl_registry_bind(registry,name,&zwlr_virtual_pointer_manager_v1_interface,
|
||||
version < 2 ? version : 2);
|
||||
}
|
||||
static void removed(void *data, struct wl_registry *registry, uint32_t name) {}
|
||||
int main(void) {
|
||||
struct wl_display *display=wl_display_connect(NULL);
|
||||
if (!display) return 1;
|
||||
struct wl_registry *registry=wl_display_get_registry(display);
|
||||
struct wl_registry_listener listener={global,removed};
|
||||
wl_registry_add_listener(registry,&listener,NULL);
|
||||
wl_display_roundtrip(display);
|
||||
if (!manager) return 2;
|
||||
struct zwlr_virtual_pointer_v1 *pointer=
|
||||
zwlr_virtual_pointer_manager_v1_create_virtual_pointer(manager,NULL);
|
||||
wl_display_roundtrip(display);
|
||||
fprintf(stderr,"POINTER_READY\n");fflush(stderr);
|
||||
char line[128];
|
||||
uint32_t stamp=1000;
|
||||
while (fgets(line,sizeof(line),stdin)) {
|
||||
char action[16];int x,y;
|
||||
unsigned width,height;
|
||||
if (sscanf(line,"%15s %d %d %u %u",action,&x,&y,&width,&height)!=5 || !width || !height || x<0 || y<0 || (unsigned)x>=width || (unsigned)y>=height || (strcmp(action,"move") && strcmp(action,"left") && strcmp(action,"right"))) return 3;
|
||||
fprintf(stderr,"INJECT %s %d,%d\n",action,x,y);fflush(stderr);
|
||||
zwlr_virtual_pointer_v1_motion_absolute(pointer,stamp++,x,y,width,height);
|
||||
zwlr_virtual_pointer_v1_frame(pointer);
|
||||
wl_display_flush(display);usleep(250000);
|
||||
if (!strcmp(action,"left") || !strcmp(action,"right")) {
|
||||
uint32_t button=!strcmp(action,"left") ? BTN_LEFT : BTN_RIGHT;
|
||||
zwlr_virtual_pointer_v1_button(pointer,stamp++,button,WL_POINTER_BUTTON_STATE_PRESSED);
|
||||
zwlr_virtual_pointer_v1_frame(pointer);
|
||||
wl_display_flush(display);usleep(80000);
|
||||
zwlr_virtual_pointer_v1_button(pointer,stamp++,button,WL_POINTER_BUTTON_STATE_RELEASED);
|
||||
zwlr_virtual_pointer_v1_frame(pointer);
|
||||
wl_display_flush(display);
|
||||
}
|
||||
wl_display_roundtrip(display);
|
||||
fprintf(stderr,"INJECT_DONE %s %d,%d\n",action,x,y);fflush(stderr);
|
||||
}
|
||||
zwlr_virtual_pointer_v1_destroy(pointer);
|
||||
zwlr_virtual_pointer_manager_v1_destroy(manager);
|
||||
wl_registry_destroy(registry);
|
||||
wl_display_disconnect(display);
|
||||
return 0;
|
||||
}
|
||||
253
tooling/probes/step20c-corec-v2/graphics/clients/saved-client.c
Normal file
253
tooling/probes/step20c-corec-v2/graphics/clients/saved-client.c
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <wayland-client.h>
|
||||
#include "xdg-shell-client.h"
|
||||
|
||||
/* WHY: the compositor must wait on a real, responsive XDG client.
|
||||
* CONTRACT: hold only a received configure; ACK the latest received serial.
|
||||
* INVARIANT: Wayland dispatch and ping replies continue while ACK is held. */
|
||||
static struct wl_display *display;
|
||||
static struct wl_compositor *compositor;
|
||||
static struct wl_shm *shm;
|
||||
static struct xdg_wm_base *wm;
|
||||
static struct wl_surface *surface;
|
||||
static struct xdg_surface *xdg;
|
||||
static struct xdg_toplevel *toplevel;
|
||||
static struct wl_seat *seat;
|
||||
static struct wl_pointer *pointer;
|
||||
static volatile sig_atomic_t running = 1;
|
||||
static bool mapped, hold_next, held;
|
||||
static uint32_t latest_serial;
|
||||
static int32_t width = 420, height = 320, configured_width = 420, configured_height = 320;
|
||||
static unsigned generation;
|
||||
|
||||
static long long stamp(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (long long)ts.tv_sec * 1000000000LL + ts.tv_nsec;
|
||||
}
|
||||
|
||||
/* Observe client-side SHM release separately from native GPU retirement.
|
||||
* Keep protocol buffer alive until release; no backing memory is reused. */
|
||||
static unsigned shm_created, shm_released;
|
||||
static void buffer_release(void *data, struct wl_buffer *buffer) {
|
||||
(void)data;
|
||||
++shm_released;
|
||||
fprintf(stderr,"SHM_RELEASE buffer=%u released=%u created=%u\n",
|
||||
wl_proxy_get_id((struct wl_proxy *)buffer),shm_released,shm_created);
|
||||
wl_buffer_destroy(buffer);
|
||||
}
|
||||
static const struct wl_buffer_listener buffer_listener = {.release=buffer_release};
|
||||
static void draw(void) {
|
||||
if (configured_width > 0) width = configured_width;
|
||||
if (configured_height > 0) height = configured_height;
|
||||
if (width <= 0 || height <= 0 || width > 4096 || height > 4096) {
|
||||
fprintf(stderr, "INVALID_SIZE %d %d\n", width, height);
|
||||
exit(2);
|
||||
}
|
||||
size_t bytes = (size_t)width * (size_t)height * 4;
|
||||
char name[4096];
|
||||
const char *scratch = getenv("TMPDIR");
|
||||
const char *lab = getenv("SMX_LAB");
|
||||
/* WHY: SHM belongs to LAB, never the short socket-only runtime.
|
||||
* CONTRACT: launcher supplies canonical LAB/scratch; fail before mkstemp. */
|
||||
char expected[4096];
|
||||
int expected_len = lab ? snprintf(expected, sizeof(expected), "%s/scratch", lab) : -1;
|
||||
int path_len = scratch ? snprintf(name, sizeof(name), "%s/smx-shm-XXXXXX", scratch) : -1;
|
||||
if (!lab || strncmp(lab, "/var/tmp/", 9) || strstr(lab, "/../") ||
|
||||
expected_len < 0 || expected_len >= (int)sizeof(expected) ||
|
||||
!scratch || strcmp(scratch, expected) || path_len < 0 ||
|
||||
path_len >= (int)sizeof(name)) {
|
||||
fprintf(stderr, "invalid LAB scratch for SHM\n");
|
||||
exit(1);
|
||||
}
|
||||
int fd = mkstemp(name);
|
||||
if (fd < 0 || ftruncate(fd, (off_t)bytes) < 0) { perror("shm"); exit(2); }
|
||||
unlink(name);
|
||||
uint32_t *pixels = mmap(NULL, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (pixels == MAP_FAILED) { perror("mmap"); exit(2); }
|
||||
uint32_t color = (generation++ % 2) ? 0x0020d0a0 : 0x002344cc;
|
||||
#ifdef STATIC_REFERENCE
|
||||
/* CONTRACT: H2 uses one opaque SHM color across every real configure. */
|
||||
color = 0x00303038;
|
||||
#endif
|
||||
for (size_t i = 0; i < bytes / 4; ++i) pixels[i] = color;
|
||||
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, (int)bytes);
|
||||
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, width, height,
|
||||
width * 4, WL_SHM_FORMAT_XRGB8888);
|
||||
++shm_created;
|
||||
wl_buffer_add_listener(buffer,&buffer_listener,NULL);
|
||||
wl_surface_attach(surface, buffer, 0, 0);
|
||||
wl_surface_damage_buffer(surface, 0, 0, width, height);
|
||||
wl_surface_commit(surface);
|
||||
fprintf(stderr, "COMMIT ns=%lld serial=%u size=%dx%d color=%08x\n",
|
||||
stamp(), latest_serial, width, height, color);
|
||||
wl_shm_pool_destroy(pool);
|
||||
munmap(pixels, bytes);
|
||||
close(fd);
|
||||
mapped = true;
|
||||
}
|
||||
|
||||
static void ack_and_commit(void) {
|
||||
if (!latest_serial) return;
|
||||
xdg_surface_ack_configure(xdg, latest_serial);
|
||||
fprintf(stderr, "ACK ns=%lld serial=%u size=%dx%d\n",
|
||||
stamp(), latest_serial, configured_width, configured_height);
|
||||
draw();
|
||||
held = false;
|
||||
}
|
||||
|
||||
static void ping(void *data, struct xdg_wm_base *base, uint32_t serial) {
|
||||
xdg_wm_base_pong(base, serial);
|
||||
fprintf(stderr, "PONG ns=%lld serial=%u\n", stamp(), serial);
|
||||
}
|
||||
static const struct xdg_wm_base_listener wm_listener = {.ping = ping};
|
||||
|
||||
static void role_configure(void *data, struct xdg_toplevel *role,
|
||||
int32_t w, int32_t h, struct wl_array *states) {
|
||||
if (w > 0) configured_width = w;
|
||||
if (h > 0) configured_height = h;
|
||||
fprintf(stderr, "ROLE_CONFIGURE ns=%lld size=%dx%d\n", stamp(), w, h);
|
||||
}
|
||||
static void role_close(void *data, struct xdg_toplevel *role) { running = 0; }
|
||||
static const struct xdg_toplevel_listener role_listener = {
|
||||
.configure = role_configure, .close = role_close,
|
||||
};
|
||||
static void configure(void *data, struct xdg_surface *s, uint32_t serial) {
|
||||
latest_serial = serial;
|
||||
fprintf(stderr, "XDG_CONFIGURE ns=%lld serial=%u size=%dx%d hold=%d previous=%d\n",
|
||||
stamp(), serial, configured_width, configured_height, hold_next || held, held);
|
||||
if (hold_next || held) {
|
||||
held = true;
|
||||
hold_next = false;
|
||||
fprintf(stderr, "HELD ns=%lld serial=%u\n", stamp(), serial);
|
||||
} else {
|
||||
ack_and_commit();
|
||||
}
|
||||
}
|
||||
static const struct xdg_surface_listener surface_listener = {.configure = configure};
|
||||
static void pointer_enter(void *data, struct wl_pointer *p, uint32_t serial,
|
||||
struct wl_surface *s, wl_fixed_t x, wl_fixed_t y) {
|
||||
fprintf(stderr, "ENTER ns=%lld x=%.2f y=%.2f\n", stamp(),
|
||||
wl_fixed_to_double(x), wl_fixed_to_double(y));
|
||||
}
|
||||
static void pointer_leave(void *data, struct wl_pointer *p, uint32_t serial,
|
||||
struct wl_surface *s) { fprintf(stderr, "LEAVE ns=%lld\n", stamp()); }
|
||||
static void pointer_motion(void *data, struct wl_pointer *p, uint32_t time,
|
||||
wl_fixed_t x, wl_fixed_t y) {
|
||||
fprintf(stderr, "MOTION ns=%lld x=%.2f y=%.2f\n", stamp(),
|
||||
wl_fixed_to_double(x), wl_fixed_to_double(y));
|
||||
}
|
||||
static void pointer_button(void *data, struct wl_pointer *p, uint32_t serial,
|
||||
uint32_t time, uint32_t button, uint32_t state) {
|
||||
fprintf(stderr, "BUTTON ns=%lld serial=%u button=%u state=%u\n",
|
||||
stamp(), serial, button, state);
|
||||
}
|
||||
static void pointer_axis(void *data, struct wl_pointer *p, uint32_t t,
|
||||
uint32_t axis, wl_fixed_t value) {}
|
||||
static void pointer_frame(void *data, struct wl_pointer *p) {}
|
||||
static const struct wl_pointer_listener pointer_listener = {
|
||||
.enter = pointer_enter, .leave = pointer_leave, .motion = pointer_motion,
|
||||
.button = pointer_button, .axis = pointer_axis, .frame = pointer_frame,
|
||||
};
|
||||
static void capabilities(void *data, struct wl_seat *s, uint32_t caps) {
|
||||
if ((caps & WL_SEAT_CAPABILITY_POINTER) && !pointer) {
|
||||
pointer = wl_seat_get_pointer(s);
|
||||
wl_pointer_add_listener(pointer, &pointer_listener, NULL);
|
||||
}
|
||||
}
|
||||
static void seat_name(void *data, struct wl_seat *s, const char *name) {}
|
||||
static const struct wl_seat_listener seat_listener = {
|
||||
.capabilities = capabilities, .name = seat_name,
|
||||
};
|
||||
static void global(void *data, struct wl_registry *registry, uint32_t name,
|
||||
const char *interface, uint32_t version) {
|
||||
if (!strcmp(interface, wl_compositor_interface.name))
|
||||
compositor = wl_registry_bind(registry, name, &wl_compositor_interface, version < 4 ? version : 4);
|
||||
else if (!strcmp(interface, wl_shm_interface.name))
|
||||
shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
|
||||
else if (!strcmp(interface, xdg_wm_base_interface.name))
|
||||
wm = wl_registry_bind(registry, name, &xdg_wm_base_interface, version < 3 ? version : 3);
|
||||
else if (!strcmp(interface, wl_seat_interface.name)) {
|
||||
seat = wl_registry_bind(registry, name, &wl_seat_interface, version < 5 ? version : 5);
|
||||
wl_seat_add_listener(seat, &seat_listener, NULL);
|
||||
}
|
||||
}
|
||||
static void global_remove(void *data, struct wl_registry *registry, uint32_t name) {}
|
||||
static const struct wl_registry_listener registry_listener = {
|
||||
.global = global, .global_remove = global_remove,
|
||||
};
|
||||
static void stop(int signum) { running = 0; }
|
||||
|
||||
int main(void) {
|
||||
signal(SIGTERM, stop);
|
||||
display = wl_display_connect(NULL);
|
||||
if (!display) { fprintf(stderr, "CONNECT_FAILED\n"); return 1; }
|
||||
struct wl_registry *registry = wl_display_get_registry(display);
|
||||
wl_registry_add_listener(registry, ®istry_listener, NULL);
|
||||
wl_display_roundtrip(display);
|
||||
wl_display_roundtrip(display);
|
||||
if (!compositor || !shm || !wm) { fprintf(stderr, "MISSING_GLOBAL\n"); return 2; }
|
||||
xdg_wm_base_add_listener(wm, &wm_listener, NULL);
|
||||
surface = wl_compositor_create_surface(compositor);
|
||||
xdg = xdg_wm_base_get_xdg_surface(wm, surface);
|
||||
xdg_surface_add_listener(xdg, &surface_listener, NULL);
|
||||
toplevel = xdg_surface_get_toplevel(xdg);
|
||||
xdg_toplevel_add_listener(toplevel, &role_listener, NULL);
|
||||
#ifdef STATIC_REFERENCE
|
||||
xdg_toplevel_set_title(toplevel, "Mosaic Static Reference");
|
||||
xdg_toplevel_set_app_id(toplevel, "org.mosaic.StaticReference");
|
||||
#else
|
||||
xdg_toplevel_set_title(toplevel, "Mosaic Saved Buffer Probe");
|
||||
xdg_toplevel_set_app_id(toplevel, "org.mosaic.SavedBufferProbe");
|
||||
#endif
|
||||
wl_surface_commit(surface);
|
||||
fprintf(stderr, "READY ns=%lld\n", stamp());
|
||||
while (running) {
|
||||
wl_display_flush(display);
|
||||
struct pollfd fds[2] = {
|
||||
{.fd = wl_display_get_fd(display), .events = POLLIN},
|
||||
{.fd = STDIN_FILENO, .events = POLLIN},
|
||||
};
|
||||
int ready = poll(fds, 2, 100);
|
||||
if (ready < 0) { if (errno == EINTR) continue; perror("poll"); break; }
|
||||
if (fds[0].revents & (POLLIN | POLLERR | POLLHUP)) {
|
||||
if (wl_display_dispatch(display) < 0) break;
|
||||
}
|
||||
if (fds[1].revents & POLLIN) {
|
||||
char cmd[64];
|
||||
if (!fgets(cmd, sizeof(cmd), stdin)) break;
|
||||
if (!strncmp(cmd, "hold_next_configure", 19)) {
|
||||
hold_next = true;
|
||||
fprintf(stderr, "COMMAND hold_next_configure DONE ns=%lld\n", stamp());
|
||||
} else if (!strncmp(cmd, "release_configure", 17)) {
|
||||
if (held) ack_and_commit();
|
||||
fprintf(stderr, "COMMAND release_configure DONE ns=%lld\n", stamp());
|
||||
} else if (!strncmp(cmd, "close", 5)) {
|
||||
fprintf(stderr,"CLOSE_RECEIVED\n");
|
||||
running = 0;
|
||||
fprintf(stderr, "COMMAND close DONE ns=%lld\n", stamp());
|
||||
}
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "EXIT ns=%lld mapped=%d held=%d error=%d\n",
|
||||
stamp(), mapped, held, wl_display_get_error(display));
|
||||
xdg_toplevel_destroy(toplevel);
|
||||
xdg_surface_destroy(xdg);
|
||||
wl_surface_destroy(surface);
|
||||
wl_display_roundtrip(display);
|
||||
fprintf(stderr,"SHM_CLOSURE created=%u released=%u\n",shm_created,shm_released);
|
||||
wl_display_flush(display);
|
||||
wl_display_disconnect(display);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
/* WHY: a deterministic H2 reference cannot contain Kitty's blinking cursor.
|
||||
* CONTRACT: use the same real XDG configure/ACK loop with fixed opaque SHM.
|
||||
* INVARIANT: once configured and stable, this client changes no pixels. */
|
||||
#define STATIC_REFERENCE 1
|
||||
#include "saved-client.c"
|
||||
|
|
@ -0,0 +1,308 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <wayland-client.h>
|
||||
#include "xdg-shell-client.h"
|
||||
|
||||
/* EXPERIMENTAL CONTRACT: every popup owns distinct XDG objects. B's actual
|
||||
* parent is A's xdg_surface; logs name object IDs as well as colors. */
|
||||
struct surface {
|
||||
const char *name;
|
||||
struct wl_surface *wl;
|
||||
struct xdg_surface *xdg;
|
||||
struct xdg_popup *popup;
|
||||
bool configured, mapped, done;
|
||||
int width, height, anchor_x, anchor_y;
|
||||
uint32_t color;
|
||||
};
|
||||
static struct wl_display *display;
|
||||
static struct wl_compositor *compositor;
|
||||
static struct wl_shm *shm;
|
||||
static struct xdg_wm_base *wm;
|
||||
static struct wl_seat *seat;
|
||||
static struct wl_pointer *pointer;
|
||||
static struct xdg_toplevel *top_role;
|
||||
static struct surface top = {.name="TOPLEVEL", .width=420, .height=320, .color=0x002344cc};
|
||||
static struct surface a = {.name="POPUP_A", .width=300, .height=170, .color=0x00e0c020};
|
||||
static struct surface b = {.name="POPUP_B", .width=300, .height=130, .color=0x0020d0a0};
|
||||
static volatile sig_atomic_t running = 1;
|
||||
static struct surface *entered;
|
||||
|
||||
/* Observe client-side SHM release separately from native GPU retirement.
|
||||
* Keep protocol buffer alive until release; no backing memory is reused. */
|
||||
static unsigned shm_created, shm_released;
|
||||
static void buffer_release(void *data, struct wl_buffer *buffer) {
|
||||
(void)data;
|
||||
++shm_released;
|
||||
fprintf(stderr,"SHM_RELEASE buffer=%u released=%u created=%u\n",
|
||||
wl_proxy_get_id((struct wl_proxy *)buffer),shm_released,shm_created);
|
||||
wl_buffer_destroy(buffer);
|
||||
}
|
||||
static const struct wl_buffer_listener buffer_listener = {.release=buffer_release};
|
||||
static void draw(struct surface *s) {
|
||||
size_t bytes = (size_t)s->width * (size_t)s->height * 4;
|
||||
char path[4096];
|
||||
const char *scratch = getenv("TMPDIR");
|
||||
const char *lab = getenv("SMX_LAB");
|
||||
/* WHY: SHM belongs to LAB, never the short socket-only runtime.
|
||||
* CONTRACT: launcher supplies canonical LAB/scratch; fail before mkstemp. */
|
||||
char expected[4096];
|
||||
int expected_len = lab ? snprintf(expected, sizeof(expected), "%s/scratch", lab) : -1;
|
||||
int path_len = scratch ? snprintf(path, sizeof(path), "%s/smx-shm-XXXXXX", scratch) : -1;
|
||||
if (!lab || strncmp(lab, "/var/tmp/", 9) || strstr(lab, "/../") ||
|
||||
expected_len < 0 || expected_len >= (int)sizeof(expected) ||
|
||||
!scratch || strcmp(scratch, expected) || path_len < 0 ||
|
||||
path_len >= (int)sizeof(path)) {
|
||||
fprintf(stderr, "invalid LAB scratch for SHM\n");
|
||||
exit(1);
|
||||
}
|
||||
int fd = mkstemp(path);
|
||||
if (fd < 0 || ftruncate(fd, (off_t)bytes) < 0) { perror("buffer"); exit(1); }
|
||||
unlink(path);
|
||||
uint32_t *pixels = mmap(NULL, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (pixels == MAP_FAILED) { perror("mmap"); exit(1); }
|
||||
for (size_t i=0; i<bytes/4; ++i) pixels[i] = s->color;
|
||||
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, (int)bytes);
|
||||
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, s->width, s->height,
|
||||
s->width*4, WL_SHM_FORMAT_XRGB8888);
|
||||
++shm_created;
|
||||
wl_buffer_add_listener(buffer,&buffer_listener,NULL);
|
||||
wl_surface_attach(s->wl, buffer, 0, 0);
|
||||
wl_surface_damage_buffer(s->wl, 0, 0, s->width, s->height);
|
||||
wl_surface_commit(s->wl);
|
||||
wl_shm_pool_destroy(pool);
|
||||
munmap(pixels, bytes);
|
||||
close(fd);
|
||||
s->mapped = true;
|
||||
fprintf(stderr, "%s MAP wl=%u xdg=%u\n", s->name,
|
||||
wl_proxy_get_id((struct wl_proxy *)s->wl), wl_proxy_get_id((struct wl_proxy *)s->xdg));
|
||||
}
|
||||
static void on_ping(void *data, struct xdg_wm_base *base, uint32_t serial) {
|
||||
xdg_wm_base_pong(base, serial);
|
||||
}
|
||||
static const struct xdg_wm_base_listener wm_listener = {.ping=on_ping};
|
||||
static void on_xdg_configure(void *data, struct xdg_surface *xdg, uint32_t serial) {
|
||||
struct surface *s=data;
|
||||
fprintf(stderr, "%s XDG_CONFIGURE serial=%u ack\n", s->name, serial);
|
||||
xdg_surface_ack_configure(xdg, serial);
|
||||
s->configured=true;
|
||||
if (!s->mapped && !s->done) draw(s);
|
||||
}
|
||||
static const struct xdg_surface_listener xdg_listener = {.configure=on_xdg_configure};
|
||||
static void on_top_configure(void *data, struct xdg_toplevel *role, int32_t w,
|
||||
int32_t h, struct wl_array *states) {
|
||||
fprintf(stderr, "TOPLEVEL ROLE_CONFIGURE w=%d h=%d\n", w,h);
|
||||
}
|
||||
static void on_top_close(void *data, struct xdg_toplevel *role) {
|
||||
fprintf(stderr, "TOPLEVEL CLOSE_REQUEST\n"); running=0;
|
||||
}
|
||||
static const struct xdg_toplevel_listener top_listener = {
|
||||
.configure=on_top_configure, .close=on_top_close,
|
||||
};
|
||||
static void on_popup_configure(void *data, struct xdg_popup *role, int32_t x,
|
||||
int32_t y, int32_t w, int32_t h) {
|
||||
struct surface *s=data;
|
||||
fprintf(stderr, "%s ROLE_CONFIGURE x=%d y=%d w=%d h=%d\n",s->name,x,y,w,h);
|
||||
}
|
||||
static void on_popup_done(void *data, struct xdg_popup *role) {
|
||||
struct surface *s=data;
|
||||
s->done=true;
|
||||
s->mapped=false;
|
||||
fprintf(stderr, "%s POPUP_DONE role=%u\n",s->name,wl_proxy_get_id((struct wl_proxy *)role));
|
||||
}
|
||||
static const struct xdg_popup_listener popup_listener = {
|
||||
.configure=on_popup_configure, .popup_done=on_popup_done,
|
||||
};
|
||||
/* WHY: XDG requires child-first destruction even if the compositor sends
|
||||
* dismissals for multiple levels in one dispatch batch. */
|
||||
static void destroy_popup(struct surface *s) {
|
||||
if (!s->popup) return;
|
||||
fprintf(stderr,"%s DESTROY role=%u xdg=%u wl=%u\n",s->name,
|
||||
wl_proxy_get_id((struct wl_proxy *)s->popup),
|
||||
wl_proxy_get_id((struct wl_proxy *)s->xdg),
|
||||
wl_proxy_get_id((struct wl_proxy *)s->wl));
|
||||
xdg_popup_destroy(s->popup);
|
||||
xdg_surface_destroy(s->xdg);
|
||||
wl_surface_destroy(s->wl);
|
||||
s->popup=NULL; s->xdg=NULL; s->wl=NULL; s->mapped=false;
|
||||
if (entered==s) entered=NULL;
|
||||
}
|
||||
static void collect_dismissals(void) {
|
||||
if (b.done) destroy_popup(&b);
|
||||
if (a.done) {
|
||||
destroy_popup(&b);
|
||||
destroy_popup(&a);
|
||||
}
|
||||
}
|
||||
static void open_popup(struct surface *s, struct surface *parent, uint32_t serial) {
|
||||
if (s->popup || !parent->mapped || !serial) return;
|
||||
s->wl=wl_compositor_create_surface(compositor);
|
||||
s->xdg=xdg_wm_base_get_xdg_surface(wm,s->wl);
|
||||
xdg_surface_add_listener(s->xdg,&xdg_listener,s);
|
||||
struct xdg_positioner *pos=xdg_wm_base_create_positioner(wm);
|
||||
xdg_positioner_set_size(pos,s->width,s->height);
|
||||
xdg_positioner_set_anchor_rect(pos,s->anchor_x,s->anchor_y,1,1);
|
||||
xdg_positioner_set_anchor(pos,XDG_POSITIONER_ANCHOR_TOP_LEFT);
|
||||
xdg_positioner_set_gravity(pos,XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
|
||||
s->popup=xdg_surface_get_popup(s->xdg,parent->xdg,pos);
|
||||
xdg_popup_add_listener(s->popup,&popup_listener,s);
|
||||
fprintf(stderr,"%s CREATE wl=%u xdg=%u role=%u parent=%s parent_xdg=%u\n",
|
||||
s->name,wl_proxy_get_id((struct wl_proxy *)s->wl),
|
||||
wl_proxy_get_id((struct wl_proxy *)s->xdg),
|
||||
wl_proxy_get_id((struct wl_proxy *)s->popup),parent->name,
|
||||
wl_proxy_get_id((struct wl_proxy *)parent->xdg));
|
||||
fprintf(stderr,"%s GRAB_REQUEST origin=wl_pointer.button target=%s serial=%u used=%u\n",
|
||||
s->name,parent->name,serial,serial);
|
||||
xdg_popup_grab(s->popup,seat,serial);
|
||||
wl_surface_commit(s->wl); /* Initial bufferless commit requests configure. */
|
||||
xdg_positioner_destroy(pos);
|
||||
}
|
||||
static struct surface *which(struct wl_surface *wl) {
|
||||
if (wl==top.wl) return ⊤
|
||||
if (wl==a.wl && wl) return &a;
|
||||
if (wl==b.wl && wl) return &b;
|
||||
return NULL;
|
||||
}
|
||||
static void on_enter(void *data,struct wl_pointer *ptr,uint32_t serial,
|
||||
struct wl_surface *wl,wl_fixed_t x,wl_fixed_t y) {
|
||||
entered=which(wl);
|
||||
fprintf(stderr,"POINTER_ENTER target=%s wl=%u serial=%u x=%.2f y=%.2f\n",
|
||||
entered?entered->name:"OTHER",wl_proxy_get_id((struct wl_proxy *)wl),serial,
|
||||
wl_fixed_to_double(x),wl_fixed_to_double(y));
|
||||
}
|
||||
static void on_leave(void *data,struct wl_pointer *ptr,uint32_t serial,
|
||||
struct wl_surface *wl) {
|
||||
struct surface *s=which(wl);
|
||||
fprintf(stderr,"POINTER_LEAVE target=%s serial=%u\n",s?s->name:"OTHER",serial);
|
||||
if (entered==s) entered=NULL;
|
||||
}
|
||||
static void on_motion(void *data,struct wl_pointer *ptr,uint32_t time,
|
||||
wl_fixed_t x,wl_fixed_t y) {
|
||||
fprintf(stderr,"POINTER_MOTION target=%s x=%.2f y=%.2f\n",
|
||||
entered?entered->name:"OTHER",wl_fixed_to_double(x),wl_fixed_to_double(y));
|
||||
}
|
||||
static void on_button(void *data,struct wl_pointer *ptr,uint32_t serial,
|
||||
uint32_t time,uint32_t button,uint32_t state) {
|
||||
fprintf(stderr,"POINTER_BUTTON target=%s serial=%u button=%u state=%u\n",
|
||||
entered?entered->name:"OTHER",serial,button,state);
|
||||
if (state!=WL_POINTER_BUTTON_STATE_PRESSED || button!=272) return;
|
||||
if (entered==&top && !a.popup) open_popup(&a,&top,serial);
|
||||
else if (entered==&a && a.mapped && !b.popup) open_popup(&b,&a,serial);
|
||||
}
|
||||
static void on_axis(void *data,struct wl_pointer *ptr,uint32_t t,uint32_t axis,wl_fixed_t v) {}
|
||||
static void on_frame(void *data,struct wl_pointer *ptr) {}
|
||||
static const struct wl_pointer_listener pointer_listener={
|
||||
.enter=on_enter,.leave=on_leave,.motion=on_motion,.button=on_button,
|
||||
.axis=on_axis,.frame=on_frame,
|
||||
};
|
||||
static void on_caps(void *data,struct wl_seat *s,uint32_t caps) {
|
||||
if ((caps & WL_SEAT_CAPABILITY_POINTER) && !pointer) {
|
||||
pointer=wl_seat_get_pointer(s);
|
||||
wl_pointer_add_listener(pointer,&pointer_listener,NULL);
|
||||
}
|
||||
}
|
||||
static void on_seat_name(void *data,struct wl_seat *s,const char *name) {}
|
||||
static const struct wl_seat_listener seat_listener={.capabilities=on_caps,.name=on_seat_name};
|
||||
static void on_global(void *data,struct wl_registry *reg,uint32_t name,
|
||||
const char *interface,uint32_t version) {
|
||||
if (!strcmp(interface,wl_compositor_interface.name))
|
||||
compositor=wl_registry_bind(reg,name,&wl_compositor_interface,version<4?version:4);
|
||||
else if (!strcmp(interface,wl_shm_interface.name))
|
||||
shm=wl_registry_bind(reg,name,&wl_shm_interface,1);
|
||||
else if (!strcmp(interface,xdg_wm_base_interface.name))
|
||||
wm=wl_registry_bind(reg,name,&xdg_wm_base_interface,version<3?version:3);
|
||||
else if (!strcmp(interface,wl_seat_interface.name)) {
|
||||
seat=wl_registry_bind(reg,name,&wl_seat_interface,version<5?version:5);
|
||||
wl_seat_add_listener(seat,&seat_listener,NULL);
|
||||
}
|
||||
}
|
||||
static void on_removed(void *data,struct wl_registry *reg,uint32_t name) {}
|
||||
static const struct wl_registry_listener registry_listener={.global=on_global,.global_remove=on_removed};
|
||||
static void stop(int sig) { running=0; }
|
||||
int main(int argc,char **argv) {
|
||||
signal(SIGTERM,stop);
|
||||
const char *placement=argc>1?argv[1]:"b_cross";
|
||||
/* CONTRACT: vertical positioner anchors remain inside the toplevel's
|
||||
* effective window geometry, which defaults to its buffer bounds. */
|
||||
if (!strncmp(placement,"v_",2)) top.height=575;
|
||||
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,"v_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,"v_b_cross")) {
|
||||
/* WHY: only B's requested rectangle reaches below y=600 when the
|
||||
* tiled toplevel content begins near y=25. Configure and pixels
|
||||
* remain the authority for the actual crossing. */
|
||||
a.anchor_x=50; a.anchor_y=350; a.width=250;
|
||||
b.anchor_x=20; b.anchor_y=155; b.width=170;
|
||||
} else if (!strcmp(placement,"v_both_cross")) {
|
||||
a.anchor_x=50; a.anchor_y=480; a.width=250;
|
||||
b.anchor_x=20; b.anchor_y=80; b.width=170;
|
||||
} else if (!strcmp(placement,"both_cross")) {
|
||||
a.anchor_x=350; a.anchor_y=80; a.width=350;
|
||||
b.anchor_x=160; b.anchor_y=60; b.width=300;
|
||||
} else if (!strcmp(placement,"b_cross")) {
|
||||
a.anchor_x=200; a.anchor_y=80; a.width=300;
|
||||
b.anchor_x=220; b.anchor_y=60; b.width=300;
|
||||
} else { fprintf(stderr,"invalid placement\n"); return 2; }
|
||||
display=wl_display_connect(NULL);
|
||||
if (!display) { fprintf(stderr,"cannot connect Wayland\n"); return 1; }
|
||||
struct wl_registry *registry=wl_display_get_registry(display);
|
||||
wl_registry_add_listener(registry,®istry_listener,NULL);
|
||||
wl_display_roundtrip(display); wl_display_roundtrip(display);
|
||||
if (!compositor || !shm || !wm || !seat || !pointer) {
|
||||
fprintf(stderr,"missing Wayland globals/pointer\n"); return 1;
|
||||
}
|
||||
xdg_wm_base_add_listener(wm,&wm_listener,NULL);
|
||||
top.wl=wl_compositor_create_surface(compositor);
|
||||
top.xdg=xdg_wm_base_get_xdg_surface(wm,top.wl);
|
||||
xdg_surface_add_listener(top.xdg,&xdg_listener,&top);
|
||||
top_role=xdg_surface_get_toplevel(top.xdg);
|
||||
xdg_toplevel_add_listener(top_role,&top_listener,NULL);
|
||||
xdg_toplevel_set_title(top_role,"Mosaic Nested Grab Probe");
|
||||
xdg_toplevel_set_app_id(top_role,"org.mosaic.NestedGrabProbe");
|
||||
fprintf(stderr,"TOPLEVEL CREATE wl=%u xdg=%u role=%u placement=%s\n",
|
||||
wl_proxy_get_id((struct wl_proxy *)top.wl),
|
||||
wl_proxy_get_id((struct wl_proxy *)top.xdg),
|
||||
wl_proxy_get_id((struct wl_proxy *)top_role),placement);
|
||||
wl_surface_commit(top.wl);
|
||||
while (running) {
|
||||
wl_display_flush(display);
|
||||
struct pollfd fds[2]={{.fd=wl_display_get_fd(display),.events=POLLIN},
|
||||
{.fd=STDIN_FILENO,.events=POLLIN}};
|
||||
int ready=poll(fds,2,500);
|
||||
if (ready<0) { if (errno==EINTR) continue; perror("poll"); break; }
|
||||
if (fds[0].revents & (POLLIN|POLLERR|POLLHUP)) {
|
||||
if (wl_display_dispatch(display)<0) break;
|
||||
collect_dismissals();
|
||||
}
|
||||
if (fds[1].revents & POLLIN) {
|
||||
char cmd[64];
|
||||
if (fgets(cmd,sizeof(cmd),stdin)) {
|
||||
if (!strncmp(cmd,"close_b",7)) { destroy_popup(&b); fprintf(stderr,"COMMAND close_b DONE\n"); }
|
||||
else if (!strncmp(cmd,"close_owner",11)) { fprintf(stderr,"CLOSE_RECEIVED\nCOMMAND close_owner\n"); running=0; }
|
||||
}
|
||||
}
|
||||
}
|
||||
destroy_popup(&b); destroy_popup(&a);
|
||||
fprintf(stderr,"TOPLEVEL DESTROY\n");
|
||||
xdg_toplevel_destroy(top_role); xdg_surface_destroy(top.xdg); wl_surface_destroy(top.wl);
|
||||
wl_display_roundtrip(display);
|
||||
fprintf(stderr,"SHM_CLOSURE created=%u released=%u\n",shm_created,shm_released);
|
||||
wl_display_flush(display);
|
||||
/* WHY/CONTRACT/INVARIANT: exit0 does not prove protocol error0. */
|
||||
fprintf(stderr,"EXIT error=%d\n",wl_display_get_error(display));
|
||||
wl_display_disconnect(display);
|
||||
return 0;
|
||||
}
|
||||
275
tooling/probes/step20c-corec-v2/graphics/engine.py
Normal file
275
tooling/probes/step20c-corec-v2/graphics/engine.py
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
"""Bounded owned headless session, usable only after the separate matrix review gate."""
|
||||
import json,os,re,shutil,signal,subprocess,tempfile,threading,time
|
||||
from pathlib import Path
|
||||
from PIL import Image,ImageDraw
|
||||
from geometry import *
|
||||
from gate import sha,clean_environment,Ledger,canonical_lab
|
||||
|
||||
import sys
|
||||
|
||||
from ipc_predicates import nodes,workspace
|
||||
from predicates import protocol,geometry,pair,dismissal
|
||||
import validators
|
||||
from lifecycle import opening_pair
|
||||
from model import Provenance
|
||||
COLORS={'TOPLEVEL':(35,68,204),'POPUP_A':(224,192,32),'POPUP_B':(32,208,160)}
|
||||
|
||||
class Session:
|
||||
def __init__(self,spec,auth):
|
||||
need(not auth.get('offline'), 'OFFLINE_TEST_ONLY cannot launch');self.spec=spec;self.auth=auth;self.lab=canonical_lab(auth['lab']);self.ledger=Ledger(self.lab);self.folder=self.lab/'graphics-runs'/spec['id'];self.run=self.folder/'run';self.run.mkdir(parents=True)
|
||||
need(os.statvfs('/tmp').f_bavail*os.statvfs('/tmp').f_frsize>=256*1024*1024,'runtime /tmp free space');self.runtime=Path(tempfile.mkdtemp(prefix='smx-',dir='/tmp'));self.runtime.chmod(0o700)
|
||||
self.env=clean_environment(self.lab);self.env.update(XDG_RUNTIME_DIR=str(self.runtime),WLR_BACKENDS='headless',WLR_HEADLESS_OUTPUTS='2')
|
||||
self.outputs=[dict(o) for o in spec['outputs']];self.frames={o['name']:'logical' for o in self.outputs}
|
||||
self.proc_lock=threading.RLock();self.proc={};self.closed=[];self.helper_exits=[];self.handles=[];self.started=time.monotonic();self.finished=threading.Event();self.expired=False;self.events=[];self.pointer=None
|
||||
def start(self):
|
||||
spec=self.spec;auth=self.auth
|
||||
cfg='\n'.join(f"output {o['name']} resolution {o['mode'][0]}x{o['mode'][1]} scale {o['scale']} transform {o['transform']} position {o['logical'][0]} {o['logical'][1]}" for o in self.outputs)
|
||||
cfg+='\nworkspace 1 output HEADLESS-1\nworkspace 2 output HEADLESS-2\nfocus_follows_mouse no\nseat seat0 hide_cursor 100\nshadows disable\nblur disable\n'
|
||||
if spec['id'] in ('C3','C4'):
|
||||
marker=Image.new('RGB',(800,600),(30,30,46));draw=ImageDraw.Draw(marker)
|
||||
for k,box in [('TL',(15,15,44,44)),('TR',(755,15,784,44)),('BL',(15,555,44,584)),('BR',(755,555,784,584)),('C',(385,285,414,314))]:draw.rectangle(box,fill=MARKERS[k])
|
||||
marker.save(self.run/'marker-source.png');self.marker_source=self.run/'marker-source.png';self.record('generated-content-PNG',path=str(self.marker_source),counts_as_capture=False)
|
||||
need(cfg==self.spec['config'],'sealed config bytes');(self.run/'config').write_text(cfg);self.watch=threading.Thread(target=self.watchdog,daemon=True);self.watch.start()
|
||||
self.comp=self.spawn('compositor',[auth['binary'],'-d',*(['-Dtxn-timeout=5000'] if spec['id']=='C6' else []),'-c',self.run/'config'])
|
||||
self.sock=self.wait(lambda:next(iter(self.runtime.glob('sway-ipc.*.sock')),None));display=self.wait(lambda:next((p for p in self.runtime.glob('wayland-*') if p.is_socket()),None))
|
||||
self.env.update(WAYLAND_DISPLAY=display.name,SWAYSOCK=str(self.sock));need(Path(f'/proc/{self.comp.pid}/exe').resolve()==Path(auth['binary']).resolve() and sha(Path(f'/proc/{self.comp.pid}/exe'))==auth['binary_sha256'],'actual executable')
|
||||
maps=Path(f'/proc/{self.comp.pid}/maps').read_text();(self.run/'proc-maps.txt').write_text(maps)
|
||||
paths={s.split()[-1] for s in maps.splitlines() if len(s.split())>=6 and s.split()[-1].startswith('/')}
|
||||
wl=[Path(p).resolve() for p in paths if 'libwlroots' in p];need(wl==[Path(auth['wlroots'])] and sha(wl[0])==auth['wlroots_sha256'],'mapped wlroots')
|
||||
scene=[Path(p).resolve() for p in paths if 'libscenefx' in p]
|
||||
if spec['binary']=='core':need(not scene,'static SceneFX')
|
||||
else:need(scene==[Path(auth['pristine_scene'])] and sha(scene[0])==auth['pristine_scene_sha256'],'mapped pristine SceneFX')
|
||||
render=[s for s in self.log('compositor').splitlines() if any(x in s for x in ('Creating scenefx FX renderer','GL renderer:','GL vendor:','Using OpenGL ES'))]
|
||||
need(any('Creating scenefx FX renderer' in s for s in render) and any('GL renderer:' in s for s in render),'actual renderer')
|
||||
self.save('runtime-identity',dict(pid=self.comp.pid,executable=str(Path(f'/proc/{self.comp.pid}/exe').resolve()),binary_sha256=sha(Path(f'/proc/{self.comp.pid}/exe')),wlroots=[dict(path=str(p),sha256=sha(p)) for p in wl],renderer=render,egl=[line for line in self.log('compositor').splitlines() if 'EGL' in line],egl_source='compositor startup log; NOT_OBSERVABLE if empty',source_manifest=self.auth['manifest_sha256']));self.record('actual-renderer',lines=render);self.snapshot('startup')
|
||||
if spec['id'] in ('C3','C4'):
|
||||
self.spawn('marker-background',['swaybg','-o','HEADLESS-1','-i',self.marker_source,'-m','fill']);time.sleep(.35)
|
||||
im=self.capture('marker-H1','HEADLESS-1',orientation_pending=True);m=marker_frame(im,self.outputs[0]['scale'],self.outputs[0]['transform']=='90');self.frames['HEADLESS-1']=m['frame'];self.record('marker-frame',**m);self.close_helper('marker-background')
|
||||
self.pointer=self.spawn('pointer',[self.lab/'graphics-tests/client-build/pointer-daemon']);self.wait(lambda:'POINTER_READY' in self.log('pointer'))
|
||||
def close_helper(self,name):
|
||||
# WHY: swaybg is a calibration helper, not an XDG SHM test client.
|
||||
# CONTRACT: retire its owned group after the marker screenshot; retain
|
||||
# raw status separately. Never reinterpret SIGTERM as scientific close.
|
||||
need(name=='marker-background','unknown helper role');p=self.proc[name]
|
||||
if p.poll() is None:
|
||||
try:os.killpg(p.pid,signal.SIGTERM)
|
||||
except ProcessLookupError:pass
|
||||
code=p.wait(timeout=2)
|
||||
proof=dict(name=name,pid=p.pid,exit=code,expected_termination='owned helper SIGTERM after calibration',scientific_client=False)
|
||||
self.helper_exits.append(proof);self.record('helper-exit',**proof)
|
||||
need(code in (0,-signal.SIGTERM),'unexpected calibration helper exit')
|
||||
need(not Path('/proc',str(p.pid)).exists(),'calibration helper residual')
|
||||
if p.stdin and not p.stdin.closed:p.stdin.close()
|
||||
del self.proc[name]
|
||||
|
||||
def record(self,kind,**fields):
|
||||
e=dict(kind=kind,ns=time.monotonic_ns(),**fields);self.events.append(e)
|
||||
with (self.run/'events.jsonl').open('a') as f:f.write(json.dumps(e)+'\n')
|
||||
def save(self,name,value):
|
||||
(self.run/(name+'.json')).write_text(json.dumps(value,indent=2)+'\n')
|
||||
def debit(self,*kinds):self.ledger.action_debit(self.auth,*kinds)
|
||||
def spawn(self,name,argv,debug=False):
|
||||
self.debit('spawn');need(not self.expired,'expired before spawn');f=(self.run/(name+'.log')).open('w');self.handles.append(f)
|
||||
requested_argv=list(map(str,argv));prlimit=shutil.which('prlimit')
|
||||
need(prlimit is not None,'util-linux prlimit unavailable; refusing unbounded child')
|
||||
# WHY: a child core dump can exhaust the bounded graphics LAB.
|
||||
# CONTRACT: every owned child starts with a zero soft and hard core limit.
|
||||
# INVARIANT: exec keeps the child PID stable for /proc identity checks.
|
||||
actual_argv=[prlimit,'--core=0:0','--',*requested_argv]
|
||||
# WHY/CONTRACT/INVARIANT: registration and expiry are atomic: no child
|
||||
# can escape the watchdog's owned-group snapshot by racing its deadline.
|
||||
with getattr(self,'proc_lock',threading.RLock()):
|
||||
need(not self.expired,'expired before process creation')
|
||||
p=subprocess.Popen(actual_argv,stdin=subprocess.PIPE,stdout=f,stderr=subprocess.STDOUT,text=True,start_new_session=True,env={**self.env,**({'WAYLAND_DEBUG':'1'} if debug else {})})
|
||||
self.proc[name]=p
|
||||
self.record('spawn',name=name,pid=p.pid,requested_argv=requested_argv,actual_argv=actual_argv);return p
|
||||
def log(self,name):return (self.run/(name+'.log')).read_text(errors='replace')
|
||||
def wait(self,fn,seconds=5):
|
||||
end=min(self.started+143,time.monotonic()+seconds)
|
||||
while time.monotonic()<end:
|
||||
need(not self.expired,'watchdog')
|
||||
if hasattr(self,'comp'):need(self.comp.poll() is None,'unexpected compositor exit')
|
||||
v=fn()
|
||||
if v:return v
|
||||
time.sleep(.02)
|
||||
raise Ambiguous('bounded wait timeout')
|
||||
def remaining(self,deadline=146):
|
||||
return max(.001,self.started+deadline-time.monotonic())
|
||||
def signal_owned(self,sig):
|
||||
with getattr(self,'proc_lock',threading.RLock()):
|
||||
for p in list(self.proc.values()):
|
||||
try:os.killpg(p.pid,sig)
|
||||
except ProcessLookupError:pass
|
||||
def watchdog(self):
|
||||
# WHY: main-thread protocol closure may block or fail.
|
||||
# CONTRACT: autonomous TERM at143, KILL by146, below150s/run.
|
||||
# INVARIANT: only registered private session groups are signalled;
|
||||
# the main cleanup shares this absolute deadline, never N grace periods.
|
||||
while not self.finished.wait(.02):
|
||||
if time.monotonic()>=self.started+143 or any(p.stat().st_size>32*1024*1024 for p in self.run.glob('*.log')):
|
||||
with getattr(self,'proc_lock',threading.RLock()):self.expired=True
|
||||
self.signal_owned(signal.SIGTERM)
|
||||
grace_end=min(self.started+146,time.monotonic()+2)
|
||||
if not self.finished.wait(max(0,grace_end-time.monotonic())):
|
||||
self.signal_owned(signal.SIGKILL)
|
||||
return
|
||||
def ipc(self,*args,strict=True):
|
||||
need(self.sock.is_relative_to(self.runtime) and self.sock.is_socket(),'private socket')
|
||||
self.record('ipc-request',args=args)
|
||||
p=subprocess.run(['swaymsg','-s',str(self.sock),*args],env=self.env,capture_output=True,text=True,timeout=4)
|
||||
self.record('ipc-reply',args=args,exit=p.returncode,stdout=p.stdout,stderr=p.stderr)
|
||||
if strict:need(p.returncode==0,'IPC command failure')
|
||||
return json.loads(p.stdout) if p.returncode==0 else None
|
||||
def cmd(self,command,business=False):
|
||||
self.debit('mutation' if business else 'configuration');reply=self.ipc(command);need(all(r['success'] for r in reply),'command rejected')
|
||||
def snapshot(self,tag,validate=True):
|
||||
snap=dict(tree=self.ipc('-t','get_tree'),outputs=self.ipc('-t','get_outputs'),workspaces=self.ipc('-t','get_workspaces'),ns=time.monotonic_ns())
|
||||
if validate:expected_outputs(snap['outputs'],self.outputs)
|
||||
self.save(tag+'-snapshot',snap);return snap
|
||||
def find(self,name):
|
||||
pid=self.proc[name].pid;rows=[n for n in nodes(self.ipc('-t','get_tree')) if n.get('pid')==pid];need(len(rows)<=1,'client owner ambiguity');return rows[0] if rows else None
|
||||
def launch_client(self,name,client,*args):
|
||||
p=self.spawn(name,[self.lab/'graphics-tests/client-build'/client,*args],True);self.wait(lambda:self.find(name));time.sleep(.25);return p
|
||||
def capture(self,tag,out,orientation_pending=False):
|
||||
self.debit('capture');spec=next(o for o in self.outputs if o['name']==out);p=self.run/(tag+'.png');need(not p.exists(),'capture unique')
|
||||
t=time.monotonic_ns();r=subprocess.run(['grim','-s',str(spec['scale']),'-o',out,str(p)],env=self.env,capture_output=True,text=True,timeout=4)
|
||||
self.record('capture',tag=tag,output=out,start_ns=t,end_ns=time.monotonic_ns(),exit=r.returncode,path=str(p),sha256=sha(p) if p.exists() else None)
|
||||
need(r.returncode==0,'capture failure');im=Image.open(p).convert('RGB')
|
||||
logical=tuple(round(v*spec['scale']) for v in spec['logical'][2:]);physical=tuple(spec['mode'])
|
||||
need(im.size==logical or (orientation_pending and im.size==physical) or (self.frames[out]=='physical90' and im.size==physical),'PNG size')
|
||||
return im
|
||||
def pair_capture(self,tag):return [self.capture(tag+'-H1','HEADLESS-1'),self.capture(tag+'-H2','HEADLESS-2')]
|
||||
def inject(self,action,point,grab=False):
|
||||
need(action in ('move','left','right'),'injection action');outs=self.ipc('-t','get_outputs');active=[o for o in outs if o['active']]
|
||||
rects=[[o['rect'][k] for k in ('x','y','width','height')] for o in active];need(any(contains(r,point) for r in rects),'logical point outside output')
|
||||
minx=min(r[0] for r in rects);miny=min(r[1] for r in rects);w=max(r[0]+r[2] for r in rects)-minx;h=max(r[1]+r[3] for r in rects)-miny
|
||||
self.debit('move',*(['click'] if action!='move' else []),*(['grab'] if grab else []))
|
||||
sent=[point[0]-minx,point[1]-miny];marker=f'INJECT_DONE {action} {sent[0]},{sent[1]}';before=self.log('pointer').count(marker)
|
||||
self.pointer.stdin.write(f'{action} {sent[0]} {sent[1]} {w} {h}\n');self.pointer.stdin.flush();self.wait(lambda:self.log('pointer').count(marker)>before)
|
||||
self.record('injection',action=action,global_logical=point,sent=sent,logical_extent=[w,h],grab=grab);time.sleep(.12)
|
||||
def click(self,name,point,action='right',grab=False):
|
||||
before={n:len(self.log(n)) for n in self.proc if n not in ('compositor','pointer','background')};t=time.monotonic_ns();self.inject(action,point,grab)
|
||||
validators.window(t,time.monotonic_ns(),[e['ns'] for e in self.events if e['kind']=='injection' and e['ns']>=t]);tails={n:self.log(n)[i:] for n,i in before.items()};lines={n:[s for s in v.splitlines() if s.startswith(('POINTER_BUTTON ','BUTTON '))] for n,v in tails.items()}
|
||||
self.record('click-observation',name=name,point=point,start_ns=t,tails=tails,buttons=lines)
|
||||
return tails,lines
|
||||
def delivery(self,name,rect,point,tails,lines,target,opening_serial=None):
|
||||
need(not any(v for n,v in lines.items() if n!=name),'wrong client received popup button')
|
||||
proof=opening_pair(lines[name],target,opening_serial) if opening_serial else pair(lines[name],target)
|
||||
rows=re.findall(rf'POINTER_(?:ENTER|MOTION) target={target} .*?x=([\d.-]+) y=([\d.-]+)',tails[name]);need(rows,'fresh pointer local coordinates')
|
||||
local=list(map(float,rows[-1]));validators.applied(rect[:2],rect[:2],[point[0]-local[0],point[1]-local[1]],'applied')
|
||||
for e in proof:need(re.search(rf'wl_pointer#\d+\.button\({e[1]}, \d+, {e[2]}, {e[3]}\)',tails[name]) is not None,'button actual wire')
|
||||
return proof
|
||||
def owner(self,name):
|
||||
snap=self.snapshot(name+'-owner')
|
||||
rows=[n for n in nodes(snap['tree']) if n.get('pid')==self.proc[name].pid]
|
||||
need(len(rows)==1,'owner identity/ambiguity');owner=rows[0]
|
||||
ws=workspace(snap['tree'],'1')
|
||||
path=__import__('ipc_predicates').ancestry(snap['tree'],owner['id'])
|
||||
validators.owner_context(owner,path,self.proc[name].pid,'1',getattr(self,'expected_output','HEADLESS-1'),getattr(self,'expected_layout','mosaic' if self.spec['mode']=='mosaic' else 'splith'))
|
||||
# WHY: standard_return changes layout after the initial campaign mode.
|
||||
# CONTRACT: camera applies only to current Mosaic tiled descendants;
|
||||
# floating branches and fullscreen group ancestry remain exempt.
|
||||
# WHY/CONTRACT: root/output/workspace IPC descriptors may have floating=null.
|
||||
# INVARIANT: null means no floating flag; branch membership still counts,
|
||||
# and this geometry remains a pending candidate, never native-applied.
|
||||
eligible=validators.camera_eligible(path,ws)
|
||||
origin=assigned_origin(owner);scene=exterior_ipc(owner)
|
||||
if eligible:
|
||||
scroll=int(ws['mosaic']['rows'][0]['scroll_x']);origin[0]-=scroll;scene[0]-=scroll
|
||||
# INVARIANT: IPC never becomes native-applied without fresh paint and pointer corroboration.
|
||||
raw='current-layout IPC candidate; native paint and fresh pointer-local corroboration required, generation NOT OBSERVABLE'
|
||||
data=dict(geometry_status='IPC_PENDING_CANDIDATE; requires fresh paint/pointer after every mutation',owner=owner,workspace=ws,origin=origin,scene=scene,evidence=raw,snapshot=snap);self.save(name+'-geometry',data);return data
|
||||
def calibrate(self,name):
|
||||
state=self.owner(name)
|
||||
if self.spec['mode']=='mosaic' and self.spec['axis']==0:
|
||||
delta=int(state['origin'][0]+state['owner']['window_rect']['width']//2)
|
||||
if delta:self.scroll(name,delta)
|
||||
state=self.owner(name);need(crossing([*state['origin'],state['owner']['window_rect']['width'],state['owner']['window_rect']['height']],0,0),'owner real x0 crossing')
|
||||
return state
|
||||
def scroll(self,name,delta):
|
||||
self.debit('camera');self.cmd(f'[con_id={self.find(name)["id"]}] layout mosaic scroll {int(delta)}');time.sleep(.35)
|
||||
self.record('camera',owner=self.find(name)['id'],delta=int(delta))
|
||||
def client_cmd(self,name,cmd,marker=None):
|
||||
self.debit('configuration' if cmd.startswith('close') else 'mutation');before=len(self.log(name));self.record('client-request',name=name,command=cmd)
|
||||
self.proc[name].stdin.write(cmd+'\n');self.proc[name].stdin.flush()
|
||||
if marker:self.wait(lambda:marker in self.log(name)[before:])
|
||||
def close_client(self,name,command='close_owner'):
|
||||
# WHY: exit0 alone cannot prove SHM release or an explicit normal close.
|
||||
# CONTRACT: retain each PID's validation, including retired same-name owners.
|
||||
# INVARIANT: a failed closure is durable evidence, never laundered by cleanup.
|
||||
p=self.proc[name];proof=dict(name=name,pid=p.pid,status='INCONCLUSIVE',failure=None)
|
||||
try:
|
||||
if p.poll() is None:self.client_cmd(name,command)
|
||||
code=p.wait(timeout=min(3,self.remaining()));raw=self.log(name)
|
||||
archive=self.run/f'{name}-closed-{p.pid}.log';archive.write_text(raw)
|
||||
proof.update(exit=code,log=str(archive),sha256=sha(archive),close_received='CLOSE_RECEIVED' in raw,shm_closure=re.findall(r'SHM_CLOSURE created=(\d+) released=(\d+)',raw),wayland_errors=re.findall(r'EXIT[^\n]*error=(\d+)',raw))
|
||||
validators.client_exit(raw,code);self.wait(lambda:self.find(name) is None)
|
||||
proof['status']='PASS'
|
||||
except Exception as error:
|
||||
proof['failure']=f'{type(error).__name__}: {error}'
|
||||
proof.setdefault('exit',p.poll())
|
||||
raise
|
||||
finally:
|
||||
self.closed.append(proof);self.record('client-closed',**proof)
|
||||
|
||||
def cleanup(self,measurement_status,failure=None):
|
||||
terminal=None;rescue=False;closures=[]
|
||||
# Close test clients while their Wayland server still exists. Mechanical
|
||||
# rescue and terminal IPC can never substitute for scientific closure.
|
||||
for name in reversed(list(self.proc)):
|
||||
if name in ('compositor','pointer') or any(c['pid']==self.proc[name].pid for c in self.closed):continue
|
||||
if self.expired or time.monotonic()>=self.started+143:break
|
||||
try:
|
||||
self.close_client(name,'close_owner' if name=='owner' and self.spec['client']!='saved-client' and self.spec['id']!='C6' else 'close')
|
||||
except Exception:pass # close_client retains the explicit validation failure.
|
||||
# WHY/CONTRACT/INVARIANT: archived and already-exited clients count equally;
|
||||
# an empty list of newly closed processes must not erase prior failures.
|
||||
closures=[dict(c) for c in self.closed]
|
||||
tracked=[p.pid for n,p in self.proc.items() if n not in ('compositor','pointer')]
|
||||
closure_coverage=all(any(c['pid']==pid for c in closures) for pid in tracked)
|
||||
|
||||
if hasattr(self,'comp') and self.comp.poll() is None and hasattr(self,'sock') and self.sock.is_socket() and not self.expired and time.monotonic()<self.started+145:
|
||||
try:
|
||||
self.debit('configuration')
|
||||
p=subprocess.run(['swaymsg','-s',str(self.sock),'exit'],env=self.env,capture_output=True,text=True,timeout=min(2,self.remaining()))
|
||||
terminal=dict(ipc_exit=p.returncode,stdout=p.stdout,stderr=p.stderr)
|
||||
self.save('terminal-ipc',terminal)
|
||||
except Exception as e:
|
||||
terminal=dict(ipc_error=str(e));self.save('terminal-ipc',terminal)
|
||||
if terminal is not None and 'ipc_exit' in terminal:
|
||||
try:self.comp.wait(timeout=min(1,self.remaining()))
|
||||
except Exception as e:
|
||||
terminal['wait_error']=str(e);self.save('terminal-ipc',terminal)
|
||||
codes=[dict(name=c['name'],pid=c['pid'],exit=c['exit']) for c in self.closed if not any(p.pid==c['pid'] for p in self.proc.values())]
|
||||
# One shared cleanup deadline prevents N owned clients multiplying waits.
|
||||
cleanup_end=min(self.started+146,time.monotonic()+2)
|
||||
if self.pointer and self.pointer.poll() is None and self.pointer.stdin and not self.pointer.stdin.closed:self.pointer.stdin.close()
|
||||
for name,p in reversed(list(self.proc.items())):
|
||||
if p.poll() is None:
|
||||
try:p.wait(timeout=max(.01,cleanup_end-time.monotonic()))
|
||||
except subprocess.TimeoutExpired:pass
|
||||
residual=[p for p in self.proc.values() if p.poll() is None]
|
||||
if residual:
|
||||
rescue=True
|
||||
for p in residual:
|
||||
try:os.killpg(p.pid,signal.SIGKILL)
|
||||
except ProcessLookupError:pass
|
||||
kill_end=min(self.started+147,time.monotonic()+1)
|
||||
for p in residual:p.wait(timeout=max(.01,kill_end-time.monotonic()))
|
||||
for name,p in self.proc.items():
|
||||
codes.append(dict(name=name,pid=p.pid,exit=p.returncode))
|
||||
if p.stdin and not p.stdin.closed:p.stdin.close()
|
||||
self.finished.set();self.watch.join(timeout=1)
|
||||
for h in self.handles:h.close()
|
||||
for p in self.proc.values():
|
||||
try:os.killpg(p.pid,0)
|
||||
except ProcessLookupError:pass
|
||||
else:rescue=True;os.killpg(p.pid,signal.SIGKILL)
|
||||
reaped=all(not Path('/proc',str(p.pid)).exists() for p in self.proc.values());need(reaped,'owned process residual');shutil.rmtree(self.runtime)
|
||||
seconds=time.monotonic()-self.started
|
||||
protocol_errors=[n for n in self.proc if n not in ('compositor','pointer') and re.search(r'wl_display[^\n]*\.error\(|protocol error|invalid_popup_parent|not_the_topmost_popup',self.log(n),re.I)]
|
||||
cleanup_pass=bool(closure_coverage and all(c['status']=='PASS' for c in closures) and not rescue and reaped and seconds<=150 and not self.expired and not protocol_errors and all(p['exit']==0 for p in codes))
|
||||
transport_pass=terminal is not None and terminal.get('ipc_exit') in (0,1) and 'wait_error' not in terminal and 'ipc_error' not in terminal
|
||||
final_status=measurement_status if failure is None and cleanup_pass and transport_pass else 'INCONCLUSIVE'
|
||||
self.save('outcome',dict(status=final_status,measurements_pass=measurement_status,architecture_verdict='INCONCLUSIVE',evidence_provenance=self.auth,untested_matrix_cells=['ATOMIC_GENERATION','COMPLETE_DAMAGE','NATIVE_BUFFER_IDENTITY','GPU_LIFETIME'],scientific_cleanup='PASS' if cleanup_pass else 'INCONCLUSIVE',runtime_exit=codes,measurements_status=measurement_status,failure=failure,terminal=terminal,helper_exits=getattr(self,'helper_exits',[]),explicit_client_closures=self.closed,mechanical_cleanup='PASS' if reaped and not rescue and not self.runtime.exists() else 'INCONCLUSIVE',client_closures=closures,processes=codes,rescue=rescue,reaped=reaped,runtime_removed=not self.runtime.exists(),seconds=seconds,watchdog=self.expired,protocol_errors=protocol_errors,cleanup_pass=cleanup_pass,transport_status='PASS' if transport_pass else 'INCONCLUSIVE'))
|
||||
self.ledger.complete(self.auth,dict(status=final_status,runner_failure=failure,terminal=terminal,processes=codes,scientific_cleanup=cleanup_pass,mechanical_rescue=rescue));return final_status
|
||||
29
tooling/probes/step20c-corec-v2/graphics/evidence.py
Normal file
29
tooling/probes/step20c-corec-v2/graphics/evidence.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
"""Mandatory raw evidence validators; declarations never synthesize native facts."""
|
||||
from gate import need
|
||||
from validators import fullscreen_ancestor
|
||||
|
||||
def fullscreen_certificate(pristine,core):
|
||||
# Offline-only certificate: no calibration client or full-screen run is started.
|
||||
for record in (pristine,core):
|
||||
ancestor=fullscreen_ancestor(record["owner_path"])
|
||||
need(record["native_xdg"] is True and record["configure_ack"]>0,"normal XDG configure/ACK")
|
||||
children=ancestor["nodes"]
|
||||
need(len(children)>=2 and record["owner_id"] in {x["id"] for x in children},"nonleaf direct children")
|
||||
need(record["configure"][0]>0 and record["configure"][1]>0,"configure dimensions")
|
||||
a,b=record["popup_A"],record["popup_B"]
|
||||
need(a[0]<record["boundary"]<a[0]+a[2] and b[0]<record["boundary"]<b[0]+b[2],"real plausible crossing")
|
||||
need(pristine["configure"]==core["configure"] and pristine["child_configures"]==core["child_configures"],"noncomparable fullscreen children/configure")
|
||||
return dict(status="OFFLINE_CERTIFICATE_ONLY",runtime="NOT_RUN")
|
||||
|
||||
def native_present(commits,presents):
|
||||
# Correlation requires an actual native event and exact run/output/sequence key.
|
||||
keys=set()
|
||||
for event in commits:
|
||||
need(event.get("source")=="native-output-commit" and event.get("native_event") is True,"fake commit")
|
||||
key=(event["run"],event["output"],event["commit_seq"]);need(key not in keys,"duplicate commit key");keys.add(key)
|
||||
seen=set()
|
||||
for event in presents:
|
||||
need(event.get("source")=="native-output-present" and event.get("native_event") is True,"fake present")
|
||||
key=(event["run"],event["output"],event["commit_seq"])
|
||||
need(key in keys and key not in seen,"unmatched/duplicate native present");seen.add(key)
|
||||
return dict(matched=len(seen),unmatched_commits=len(keys-seen),gpu_completion="NOT_PROVEN",native_buffer_identity="NOT_PROVEN",atomic_generation="NOT_PROVEN")
|
||||
15
tooling/probes/step20c-corec-v2/graphics/freeze.py
Normal file
15
tooling/probes/step20c-corec-v2/graphics/freeze.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Freeze source-only bytes. No ledger initialization/admission/reservation."""
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from gate import canonical_lab,sha,durable,TICKET
|
||||
from plans import RUNS,LIMITS
|
||||
|
||||
def freeze(lab):
|
||||
lab=canonical_lab(lab);root=Path(__file__).resolve().parent
|
||||
files={str(p.relative_to(root)):sha(p) for p in sorted(root.rglob("*")) if p.is_file()}
|
||||
if any(name.endswith((".pyc",".png",".so",".o")) or "__pycache__" in name for name in files):raise ValueError("generated artifact in source kit")
|
||||
result=dict(schema="graphics-v2-source-freeze",ticket=TICKET,source_root=str(root),files=files,plans=RUNS,limits=LIMITS,preflight="PENDING_INDEPENDENT_REVIEW",runtime="NOT_RUN",real_reservations=0)
|
||||
durable(lab/"results/graphics-source-freeze.json",result);return result
|
||||
if __name__=="__main__":
|
||||
p=argparse.ArgumentParser();p.add_argument("--lab",required=True);a=p.parse_args();freeze(a.lab)
|
||||
132
tooling/probes/step20c-corec-v2/graphics/gate.py
Normal file
132
tooling/probes/step20c-corec-v2/graphics/gate.py
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
"""V2 reviewed admission and durable debit. No reset/refund or ledger CLI.
|
||||
|
||||
WHY: reservation failure is consumed; immutable input SHA and run token bind
|
||||
all later actions. The lock inode remains stable across atomic JSON replaces.
|
||||
OFFLINE_TEST_ONLY ledgers are never accepted as native-run authorizations.
|
||||
"""
|
||||
import fcntl,hashlib,json,os,re,stat,time,uuid
|
||||
from pathlib import Path
|
||||
from plans import RUNS,LIMITS
|
||||
TICKET="SWAYFX_MOSAIC_STEP20C_REBUILD_FROM_GIT_COREC_OOM_GPU_NIGHT_SHIFT_V1"
|
||||
WL_SHA="7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837"
|
||||
def need(v,m):
|
||||
if not v:raise ValueError(m)
|
||||
def sha(p):return hashlib.sha256(Path(p).read_bytes()).hexdigest()
|
||||
def encoded(value):return (json.dumps(value,sort_keys=True,indent=2)+"\n").encode()
|
||||
def digest(value):return hashlib.sha256(encoded(value)).hexdigest()
|
||||
def canonical_lab(lab):
|
||||
p=Path(lab);need(p.is_absolute() and str(p)==str(p.resolve()) and p.parent==Path("/var/tmp"),"canonical direct /var/tmp LAB required")
|
||||
st=p.stat();need(st.st_uid==os.getuid() and stat.S_ISDIR(st.st_mode),"LAB owner/type")
|
||||
return p
|
||||
|
||||
def clean_environment(lab):
|
||||
lab=canonical_lab(lab)
|
||||
need((lab/"scratch").resolve()==lab/"scratch" and (lab/"scratch").stat().st_uid==os.getuid(),"canonical owned scratch required")
|
||||
# Allowlist avoids inherited loader, backend, activation and desktop authority.
|
||||
e={k:os.environ[k] for k in ("PATH","LANG","LC_ALL","TZ") if k in os.environ}
|
||||
e.update(TMPDIR=str(lab/"scratch"),TMP=str(lab/"scratch"),TEMP=str(lab/"scratch"),SMX_LAB=str(lab),XDG_CACHE_HOME=str(lab/"cache"),XDG_DATA_HOME=str(lab/"data"),PYTHONPYCACHEPREFIX=str(lab/"pycache"),CCACHE_DIR=str(lab/"cache/ccache"))
|
||||
return e
|
||||
|
||||
def durable(path,value,exclusive=False):
|
||||
p=Path(path);tmp=p.with_name(p.name+".tmp-"+uuid.uuid4().hex)
|
||||
try:
|
||||
with tmp.open("xb") as f:f.write(encoded(value));f.flush();os.fsync(f.fileno())
|
||||
if exclusive:os.link(tmp,p);tmp.unlink()
|
||||
else:os.replace(tmp,p)
|
||||
fd=os.open(p.parent,os.O_RDONLY|os.O_DIRECTORY)
|
||||
try:os.fsync(fd)
|
||||
finally:os.close(fd)
|
||||
finally:
|
||||
if tmp.exists():tmp.unlink()
|
||||
|
||||
class Ledger:
|
||||
def __init__(self,lab,path=None,offline=False):
|
||||
self.lab=canonical_lab(lab);self.offline=offline
|
||||
self.path=Path(path) if path else self.lab/"budget.json"
|
||||
need(self.path.parent.resolve().is_relative_to(self.lab),"ledger outside LAB")
|
||||
need(not self.path.is_symlink(),"ledger symlink")
|
||||
if offline:need("OFFLINE_TEST_ONLY" in self.path.parts,"offline fixture namespace required")
|
||||
else:need(self.path==self.lab/"budget.json","real ledger fixed path")
|
||||
self.lock=self.path.with_name(self.path.name+".lock")
|
||||
def transaction(self,fn,initialize=False):
|
||||
fd=os.open(self.lock,os.O_RDWR|os.O_CREAT|os.O_NOFOLLOW,0o600)
|
||||
try:
|
||||
st=os.fstat(fd);need(st.st_uid==os.getuid() and stat.S_ISREG(st.st_mode) and st.st_nlink==1 and not st.st_mode&0o077,"stable lock owner/type/link")
|
||||
fcntl.flock(fd,fcntl.LOCK_EX)
|
||||
need(os.stat(self.lock,follow_symlinks=False).st_ino==st.st_ino,"lock inode changed")
|
||||
if initialize:need(not self.path.exists(),"ledger already initialized");d=None
|
||||
else:
|
||||
s=self.path.lstat();need(s.st_uid==os.getuid() and stat.S_ISREG(s.st_mode) and s.st_nlink==1,"ledger owner/type/link")
|
||||
d=json.loads(self.path.read_text());need(d["ticket"]==TICKET and d["lab"]==str(self.lab) and d["uid"]==os.getuid(),"old ticket/LAB/UID")
|
||||
need(d["namespace"]==("OFFLINE_TEST_ONLY" if self.offline else "REAL") and d["limits"]==LIMITS,"namespace/limits")
|
||||
value,new=fn(d);durable(self.path,new,exclusive=initialize);return value
|
||||
finally:os.close(fd)
|
||||
def initialize(self):
|
||||
return self.transaction(lambda _: (None,dict(ticket=TICKET,lab=str(self.lab),uid=os.getuid(),namespace="OFFLINE_TEST_ONLY" if self.offline else "REAL",limits=LIMITS,runs=[])),True)
|
||||
def admit(self,rid,manifest,review,publication):
|
||||
auth=validate_review(self.lab,manifest,review,publication,rid,offline=self.offline)
|
||||
def reserve(d):
|
||||
spec=RUNS[rid];need(not any(r["id"]==rid for r in d["runs"]),"consumed run")
|
||||
need(len(d["runs"])<14 and sum(r["capture_limit"] for r in d["runs"])+spec["capture_limit"]<=180,"global budget")
|
||||
token=uuid.uuid4().hex;r=dict(id=rid,token=token,started_ns=time.monotonic_ns(),capture_limit=spec["capture_limit"],action_limits=spec["action_limits"],actions={},status="RESERVED",bindings=auth)
|
||||
d["runs"].append(r);return dict(auth,token=token,run_id=rid,lab=str(self.lab),offline=self.offline),d
|
||||
return self.transaction(reserve)
|
||||
def action_debit(self,auth,*kinds):
|
||||
# Revalidate all frozen source/review/publication/binary bytes before each action.
|
||||
fresh=validate_review(self.lab,auth["manifest"],auth["review"],auth["publication"],auth["run_id"],offline=self.offline)
|
||||
def update(d):
|
||||
rows=[r for r in d["runs"] if r["id"]==auth["run_id"]];need(len(rows)==1,"missing reservation");r=rows[0]
|
||||
need(r["token"]==auth["token"] and r["bindings"]==fresh and r["status"]=="RESERVED","token/stale bindings/completed")
|
||||
need(time.monotonic_ns()-r["started_ns"]<150_000_000_000,"duration exhausted")
|
||||
actions=dict(r["actions"])
|
||||
for kind in kinds:
|
||||
limit=r["capture_limit"] if kind=="capture" else r["action_limits"].get(kind,0)
|
||||
need(actions.get(kind,0)<limit,"local "+kind+" exhausted");actions[kind]=actions.get(kind,0)+1
|
||||
r["actions"]=actions;return None,d
|
||||
return self.transaction(update)
|
||||
def complete(self,auth,outcome):
|
||||
def finish(d):
|
||||
rows=[r for r in d["runs"] if r["id"]==auth["run_id"]];need(len(rows)==1,"missing reservation");r=rows[0]
|
||||
need(r["token"]==auth["token"] and r["status"]=="RESERVED","token/completed")
|
||||
r.update(status="COMPLETED",finished_ns=time.monotonic_ns(),outcome=outcome);return None,d
|
||||
self.transaction(finish)
|
||||
|
||||
def validate_review(lab,manifest,review,publication,rid,offline=False):
|
||||
lab=canonical_lab(lab);manifest=Path(manifest);review=Path(review);publication=Path(publication)
|
||||
m=json.loads(manifest.read_text());r=json.loads(review.read_text());p=json.loads(publication.read_text())
|
||||
need(m.get("ticket")==TICKET and m.get("lab")==str(lab),"old ticket/LAB manifest")
|
||||
need(m.get("schema")=="graphics-v2-frozen-inputs" and m.get("limits")==LIMITS and m.get("plans")==RUNS,"manifest schema/plans/caps")
|
||||
need(r.get("decision")=="ACCEPTED" and r.get("independent") is True and r.get("reviewer")!=m.get("author") and r.get("reviewer"),"independent review absent")
|
||||
need(r.get("manifest_sha256")==sha(manifest) and rid in r.get("authorized_runs",[]),"stale review/case not admitted")
|
||||
need(RUNS[rid]["admission"]!="NOT_ADMITTED","scenario NOT_ADMITTED: "+RUNS[rid].get("reason",""))
|
||||
root=Path(m["source_root"]);need(root.resolve()==root and root.is_relative_to(lab),"source root outside LAB")
|
||||
need(m.get("files"),"empty inputs")
|
||||
for rel,h in m["files"].items():
|
||||
path=root/rel;need(not Path(rel).is_absolute() and path.resolve().is_relative_to(root) and path.is_file(),"input path")
|
||||
need(not any(v in path.parts for v in ("runs","results","logs","build","__pycache__")) and path not in (manifest,review,publication),"mutable/self input")
|
||||
need(sha(path)==h,"frozen source changed: "+rel)
|
||||
actual={str(x.relative_to(root)) for x in root.rglob("*") if x.is_file()}
|
||||
need(actual==set(m["files"]),"unfrozen source addition/removal")
|
||||
# Full source-only kit (including core recipes/patches/tests) has a separate,
|
||||
# immutable manifest outside the source tree. Parent records remote observation;
|
||||
# matching hex strings alone do not bind source bytes to that observation.
|
||||
kit_manifest=Path(m["kit_manifest"])
|
||||
need(sha(kit_manifest)==m["kit_manifest_sha256"],"full kit manifest changed")
|
||||
kit=json.loads(kit_manifest.read_text());kit_root=Path(kit["source_root"])
|
||||
need(kit_root.resolve()==kit_root and kit_root.is_relative_to(lab) and kit.get("ticket")==TICKET,"full kit root/ticket")
|
||||
need(root.is_relative_to(kit_root),"graphics outside full kit")
|
||||
need(not kit_manifest.is_relative_to(kit_root),"full kit manifest self-cycle")
|
||||
need(kit.get("files"),"empty full kit")
|
||||
for rel,h in kit["files"].items():
|
||||
file=kit_root/rel
|
||||
need(not Path(rel).is_absolute() and file.resolve().is_relative_to(kit_root) and sha(file)==h,"full kit source changed")
|
||||
need({str(x.relative_to(kit_root)) for x in kit_root.rglob("*") if x.is_file()}==set(kit["files"]),"full kit added/removed source")
|
||||
need(p.get("kit_manifest_sha256")==sha(kit_manifest) and p.get("source_commit")==kit.get("source_commit")==p.get("origin_sha"),"publication not bound to full source kit")
|
||||
source_digest=digest(m["files"])
|
||||
need(p.get("ticket")==TICKET and p.get("source_digest")==source_digest and p.get("decision")=="VERIFIED" and p.get("origin_sha")==p.get("github_sha") and re.fullmatch(r"[0-9a-f]{40}",p.get("origin_sha","")),"source kit unpublished")
|
||||
need(p.get("offline_test_only",False)==offline,"publication namespace")
|
||||
auth=m["identities"][RUNS[rid]["binary"]]
|
||||
need(sha(auth["binary"])==auth["binary_sha256"],"binary changed")
|
||||
need(sha(auth["wlroots"])==auth["wlroots_sha256"]==WL_SHA,"wlroots changed")
|
||||
need(auth.get("build_review")=="ACCEPTED" and auth.get("source_qualification")=="ACCEPTED","binary/source qualification pending")
|
||||
return dict(auth,manifest=str(manifest),review=str(review),publication=str(publication),manifest_sha256=sha(manifest),review_sha256=sha(review),publication_sha256=sha(publication),source_digest=source_digest)
|
||||
62
tooling/probes/step20c-corec-v2/graphics/geometry.py
Normal file
62
tooling/probes/step20c-corec-v2/graphics/geometry.py
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
"""Explicit logical global, IPC assignment, physical mode and PNG-frame adapters."""
|
||||
from dataclasses import dataclass
|
||||
import re
|
||||
from PIL import Image
|
||||
|
||||
class Ambiguous(ValueError):pass
|
||||
def need(ok,msg):
|
||||
if not ok:raise Ambiguous(msg)
|
||||
def box_end(r):return [r[0]+r[2],r[1]+r[3]]
|
||||
def contains(r,p):return r[0]<=p[0]<r[0]+r[2] and r[1]<=p[1]<r[1]+r[3]
|
||||
def crossing(r,axis,boundary):return r[axis]<boundary<r[axis]+r[axis+2]
|
||||
def intersection(a,b):
|
||||
x=max(a[0],b[0]);y=max(a[1],b[1]);return [x,y,max(0,min(a[0]+a[2],b[0]+b[2])-x),max(0,min(a[1]+a[3],b[1]+b[3])-y)]
|
||||
def assigned_origin(owner):
|
||||
# IPC rect already excludes top decoration (ipc-json.c899–906).
|
||||
return [owner['rect']['x']+owner['window_rect']['x'],owner['rect']['y']+owner['window_rect']['y']]
|
||||
def exterior_ipc(owner):
|
||||
r=owner['rect'];d=owner['deco_rect']['height'];return [r['x'],r['y']-d,r['width'],r['height']+d]
|
||||
MARKERS={'TL':(255,32,32),'TR':(32,255,32),'BL':(32,32,255),'BR':(255,255,32),'C':(192,32,255)}
|
||||
def marker_frame(image,scale,rotated):
|
||||
positions={k:[] for k in MARKERS};lookup={v:k for k,v in MARKERS.items()}
|
||||
for y in range(image.height):
|
||||
for x in range(image.width):
|
||||
k=lookup.get(image.getpixel((x,y)))
|
||||
if k:positions[k].append((x,y))
|
||||
need(all(len(v)>=100 for v in positions.values()),'marker color missing')
|
||||
c={k:[sum(p[i] for p in v)/len(v) for i in (0,1)] for k,v in positions.items()}
|
||||
logical=image.size==(round(800*scale),round(600*scale)) and c['TL'][0]<c['TR'][0] and c['TL'][1]<c['BL'][1]
|
||||
physical=rotated and image.size==(round(600*scale),round(800*scale)) and c['TL'][0]>c['BL'][0] and c['TL'][1]<c['TR'][1]
|
||||
need(logical or physical,'PNG orientation unestablished')
|
||||
return dict(frame='logical' if logical else 'physical90',centers=c,counts={k:len(v) for k,v in positions.items()},size=list(image.size))
|
||||
def png_global(px,output,frame):
|
||||
scale=output['scale'];x,y=px;ox,oy,w,h=output['logical']
|
||||
if frame=='physical90':x,y=y,round(h*scale)-1-x
|
||||
need(frame in ('logical','physical90'),'unknown PNG frame')
|
||||
# Sample integer logical centers only; resample at the chosen center later.
|
||||
return [ox+int(x/scale),oy+int(y/scale)]
|
||||
def global_png(p,output,frame):
|
||||
x=(p[0]-output['logical'][0])*output['scale'];y=(p[1]-output['logical'][1])*output['scale']
|
||||
if frame=='physical90':x,y=output['logical'][3]*output['scale']-1-y,x
|
||||
return [round(x),round(y)]
|
||||
def solid_point(image,color,output,frame,rectangle=None,margin=3):
|
||||
for y in range(margin,image.height-margin,3):
|
||||
for x in range(margin,image.width-margin,3):
|
||||
if image.getpixel((x,y))!=color:continue
|
||||
p=png_global((x,y),output,frame)
|
||||
if rectangle is not None and not contains(rectangle,p):continue
|
||||
pp=global_png(p,output,frame)
|
||||
if not (margin<=pp[0]<image.width-margin and margin<=pp[1]<image.height-margin):continue
|
||||
if all(image.getpixel((xx,yy))==color for yy in range(pp[1]-margin,pp[1]+margin+1) for xx in range(pp[0]-margin,pp[0]+margin+1)):
|
||||
return p
|
||||
raise Ambiguous('no measured solid logical interior')
|
||||
def count(image,color):return sum(v==color for v in image.get_flattened_data())
|
||||
def exact(a,b):need(a.size==b.size and a.tobytes()==b.tobytes(),'whole RGB differs');return dict(equal=True,pixels=a.width*a.height)
|
||||
def expected_outputs(outputs,spec):
|
||||
active=[o for o in outputs if o['active']];need(len(active)==2,'active output count')
|
||||
for want in spec:
|
||||
rows=[o for o in active if o['name']==want['name']];need(len(rows)==1,'output identity')
|
||||
o=rows[0];need([o['rect'][k] for k in ('x','y','width','height')]==want['logical'],'logical output geometry')
|
||||
need(o['scale']==want['scale'] and o['transform']==want['transform'],'scale/IPC transform string')
|
||||
need([o['current_mode'][k] for k in ('width','height')]==want['mode'],'physical mode')
|
||||
return active
|
||||
21
tooling/probes/step20c-corec-v2/graphics/ipc_predicates.py
Normal file
21
tooling/probes/step20c-corec-v2/graphics/ipc_predicates.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
"""Pure tree identity helpers only; IPC geometry is pending candidate."""
|
||||
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 ()
|
||||
64
tooling/probes/step20c-corec-v2/graphics/lifecycle.py
Normal file
64
tooling/probes/step20c-corec-v2/graphics/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-corec-v2/graphics/model.py
Normal file
194
tooling/probes/step20c-corec-v2/graphics/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}
|
||||
|
|
@ -0,0 +1,374 @@
|
|||
"""OFFLINE_TEST_ONLY: actual runtime modules, synthetic protocol/tree/process I/O.
|
||||
No old raw evidence, client execution, compositor, IPC or real budget access.
|
||||
"""
|
||||
import copy,io,json,os,re,resource,shutil,sys,tempfile,threading,time,unittest
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import Mock,patch
|
||||
LAB=Path(os.environ["SMX_TEST_LAB"]).resolve() if "SMX_TEST_LAB" in os.environ else Path(__file__).resolve().parents[2]
|
||||
KIT=Path(__file__).resolve().parents[1];sys.path.insert(0,str(KIT))
|
||||
import gate,plans,validators as v,saved_validators as sv,geometry,engine,cases,run,evidence
|
||||
from ipc_predicates import ancestry
|
||||
|
||||
class V2(unittest.TestCase):
|
||||
def rejects(self,fn,*args,**kw):
|
||||
with self.assertRaises((ValueError,AssertionError,RuntimeError)):fn(*args,**kw)
|
||||
def setUp(self):
|
||||
root=Path(os.environ.get("SMX_OFFLINE_SCRATCH",str(LAB/"graphics-tests/OFFLINE_TEST_ONLY")));root.mkdir(parents=True,exist_ok=True)
|
||||
self.temp=tempfile.TemporaryDirectory(dir=root);self.addCleanup(self.temp.cleanup);self.root=Path(self.temp.name)
|
||||
def fixture(self):
|
||||
source=self.root/"source";source.mkdir();(source/"input.py").write_text("# synthetic source binding\n")
|
||||
binary=self.root/"binary";binary.write_bytes(b"OFFLINE_TEST_ONLY not executable")
|
||||
auth=dict(binary=str(binary),binary_sha256=gate.sha(binary),wlroots="/usr/lib/libwlroots-0.20.so",wlroots_sha256=gate.WL_SHA,build_review="ACCEPTED",source_qualification="ACCEPTED")
|
||||
kit_manifest=self.root/"kit-manifest.json"
|
||||
manifest=self.root/"inputs.json";review=self.root/"review.json";publication=self.root/"publication.json"
|
||||
m=dict(schema="graphics-v2-frozen-inputs",ticket=gate.TICKET,lab=str(LAB),author="offline-author",source_root=str(source),files={"input.py":gate.sha(source/"input.py")},limits=plans.LIMITS,plans=plans.RUNS,identities=dict(core=auth,pristine=auth))
|
||||
def seal():
|
||||
gate.durable(kit_manifest,dict(ticket=gate.TICKET,source_root=str(source),files=m["files"],source_commit="f"*40))
|
||||
m.update(kit_manifest=str(kit_manifest),kit_manifest_sha256=gate.sha(kit_manifest))
|
||||
gate.durable(manifest,m);gate.durable(review,dict(decision="ACCEPTED",independent=True,reviewer="offline-reviewer",manifest_sha256=gate.sha(manifest),authorized_runs=list(plans.RUNS)))
|
||||
gate.durable(publication,dict(ticket=gate.TICKET,decision="VERIFIED",source_digest=gate.digest(m["files"]),origin_sha="f"*40,github_sha="f"*40,offline_test_only=True,kit_manifest_sha256=gate.sha(kit_manifest),source_commit="f"*40))
|
||||
seal();ledger=gate.Ledger(LAB,self.root/"budget.json",offline=True);ledger.initialize()
|
||||
return ledger,manifest,review,publication,m,seal
|
||||
def tree(self):
|
||||
owner=dict(id=40,pid=100,type="con",floating=None,nodes=[],floating_nodes=[],fullscreen_mode=0)
|
||||
branch=dict(id=30,type="con",floating=None,nodes=[owner],floating_nodes=[],fullscreen_mode=0)
|
||||
ws=dict(id=20,type="workspace",name="1",output="HEADLESS-1",layout="mosaic",floating=None,nodes=[branch],floating_nodes=[])
|
||||
out=dict(id=10,type="output",name="HEADLESS-1",floating=None,nodes=[ws],floating_nodes=[])
|
||||
root=dict(id=1,type="root",nodes=[out],floating_nodes=[])
|
||||
return root,owner,ancestry(root,40)
|
||||
def test_caps_and_non_admitted(self):
|
||||
self.assertEqual(len(plans.RUNS),14);self.assertEqual(sum(plans.CAPS.values()),170)
|
||||
self.assertEqual(max(plans.CAPS.values()),18)
|
||||
l,m,r,p,_,_=self.fixture()
|
||||
for rid in ("P1","C8","O1","O2","G2","R1"):self.rejects(l.admit,rid,m,r,p)
|
||||
self.assertEqual(json.loads(l.path.read_text())["runs"],[])
|
||||
def test_real_gate_consumption_token_caps_completion(self):
|
||||
l,m,r,p,_,_=self.fixture();auth=l.admit("C1",m,r,p)
|
||||
self.rejects(l.admit,"C1",m,r,p);self.rejects(l.initialize)
|
||||
self.rejects(l.action_debit,dict(auth,token="stale"),"capture")
|
||||
for _ in range(18):l.action_debit(auth,"capture")
|
||||
before=l.path.read_bytes();self.rejects(l.action_debit,auth,"capture");self.assertEqual(before,l.path.read_bytes())
|
||||
l.complete(auth,dict(status="INCONCLUSIVE",runner_exit=1));self.rejects(l.action_debit,auth,"move");self.rejects(l.complete,auth,{})
|
||||
def test_global_and_per_action_debits_atomic(self):
|
||||
l,m,r,p,_,_=self.fixture();auth=l.admit("C2",m,r,p)
|
||||
for _ in range(plans.RUNS["C2"]["action_limits"]["grab"]):l.action_debit(auth,"grab")
|
||||
before=l.path.read_bytes();self.rejects(l.action_debit,auth,"move","grab");self.assertEqual(before,l.path.read_bytes())
|
||||
def fill(d):
|
||||
d["runs"]=[dict(id="used"+str(i),capture_limit=18) for i in range(10)];return None,d
|
||||
l.transaction(fill);self.rejects(l.admit,"C1",m,r,p)
|
||||
def test_source_review_binary_and_publication_bindings(self):
|
||||
l,m,r,p,data,seal=self.fixture();auth=l.admit("C1",m,r,p)
|
||||
source=Path(data["source_root"])/"input.py";old=source.read_bytes();source.write_bytes(b"changed")
|
||||
self.rejects(l.action_debit,auth,"move");source.write_bytes(old)
|
||||
binary=Path(auth["binary"]);old=binary.read_bytes();binary.write_bytes(b"changed");self.rejects(l.action_debit,auth,"move");binary.write_bytes(old)
|
||||
rr=json.loads(r.read_text());rr["reviewer"]="different";gate.durable(r,rr);self.rejects(l.action_debit,auth,"move");seal()
|
||||
pp=json.loads(p.read_text());pp["github_sha"]="e"*40;gate.durable(p,pp);self.rejects(l.action_debit,auth,"move");seal()
|
||||
data["ticket"]="OLD_TICKET";seal();self.rejects(l.action_debit,auth,"move")
|
||||
def test_fullkit_publication_source_binding(self):
|
||||
l,m,r,p,data,seal=self.fixture();auth=l.admit("C1",m,r,p)
|
||||
km=Path(data["kit_manifest"]);km.write_text("{}")
|
||||
self.rejects(l.action_debit,auth,"move")
|
||||
seal();pub=json.loads(p.read_text());pub["source_commit"]="a"*40;gate.durable(p,pub)
|
||||
self.rejects(l.action_debit,auth,"move")
|
||||
def test_old_lab_uid_limits_lock_symlink_and_source_addition(self):
|
||||
l,m,r,p,data,seal=self.fixture()
|
||||
data["lab"]="/var/tmp/old-lab";seal();self.rejects(l.admit,"C1",m,r,p);data["lab"]=str(LAB);seal()
|
||||
new=Path(data["source_root"])/"unreviewed.py";new.write_text("new");self.rejects(l.admit,"C1",m,r,p);new.unlink()
|
||||
data["identities"]["core"]["wlroots_sha256"]="bad";seal();self.rejects(l.admit,"C1",m,r,p)
|
||||
d=json.loads(l.path.read_text());d["limits"]["runs"]=15;gate.durable(l.path,d);self.rejects(l.admit,"C1",m,r,p)
|
||||
other=self.root/"other";other.write_text("");l.lock.unlink();l.lock.symlink_to(other)
|
||||
with self.assertRaises(OSError):l.transaction(lambda d:(None,d))
|
||||
def test_environment_and_socket_paths(self):
|
||||
with patch.dict(os.environ,{k:"daily" for k in ("DISPLAY","WAYLAND_DISPLAY","SWAYSOCK","XDG_RUNTIME_DIR","LD_PRELOAD","LD_LIBRARY_PATH","WLR_BACKENDS","EGL_PLATFORM","DBUS_SESSION_BUS_ADDRESS")}):
|
||||
env=gate.clean_environment(LAB);self.assertNotIn("SWAYSOCK",env);self.assertNotIn("LD_PRELOAD",env);self.assertEqual(env["TMPDIR"],str(LAB/"scratch"))
|
||||
s=engine.Session.__new__(engine.Session);s.runtime=Path("/tmp/smx-offline");s.sock=Path("/tmp/daily.sock");s.record=Mock();s.env={}
|
||||
with patch.object(engine.subprocess,"run") as launch:
|
||||
self.rejects(s.ipc,"exit");launch.assert_not_called()
|
||||
self.rejects(engine.Session,plans.RUNS["C1"],dict(offline=True))
|
||||
def test_spawn_wraps_argv_and_records_requested_and_actual(self):
|
||||
s=engine.Session.__new__(engine.Session);s.run=self.root;s.handles=[];s.proc={};s.expired=False;s.env={};s.debit=Mock();s.record=Mock()
|
||||
child=Mock(pid=12345);requested=[sys.executable,"-c","pass"]
|
||||
with patch.object(engine.shutil,"which",return_value="/usr/bin/prlimit"),patch.object(engine.subprocess,"Popen",return_value=child) as popen:
|
||||
self.assertIs(s.spawn("mocked",requested),child)
|
||||
argv=popen.call_args.args[0]
|
||||
self.assertEqual(argv,["/usr/bin/prlimit","--core=0:0","--",*requested])
|
||||
event=s.record.call_args
|
||||
self.assertEqual(event.kwargs["requested_argv"],requested)
|
||||
self.assertEqual(event.kwargs["actual_argv"],argv)
|
||||
self.assertEqual(event.args[0],"spawn")
|
||||
def test_spawn_real_non_graphic_child_core_limit_and_parent_unchanged(self):
|
||||
prlimit=shutil.which("prlimit")
|
||||
self.assertIsNotNone(prlimit,"util-linux prlimit required")
|
||||
parent_limit=resource.getrlimit(resource.RLIMIT_CORE)
|
||||
s=engine.Session.__new__(engine.Session);s.run=self.root;s.handles=[];s.proc={};s.expired=False;s.env={};s.debit=Mock();s.events=[]
|
||||
s.record=lambda kind,**fields:s.events.append(dict(kind=kind,**fields))
|
||||
code="import json,os,resource; print(json.dumps({'pid':os.getpid(),'core':resource.getrlimit(resource.RLIMIT_CORE)}))"
|
||||
child=s.spawn("corelimit-child",[sys.executable,"-c",code])
|
||||
self.assertEqual(child.wait(timeout=5),0)
|
||||
observed=json.loads((self.root/"corelimit-child.log").read_text())
|
||||
self.assertEqual(observed["core"],[0,0])
|
||||
self.assertEqual(observed["pid"],child.pid)
|
||||
self.assertEqual(resource.getrlimit(resource.RLIMIT_CORE),parent_limit)
|
||||
event=s.events[0]
|
||||
self.assertEqual(event["requested_argv"],[sys.executable,"-c",code])
|
||||
self.assertEqual(event["actual_argv"][:3],[prlimit,"--core=0:0","--"])
|
||||
def test_watchdog_autonomous_kill_term_resistant_private_group(self):
|
||||
import signal,subprocess
|
||||
s=engine.Session.__new__(engine.Session);s.run=self.root;s.handles=[];s.proc={};s.proc_lock=threading.RLock();s.expired=False;s.env={};s.debit=Mock();s.record=Mock();s.finished=threading.Event()
|
||||
code="import os,signal,time; signal.signal(signal.SIGTERM,signal.SIG_IGN); print('READY',flush=True); time.sleep(30)"
|
||||
p=s.spawn('resistant',[sys.executable,'-c',code])
|
||||
self.addCleanup(lambda:p.poll() is None and os.killpg(p.pid,signal.SIGKILL))
|
||||
end=time.monotonic()+3
|
||||
while 'READY' not in (self.root/'resistant.log').read_text() and time.monotonic()<end:time.sleep(.01)
|
||||
self.assertIn('READY',(self.root/'resistant.log').read_text())
|
||||
self.assertEqual(os.getpgid(p.pid),p.pid)
|
||||
# Simulate the last seconds; no main cleanup is called to rescue the watchdog.
|
||||
s.started=time.monotonic()-143.1;start=time.monotonic()
|
||||
watch=threading.Thread(target=s.watchdog);watch.start();watch.join(timeout=4)
|
||||
self.assertFalse(watch.is_alive());self.assertEqual(p.wait(timeout=1),-signal.SIGKILL)
|
||||
self.assertTrue(s.expired);self.assertLess(time.monotonic()-start,3)
|
||||
self.assertLess(time.monotonic()-s.started,147)
|
||||
with self.assertRaises(ProcessLookupError):os.killpg(p.pid,0)
|
||||
p.stdin.close()
|
||||
for h in s.handles:h.close()
|
||||
def test_watchdog_expired_registration_cannot_launch(self):
|
||||
s=engine.Session.__new__(engine.Session);s.run=self.root;s.handles=[];s.proc={};s.proc_lock=threading.RLock();s.expired=False;s.env={};s.record=Mock()
|
||||
# Expiry occurs after the first check, during the debit, before atomic spawn.
|
||||
s.debit=lambda *_:setattr(s,'expired',True)
|
||||
with patch.object(engine.subprocess,'Popen') as launch:
|
||||
self.rejects(s.spawn,'late',[sys.executable,'-c','pass']);launch.assert_not_called()
|
||||
def test_close_wait_uses_absolute_deadline(self):
|
||||
s=engine.Session.__new__(engine.Session);s.started=time.monotonic()-145.5;s.proc={'owner':Mock()};s.proc['owner'].poll.return_value=None;s.closed=[];s.record=Mock();s.client_cmd=Mock()
|
||||
s.proc['owner'].wait.side_effect=engine.subprocess.TimeoutExpired('private',.5)
|
||||
with self.assertRaises(engine.subprocess.TimeoutExpired):s.close_client('owner')
|
||||
self.assertLessEqual(s.proc['owner'].wait.call_args.kwargs['timeout'],.5)
|
||||
self.assertEqual(s.closed[0]['status'],'INCONCLUSIVE')
|
||||
def dynamic_snapshot(self,width=800,scroll=1000,output='HEADLESS-1'):
|
||||
x=-width if output=='HEADLESS-1' else 0
|
||||
return dict(tree=dict(type='workspace',name='1',id=20,output=output,rect=dict(x=x,y=0,width=width,height=600),mosaic=dict(rows=[dict(children=[1,2,3],scroll_x=scroll)]),nodes=[dict(id=i+1,rect=dict(x=x+i*640,y=20,width=640,height=580)) for i in range(3)]))
|
||||
def test_dynamic_reconstructed_clamp_resize_migration_and_negatives(self):
|
||||
initial=cases.dynamic_geometry(self.dynamic_snapshot())
|
||||
resized=cases.dynamic_geometry(self.dynamic_snapshot(960,960),initial)
|
||||
self.assertEqual(resized['expected_scroll'],960);self.assertEqual(resized['minimum_width'],640)
|
||||
back=cases.dynamic_geometry(self.dynamic_snapshot(800,960),resized)
|
||||
moved=cases.dynamic_geometry(self.dynamic_snapshot(800,960,'HEADLESS-2'),back)
|
||||
self.assertEqual(moved['output'],'HEADLESS-2')
|
||||
self.rejects(cases.dynamic_geometry,self.dynamic_snapshot(960,1000),initial)
|
||||
for mutate in (lambda w:w['nodes'][0]['rect'].update(width=639),lambda w:w['nodes'][1]['rect'].update(x=123),lambda w:w['mosaic']['rows'][0].update(children=[3,2,1]),lambda w:w.update(id=21)):
|
||||
snap=self.dynamic_snapshot(960,960);mutate(snap['tree']);self.rejects(cases.dynamic_geometry,snap,initial)
|
||||
def test_dynamic_pixels_whole_h2_old_output_and_exclusion_negatives(self):
|
||||
from PIL import Image
|
||||
outputs=copy.deepcopy(plans.RUNS['C7']['outputs']);frames={o['name']:'logical' for o in outputs}
|
||||
baseline=Image.new('RGB',(800,600),(48,48,56));images=[baseline.copy(),baseline.copy()]
|
||||
images[0].putpixel((790,50),engine.COLORS['POPUP_B'])
|
||||
cases.dynamic_pixels(images,outputs,frames,[-800,0,800,600],baseline)
|
||||
images[1].putpixel((0,50),engine.COLORS['POPUP_B']);self.rejects(cases.dynamic_pixels,images,outputs,frames,[-800,0,800,600],baseline)
|
||||
images[1]=baseline.copy();images[1].putpixel((799,599),(0,0,1));self.rejects(cases.dynamic_pixels,images,outputs,frames,[-800,0,800,600],baseline)
|
||||
images[1]=baseline.copy();self.rejects(cases.dynamic_pixels,images,outputs,frames,[0,0,800,600])
|
||||
images[0]=baseline.copy();images[1].putpixel((1,50),engine.COLORS['POPUP_B']);cases.dynamic_pixels(images,outputs,frames,[0,0,800,600])
|
||||
|
||||
def test_tree_tiled_branch_none_and_wrong_owner(self):
|
||||
tree,node,path=self.tree();ws=path[-3];self.assertTrue(v.camera_eligible(path,ws));v.owner_context(node,path,100,"1","HEADLESS-1","mosaic")
|
||||
self.rejects(v.owner_context,node,path,101,"1","HEADLESS-1","mosaic")
|
||||
self.rejects(v.owner_context,node,path,100,"1","HEADLESS-2","mosaic")
|
||||
self.rejects(v.owner_context,dict(node,id=999),path,100,"1","HEADLESS-1","mosaic")
|
||||
parent=path[-2];parent["nodes"].remove(node);parent["floating_nodes"].append(node)
|
||||
self.assertFalse(v.camera_eligible(path,ws));self.rejects(v.owner_context,node,path,100,"1","HEADLESS-1","mosaic")
|
||||
def test_wire_actual_button_grab_and_native_sizes(self):
|
||||
raw=(KIT/"wire-fixture.txt").read_text();proof=v.grab(raw)
|
||||
for bad in (raw.replace("origin=wl_pointer.button","origin=fake"),raw.replace("POPUP_B MAP ","BAD MAP "),raw.replace("used="+str(proof["grabs"][0]["serial"]),"used=999999"),re.sub(r"wl_pointer#\d+\.button\([^\n]+", "removed-wire-button",raw),re.sub(r"(create_buffer\(new id wl_buffer#\d+, 0, )\d+, \d+, \d+, 1\)",r"\g<1>999, 999, 3996, 1)",raw)):
|
||||
self.rejects(v.grab,bad)
|
||||
self.rejects(v.hidden_survival,raw,proof,[dict(action="left")]);v.hidden_survival(raw,proof,[dict(action="move")])
|
||||
def test_vertical_scaled_rotated_exact_rgb(self):
|
||||
v.crosses([50,505,350,170],1,600);self.rejects(v.crosses,[50,505,350,170],0,0)
|
||||
o=plans.RUNS["C2"]["outputs"][1];self.assertEqual(geometry.png_global((30,40),o,"logical"),[30,640])
|
||||
self.assertEqual(plans.RUNS["C3"]["outputs"][0]["mode"],[1000,750]);self.assertEqual(plans.RUNS["C4"]["outputs"][0]["transform"],"90")
|
||||
from PIL import Image
|
||||
a=Image.new("RGB",(20,20));b=a.copy();b.putpixel((19,19),(0,0,1));self.rejects(v.rgb,a,b);self.rejects(v.rgb,a,a,True);v.rgb(a,a)
|
||||
def test_saved_phase_actual_validator_and_closure(self):
|
||||
before="ACK ns=1 serial=10 size=640x480\nxdg_surface#1.ack_configure(10)\nwl_shm_pool#4.create_buffer(\nCOMMIT ns=2\n"
|
||||
held="HELD ns=100 serial=11\nxdg_surface#1.configure(11)\nXDG_CONFIGURE ns=100 serial=11 size=580x420 hold=1\n"
|
||||
proof=sv.held_configure(before,held,10)
|
||||
after=held+"xdg_surface#1.ack_configure(11)\nACK ns=400 serial=11\nCOMMIT ns=401 serial=11\n"
|
||||
sv.released_configure(proof,after,300,200);self.rejects(sv.held_configure,before,after,10);self.rejects(sv.released_configure,proof,after,500,200)
|
||||
good="CLOSE_RECEIVED\nSHM_CLOSURE created=2 released=2\nEXIT error=0";v.client_exit(good,0)
|
||||
for bad in (good.replace("released=2","released=1"),good.replace("error=0","error=32"),good.replace("CLOSE_RECEIVED","")):self.rejects(v.client_exit,bad,0)
|
||||
def test_no_fake_present_or_fullscreen(self):
|
||||
tree,node,path=self.tree();self.rejects(v.fullscreen_ancestor,path)
|
||||
event=dict(run="C1",output="HEADLESS-1",commit_seq=1,native_event=True,source="native-output-commit")
|
||||
present=dict(event,source="native-output-present");self.assertEqual(evidence.native_present([event],[present])["matched"],1)
|
||||
self.rejects(evidence.native_present,[event],[dict(present,native_event=False)])
|
||||
self.rejects(evidence.native_present,[event],[dict(present,run="C2")])
|
||||
path[-2]["fullscreen_mode"]=1;path[-2]["nodes"].append(dict(id=41))
|
||||
record=dict(owner_path=path,native_xdg=True,configure_ack=1,owner_id=40,configure=[800,600],child_configures=[[800,300],[800,300]],popup_A=[-10,0,350,170],popup_B=[-5,20,300,130],boundary=0)
|
||||
evidence.fullscreen_certificate(record,record)
|
||||
bad=copy.deepcopy(record);bad["popup_B"][0]=20;self.rejects(evidence.fullscreen_certificate,record,bad)
|
||||
bad=copy.deepcopy(record);bad["configure"]=[800,500];self.rejects(evidence.fullscreen_certificate,record,bad)
|
||||
def test_runner_dispatch_failures_and_no_launch_before_admission(self):
|
||||
# execute is the real dispatch/orchestration; only transport and ledger are fake.
|
||||
s=Mock();s.proc={};s.cleanup.side_effect=lambda status,failure:status
|
||||
ledger=Mock();ledger.admit.return_value={}
|
||||
with patch.object(run,"Ledger",return_value=ledger),patch.object(cases,"popup_campaign",return_value="PARTIALLY_FEASIBLE") as campaign:
|
||||
result=run.execute("C2",LAB,"m","r","p",session_factory=lambda *_:s)
|
||||
self.assertEqual(result["runner_exit"],0);campaign.assert_called_once_with(s);s.start.assert_called_once()
|
||||
s.reset_mock();s.start.side_effect=ValueError("source changed")
|
||||
with patch.object(run,"Ledger",return_value=ledger):
|
||||
result=run.execute("C2",LAB,"m","r","p",session_factory=lambda *_:s)
|
||||
self.assertEqual(result["runner_exit"],1);self.assertIn("source changed",result["failure"])
|
||||
ledger.admit.side_effect=ValueError("no review");factory=Mock()
|
||||
with patch.object(run,"Ledger",return_value=ledger):self.rejects(run.execute,"C2",LAB,"m","r","p",session_factory=factory)
|
||||
factory.assert_not_called()
|
||||
|
||||
def closure_execute(self,bad=None,archived=False,exited=False,scenario_failure=False):
|
||||
good='CLOSE_RECEIVED\nSHM_CLOSURE created=2 released=2\nEXIT error=0'
|
||||
class Process:
|
||||
def __init__(self,pid,done=False):self.pid=pid;self.returncode=0 if done else None;self.stdin=io.StringIO()
|
||||
def poll(self):return self.returncode
|
||||
def wait(self,timeout=None):self.returncode=0;return 0
|
||||
s=engine.Session.__new__(engine.Session);s.spec=plans.RUNS['C2'];s.auth={};s.ledger=Mock()
|
||||
s.run=self.root/'run';s.run.mkdir();s.runtime=self.root/'runtime';s.runtime.mkdir()
|
||||
s.closed=[];s.handles=[];s.events=[];s.started=time.monotonic();s.finished=threading.Event();s.expired=False;s.pointer=None;s.env={}
|
||||
s.watch=Mock();s.sock=Mock();s.sock.is_socket.return_value=True
|
||||
s.comp=Process(999999991);s.proc={'compositor':s.comp,'reference':Process(999999992,exited)}
|
||||
(s.run/'reference.log').write_text(good if archived else bad or good);s.start=Mock();s.find=Mock(return_value=None);s.debit=Mock()
|
||||
def campaign(session):
|
||||
if archived:
|
||||
old=Process(999999993);s.proc['owner']=old;(s.run/'owner.log').write_text(bad)
|
||||
try:s.close_client('owner')
|
||||
except engine.Ambiguous:pass
|
||||
s.proc['owner']=Process(999999994);(s.run/'owner.log').write_text(good)
|
||||
if scenario_failure:raise ValueError('scenario failed')
|
||||
return 'PARTIALLY_FEASIBLE'
|
||||
def terminal(*args,**kwargs):s.comp.returncode=0;return SimpleNamespace(returncode=0,stdout='',stderr='')
|
||||
with patch.object(run,'Ledger',return_value=Mock()),patch.object(cases,'popup_campaign',side_effect=campaign),patch.object(engine.subprocess,'run',side_effect=terminal),patch.object(engine.os,'killpg',side_effect=ProcessLookupError):
|
||||
result=run.execute('C2',LAB,'m','r','p',session_factory=lambda *_:s)
|
||||
return result,json.loads((s.run/'outcome.json').read_text())
|
||||
def test_actual_session_normal_scientific_mechanical_close(self):
|
||||
result,out=self.closure_execute();self.assertEqual(result['runner_exit'],0);self.assertEqual(out['scientific_cleanup'],'PASS');self.assertEqual(out['mechanical_cleanup'],'PASS')
|
||||
def test_actual_session_shm_failure_not_laundered(self):
|
||||
result,out=self.closure_execute('CLOSE_RECEIVED\nSHM_CLOSURE created=2 released=1\nEXIT error=0')
|
||||
self.assertEqual(result['runner_exit'],1);self.assertEqual(out['scientific_cleanup'],'INCONCLUSIVE');self.assertEqual(out['mechanical_cleanup'],'PASS');self.assertIn('SHM release',out['failure'])
|
||||
def test_actual_session_archived_same_name_failure(self):
|
||||
result,out=self.closure_execute('CLOSE_RECEIVED\nSHM_CLOSURE created=2 released=1\nEXIT error=0',archived=True)
|
||||
self.assertEqual(result['runner_exit'],1);owners=[c for c in out['client_closures'] if c['name']=='owner'];self.assertEqual([c['status'] for c in owners],['INCONCLUSIVE','PASS'])
|
||||
def test_actual_session_already_exited_invalid_client(self):
|
||||
result,out=self.closure_execute('SHM_CLOSURE created=2 released=2\nEXIT error=0',exited=True)
|
||||
self.assertEqual(result['runner_exit'],1);self.assertEqual(out['scientific_cleanup'],'INCONCLUSIVE')
|
||||
def test_actual_session_scenario_failure(self):
|
||||
result,out=self.closure_execute(scenario_failure=True);self.assertEqual(result['runner_exit'],1);self.assertEqual(out['scientific_cleanup'],'PASS');self.assertIn('scenario failed',out['failure'])
|
||||
def test_open_chain_selects_and_checks_before_and_after_spawn(self):
|
||||
session=Mock();session.spec=plans.RUNS['C1'];session.events=[]
|
||||
session.owner.side_effect=ValueError('wrong mapped H2 owner');session.snapshot.return_value={}
|
||||
with patch.object(v,'selected_workspace') as selected:
|
||||
self.rejects(cases.open_chain,session)
|
||||
self.assertEqual(session.cmd.call_args_list[0].args,('workspace 1',));self.assertEqual(session.cmd.call_args_list[1].args,('layout mosaic',));selected.assert_called_once();session.owner.assert_called_once_with('owner');session.click.assert_not_called()
|
||||
def test_actual_delivery_requires_press_wire_and_grab_serial(self):
|
||||
s=engine.Session.__new__(engine.Session)
|
||||
lines={'owner':['POINTER_BUTTON target=TOPLEVEL serial=20 button=272 state=1','POINTER_BUTTON target=TOPLEVEL serial=21 button=272 state=0']}
|
||||
tail={'owner':'POINTER_MOTION target=TOPLEVEL x=5 y=6\nwl_pointer#4.button(20, 10, 272, 1)\nwl_pointer#4.button(21, 11, 272, 0)'}
|
||||
s.delivery('owner',[100,200,40,40],[105,206],tail,lines,'TOPLEVEL',20)
|
||||
self.rejects(s.delivery,'owner',[100,200,40,40],[105,206],tail,lines,'TOPLEVEL',22)
|
||||
self.rejects(s.delivery,'owner',[100,200,40,40],[105,206],{'owner':'POINTER_MOTION target=TOPLEVEL x=5 y=6'},lines,'TOPLEVEL',20)
|
||||
self.rejects(s.delivery,'owner',[100,200,40,40],[105,206],tail,{'owner':lines['owner'][1:]},'TOPLEVEL',20)
|
||||
|
||||
def quota_scenario(self,rid):
|
||||
# Actual open_chain/horizontal/standard-return handlers; replace measured I/O
|
||||
# and scientific predicates, never duplicate their action/capture sequencing.
|
||||
from PIL import Image
|
||||
from collections import Counter
|
||||
from contextlib import ExitStack
|
||||
class FakeSession:
|
||||
def __init__(self):
|
||||
self.spec=plans.RUNS[rid];self.outputs=copy.deepcopy(self.spec['outputs']);self.frames={o['name']:'logical' for o in self.outputs};self.actions=Counter();self.events=[];self.proc={};self.sequence=0;self.tag='';self.closed=[];self.scroll_x=0;self.expected_output='HEADLESS-1';self.auth={};self.comp=SimpleNamespace(pid=999999995)
|
||||
def debit(self,*kinds):
|
||||
self.actions.update(kinds)
|
||||
for k in kinds:
|
||||
cap=self.spec['capture_limit'] if k=='capture' else self.spec['action_limits'][k]
|
||||
if self.actions[k]>cap:raise ValueError('actual handler quota '+k)
|
||||
def cmd(self,command,business=False):
|
||||
self.debit('mutation' if business else 'configuration')
|
||||
if command.startswith('move workspace to output '):self.expected_output=command.split()[-1]
|
||||
def launch_client(self,name,*args):
|
||||
self.debit('spawn');self.sequence+=1;self.events.append(dict(kind='spawn',name=name));self.proc[name]=SimpleNamespace(pid=self.sequence,poll=lambda:None,returncode=0)
|
||||
def find(self,name):return dict(id=self.proc[name].pid,pid=self.proc[name].pid)
|
||||
def owner(self,name):
|
||||
n=self.find(name);n.update(window_rect=dict(width=640,height=575));return dict(owner=n,origin=[-320-self.scroll_x,0],scene=[-320-self.scroll_x,0,640,575],workspace=dict(id=20,output=self.expected_output),evidence='synthetic quota only',snapshot={})
|
||||
calibrate=owner
|
||||
def snapshot(self,tag):return dict(tree=dict(type='workspace',name='1',id=20,output=self.expected_output,mosaic=dict(rows=[dict(children=[1,2,3])]),nodes=[]))
|
||||
def log(self,name):
|
||||
if rid=='C6':
|
||||
if name=='compositor':return 'Transaction 1 timed out (1 waiting)'
|
||||
return 'ACK ns=1 serial=10 size=640x480\nCOMMIT ns=2 serial=10 size=640x480 color=303038\nHELD ns=3 serial=11\nACK ns=9 serial=11'
|
||||
return (KIT/'wire-fixture.txt').read_text()
|
||||
def capture(self,tag,out):
|
||||
self.debit('capture');self.tag=tag;image=Image.new('RGB',(800,600),(48,48,56));image.info['tag']=tag;return image
|
||||
def pair_capture(self,tag):return [self.capture(tag+'-H1','HEADLESS-1'),self.capture(tag+'-H2','HEADLESS-2')]
|
||||
def inject(self,action,point,grab=False):
|
||||
self.debit('move',*(['click'] if action!='move' else []),*(['grab'] if grab else []));self.events.append(dict(kind='injection',action=action,ns=time.monotonic_ns()))
|
||||
def scroll(self,name,delta):self.debit('camera');self.cmd('synthetic scroll');self.scroll_x+=delta
|
||||
def click(self,name,point,action='right',grab=False):self.inject(action,point,grab);return {'owner':''},{'owner':[]}
|
||||
def delivery(self,*args):pass
|
||||
def wait(self,*args,**kwargs):
|
||||
if rid=='C6' and kwargs.get('seconds')==6:clock[0]+=5_000_000_000
|
||||
def save(self,*args,**kwargs):pass
|
||||
def close_client(self,*args):self.debit('configuration')
|
||||
def client_cmd(self,*args):self.debit('configuration')
|
||||
s=FakeSession();geo=dict(A=[-20,50,350,170],B=[-10,100,300,130])
|
||||
if rid=='C2':geo=dict(A=[50,570,250,170],B=[70,590,170,130])
|
||||
prot=v.grab((KIT/'wire-fixture.txt').read_text())
|
||||
def counts(image,color):
|
||||
if 'final' in image.info['tag'] or 'fully-hidden' in image.info['tag'] or image.info['tag']=='migrated-H2-H1':return 0
|
||||
if 'B-closed' in image.info['tag'] and color==engine.COLORS['POPUP_B']:return 0
|
||||
return 1
|
||||
clock=[100_000_000]
|
||||
with ExitStack() as stack:
|
||||
for module,name,value in [(cases,'geometry',lambda *_:geo),(cases,'solid_point',lambda *_:[-250,100]),(run,'solid_point',lambda *_:[-250,100]),(cases,'count',counts),(run,'count',counts),(cases,'exact',lambda *_:True),(v,'selected_workspace',lambda *_:None),(v,'crosses',lambda *_:None),(v,'standard_spill',lambda *_:None),(v,'rgb',lambda *_:True),(cases,'dismissal',lambda *_:dict(status='synthetic quota only'))]:stack.enter_context(patch.object(module,name,side_effect=value))
|
||||
import lifecycle
|
||||
for name in ('explicit_child_close','a_survival','normal_owner_close'):stack.enter_context(patch.object(lifecycle,name))
|
||||
stack.enter_context(patch.object(time,'sleep'))
|
||||
if rid=='C1':run.horizontal(s)
|
||||
elif rid=='C6':
|
||||
import saved
|
||||
def tick():clock[0]+=100_000_000;return clock[0]
|
||||
stack.enter_context(patch.object(saved.time,'monotonic_ns',side_effect=tick))
|
||||
stack.enter_context(patch.object(saved.Path,'read_bytes',return_value=b'compositor\0-Dtxn-timeout=5000\0'))
|
||||
stack.enter_context(patch.object(saved,'pending_geometry',return_value=dict(held=dict(ns=1,serial=11))))
|
||||
stack.enter_context(patch.object(saved,'held_configure',return_value=dict(ns=1,serial=11)))
|
||||
stack.enter_context(patch.object(saved,'timeout_line',return_value='synthetic quota only'))
|
||||
stack.enter_context(patch.object(saved,'released_configure',return_value=dict(status='synthetic quota only')))
|
||||
saved.run(s)
|
||||
elif rid=='C5':cases.hidden_campaign(s)
|
||||
elif rid=='C7':
|
||||
candidate=dict(workspace_id=20,output='HEADLESS-1',children=[1,2,3],viewport=[-800,0,800,600],scroll=0)
|
||||
def dynamic_candidate(*args):
|
||||
return dict(candidate,output=s.expected_output,viewport=next(o['logical'] for o in s.outputs if o['name']==s.expected_output))
|
||||
stack.enter_context(patch.object(cases,'dynamic_geometry',side_effect=dynamic_candidate))
|
||||
stack.enter_context(patch.object(cases,'dynamic_pixels',return_value=dict(status='synthetic quota only')))
|
||||
s.owner=lambda name:dict(owner=dict(id=s.find(name)['id'],window_rect=dict(width=640,height=575)),origin=[-320,0],workspace=dict(id=20,output=s.expected_output),scene=[-320,0,640,575],snapshot={},evidence='synthetic quota only')
|
||||
cases.dynamic_campaign(s)
|
||||
elif rid=='P0':
|
||||
cases.init_reference(s);state,baseline,p,g,left,right=cases.open_chain(s);run.standard_return(s,state,baseline,p,g,[left,right])
|
||||
else:cases.popup_campaign(s)
|
||||
return dict(s.actions)
|
||||
def test_actual_p0_c1_handlers_capture_and_action_caps(self):
|
||||
self.assertEqual(self.quota_scenario('P0')['capture'],10)
|
||||
self.assertEqual(self.quota_scenario('C1')['capture'],16)
|
||||
def test_actual_saved_handler_capture_caps(self):
|
||||
self.assertEqual(self.quota_scenario('C6')['capture'],10)
|
||||
def test_actual_hidden_dynamic_handlers_capture_caps(self):
|
||||
self.assertEqual(self.quota_scenario('C5')['capture'],12)
|
||||
self.assertEqual(self.quota_scenario('C7')['capture'],14)
|
||||
def test_actual_other_popup_handlers_capture_caps(self):
|
||||
self.assertEqual(self.quota_scenario('C2')['capture'],8)
|
||||
self.assertEqual(self.quota_scenario('C3')['capture'],8)
|
||||
self.assertEqual(self.quota_scenario('C4')['capture'],8)
|
||||
# Their real Session.start adds exactly one calibrated-marker screenshot.
|
||||
|
||||
def test_marker_helper_is_reaped_separate_from_shm_client_closure(self):
|
||||
s=engine.Session.__new__(engine.Session);s.helper_exits=[];s.record=Mock();p=Mock();p.pid=999999996;p.poll.return_value=None;p.wait.return_value=-15;p.stdin=io.StringIO();s.proc={'marker-background':p}
|
||||
with patch.object(engine.os,'killpg') as kill:s.close_helper('marker-background')
|
||||
kill.assert_called_once_with(p.pid,engine.signal.SIGTERM);p.wait.assert_called_once_with(timeout=2)
|
||||
self.assertEqual(s.proc,{});self.assertEqual(s.helper_exits[0]['exit'],-15);self.assertFalse(s.helper_exits[0]['scientific_client'])
|
||||
|
||||
if __name__=="__main__":unittest.main(verbosity=2)
|
||||
115
tooling/probes/step20c-corec-v2/graphics/parent-provenance.json
Normal file
115
tooling/probes/step20c-corec-v2/graphics/parent-provenance.json
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
{
|
||||
"baseline": "339dda45bc81b953e2f0afadf28765149e3a1ba1",
|
||||
"deltas": [
|
||||
"New V2 explicit canonical LAB and fixed14-run plan; old gate/run/prepare/replay/seal not copied",
|
||||
"Durable full-kit+review+publication+binary binding and offline-only ledger namespace; no reset/refund",
|
||||
"Socket-only /tmp/smx runtime, scrubbed environment, LAB-backed checked-length C SHM paths",
|
||||
"Actual second-chain pre/post workspace/layout/PID/tiling validation and fresh ownerID/PID",
|
||||
"P0 ten captures; C1 sixteen; C2 eight; C3/C4 nine including marker; C5 twelve; C6 ten; C7 fourteen with postclose pixels explicitly NOT_TESTED",
|
||||
"Fullscreen/OOM/GPU/discriminant NOT_ADMITTED pending specific proof/hooks",
|
||||
"Scientific closure includes archived same-name/already-exited clients and separates mechanical forced cleanup",
|
||||
"No old raw replay; native generation/saved identity/complete damage/present/buffer/GPU claims NOT_PROVEN absent actual native evidence"
|
||||
],
|
||||
"historical_raw": "MISSING_NOT_RECOVERABLE",
|
||||
"new_runtime": "NOT_RUN",
|
||||
"parents": {
|
||||
"cases.py": {
|
||||
"classification": "REUSED_VERSIONED_SOURCE_WITH_DECLARED_V2_DELTAS",
|
||||
"sha256": "7ef309e39b7ac814246a0822428334cfce35d0b5c641ca7b220b0f4475709e4b",
|
||||
"source": "tooling/probes/step20c-core-recovery/graphics/cases.py"
|
||||
},
|
||||
"clients/nested-client.c": {
|
||||
"sha256": "c4baa574baf04c944f5b8b0012b434369a30afdbf3f989d6f26e554783f82d86",
|
||||
"source": "tooling/probes/step20c-core-recovery/graphics/clients/nested-client.c"
|
||||
},
|
||||
"clients/pointer-daemon.c": {
|
||||
"sha256": "8e4eaad95882d0d73866f801899007240b01cb6e15a712e820b3e3f71a78bbe5",
|
||||
"source": "tooling/probes/step20c-minimal-core-gate/graphics/clients/pointer-daemon.c"
|
||||
},
|
||||
"clients/saved-client.c": {
|
||||
"sha256": "3c78af047af5834957fee16a25e42f0bd6f02e85e4385a52116e9d5beb63e912",
|
||||
"source": "tooling/probes/step20c-core-recovery/graphics/clients/saved-client.c"
|
||||
},
|
||||
"clients/static-reference.c": {
|
||||
"sha256": "e346a85ded3345b232cd6edca5a1ac57aebfd548a60e12b3c47a5cf515e6c50e",
|
||||
"source": "tooling/probes/step20c-minimal-core-gate/graphics/clients/static-reference.c"
|
||||
},
|
||||
"clients/vertical-client.c": {
|
||||
"sha256": "63b7e1615a452a87f4d790e46456a7cc66d58cc2755ca2801fc1ab23c8e9b179",
|
||||
"source": "tooling/probes/step20c-core-recovery/graphics/clients/vertical-client.c"
|
||||
},
|
||||
"engine.py": {
|
||||
"classification": "REUSED_VERSIONED_SOURCE_WITH_DECLARED_V2_DELTAS",
|
||||
"sha256": "c6fd4118dd1def6f2dede2eaa4a6c1461a47b3fcd2ce8d6a40577b381ffd24e5",
|
||||
"source": "tooling/probes/step20c-core-recovery/graphics/engine.py"
|
||||
},
|
||||
"geometry.py": {
|
||||
"classification": "REUSED_VERSIONED_SOURCE_WITH_DECLARED_V2_DELTAS",
|
||||
"sha256": "76a80e98887d3a3707ee942b48a49c1850cdc4aeeb28237d9fea33fa3470fb12",
|
||||
"source": "tooling/probes/step20c-core-recovery/graphics/geometry.py"
|
||||
},
|
||||
"ipc_predicates.py": {
|
||||
"classification": "REUSED_VERSIONED_SOURCE_WITH_DECLARED_V2_DELTAS",
|
||||
"sha256": "5354ba53e1b3f8d0026f4475beebee58d093a81db7d26304cad7085ae3efd352",
|
||||
"source": "tooling/probes/step20c-core-recovery/graphics/ipc_predicates.py"
|
||||
},
|
||||
"lifecycle.py": {
|
||||
"classification": "REUSED_VERSIONED_SOURCE_WITH_DECLARED_V2_DELTAS",
|
||||
"sha256": "df0c11027b3b8b0ef371ddb6ec4bcfd28dac09835644d0c3ce6e273d5e565e92",
|
||||
"source": "tooling/probes/step20c-core-recovery/graphics/lifecycle.py"
|
||||
},
|
||||
"model.py": {
|
||||
"classification": "REUSED_VERSIONED_SOURCE_WITH_DECLARED_V2_DELTAS",
|
||||
"sha256": "a578fc589fb092dae3ccdcf8c4eec12a97e515f721decc4c968078dedcb2367a",
|
||||
"source": "tooling/probes/step20c-core-recovery/graphics/model.py"
|
||||
},
|
||||
"pixels.py": {
|
||||
"classification": "REUSED_VERSIONED_SOURCE_WITH_DECLARED_V2_DELTAS",
|
||||
"sha256": "d315ddad978238ffbf1c8e4d712d4608ba23564703332d814f627f57f7b6e7f5",
|
||||
"source": "tooling/probes/step20c-core-recovery/graphics/pixels.py"
|
||||
},
|
||||
"predicates.py": {
|
||||
"classification": "REUSED_VERSIONED_SOURCE_WITH_DECLARED_V2_DELTAS",
|
||||
"sha256": "222a94764bfe6d76f0b7bcdab4b28b4d90cccb479af49b87e778813f0bdc3a71",
|
||||
"source": "tooling/probes/step20c-core-recovery/graphics/predicates.py"
|
||||
},
|
||||
"protocols/wlr-virtual-pointer-unstable-v1.xml": {
|
||||
"classification": "REUSED_VERSIONED_SOURCE",
|
||||
"sha256": "3ff6d540be0bc5228195bf072bde42117ea17945a5c2061add5d3cf97d6bb524",
|
||||
"source": "tooling/probes/step20c-valid-grab/wlr-virtual-pointer-unstable-v1.xml"
|
||||
},
|
||||
"protocols/xdg-shell.xml": {
|
||||
"classification": "SYSTEM_PROTOCOL_SOURCE",
|
||||
"sha256": "7ba7f9c8473deee674cb1f154a18abd0bb0cc072604fc055b0c15e459fc4c7df",
|
||||
"source": "/usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml"
|
||||
},
|
||||
"saved.py": {
|
||||
"classification": "REUSED_VERSIONED_SOURCE_WITH_DECLARED_V2_DELTAS",
|
||||
"sha256": "f3e969781c1f0c90094babad0dffb08f63544a03da4c084d2fb4c3bdb37d9abf",
|
||||
"source": "tooling/probes/step20c-core-recovery/graphics/saved.py"
|
||||
},
|
||||
"saved_validators.py": {
|
||||
"classification": "REUSED_VERSIONED_SOURCE_WITH_DECLARED_V2_DELTAS",
|
||||
"sha256": "b412fedc24869274c8b1506a2800c8d4815c93df859785c6e96f719d692014af",
|
||||
"source": "tooling/probes/step20c-core-recovery/graphics/saved_validators.py"
|
||||
},
|
||||
"validators.py": {
|
||||
"classification": "REUSED_VERSIONED_SOURCE_WITH_DECLARED_V2_DELTAS",
|
||||
"sha256": "dfc3e0c6a57b032a311a7ccfc3ac0f30447f9029fa933819bd65f2610d763908",
|
||||
"source": "tooling/probes/step20c-core-recovery/graphics/validators.py"
|
||||
},
|
||||
"wire-fixture.txt": {
|
||||
"classification": "REUSED_VERSIONED_SOURCE_WITH_DECLARED_V2_DELTAS",
|
||||
"sha256": "c6c9fc072f78fbcf9cfdeebcc5becdbdfb61824352495a0e767d7d390267f640",
|
||||
"source": "tooling/probes/step20c-core-recovery/graphics/wire-fixture.txt"
|
||||
}
|
||||
},
|
||||
"retained_core_traces": {
|
||||
"PI_OPT": "REMOVED",
|
||||
"PROBE_POPUP_CLIP": "owner=%p local=%d,%d %dx%d",
|
||||
"PROBE_POPUP_HIDDEN_OWNER": "owner=%p applied=%d,%d %dx%d viewport=%d,%d %dx%d",
|
||||
"SPIKE_CLIP": "tree=%p rect=%d,%d %dx%d",
|
||||
"SPIKE_DAMAGE": "REMOVED",
|
||||
"temporal_neutrality": "NOT_PROVEN"
|
||||
}
|
||||
}
|
||||
21
tooling/probes/step20c-corec-v2/graphics/pixels.py
Normal file
21
tooling/probes/step20c-corec-v2/graphics/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())
|
||||
22
tooling/probes/step20c-corec-v2/graphics/plans.py
Normal file
22
tooling/probes/step20c-corec-v2/graphics/plans.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
"""Frozen V2 intent. Admission is independent of intent; no calibration/retry runs."""
|
||||
from copy import deepcopy
|
||||
CAPS=dict(P0=10,P1=10,C1=18,C2=12,C3=12,C4=12,C5=12,C6=12,C7=14,C8=12,O1=8,O2=8,G2=16,R1=14)
|
||||
LIMITS=dict(runs=14,captures=180,captures_per_run=18,duration_s=150)
|
||||
def output(name,logical,mode=None,scale=1,transform='normal'):
|
||||
return dict(name=name,logical=logical,mode=mode or logical[2:],scale=scale,transform=transform)
|
||||
H=[output('HEADLESS-1',[-800,0,800,600]),output('HEADLESS-2',[0,0,800,600])]
|
||||
RUNS={rid:dict(id=rid,binary='pristine' if rid in ('P0','P1') else 'core',mode='pristine' if rid in ('P0','P1') else 'mosaic',outputs=deepcopy(H),client='nested-client',placement='static_cross',axis=0,boundary=0,capture_limit=cap,action_limits=dict(spawn=12,configuration=70,mutation=26,move=24,click=12,grab=4,camera=6,chain=2),admission='PENDING_INDEPENDENT_REVIEW') for rid,cap in CAPS.items()}
|
||||
RUNS['C2'].update(outputs=[output('HEADLESS-1',[0,0,800,600]),output('HEADLESS-2',[0,600,800,600])],client='vertical-client',placement='v_both_cross',axis=1,boundary=600)
|
||||
RUNS['C3']['outputs'][0]=output('HEADLESS-1',[-800,0,800,600],[1000,750],1.25)
|
||||
RUNS['C4']['outputs'][0]=output('HEADLESS-1',[-800,0,800,600],[750,1000],1.25,'90')
|
||||
# Geometry certificates and distinct diagnostics are prerequisite source evidence.
|
||||
for rid in ('P1','C8'):
|
||||
RUNS[rid].update(admission='NOT_ADMITTED',reason='No offline equal-child nonleaf fullscreen configure/crossing certificate; no empirical calibration authorized')
|
||||
for rid in ('O1','O2','G2','R1'):
|
||||
RUNS[rid].update(admission='NOT_ADMITTED',reason='Separately reviewed diagnostic/discriminant source hook and exact binary unavailable')
|
||||
assert len(RUNS)==14 and sum(CAPS.values())==170 and max(CAPS.values())==18
|
||||
|
||||
def config(spec):
|
||||
cfg='\n'.join(f"output {o['name']} resolution {o['mode'][0]}x{o['mode'][1]} scale {o['scale']} transform {o['transform']} position {o['logical'][0]} {o['logical'][1]}" for o in spec['outputs'])
|
||||
return cfg+'\nworkspace 1 output HEADLESS-1\nworkspace 2 output HEADLESS-2\nfocus_follows_mouse no\nseat seat0 hide_cursor 100\nshadows disable\nblur disable\n'
|
||||
for spec in RUNS.values():spec['config']=config(spec)
|
||||
124
tooling/probes/step20c-corec-v2/graphics/predicates.py
Normal file
124
tooling/probes/step20c-corec-v2/graphics/predicates.py
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
"""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')
|
||||
25
tooling/probes/step20c-corec-v2/graphics/prepare.py
Normal file
25
tooling/probes/step20c-corec-v2/graphics/prepare.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate protocols and compile C17 clients, without executing any client."""
|
||||
import argparse,json,shlex,subprocess
|
||||
from pathlib import Path
|
||||
from gate import canonical_lab,clean_environment,durable,sha,need
|
||||
|
||||
def prepare(lab):
|
||||
lab=canonical_lab(lab);src=Path(__file__).resolve().parent;build=lab/"graphics-tests/client-build";build.mkdir(parents=True,exist_ok=False)
|
||||
env=clean_environment(lab);commands=[]
|
||||
def invoke(argv):
|
||||
r=subprocess.run(list(map(str,argv)),env=env,capture_output=True,text=True,timeout=30)
|
||||
commands.append(dict(argv=list(map(str,argv)),exit=r.returncode,stdout=r.stdout,stderr=r.stderr))
|
||||
durable(lab/"results/graphics-client-compile.json",dict(status="PENDING" if r.returncode==0 else "FAIL",commands=commands,runtime="NOT_RUN"))
|
||||
need(r.returncode==0,"client build command failed");return r.stdout.strip()
|
||||
versions={name:invoke([name,"--version"]) for name in ("cc","wayland-scanner","pkg-config")}
|
||||
flags=shlex.split(invoke(["pkg-config","--cflags","--libs","wayland-client"]))
|
||||
for protocol,xml in (("xdg-shell","xdg-shell.xml"),("virtual-pointer","wlr-virtual-pointer-unstable-v1.xml")):
|
||||
invoke(["wayland-scanner","client-header",src/"protocols"/xml,build/(protocol+"-client.h")])
|
||||
invoke(["wayland-scanner","private-code",src/"protocols"/xml,build/(protocol+"-protocol.c")])
|
||||
for name,protocol in (("nested-client","xdg-shell"),("vertical-client","xdg-shell"),("saved-client","xdg-shell"),("static-reference","xdg-shell"),("pointer-daemon","virtual-pointer")):
|
||||
invoke(["cc","-std=c17","-O2","-Wall","-Wextra","-I",build,src/"clients"/(name+".c"),build/(protocol+"-protocol.c"),"-o",build/name,*flags])
|
||||
durable(lab/"results/graphics-client-compile.json",dict(status="PASS",commands=commands,versions=versions,inputs={str(p.relative_to(src)):sha(p) for folder in ("clients","protocols") for p in (src/folder).iterdir()},generated={str(p.relative_to(build)):sha(p) for p in build.iterdir()},runtime="NOT_RUN"))
|
||||
|
||||
if __name__=="__main__":
|
||||
p=argparse.ArgumentParser();p.add_argument("--lab",required=True);a=p.parse_args();prepare(a.lab)
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="wlr_virtual_pointer_unstable_v1">
|
||||
<copyright>
|
||||
Copyright © 2019 Josef Gajdusek
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next
|
||||
paragraph) shall be included in all copies or substantial portions of the
|
||||
Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<interface name="zwlr_virtual_pointer_v1" version="2">
|
||||
<description summary="virtual pointer">
|
||||
This protocol allows clients to emulate a physical pointer device. The
|
||||
requests are mostly mirror opposites of those specified in wl_pointer.
|
||||
</description>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="invalid_axis" value="0"
|
||||
summary="client sent invalid axis enumeration value" />
|
||||
<entry name="invalid_axis_source" value="1"
|
||||
summary="client sent invalid axis source enumeration value" />
|
||||
</enum>
|
||||
|
||||
<request name="motion">
|
||||
<description summary="pointer relative motion event">
|
||||
The pointer has moved by a relative amount to the previous request.
|
||||
|
||||
Values are in the global compositor space.
|
||||
</description>
|
||||
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
|
||||
<arg name="dx" type="fixed" summary="displacement on the x-axis"/>
|
||||
<arg name="dy" type="fixed" summary="displacement on the y-axis"/>
|
||||
</request>
|
||||
|
||||
<request name="motion_absolute">
|
||||
<description summary="pointer absolute motion event">
|
||||
The pointer has moved in an absolute coordinate frame.
|
||||
|
||||
Value of x can range from 0 to x_extent, value of y can range from 0
|
||||
to y_extent.
|
||||
</description>
|
||||
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
|
||||
<arg name="x" type="uint" summary="position on the x-axis"/>
|
||||
<arg name="y" type="uint" summary="position on the y-axis"/>
|
||||
<arg name="x_extent" type="uint" summary="extent of the x-axis"/>
|
||||
<arg name="y_extent" type="uint" summary="extent of the y-axis"/>
|
||||
</request>
|
||||
|
||||
<request name="button">
|
||||
<description summary="button event">
|
||||
A button was pressed or released.
|
||||
</description>
|
||||
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
|
||||
<arg name="button" type="uint" summary="button that produced the event"/>
|
||||
<arg name="state" type="uint" enum="wl_pointer.button_state" summary="physical state of the button"/>
|
||||
</request>
|
||||
|
||||
<request name="axis">
|
||||
<description summary="axis event">
|
||||
Scroll and other axis requests.
|
||||
</description>
|
||||
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
|
||||
<arg name="axis" type="uint" enum="wl_pointer.axis" summary="axis type"/>
|
||||
<arg name="value" type="fixed" summary="length of vector in touchpad coordinates"/>
|
||||
</request>
|
||||
|
||||
<request name="frame">
|
||||
<description summary="end of a pointer event sequence">
|
||||
Indicates the set of events that logically belong together.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="axis_source">
|
||||
<description summary="axis source event">
|
||||
Source information for scroll and other axis.
|
||||
</description>
|
||||
<arg name="axis_source" type="uint" enum="wl_pointer.axis_source" summary="source of the axis event"/>
|
||||
</request>
|
||||
|
||||
<request name="axis_stop">
|
||||
<description summary="axis stop event">
|
||||
Stop notification for scroll and other axes.
|
||||
</description>
|
||||
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
|
||||
<arg name="axis" type="uint" enum="wl_pointer.axis" summary="the axis stopped with this event"/>
|
||||
</request>
|
||||
|
||||
<request name="axis_discrete">
|
||||
<description summary="axis click event">
|
||||
Discrete step information for scroll and other axes.
|
||||
|
||||
This event allows the client to extend data normally sent using the axis
|
||||
event with discrete value.
|
||||
</description>
|
||||
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
|
||||
<arg name="axis" type="uint" enum="wl_pointer.axis" summary="axis type"/>
|
||||
<arg name="value" type="fixed" summary="length of vector in touchpad coordinates"/>
|
||||
<arg name="discrete" type="int" summary="number of steps"/>
|
||||
</request>
|
||||
|
||||
<request name="destroy" type="destructor" since="1">
|
||||
<description summary="destroy the virtual pointer object"/>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_virtual_pointer_manager_v1" version="2">
|
||||
<description summary="virtual pointer manager">
|
||||
This object allows clients to create individual virtual pointer objects.
|
||||
</description>
|
||||
|
||||
<request name="create_virtual_pointer">
|
||||
<description summary="Create a new virtual pointer">
|
||||
Creates a new virtual pointer. The optional seat is a suggestion to the
|
||||
compositor.
|
||||
</description>
|
||||
<arg name="seat" type="object" interface="wl_seat" allow-null="true"/>
|
||||
<arg name="id" type="new_id" interface="zwlr_virtual_pointer_v1"/>
|
||||
</request>
|
||||
|
||||
<request name="destroy" type="destructor" since="1">
|
||||
<description summary="destroy the virtual pointer manager"/>
|
||||
</request>
|
||||
|
||||
<!-- Version 2 additions -->
|
||||
<request name="create_virtual_pointer_with_output" since="2">
|
||||
<description summary="Create a new virtual pointer">
|
||||
Creates a new virtual pointer. The seat and the output arguments are
|
||||
optional. If the seat argument is set, the compositor should assign the
|
||||
input device to the requested seat. If the output argument is set, the
|
||||
compositor should map the input device to the requested output.
|
||||
</description>
|
||||
<arg name="seat" type="object" interface="wl_seat" allow-null="true"/>
|
||||
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
|
||||
<arg name="id" type="new_id" interface="zwlr_virtual_pointer_v1"/>
|
||||
</request>
|
||||
</interface>
|
||||
</protocol>
|
||||
1420
tooling/probes/step20c-corec-v2/graphics/protocols/xdg-shell.xml
Normal file
1420
tooling/probes/step20c-corec-v2/graphics/protocols/xdg-shell.xml
Normal file
File diff suppressed because it is too large
Load diff
73
tooling/probes/step20c-corec-v2/graphics/run.py
Normal file
73
tooling/probes/step20c-corec-v2/graphics/run.py
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#!/usr/bin/env python3
|
||||
"""WHY/CONTRACT/INVARIANT: reserve once, reviewed bytes only, no retry."""
|
||||
import argparse,time,re
|
||||
from gate import Ledger,need
|
||||
from plans import RUNS
|
||||
from engine import Session
|
||||
import cases,validators
|
||||
from geometry import *
|
||||
from ipc_predicates import ancestry
|
||||
|
||||
def normal_remaining(s):
|
||||
for n in list(s.proc):
|
||||
if n not in ('owner','compositor','pointer') and s.proc[n].poll() is None:s.close_client(n,'close')
|
||||
|
||||
def standard_return(s,state,baseline,p,g,existing_images=None):
|
||||
oid=state['owner']['id'];s.expected_layout='splith'
|
||||
if existing_images is None:s.cmd(f'[con_id={oid}] layout splith',True);time.sleep(.4)
|
||||
state=s.owner('owner');prot=validators.grab(s.log('owner'));need(prot['ids']==p['ids'],'live second chain preserved')
|
||||
geo=cases.geometry(s.log('owner'),state['origin'],cases.Provenance(oid,state['workspace']['id'],'NOT OBSERVABLE','pointer+paint corroboration'))
|
||||
images=existing_images if existing_images is not None else s.pair_capture('standard-live');validators.standard_spill(images,COLORS)
|
||||
for target,key,cmd in [('POPUP_B','B','close_b'),('POPUP_A','A',None)]:
|
||||
state=s.owner('owner');geo=cases.geometry(s.log('owner'),state['origin'],cases.Provenance(oid,state['workspace']['id'],'NOT OBSERVABLE','fresh current pointer/paint candidate'));before_input=s.log('owner');im=images[1];xy=solid_point(im,COLORS[target],s.outputs[1],s.frames['HEADLESS-2'],geo[key]);tails,buttons=s.click('owner',xy);s.delivery('owner',geo[key],xy,tails,buttons,target)
|
||||
if target=='POPUP_A':
|
||||
from lifecycle import a_survival
|
||||
a_survival(images[1],before_input,s.log('owner'),p['ids'],buttons['owner'],xy,geo['A'])
|
||||
if cmd:
|
||||
before=s.log('owner');request=time.monotonic_ns();s.client_cmd('owner',cmd,'COMMAND close_b DONE');time.sleep(.2)
|
||||
from lifecycle import explicit_child_close
|
||||
explicit_child_close(before,s.log('owner'),p['ids'],request,time.monotonic_ns())
|
||||
images=s.pair_capture('B-closed-A-survives');need(count(images[1],COLORS['POPUP_A'])>0 and count(images[1],COLORS['POPUP_B'])==0,'A not alive/B residual')
|
||||
before_close=s.log('owner');s.close_client('owner')
|
||||
from lifecycle import normal_owner_close
|
||||
normal_owner_close(before_close,s.log('owner'),p['ids'],s.proc['owner'].returncode)
|
||||
final=s.pair_capture('standard-final');validators.rgb(baseline,final[1])
|
||||
return 'PARTIALLY_FEASIBLE'
|
||||
|
||||
def horizontal(s):
|
||||
cases.init_reference(s);state,baseline,p,g,left,right=cases.open_chain(s)
|
||||
xy=solid_point(left,COLORS['POPUP_B'],s.outputs[0],s.frames['HEADLESS-1'],g['B']);tails,buttons=s.click('owner',xy);s.delivery('owner',g['B'],xy,tails,buttons,'POPUP_B')
|
||||
forbidden=intersection(g['B'],s.outputs[1]['logical']);need(forbidden[2]>6 and forbidden[3]>6,'forbidden interior');point=[forbidden[0]+forbidden[2]//2,forbidden[1]+forbidden[3]//2]
|
||||
tails,buttons=s.click('owner',point);proof=cases.dismissal(tails['owner'],buttons['owner']);s.save('dismissal',proof);s.close_client('owner');s.save('first-chain-protocol-log',dict(raw=s.log('owner')))
|
||||
# Two independent real chains; no resurrected dismissed popup.
|
||||
old_id=state['owner']['id'];old_pid=state['owner']['pid'];state,baseline2,p,g,left,right=cases.open_chain(s);need(state['owner']['id']!=old_id and state['owner']['pid']!=old_pid,'second chain must have fresh owner/PID');return standard_return(s,state,baseline2,p,g)
|
||||
|
||||
COLORS=cases.COLORS
|
||||
def execute(rid,lab,manifest,review,publication,session_factory=Session):
|
||||
auth=Ledger(lab).admit(rid,manifest,review,publication);s=None;status='INCONCLUSIVE';failure=None
|
||||
try:
|
||||
s=session_factory(RUNS[rid],auth);s.start()
|
||||
if rid=='C1':status=horizontal(s)
|
||||
elif rid=='C5':status=cases.hidden_campaign(s)
|
||||
elif rid=='C6':
|
||||
from saved import run
|
||||
status=run(s)
|
||||
elif rid=='C7':status=cases.dynamic_campaign(s)
|
||||
elif rid=='P0':
|
||||
cases.init_reference(s);state,baseline,p,g,left,right=cases.open_chain(s)
|
||||
status=standard_return(s,state,baseline,p,g,[left,right])
|
||||
elif rid in ('C2','C3','C4'):status=cases.popup_campaign(s)
|
||||
else:raise ValueError('NOT_ADMITTED scenario')
|
||||
normal_remaining(s)
|
||||
except Exception as error:
|
||||
failure=f'{type(error).__name__}: {error}';status='INCONCLUSIVE'
|
||||
finally:
|
||||
if s is not None:status=s.cleanup(status,failure)
|
||||
else:Ledger(lab).complete(auth,dict(status='INCONCLUSIVE',failure=failure,session_constructed=False))
|
||||
return dict(status=status,failure=failure,runner_exit=0 if status=='PARTIALLY_FEASIBLE' else 1)
|
||||
|
||||
def main():
|
||||
p=argparse.ArgumentParser(description='One independently admitted run; never all-cases or retry')
|
||||
p.add_argument('run_id',choices=RUNS);p.add_argument('--lab',required=True);p.add_argument('--manifest',required=True);p.add_argument('--review',required=True);p.add_argument('--publication',required=True)
|
||||
a=p.parse_args();result=execute(a.run_id,a.lab,a.manifest,a.review,a.publication);print(result);return result['runner_exit']
|
||||
if __name__=='__main__':raise SystemExit(main())
|
||||
30
tooling/probes/step20c-corec-v2/graphics/saved.py
Normal file
30
tooling/probes/step20c-corec-v2/graphics/saved.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
"""One actual held→natural-timeout episode, exact binary, EXPERIMENTAL."""
|
||||
import re,time
|
||||
from pathlib import Path
|
||||
import cases,engine
|
||||
from geometry import need
|
||||
from saved_validators import pending_geometry,held_configure,saved_identity,natural_retirement,rgb_exact,timeout_line,released_configure
|
||||
|
||||
def run(s):
|
||||
cases.init_reference(s,2);s.launch_client('owner','saved-client');s.cmd(f'[con_id={s.find("owner")["id"]}] focus');state=s.calibrate('owner');baseline=cases.stable_reference(s)
|
||||
initial=s.snapshot('initial-applied');before=s.log('owner');acks=re.findall(r'ACK ns=\d+ serial=(\d+)',before);need(acks,'initial actual ACK');prior=int(acks[-1])
|
||||
argv=Path(f'/proc/{s.comp.pid}/cmdline').read_bytes().split(b'\0');need(b'-Dtxn-timeout=5000' in argv,'actual startup timeout5000')
|
||||
s.client_cmd('owner','hold_next_configure','COMMAND hold_next_configure DONE');cut=len(s.log('owner'));ccut=len(s.log('compositor'));s.cmd('workspace 1');request=time.monotonic_ns();s.cmd('gaps inner current set 30',True)
|
||||
pending=s.snapshot('saved-requested-pending');s.wait(lambda:'HELD ' in s.log('owner')[cut:]);proof=pending_geometry(initial,pending,state['owner']['id'],state['workspace']['id'],before,s.log('owner')[cut:],prior,request)
|
||||
held_start=time.monotonic_ns();held=s.pair_capture('held');held_end=time.monotonic_ns();need(held_start>proof['held']['ns'] and held_end<request+4_700_000_000,'held capture before expiration')
|
||||
commits=re.findall(r'COMMIT ns=\d+ serial=\d+ size=\d+x\d+ color=([0-9a-f]+)',before);need(commits,'actual retained SHM color');value=int(commits[-1],16);color=((value>>16)&255,(value>>8)&255,value&255)
|
||||
rgb_exact(baseline,held[1]);need(any(p==color for p in held[0].getdata()),'owner retained SHM color visible H1')
|
||||
native=None
|
||||
if False:native=saved_identity(s.log('compositor'),state['owner']['id'],request)
|
||||
s.wait(lambda:re.search(r'Transaction .*timed out \(\d*[1-9]\d* waiting\)',s.log('compositor')[ccut:]),seconds=6)
|
||||
observed=time.monotonic_ns();need(s.proc['owner'].poll() is None,'owner alive natural timeout');hold=held_configure(before,s.log('owner')[cut:],prior)
|
||||
timeout=timeout_line(s.log('compositor')[ccut:],-1,True,request,observed,2**63)
|
||||
after_start=time.monotonic_ns();after=s.pair_capture('natural-timeout');rgb_exact(baseline,after[1]);release=time.monotonic_ns();s.client_cmd('owner','release_configure','COMMAND release_configure DONE');s.wait(lambda:re.search(r'ACK ns=\d+ serial='+str(proof['held']['serial'])+r'\b',s.log('owner')[cut:]));live=s.pair_capture('restored-live');rgb_exact(baseline,live[1])
|
||||
if native:
|
||||
native=saved_identity(s.log('compositor'),state['owner']['id'],request,True)
|
||||
retirement=natural_retirement(s.log('compositor'),native,after_start,time.monotonic_ns(),True,before,s.log('owner')[cut:],prior)
|
||||
else:retirement={'status':'NOT OBSERVABLE; CORE has no native saved identity'}
|
||||
# WHY CONTRACT INVARIANT: CPU destruction is measured separately from GPU
|
||||
# completion, and process exit0 alone never qualifies Wayland cleanup.
|
||||
s.save('saved-evidence',dict(request_ns=request,held_start_ns=held_start,held_end_ns=held_end,timeout_observed_ns=observed,timeout_native=timeout,geometry=proof,native=native,retirement=retirement,held_wire=hold,post_release=released_configure(hold,s.log('owner')[cut:],release,observed,s.proc['owner'].poll() is None),gpu_completion='NOT PROVEN',native_generation='NOT PROVEN',binary=s.auth))
|
||||
s.close_client('owner','close');final=s.pair_capture('final');rgb_exact(baseline,final[1]);return 'PARTIALLY_FEASIBLE'
|
||||
55
tooling/probes/step20c-corec-v2/graphics/saved_validators.py
Normal file
55
tooling/probes/step20c-corec-v2/graphics/saved_validators.py
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
"""Local pure CORE saved evidence; no diagnostic/historical import."""
|
||||
import re
|
||||
from geometry import need,intersection
|
||||
from ipc_predicates import workspace,nodes
|
||||
from PIL import ImageChops
|
||||
def held_configure(before,after,prior_serial):
|
||||
need(type(prior_serial)is int and prior_serial>0,'prior serial required')
|
||||
need(re.search(rf'ACK ns=\d+ serial={prior_serial}\b',before) and re.search(rf'ack_configure\({prior_serial}\)',before),'initial actual ACK required')
|
||||
need('wl_shm_pool#' in before and '.create_buffer(' in before and 'COMMIT ns=' in before,'real initial SHM required')
|
||||
held=re.findall(r'HELD ns=(\d+) serial=(\d+)',after);need(len(held)==1,'unique real HELD configure')
|
||||
ns,serial=map(int,held[0]);need(serial!=prior_serial,'new held serial')
|
||||
need(re.search(rf'xdg_surface#\d+\.configure\({serial}\)',after),'received actual configure wire')
|
||||
need(re.search(rf'XDG_CONFIGURE ns=\d+ serial={serial}\b.*hold=1',after),'client received hold state')
|
||||
old=re.findall(r'ACK ns=\d+ serial='+str(prior_serial)+r' size=(\d+)x(\d+)',before);new=re.findall(r'XDG_CONFIGURE ns=\d+ serial='+str(serial)+r' size=(\d+)x(\d+)',after)
|
||||
need(old and new and old[-1]!=new[-1],'new geometry configure required')
|
||||
need(not re.search(rf'ack_configure\({serial}\)|ACK ns=\d+ serial={serial}\b',after),'held serial must remain unACKed')
|
||||
return dict(ns=ns,serial=serial,prior_serial=prior_serial,initial_size=list(map(int,old[-1])),requested_size=list(map(int,new[-1])),status='HELD_WIRE_VALIDATED',native_saved_identity='NOT OBSERVABLE')
|
||||
|
||||
def pending_geometry(initial,pending,owner_id,workspace_id,before,after,prior,request_ns):
|
||||
oldws=workspace(initial['tree'],'1');ws=workspace(pending['tree'],'1')
|
||||
need(oldws['id']==ws['id']==workspace_id and ws['output']==oldws['output']=='HEADLESS-1' and ws['layout']==oldws['layout']=='mosaic','same requested workspace')
|
||||
need('gaps' not in ws,'IPC gaps field is absent in audited source')
|
||||
old=next(n for n in nodes(oldws) if n['id']==owner_id)
|
||||
owners=[n for n in nodes(ws) if n['id']==owner_id];need(len(owners)==1,'same owner ancestry')
|
||||
target=owners[0];need(target['pid']==old['pid'] and target['app_id']==old['app_id']=='org.mosaic.SavedBufferProbe','same actual client')
|
||||
a=oldws['rect'];b=ws['rect'];need([b[k] for k in ('x','y','width','height')]==[a['x']+30,a['y']+30,a['width']-60,a['height']-60],'requested workspace rect changed by runtime gaps30')
|
||||
proof=held_configure(before,after,prior)
|
||||
need(proof['ns']>request_ns and proof['requested_size']==[target['window_rect']['width'],target['window_rect']['height']],'held wire after command matches requested dimensions')
|
||||
need(proof['initial_size']==[old['window_rect']['width'],old['window_rect']['height']],'initial applied dimensions')
|
||||
return dict(held=proof,workspace=ws,owner=target)
|
||||
|
||||
def rgb_exact(a,b):
|
||||
need(a.mode==b.mode=='RGB' and a.size==b.size,'whole RGB contract')
|
||||
need(ImageChops.difference(a,b).getbbox() is None,'whole RGB equal; no masked channel/ROI');return True
|
||||
|
||||
def timeout_line(text,activation_line,client_alive,request_ns,observed_ns,cleanup_ns):
|
||||
need(client_alive and request_ns+4_700_000_000<=observed_ns<cleanup_ns,'natural timeout window/client alive')
|
||||
matches=[(i,l) for i,l in enumerate(text.splitlines()) if i>activation_line and re.search(r'Transaction .*timed out \(\d*[1-9]\d* waiting\)',l)]
|
||||
need(len(matches)==1,'native natural timeout required');return matches[0]
|
||||
|
||||
def saved_identity(*args,**kwargs):raise ValueError("CORE native saved identity NOT OBSERVABLE")
|
||||
def natural_retirement(*args,**kwargs):raise ValueError("CORE retirement NOT OBSERVABLE")
|
||||
|
||||
|
||||
def released_configure(held,after,release_ns,timeout_ns,client_alive=True):
|
||||
"""POST-RELEASE accepts the ACK forbidden in the immutable HOLD prefix."""
|
||||
need(client_alive and held['ns']<timeout_ns<release_ns,'HOLD timeout release phase order')
|
||||
serial=held['serial']
|
||||
a=re.findall(rf'ACK ns=(\d+) serial={serial}\b',after)
|
||||
c=re.findall(rf'COMMIT ns=(\d+) serial={serial}\b',after)
|
||||
need(len(a)==1 and len(c)==1,'unique released ACK/commit')
|
||||
ack,commit=int(a[0]),int(c[0])
|
||||
need(release_ns<ack<=commit,'release precedes live ACK and commit')
|
||||
need(re.search(rf'ack_configure\({serial}\)',after),'released ACK actual wire')
|
||||
return dict(serial=serial,release_ns=release_ns,ack_ns=ack,commit_ns=commit,status='POST_RELEASE_LIVE')
|
||||
74
tooling/probes/step20c-corec-v2/graphics/validators.py
Normal file
74
tooling/probes/step20c-corec-v2/graphics/validators.py
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
"""Imported live/offline scientific predicates, not a parallel test model."""
|
||||
import re
|
||||
from geometry import need,exact,crossing,expected_outputs
|
||||
from predicates import protocol
|
||||
|
||||
def client_exit(log,code):
|
||||
if 'TOPLEVEL CREATE ' in log:
|
||||
order=re.findall(r'(POPUP_[AB]|TOPLEVEL) DESTROY',log)
|
||||
need(order in (['POPUP_B','POPUP_A','TOPLEVEL'],['TOPLEVEL']),'popup child-first destruction order')
|
||||
need(log.index('CLOSE_RECEIVED')<log.index('TOPLEVEL DESTROY'),'close precedes destruction')
|
||||
need('CLOSE_RECEIVED' in log,'explicit close receipt absent')
|
||||
releases=re.findall(r'SHM_CLOSURE created=(\d+) released=(\d+)',log)
|
||||
need(len(releases)==1 and int(releases[0][0])>0 and releases[0][0]==releases[0][1],'client SHM release incomplete/unobserved')
|
||||
need(code==0,'client nonzero');errors=re.findall(r'EXIT[^\n]*error=(\d+)',log)
|
||||
need(bool(errors) and all(e=='0' for e in errors),'client error/nonobservable exit')
|
||||
need(not re.search(r'protocol error|wl_display[^\n]*\.error\(',log,re.I),'wire error')
|
||||
def window(start,end,events):
|
||||
need(start<end and len(events)==1 and start<=events[0]<=end,'ambiguous event window')
|
||||
def owner(node,owner_id,workspace_id,path):
|
||||
need(node['id']==owner_id and [n['id'] for n in path if n['type']=='workspace']==[workspace_id],'wrong owner ancestry')
|
||||
def rgb(reference,actual,masked=False):
|
||||
need(not masked,'H2 occlusion/mask forbidden');return exact(reference,actual)
|
||||
def grab(log):return protocol(log)
|
||||
def applied(geometry,paint_origin,pointer_origin,state):
|
||||
need(state=='applied' and geometry==paint_origin==pointer_origin,'pending/uncorroborated origin')
|
||||
def crosses(rect,axis,boundary):need(crossing(rect,axis,boundary),'crossing not exercised')
|
||||
def shadow(before,after):need(before.tobytes()!=after.tobytes(),'shadow not exercised')
|
||||
def saved_identity(identity,binary_sha):
|
||||
need(identity['binary_sha256']==binary_sha,'wrong binary saved identity')
|
||||
need(identity.get('native') is True,'saved identity not observable')
|
||||
|
||||
def camera_eligible(path,ws):
|
||||
# Null flags are legitimate on IPC descriptors; branch membership remains
|
||||
# authoritative even when a child omits its floating flag.
|
||||
floating=any((n.get('floating') or 'auto_off').endswith('on') for n in path)
|
||||
floating |= any(c in p.get('floating_nodes',[]) for p,c in zip(path,path[1:]))
|
||||
return ws.get('layout')=='mosaic' and not floating and not any(n.get('type')=='con' and n.get('fullscreen_mode') for n in path)
|
||||
|
||||
def owner_context(node,path,pid,workspace_name,output,layout):
|
||||
need(path and path[-1]==node and node.get('pid')==pid,'owner path/pid mismatch')
|
||||
ws=[n for n in path if n.get('type')=='workspace'];outs=[n for n in path if n.get('type')=='output']
|
||||
need(len(ws)==len(outs)==1,'owner ancestry must include workspace/output')
|
||||
need(ws[0].get('name')==workspace_name and ws[0].get('output')==output and outs[0].get('name')==output and ws[0].get('layout')==layout,'wrong mapped workspace/output/layout')
|
||||
need(not any(c in p.get('floating_nodes',[]) for p,c in zip(path,path[1:])),'owner floating ancestry')
|
||||
need(not any((n.get('floating') or 'auto_off').endswith('on') for n in path),'owner floating flag')
|
||||
owner(node,node['id'],ws[0]['id'],path)
|
||||
return ws[0]
|
||||
|
||||
def selected_workspace(snapshot,name,output,layout):
|
||||
from ipc_predicates import workspace
|
||||
ws=workspace(snapshot['tree'],name)
|
||||
need(ws.get('output')==output and ws.get('layout')==layout,'pre-map workspace/output/layout')
|
||||
rows=[r for r in snapshot['workspaces'] if r.get('name')==name]
|
||||
need(len(rows)==1 and rows[0].get('focused') and rows[0].get('output')==output,'pre-map workspace not selected')
|
||||
return ws
|
||||
|
||||
def fullscreen_ancestor(path):
|
||||
rows=[n for n in path[:-1] if n.get('type')=='con' and n.get('fullscreen_mode') and n.get('nodes')]
|
||||
need(rows,'nonleaf current fullscreen ancestor NOT EXERCISED')
|
||||
return rows[-1]
|
||||
|
||||
def hidden_survival(log,initial,injections):
|
||||
need(not any(e.get('action')!='move' for e in injections),'hide/return contaminated by dismissal click')
|
||||
fresh=grab(log)
|
||||
need(fresh['ids']==initial['ids'] and fresh['grabs']==initial['grabs'],'same live grabbed chain required')
|
||||
return fresh
|
||||
|
||||
def standard_spill(images,colors):
|
||||
# Native clip identity is unavailable on CORE: demonstrate effective removal
|
||||
# through both continued popup paints, then freshly corroborated pointer input.
|
||||
need(all(any(px==colors[n] for px in images[1].get_flattened_data()) for n in ('POPUP_A','POPUP_B')),'standard continuation for both A/B NOT EXERCISED')
|
||||
|
||||
def evidence_identity(record,binary_sha):
|
||||
need(record.get('binary_sha256')==binary_sha and record.get('measurement_kind')=='raw','wrong SHA or superficial status')
|
||||
221
tooling/probes/step20c-corec-v2/graphics/wire-fixture.txt
Normal file
221
tooling/probes/step20c-corec-v2/graphics/wire-fixture.txt
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
[22:46:38.282527] {Default Queue} -> wl_display#1.get_registry(new id wl_registry#2)
|
||||
[22:46:38.282546] {Default Queue} -> wl_display#1.sync(new id wl_callback#3)
|
||||
[22:46:38.282664] {Display Queue} wl_display#1.delete_id(3)
|
||||
[22:46:38.282677] {Default Queue} wl_registry#2.global(1, "wl_fixes", 1)
|
||||
[22:46:38.282685] {Default Queue} wl_registry#2.global(2, "wl_shm", 2)
|
||||
[22:46:38.282692] {Default Queue} -> wl_registry#2.bind(2, "wl_shm", 1, new id [unknown]#4)
|
||||
[22:46:38.282699] {Default Queue} wl_registry#2.global(3, "zwp_linux_dmabuf_v1", 5)
|
||||
[22:46:38.282703] {Default Queue} wl_registry#2.global(4, "wp_linux_drm_syncobj_manager_v1", 1)
|
||||
[22:46:38.282708] {Default Queue} wl_registry#2.global(5, "wl_compositor", 6)
|
||||
[22:46:38.282712] {Default Queue} -> wl_registry#2.bind(5, "wl_compositor", 4, new id [unknown]#5)
|
||||
[22:46:38.282716] {Default Queue} wl_registry#2.global(6, "wl_subcompositor", 1)
|
||||
[22:46:38.282720] {Default Queue} wl_registry#2.global(7, "wl_data_device_manager", 3)
|
||||
[22:46:38.282724] {Default Queue} wl_registry#2.global(8, "zwlr_gamma_control_manager_v1", 1)
|
||||
[22:46:38.282728] {Default Queue} wl_registry#2.global(9, "zxdg_output_manager_v1", 3)
|
||||
[22:46:38.282732] {Default Queue} wl_registry#2.global(10, "ext_idle_notifier_v1", 2)
|
||||
[22:46:38.282736] {Default Queue} wl_registry#2.global(11, "zwp_idle_inhibit_manager_v1", 1)
|
||||
[22:46:38.282740] {Default Queue} wl_registry#2.global(12, "zwlr_layer_shell_v1", 5)
|
||||
[22:46:38.282744] {Default Queue} wl_registry#2.global(13, "xdg_wm_base", 5)
|
||||
[22:46:38.282751] {Default Queue} -> wl_registry#2.bind(13, "xdg_wm_base", 3, new id [unknown]#6)
|
||||
[22:46:38.282756] {Default Queue} wl_registry#2.global(14, "zwp_tablet_manager_v2", 1)
|
||||
[22:46:38.282760] {Default Queue} wl_registry#2.global(15, "org_kde_kwin_server_decoration_manager", 1)
|
||||
[22:46:38.282764] {Default Queue} wl_registry#2.global(16, "zxdg_decoration_manager_v1", 1)
|
||||
[22:46:38.282768] {Default Queue} wl_registry#2.global(17, "zwp_relative_pointer_manager_v1", 1)
|
||||
[22:46:38.282772] {Default Queue} wl_registry#2.global(18, "zwp_pointer_constraints_v1", 1)
|
||||
[22:46:38.282776] {Default Queue} wl_registry#2.global(19, "wp_presentation", 2)
|
||||
[22:46:38.282780] {Default Queue} wl_registry#2.global(20, "wp_alpha_modifier_v1", 1)
|
||||
[22:46:38.282784] {Default Queue} wl_registry#2.global(21, "zwlr_output_manager_v1", 4)
|
||||
[22:46:38.282788] {Default Queue} wl_registry#2.global(22, "zwlr_output_power_manager_v1", 1)
|
||||
[22:46:38.282792] {Default Queue} wl_registry#2.global(23, "zwp_input_method_manager_v2", 1)
|
||||
[22:46:38.282796] {Default Queue} wl_registry#2.global(24, "zwp_text_input_manager_v3", 1)
|
||||
[22:46:38.282799] {Default Queue} wl_registry#2.global(25, "ext_foreign_toplevel_list_v1", 1)
|
||||
[22:46:38.282803] {Default Queue} wl_registry#2.global(26, "zwlr_foreign_toplevel_manager_v1", 3)
|
||||
[22:46:38.282807] {Default Queue} wl_registry#2.global(27, "ext_session_lock_manager_v1", 1)
|
||||
[22:46:38.282812] {Default Queue} wl_registry#2.global(28, "ext_workspace_manager_v1", 1)
|
||||
[22:46:38.282816] {Default Queue} wl_registry#2.global(29, "zwlr_export_dmabuf_manager_v1", 1)
|
||||
[22:46:38.282820] {Default Queue} wl_registry#2.global(30, "zwlr_screencopy_manager_v1", 3)
|
||||
[22:46:38.282824] {Default Queue} wl_registry#2.global(31, "ext_image_copy_capture_manager_v1", 1)
|
||||
[22:46:38.282828] {Default Queue} wl_registry#2.global(32, "ext_output_image_capture_source_manager_v1", 1)
|
||||
[22:46:38.282832] {Default Queue} wl_registry#2.global(33, "zwlr_data_control_manager_v1", 2)
|
||||
[22:46:38.282836] {Default Queue} wl_registry#2.global(34, "ext_data_control_manager_v1", 1)
|
||||
[22:46:38.282840] {Default Queue} wl_registry#2.global(35, "wp_security_context_manager_v1", 1)
|
||||
[22:46:38.282844] {Default Queue} wl_registry#2.global(36, "wp_viewporter", 1)
|
||||
[22:46:38.282848] {Default Queue} wl_registry#2.global(37, "wp_single_pixel_buffer_manager_v1", 1)
|
||||
[22:46:38.282852] {Default Queue} wl_registry#2.global(38, "wp_content_type_manager_v1", 1)
|
||||
[22:46:38.282856] {Default Queue} wl_registry#2.global(39, "wp_fractional_scale_manager_v1", 1)
|
||||
[22:46:38.282860] {Default Queue} wl_registry#2.global(40, "ext_foreign_toplevel_image_capture_source_manager_v1", 1)
|
||||
[22:46:38.282872] {Default Queue} wl_registry#2.global(41, "wp_tearing_control_manager_v1", 1)
|
||||
[22:46:38.282886] {Default Queue} wl_registry#2.global(42, "zxdg_exporter_v1", 1)
|
||||
[22:46:38.282890] {Default Queue} wl_registry#2.global(43, "zxdg_importer_v1", 1)
|
||||
[22:46:38.282894] {Default Queue} wl_registry#2.global(44, "zxdg_exporter_v2", 1)
|
||||
[22:46:38.282898] {Default Queue} wl_registry#2.global(45, "zxdg_importer_v2", 1)
|
||||
[22:46:38.282903] {Default Queue} wl_registry#2.global(46, "xdg_activation_v1", 1)
|
||||
[22:46:38.282907] {Default Queue} wl_registry#2.global(47, "xdg_toplevel_tag_manager_v1", 1)
|
||||
[22:46:38.282911] {Default Queue} wl_registry#2.global(48, "wp_cursor_shape_manager_v1", 2)
|
||||
[22:46:38.282915] {Default Queue} wl_registry#2.global(49, "wp_color_representation_manager_v1", 1)
|
||||
[22:46:38.282919] {Default Queue} wl_registry#2.global(50, "zwp_virtual_keyboard_manager_v1", 1)
|
||||
[22:46:38.282923] {Default Queue} wl_registry#2.global(51, "zwlr_virtual_pointer_manager_v1", 2)
|
||||
[22:46:38.282927] {Default Queue} wl_registry#2.global(52, "zwp_keyboard_shortcuts_inhibit_manager_v1", 1)
|
||||
[22:46:38.282931] {Default Queue} wl_registry#2.global(53, "zwp_pointer_gestures_v1", 3)
|
||||
[22:46:38.282935] {Default Queue} wl_registry#2.global(54, "ext_transient_seat_manager_v1", 1)
|
||||
[22:46:38.282939] {Default Queue} wl_registry#2.global(55, "wl_seat", 9)
|
||||
[22:46:38.282944] {Default Queue} -> wl_registry#2.bind(55, "wl_seat", 5, new id [unknown]#7)
|
||||
[22:46:38.282948] {Default Queue} wl_registry#2.global(57, "zwp_primary_selection_device_manager_v1", 1)
|
||||
[22:46:38.282953] {Default Queue} wl_registry#2.global(58, "wl_output", 4)
|
||||
[22:46:38.282957] {Default Queue} wl_registry#2.global(59, "wl_output", 4)
|
||||
[22:46:38.282961] {Default Queue} wl_callback#3.done(16)
|
||||
[22:46:38.282966] {Default Queue} -> wl_display#1.sync(new id wl_callback#3)
|
||||
[22:46:38.283205] {Display Queue} wl_display#1.delete_id(3)
|
||||
[22:46:38.283217] {Default Queue} discarded wl_shm#4.format(0)
|
||||
[22:46:38.283222] {Default Queue} discarded wl_shm#4.format(1)
|
||||
[22:46:38.283227] {Default Queue} discarded wl_shm#4.format(875709016)
|
||||
[22:46:38.283231] {Default Queue} discarded wl_shm#4.format(875708993)
|
||||
[22:46:38.283235] {Default Queue} discarded wl_shm#4.format(875710274)
|
||||
[22:46:38.283239] {Default Queue} discarded wl_shm#4.format(842094674)
|
||||
[22:46:38.283242] {Default Queue} discarded wl_shm#4.format(842088786)
|
||||
[22:46:38.283246] {Default Queue} discarded wl_shm#4.format(892426322)
|
||||
[22:46:38.283249] {Default Queue} discarded wl_shm#4.format(892420434)
|
||||
[22:46:38.283253] {Default Queue} discarded wl_shm#4.format(909199186)
|
||||
[22:46:38.283257] {Default Queue} discarded wl_shm#4.format(808665688)
|
||||
[22:46:38.283260] {Default Queue} discarded wl_shm#4.format(808665665)
|
||||
[22:46:38.283264] {Default Queue} discarded wl_shm#4.format(1213351746)
|
||||
[22:46:38.283268] {Default Queue} discarded wl_shm#4.format(1211384408)
|
||||
[22:46:38.283271] {Default Queue} discarded wl_shm#4.format(1211384385)
|
||||
[22:46:38.283275] {Default Queue} discarded wl_shm#4.format(942950210)
|
||||
[22:46:38.283279] {Default Queue} discarded wl_shm#4.format(942948952)
|
||||
[22:46:38.283282] {Default Queue} discarded wl_shm#4.format(942948929)
|
||||
[22:46:38.283286] {Default Queue} wl_seat#7.name("seat0")
|
||||
[22:46:38.283291] {Default Queue} wl_seat#7.capabilities(1)
|
||||
[22:46:38.283296] {Default Queue} -> wl_seat#7.get_pointer(new id wl_pointer#8)
|
||||
[22:46:38.283300] {Default Queue} wl_callback#3.done(16)
|
||||
[22:46:38.283305] {Default Queue} -> wl_compositor#5.create_surface(new id wl_surface#3)
|
||||
[22:46:38.283310] {Default Queue} -> xdg_wm_base#6.get_xdg_surface(new id xdg_surface#9, wl_surface#3)
|
||||
[22:46:38.283319] {Default Queue} -> xdg_surface#9.get_toplevel(new id xdg_toplevel#10)
|
||||
[22:46:38.283324] {Default Queue} -> xdg_toplevel#10.set_title("Mosaic Nested Grab Probe")
|
||||
[22:46:38.283328] {Default Queue} -> xdg_toplevel#10.set_app_id("org.mosaic.NestedGrabProbe")
|
||||
TOPLEVEL CREATE wl=3 xdg=9 role=10 placement=v_both_cross
|
||||
[22:46:38.283338] {Default Queue} -> wl_surface#3.commit()
|
||||
[22:46:38.283679] {Default Queue} xdg_wm_base#6.ping(17)
|
||||
[22:46:38.283698] {Default Queue} -> xdg_wm_base#6.pong(17)
|
||||
[22:46:38.283708] {Default Queue} xdg_toplevel#10.configure(0, 0, array[0])
|
||||
TOPLEVEL ROLE_CONFIGURE w=0 h=0
|
||||
[22:46:38.283716] {Default Queue} xdg_surface#9.configure(18)
|
||||
TOPLEVEL XDG_CONFIGURE serial=18 ack
|
||||
[22:46:38.283724] {Default Queue} -> xdg_surface#9.ack_configure(18)
|
||||
[22:46:38.284502] {Default Queue} -> wl_shm#4.create_pool(new id wl_shm_pool#11, fd 5, 966000)
|
||||
[22:46:38.284513] {Default Queue} -> wl_shm_pool#11.create_buffer(new id wl_buffer#12, 0, 420, 575, 1680, 1)
|
||||
[22:46:38.284520] {Default Queue} -> wl_surface#3.attach(wl_buffer#12, 0, 0)
|
||||
[22:46:38.284525] {Default Queue} -> wl_surface#3.damage_buffer(0, 0, 420, 575)
|
||||
[22:46:38.284530] {Default Queue} -> wl_surface#3.commit()
|
||||
[22:46:38.284534] {Default Queue} -> wl_buffer#12.destroy()
|
||||
[22:46:38.284538] {Default Queue} -> wl_shm_pool#11.destroy()
|
||||
TOPLEVEL MAP wl=3 xdg=9
|
||||
[22:46:38.285508] discarded [unknown]#12.[event 0](0 fd, 8 byte)
|
||||
[22:46:38.285514] {Display Queue} wl_display#1.delete_id(12)
|
||||
[22:46:38.285519] {Display Queue} wl_display#1.delete_id(11)
|
||||
[22:46:38.285524] {Default Queue} xdg_toplevel#10.configure(636, 573, array[20])
|
||||
TOPLEVEL ROLE_CONFIGURE w=636 h=573
|
||||
[22:46:38.285532] {Default Queue} xdg_surface#9.configure(19)
|
||||
TOPLEVEL XDG_CONFIGURE serial=19 ack
|
||||
[22:46:38.285539] {Default Queue} -> xdg_surface#9.ack_configure(19)
|
||||
[22:46:38.583097] {Default Queue} wl_pointer#8.enter(24, wl_surface#3, 238.00000000, 275.00000000)
|
||||
POINTER_ENTER target=TOPLEVEL wl=3 serial=24 x=238.00 y=275.00
|
||||
[22:46:38.583124] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:38.583129] {Default Queue} wl_pointer#8.leave(25, wl_surface#3)
|
||||
POINTER_LEAVE target=TOPLEVEL serial=25
|
||||
[22:46:38.583137] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:39.867771] {Default Queue} wl_pointer#8.enter(30, wl_surface#3, 3.00000000, 5.00000000)
|
||||
POINTER_ENTER target=TOPLEVEL wl=3 serial=30 x=3.00 y=5.00
|
||||
[22:46:39.867787] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:39.867792] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:39.968604] {Default Queue} wl_pointer#8.leave(31, wl_surface#3)
|
||||
POINTER_LEAVE target=TOPLEVEL serial=31
|
||||
[22:46:39.968649] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:40.117908] {Default Queue} wl_pointer#8.enter(32, wl_surface#3, 3.00000000, 5.00000000)
|
||||
POINTER_ENTER target=TOPLEVEL wl=3 serial=32 x=3.00 y=5.00
|
||||
[22:46:40.117939] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:40.117943] {Default Queue} wl_pointer#8.button(33, 1002, 272, 1)
|
||||
POINTER_BUTTON target=TOPLEVEL serial=33 button=272 state=1
|
||||
[22:46:40.117951] {Default Queue} -> wl_compositor#5.create_surface(new id wl_surface#11)
|
||||
[22:46:40.117956] {Default Queue} -> xdg_wm_base#6.get_xdg_surface(new id xdg_surface#12, wl_surface#11)
|
||||
[22:46:40.117960] {Default Queue} -> xdg_wm_base#6.create_positioner(new id xdg_positioner#13)
|
||||
[22:46:40.117964] {Default Queue} -> xdg_positioner#13.set_size(250, 170)
|
||||
[22:46:40.117967] {Default Queue} -> xdg_positioner#13.set_anchor_rect(50, 480, 1, 1)
|
||||
[22:46:40.117970] {Default Queue} -> xdg_positioner#13.set_anchor(5)
|
||||
[22:46:40.117973] {Default Queue} -> xdg_positioner#13.set_gravity(8)
|
||||
[22:46:40.117976] {Default Queue} -> xdg_surface#12.get_popup(new id xdg_popup#14, xdg_surface#9, xdg_positioner#13)
|
||||
POPUP_A CREATE wl=11 xdg=12 role=14 parent=TOPLEVEL parent_xdg=9
|
||||
POPUP_A GRAB_REQUEST origin=wl_pointer.button target=TOPLEVEL serial=33 used=33
|
||||
[22:46:40.117984] {Default Queue} -> xdg_popup#14.grab(wl_seat#7, 33)
|
||||
[22:46:40.117988] {Default Queue} -> wl_surface#11.commit()
|
||||
[22:46:40.117991] {Default Queue} -> xdg_positioner#13.destroy()
|
||||
[22:46:40.117994] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:40.118126] {Display Queue} wl_display#1.delete_id(13)
|
||||
[22:46:40.118129] {Default Queue} xdg_popup#14.configure(50, 480, 250, 170)
|
||||
POPUP_A ROLE_CONFIGURE x=50 y=480 w=250 h=170
|
||||
[22:46:40.118135] {Default Queue} xdg_surface#12.configure(34)
|
||||
POPUP_A XDG_CONFIGURE serial=34 ack
|
||||
[22:46:40.118140] {Default Queue} -> xdg_surface#12.ack_configure(34)
|
||||
[22:46:40.118286] {Default Queue} -> wl_shm#4.create_pool(new id wl_shm_pool#13, fd 5, 170000)
|
||||
[22:46:40.118298] {Default Queue} -> wl_shm_pool#13.create_buffer(new id wl_buffer#15, 0, 250, 170, 1000, 1)
|
||||
[22:46:40.118313] {Default Queue} -> wl_surface#11.attach(wl_buffer#15, 0, 0)
|
||||
[22:46:40.118317] {Default Queue} -> wl_surface#11.damage_buffer(0, 0, 250, 170)
|
||||
[22:46:40.118320] {Default Queue} -> wl_surface#11.commit()
|
||||
[22:46:40.118322] {Default Queue} -> wl_buffer#15.destroy()
|
||||
[22:46:40.118325] {Default Queue} -> wl_shm_pool#13.destroy()
|
||||
POPUP_A MAP wl=11 xdg=12
|
||||
[22:46:40.118959] discarded [unknown]#15.[event 0](0 fd, 8 byte)
|
||||
[22:46:40.118962] {Display Queue} wl_display#1.delete_id(15)
|
||||
[22:46:40.118965] {Display Queue} wl_display#1.delete_id(13)
|
||||
[22:46:40.197365] {Default Queue} wl_pointer#8.button(35, 1003, 272, 0)
|
||||
POINTER_BUTTON target=TOPLEVEL serial=35 button=272 state=0
|
||||
[22:46:40.197412] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:40.298184] {Default Queue} wl_pointer#8.leave(36, wl_surface#3)
|
||||
POINTER_LEAVE target=TOPLEVEL serial=36
|
||||
[22:46:40.298225] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:40.585174] {Default Queue} wl_pointer#8.enter(37, wl_surface#3, 3.00000000, 5.00000000)
|
||||
POINTER_ENTER target=TOPLEVEL wl=3 serial=37 x=3.00 y=5.00
|
||||
[22:46:40.585201] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:40.585208] {Default Queue} wl_pointer#8.leave(38, wl_surface#3)
|
||||
POINTER_LEAVE target=TOPLEVEL serial=38
|
||||
[22:46:40.585221] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:40.585227] {Default Queue} wl_pointer#8.enter(39, wl_surface#11, 4.00000000, 5.00000000)
|
||||
POINTER_ENTER target=POPUP_A wl=11 serial=39 x=4.00 y=5.00
|
||||
[22:46:40.585240] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:40.585246] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:40.685838] {Default Queue} wl_pointer#8.leave(40, wl_surface#11)
|
||||
POINTER_LEAVE target=POPUP_A serial=40
|
||||
[22:46:40.685909] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:40.835646] {Default Queue} wl_pointer#8.enter(41, wl_surface#11, 4.00000000, 5.00000000)
|
||||
POINTER_ENTER target=POPUP_A wl=11 serial=41 x=4.00 y=5.00
|
||||
[22:46:40.835675] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:40.835682] {Default Queue} wl_pointer#8.button(42, 1005, 272, 1)
|
||||
POINTER_BUTTON target=POPUP_A serial=42 button=272 state=1
|
||||
[22:46:40.835696] {Default Queue} -> wl_compositor#5.create_surface(new id wl_surface#13)
|
||||
[22:46:40.835704] {Default Queue} -> xdg_wm_base#6.get_xdg_surface(new id xdg_surface#15, wl_surface#13)
|
||||
[22:46:40.835712] {Default Queue} -> xdg_wm_base#6.create_positioner(new id xdg_positioner#16)
|
||||
[22:46:40.835719] {Default Queue} -> xdg_positioner#16.set_size(170, 130)
|
||||
[22:46:40.835726] {Default Queue} -> xdg_positioner#16.set_anchor_rect(20, 80, 1, 1)
|
||||
[22:46:40.835733] {Default Queue} -> xdg_positioner#16.set_anchor(5)
|
||||
[22:46:40.835738] {Default Queue} -> xdg_positioner#16.set_gravity(8)
|
||||
[22:46:40.835745] {Default Queue} -> xdg_surface#15.get_popup(new id xdg_popup#17, xdg_surface#12, xdg_positioner#16)
|
||||
POPUP_B CREATE wl=13 xdg=15 role=17 parent=POPUP_A parent_xdg=12
|
||||
POPUP_B GRAB_REQUEST origin=wl_pointer.button target=POPUP_A serial=42 used=42
|
||||
[22:46:40.835761] {Default Queue} -> xdg_popup#17.grab(wl_seat#7, 42)
|
||||
[22:46:40.835768] {Default Queue} -> wl_surface#13.commit()
|
||||
[22:46:40.835775] {Default Queue} -> xdg_positioner#16.destroy()
|
||||
[22:46:40.835781] {Default Queue} wl_pointer#8.frame()
|
||||
[22:46:40.835930] {Display Queue} wl_display#1.delete_id(16)
|
||||
[22:46:40.835937] {Default Queue} xdg_popup#17.configure(20, 80, 170, 130)
|
||||
POPUP_B ROLE_CONFIGURE x=20 y=80 w=170 h=130
|
||||
[22:46:40.835949] {Default Queue} xdg_surface#15.configure(43)
|
||||
POPUP_B XDG_CONFIGURE serial=43 ack
|
||||
[22:46:40.835960] {Default Queue} -> xdg_surface#15.ack_configure(43)
|
||||
[22:46:40.836118] {Default Queue} -> wl_shm#4.create_pool(new id wl_shm_pool#16, fd 5, 88400)
|
||||
[22:46:40.836128] {Default Queue} -> wl_shm_pool#16.create_buffer(new id wl_buffer#18, 0, 170, 130, 680, 1)
|
||||
[22:46:40.836135] {Default Queue} -> wl_surface#13.attach(wl_buffer#18, 0, 0)
|
||||
[22:46:40.836142] {Default Queue} -> wl_surface#13.damage_buffer(0, 0, 170, 130)
|
||||
[22:46:40.836149] {Default Queue} -> wl_surface#13.commit()
|
||||
[22:46:40.836155] {Default Queue} -> wl_buffer#18.destroy()
|
||||
[22:46:40.836168] {Default Queue} -> wl_shm_pool#16.destroy()
|
||||
POPUP_B MAP wl=13 xdg=15
|
||||
116
tooling/probes/step20c-corec-v2/reviews/build-review.json
Normal file
116
tooling/probes/step20c-corec-v2/reviews/build-review.json
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
{
|
||||
"reviewer": "/root/v2_build_review",
|
||||
"identity": "COREC_V2_REDERIVED",
|
||||
"scope": "D5 independent exact private build/ELF/current pair/stage/compiled client identities and bounded retained pristine control; no compositor main/client execution, IPC, headless, ledger reservation, rebuild or Git mutation",
|
||||
"verdict": "PASS",
|
||||
"gate_results": {
|
||||
"COREC_V2_PRIVATE_BUILD": "PASS",
|
||||
"COREC_V2_PRIVATE_ELF_ABI": "PASS_BOUNDED_EXACT_CURRENT_PAIR",
|
||||
"COREC_V2_PRIVATE_STAGE": "PASS",
|
||||
"COREC_V2_BUILD_REVIEW": "ACCEPTED"
|
||||
},
|
||||
"blocking_findings": [],
|
||||
"nonblocking_findings": [
|
||||
{
|
||||
"classification": "HISTORICAL_LIMITATION",
|
||||
"file": "inputs/run-env-before-desktop-clear.sh; logs/run-command.py",
|
||||
"symbol": "inherited subprocess environment",
|
||||
"exact_requirement": "D5 environnement sans h\u00e9ritage de session",
|
||||
"evidence": "Original wrapper retained SWAYSOCK/WAYLAND_DISPLAY/DISPLAY sentinels; historical command receipts lack full environment. No compositor/client main or IPC consumer executed in this build scope; no session access observed. Historical clear is NOT_PROVEN. Prospective wrapper corrected by parent, independently retested and clean_environment allowlist rejects session/loader/backend sentinels.",
|
||||
"correction_direction": "Preserve historical NOT_PROVEN; use corrected wrapper and independently reviewed allowlist for future commands. No rebuild or identity replacement is justified by this traceability limitation."
|
||||
}
|
||||
],
|
||||
"identities": {
|
||||
"source_review_sha256": "26aa862c741196fdbb8d7cdf5509301e49f763cdd22ffa7bcb1a4bbf09c66f90",
|
||||
"archive_sha256": "769b56c4a11bef13f0abdcfd189b11af954eebbfa69fd06c972e01508d8d376b",
|
||||
"binary_sha256": "91f48aae2675331e0a2c60ec2fedf983745c521c75232c704a6516a4140801fb",
|
||||
"build_id": "d5d304533f2c8db9d738365e9bd7f2c96779ef8d",
|
||||
"wlroots_sha256": "7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837",
|
||||
"pristine_sha256": "b7fa32116e3c42706b9b02da7df12f27e76d82dd253deb8bdec81519eed145f6",
|
||||
"pristine_build_id": "ba9751c82e73d4043b7651354f20936d070c2227"
|
||||
},
|
||||
"evidence": {
|
||||
"source_files_verified": 502,
|
||||
"overlap_count": 85,
|
||||
"wlroots_relocations_bound_to_executable": 64,
|
||||
"stage": "5 exact regular files; binary0755 and notices0644; pinned license bytes verified; legal PARTIAL_NOT_APPROVED",
|
||||
"clients": "7 input files and 9 generated files match receipt; five C17 compiler exits0; old receipt paths relocated to graphics-tests/client-build disclosed",
|
||||
"pristine_files": 395,
|
||||
"pristine_qualification": "QUALIFIED_BOUNDED_RETAINED_CONTROL: current 395 pinned sources exact, retained build metadata and binary identities exact; original compile-time manifest absent, not bit-rebuild proof; runtime /proc mapping and renderer NOT_RUN"
|
||||
},
|
||||
"native_bits_inspection": [
|
||||
"factory disassembly: parent assertion; calloc128 -> checked NULL; calloc64 -> free unpublished tree and NULL on failure; scene_tree_init and destroy-listener registration only afterwards",
|
||||
"setter disassembly: reserved-sidecar lookup; abort on unreserved/embedded root; no calloc; storage pointer private",
|
||||
"private set/get/sidecar absent from executable dynsym; no scenefx DT_NEEDED; no RPATH/RUNPATH; link command names exact private static archive before shared wlroots",
|
||||
"85 exact overlap inventory; 64 wlroots overlap symbols with dynamic relocations resolved to executable in loader trace; remaining21 have no wlroots dynamic relocation; ldd-r and explicit LD_TRACE_LOADED_OBJECTS1/LD_WARNyes/LD_BIND_NOWyes/LD_DEBUGbindings exit0, no unresolved symbol",
|
||||
"Accepted source/C review binds NULL/embedded-root caller audit, full native types and unchanged public layout; 502 current source bytes/modes reverified rather than re-auditing unrelated code"
|
||||
],
|
||||
"compiler_policy": {
|
||||
"scene": "C23 native build; werror true; examples false; default_library static",
|
||||
"mosaic": "C11 native build; werror true with only c_args=-Wno-error=switch",
|
||||
"clients": "five C17 compile exits0, no execution",
|
||||
"initial_failure": "Preserved GCC16.2.1 LIBINPUT_CONFIG_SCROLL_CIRCULAR Werror switch failure; attempt2 warning remains. ipc-json.c1174 initializes scroll_method unknown; unhandled enum does not read an uninitialized pointer. No source mutation."
|
||||
},
|
||||
"negative_controls": {
|
||||
"file": "negative-controls.json",
|
||||
"sha256": "0767205142e82a9836b848cf18c6ba8ef50d7ff0cc044059f09d71abaf298858",
|
||||
"status": "PASS five direct review-validator rejections; actual modified source copy resides only review-build, never a build input; independent static magic rejects ELF shared object",
|
||||
"boundary": "These are reviewer identity checks, not proof of runtime launcher/reservation correctness; separate graphics review owns those gates"
|
||||
},
|
||||
"pristine_control": "QUALIFIED_BOUNDED_RETAINED_CONTROL; current395 pinned source files independently compared against git fd71a6bdc061bd633b488ae7b83e8a6981d22f86, retained cached source/build directory agrees, original/copied binary SHA and BuildID exact. Historical compile-time source manifest absent; no claim of immutable compile-time source provenance or bit rebuild. Runtime mapping/renderer NOT_RUN.",
|
||||
"recipe_review": "BUILD_RECIPE.md consistent with assembled scene/mosaic paths, accepted source precondition, private static link closure, warning policy, model check and 5-file DESTDIR contract; no rebuild requested or performed",
|
||||
"commands_run": [
|
||||
"git status --short --branch; git log -3 --oneline; git diff --stat; git diff --check",
|
||||
"LAB/run-env python review-build/validate.py",
|
||||
"readelf -n/-d/--dyn-syms/-rW; nm archive and wlroots; objdump factory/setter",
|
||||
"ldd -r with LD_DEBUG=bindings (loader trace only)",
|
||||
"explicit binary argv under LD_TRACE_LOADED_OBJECTS=1 LD_WARN=yes LD_BIND_NOW=yes LD_DEBUG=bindings (loader trace only, no compositor init/main)",
|
||||
"LAB/run-env Python sentinel env checks and direct gate.clean_environment check; git show pin:file comparisons"
|
||||
],
|
||||
"verification_notes": [
|
||||
"Reviewer initial C17 selector mistakenly included cc --version and failed; selector corrected to actual compile commands; final five commands pass. This was a reviewer harness error, not a client/build defect.",
|
||||
"Parent initial build failure retained; accepted attempt2 and model PASS verified from logs and final Meson introspection, no build/model rerun necessary."
|
||||
],
|
||||
"limitations": [
|
||||
"No compositor main/init, Wayland client, IPC, native graphics/OOM/damage/runtime test executed. Runtime wlroots /proc mapping NOT_RUN; loader mapping is separate evidence.",
|
||||
"ABI qualification limited to the exact linked pair and reviewed native source/header contract, not future libraries or full semantic/runtime behavior.",
|
||||
"Graphics Python repair and publication binding remain separate; compiled client input identities alone do not accept scenario behavior.",
|
||||
"Legal PARTIAL_NOT_APPROVED; no distribution authorization."
|
||||
],
|
||||
"headless_authorization": "NOT_AUTHORIZED_BY_THIS_REVIEW_ALONE; require independent final graphics fixture/scenario review, publication binding, resource and runtime guards; no reservation made",
|
||||
"input_hashes": {
|
||||
"results/core-build.json": "7ca04d1b719c0a977b8fd94e0b4c58e7107bc9b1532ece9adbdf88c4b09d81f8",
|
||||
"private-build-recipe.json": "bea6e3e3e10705a77981bd70d744fe51ddf3a283b58e4ece47e820bf5f1ce757",
|
||||
"BUILD_RECIPE.md": "e68766c02f93feaf8128dbd57e645ec3c93b650d077a90b6e1cb2476e804f859",
|
||||
"sources/manifest.json": "0058cf56348f3c70dffbdea85e85a73b923c4d830c86efe859048b58ce8ce7e3",
|
||||
"review-source-c/source-c-review.json": "26aa862c741196fdbb8d7cdf5509301e49f763cdd22ffa7bcb1a4bbf09c66f90",
|
||||
"results/pristine-candidate.json": "4df5ddf322595598cc5bd227650e18c10f99ea34a75873364f1e5df8508c2f67",
|
||||
"results/pristine-elf.json": "1f27292aec0701a987c44e8f8c1a9808139a7e02f9500f87dbc0dd9a45e35e07",
|
||||
"results/graphics-client-compile.json": "d1b1c2ebb13994ecc85f2ba166734637f8848dca6e99cd9acab3bb89c4bddc22",
|
||||
"results/wrapper-environment-correction.json": "3c2a685587782c6cd0a91c0317e9be9aef27ddb45048918cb7f092ac021ec853",
|
||||
"run-env": "51ed9377c74510c1e7bd9d4278920fd2f37cb79ec56c513a63a23f069edcb756",
|
||||
"inputs/run-env-before-desktop-clear.sh": "717a90063329433b90fba966c8338ed94be98ac3ca0418bd4e2192eafabe0c2c",
|
||||
"inputs/ticket.md": "8a082b63ea28a06a192f002a985642ba4108edf41ce32d0a1245980797af0dad"
|
||||
},
|
||||
"artifact_hashes": {
|
||||
"loader-bindings.log": "fa6fa27ef4d1812a795d5896beb766d6c9bb1cc9ee3c42c065bfea3da2ff4f21",
|
||||
"loader-list.log": "28a042ca26f1ed4385a2bf3a085a2434d4303be4adc33e7dc3cfe9086c064535",
|
||||
"archive-symbols.txt": "e13aea4515dbe6f7706b1b15045646d243d35b0870b709fa43a09dfde40f8043",
|
||||
"wlroots-symbols.txt": "729e5b57302da74d90adb1da7cd092b68d0abd69761c6bf2cea2df57f9ccbe61",
|
||||
"factory-disassembly.txt": "5c822f765729dbd32550f9f1f39083a1ba0756b9b08f3ab44d7788a512a894c8",
|
||||
"setter-disassembly.txt": "39fa43770608db5016d1fa9c6f15ea0a8e02a7e0f77254caa0a37145f197d860",
|
||||
"overlap-symbols.json": "6c6f0c0fe19460c0ce3456bb3ee1ac43ab1fe736f17295390585eeb58c2c2c04",
|
||||
"environment-before.json": "dd87762bcc48cd5304f780fed330198c98925363d5e881a316ec2e40e00bd48e",
|
||||
"validate.py": "b4a80f13b7e6b87bf170ebe176fa197a012d7f919042205d2e07e8580d1eb297",
|
||||
"negative-controls.json": "0767205142e82a9836b848cf18c6ba8ef50d7ff0cc044059f09d71abaf298858",
|
||||
"overlap-bindings.json": "963d10e0059eebe9b77b92262f8a785951c55cc423737a0e64eaf2593bd56781",
|
||||
"validation.json": "c3287d45c82967af9ec739ca809bd77eda7f2006aee2c573b93773983c8a3cdb",
|
||||
"environment-after.json": "1004c9f5cc5914aaa42c2236d58406b1f3726e72f8dd0f8d0a19e108bc10a628",
|
||||
"explicit-loader-trace.stdout": "faa5cfea9253ba0b59d8091a41a94e24bcf1f5cd4be595788a8288275f98fe01",
|
||||
"explicit-loader-trace.stderr": "a32f0ea3475edc81597d00d39e901ccea267db561cb999ef6eb0fb573439cd20",
|
||||
"core-scene-options.json": "b470a74b0269b5caf3154253464ea59e6a3746f6e2af3f3208b0ae3cf743bab7",
|
||||
"core-mosaic-options.json": "439515b193d5bda2e790988c6a1a8dc9f9fbfa502a37d932e083ee6a0ad6d941",
|
||||
"mutated-source-NOT-A-BUILD-INPUT.c": "103e4b9eda942c7616d26c2e3f2d1dea25beb1bc3428c219ead3bb4021cb547e"
|
||||
},
|
||||
"changes": "Only review-build artifacts created/updated by reviewer. No source/doc/fixture edit, commit, push or Git mutation. Parent prospective wrapper correction reviewed."
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
{
|
||||
"schema": "corec-v2-graphics-independent-delta-review",
|
||||
"decision": "ACCEPTED",
|
||||
"audit": "PASS",
|
||||
"scope": "B1/B2 correction and direct regressions only; SOURCE/OFFLINE",
|
||||
"reviewer": "independent R-runtime reviewer",
|
||||
"baseline_sha256": "be6666d6b2c6fd6ea73d989f53190d6a851d32d267527fe58a1eb15c889578d6",
|
||||
"source_freeze_sha256": "a905cd571e92668d8352f13ca2b7eef5b19ce7baf0f2c497855ac3c81c8e64fb",
|
||||
"repair_receipt_sha256": "4845ea64e43830b686940e62950e830f9e98e14e01612f9de9f2ff45fd237ffe",
|
||||
"source_files_verified": 28,
|
||||
"changed_files": [
|
||||
"README.md",
|
||||
"cases.py",
|
||||
"engine.py",
|
||||
"offline/test_graphics_v2.py"
|
||||
],
|
||||
"blocking_findings": [],
|
||||
"resolved": {
|
||||
"B1": "Watchdog independently escalates TERM to KILL with absolute146 deadline; spawn registration/expiry lock; cleanup absolute deadlines. Real nongraphic TERM-resistant owned group test passes without main cleanup.",
|
||||
"B2": "Actual640 widths/zero-gap row reconstruction; each resize/migration checks min(previous_scroll,1920-viewport_width), stable owner/workspace/order/output; sampled popup viewport exclusion and wholeH2 on H1; fresh B input or explicit dismissal NOT_EXERCISED."
|
||||
},
|
||||
"tests": {
|
||||
"command": "LAB/run-env Python unittest loader; original32 tests with only temporary fixture location redirected in memory to LAB/review-graphics/OFFLINE_TEST_ONLY",
|
||||
"tests": 32,
|
||||
"failures": 0,
|
||||
"errors": 0,
|
||||
"result": "PASS",
|
||||
"log": "delta-offline-tests.log",
|
||||
"log_sha256": "32d6d619f9a7ccf0676fedbf52258fd9a9c04f84df37febc46b40661ab8a43d6"
|
||||
},
|
||||
"additional_negatives": {
|
||||
"file": "delta-negative-controls.json",
|
||||
"sha256": "827d30d43691c6e90a993a3698f0ab06c6af1c6d4c835ce7f8f2375999b6a90b",
|
||||
"result": "PASS",
|
||||
"checks": [
|
||||
"late TERM145.92 / KILL146.00 absolute deadline",
|
||||
"wrong scroll900 within legal max960 rejected (expected960)",
|
||||
"one-pixel wrong row position rejected"
|
||||
]
|
||||
},
|
||||
"conditional_source_offline_admissible": [
|
||||
"P0",
|
||||
"C1",
|
||||
"C2",
|
||||
"C3",
|
||||
"C4",
|
||||
"C5",
|
||||
"C6",
|
||||
"C7"
|
||||
],
|
||||
"admission_conditions": [
|
||||
"Verified source-only publication binds exact graphics/full kit bytes and source_commit==origin==github",
|
||||
"Accepted exact build/source identities",
|
||||
"Fresh proper frozen manifest and per-case independent review binding the repaired bytes",
|
||||
"Real ledger admission by parent only; no retroactive authorization"
|
||||
],
|
||||
"not_admitted": [
|
||||
"P1",
|
||||
"C8",
|
||||
"O1",
|
||||
"O2",
|
||||
"G2",
|
||||
"R1"
|
||||
],
|
||||
"limitations": [
|
||||
"All native runtime NOT_RUN by this reviewer",
|
||||
"Synthetic protocols/pixels do not qualify native behavior",
|
||||
"C7 post-close capture NOT_TESTED at14cap",
|
||||
"Nonsaturating clamp may remain so, numeric evidence must state it",
|
||||
"Native generation/full damage/saved-buffer/GPU lifetime not proven"
|
||||
],
|
||||
"real_reservations": 0,
|
||||
"native_runs": 0,
|
||||
"source_edits": false,
|
||||
"commit_push_by_reviewer": false,
|
||||
"git_diff_check": "PASS",
|
||||
"working_tree_observed": "clean"
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
{
|
||||
"schema": "corec-v2-graphics-independent-source-offline-review",
|
||||
"decision": "CORRECTION_REQUIRED",
|
||||
"reviewer": "independent R-runtime reviewer",
|
||||
"source_freeze_sha256": "be6666d6b2c6fd6ea73d989f53190d6a851d32d267527fe58a1eb15c889578d6",
|
||||
"source_files_verified": 28,
|
||||
"offline_tests": {
|
||||
"command": "LAB/run-env Python unittest loader of graphics-kit/offline/test_graphics_v2.py; V2.setUp redirects only temporary fixtures into LAB/review-graphics/OFFLINE_TEST_ONLY; source modules unchanged",
|
||||
"tests": 27,
|
||||
"failures": 0,
|
||||
"errors": 0,
|
||||
"log_sha256": "e9f994eccd1e04cf7263f694745c59d46347bce2e8f2d813f4d270af5d130431"
|
||||
},
|
||||
"negative_controls": {
|
||||
"file": "negative-controls.json",
|
||||
"sha256": "a34f071e8cbf1de5a9358d54d561475849cd0a7afa7370a4051b7c5a9b836944"
|
||||
},
|
||||
"blockers": [
|
||||
{
|
||||
"id": "B1",
|
||||
"severity": "HIGH / BLOCKING",
|
||||
"file": "graphics-kit/engine.py",
|
||||
"symbol": "Session.watchdog / Session.cleanup",
|
||||
"lines": [
|
||||
91,
|
||||
98,
|
||||
202,
|
||||
205
|
||||
],
|
||||
"requirement": "Ticket section4: duree150s/run; owned-process watchdog confinement",
|
||||
"evidence": "watchdog_at_144 returns after SIGTERM only. SIGKILL is reached only from main-thread cleanup, after sequential close_client waits and other cleanup waits. No autonomous hard deadline or escalation exists.",
|
||||
"smallest_correction": "Keep watchdog active through bounded TERM grace and SIGKILL before the150s ceiling; make cleanup use the same absolute deadline and test TERM-resistant owned process/group offline."
|
||||
},
|
||||
{
|
||||
"id": "B2",
|
||||
"severity": "HIGH / BLOCKING for C7 admission",
|
||||
"file": "graphics-kit/cases.py",
|
||||
"symbol": "dynamic_campaign",
|
||||
"lines": [
|
||||
95,
|
||||
97,
|
||||
98,
|
||||
103,
|
||||
105
|
||||
],
|
||||
"requirement": "Ticket F7: reconstructed camera clamp and old/new clips/residual pixels after resize and migration",
|
||||
"evidence": "clamp is assigned NOT_PROVEN unconditionally. Both resize captures only feed sample_surviving (B input); no residual/whole-H2 check or reconstructed clamp assertion is applied before dynamic-ownership PASS and PARTIALLY_FEASIBLE.",
|
||||
"smallest_correction": "Implement offline-reviewed clamp and per-mutation rendered-region predicates within frozen caps, or mark C7 NOT_ADMITTED pending its narrower review; do not claim complete F7 qualification."
|
||||
}
|
||||
],
|
||||
"accepted_components": [
|
||||
"Exact28-file source freeze verified",
|
||||
"27 offline tests pass",
|
||||
"prlimit core0:0 real non-graphic child test passes with stablePID and unchanged parent limits; argv traces validated; no preexec_fn",
|
||||
"budgets/reuse/completion/token/source/pub bindings negative controls pass",
|
||||
"additional UID/duration/publication/symlink negatives pass",
|
||||
"P1,C8,O1,O2,G2,R1 NOT_ADMITTED",
|
||||
"native generation/damage/saved/buffer/GPU limitations declared honestly"
|
||||
],
|
||||
"non_blocking": [
|
||||
"C7 post-close capture NOT_TESTED is explicit; no additional capture budget requested",
|
||||
"Synthetic transports do not prove native input, graphics or cleanup"
|
||||
],
|
||||
"runtime": "NOT_RUN",
|
||||
"real_reservations": 0,
|
||||
"native_builds": 0,
|
||||
"sources_edited": false,
|
||||
"commit_push": false,
|
||||
"scope": "No production/live IPC/headless/capture/GPU execution"
|
||||
}
|
||||
54
tooling/probes/step20c-corec-v2/reviews/graphics-repair.json
Normal file
54
tooling/probes/step20c-corec-v2/reviews/graphics-repair.json
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"baseline_sha256": "be6666d6b2c6fd6ea73d989f53190d6a851d32d267527fe58a1eb15c889578d6",
|
||||
"blockers_addressed": [
|
||||
"B1",
|
||||
"B2"
|
||||
],
|
||||
"caps": {
|
||||
"C7": 14,
|
||||
"global_captures": 180,
|
||||
"planned_captures": 170,
|
||||
"runs": 14
|
||||
},
|
||||
"captures": 0,
|
||||
"commit_push": false,
|
||||
"decision": "PENDING_INDEPENDENT_DELTA_REVIEW",
|
||||
"files_changed": [
|
||||
"README.md",
|
||||
"cases.py",
|
||||
"engine.py",
|
||||
"offline/test_graphics_v2.py"
|
||||
],
|
||||
"limitations": [
|
||||
"IPC_PENDING_CANDIDATE; native applied generation NOT OBSERVABLE",
|
||||
"Sampled pixels cannot prove complete damage",
|
||||
"Post-close C7 capture NOT_TESTED; no extra budget",
|
||||
"A nonsaturating expected clamp is recorded as such by numeric old/new values; no extra camera mutation",
|
||||
"Offline synthetic cases do not qualify native C7"
|
||||
],
|
||||
"native_builds": 0,
|
||||
"new_freeze": "/var/tmp/swayfx-mosaic-corec-v2-i516p6po/results/graphics-source-freeze-repaired.json",
|
||||
"new_freeze_sha256": "a905cd571e92668d8352f13ca2b7eef5b19ce7baf0f2c497855ac3c81c8e64fb",
|
||||
"offline_tests": {
|
||||
"errors": 0,
|
||||
"exit": 0,
|
||||
"failures": 0,
|
||||
"log": "/var/tmp/swayfx-mosaic-corec-v2-i516p6po/repair-graphics/offline-final.log",
|
||||
"log_sha256": "fff3f5ec74611f5bcad65c8b8029e6b185b9a2d5e91ee25221c78d0f0c493a28",
|
||||
"tests": 32
|
||||
},
|
||||
"original_failures_preserved": [
|
||||
"offline-attempt1.log: fixture path lacked OFFLINE_TEST_ONLY; 7 errors",
|
||||
"offline-attempt2.log: synthetic quota state missing snapshot; 1 error",
|
||||
"offline-attempt3.log: repeated synthetic snapshot keyword; SyntaxError"
|
||||
],
|
||||
"production_changes": false,
|
||||
"real_reservations": 0,
|
||||
"runtime": "NOT_RUN",
|
||||
"schema": "corec-v2-graphics-bounded-repair",
|
||||
"solution": {
|
||||
"B1": "Atomic registration/expiry; independent TERM143 and KILL<=146; shared absolute cleanup deadline146 and reap147; private group only",
|
||||
"B2": "Pinned calloc/fallback640 corroborated with three actual IPC widths and zero-gap positions; per-mutation clamp min(previous,content-viewport), owner/ws/order/output identity, popup-color exclusion, H2 whole reference on H1, fresh surviving B paint/pointer input or dismissal NOT_EXERCISED"
|
||||
},
|
||||
"source_identity_publication_native_limits": "UNCHANGED"
|
||||
}
|
||||
Loading…
Reference in a new issue