diff --git a/tooling/probes/step20c-corec-v2/BUILD_RECIPE.md b/tooling/probes/step20c-corec-v2/BUILD_RECIPE.md new file mode 100644 index 0000000..d4a7a0a --- /dev/null +++ b/tooling/probes/step20c-corec-v2/BUILD_RECIPE.md @@ -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. diff --git a/tooling/probes/step20c-corec-v2/graphics/README.md b/tooling/probes/step20c-corec-v2/graphics/README.md new file mode 100644 index 0000000..503ff16 --- /dev/null +++ b/tooling/probes/step20c-corec-v2/graphics/README.md @@ -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/` 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. diff --git a/tooling/probes/step20c-corec-v2/graphics/cases.py b/tooling/probes/step20c-corec-v2/graphics/cases.py new file mode 100644 index 0000000..22cc63e --- /dev/null +++ b/tooling/probes/step20c-corec-v2/graphics/cases.py @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; icolor; + 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; +} diff --git a/tooling/probes/step20c-corec-v2/graphics/clients/pointer-daemon.c b/tooling/probes/step20c-corec-v2/graphics/clients/pointer-daemon.c new file mode 100644 index 0000000..9c85433 --- /dev/null +++ b/tooling/probes/step20c-corec-v2/graphics/clients/pointer-daemon.c @@ -0,0 +1,57 @@ +#define _DEFAULT_SOURCE +#include +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/tooling/probes/step20c-corec-v2/graphics/clients/saved-client.c b/tooling/probes/step20c-corec-v2/graphics/clients/saved-client.c new file mode 100644 index 0000000..2eb856c --- /dev/null +++ b/tooling/probes/step20c-corec-v2/graphics/clients/saved-client.c @@ -0,0 +1,253 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/tooling/probes/step20c-corec-v2/graphics/clients/static-reference.c b/tooling/probes/step20c-corec-v2/graphics/clients/static-reference.c new file mode 100644 index 0000000..c6818ba --- /dev/null +++ b/tooling/probes/step20c-corec-v2/graphics/clients/static-reference.c @@ -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" diff --git a/tooling/probes/step20c-corec-v2/graphics/clients/vertical-client.c b/tooling/probes/step20c-corec-v2/graphics/clients/vertical-client.c new file mode 100644 index 0000000..00c4236 --- /dev/null +++ b/tooling/probes/step20c-corec-v2/graphics/clients/vertical-client.c @@ -0,0 +1,308 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; icolor; + 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; +} diff --git a/tooling/probes/step20c-corec-v2/graphics/engine.py b/tooling/probes/step20c-corec-v2/graphics/engine.py new file mode 100644 index 0000000..c895a6a --- /dev/null +++ b/tooling/probes/step20c-corec-v2/graphics/engine.py @@ -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()=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()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]=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['BL'][0] and c['TL'][1]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]= 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} diff --git a/tooling/probes/step20c-corec-v2/graphics/offline/test_graphics_v2.py b/tooling/probes/step20c-corec-v2/graphics/offline/test_graphics_v2.py new file mode 100644 index 0000000..25e9652 --- /dev/null +++ b/tooling/probes/step20c-corec-v2/graphics/offline/test_graphics_v2.py @@ -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()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) diff --git a/tooling/probes/step20c-corec-v2/graphics/parent-provenance.json b/tooling/probes/step20c-corec-v2/graphics/parent-provenance.json new file mode 100644 index 0000000..d4db5ef --- /dev/null +++ b/tooling/probes/step20c-corec-v2/graphics/parent-provenance.json @@ -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" + } +} diff --git a/tooling/probes/step20c-corec-v2/graphics/pixels.py b/tooling/probes/step20c-corec-v2/graphics/pixels.py new file mode 100644 index 0000000..e5660b6 --- /dev/null +++ b/tooling/probes/step20c-corec-v2/graphics/pixels.py @@ -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()) diff --git a/tooling/probes/step20c-corec-v2/graphics/plans.py b/tooling/probes/step20c-corec-v2/graphics/plans.py new file mode 100644 index 0000000..e6d81c3 --- /dev/null +++ b/tooling/probes/step20c-corec-v2/graphics/plans.py @@ -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) diff --git a/tooling/probes/step20c-corec-v2/graphics/predicates.py b/tooling/probes/step20c-corec-v2/graphics/predicates.py new file mode 100644 index 0000000..5f97602 --- /dev/null +++ b/tooling/probes/step20c-corec-v2/graphics/predicates.py @@ -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=2 + assert request.start() 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 + + + 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. + + + + + This protocol allows clients to emulate a physical pointer device. The + requests are mostly mirror opposites of those specified in wl_pointer. + + + + + + + + + + The pointer has moved by a relative amount to the previous request. + + Values are in the global compositor space. + + + + + + + + + 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. + + + + + + + + + + + A button was pressed or released. + + + + + + + + + Scroll and other axis requests. + + + + + + + + + Indicates the set of events that logically belong together. + + + + + + Source information for scroll and other axis. + + + + + + + Stop notification for scroll and other axes. + + + + + + + + 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. + + + + + + + + + + + + + + + This object allows clients to create individual virtual pointer objects. + + + + + Creates a new virtual pointer. The optional seat is a suggestion to the + compositor. + + + + + + + + + + + + + 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. + + + + + + + diff --git a/tooling/probes/step20c-corec-v2/graphics/protocols/xdg-shell.xml b/tooling/probes/step20c-corec-v2/graphics/protocols/xdg-shell.xml new file mode 100644 index 0000000..f8b78ee --- /dev/null +++ b/tooling/probes/step20c-corec-v2/graphics/protocols/xdg-shell.xml @@ -0,0 +1,1420 @@ + + + + + Copyright © 2008-2013 Kristian Høgsberg + Copyright © 2013 Rafael Antognolli + Copyright © 2013 Jasper St. Pierre + Copyright © 2010-2013 Intel Corporation + Copyright © 2015-2017 Samsung Electronics Co., Ltd + Copyright © 2015-2017 Red Hat Inc. + + 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. + + + + + The xdg_wm_base interface is exposed as a global object enabling clients + to turn their wl_surfaces into windows in a desktop environment. It + defines the basic functionality needed for clients and the compositor to + create windows that can be dragged, resized, maximized, etc, as well as + creating transient windows such as popup menus. + + + + + + + + + + + + + + + Destroy this xdg_wm_base object. + + Destroying a bound xdg_wm_base object while there are surfaces + still alive created by this xdg_wm_base object instance is illegal + and will result in a defunct_surfaces error. + + + + + + Create a positioner object. A positioner object is used to position + surfaces relative to some parent surface. See the interface description + and xdg_surface.get_popup for details. + + + + + + + This creates an xdg_surface for the given surface. An xdg_surface is + used as basis to define a role to a given surface, such as xdg_toplevel + or xdg_popup. It also manages functionality shared between xdg_surface + based surface roles. + + While xdg_surface itself is not a role, the corresponding surface may + only be assigned a role extending xdg_surface, such as xdg_toplevel or + xdg_popup. It is illegal to create an xdg_surface for a wl_surface which + already has anassigned role and this will result in a role error. + + See the documentation of xdg_surface for more details about what an + xdg_surface is and how it is used. + + + + + + + + A client must respond to a ping event with a pong request or + the client may be deemed unresponsive. See xdg_wm_base.ping + and xdg_wm_base.error.unresponsive. + + + + + + + The ping event asks the client if it's still alive. Pass the + serial specified in the event back to the compositor by sending + a "pong" request back with the specified serial. See xdg_wm_base.pong. + + Compositors can use this to determine if the client is still + alive. It's unspecified what will happen if the client doesn't + respond to the ping request, or in what timeframe. Clients should + try to respond in a reasonable amount of time. The “unresponsive” + error is provided for compositors that wish to disconnect unresponsive + clients. + + A compositor is free to ping in any way it wants, but a client must + always respond to any xdg_wm_base object it created. + + + + + + + + The xdg_positioner provides a collection of rules for the placement of a + child surface relative to a parent surface. Rules can be defined to ensure + the child surface remains within the visible area's borders, and to + specify how the child surface changes its position, such as sliding along + an axis, or flipping around a rectangle. These positioner-created rules are + constrained by the requirement that a child surface must intersect with or + be at least partially adjacent to its parent surface. + + See the various requests for details about possible rules. + + At the time of the request, the compositor makes a copy of the rules + specified by the xdg_positioner. Thus, after the request is complete the + xdg_positioner object can be destroyed or reused; further changes to the + object will have no effect on previous usages. + + For an xdg_positioner object to be considered complete, it must have a + non-zero size set by set_size, and a non-zero anchor rectangle set by + set_anchor_rect. Passing an incomplete xdg_positioner object when + positioning a surface raises an invalid_positioner error. + + + + + + + + + Notify the compositor that the xdg_positioner will no longer be used. + + + + + + Set the size of the surface that is to be positioned with the positioner + object. The size is in surface-local coordinates and corresponds to the + window geometry. See xdg_surface.set_window_geometry. + + If a zero or negative size is set the invalid_input error is raised. + + + + + + + + Specify the anchor rectangle within the parent surface that the child + surface will be placed relative to. The rectangle is relative to the + window geometry as defined by xdg_surface.set_window_geometry of the + parent surface. + + When the xdg_positioner object is used to position a child surface, the + anchor rectangle may not extend outside the window geometry of the + positioned child's parent surface. + + If a negative size is set the invalid_input error is raised. + + + + + + + + + + + + + + + + + + + + + + Defines the anchor point for the anchor rectangle. The specified anchor + is used to derive an anchor point that the child surface will be + positioned relative to. If a corner anchor is set (e.g. 'top_left' or + 'bottom_right'), the anchor point will be at the specified corner; + otherwise, the derived anchor point will be centered on the specified + edge, or in the center of the anchor rectangle if no edge is specified. + + + + + + + + + + + + + + + + + + + Defines in what direction a surface should be positioned, relative to + the anchor point of the parent surface. If a corner gravity is + specified (e.g. 'bottom_right' or 'top_left'), then the child surface + will be placed towards the specified gravity; otherwise, the child + surface will be centered over the anchor point on any axis that had no + gravity specified. If the gravity is not in the ‘gravity’ enum, an + invalid_input error is raised. + + + + + + + The constraint adjustment value define ways the compositor will adjust + the position of the surface, if the unadjusted position would result + in the surface being partly constrained. + + Whether a surface is considered 'constrained' is left to the compositor + to determine. For example, the surface may be partly outside the + compositor's defined 'work area', thus necessitating the child surface's + position be adjusted until it is entirely inside the work area. + + The adjustments can be combined, according to a defined precedence: 1) + Flip, 2) Slide, 3) Resize. + + + + Don't alter the surface position even if it is constrained on some + axis, for example partially outside the edge of an output. + + + + + Slide the surface along the x axis until it is no longer constrained. + + First try to slide towards the direction of the gravity on the x axis + until either the edge in the opposite direction of the gravity is + unconstrained or the edge in the direction of the gravity is + constrained. + + Then try to slide towards the opposite direction of the gravity on the + x axis until either the edge in the direction of the gravity is + unconstrained or the edge in the opposite direction of the gravity is + constrained. + + + + + Slide the surface along the y axis until it is no longer constrained. + + First try to slide towards the direction of the gravity on the y axis + until either the edge in the opposite direction of the gravity is + unconstrained or the edge in the direction of the gravity is + constrained. + + Then try to slide towards the opposite direction of the gravity on the + y axis until either the edge in the direction of the gravity is + unconstrained or the edge in the opposite direction of the gravity is + constrained. + + + + + Invert the anchor and gravity on the x axis if the surface is + constrained on the x axis. For example, if the left edge of the + surface is constrained, the gravity is 'left' and the anchor is + 'left', change the gravity to 'right' and the anchor to 'right'. + + If the adjusted position also ends up being constrained, the resulting + position of the flip_x adjustment will be the one before the + adjustment. + + + + + Invert the anchor and gravity on the y axis if the surface is + constrained on the y axis. For example, if the bottom edge of the + surface is constrained, the gravity is 'bottom' and the anchor is + 'bottom', change the gravity to 'top' and the anchor to 'top'. + + The adjusted position is calculated given the original anchor + rectangle and offset, but with the new flipped anchor and gravity + values. + + If the adjusted position also ends up being constrained, the resulting + position of the flip_y adjustment will be the one before the + adjustment. + + + + + Resize the surface horizontally so that it is completely + unconstrained. + + + + + Resize the surface vertically so that it is completely unconstrained. + + + + + + + Specify how the window should be positioned if the originally intended + position caused the surface to be constrained, meaning at least + partially outside positioning boundaries set by the compositor. The + adjustment is set by constructing a bitmask describing the adjustment to + be made when the surface is constrained on that axis. + + If no bit for one axis is set, the compositor will assume that the child + surface should not change its position on that axis when constrained. + + If more than one bit for one axis is set, the order of how adjustments + are applied is specified in the corresponding adjustment descriptions. + + The default adjustment is none. + + + + + + + Specify the surface position offset relative to the position of the + anchor on the anchor rectangle and the anchor on the surface. For + example if the anchor of the anchor rectangle is at (x, y), the surface + has the gravity bottom|right, and the offset is (ox, oy), the calculated + surface position will be (x + ox, y + oy). The offset position of the + surface is the one used for constraint testing. See + set_constraint_adjustment. + + An example use case is placing a popup menu on top of a user interface + element, while aligning the user interface element of the parent surface + with some user interface element placed somewhere in the popup surface. + + + + + + + + + + When set reactive, the surface is reconstrained if the conditions used + for constraining changed, e.g. the parent window moved. + + If the conditions changed and the popup was reconstrained, an + xdg_popup.configure event is sent with updated geometry, followed by an + xdg_surface.configure event. + + + + + + Set the parent window geometry the compositor should use when + positioning the popup. The compositor may use this information to + determine the future state the popup should be constrained using. If + this doesn't match the dimension of the parent the popup is eventually + positioned against, the behavior is undefined. + + The arguments are given in the surface-local coordinate space. + + + + + + + + Set the serial of an xdg_surface.configure event this positioner will be + used in response to. The compositor may use this information together + with set_parent_size to determine what future state the popup should be + constrained using. + + + + + + + + An interface that may be implemented by a wl_surface, for + implementations that provide a desktop-style user interface. + + It provides a base set of functionality required to construct user + interface elements requiring management by the compositor, such as + toplevel windows, menus, etc. The types of functionality are split into + xdg_surface roles. + + Creating an xdg_surface does not set the role for a wl_surface. In order + to map an xdg_surface, the client must create a role-specific object + using, e.g., get_toplevel, get_popup. The wl_surface for any given + xdg_surface can have at most one role, and may not be assigned any role + not based on xdg_surface. + + A role must be assigned before any other requests are made to the + xdg_surface object. + + The client must call wl_surface.commit on the corresponding wl_surface + for the xdg_surface state to take effect. + + Creating an xdg_surface from a wl_surface which has a buffer attached or + committed is a client error, and any attempts by a client to attach or + manipulate a buffer prior to the first xdg_surface.configure call must + also be treated as errors. + + After creating a role-specific object and setting it up (e.g. by sending + the title, app ID, size constraints, parent, etc), the client must + perform an initial commit without any buffer attached. The compositor + will reply with initial wl_surface state such as + wl_surface.preferred_buffer_scale followed by an xdg_surface.configure + event. The client must acknowledge it and is then allowed to attach a + buffer to map the surface. + + Mapping an xdg_surface-based role surface is defined as making it + possible for the surface to be shown by the compositor. Note that + a mapped surface is not guaranteed to be visible once it is mapped. + + For an xdg_surface to be mapped by the compositor, the following + conditions must be met: + (1) the client has assigned an xdg_surface-based role to the surface + (2) the client has set and committed the xdg_surface state and the + role-dependent state to the surface + (3) the client has committed a buffer to the surface + + A newly-unmapped surface is considered to have met condition (1) out + of the 3 required conditions for mapping a surface if its role surface + has not been destroyed, i.e. the client must perform the initial commit + again before attaching a buffer. + + + + + + + + + + + + + + Destroy the xdg_surface object. An xdg_surface must only be destroyed + after its role object has been destroyed, otherwise + a defunct_role_object error is raised. + + + + + + This creates an xdg_toplevel object for the given xdg_surface and gives + the associated wl_surface the xdg_toplevel role. + + See the documentation of xdg_toplevel for more details about what an + xdg_toplevel is and how it is used. + + + + + + + This creates an xdg_popup object for the given xdg_surface and gives + the associated wl_surface the xdg_popup role. + + If null is passed as a parent, a parent surface must be specified using + some other protocol, before committing the initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + + + The window geometry of a surface is its "visible bounds" from the + user's perspective. Client-side decorations often have invisible + portions like drop-shadows which should be ignored for the + purposes of aligning, placing and constraining windows. Note that + in some situations, compositors may clip rendering to the window + geometry, so the client should avoid putting functional elements + outside of it. + + The window geometry is double-buffered state, see wl_surface.commit. + + When maintaining a position, the compositor should treat the (x, y) + coordinate of the window geometry as the top left corner of the window. + A client changing the (x, y) window geometry coordinate should in + general not alter the position of the window. + + Once the window geometry of the surface is set, it is not possible to + unset it, and it will remain the same until set_window_geometry is + called again, even if a new subsurface or buffer is attached. + + If never set, the value is the full bounds of the surface, + including any subsurfaces. This updates dynamically on every + commit. This unset is meant for extremely simple clients. + + The arguments are given in the surface-local coordinate space of + the wl_surface associated with this xdg_surface, and may extend outside + of the wl_surface itself to mark parts of the subsurface tree as part of + the window geometry. + + When applied, the effective window geometry will be the set window + geometry clamped to the bounding rectangle of the combined + geometry of the surface of the xdg_surface and the associated + subsurfaces. + + The effective geometry will not be recalculated unless a new call to + set_window_geometry is done and the new pending surface state is + subsequently applied. + + The width and height of the effective window geometry must be + greater than zero. Setting an invalid size will raise an + invalid_size error. + + + + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + For instance, for toplevel surfaces the compositor might use this + information to move a surface to the top left only when the client has + drawn itself for the maximized or fullscreen state. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + Acking a configure event that was never sent raises an invalid_serial + error. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + Sending an ack_configure request consumes the serial number sent with + the request, as well as serial numbers sent by all configure events + sent on this xdg_surface prior to the configure event referenced by + the committed serial. + + It is an error to issue multiple ack_configure requests referencing a + serial from the same configure event, or to issue an ack_configure + request referencing a serial from a configure event issued before the + event identified by the last ack_configure request for the same + xdg_surface. Doing so will raise an invalid_serial error. + + + + + + + The configure event marks the end of a configure sequence. A configure + sequence is a set of one or more events configuring the state of the + xdg_surface, including the final xdg_surface.configure event. + + Where applicable, xdg_surface surface roles will during a configure + sequence extend this event as a latched state sent as events before the + xdg_surface.configure event. Such events should be considered to make up + a set of atomically applied configuration states, where the + xdg_surface.configure commits the accumulated state. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + If the client receives multiple configure events before it can respond + to one, it is free to discard all but the last event it received. + + + + + + + + + This interface defines an xdg_surface role which allows a surface to, + among other things, set window-like properties such as maximize, + fullscreen, and minimize, set application-specific metadata like title and + id, and well as trigger user interactive operations such as interactive + resize and move. + + An xdg_toplevel by default is responsible for providing the full intended + visual representation of the toplevel, which depending on the window + state, may mean things like a title bar, window controls and drop shadow. + + Unmapping an xdg_toplevel means that the surface cannot be shown + by the compositor until it is explicitly mapped again. + All active operations (e.g., move, resize) are canceled and all + attributes (e.g. title, state, stacking, ...) are discarded for + an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to + the state it had right after xdg_surface.get_toplevel. The client + can re-map the toplevel by performing a commit without any buffer + attached, waiting for a configure event and handling it as usual (see + xdg_surface description). + + Attaching a null buffer to a toplevel unmaps the surface. + + + + + This request destroys the role surface and unmaps the surface; + see "Unmapping" behavior in interface section for details. + + + + + + + + + + + + Set the "parent" of this surface. This surface should be stacked + above the parent surface and all other ancestor surfaces. + + Parent surfaces should be set on dialogs, toolboxes, or other + "auxiliary" surfaces, so that the parent is raised when the dialog + is raised. + + Setting a null parent for a child surface unsets its parent. Setting + a null parent for a surface which currently has no parent is a no-op. + + Only mapped surfaces can have child surfaces. Setting a parent which + is not mapped is equivalent to setting a null parent. If a surface + becomes unmapped, its children's parent is set to the parent of + the now-unmapped surface. If the now-unmapped surface has no parent, + its children's parent is unset. If the now-unmapped surface becomes + mapped again, its parent-child relationship is not restored. + + The parent toplevel must not be one of the child toplevel's + descendants, and the parent must be different from the child toplevel, + otherwise the invalid_parent protocol error is raised. + + + + + + + Set a short title for the surface. + + This string may be used to identify the surface in a task bar, + window list, or other user interface elements provided by the + compositor. + + The string must be encoded in UTF-8. + + + + + + + Set an application identifier for the surface. + + The app ID identifies the general class of applications to which + the surface belongs. The compositor can use this to group multiple + surfaces together, or to determine how to launch a new application. + + For D-Bus activatable applications, the app ID is used as the D-Bus + service name. + + The compositor shell will try to group application surfaces together + by their app ID. As a best practice, it is suggested to select app + ID's that match the basename of the application's .desktop file. + For example, "org.freedesktop.FooViewer" where the .desktop file is + "org.freedesktop.FooViewer.desktop". + + Like other properties, a set_app_id request can be sent after the + xdg_toplevel has been mapped to update the property. + + See the desktop-entry specification [0] for more details on + application identifiers and how they relate to well-known D-Bus + names and .desktop files. + + [0] https://standards.freedesktop.org/desktop-entry-spec/ + + + + + + + Clients implementing client-side decorations might want to show + a context menu when right-clicking on the decorations, giving the + user a menu that they can use to maximize or minimize the window. + + This request asks the compositor to pop up such a window menu at + the given position, relative to the local surface coordinates of + the parent surface. There are no guarantees as to what menu items + the window menu contains, or even if a window menu will be drawn + at all. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. + + + + + + + + + + Start an interactive, user-driven move of the surface. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. The passed + serial is used to determine the type of interactive move (touch, + pointer, etc). + + The server may ignore move requests depending on the state of + the surface (e.g. fullscreen or maximized), or if the passed serial + is no longer valid. + + If triggered, the surface will lose the focus of the device + (wl_pointer, wl_touch, etc) used for the move. It is up to the + compositor to visually indicate that the move is taking place, such as + updating a pointer cursor, during the move. There is no guarantee + that the device focus will return when the move is completed. + + + + + + + + These values are used to indicate which edge of a surface + is being dragged in a resize operation. + + + + + + + + + + + + + + + Start a user-driven, interactive resize of the surface. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. The passed + serial is used to determine the type of interactive resize (touch, + pointer, etc). + + The server may ignore resize requests depending on the state of + the surface (e.g. fullscreen or maximized). + + If triggered, the client will receive configure events with the + "resize" state enum value and the expected sizes. See the "resize" + enum value for more details about what is required. The client + must also acknowledge configure events using "ack_configure". After + the resize is completed, the client will receive another "configure" + event without the resize state. + + If triggered, the surface also will lose the focus of the device + (wl_pointer, wl_touch, etc) used for the resize. It is up to the + compositor to visually indicate that the resize is taking place, + such as updating a pointer cursor, during the resize. There is no + guarantee that the device focus will return when the resize is + completed. + + The edges parameter specifies how the surface should be resized, and + is one of the values of the resize_edge enum. Values not matching + a variant of the enum will cause the invalid_resize_edge protocol error. + The compositor may use this information to update the surface position + for example when dragging the top left corner. The compositor may also + use this information to adapt its behavior, e.g. choose an appropriate + cursor image. + + + + + + + + + The different state values used on the surface. This is designed for + state values like maximized, fullscreen. It is paired with the + configure event to ensure that both the client and the compositor + setting the state can be synchronized. + + States set in this way are double-buffered, see wl_surface.commit. + + + + The surface is maximized. The window geometry specified in the configure + event must be obeyed by the client, or the xdg_wm_base.invalid_surface_state + error is raised. + + The client should draw without shadow or other + decoration outside of the window geometry. + + + + + The surface is fullscreen. The window geometry specified in the + configure event is a maximum; the client cannot resize beyond it. For + a surface to cover the whole fullscreened area, the geometry + dimensions must be obeyed by the client. For more details, see + xdg_toplevel.set_fullscreen. + + + + + The surface is being resized. The window geometry specified in the + configure event is a maximum; the client cannot resize beyond it. + Clients that have aspect ratio or cell sizing configuration can use + a smaller size, however. + + + + + Client window decorations should be painted as if the window is + active. Do not assume this means that the window actually has + keyboard or pointer focus. + + + + + The window is currently in a tiled layout and the left edge is + considered to be adjacent to another part of the tiling grid. + + The client should draw without shadow or other decoration outside of + the window geometry on the left edge. + + + + + The window is currently in a tiled layout and the right edge is + considered to be adjacent to another part of the tiling grid. + + The client should draw without shadow or other decoration outside of + the window geometry on the right edge. + + + + + The window is currently in a tiled layout and the top edge is + considered to be adjacent to another part of the tiling grid. + + The client should draw without shadow or other decoration outside of + the window geometry on the top edge. + + + + + The window is currently in a tiled layout and the bottom edge is + considered to be adjacent to another part of the tiling grid. + + The client should draw without shadow or other decoration outside of + the window geometry on the bottom edge. + + + + + The surface is currently not ordinarily being repainted; for + example because its content is occluded by another window, or its + outputs are switched off due to screen locking. + + + + + The left edge of the window is currently constrained, meaning it + shouldn't attempt to resize from that edge. It can for example mean + it's tiled next to a monitor edge on the constrained side of the + window. + + + + + The right edge of the window is currently constrained, meaning it + shouldn't attempt to resize from that edge. It can for example mean + it's tiled next to a monitor edge on the constrained side of the + window. + + + + + The top edge of the window is currently constrained, meaning it + shouldn't attempt to resize from that edge. It can for example mean + it's tiled next to a monitor edge on the constrained side of the + window. + + + + + The bottom edge of the window is currently constrained, meaning it + shouldn't attempt to resize from that edge. It can for example mean + it's tiled next to a monitor edge on the constrained side of the + window. + + + + + + + Set a maximum size for the window. + + The client can specify a maximum size so that the compositor does + not try to configure the window beyond this size. + + The width and height arguments are in window geometry coordinates. + See xdg_surface.set_window_geometry. + + Values set in this way are double-buffered, see wl_surface.commit. + + The compositor can use this information to allow or disallow + different states like maximize or fullscreen and draw accurate + animations. + + Similarly, a tiling window manager may use this information to + place and resize client windows in a more effective way. + + The client should not rely on the compositor to obey the maximum + size. The compositor may decide to ignore the values set by the + client and request a larger size. + + If never set, or a value of zero in the request, means that the + client has no expected maximum size in the given dimension. + As a result, a client wishing to reset the maximum size + to an unspecified state can use zero for width and height in the + request. + + Requesting a maximum size to be smaller than the minimum size of + a surface is illegal and will result in an invalid_size error. + + The width and height must be greater than or equal to zero. Using + strictly negative values for width or height will result in an + invalid_size error. + + + + + + + + Set a minimum size for the window. + + The client can specify a minimum size so that the compositor does + not try to configure the window below this size. + + The width and height arguments are in window geometry coordinates. + See xdg_surface.set_window_geometry. + + Values set in this way are double-buffered, see wl_surface.commit. + + The compositor can use this information to allow or disallow + different states like maximize or fullscreen and draw accurate + animations. + + Similarly, a tiling window manager may use this information to + place and resize client windows in a more effective way. + + The client should not rely on the compositor to obey the minimum + size. The compositor may decide to ignore the values set by the + client and request a smaller size. + + If never set, or a value of zero in the request, means that the + client has no expected minimum size in the given dimension. + As a result, a client wishing to reset the minimum size + to an unspecified state can use zero for width and height in the + request. + + Requesting a minimum size to be larger than the maximum size of + a surface is illegal and will result in an invalid_size error. + + The width and height must be greater than or equal to zero. Using + strictly negative values for width and height will result in an + invalid_size error. + + + + + + + + Maximize the surface. + + After requesting that the surface should be maximized, the compositor + will respond by emitting a configure event. Whether this configure + actually sets the window maximized is subject to compositor policies. + The client must then update its content, drawing in the configured + state. The client must also acknowledge the configure when committing + the new content (see ack_configure). + + It is up to the compositor to decide how and where to maximize the + surface, for example which output and what region of the screen should + be used. + + If the surface was already maximized, the compositor will still emit + a configure event with the "maximized" state. + + If the surface is in a fullscreen state, this request has no direct + effect. It may alter the state the surface is returned to when + unmaximized unless overridden by the compositor. + + + + + + Unmaximize the surface. + + After requesting that the surface should be unmaximized, the compositor + will respond by emitting a configure event. Whether this actually + un-maximizes the window is subject to compositor policies. + If available and applicable, the compositor will include the window + geometry dimensions the window had prior to being maximized in the + configure event. The client must then update its content, drawing it in + the configured state. The client must also acknowledge the configure + when committing the new content (see ack_configure). + + It is up to the compositor to position the surface after it was + unmaximized; usually the position the surface had before maximizing, if + applicable. + + If the surface was already not maximized, the compositor will still + emit a configure event without the "maximized" state. + + If the surface is in a fullscreen state, this request has no direct + effect. It may alter the state the surface is returned to when + unmaximized unless overridden by the compositor. + + + + + + Make the surface fullscreen. + + After requesting that the surface should be fullscreened, the + compositor will respond by emitting a configure event. Whether the + client is actually put into a fullscreen state is subject to compositor + policies. The client must also acknowledge the configure when + committing the new content (see ack_configure). + + The output passed by the request indicates the client's preference as + to which display it should be set fullscreen on. If this value is NULL, + it's up to the compositor to choose which display will be used to map + this surface. + + If the surface doesn't cover the whole output, the compositor will + position the surface in the center of the output and compensate with + border fill covering the rest of the output. The content of the + border fill is undefined, but should be assumed to be in some way that + attempts to blend into the surrounding area (e.g. solid black). + + If the fullscreened surface is not opaque, the compositor must make + sure that other screen content not part of the same surface tree (made + up of subsurfaces, popups or similarly coupled surfaces) are not + visible below the fullscreened surface. + + + + + + + Make the surface no longer fullscreen. + + After requesting that the surface should be unfullscreened, the + compositor will respond by emitting a configure event. + Whether this actually removes the fullscreen state of the client is + subject to compositor policies. + + Making a surface unfullscreen sets states for the surface based on the following: + * the state(s) it may have had before becoming fullscreen + * any state(s) decided by the compositor + * any state(s) requested by the client while the surface was fullscreen + + The compositor may include the previous window geometry dimensions in + the configure event, if applicable. + + The client must also acknowledge the configure when committing the new + content (see ack_configure). + + + + + + Request that the compositor minimize your surface. There is no + way to know if the surface is currently minimized, nor is there + any way to unset minimization on this surface. + + If you are looking to throttle redrawing when minimized, please + instead use the wl_surface.frame event for this, as this will + also work with live previews on windows in Alt-Tab, Expose or + similar compositor features. + + + + + + This configure event asks the client to resize its toplevel surface or + to change its state. The configured state should not be applied + immediately. See xdg_surface.configure for details. + + The width and height arguments specify a hint to the window + about how its surface should be resized in window geometry + coordinates. See set_window_geometry. + + If the width or height arguments are zero, it means the client + should decide its own window dimension. This may happen when the + compositor needs to configure the state of the surface but doesn't + have any information about any previous or expected dimension. + + The states listed in the event specify how the width/height + arguments should be interpreted, and possibly how it should be + drawn. + + The states are sent as an array of 32-bit unsigned integers in + native endianness. State values are defined in the state enum. + + Clients must send an ack_configure in response to this event. See + xdg_surface.configure and xdg_surface.ack_configure for details. + + + + + + + + + The close event is sent by the compositor when the user + wants the surface to be closed. This should be equivalent to + the user clicking the close button in client-side decorations, + if your application has any. + + This is only a request that the user intends to close the + window. The client may choose to ignore this request, or show + a dialog to ask the user to save their data, etc. + + + + + + + + The configure_bounds event may be sent prior to a xdg_toplevel.configure + event to communicate the bounds a window geometry size is recommended + to constrain to. + + The passed width and height are in surface coordinate space. If width + and height are 0, it means bounds is unknown and equivalent to as if no + configure_bounds event was ever sent for this surface. + + The bounds can for example correspond to the size of a monitor excluding + any panels or other shell components, so that a surface isn't created in + a way that it cannot fit. + + The bounds may change at any point, and in such a case, a new + xdg_toplevel.configure_bounds will be sent, followed by + xdg_toplevel.configure and xdg_surface.configure. + + + + + + + + + + + + + + + + + This event advertises the capabilities supported by the compositor. If + a capability isn't supported, clients should hide or disable the UI + elements that expose this functionality. For instance, if the + compositor doesn't advertise support for minimized toplevels, a button + triggering the set_minimized request should not be displayed. + + The compositor will ignore requests it doesn't support. For instance, + a compositor which doesn't advertise support for minimized will ignore + set_minimized requests. + + Compositors must send this event once before the first + xdg_surface.configure event. When the capabilities change, compositors + must send this event again and then send an xdg_surface.configure + event. + + The configured state should not be applied immediately. See + xdg_surface.configure for details. + + The capabilities are sent as an array of 32-bit unsigned integers in + native endianness. Capability values are defined in the wm_capabilities enum. + + + + + + + + A popup surface is a short-lived, temporary surface. It can be used to + implement for example menus, popovers, tooltips and other similar user + interface concepts. + + A popup can be made to take an explicit grab. See xdg_popup.grab for + details. + + When the popup is dismissed, a popup_done event will be sent out, and at + the same time the surface will be unmapped. See the xdg_popup.popup_done + event for details. + + Explicitly destroying the xdg_popup object will also dismiss the popup and + unmap the surface. Clients that want to dismiss the popup when another + surface of their own is clicked should dismiss the popup using the destroy + request. + + A newly created xdg_popup will be stacked on top of all previously created + xdg_popup surfaces associated with the same xdg_toplevel. + + The parent of an xdg_popup must be mapped (see the xdg_surface + description) before the xdg_popup itself. + + The client must call wl_surface.commit on the corresponding wl_surface + for the xdg_popup state to take effect. + + + + + + + + + This destroys the popup. Explicitly destroying the xdg_popup + object will also dismiss the popup, and unmap the surface. + + If this xdg_popup is not the "topmost" popup, the + xdg_wm_base.not_the_topmost_popup protocol error will be sent. + + + + + + This request makes the created popup take an explicit grab. An explicit + grab will be dismissed when the user dismisses the popup, or when the + client destroys the xdg_popup. This can be done by the user clicking + outside the surface, using the keyboard, or even locking the screen + through closing the lid or a timeout. + + If the compositor denies the grab, the popup will be immediately + dismissed. + + This request must be used in response to some sort of user action like a + button press, key press, or touch down event. The serial number of the + event should be passed as 'serial'. + + The parent of a grabbing popup must either be an xdg_toplevel surface or + another xdg_popup with an explicit grab. If the parent is another + xdg_popup it means that the popups are nested, with this popup now being + the topmost popup. + + Nested popups must be destroyed in the reverse order they were created + in, e.g. the only popup you are allowed to destroy at all times is the + topmost one. + + When compositors choose to dismiss a popup, they may dismiss every + nested grabbing popup as well. When a compositor dismisses popups, it + will follow the same dismissing order as required from the client. + + If the topmost grabbing popup is destroyed, the grab will be returned to + the parent of the popup, if that parent previously had an explicit grab. + + If the parent is a grabbing popup which has already been dismissed, this + popup will be immediately dismissed. If the parent is a popup that did + not take an explicit grab, an error will be raised. + + During a popup grab, the client owning the grab will receive pointer + and touch events for all their surfaces as normal (similar to an + "owner-events" grab in X11 parlance), while the top most grabbing popup + will always have keyboard focus. + + + + + + + + This event asks the popup surface to configure itself given the + configuration. The configured state should not be applied immediately. + See xdg_surface.configure for details. + + The x and y arguments represent the position the popup was placed at + given the xdg_positioner rule, relative to the upper left corner of the + window geometry of the parent surface. + + For version 2 or older, the configure event for an xdg_popup is only + ever sent once for the initial configuration. Starting with version 3, + it may be sent again if the popup is setup with an xdg_positioner with + set_reactive requested, or in response to xdg_popup.reposition requests. + + + + + + + + + + The popup_done event is sent out when a popup is dismissed by the + compositor. The client should destroy the xdg_popup object at this + point. + + + + + + + + Reposition an already-mapped popup. The popup will be placed given the + details in the passed xdg_positioner object, and a + xdg_popup.repositioned followed by xdg_popup.configure and + xdg_surface.configure will be emitted in response. Any parameters set + by the previous positioner will be discarded. + + The passed token will be sent in the corresponding + xdg_popup.repositioned event. The new popup position will not take + effect until the corresponding configure event is acknowledged by the + client. See xdg_popup.repositioned for details. The token itself is + opaque, and has no other special meaning. + + If multiple reposition requests are sent, the compositor may skip all + but the last one. + + If the popup is repositioned in response to a configure event for its + parent, the client should send an xdg_positioner.set_parent_configure + and possibly an xdg_positioner.set_parent_size request to allow the + compositor to properly constrain the popup. + + If the popup is repositioned together with a parent that is being + resized, but not in response to a configure event, the client should + send an xdg_positioner.set_parent_size request. + + + + + + + + The repositioned event is sent as part of a popup configuration + sequence, together with xdg_popup.configure and lastly + xdg_surface.configure to notify the completion of a reposition request. + + The repositioned event is to notify about the completion of a + xdg_popup.reposition request. The token argument is the token passed + in the xdg_popup.reposition request. + + Immediately after this event is emitted, xdg_popup.configure and + xdg_surface.configure will be sent with the updated size and position, + as well as a new configure serial. + + The client should optionally update the content of the popup, but must + acknowledge the new popup configuration for the new position to take + effect. See xdg_surface.ack_configure for details. + + + + + + diff --git a/tooling/probes/step20c-corec-v2/graphics/run.py b/tooling/probes/step20c-corec-v2/graphics/run.py new file mode 100644 index 0000000..690bd6d --- /dev/null +++ b/tooling/probes/step20c-corec-v2/graphics/run.py @@ -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()) diff --git a/tooling/probes/step20c-corec-v2/graphics/saved.py b/tooling/probes/step20c-corec-v2/graphics/saved.py new file mode 100644 index 0000000..90161ad --- /dev/null +++ b/tooling/probes/step20c-corec-v2/graphics/saved.py @@ -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>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' diff --git a/tooling/probes/step20c-corec-v2/graphics/saved_validators.py b/tooling/probes/step20c-corec-v2/graphics/saved_validators.py new file mode 100644 index 0000000..38f4bcb --- /dev/null +++ b/tooling/probes/step20c-corec-v2/graphics/saved_validators.py @@ -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_nsactivation_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']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 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 diff --git a/tooling/probes/step20c-corec-v2/reviews/build-review.json b/tooling/probes/step20c-corec-v2/reviews/build-review.json new file mode 100644 index 0000000..1b7b4cd --- /dev/null +++ b/tooling/probes/step20c-corec-v2/reviews/build-review.json @@ -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." +} diff --git a/tooling/probes/step20c-corec-v2/reviews/graphics-delta-review.json b/tooling/probes/step20c-corec-v2/reviews/graphics-delta-review.json new file mode 100644 index 0000000..5445269 --- /dev/null +++ b/tooling/probes/step20c-corec-v2/reviews/graphics-delta-review.json @@ -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" +} diff --git a/tooling/probes/step20c-corec-v2/reviews/graphics-initial-review.json b/tooling/probes/step20c-corec-v2/reviews/graphics-initial-review.json new file mode 100644 index 0000000..80aa617 --- /dev/null +++ b/tooling/probes/step20c-corec-v2/reviews/graphics-initial-review.json @@ -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" +} diff --git a/tooling/probes/step20c-corec-v2/reviews/graphics-repair.json b/tooling/probes/step20c-corec-v2/reviews/graphics-repair.json new file mode 100644 index 0000000..4a5ce04 --- /dev/null +++ b/tooling/probes/step20c-corec-v2/reviews/graphics-repair.json @@ -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" +}