test(mosaic): preserve focused CORE-C V2 OOM and GLES gates

This commit is contained in:
fy59 2026-10-15 11:41:20 +02:00
parent d46f854b30
commit c55373eab3
49 changed files with 9163 additions and 0 deletions

View file

@ -0,0 +1,59 @@
# CORE-C V2 native closure follow-up — experimental source-only kit
This is the new, separately budgeted
`SWAYFX_MOSAIC_STEP20C_COREC_V2_FULLSCREEN_OOM_GLES_NATIVE_CLOSURE_BATCH_V1`
ticket. Its parent is project commit
`d46f854b3026e911659528fe94792aa13837a1d6`. The
[parent kit](../step20c-corec-v2/README.md) and its closed runtime receipts
remain unchanged. This directory does not add a production dependency.
The follow-up derives prospective fullscreen, migration/refocus and root/nested
OOM fixtures in `graphics/`, and a distinct intrusive GLES diagnostic in `gpu/`.
Each implementation has its own offline tests. Independent acceptance of the
final source bytes and verified publication to both project remotes are
required before a runtime reservation. A source test or a successful build is
not native runtime evidence. No campaign is admitted by this README.
The G2 Wayland client sets `RLIMIT_CORE` to zero, sets
`PR_SET_DUMPABLE=0`, and verifies `PR_GET_DUMPABLE=0` before any Wayland
operation. This protects the client itself; the compositor's `exec` does not
preserve a parent's `prctl` dumpable setting. The parent also applies
`RLIMIT_CORE=0` to owned children, while compositor dumpability after `exec`
remains an explicit limitation.
Generated sources, clients, binaries, builds, receipts, logs, raw readbacks and
captures belong in a new mode-0700 LAB below `/var/tmp`. Redirect `TMPDIR`,
`TMP`, `TEMP`, `XDG_CACHE_HOME`, `XDG_DATA_HOME`, `PYTHONPYCACHEPREFIX` and
`CCACHE_DIR` there for every build/test. Runtime sockets must be private,
short, checked for quota, and explicitly selected. Never use an inherited
desktop socket or display. Keep heavy artifacts outside HOME and Git.
The eight named reservations have fixed, nonfungible ceilings:
| Run | Output captures maximum | Dependency |
| --- | ---: | --- |
| FS-P | 12 | Reviewed comparable pristine fullscreen fixture |
| FS-C | 16 | Same reviewed client and actual FS-P comparison |
| DY-C | 16 | Reviewed native-source refocus oracle |
| OM-ROOT | 10 | Reviewed root-only closure and availability oracle |
| OM-NESTED | 10 | Safe root result, or explicit independent dependency review |
| GPU-G3 | 12 | Reviewed capability/readback source and distinct built ELF |
| OM-EXTRA | 12 | New distinct discriminant and additional review |
| GPU-CTRL | 12 | New attribution question and additional review |
Maximum eight runs, 150 seconds each, 18 captures per run, and 110 output
captures globally; the named ceilings sum to 100. A reservation is consumed
even on failure. No retry, reset, refund, historical token or scenario renaming
is permitted. Optional entries are not admitted by default. The previous
ticket's unused reservations are closed and cannot be transferred.
CORE and OOM production mechanisms remain unchanged. Runtime Row 0 borrows
Sway's real tiling list. Pending IPC geometry, applied scene/input evidence,
native commits, presentation and buffer lifetime remain distinct. Stable
screenshots cannot establish atomic generation, complete damage or GPU
completion. OOM production policy and offscreen-popup UX remain undecided.
STEP20A DONE; STEP20B EXPERIMENTAL; STEP20C NEXT; STEP20D–G PLANNED.
PRODUCTION_MULTI_OUTPUT=NOT_YET_SUPPORTED; PRODUCTION_AUTHORIZED=FALSE.
No installation, live session operation, production source/patch, public ABI,
package recipe or UX change is authorized by this kit.

View file

@ -0,0 +1,132 @@
# GPU-G3 — private GLES admission follow-up
EXPERIMENTAL_SOURCE_ONLY. This kit derives a new diagnostic from the published
[G2 sources](../../step20c-corec-v2/gpu/README.md). It carries no G2 binary or
runtime qualification. B4 is INCONCLUSIVE until an independent reviewer accepts
the exact final bytes and receipts. Compositor build, staging and native GPU
run are NOT_RUN. STEP20C remains NEXT; production multi-output is
NOT_YET_SUPPORTED and PRODUCTION_AUTHORIZED is FALSE.
## Normative basis and bounded contract
[EGL 1.5](https://registry.khronos.org/EGL/specs/eglspec.1.5.pdf),
§3.7.1.1, printed pp52–53 (PDF pages58–59), permits a later backwards compatible
context. Its footnote18 identifies CLIENT_VERSION as the major-version alias.
However §3.7.4, printed p63 (PDF page69), describes the context query as the
supported client version. Thus EGL2/GL3.2 remains an observed divergence; this
kit does **not** relabel the queried EGL value as merely a creation request or
assert universal conformance. The independent reviewer must assess the limited
diagnostic admission separately from that divergence.
[OpenGL ES 3.2, May 5 2022](https://registry.khronos.org/OpenGL/specs/es/3.2/es_spec_3.2.pdf),
§20.2, printed pp441–442 (PDF pages458–459), specifies the VERSION string and
matching MAJOR_VERSION/MINOR_VERSION queries. The code first validates current
EGL context/display and ES API, then bounded GL_VERSION attesting ES3.0–3.2,
then these ES3 queries and GLSL string without errors. ES2, ES4, unqualified
ES3 minors, forged framing and contradictory integer results are refused.
Installed headers define EGL_CONTEXT_CLIENT_VERSION and MAJOR_VERSION as
0x3098; GLES3 MAJOR_VERSION/MINOR_VERSION are 0x821b/0x821c. Their hashes are
recorded in the offline receipt; headers alone prove no native capability.
`g2_gl.h` keeps the exact `g2_read` symbol used by the unchanged G2 hooks.
`g2_native.h` adds observable lifecycle flags and G3 metadata. Queries consume
the GL error queue in separate pre/version/capability/state/attachment/read/
restore windows. Error consumption cannot be restored. State is null when
unobserved; attempted restoration requires a clean error window and exact
state/context/display/API equality. Bindings restored are READ/DRAW FBO, RBO,
PBO and PACK alignment/row/skip. READ_BUFFER is observed on the target and is
never modified; returning the original FBO restores its original selection.
Other queried state is unchanged. Only complete, single-sample native RBO
color attachments with supported byte channels/dimensions are read. Texture
attachment dimensions remain NOT_PROVEN and are refused. Black RGBA with
alpha zero is valid. Functional blur shaders, sampler and rendering policy
are unchanged. GLSL string is evidence about the implementation; the linked
program's mediump/ESSL100 requirements remain tied to frozen shader sources,
actual program/type/location, precision capability and uniform bits.
DMA-BUF FDs are borrowed and fstat identities are recorded without closing,
seeking or decoding. AMD modifier020000001046bb04 accepts image stride3584
offset0 and auxiliary stride1024 offset2293760. Only the image plane requires
width×4; auxiliary allocation bounds/decoding remain NOT_PROVEN. Unknown
modifiers are refused. `halfpixel` records native bits and capability; there
is no fitted tolerance or mandatory CPU-float32 equality for mediump.
## Source reconstruction and offline commands
Start with the [CORE assembler](../../step20c-corec-v2/README.md) from a fresh
Git extraction and its verified 85-file SceneFX tree. Use a NEW LAB created by
mkdtemp under `/var/tmp/swayfx-mosaic-step20c-native-closure-*`, UID owner,
mode0700. All generated files and every temp/cache directory stay there.
Run commands with an allowlisted environment, for example:
```sh
env -i PATH=/usr/bin:/bin LANG=C.UTF-8 \
TMPDIR="$LAB/scratch" TMP="$LAB/scratch" TEMP="$LAB/scratch" \
XDG_CACHE_HOME="$LAB/cache" XDG_DATA_HOME="$LAB/data" \
PYTHONPYCACHEPREFIX="$LAB/pycache" CCACHE_DIR="$LAB/ccache" \
python "$KIT/reproduce.py" --lab "$LAB" --core "$CORE/scene" \
--destination "$LAB/replay-g3-scene"
```
Create the listed environment directories first, mode0700. `$KIT` is this
directory, `$CORE` the verified assembler destination. `reproduce.py` verifies
every CORE byte and mode against the versioned provenance. It applies the
relative parent kit's `gpu/g2-native.patch` with fixed SHA256
77d7445ceaf643fd5cc708d31c3f2c77feaeb80f8130c419c55e5514ecc8a75d, then applies the G3 delta and verifies both diagnostic headers against
the supplied full new headers. The hook source SHA remains
c2886ef259fc91431dfe97841387082420ab80a0fe2f07e7dcdb71fc95731181.
`g3-admission.patch` is the review delta between the parent and new headers;
full new headers are the assembly authority and are explicitly frozen.
With the same allowlisted environment, run:
```sh
python "$KIT/test-targeted.py" --lab "$LAB" --scene "$LAB/replay-g3-scene"
python "$KIT/freeze.py" --lab "$LAB" --output "$LAB/g3-source-freeze-v2.json"
```
This performs C17 FakeGL execution of the exact header, controlled wrong
native variants, exact arm-consumer C extraction, actual native helper C,
Python parser/scenario negative tests, source syntax checking, Wayland header
generation and client syntax compilation. The client is not executed. It
does not require compile_commands from any historical LAB. C processes set
RLIMIT_CORE=0 and PR_SET_DUMPABLE=0. Generated tests, logs and receipts remain
under `LAB/g3-offline`; the relative source manifest is
`LAB/g3-source-freeze.json`. The freezer never grants its own review.
## Prospective native wiring — requires parent admission
Define `G3_ROOT` at compile time as the reviewed new LAB's exact
`gpu-work/acquisition` path. There is deliberately no default absolute LAB.
That definition is part of the new binary's reviewed identity. The C hook
requires MOSAIC_G3_DIR to equal both the macro and realpath, mode0700. Python
ROOT comes from MOSAIC_G3_DIR supplied **before import** and verifies the new
LAB prefix. No runtime directory is created by this kit.
The parent must bind `scenario.run(session,binding)` to a **GPU-G3** Session
whose sockets, binary/maps, watchdog, capture limits and durable ledger have
been independently reviewed. `binding` requires `G3_ledger_reserved=True`,
gate_file, binary, client, client_sha256, nonce and source_manifest_sha256.
The Session binary category is `gpu` and its actual executable must be the
new reviewed G3 binary. Environment variables MOSAIC_G3_DIR/NONCE belong in
the Session's explicit compositor allowlist. The caller owns finally/cleanup.
The parent must provide a gate mode0600 containing source_review=ACCEPTED,
gl_observability=PASS, runtime_authorized=true, diagnostic=GPU-G3,
acquisition_root, and exact binary_sha256. This kit never produces that gate.
Epochs A/B/A-return are single use, strictly ordered, capped at three. All
ten physical readbacks per epoch are full800×600 RGBA (the prospective ROI is
the complete field, without a post-hoc mask), total57,600,000 bytes, cap256MiB.
There are two H2 baselines then three H1/H2 pairs: eight output captures.
Every arm/raw/metadata publication is exclusive, nofollow and bounded.
Rejected/incomplete A forbids B and return. Input commands are G3-A/G3-B/
G3-A-return; client content is the unchanged alternating 8-pixel RGB pattern.
Whole H1/H2 comparisons report zero-tolerance differences and row counts.
Synchronous reads may perturb timing; GPU completion, presented-buffer
identity, object lifetime, complete damage, atomic generation and whole-field
causality remain NOT_PROVEN. A future build/run requires B4 and C gates;
offline success does not consume or authorize a GPU-G3 run. The freezer's
default output name is `g3-source-freeze.json`; it creates a new mode-0600 file
exclusively, refuses any existing destination, and syncs the file and LAB
directory. Use a new explicit direct-child name for every later freeze; a prior
freeze is immutable evidence.

View file

@ -0,0 +1,358 @@
#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L
#include "layer-shell-client.h"
#include "xdg-shell-client.h"
#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <wayland-client.h>
#include <sys/resource.h>
#include <sys/prctl.h>
/* WHY: one persistent background supplies real A/B commits below blur_tree.
* CONTRACT: fixed opaque background, premultiplied ARGB glass and static H2.
* INVARIANT: no animation, synthetic configure, popup or held transaction. */
static struct wl_display *display;
static struct wl_compositor *compositor;
static struct wl_shm *shm;
static struct xdg_wm_base *wm;
static struct zwlr_layer_shell_v1 *shell;
static struct wl_output *h1;
static struct wl_surface *surface;
static struct xdg_surface *xdg;
static struct xdg_toplevel *top;
static struct zwlr_layer_surface_v1 *layer;
static const char *mode;
static int width = 420, height = 320;
static unsigned commits, live_buffers, released_buffers;
static char pattern = 'A';
/* G3 new explicit source command protocol; first A bytes retained verbatim. */
static unsigned char *source_a;
static size_t source_a_size;
static const char *source_command;
static uint32_t source_crc(const unsigned char *p,size_t n) {
uint32_t crc=~0u;
for(size_t i=0;i<n;i++) {crc^=p[i];for(int b=0;b<8;b++)crc=(crc>>1)^((0u-(crc&1u))&0xedb88320u);}
return ~crc;
}
static volatile sig_atomic_t running = 1;
struct pixels {
struct wl_buffer *buffer;
void *map;
size_t size;
struct pixels *next;
};
static struct pixels *buffers;
static long long stamp(void) {
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return (long long)t.tv_sec * 1000000000LL + t.tv_nsec;
}
static void free_pixels(struct pixels *p) {
struct pixels **it = &buffers;
while (*it && *it != p)
it = &(*it)->next;
if (*it)
*it = p->next;
wl_buffer_destroy(p->buffer);
munmap(p->map, p->size);
free(p);
--live_buffers;
}
static void release(void *data, struct wl_buffer *b) {
++released_buffers;
fprintf(stderr, "RELEASE ns=%lld buffer=%u\n", stamp(),
wl_proxy_get_id((struct wl_proxy *)b));
free_pixels(data);
}
static const struct wl_buffer_listener buffer_listener = {.release = release};
static void draw(void) {
/* CONTRACT: exactly one runner command per reserved A/B image.
* INVARIANT: configures cannot draw, and the sequence is A/B/A only. */
if (!strcmp(mode, "background") &&
(width != 800 || height != 600 || commits >= 3 ||
pattern != (commits == 1 ? 'B' : 'A')))
exit(2);
if (width <= 0 || height <= 0 || width > 4096 || height > 4096 ||
live_buffers >= 8)
exit(2);
size_t size = (size_t)width * (size_t)height * 4;
/* CONTRACT: each mapping stays immutable until native wl_buffer.release.
* Bounds keep pool/stride integers valid, with at most eight outstanding. */
char name[512];
const char *runtime = getenv("TMPDIR");
if (!runtime || strcmp(runtime,"/var/tmp/swayfx-mosaic-corec-v2-i516p6po/scratch") || snprintf(name, sizeof(name), "%s/g2-shm-XXXXXX", runtime) >=
(int)sizeof(name))
exit(2);
int fd = mkstemp(name);
if (fd < 0)
exit(2);
unlink(name);
if (ftruncate(fd, (off_t)size) < 0)
exit(2);
struct pixels *p = calloc(1, sizeof(*p));
if (!p)
exit(2);
p->map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
p->size = size;
if (p->map == MAP_FAILED)
exit(2);
uint32_t *pixels = p->map;
for (int y = 0; y < height; ++y)
for (int x = 0; x < width; ++x) {
uint32_t color = 0x00303038;
if (!strcmp(mode, "background"))
color = pattern == 'A' ? (x / 8 % 2 ? 0x000f192d : 0x00f5f5f5)
: (x / 8 % 2 ? 0x00b02040 : 0x0020c0e0);
else if (!strcmp(mode, "glass"))
color = 0x88442010;
else if (!strcmp(mode, "filler"))
color = 0x00000000;
pixels[(size_t)y * (size_t)width + (size_t)x] = color;
}
if (!strcmp(mode,"background")) {
if (!commits) {
source_a=malloc(size);if(!source_a)exit(2);
memcpy(source_a,p->map,size);source_a_size=size;
} else if (commits==2) {
if(!source_a || source_a_size!=size)exit(2);
/* A-return is the preserved original payload, not a fitted redraw. */
memcpy(p->map,source_a,size);
} else if(source_a_size==size && !memcmp(source_a,p->map,size)) exit(2);
fprintf(stderr,"G3_SOURCE_BYTES command=%s bytes=%zu crc32=%08x exact_original=%s\n",
source_command,size,source_crc(p->map,size),commits==2?"true":"not-return");
}
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, (int)size);
p->buffer = wl_shm_pool_create_buffer(
pool, 0, width, height, width * 4,
(!strcmp(mode, "glass") || !strcmp(mode, "filler"))
? WL_SHM_FORMAT_ARGB8888
: WL_SHM_FORMAT_XRGB8888);
p->next = buffers;
buffers = p;
++live_buffers;
wl_buffer_add_listener(p->buffer, &buffer_listener, p);
wl_surface_attach(surface, p->buffer, 0, 0);
wl_surface_damage_buffer(surface, 0, 0, width, height);
wl_surface_commit(surface);
fprintf(stderr,
"CLIENT_COMMIT ns=%lld mode=%s surface=%u buffer=%u index=%u "
"pattern=%c size=%dx%d damage=0,0,%d,%d\n",
stamp(), mode, wl_proxy_get_id((struct wl_proxy *)surface),
wl_proxy_get_id((struct wl_proxy *)p->buffer), ++commits, pattern,
width, height, width, height);
wl_shm_pool_destroy(pool);
close(fd);
}
static void ping(void *d, struct xdg_wm_base *b, uint32_t s) {
xdg_wm_base_pong(b, s);
}
static const struct xdg_wm_base_listener wm_listener = {.ping = ping};
static void top_configure(void *d, struct xdg_toplevel *t, int32_t w, int32_t h,
struct wl_array *a) {
if (w > 0)
width = w;
if (h > 0)
height = h;
}
static void top_close(void *d, struct xdg_toplevel *t) { running = 0; }
static const struct xdg_toplevel_listener top_listener = {
.configure = top_configure, .close = top_close};
static void configure(void *d, struct xdg_surface *s, uint32_t serial) {
xdg_surface_ack_configure(s, serial);
draw();
}
static const struct xdg_surface_listener xdg_listener = {.configure =
configure};
/* CONTRACT: configures ACK geometry, never submit an unreserved image.
* WHY: duplicate layer configures must not spend autonomous mutations.
* INVARIANT: the runner reserves then sends every A/B draw command. */
static void layer_configure(void *d, struct zwlr_layer_surface_v1 *s,
uint32_t serial, uint32_t w, uint32_t h) {
if (w == 0 || h == 0 || w > 4096 || h > 4096)
exit(2);
if (commits && ((int)w != width || (int)h != height))
exit(2);
width = (int)w;
height = (int)h;
zwlr_layer_surface_v1_ack_configure(s, serial);
fprintf(stderr, "BACKGROUND_CONFIGURED ns=%lld serial=%u size=%dx%d\n",
stamp(), serial, width, height);
}
static void layer_close(void *d, struct zwlr_layer_surface_v1 *s) {
running = 0;
}
static const struct zwlr_layer_surface_v1_listener layer_listener = {
.configure = layer_configure, .closed = layer_close};
static void output_name(void *d, struct wl_output *o, const char *name) {
if (!strcmp(name, "HEADLESS-1"))
h1 = o;
}
static void output_geometry(void *d, struct wl_output *o, int32_t x, int32_t y,
int32_t w, int32_t h, int32_t sp, const char *m,
const char *n, int32_t tr) {}
static void output_mode(void *d, struct wl_output *o, uint32_t flags, int32_t w,
int32_t h, int32_t refresh) {}
static void output_done(void *d, struct wl_output *o) {}
static void output_scale(void *d, struct wl_output *o, int32_t scale) {}
static void output_description(void *d, struct wl_output *o, const char *desc) {
}
static const struct wl_output_listener output_listener = {
.geometry = output_geometry,
.mode = output_mode,
.done = output_done,
.scale = output_scale,
.name = output_name,
.description = output_description};
static void global(void *d, struct wl_registry *r, uint32_t id,
const char *name, uint32_t v) {
if (!strcmp(name, "wl_compositor"))
compositor =
wl_registry_bind(r, id, &wl_compositor_interface, v < 4 ? v : 4);
else if (!strcmp(name, "wl_shm"))
shm = wl_registry_bind(r, id, &wl_shm_interface, 1);
else if (!strcmp(name, "xdg_wm_base"))
wm = wl_registry_bind(r, id, &xdg_wm_base_interface, v < 3 ? v : 3);
else if (!strcmp(name, "zwlr_layer_shell_v1"))
shell =
wl_registry_bind(r, id, &zwlr_layer_shell_v1_interface, v < 2 ? v : 2);
else if (!strcmp(name, "wl_output") && v >= 4) {
struct wl_output *o = wl_registry_bind(r, id, &wl_output_interface, 4);
wl_output_add_listener(o, &output_listener, NULL);
}
}
static void removed(void *d, struct wl_registry *r, uint32_t id) {}
static const struct wl_registry_listener registry_listener = {
.global = global, .global_remove = removed};
static void stop(int s) { running = 0; }
int main(int argc, char **argv) {
/* WHY: Linux may route core dumps through a pipe handler that execs another
* process, so a zero RLIMIT_CORE alone does not protect this client.
* CONTRACT/INVARIANT: establish both limits before any Wayland operation. */
struct rlimit no_core = {0, 0};
if (setrlimit(RLIMIT_CORE, &no_core) < 0) {
perror("setrlimit(RLIMIT_CORE)");
return 1;
}
if (prctl(PR_SET_DUMPABLE, 0) < 0) {
perror("prctl(PR_SET_DUMPABLE, 0)");
return 1;
}
int dumpable = prctl(PR_GET_DUMPABLE);
if (dumpable < 0) {
perror("prctl(PR_GET_DUMPABLE)");
return 1;
}
if (dumpable != 0) {
fprintf(stderr, "PR_GET_DUMPABLE returned %d; expected 0\n", dumpable);
return 1;
}
if (argc != 2)
return 2;
mode = argv[1];
if (strcmp(mode, "background") && strcmp(mode, "glass") &&
strcmp(mode, "filler") && strcmp(mode, "reference"))
return 2;
signal(SIGTERM, stop);
display = wl_display_connect(NULL);
if (!display)
return 1;
struct wl_registry *r = wl_display_get_registry(display);
wl_registry_add_listener(r, &registry_listener, NULL);
if (wl_display_roundtrip(display) < 0 || wl_display_roundtrip(display) < 0)
return 1;
if (!compositor || !shm || !wm)
return 2;
xdg_wm_base_add_listener(wm, &wm_listener, NULL);
surface = wl_compositor_create_surface(compositor);
if (!strcmp(mode, "background")) {
if (!shell || !h1)
return 2;
layer = zwlr_layer_shell_v1_get_layer_surface(
shell, surface, h1, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND,
"mosaic-dynamic-background");
zwlr_layer_surface_v1_add_listener(layer, &layer_listener, NULL);
zwlr_layer_surface_v1_set_size(layer, 0, 0);
zwlr_layer_surface_v1_set_anchor(layer,
ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM |
ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT);
zwlr_layer_surface_v1_set_exclusive_zone(layer, -1);
zwlr_layer_surface_v1_set_keyboard_interactivity(layer, 0);
} else {
xdg = xdg_wm_base_get_xdg_surface(wm, surface);
xdg_surface_add_listener(xdg, &xdg_listener, NULL);
top = xdg_surface_get_toplevel(xdg);
xdg_toplevel_add_listener(top, &top_listener, NULL);
const char *app = !strcmp(mode, "reference") ? "org.mosaic.StaticReference"
: !strcmp(mode, "glass") ? "org.mosaic.BlurGlass"
: "org.mosaic.BlurFiller";
xdg_toplevel_set_app_id(top, app);
xdg_toplevel_set_title(top, app);
}
wl_surface_commit(surface);
while (running) {
if (wl_display_dispatch_pending(display) < 0)
break;
if (wl_display_flush(display) < 0 && errno != EAGAIN)
break;
struct pollfd fds[2] = {
{.fd = wl_display_get_fd(display), .events = POLLIN},
{.fd = STDIN_FILENO, .events = POLLIN}};
int n = poll(fds, 2, 100);
if (n < 0) {
if (errno == EINTR)
continue;
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 (!strcmp(mode, "background") &&
((!commits && !strcmp(cmd,"G3-A\n")) ||
(commits==1 && !strcmp(cmd,"G3-B\n")) ||
(commits==2 && !strcmp(cmd,"G3-A-return\n")))) {
pattern = commits==1?'B':'A';
source_command=commits==0?"G3-A":commits==1?"G3-B":"G3-A-return";
draw();
fprintf(stderr, "COMMAND %c DONE ns=%lld\n", pattern, stamp());
} else if (!strcmp(cmd, "close\n")) {
fprintf(stderr,"CLOSE_RECEIVED\n");running = 0;
}
else if(!strcmp(mode,"background"))return 2;
}
}
int error = wl_display_get_error(display);
fprintf(stderr, "EXIT ns=%lld mode=%s error=%d commits=%u\n", stamp(), mode,
error, commits);
if (layer)
zwlr_layer_surface_v1_destroy(layer);
if (top)
xdg_toplevel_destroy(top);
if (xdg)
xdg_surface_destroy(xdg);
wl_surface_destroy(surface);
wl_display_roundtrip(display);
fprintf(stderr,"SHM_CLOSURE created=%u released=%u\n",commits,released_buffers);
bool all_released=commits==released_buffers;
wl_display_flush(display);
while (buffers)
free_pixels(buffers);
free(source_a);
wl_registry_destroy(r);
wl_display_disconnect(display);
return error || !all_released ? 1 : 0;
}

View file

@ -0,0 +1,43 @@
"""Write a relative G3 source manifest outside Git; grants no review."""
import argparse
import os
import json
from pathlib import Path
from reproduce import KIT, sha, tree
if __name__ == '__main__':
p = argparse.ArgumentParser(description=__doc__)
p.add_argument('--lab', type=Path, required=True)
p.add_argument('--output', type=Path)
a = p.parse_args()
lab = a.lab.resolve(strict=True)
assert str(lab).startswith('/var/tmp/swayfx-mosaic-step20c-native-closure-')
files = tree(KIT)
result = {'schema': 'G3-relative-source-freeze-v1', 'status': 'EXPERIMENTAL_SOURCE_ONLY',
'review': 'PENDING_INDEPENDENT', 'build': 'NOT_RUN', 'runtime': 'NOT_RUN',
'no_inherited_G2_qualification': True, 'files': files}
out = a.output if a.output is not None else Path('g3-source-freeze.json')
if not out.is_absolute():
out = lab / out
if out.parent.resolve(strict=True) != lab or out.name in ('', '.', '..'):
p.error('--output must name a direct file below --lab')
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
if hasattr(os, 'O_NOFOLLOW'):
flags |= os.O_NOFOLLOW
fd = os.open(out, flags, 0o600)
try:
payload = (json.dumps(result, indent=2) + '\n').encode()
with os.fdopen(fd, 'wb') as stream:
fd = -1
stream.write(payload)
stream.flush()
os.fsync(stream.fileno())
dirfd = os.open(lab, os.O_RDONLY | getattr(os, 'O_DIRECTORY', 0))
try:
os.fsync(dirfd)
finally:
os.close(dirfd)
finally:
if fd >= 0:
os.close(fd)
print(sha(out))

View file

@ -0,0 +1,152 @@
/* G3 private admission delta; G2 hook symbols retained. No production API. */
#ifndef G2_GL_H
#define G2_GL_H
#include <EGL/egl.h>
#include <GLES3/gl3.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define G2_CAP (256u * 1024u * 1024u)
#define G2_ERRORS 16
struct g2_errors { GLenum values[G2_ERRORS]; unsigned count; bool exhausted; };
struct g2_state {
GLint read_fbo, draw_fbo, rbo, pack, pbo, row, skip_rows, skip_pixels;
GLint viewport[4], scissor[4], program, active, texture;
GLboolean scissor_on;
};
struct g2_sample {
EGLContext context; EGLint client_version; EGLenum api;
EGLDisplay display; char version[160], glsl[160]; GLint major, minor;
bool capability_queried, version_queried, state_saved, bound, read_attempted, restore_attempted; struct g2_state before, after;
struct g2_errors pre, version_errors, capability, state_errors, query, read, restore;
GLint object_type, object, level, width, height, format, channels[4];
GLint read_buffer, samples; GLenum status; size_t bytes; unsigned char *pixels;
bool success; const char *reason;
};
static struct g2_errors g2_drain(void) {
struct g2_errors e = {0};
for (unsigned i=0;i<G2_ERRORS;i++) {
GLenum v=glGetError(); if (v==GL_NO_ERROR) return e;
e.values[e.count++]=v;
}
/* No unbounded probing: sixteen errors means attribution rejected. */
e.exhausted=true; return e;
}
static bool g2_clean(struct g2_errors e) { return !e.count && !e.exhausted; }
static bool g2_size(int w,int h,size_t used,size_t *n) {
if(w<=0 || h<=0 || used>G2_CAP || (size_t)w>SIZE_MAX/4/(size_t)h) return false;
*n=(size_t)w*(size_t)h*4; return *n<=G2_CAP-used;
}
static void g2_state_get(struct g2_state *s) {
memset(s,0,sizeof(*s));
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING,&s->read_fbo);
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING,&s->draw_fbo);
glGetIntegerv(GL_RENDERBUFFER_BINDING,&s->rbo);
glGetIntegerv(GL_PACK_ALIGNMENT,&s->pack);
glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING,&s->pbo);
glGetIntegerv(GL_PACK_ROW_LENGTH,&s->row);
glGetIntegerv(GL_PACK_SKIP_ROWS,&s->skip_rows);
glGetIntegerv(GL_PACK_SKIP_PIXELS,&s->skip_pixels);
glGetIntegerv(GL_VIEWPORT,s->viewport); glGetIntegerv(GL_SCISSOR_BOX,s->scissor);
s->scissor_on=glIsEnabled(GL_SCISSOR_TEST);
glGetIntegerv(GL_CURRENT_PROGRAM,&s->program);
glGetIntegerv(GL_ACTIVE_TEXTURE,&s->active);
glGetIntegerv(GL_TEXTURE_BINDING_2D,&s->texture);
}
static void g2_state_restore(const struct g2_state *s) {
glBindFramebuffer(GL_READ_FRAMEBUFFER,(GLuint)s->read_fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER,(GLuint)s->draw_fbo);
glBindRenderbuffer(GL_RENDERBUFFER,(GLuint)s->rbo);
glBindBuffer(GL_PIXEL_PACK_BUFFER,(GLuint)s->pbo);
glPixelStorei(GL_PACK_ALIGNMENT,s->pack);
glPixelStorei(GL_PACK_ROW_LENGTH,s->row);
glPixelStorei(GL_PACK_SKIP_ROWS,s->skip_rows);
glPixelStorei(GL_PACK_SKIP_PIXELS,s->skip_pixels);
}
/* WHY: EGL creation allows a newer compatible ES context (§3.7.1), while
* EGL's context query describes supported version (§3.7.4). Preserve any
* EGL/GL divergence as evidence; never relabel EGL as a creation request.
* CONTRACT: attest bounded GL_VERSION first, then safely query ES3 enums.
* INVARIANT: only ES 3.0..3.2 native RBO reads; no shader or error-queue neutrality. */
static bool g2_version(const char *v, GLint *minor) {
if(!v || strnlen(v,160)>=160 || strncmp(v,"OpenGL ES 3.",12))return false;
if(v[12]<'0' || v[12]>'2' || (v[13] && v[13]!=' '))return false;
for(size_t i=13;v[i];i++)if((unsigned char)v[i]<32 || (unsigned char)v[i]>126)return false;
*minor=v[12]-'0';return true;
}
static struct g2_sample g2_read(GLuint fbo,int width,int height,size_t used) {
struct g2_sample s={.reason="NOT_PROVEN"};
s.context=eglGetCurrentContext(); s.api=eglQueryAPI(); s.display=eglGetCurrentDisplay();
if(s.context==EGL_NO_CONTEXT || s.display==EGL_NO_DISPLAY || s.api!=EGL_OPENGL_ES_API) {s.reason="UNKNOWN_CONTEXT"; return s;}
if(!eglQueryContext(s.display,s.context,EGL_CONTEXT_CLIENT_VERSION,&s.client_version)) {
s.reason="EGL_QUERY_FAILED"; return s;
}
s.pre=g2_drain(); if(!g2_clean(s.pre)) {s.reason="PREEXISTING_GL_ERRORS"; return s;}
s.version_queried=true;
const char *v=(const char *)glGetString(GL_VERSION);
if(v && strnlen(v,sizeof(s.version))<sizeof(s.version))snprintf(s.version,sizeof(s.version),"%s",v);
s.version_errors=g2_drain(); if(!g2_clean(s.version_errors)) {s.reason="VERSION_QUERY_ERROR"; return s;}
GLint parsed_minor=0;
if(s.client_version<2 || s.client_version>3 || !g2_version(v,&parsed_minor)) {
s.reason="GLES_STATE_NOT_SUPPORTED"; return s;
}
s.capability_queried=true;
glGetIntegerv(GL_MAJOR_VERSION,&s.major);glGetIntegerv(GL_MINOR_VERSION,&s.minor);
const char *glsl=(const char *)glGetString(GL_SHADING_LANGUAGE_VERSION);
if(glsl && strnlen(glsl,sizeof(s.glsl))<sizeof(s.glsl))snprintf(s.glsl,sizeof(s.glsl),"%s",glsl);
s.capability=g2_drain();
if(!g2_clean(s.capability)) {s.reason="CAPABILITY_QUERY_ERROR";return s;}
if(s.major!=3 || s.minor!=parsed_minor || !s.glsl[0]) {s.reason="VERSION_INCONSISTENT";return s;}
if(!g2_size(width,height,used,&s.bytes)) {s.reason="RAW_BUDGET_OR_OVERFLOW"; return s;}
g2_state_get(&s.before); s.state_errors=g2_drain();
if(!g2_clean(s.state_errors)) {s.reason="STATE_QUERY_ERROR"; return s;}
s.state_saved=true; s.bound=true;
glBindFramebuffer(GL_READ_FRAMEBUFFER,fbo);
glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,
GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE,&s.object_type);
glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,
GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,&s.object);
if(s.object_type==GL_RENDERBUFFER && s.object>0) {
glBindRenderbuffer(GL_RENDERBUFFER,(GLuint)s.object);
glGetRenderbufferParameteriv(GL_RENDERBUFFER,GL_RENDERBUFFER_WIDTH,&s.width);
glGetRenderbufferParameteriv(GL_RENDERBUFFER,GL_RENDERBUFFER_HEIGHT,&s.height);
glGetRenderbufferParameteriv(GL_RENDERBUFFER,GL_RENDERBUFFER_INTERNAL_FORMAT,&s.format);
glGetRenderbufferParameteriv(GL_RENDERBUFFER,GL_RENDERBUFFER_SAMPLES,&s.samples);
const GLenum c[4]={GL_RENDERBUFFER_RED_SIZE,GL_RENDERBUFFER_GREEN_SIZE,GL_RENDERBUFFER_BLUE_SIZE,GL_RENDERBUFFER_ALPHA_SIZE};
for(int i=0;i<4;i++) glGetRenderbufferParameteriv(GL_RENDERBUFFER,c[i],&s.channels[i]);
} else if(s.object_type==GL_TEXTURE) {
glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,
GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL,&s.level);
}
glGetIntegerv(GL_READ_BUFFER,&s.read_buffer);
s.status=glCheckFramebufferStatus(GL_READ_FRAMEBUFFER);
s.query=g2_drain();
if(!g2_clean(s.query)) {s.reason="ATTACHMENT_QUERY_ERROR"; goto done;}
if(s.object_type!=GL_RENDERBUFFER) {s.reason="TEXTURE_ATTACHMENT_DIMENSIONS_NOT_PROVEN"; goto done;}
if(s.read_buffer!=GL_COLOR_ATTACHMENT0 || s.samples!=0 || s.status!=GL_FRAMEBUFFER_COMPLETE || s.width!=width || s.height!=height ||
s.channels[0]!=8 || s.channels[1]!=8 || s.channels[2]!=8 || (s.channels[3]!=0 && s.channels[3]!=8)) {
s.reason="ATTACHMENT_UNSUPPORTED"; goto done;
}
glBindBuffer(GL_PIXEL_PACK_BUFFER,0); glPixelStorei(GL_PACK_ALIGNMENT,1);
glPixelStorei(GL_PACK_ROW_LENGTH,0); glPixelStorei(GL_PACK_SKIP_ROWS,0); glPixelStorei(GL_PACK_SKIP_PIXELS,0);
s.query=g2_drain(); if(!g2_clean(s.query)) {s.reason="PACK_QUERY_ERROR"; goto done;}
s.pixels=malloc(s.bytes); if(!s.pixels) {s.reason="ALLOCATION_FAILED"; goto done;}
/* glReadPixels returns void. NO alpha sentinel or file hash success gate. */
s.read_attempted=true;
glReadPixels(0,0,width,height,GL_RGBA,GL_UNSIGNED_BYTE,s.pixels);
s.read=g2_drain();
if(!g2_clean(s.read)) {s.reason="READ_GL_ERROR"; goto done;}
s.success=true; s.reason="READ_SUCCESS";
done:
if(s.state_saved && s.bound) {
s.restore_attempted=true;
g2_state_restore(&s.before); g2_state_get(&s.after); s.restore=g2_drain();
if(!g2_clean(s.restore) || memcmp(&s.before,&s.after,sizeof(s.before)) || eglGetCurrentContext()!=s.context || eglGetCurrentDisplay()!=s.display || eglQueryAPI()!=s.api) {
s.success=false; s.reason="RESTORE_FAILED";
}
}
return s;
}
#endif

View file

@ -0,0 +1,228 @@
/* PRIVATE G3 admission; G2 symbols/hooks preserved. No public API. */
#ifndef G2_NATIVE_H
#define G2_NATIVE_H
#include "g2_gl.h"
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <wlr/render/dmabuf.h>
#include <drm_fourcc.h>
#include <wlr/types/wlr_buffer.h>
#ifndef G3_ROOT
#error "Define G3_ROOT as the reviewed new LAB acquisition path"
#endif
#define G2_ROOT G3_ROOT
static struct {
bool stopped, active; unsigned epoch, point; size_t used;
int dir; FILE *meta; const void *pass, *output;
uintptr_t pass_sequence; char nonce[65];
} g2={.dir=-1};
static const char *g2_names[]={"source","down0","down1","down2","up2","up1","up0","optimized-cache","no-blur-cache","composition-presubmit"};
static void g2_fail(const char *reason) {
g2.stopped=true;
if(g2.meta) {fprintf(g2.meta,"{\"kind\":\"rejected\",\"reason\":\"%s\",\"point\":%u}\n",reason,g2.point); fflush(g2.meta); fsync(fileno(g2.meta)); fclose(g2.meta); g2.meta=NULL;}
g2.active=false;
}
static uint32_t g2_crc(const unsigned char *p,size_t n) {
uint32_t crc=~0u;
for(size_t i=0;i<n;i++) {crc^=p[i]; for(int b=0;b<8;b++) crc=(crc>>1)^((0u-(crc&1u))&0xedb88320u);}
return ~crc;
}
static bool g2_write(int fd,const void *ptr,size_t n) {
const char *p=ptr; while(n) {ssize_t k=write(fd,p,n); if(k<0 && errno==EINTR) continue; if(k<=0)return false; p+=k;n-=(size_t)k;} return true;
}
static void g2_error_json(const char *name,struct g2_errors e) {
fprintf(g2.meta,",\"%s\":{\"exhausted\":%s,\"codes\":[",name,e.exhausted?"true":"false");
for(unsigned i=0;i<e.count;i++) {fprintf(g2.meta,"%s%u",i?",":"",e.values[i]);}
fputs("]}",g2.meta);
}
static void g2_state_json(const char *name,const struct g2_state *s) {
fprintf(g2.meta,",\"%s\":{\"read_draw_rbo_pack_pbo_row_skip\":[%d,%d,%d,%d,%d,%d,%d,%d],\"viewport\":[%d,%d,%d,%d],\"scissor\":[%d,%d,%d,%d],\"scissor_enabled\":%u,\"program_active_texture\":[%d,%d,%d]}",name,s->read_fbo,s->draw_fbo,s->rbo,s->pack,s->pbo,s->row,s->skip_rows,s->skip_pixels,s->viewport[0],s->viewport[1],s->viewport[2],s->viewport[3],s->scissor[0],s->scissor[1],s->scissor[2],s->scissor[3],s->scissor_on,s->program,s->active,s->texture);
}
static void g2_region(const pixman_region32_t *region) {
int n=0; pixman_box32_t *r=region?pixman_region32_rectangles(region,&n):NULL;
fputs(",\"fresh_rectangles\":[",g2.meta);
if(n>256) {fputs("],\"fresh_region_overflow\":true",g2.meta);return;}
for(int i=0;i<n;i++)fprintf(g2.meta,"%s[%d,%d,%d,%d]",i?",":"",r[i].x1,r[i].y1,r[i].x2,r[i].y2);
fputs("]",g2.meta);
}
static bool g2_dmabuf_supported(struct fx_framebuffer *b) {
struct wlr_dmabuf_attributes a={0};
if(!wlr_buffer_get_dmabuf(b->buffer,&a))return false;
bool format=a.format==DRM_FORMAT_ABGR8888 || a.format==DRM_FORMAT_XBGR8888 || a.format==DRM_FORMAT_ARGB8888 || a.format==DRM_FORMAT_XRGB8888;
bool amd=a.modifier==UINT64_C(0x020000001046bb04);
if(!format || (a.modifier!=0 && !amd) || a.n_planes!=(amd?2:1) || a.width!=b->buffer->width || a.height!=b->buffer->height || a.width<=0 || a.height<=0)return false;
if((uint64_t)a.stride[0]<(uint64_t)a.width*4)return false;
for(int i=0;i<a.n_planes;i++) {struct stat st;if(!a.stride[i] || a.fd[i]<0 || fstat(a.fd[i],&st))return false;}
return true; /* Metadata sanity, NEVER modifier decoding/allocation proof. */
}
static void g2_native_object(struct fx_framebuffer *b) {
fprintf(g2.meta,",\"native\":{\"fx\":\"%"PRIxPTR"\",\"wlr\":\"%"PRIxPTR"\",\"fbo_rbo_tex\":[%u,%u,%u],\"dimensions\":[%d,%d],\"image\":\"%"PRIxPTR"\"}",(uintptr_t)b,(uintptr_t)b->buffer,b->fbo,b->rbo,b->tex,b->buffer->width,b->buffer->height,(uintptr_t)b->image);
struct wlr_dmabuf_attributes a={0};
if(!wlr_buffer_get_dmabuf(b->buffer,&a)) {fputs(",\"dmabuf\":null",g2.meta);return;}
/* Borrowed descriptors: NEVER finish(), close(), duplicate or seek these FDs.
* fstat exposes descriptor identity, not a trustworthy DCC allocation length. */
fprintf(g2.meta,",\"dmabuf\":{\"format\":%u,\"modifier\":\"%016"PRIx64"\",\"dimensions\":[%d,%d],\"borrowed\":true,\"allocation_length\":\"NOT_PROVEN\",\"planes\":[",a.format,a.modifier,a.width,a.height);
if(a.n_planes<1 || a.n_planes>4) {fputs("]}",g2.meta);return;}
for(int i=0;i<a.n_planes;i++) {struct stat st={0};bool ok=fstat(a.fd[i],&st)==0;
fprintf(g2.meta,"%s{\"stride\":%u,\"offset\":%u,\"fd\":%d,\"fstat_ok\":%s,\"dev\":%ju,\"ino\":%ju}",i?",":"",a.stride[i],a.offset[i],a.fd[i],ok?"true":"false",(uintmax_t)st.st_dev,(uintmax_t)st.st_ino);}
fputs("]}",g2.meta);
}
static bool g2_owned(int fd,mode_t mode) {struct stat s;return fstat(fd,&s)==0 && s.st_uid==geteuid() && (s.st_mode&0777)==mode;}
static bool g2_open(void) {
if(g2.dir>=0)return true;
const char *d=getenv("MOSAIC_G3_DIR"),*nonce=getenv("MOSAIC_G3_NONCE"); char real[PATH_MAX];
if(!d || !nonce)return false;
if(strlen(nonce)!=64 || !realpath(d,real) || strcmp(real,G2_ROOT) || strcmp(d,G2_ROOT)) {g2.stopped=true;return false;}
for(unsigned i=0;i<64;i++)if(!((nonce[i]>='0'&&nonce[i]<='9')||(nonce[i]>='a'&&nonce[i]<='f'))) {g2.stopped=true;return false;}
g2.dir=open(d,O_RDONLY|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
if(g2.dir<0 || !g2_owned(g2.dir,0700)) {if(g2.dir>=0)close(g2.dir);g2.dir=-1;g2.stopped=true;return false;}
memcpy(g2.nonce,nonce,65);return true;
}
static void g2_begin(struct fx_gles_render_pass *pass,struct fx_framebuffer *source,struct fx_render_blur_pass_options *options);
static bool g2_expected_source(const struct g2_sample *s,unsigned epoch) {
if(s->width!=800 || s->height!=600 || !s->pixels)return false;
const unsigned char a[2][3]={{245,245,245},{15,25,45}};
const unsigned char b[2][3]={{32,192,224},{176,32,64}};
for(int y=0;y<600;y++)for(int x=0;x<800;x++) {
const unsigned char *expected=(epoch==2?b:a)[(x/8)%2];
if(memcmp(s->pixels+((size_t)y*800+(size_t)x)*4,expected,3))return false;
}
return true; /* RGB source admission only; alpha is recorded, never sentinel. */
}
static void g2_sample_native(struct fx_gles_render_pass *pass,struct fx_framebuffer *b,unsigned point,const pixman_region32_t *region) {
if(!g2.active || g2.pass!=pass)return;
if(point!=g2.point || point>=10) {g2_fail("STAGE_ORDER_OR_DUPLICATE");return;}
if(region && pixman_region32_n_rects(region)>256) {g2_fail("REGION_LIMIT");return;}
if(!g2_dmabuf_supported(b)) {g2_fail("DMABUF_METADATA_UNSUPPORTED");return;}
struct g2_sample s=g2_read(b->fbo,b->buffer->width,b->buffer->height,g2.used);
fprintf(g2.meta,"{\"kind\":\"sample\",\"epoch\":%u,\"pass_sequence\":%"PRIuPTR",\"point\":%u,\"stage\":\"%s\",\"context\":\"%"PRIxPTR"\",\"api\":%u,\"client_version\":%d,\"result\":\"%s\",\"bytes\":%zu,\"attachment\":[%d,%d,%d,%d,%d,%d],\"channels\":[%d,%d,%d,%d],\"status\":%u",g2.epoch,g2.pass_sequence,point,g2_names[point],(uintptr_t)s.context,s.api,s.client_version,s.reason,s.bytes,s.object_type,s.object,s.level,s.width,s.height,s.format,s.channels[0],s.channels[1],s.channels[2],s.channels[3],s.status);
fprintf(g2.meta,",\"read_success\":%s,\"read_buffer\":%d,\"samples\":%d",s.success?"true":"false",s.read_buffer,s.samples);
fputs(",\"gl_version_hex\":\"",g2.meta);for(size_t i=0;i<strlen(s.version);i++)fprintf(g2.meta,"%02x",(unsigned char)s.version[i]);fputc('"',g2.meta);
g2_error_json("pre_errors",s.pre);g2_error_json("query_errors",s.query);g2_error_json("read_errors",s.read);g2_error_json("restore_errors",s.restore);
fprintf(g2.meta,",\"version_queried\":%s,\"state_saved\":%s,\"bound\":%s,\"read_attempted\":%s,\"restore_attempted\":%s",s.version_queried?"true":"false",s.state_saved?"true":"false",s.bound?"true":"false",s.read_attempted?"true":"false",s.restore_attempted?"true":"false");
if(s.capability_queried)fprintf(g2.meta,",\"gl_major\":%d,\"gl_minor\":%d",s.major,s.minor);else fputs(",\"gl_major\":null,\"gl_minor\":null",g2.meta);
fputs(",\"glsl_hex\":\"",g2.meta);for(size_t i=0;i<strlen(s.glsl);i++)fprintf(g2.meta,"%02x",(unsigned char)s.glsl[i]);fputc('"',g2.meta);
g2_error_json("version_errors",s.version_errors);g2_error_json("capability_errors",s.capability);g2_error_json("state_errors",s.state_errors);
if(s.state_saved)g2_state_json("before",&s.before);else fputs(",\"before\":null",g2.meta);
if(s.restore_attempted)g2_state_json("after",&s.after);else fputs(",\"after\":null",g2.meta);g2_native_object(b);g2_region(region);
if(!s.success) {fputs("}\n",g2.meta);free(s.pixels);g2_fail(s.reason);return;}
char name[64];snprintf(name,sizeof(name),"epoch-%u-point-%u.rgba",g2.epoch,point);
int fd=openat(g2.dir,name,O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW|O_CLOEXEC,0600);
bool ok=fd>=0 && g2_write(fd,s.pixels,s.bytes) && fsync(fd)==0;
if(fd>=0 && close(fd))ok=false;
fprintf(g2.meta,",\"crc32\":\"%08x\",\"file\":\"%s\",\"published\":%s}\n",g2_crc(s.pixels,s.bytes),name,ok?"true":"false");
g2.used+=s.bytes;
if(!ok || fflush(g2.meta) || fsync(fileno(g2.meta))) {free(s.pixels);g2_fail("PUBLICATION_FAILED");return;}
g2.point++;
if(point==0 && !g2_expected_source(&s,g2.epoch)) {free(s.pixels);g2_fail("SOURCE_PATTERN_NOT_CURRENT_EPOCH");return;}
free(s.pixels);
if(point==9) {
fprintf(g2.meta,"{\"kind\":\"end\",\"epoch\":%u,\"points\":10,\"raw_total\":%zu,\"accepted\":true}\n",g2.epoch,g2.used);
bool final_ok=fflush(g2.meta)==0 && fsync(fileno(g2.meta))==0;
if(fclose(g2.meta)) {final_ok=false;}
g2.meta=NULL;g2.active=false;
if(!final_ok)g2.stopped=true;
if(g2.epoch==3)g2.stopped=true;
}
}
/* Explicit intended-output mapping; bounded eight outputs, no public ABI. */
static struct {const void *pass;bool intended;} g2_outputs[8];
static void g2_forget_output(struct fx_gles_render_pass *pass) {
for(unsigned i=0;i<8;i++)if(g2_outputs[i].pass==pass) {
g2_outputs[i].pass=NULL;g2_outputs[i].intended=false;
}
}
static bool g2_intended_output(struct fx_gles_render_pass *pass) {
for(unsigned i=0;i<8;i++)if(g2_outputs[i].pass==pass)return g2_outputs[i].intended;
return false;
}
static void g2_track_output(struct fx_gles_render_pass *pass,struct wlr_output *output) {
/* A native pass owns its mapping only until submit. Address reuse overwrites
* prior identity. Overflow never silently associates another output. */
g2_forget_output(pass);
for(unsigned i=0;i<8;i++)if(!g2_outputs[i].pass) {
g2_outputs[i].pass=pass;
g2_outputs[i].intended=strcmp(output->name,"HEADLESS-1")==0 && output->scale==1.0f && output->transform==WL_OUTPUT_TRANSFORM_NORMAL;
return;
}
g2_fail("TOO_MANY_SIMULTANEOUS_NATIVE_PASSES");
}
static void g2_begin(struct fx_gles_render_pass *pass,struct fx_framebuffer *source,struct fx_render_blur_pass_options *options) {
if(g2.stopped)return;
if(g2.active) {if(g2.pass==pass)g2_fail("MULTIPLE_OPTIMIZED_CANDIDATES");return;}
if(!g2_intended_output(pass) || !g2_open())return;
unsigned e=g2.epoch+1;if(e>3)return;
char arm[40],consumed[40],meta[40],expected[80],data[81]={0};
snprintf(arm,sizeof(arm),"epoch-%u.arm",e);snprintf(consumed,sizeof(consumed),"epoch-%u.consumed",e);
snprintf(expected,sizeof(expected),"%s %u\n",g2.nonce,e);
/* WHY: O_NONBLOCK makes non-regular substitutions (especially a FIFO with
* no writer) fail closed without stalling the compositor callback.
* CONTRACT: validate identity, permissions, link count and exact bounded
* framing size before reading or consuming the arm token. */
int fd=openat(g2.dir,arm,O_RDONLY|O_NONBLOCK|O_NOFOLLOW|O_CLOEXEC);if(fd<0) {if(errno!=ENOENT)g2.stopped=true;return;}
struct stat st;
bool ok=fstat(fd,&st)==0 && S_ISREG(st.st_mode) && st.st_uid==geteuid() &&
(st.st_mode&0777)==0600 && st.st_nlink==1 && st.st_size==(off_t)strlen(expected);
if(!ok) {close(fd);g2.stopped=true;return;}
ssize_t n=read(fd,data,sizeof(data)-1);
if(n!=(ssize_t)strlen(expected) || memcmp(data,expected,(size_t)n)) {close(fd);g2.stopped=true;return;}
/* Consume BEFORE any query, readback or publication. Existing consumed token
* is fatal, even if a prior process died or first A was rejected. */
struct stat linked;
if(linkat(g2.dir,arm,g2.dir,consumed,0) || fstatat(g2.dir,consumed,&linked,AT_SYMLINK_NOFOLLOW) ||
!S_ISREG(linked.st_mode) || linked.st_dev!=st.st_dev || linked.st_ino!=st.st_ino || linked.st_nlink!=2 ||
unlinkat(g2.dir,arm,0) || fsync(g2.dir)) {close(fd);g2.stopped=true;return;}
close(fd);
g2.epoch=e;g2.point=0;g2.pass=pass;g2.pass_sequence++;g2.active=true;
snprintf(meta,sizeof(meta),"epoch-%u.jsonl",e);fd=openat(g2.dir,meta,O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW|O_CLOEXEC,0600);
if(fd<0 || !(g2.meta=fdopen(fd,"w"))) {if(fd>=0)close(fd);g2.stopped=true;g2.active=false;return;}
fprintf(g2.meta,"{\"kind\":\"begin\",\"schema\":\"G3-followup-v1\",\"epoch\":%u,\"label\":\"%s\",\"nonce\":\"%s\",\"output\":\"HEADLESS-1\",\"pass\":\"%"PRIxPTR"\",\"offscreen_output_key\":\"%"PRIxPTR"\",\"intrusive_error_queue\":true,\"error_queue_restorable\":false,\"readback_may_synchronize\":true,\"rows\":\"GL_LOW_TO_HIGH\",\"present_buffer\":\"NOT_PROVEN\",\"gpu_completion\":\"NOT_PROVEN\",\"lifetime\":\"NOT_PROVEN\"}\n",e,e==1?"A":e==2?"B":"A-return",g2.nonce,(uintptr_t)pass,(uintptr_t)pass->fx_offscreen_buffers);
if(options->blur_data->num_passes!=3 || options->blur_strength!=1.0f || blur_data_should_parameters_blur_effects(options->blur_data)) {g2_fail("NON_PROSPECTIVE_BLUR_CONFIGURATION");return;}
g2_sample_native(pass,source,0,options->tex_options.base.clip);
}
/* Executed while the actual blur program and source sampler remain bound. */
static void g2_uniform(struct fx_gles_render_pass *pass,struct blur_shader *shader,struct fx_texture *texture,const pixman_region32_t *region) {
if(!g2.active || g2.pass!=pass || g2.point<1 || g2.point>6)return;
if(region && pixman_region32_n_rects(region)>256) {g2_fail("REGION_LIMIT");return;}
struct g2_errors pre=g2_drain();if(!g2_clean(pre)) {fputs("{\"kind\":\"uniform-errors\"",g2.meta);g2_error_json("pre_errors",pre);fputs("}\n",g2.meta);g2_fail("PRE_UNIFORM_GL_ERROR");return;}
struct g2_state draw_state;GLboolean color_mask[4]={0};
g2_state_get(&draw_state);glGetBooleanv(GL_COLOR_WRITEMASK,color_mask);
GLboolean blend=glIsEnabled(GL_BLEND),stencil=glIsEnabled(GL_STENCIL_TEST);
GLint program=0,min=0,mag=0,wraps=0,wrapt=0;GLfloat value[2]={0};GLint range[2]={0},precision=0;
glGetIntegerv(GL_CURRENT_PROGRAM,&program);
struct g2_errors initial=g2_drain();
if(!g2_clean(initial)) {fputs("{\"kind\":\"uniform-errors\"",g2.meta);g2_error_json("query_errors",initial);fputs("}\n",g2.meta);g2_fail("UNIFORM_PROGRAM_QUERY_ERROR");return;}
if(program!=(GLint)shader->program || shader->halfpixel<0 || texture->target!=GL_TEXTURE_2D) {g2_fail("UNIFORM_PROGRAM_OR_TARGET");return;}
GLint actual=glGetUniformLocation((GLuint)program,"halfpixel");
initial=g2_drain();
if(!g2_clean(initial) || actual!=shader->halfpixel) {fputs("{\"kind\":\"uniform-errors\"",g2.meta);g2_error_json("query_errors",initial);fputs("}\n",g2.meta);g2_fail("UNIFORM_LOCATION");return;}
GLint count=0;glGetProgramiv((GLuint)program,GL_ACTIVE_UNIFORMS,&count);bool typed=false;
initial=g2_drain();
if(!g2_clean(initial) || count<0 || count>128) {fputs("{\"kind\":\"uniform-errors\"",g2.meta);g2_error_json("query_errors",initial);fputs("}\n",g2.meta);g2_fail("UNIFORM_COUNT");return;}
for(GLuint i=0;i<(GLuint)count;i++) {char name[128];GLsizei len=0;GLint size=0;GLenum type=0;glGetActiveUniform((GLuint)program,i,sizeof(name),&len,&size,&type,name);
if(len==9 && !memcmp(name,"halfpixel",9) && size==1 && type==GL_FLOAT_VEC2)typed=true;
}
GLfloat radius=0,proj[9]={0},tex_proj[9]={0};
glGetUniformfv((GLuint)program,shader->radius,&radius);
glGetUniformfv((GLuint)program,shader->proj,proj);glGetUniformfv((GLuint)program,shader->tex_proj,tex_proj);
glGetUniformfv((GLuint)program,actual,value);
glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER,GL_MEDIUM_FLOAT,range,&precision);
glGetTexParameteriv(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,&min);glGetTexParameteriv(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,&mag);
glGetTexParameteriv(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,&wraps);glGetTexParameteriv(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,&wrapt);
struct g2_errors errors=g2_drain();if(!g2_clean(errors)||!typed) {fputs("{\"kind\":\"uniform-errors\"",g2.meta);g2_error_json("query_errors",errors);fputs("}\n",g2.meta);g2_fail("UNIFORM_QUERY_OR_TYPE");return;}
uint32_t bits[2];memcpy(bits,value,sizeof(bits));
fprintf(g2.meta,"{\"kind\":\"uniform\",\"point\":%u,\"program\":%d,\"location\":%d,\"type\":%u,\"actual_bits\":[%u,%u],\"shader_default_float\":\"mediump\",\"varying\":\"mediump\",\"precision_capability\":[%d,%d,%d],\"operation_precision\":\"NOT_PROVEN\",\"sampler\":[%d,%d,%d,%d],\"source_texture\":%u,\"native_texture_dimensions\":[%u,%u],\"gl_level_dimensions\":\"NOT_PROVEN\"",g2.point,program,actual,GL_FLOAT_VEC2,bits[0],bits[1],range[0],range[1],precision,min,mag,wraps,wrapt,texture->tex,texture->wlr_texture.width,texture->wlr_texture.height);
fprintf(g2.meta,",\"qualifier_basis\":\"FROZEN_LINK_INPUT_SOURCE\",\"shader_source_sha256\":\"%s\"",g2.point<=3?"01664694e4201efb35ec698e89783e220ed930972b5396b86fa842814cd82d4d":"1db325237c9a6c70c2d97518e2f690d448cd006139e9f410cd16e097a1677ad1");
uint32_t radius_bits,proj_bits[9],tex_proj_bits[9];memcpy(&radius_bits,&radius,4);memcpy(proj_bits,proj,sizeof(proj));memcpy(tex_proj_bits,tex_proj,sizeof(tex_proj));
fprintf(g2.meta,",\"other_uniforms\":{\"radius_location\":%d,\"radius_bits\":%u,\"proj_location\":%d,\"proj_bits\":[",shader->radius,radius_bits,shader->proj);
for(unsigned i=0;i<9;i++)fprintf(g2.meta,"%s%u",i?",":"",proj_bits[i]);
fprintf(g2.meta,"],\"tex_proj_location\":%d,\"tex_proj_bits\":[",shader->tex_proj);
for(unsigned i=0;i<9;i++)fprintf(g2.meta,"%s%u",i?",":"",tex_proj_bits[i]);
fputs("]}",g2.meta);g2_state_json("draw_state",&draw_state);
fprintf(g2.meta,",\"blend_stencil\":[%u,%u],\"color_mask\":[%u,%u,%u,%u]",blend,stencil,color_mask[0],color_mask[1],color_mask[2],color_mask[3]);
g2_region(region);fputs("}\n",g2.meta);
}
#endif

View file

@ -0,0 +1,155 @@
--- a/render/fx_renderer/g2_gl.h
+++ b/render/fx_renderer/g2_gl.h
@@ -1,4 +1,4 @@
-/* G2 new source-only intrusive diagnostic. No production API. */
+/* G3 private admission delta; G2 hook symbols retained. No production API. */
#ifndef G2_GL_H
#define G2_GL_H
#include <EGL/egl.h>
@@ -18,8 +18,9 @@
};
struct g2_sample {
EGLContext context; EGLint client_version; EGLenum api;
- char version[160]; struct g2_state before, after;
- struct g2_errors pre, query, read, restore;
+ EGLDisplay display; char version[160], glsl[160]; GLint major, minor;
+ bool capability_queried, version_queried, state_saved, bound, read_attempted, restore_attempted; struct g2_state before, after;
+ struct g2_errors pre, version_errors, capability, state_errors, query, read, restore;
GLint object_type, object, level, width, height, format, channels[4];
GLint read_buffer, samples; GLenum status; size_t bytes; unsigned char *pixels;
bool success; const char *reason;
@@ -64,29 +65,44 @@
glPixelStorei(GL_PACK_SKIP_ROWS,s->skip_rows);
glPixelStorei(GL_PACK_SKIP_PIXELS,s->skip_pixels);
}
-/* Called with the actual current native target. ES2 is explicitly unsupported:
- * no ES3-only enum is ever queried before both EGL and GL attest ES3.
- * Only native RBO color attachments are accepted. Texture attachments retain
- * type/name/level, but safe dimensions on this pinned API are NOT_PROVEN.
- * Error queue consumption and synchronous readback cannot be restored. */
+/* WHY: EGL creation allows a newer compatible ES context (§3.7.1), while
+ * EGL's context query describes supported version (§3.7.4). Preserve any
+ * EGL/GL divergence as evidence; never relabel EGL as a creation request.
+ * CONTRACT: attest bounded GL_VERSION first, then safely query ES3 enums.
+ * INVARIANT: only ES 3.0..3.2 native RBO reads; no shader or error-queue neutrality. */
+static bool g2_version(const char *v, GLint *minor) {
+ if(!v || strnlen(v,160)>=160 || strncmp(v,"OpenGL ES 3.",12))return false;
+ if(v[12]<'0' || v[12]>'2' || (v[13] && v[13]!=' '))return false;
+ for(size_t i=13;v[i];i++)if((unsigned char)v[i]<32 || (unsigned char)v[i]>126)return false;
+ *minor=v[12]-'0';return true;
+}
static struct g2_sample g2_read(GLuint fbo,int width,int height,size_t used) {
- struct g2_sample s={.reason="NOT_PROVEN"}; bool saved=false, changed=false;
- s.context=eglGetCurrentContext(); s.api=eglQueryAPI();
- if(s.context==EGL_NO_CONTEXT || s.api!=EGL_OPENGL_ES_API) {s.reason="UNKNOWN_CONTEXT"; return s;}
- if(!eglQueryContext(eglGetCurrentDisplay(),s.context,EGL_CONTEXT_CLIENT_VERSION,&s.client_version)) {
+ struct g2_sample s={.reason="NOT_PROVEN"};
+ s.context=eglGetCurrentContext(); s.api=eglQueryAPI(); s.display=eglGetCurrentDisplay();
+ if(s.context==EGL_NO_CONTEXT || s.display==EGL_NO_DISPLAY || s.api!=EGL_OPENGL_ES_API) {s.reason="UNKNOWN_CONTEXT"; return s;}
+ if(!eglQueryContext(s.display,s.context,EGL_CONTEXT_CLIENT_VERSION,&s.client_version)) {
s.reason="EGL_QUERY_FAILED"; return s;
}
s.pre=g2_drain(); if(!g2_clean(s.pre)) {s.reason="PREEXISTING_GL_ERRORS"; return s;}
+ s.version_queried=true;
const char *v=(const char *)glGetString(GL_VERSION);
- if(v) snprintf(s.version,sizeof(s.version),"%s",v);
- s.query=g2_drain(); if(!g2_clean(s.query)) {s.reason="VERSION_QUERY_ERROR"; return s;}
- if(s.client_version<3 || !v || strncmp(v,"OpenGL ES 3.",12)!=0) {
+ if(v && strnlen(v,sizeof(s.version))<sizeof(s.version))snprintf(s.version,sizeof(s.version),"%s",v);
+ s.version_errors=g2_drain(); if(!g2_clean(s.version_errors)) {s.reason="VERSION_QUERY_ERROR"; return s;}
+ GLint parsed_minor=0;
+ if(s.client_version<2 || s.client_version>3 || !g2_version(v,&parsed_minor)) {
s.reason="GLES_STATE_NOT_SUPPORTED"; return s;
}
+ s.capability_queried=true;
+ glGetIntegerv(GL_MAJOR_VERSION,&s.major);glGetIntegerv(GL_MINOR_VERSION,&s.minor);
+ const char *glsl=(const char *)glGetString(GL_SHADING_LANGUAGE_VERSION);
+ if(glsl && strnlen(glsl,sizeof(s.glsl))<sizeof(s.glsl))snprintf(s.glsl,sizeof(s.glsl),"%s",glsl);
+ s.capability=g2_drain();
+ if(!g2_clean(s.capability)) {s.reason="CAPABILITY_QUERY_ERROR";return s;}
+ if(s.major!=3 || s.minor!=parsed_minor || !s.glsl[0]) {s.reason="VERSION_INCONSISTENT";return s;}
if(!g2_size(width,height,used,&s.bytes)) {s.reason="RAW_BUDGET_OR_OVERFLOW"; return s;}
- g2_state_get(&s.before); s.query=g2_drain();
- if(!g2_clean(s.query)) {s.reason="STATE_QUERY_ERROR"; return s;}
- saved=true; changed=true;
+ g2_state_get(&s.before); s.state_errors=g2_drain();
+ if(!g2_clean(s.state_errors)) {s.reason="STATE_QUERY_ERROR"; return s;}
+ s.state_saved=true; s.bound=true;
glBindFramebuffer(GL_READ_FRAMEBUFFER,fbo);
glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,
GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE,&s.object_type);
@@ -118,14 +134,16 @@
s.query=g2_drain(); if(!g2_clean(s.query)) {s.reason="PACK_QUERY_ERROR"; goto done;}
s.pixels=malloc(s.bytes); if(!s.pixels) {s.reason="ALLOCATION_FAILED"; goto done;}
/* glReadPixels returns void. NO alpha sentinel or file hash success gate. */
+ s.read_attempted=true;
glReadPixels(0,0,width,height,GL_RGBA,GL_UNSIGNED_BYTE,s.pixels);
s.read=g2_drain();
if(!g2_clean(s.read)) {s.reason="READ_GL_ERROR"; goto done;}
s.success=true; s.reason="READ_SUCCESS";
done:
- if(saved && changed) {
+ if(s.state_saved && s.bound) {
+ s.restore_attempted=true;
g2_state_restore(&s.before); g2_state_get(&s.after); s.restore=g2_drain();
- if(!g2_clean(s.restore) || memcmp(&s.before,&s.after,sizeof(s.before)) || eglGetCurrentContext()!=s.context) {
+ if(!g2_clean(s.restore) || memcmp(&s.before,&s.after,sizeof(s.before)) || eglGetCurrentContext()!=s.context || eglGetCurrentDisplay()!=s.display || eglQueryAPI()!=s.api) {
s.success=false; s.reason="RESTORE_FAILED";
}
}
--- a/render/fx_renderer/g2_native.h
+++ b/render/fx_renderer/g2_native.h
@@ -1,4 +1,4 @@
-/* PRIVATE G2. Included only by the new diagnostic fx_pass.c. */
+/* PRIVATE G3 admission; G2 symbols/hooks preserved. No public API. */
#ifndef G2_NATIVE_H
#define G2_NATIVE_H
#include "g2_gl.h"
@@ -10,7 +10,10 @@
#include <wlr/render/dmabuf.h>
#include <drm_fourcc.h>
#include <wlr/types/wlr_buffer.h>
-#define G2_ROOT "/var/tmp/swayfx-mosaic-corec-v2-i516p6po/gpu-work/acquisition"
+#ifndef G3_ROOT
+#error "Define G3_ROOT as the reviewed new LAB acquisition path"
+#endif
+#define G2_ROOT G3_ROOT
static struct {
bool stopped, active; unsigned epoch, point; size_t used;
int dir; FILE *meta; const void *pass, *output;
@@ -70,7 +73,7 @@
static bool g2_owned(int fd,mode_t mode) {struct stat s;return fstat(fd,&s)==0 && s.st_uid==geteuid() && (s.st_mode&0777)==mode;}
static bool g2_open(void) {
if(g2.dir>=0)return true;
- const char *d=getenv("MOSAIC_G2_DIR"),*nonce=getenv("MOSAIC_G2_NONCE"); char real[PATH_MAX];
+ const char *d=getenv("MOSAIC_G3_DIR"),*nonce=getenv("MOSAIC_G3_NONCE"); char real[PATH_MAX];
if(!d || !nonce)return false;
if(strlen(nonce)!=64 || !realpath(d,real) || strcmp(real,G2_ROOT) || strcmp(d,G2_ROOT)) {g2.stopped=true;return false;}
for(unsigned i=0;i<64;i++)if(!((nonce[i]>='0'&&nonce[i]<='9')||(nonce[i]>='a'&&nonce[i]<='f'))) {g2.stopped=true;return false;}
@@ -96,10 +99,15 @@
if(!g2_dmabuf_supported(b)) {g2_fail("DMABUF_METADATA_UNSUPPORTED");return;}
struct g2_sample s=g2_read(b->fbo,b->buffer->width,b->buffer->height,g2.used);
fprintf(g2.meta,"{\"kind\":\"sample\",\"epoch\":%u,\"pass_sequence\":%"PRIuPTR",\"point\":%u,\"stage\":\"%s\",\"context\":\"%"PRIxPTR"\",\"api\":%u,\"client_version\":%d,\"result\":\"%s\",\"bytes\":%zu,\"attachment\":[%d,%d,%d,%d,%d,%d],\"channels\":[%d,%d,%d,%d],\"status\":%u",g2.epoch,g2.pass_sequence,point,g2_names[point],(uintptr_t)s.context,s.api,s.client_version,s.reason,s.bytes,s.object_type,s.object,s.level,s.width,s.height,s.format,s.channels[0],s.channels[1],s.channels[2],s.channels[3],s.status);
- fprintf(g2.meta,",\"read_buffer\":%d,\"samples\":%d",s.read_buffer,s.samples);
+ fprintf(g2.meta,",\"read_success\":%s,\"read_buffer\":%d,\"samples\":%d",s.success?"true":"false",s.read_buffer,s.samples);
fputs(",\"gl_version_hex\":\"",g2.meta);for(size_t i=0;i<strlen(s.version);i++)fprintf(g2.meta,"%02x",(unsigned char)s.version[i]);fputc('"',g2.meta);
g2_error_json("pre_errors",s.pre);g2_error_json("query_errors",s.query);g2_error_json("read_errors",s.read);g2_error_json("restore_errors",s.restore);
- g2_state_json("before",&s.before);g2_state_json("after",&s.after);g2_native_object(b);g2_region(region);
+ fprintf(g2.meta,",\"version_queried\":%s,\"state_saved\":%s,\"bound\":%s,\"read_attempted\":%s,\"restore_attempted\":%s",s.version_queried?"true":"false",s.state_saved?"true":"false",s.bound?"true":"false",s.read_attempted?"true":"false",s.restore_attempted?"true":"false");
+ if(s.capability_queried)fprintf(g2.meta,",\"gl_major\":%d,\"gl_minor\":%d",s.major,s.minor);else fputs(",\"gl_major\":null,\"gl_minor\":null",g2.meta);
+ fputs(",\"glsl_hex\":\"",g2.meta);for(size_t i=0;i<strlen(s.glsl);i++)fprintf(g2.meta,"%02x",(unsigned char)s.glsl[i]);fputc('"',g2.meta);
+ g2_error_json("version_errors",s.version_errors);g2_error_json("capability_errors",s.capability);g2_error_json("state_errors",s.state_errors);
+ if(s.state_saved)g2_state_json("before",&s.before);else fputs(",\"before\":null",g2.meta);
+ if(s.restore_attempted)g2_state_json("after",&s.after);else fputs(",\"after\":null",g2.meta);g2_native_object(b);g2_region(region);
if(!s.success) {fputs("}\n",g2.meta);free(s.pixels);g2_fail(s.reason);return;}
char name[64];snprintf(name,sizeof(name),"epoch-%u-point-%u.rgba",g2.epoch,point);
int fd=openat(g2.dir,name,O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW|O_CLOEXEC,0600);
@@ -171,7 +179,7 @@
g2.epoch=e;g2.point=0;g2.pass=pass;g2.pass_sequence++;g2.active=true;
snprintf(meta,sizeof(meta),"epoch-%u.jsonl",e);fd=openat(g2.dir,meta,O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW|O_CLOEXEC,0600);
if(fd<0 || !(g2.meta=fdopen(fd,"w"))) {if(fd>=0)close(fd);g2.stopped=true;g2.active=false;return;}
- fprintf(g2.meta,"{\"kind\":\"begin\",\"schema\":\"G2-new-v1\",\"epoch\":%u,\"label\":\"%s\",\"nonce\":\"%s\",\"output\":\"HEADLESS-1\",\"pass\":\"%"PRIxPTR"\",\"offscreen_output_key\":\"%"PRIxPTR"\",\"intrusive_error_queue\":true,\"error_queue_restorable\":false,\"readback_may_synchronize\":true,\"rows\":\"GL_LOW_TO_HIGH\",\"present_buffer\":\"NOT_PROVEN\",\"gpu_completion\":\"NOT_PROVEN\",\"lifetime\":\"NOT_PROVEN\"}\n",e,e==1?"A":e==2?"B":"A-return",g2.nonce,(uintptr_t)pass,(uintptr_t)pass->fx_offscreen_buffers);
+ fprintf(g2.meta,"{\"kind\":\"begin\",\"schema\":\"G3-followup-v1\",\"epoch\":%u,\"label\":\"%s\",\"nonce\":\"%s\",\"output\":\"HEADLESS-1\",\"pass\":\"%"PRIxPTR"\",\"offscreen_output_key\":\"%"PRIxPTR"\",\"intrusive_error_queue\":true,\"error_queue_restorable\":false,\"readback_may_synchronize\":true,\"rows\":\"GL_LOW_TO_HIGH\",\"present_buffer\":\"NOT_PROVEN\",\"gpu_completion\":\"NOT_PROVEN\",\"lifetime\":\"NOT_PROVEN\"}\n",e,e==1?"A":e==2?"B":"A-return",g2.nonce,(uintptr_t)pass,(uintptr_t)pass->fx_offscreen_buffers);
if(options->blur_data->num_passes!=3 || options->blur_strength!=1.0f || blur_data_should_parameters_blur_effects(options->blur_data)) {g2_fail("NON_PROSPECTIVE_BLUR_CONFIGURATION");return;}
g2_sample_native(pass,source,0,options->tex_options.base.clip);
}

View file

@ -0,0 +1,98 @@
"""G3 protocol tooling; never spawns compositor/client/GPU or grants review.
prepare-plan prints prospective framing only. arm requires an external accepted
review and runtime authorization. No retry is implemented. All files exclusive.
"""
import argparse,hashlib,json,os,stat,zlib,re
from pathlib import Path
from validate_source import need,metadata,halfpixel
CAP=256*1024*1024
FRAME=(800,600)
STAGES=['source','down0','down1','down2','up2','up1','up0','optimized-cache','no-blur-cache','composition-presubmit']
ROOT=Path(os.environ.get('MOSAIC_G3_DIR','/G3_ROOT_NOT_BOUND'))
def owned(p,mode):
s=p.lstat();need(not stat.S_ISLNK(s.st_mode) and s.st_uid==os.geteuid() and stat.S_IMODE(s.st_mode)==mode,'owned path/mode')
return s
def bounded(p,cap):
s=owned(p,0o600);need(stat.S_ISREG(s.st_mode) and s.st_size<=cap and s.st_nlink==1,'bounded regular exclusive file')
fd=os.open(p,os.O_RDONLY|os.O_NOFOLLOW|os.O_CLOEXEC)
try:
after=os.fstat(fd);need((s.st_dev,s.st_ino)==(after.st_dev,after.st_ino),'path identity')
data=os.read(fd,cap+1);need(len(data)==s.st_size and len(data)<=cap,'bounded stable framing');return data
finally:os.close(fd)
def exclusive(p,b):
fd=os.open(p,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW|os.O_CLOEXEC,0o600)
try:
with os.fdopen(fd,'wb',closefd=False) as f:f.write(b);f.flush();os.fsync(fd)
finally:os.close(fd)
d=os.open(p.parent,os.O_RDONLY|os.O_DIRECTORY|os.O_NOFOLLOW)
try:os.fsync(d)
finally:os.close(d)
def epoch(directory,e,nonce,prior=0):
need(e in (1,2,3),'epoch')
raw=bounded(directory/f'epoch-{e}.jsonl',1024*1024);need(raw.endswith(b'\n'),'metadata line framing')
rows=[json.loads(x) for x in raw.splitlines()];need(len(rows)==18,'bounded exact event count')
begin,end=rows[0],rows[-1]
need(begin['kind']=='begin' and begin['schema']=='G3-followup-v1' and begin['epoch']==e and begin['nonce']==nonce and begin['label']==('A','B','A-return')[e-1],'begin identity')
need(begin['output']=='HEADLESS-1' and begin['intrusive_error_queue'] is True and begin['error_queue_restorable'] is False,'intrusion/output')
need(end['kind']=='end' and end['epoch']==e and end['points']==10 and end['accepted'] is True,'complete accepted epoch; rejection or incomplete A stops all')
# Consumed files intentionally had one hard link during atomic consumption;
# publication is accepted only after arm unlink gives a single link again.
need(bounded(directory/f'epoch-{e}.consumed',80)==f'{nonce} {e}\n'.encode(),'consumed token')
samples=[x for x in rows if x['kind']=='sample'];uniforms=[x for x in rows if x['kind']=='uniform']
need(len(samples)==10 and len(uniforms)==6,'stage/uniform count')
need([x['point'] for x in samples]==list(range(10)) and [x['point'] for x in uniforms]==list(range(1,7)),'strict point order')
need([x['kind'] for x in rows[1:-1]]==['sample']+['uniform','sample']*6+['sample']*3,'query/draw/read sequence')
total=prior;context=samples[0]['context'];source_analysis=[]
for p,s in enumerate(samples):
need(s['point']==p and s['stage']==STAGES[p] and s['epoch']==e and s['pass_sequence']==e and s['context']==context and context!='0','pass/context identity')
need(s['result']=='READ_SUCCESS' and s['read_success'] is True and s['published'] is True and s['before']==s['after'],'GL/read/state acceptance')
for key in ('pre_errors','query_errors','read_errors','restore_errors'):need(s[key]=={'exhausted':False,'codes':[]},'error queue ambiguity')
need(s['client_version'] in (2,3) and re.fullmatch(rb'OpenGL ES 3\.([0-2])(?: [ -~]*)?',bytes.fromhex(s['gl_version_hex'])) is not None,'bounded actual GLES3 version')
need(s['gl_major']==3 and s['gl_minor']==int(bytes.fromhex(s['gl_version_hex'])[12:13]) and bool(bytes.fromhex(s['glsl_hex'])),'consistent native GL version/GLSL')
need(all(s.get(k) is True for k in ('version_queried','state_saved','bound','read_attempted','restore_attempted')),'observable exact read lifecycle')
for key in ('version_errors','capability_errors','state_errors'):need(s[key]=={'exhausted':False,'codes':[]},'capability/state error attribution')
a=s['attachment'];need(a[0]==0x8d41 and a[1]>0 and a[2]==0 and a[3:5]==s['native']['dimensions'] and s['status']==0x8cd5 and s['read_buffer']==0x8ce0 and s['samples']==0,'supported RBO attachment')
need(s['channels'][:3]==[8,8,8] and s['channels'][3] in (0,8),'actual alpha storage')
native=s['native'];need(native['fbo_rbo_tex'][1]==a[1] and all(int(native[key],16)>0 for key in ('fx','wlr','image')),'native attachment identity')
d=s['dmabuf'];need(d is not None and d['borrowed'] is True and d['dimensions']==native['dimensions'],'actual borrowed dmabuf')
metadata(dict(d,width=d['dimensions'][0],height=d['dimensions'][1]))
need(all(p.get('fstat_ok') is True and all(type(p.get(k)) is int and p[k]>=0 for k in ('fd','dev','ino')) for p in d['planes']),'borrowed FD fstat identity observed')
w,h=native['dimensions'];need((w,h)==FRAME,'prospective physical full-frame dimensions');n=w*h*4;need(0<n<=CAP-total and s['bytes']==n,'checked full-frame raw budget');total+=n
need(s['file']==f'epoch-{e}-point-{p}.rgba','fixed exclusive raw filename')
b=bounded(directory/s['file'],CAP);need(len(b)==n and f'{zlib.crc32(b):08x}'==s['crc32'],'raw framing/CRC')
if p==0:
colors=((32,192,224),(176,32,64)) if e==2 else ((245,245,245),(15,25,45))
for y in range(h):
for x in range(w):need(tuple(b[(y*w+x)*4:(y*w+x)*4+3])==colors[(x//8)%2],'native RGB source does not belong to current epoch')
need(s.get('fresh_region_overflow') is not True and len(s['fresh_rectangles'])<=256 and all(len(r)==4 and 0<=r[0]<=r[2]<=w and 0<=r[1]<=r[3]<=h for r in s['fresh_rectangles']),'actual fresh region rectangles')
for u in uniforms:
need(u['type']==0x8b50 and u['program']>0 and u['location']>=0 and u['shader_default_float']=='mediump' and u['varying']=='mediump','uniform native program/type/qualifier')
need(u['sampler']==[9729,9729,33071,33071] and len(u['precision_capability'])==3,'actual sampler/capability')
source_analysis.append(halfpixel(*u['native_texture_dimensions'],u['point']<=3,u['actual_bits']))
need(end['raw_total']==total,'cumulative raw budget')
return {'epoch':e,'raw_total':total,'context':context,'observed_versions':[dict(egl_client_version=s['client_version'],gl_version=bytes.fromhex(s['gl_version_hex']).decode('ascii'),gl_major=s['gl_major'],gl_minor=s['gl_minor'],egl_gl_divergence=s['client_version']!=s['gl_major']) for s in samples],'uniform_comparison':source_analysis,'whole_field_causality':'NOT_PROVEN','native_present_buffer':'NOT_PROVEN','gpu_completion':'NOT_PROVEN'}
def gate(directory,gatefile,binary):
need(directory==ROOT and directory.resolve(strict=True)==ROOT,'fixed real acquisition path')
need(str(ROOT).startswith('/var/tmp/swayfx-mosaic-step20c-native-closure-') and ROOT.name=='acquisition','new LAB acquisition only')
owned(directory,0o700)
g=json.loads(bounded(gatefile,65536));need(g['source_review']=='ACCEPTED' and g['gl_observability']=='PASS' and g['runtime_authorized'] is True and g['diagnostic']=='GPU-G3' and g['acquisition_root']==str(ROOT),'independent external review/authorization gate')
need(hashlib.sha256(binary.read_bytes()).hexdigest()==g['binary_sha256'],'distinct reviewed binary identity')
return g
def arm(directory,e,nonce,gatefile,binary):
gate(directory,gatefile,binary);need(len(nonce)==64 and all(x in '0123456789abcdef' for x in nonce),'nonce')
# Prior complete success is mandatory. An existing consumed/current epoch,
# failed A, or any unexpected future epoch prevents every subsequent arm.
total=0
for prior in range(1,e):total=epoch(directory,prior,nonce,total)['raw_total']
need(e in (1,2,3),'one-shot three epochs')
for future in range(e,5):need(not any(directory.glob(f'epoch-{future}.*')),'no retry/future token')
exclusive(directory/f'epoch-{e}.arm',f'{nonce} {e}\n'.encode())
def plan():
return {'schema':'G3-followup-v1','status':'EXPERIMENTAL_SOURCE_ONLY_REVIEW_REQUIRED','diagnostic':'GPU-G3','max_raw_bytes':CAP,'epochs':['A','B','A-return'],'points':STAGES,'full_field':[0,0,800,600],'ROI':[0,0,800,600],'ROI_policy':'FULL_FIELD_NO_POSTHOC_MASK','requirements':['Independent R-GPU exact-source acceptance','GL observability acceptance','Explicit new pair and distinct binary identity','External one-shot ledger reservation before compositor spawn','Explicit source A/B/A client commands with original A byte identity','If A incomplete/rejected STOP; preserve bytes, no B, no retry'],'intrusive':['glGetError consumes nonrestorable queue','glReadPixels may synchronize timing'],'unobserved':['native presented physical buffer','GPU completion','GPU object lifetime','atomic generation'],'launches_gpu':False}
if __name__=='__main__':
p=argparse.ArgumentParser();p.add_argument('operation',choices=['prepare-plan','verify-epoch','arm']);p.add_argument('--epoch',type=int);p.add_argument('--nonce');p.add_argument('--gate',type=Path);p.add_argument('--binary',type=Path);a=p.parse_args()
if a.operation=='prepare-plan':print(json.dumps(plan(),indent=2))
elif a.operation=='verify-epoch':print(json.dumps(epoch(ROOT,a.epoch,a.nonce),indent=2))
else:arm(ROOT,a.epoch,a.nonce,a.gate,a.binary)

View file

@ -0,0 +1,390 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="wlr_layer_shell_unstable_v1">
<copyright>
Copyright © 2017 Drew DeVault
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
</copyright>
<interface name="zwlr_layer_shell_v1" version="4">
<description summary="create surfaces that are layers of the desktop">
Clients can use this interface to assign the surface_layer role to
wl_surfaces. Such surfaces are assigned to a "layer" of the output and
rendered with a defined z-depth respective to each other. They may also be
anchored to the edges and corners of a screen and specify input handling
semantics. This interface should be suitable for the implementation of
many desktop shell components, and a broad number of other applications
that interact with the desktop.
</description>
<request name="get_layer_surface">
<description summary="create a layer_surface from a surface">
Create a layer surface for an existing surface. This assigns the role of
layer_surface, or raises a protocol error if another role is already
assigned.
Creating a layer 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 layer_surface.configure call
must also be treated as errors.
After creating a layer_surface object and setting it up, the client
must perform an initial commit without any buffer attached.
The compositor will reply with a layer_surface.configure event.
The client must acknowledge it and is then allowed to attach a buffer
to map the surface.
You may pass NULL for output to allow the compositor to decide which
output to use. Generally this will be the one that the user most
recently interacted with.
Clients can specify a namespace that defines the purpose of the layer
surface.
</description>
<arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/>
<arg name="surface" type="object" interface="wl_surface"/>
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
<arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/>
<arg name="namespace" type="string" summary="namespace for the layer surface"/>
</request>
<enum name="error">
<entry name="role" value="0" summary="wl_surface has another role"/>
<entry name="invalid_layer" value="1" summary="layer value is invalid"/>
<entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/>
</enum>
<enum name="layer">
<description summary="available layers for surfaces">
These values indicate which layers a surface can be rendered in. They
are ordered by z depth, bottom-most first. Traditional shell surfaces
will typically be rendered between the bottom and top layers.
Fullscreen shell surfaces are typically rendered at the top layer.
Multiple surfaces can share a single layer, and ordering within a
single layer is undefined.
</description>
<entry name="background" value="0"/>
<entry name="bottom" value="1"/>
<entry name="top" value="2"/>
<entry name="overlay" value="3"/>
</enum>
<!-- Version 3 additions -->
<request name="destroy" type="destructor" since="3">
<description summary="destroy the layer_shell object">
This request indicates that the client will not use the layer_shell
object any more. Objects that have been created through this instance
are not affected.
</description>
</request>
</interface>
<interface name="zwlr_layer_surface_v1" version="4">
<description summary="layer metadata interface">
An interface that may be implemented by a wl_surface, for surfaces that
are designed to be rendered as a layer of a stacked desktop-like
environment.
Layer surface state (layer, size, anchor, exclusive zone,
margin, interactivity) is double-buffered, and will be applied at the
time wl_surface.commit of the corresponding wl_surface is called.
Attaching a null buffer to a layer surface unmaps it.
Unmapping a layer_surface means that the surface cannot be shown by the
compositor until it is explicitly mapped again. The layer_surface
returns to the state it had right after layer_shell.get_layer_surface.
The client can re-map the surface by performing a commit without any
buffer attached, waiting for a configure event and handling it as usual.
</description>
<request name="set_size">
<description summary="sets the size of the surface">
Sets the size of the surface in surface-local coordinates. The
compositor will display the surface centered with respect to its
anchors.
If you pass 0 for either value, the compositor will assign it and
inform you of the assignment in the configure event. You must set your
anchor to opposite edges in the dimensions you omit; not doing so is a
protocol error. Both values are 0 by default.
Size is double-buffered, see wl_surface.commit.
</description>
<arg name="width" type="uint"/>
<arg name="height" type="uint"/>
</request>
<request name="set_anchor">
<description summary="configures the anchor point of the surface">
Requests that the compositor anchor the surface to the specified edges
and corners. If two orthogonal edges are specified (e.g. 'top' and
'left'), then the anchor point will be the intersection of the edges
(e.g. the top left corner of the output); otherwise the anchor point
will be centered on that edge, or in the center if none is specified.
Anchor is double-buffered, see wl_surface.commit.
</description>
<arg name="anchor" type="uint" enum="anchor"/>
</request>
<request name="set_exclusive_zone">
<description summary="configures the exclusive geometry of this surface">
Requests that the compositor avoids occluding an area with other
surfaces. The compositor's use of this information is
implementation-dependent - do not assume that this region will not
actually be occluded.
A positive value is only meaningful if the surface is anchored to one
edge or an edge and both perpendicular edges. If the surface is not
anchored, anchored to only two perpendicular edges (a corner), anchored
to only two parallel edges or anchored to all edges, a positive value
will be treated the same as zero.
A positive zone is the distance from the edge in surface-local
coordinates to consider exclusive.
Surfaces that do not wish to have an exclusive zone may instead specify
how they should interact with surfaces that do. If set to zero, the
surface indicates that it would like to be moved to avoid occluding
surfaces with a positive exclusive zone. If set to -1, the surface
indicates that it would not like to be moved to accommodate for other
surfaces, and the compositor should extend it all the way to the edges
it is anchored to.
For example, a panel might set its exclusive zone to 10, so that
maximized shell surfaces are not shown on top of it. A notification
might set its exclusive zone to 0, so that it is moved to avoid
occluding the panel, but shell surfaces are shown underneath it. A
wallpaper or lock screen might set their exclusive zone to -1, so that
they stretch below or over the panel.
The default value is 0.
Exclusive zone is double-buffered, see wl_surface.commit.
</description>
<arg name="zone" type="int"/>
</request>
<request name="set_margin">
<description summary="sets a margin from the anchor point">
Requests that the surface be placed some distance away from the anchor
point on the output, in surface-local coordinates. Setting this value
for edges you are not anchored to has no effect.
The exclusive zone includes the margin.
Margin is double-buffered, see wl_surface.commit.
</description>
<arg name="top" type="int"/>
<arg name="right" type="int"/>
<arg name="bottom" type="int"/>
<arg name="left" type="int"/>
</request>
<enum name="keyboard_interactivity">
<description summary="types of keyboard interaction possible for a layer shell surface">
Types of keyboard interaction possible for layer shell surfaces. The
rationale for this is twofold: (1) some applications are not interested
in keyboard events and not allowing them to be focused can improve the
desktop experience; (2) some applications will want to take exclusive
keyboard focus.
</description>
<entry name="none" value="0">
<description summary="no keyboard focus is possible">
This value indicates that this surface is not interested in keyboard
events and the compositor should never assign it the keyboard focus.
This is the default value, set for newly created layer shell surfaces.
This is useful for e.g. desktop widgets that display information or
only have interaction with non-keyboard input devices.
</description>
</entry>
<entry name="exclusive" value="1">
<description summary="request exclusive keyboard focus">
Request exclusive keyboard focus if this surface is above the shell surface layer.
For the top and overlay layers, the seat will always give
exclusive keyboard focus to the top-most layer which has keyboard
interactivity set to exclusive. If this layer contains multiple
surfaces with keyboard interactivity set to exclusive, the compositor
determines the one receiving keyboard events in an implementation-
defined manner. In this case, no guarantee is made when this surface
will receive keyboard focus (if ever).
For the bottom and background layers, the compositor is allowed to use
normal focus semantics.
This setting is mainly intended for applications that need to ensure
they receive all keyboard events, such as a lock screen or a password
prompt.
</description>
</entry>
<entry name="on_demand" value="2" since="4">
<description summary="request regular keyboard focus semantics">
This requests the compositor to allow this surface to be focused and
unfocused by the user in an implementation-defined manner. The user
should be able to unfocus this surface even regardless of the layer
it is on.
Typically, the compositor will want to use its normal mechanism to
manage keyboard focus between layer shell surfaces with this setting
and regular toplevels on the desktop layer (e.g. click to focus).
Nevertheless, it is possible for a compositor to require a special
interaction to focus or unfocus layer shell surfaces (e.g. requiring
a click even if focus follows the mouse normally, or providing a
keybinding to switch focus between layers).
This setting is mainly intended for desktop shell components (e.g.
panels) that allow keyboard interaction. Using this option can allow
implementing a desktop shell that can be fully usable without the
mouse.
</description>
</entry>
</enum>
<request name="set_keyboard_interactivity">
<description summary="requests keyboard events">
Set how keyboard events are delivered to this surface. By default,
layer shell surfaces do not receive keyboard events; this request can
be used to change this.
This setting is inherited by child surfaces set by the get_popup
request.
Layer surfaces receive pointer, touch, and tablet events normally. If
you do not want to receive them, set the input region on your surface
to an empty region.
Keyboard interactivity is double-buffered, see wl_surface.commit.
</description>
<arg name="keyboard_interactivity" type="uint" enum="keyboard_interactivity"/>
</request>
<request name="get_popup">
<description summary="assign this layer_surface as an xdg_popup parent">
This assigns an xdg_popup's parent to this layer_surface. This popup
should have been created via xdg_surface::get_popup with the parent set
to NULL, and this request must be invoked before committing the popup's
initial state.
See the documentation of xdg_popup for more details about what an
xdg_popup is and how it is used.
</description>
<arg name="popup" type="object" interface="xdg_popup"/>
</request>
<request name="ack_configure">
<description summary="ack a configure event">
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.
If the client receives multiple configure events before it
can respond to one, it only has to ack the last configure event.
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.
</description>
<arg name="serial" type="uint" summary="the serial from the configure event"/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy the layer_surface">
This request destroys the layer surface.
</description>
</request>
<event name="configure">
<description summary="suggest a surface change">
The configure event asks the client to resize its surface.
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.
The client is free to dismiss all but the last configure event it
received.
The width and height arguments specify the size of the window in
surface-local coordinates.
The size is a hint, in the sense that the client is free to ignore it if
it doesn't resize, pick a smaller size (to satisfy aspect ratio or
resize in steps of NxM pixels). If the client picks a smaller size and
is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the
surface will be centered on this axis.
If the width or height arguments are zero, it means the client should
decide its own window dimension.
</description>
<arg name="serial" type="uint"/>
<arg name="width" type="uint"/>
<arg name="height" type="uint"/>
</event>
<event name="closed">
<description summary="surface should be closed">
The closed event is sent by the compositor when the surface will no
longer be shown. The output may have been destroyed or the user may
have asked for it to be removed. Further changes to the surface will be
ignored. The client should destroy the resource after receiving this
event, and create a new surface if they so choose.
</description>
</event>
<enum name="error">
<entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/>
<entry name="invalid_size" value="1" summary="size is invalid"/>
<entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/>
<entry name="invalid_keyboard_interactivity" value="3" summary="keyboard interactivity is invalid"/>
</enum>
<enum name="anchor" bitfield="true">
<entry name="top" value="1" summary="the top edge of the anchor rectangle"/>
<entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/>
<entry name="left" value="4" summary="the left edge of the anchor rectangle"/>
<entry name="right" value="8" summary="the right edge of the anchor rectangle"/>
</enum>
<!-- Version 2 additions -->
<request name="set_layer" since="2">
<description summary="change the layer of the surface">
Change the layer that the surface is rendered on.
Layer is double-buffered, see wl_surface.commit.
</description>
<arg name="layer" type="uint" enum="zwlr_layer_shell_v1.layer" summary="layer to move this surface to"/>
</request>
</interface>
</protocol>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,73 @@
"""Assemble private G3 sources, never build or launch a compositor."""
import argparse
import ctypes
import hashlib
import json
import os
from pathlib import Path
import resource
import shutil
import stat
import subprocess
KIT = Path(__file__).resolve().parent
PARENT = KIT.parents[1] / 'step20c-corec-v2'
PATCH_SHA = '77d7445ceaf643fd5cc708d31c3f2c77feaeb80f8130c419c55e5514ecc8a75d'
def sha(p):
return hashlib.sha256(p.read_bytes()).hexdigest()
def tree(p):
return {str(f.relative_to(p)): {'sha256': sha(f), 'mode': oct(stat.S_IMODE(f.stat().st_mode))}
for f in sorted(p.rglob('*')) if f.is_file() and '.git' not in f.relative_to(p).parts}
def main():
resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
assert ctypes.CDLL(None).prctl(4, 0, 0, 0, 0) == 0
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--lab', type=Path, required=True)
parser.add_argument('--core', type=Path, required=True)
parser.add_argument('--destination', type=Path, required=True)
a = parser.parse_args()
lab, core, dest = a.lab.resolve(strict=True), a.core.resolve(strict=True), a.destination.resolve()
assert str(lab).startswith('/var/tmp/swayfx-mosaic-step20c-native-closure-')
assert lab.stat().st_uid == os.geteuid() and stat.S_IMODE(lab.stat().st_mode) == 0o700
assert dest.is_relative_to(lab) and not dest.exists() and not dest.is_relative_to(core)
before = tree(core)
expected = json.loads((PARENT / 'provenance.json').read_text())['components']['scene']['files']
assert before == expected, 'complete CORE bytes/modes differ from versioned provenance'
patch = PARENT / 'gpu/g2-native.patch'
assert sha(patch) == PATCH_SHA
shutil.copytree(core, dest, ignore=shutil.ignore_patterns('.git'))
result = subprocess.run(['patch', '--batch', '--forward', '-p1', '-i', str(patch)],
cwd=dest, capture_output=True, text=True)
assert result.returncode == 0, result.stdout + result.stderr
# WHY: hook placement is frozen G2 instrumentation; only admission and
# observability headers differ. CONTRACT: no shader or CORE source mutation.
native = dest / 'render/fx_renderer'
delta = KIT / 'g3-admission.patch'
result = subprocess.run(['patch', '--batch', '--forward', '-p1', '-i', str(delta)],
cwd=dest, capture_output=True, text=True)
assert result.returncode == 0, result.stdout + result.stderr
for name in ('g2_gl.h', 'g2_native.h'):
assert sha(KIT / name) == sha(native / name), 'delta/header mismatch'
assert sha(native / 'fx_pass.c') == 'c2886ef259fc91431dfe97841387082420ab80a0fe2f07e7dcdb71fc95731181'
after = tree(dest)
changed = sorted(n for n in before.keys() | after.keys() if before.get(n) != after.get(n))
assert changed == ['render/fx_renderer/fx_pass.c', 'render/fx_renderer/g2_gl.h', 'render/fx_renderer/g2_native.h']
assert tree(core) == before
receipt = {'schema': 'G3-source-assembly-v1', 'status': 'EXPERIMENTAL_SOURCE_ONLY',
'review': 'PENDING_INDEPENDENT', 'runtime': 'NOT_RUN', 'build': 'NOT_RUN',
'core_manifest_sha256': sha(PARENT / 'provenance.json'),
'g2_patch_sha256': sha(patch), 'g3_delta_sha256': sha(delta), 'changed': {n: after[n] for n in changed},
'core_unchanged': True, 'acquisition_root': str(lab / 'gpu-work/acquisition'),
'G3_ROOT_compile_definition_required': True}
(dest.parent / (dest.name + '-receipt.json')).write_text(json.dumps(receipt, indent=2) + '\n')
print(json.dumps(receipt, indent=2))
if __name__ == '__main__':
main()

View file

@ -0,0 +1,68 @@
"""Executable G3 scenario adapter. Import has NO run/arm/spawn side effect.
The parent binds an accepted new diagnostic pair Session and reserves its own G3 ledger.
The common Session owns its private sockets, process groups and timeout cleanup.
"""
import hashlib,json,time
from pathlib import Path
from PIL import ImageChops
from protocol import ROOT,arm,epoch,gate,need
def exact_rgb(a,b):
need(a.mode==b.mode=='RGB' and a.size==b.size,'whole-output homologous RGB')
d=ImageChops.difference(a,b)
# Exact all-output zero tolerance. This is a result, not an admission mask.
return {'equal':d.getbbox() is None,'bbox':list(d.getbbox()) if d.getbbox() else None,'extrema':[list(x) for x in d.getextrema()],'pixels':a.width*a.height,'difference_count':sum(any(p) for p in d.getdata()),'differences_per_row':[sum(any(d.getpixel((x,y))) for x in range(a.width)) for y in range(a.height)],'tolerance':0}
def run(s,binding):
"""Must be called once AFTER external review+ledger binding, BEFORE s.start.
binding: gate_file, binary, client, nonce, source_manifest_sha256,
G3_ledger_reserved=True, reviewed_scene_sha256. All operations debit s.ledger.
No retry path; an exception before epoch A completes bypasses B/A-return.
"""
need(binding.get('G3_ledger_reserved') is True,'external closed-ledger reservation absent')
need(s.spec['id']=='GPU-G3' and s.spec['binary']=='gpu','distinct G3 Session identity/static pair')
need(s.auth['binary']==binding['binary'],'accepted exact third executable')
need(s.auth['manifest_sha256']==binding['source_manifest_sha256'],'frozen G3 manifest identity')
gate(ROOT,Path(binding['gate_file']),Path(binding['binary']))
need(not any(ROOT.glob('epoch-*')),'fresh one-shot publication directory')
nonce=binding['nonce'];s.env.update(MOSAIC_G3_DIR=str(ROOT),MOSAIC_G3_NONCE=nonce,WLR_RENDERER='gles2')
s.start()
# Session captures actual executable hash, mapped libraries, renderer and
# explicit private sockets. Its caller owns finally/cleanup and verdict.
client=Path(binding['client']);need(client.is_file() and hashlib.sha256(client.read_bytes()).hexdigest()==binding['client_sha256'],'new client identity')
s.cmd('workspace 2');s.spawn('g2-reference',[client,'reference']);s.wait(lambda:s.find('g2-reference'));rid=s.find('g2-reference')['id'];s.cmd(f'[con_id={rid}] border none')
s.cmd('workspace 1');s.cmd('layout mosaic');s.cmd('gaps inner 0')
s.spawn('g2-background',[client,'background']);s.wait(lambda:'BACKGROUND_CONFIGURED ' in s.log('g2-background'))
s.spawn('g2-glass',[client,'glass']);s.wait(lambda:s.find('g2-glass'));gid=s.find('g2-glass')['id'];s.cmd(f'[con_id={gid}] border none')
s.cmd('blur enable');s.cmd('blur_xray disable');s.cmd('blur_passes 3');s.cmd('blur_radius 5');s.cmd('blur_brightness 1');s.cmd('blur_contrast 1');s.cmd('blur_noise 0');s.cmd('blur_saturation 1')
# Settle setup before the first arm. This sleep carries no proof of GPU
# completion; exact native source admission rejects any wrong candidate.
time.sleep(.35)
reference=s.capture('G3-H2-baseline1','HEADLESS-2');time.sleep(.15)
reference2=s.capture('G3-H2-baseline2','HEADLESS-2')
baseline=exact_rgb(reference,reference2);s.save('G3-H2-baseline',baseline)
need(baseline['equal'],'static whole H2 baseline changed before A')
captures=[];validated=[];total=0
for e,command in enumerate(('G3-A','G3-B','G3-A-return'),1):
s.debit('mutation');s.record('G3-epoch-reservation',epoch=e,raw_cap=256*1024*1024)
arm(ROOT,e,nonce,Path(binding['gate_file']),Path(binding['binary']))
s.proc['g2-background'].stdin.write(command+'\n');s.proc['g2-background'].stdin.flush()
s.wait(lambda:f'G3_SOURCE_BYTES command={command} ' in s.log('g2-background'))
path=ROOT/f'epoch-{e}.jsonl'
def complete_or_rejected():
if not path.exists():return False
data=path.read_bytes();need(len(data)<=1024*1024,'bounded metadata while waiting')
if b'"kind":"rejected"' in data:raise RuntimeError(f'epoch {e} rejected; STOP, no next command/retry')
return data.endswith(b'\n') and b'"kind":"end"' in data
s.wait(complete_or_rejected)
# Failure here exits before subsequent epoch token or source command.
result=epoch(ROOT,e,nonce,total);total=result['raw_total'];validated.append(result)
pictures=s.pair_capture(f'G3-{command}');captures.append(pictures);s.snapshot(f'G3-{command}')
h2=exact_rgb(reference,pictures[1]);s.save(f'G3-{command}-wholeH2',h2)
need(h2['equal'],'whole H2 changed: rejected epoch, STOP without retry')
s.record('G3-epoch-accepted',epoch=e,command=command,native_raw_total=total)
whole_h1=exact_rgb(captures[0][0],captures[2][0]);whole_h2=[exact_rgb(captures[0][1],x[1]) for x in captures[1:]]
sa=(ROOT/'epoch-1-point-0.rgba').read_bytes();sr=(ROOT/'epoch-3-point-0.rgba').read_bytes()
result={'schema':'G3-followup-v1','epochs':validated,'native_source_A_Areturn_exact':sa==sr,'whole_H1_A_Areturn':whole_h1,'whole_H2_baseline':baseline,'whole_H2_A_B_Areturn':whole_h2,'whole_field_causality':'NOT_PROVEN','physical_present_buffer':'NOT_PROVEN','gpu_completion':'NOT_PROVEN','atomic_generation':'NOT_PROVEN','instrumentation':'INTRUSIVE_ERROR_QUEUE_AND_READBACK_SYNC','source_commands':['G3-A','G3-B','G3-A-return']}
s.save('G3-result',result)
for name in ('g2-glass','g2-background','g2-reference'):s.close_client(name,'close')
return result

View file

@ -0,0 +1,71 @@
"""G3 offline C17/FakeGL/protocol/client checks; no compositor build/run."""
import argparse
import ctypes
import json
import hashlib
import os
from pathlib import Path
import resource
import shlex
import subprocess
import sys
KIT = Path(__file__).resolve().parent
def main():
resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
assert ctypes.CDLL(None).prctl(4, 0, 0, 0, 0) == 0
p = argparse.ArgumentParser(description=__doc__)
p.add_argument('--lab', required=True, type=Path)
p.add_argument('--scene', required=True, type=Path)
a = p.parse_args()
lab, scene = a.lab.resolve(strict=True), a.scene.resolve(strict=True)
assert str(lab).startswith('/var/tmp/swayfx-mosaic-step20c-native-closure-')
assert scene.is_relative_to(lab)
work = lab / 'g3-offline'
work.mkdir(mode=0o700, exist_ok=True)
env = {'PATH': '/usr/bin:/bin', 'LANG': 'C.UTF-8'}
for key, name in [('TMPDIR','scratch'),('TMP','scratch'),('TEMP','scratch'),
('XDG_CACHE_HOME','cache'),('XDG_DATA_HOME','data'),
('PYTHONPYCACHEPREFIX','pycache'),('CCACHE_DIR','ccache')]:
path = work / name
path.mkdir(mode=0o700, exist_ok=True)
env[key] = str(path)
flags = shlex.split(subprocess.check_output(['pkg-config','--cflags','wlroots-0.20','pixman-1','egl','glesv2','libdrm','wayland-server'],env=env,text=True))
base = ['cc','-std=c17','-D_GNU_SOURCE','-DWLR_USE_UNSTABLE','-DWLR_PRIVATE=',
'-DWLR_LITTLE_ENDIAN=1','-DWLR_BIG_ENDIAN=0','-Wall','-Wextra','-Werror',
'-Wno-unused-parameter','-Wno-missing-braces','-I'+str(scene/'include')]
root = '-DG3_ROOT="'+str(lab/'gpu-work/acquisition')+'"'
commands = [
['cc','-std=c17','-Wall','-Wextra','-Werror','-I'+str(KIT),str(KIT/'test_gl.c'),'-o',str(work/'test_gl')],
[str(work/'test_gl')],
[sys.executable,str(KIT/'test_mutations.py')],
[sys.executable,str(KIT/'validate_source.py')],
[sys.executable,str(KIT/'test_protocol.py')],
[sys.executable,str(KIT/'test_begin.py')],
base + flags + [root,'-I'+str(KIT),'-Wno-unused-function','-ffunction-sections','-fdata-sections',str(KIT/'test_native.c'),'-Wl,--gc-sections','-o',str(work/'test_native')],
[str(work/'test_native')],
base + flags + [root,'-fsyntax-only',str(scene/'render/fx_renderer/fx_pass.c')],
]
for xml, header in [('wlr-layer-shell-unstable-v1.xml','layer-shell-client.h'),('xdg-shell.xml','xdg-shell-client.h')]:
commands.append(['wayland-scanner','client-header',str(KIT/'protocols'/xml),str(work/header)])
commands.append(['cc','-std=c17','-Wall','-Wextra','-Werror','-Wno-unused-parameter','-Wno-missing-braces','-I'+str(work),'-fsyntax-only',str(KIT/'clients-g2.c')])
rows=[]
for i, command in enumerate(commands):
r = subprocess.run(command, cwd=work, env=env, text=True, capture_output=True, timeout=60)
log = work / f'test-{i}.log'
log.write_text(r.stdout+r.stderr)
rows.append({'command':command,'exit':r.returncode,'log':log.name})
print(f'{i}: exit={r.returncode}\n{r.stdout}{r.stderr}',flush=True)
if r.returncode:
break
passed=len(rows)==len(commands) and all(r['exit']==0 for r in rows)
(work/'receipt.json').write_text(json.dumps({'schema':'G3-offline-v1','pass':passed,
'review':'PENDING_INDEPENDENT','runtime':'NOT_RUN','compositor_build':'NOT_RUN',
'environment':env,'scene':str(scene),'native_sha256':{n:hashlib.sha256((scene/'render/fx_renderer'/n).read_bytes()).hexdigest() for n in ('fx_pass.c','g2_gl.h','g2_native.h')},'headers':{n:hashlib.sha256(Path(n).read_bytes()).hexdigest() for n in ('/usr/include/EGL/egl.h','/usr/include/GLES3/gl3.h','/usr/include/libdrm/drm_fourcc.h')},'commands':rows},indent=2)+'\n')
raise SystemExit(0 if passed else 1)
if __name__=='__main__':
main()

View file

@ -0,0 +1,73 @@
"""Compile the exact native arm consumer against bounded filesystem fixtures."""
import ctypes
import os
from pathlib import Path
import resource
import subprocess
import tempfile
resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
assert ctypes.CDLL(None).prctl(4, 0, 0, 0, 0) == 0
KIT = Path(__file__).resolve().parent
header = (KIT/'g2_native.h').read_text()
marker = 'static void g2_begin(struct fx_gles_render_pass *pass,struct fx_framebuffer *source,struct fx_render_blur_pass_options *options) {'
start = header.index(marker)
end = header.index('\n}\n',start)+3
body = header[start:end]
prefix = r'''
#define _GNU_SOURCE
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <sys/prctl.h>
#include <errno.h>
struct fx_gles_render_pass {void *fx_offscreen_buffers;};
struct fx_framebuffer {int unused;};
struct bd {int num_passes;};
struct fx_render_blur_pass_options {struct bd *blur_data;float blur_strength;struct {struct {void *clip;}base;}tex_options;};
static struct {bool stopped,active;unsigned epoch,point;size_t used;int dir;FILE *meta;const void *pass;uintptr_t pass_sequence;char nonce[65];}g2;
static bool g2_intended_output(void *p){(void)p;return true;}
static bool g2_open(void){return true;}
static void g2_fail(const char *s){(void)s;g2.stopped=true;}
static bool blur_data_should_parameters_blur_effects(void *p){(void)p;return false;}
static void g2_sample_native(void *p,void *b,unsigned x,void *r){(void)p;(void)b;(void)x;(void)r;}
'''
suffix = r'''
int main(int argc,char **argv) {
(void)argc;struct rlimit r={0,0};assert(!setrlimit(RLIMIT_CORE,&r));assert(!prctl(PR_SET_DUMPABLE,0));
g2.dir=open(argv[1],O_RDONLY|O_DIRECTORY|O_NOFOLLOW);assert(g2.dir>=0);memset(g2.nonce,'a',64);
struct fx_gles_render_pass pass={0};struct bd bd={.num_passes=3};
struct fx_render_blur_pass_options options={.blur_data=&bd,.blur_strength=1};
g2_begin(&pass,NULL,&options);
if(!strcmp(argv[2],"accept")){assert(g2.active&&!g2.stopped&&g2.epoch==1);assert(!fclose(g2.meta));}
else assert(g2.stopped&&!g2.active&&g2.epoch==0);
assert(!close(g2.dir));return 0;
}
'''
with tempfile.TemporaryDirectory(prefix='g3-begin-',dir=os.environ['TMPDIR']) as name:
work=Path(name);work.chmod(0o700)
source=work/'begin.c';source.write_text(prefix+body+suffix)
binary=work/'begin'
subprocess.run(['cc','-std=c17','-Wall','-Wextra','-Werror',str(source),'-o',str(binary)],check=True)
for case in ('fifo','symlink','wrong-mode','hardlink','oversize','consumed','accept'):
d=work/case;d.mkdir(mode=0o700);arm=d/'epoch-1.arm'
if case=='fifo':os.mkfifo(arm,0o600)
elif case=='symlink':arm.symlink_to('/dev/null')
else:
arm.write_bytes(b'a'*64+b' 1\n');arm.chmod(0o600)
if case=='wrong-mode':arm.chmod(0o644)
if case=='hardlink':os.link(arm,d/'alias')
if case=='oversize':arm.write_bytes(b'a'*1000)
if case=='consumed':(d/'epoch-1.consumed').write_bytes(b'old')
subprocess.run([str(binary),str(d),case],check=True,timeout=3)
if case=='accept':assert not arm.exists() and (d/'epoch-1.consumed').exists()
else:assert arm.exists() or arm.is_symlink()
print('PASS exact native g2_begin',case)

View file

@ -0,0 +1,87 @@
#define _GNU_SOURCE
#include "g2_gl.h"
#include <assert.h>
#include <limits.h>
#include <sys/resource.h>
#include <sys/prctl.h>
static struct g2_state state;
static int mode, read_calls, unsupported, pending, query_count;
static bool read_done;
static const char *version="OpenGL ES 3.2 FakeGL";
static void reset(int m) {
memset(&state,0,sizeof(state));state.read_fbo=11;state.draw_fbo=12;state.rbo=13;
state.pack=8;state.pbo=15;state.row=32;state.skip_rows=2;state.skip_pixels=3;
state.viewport[2]=2;state.viewport[3]=3;state.scissor[2]=2;state.scissor[3]=3;
state.program=21;state.active=GL_TEXTURE0;state.texture=22;
mode=m;read_calls=unsupported=pending=query_count=0;read_done=false;
version=(m==6||m==14)?"OpenGL ES 2.0 FakeGL":m==20?"OpenGL ES 3.20 FakeGL":m==21?"OpenGL ES 3.3 FakeGL":m==22?"OpenGL 3.2":m==23?"OpenGL ES 4.0":"OpenGL ES 3.2 FakeGL";
}
EGLContext EGLAPIENTRY eglGetCurrentContext(void) {return mode==5?EGL_NO_CONTEXT:(EGLContext)(uintptr_t)(mode==17&&read_done?7:5);}
EGLDisplay EGLAPIENTRY eglGetCurrentDisplay(void) {return mode==24?EGL_NO_DISPLAY:(EGLDisplay)(uintptr_t)6;}
EGLenum EGLAPIENTRY eglQueryAPI(void) {return mode==12?EGL_OPENGL_API:EGL_OPENGL_ES_API;}
EGLBoolean EGLAPIENTRY eglQueryContext(EGLDisplay d,EGLContext c,EGLint a,EGLint *v) {(void)d;(void)c;(void)a;*v=(mode==6||mode==18)?2:3;return mode==13?EGL_FALSE:EGL_TRUE;}
GLenum GL_APIENTRY glGetError(void) {
if(mode==1 && query_count==0) {query_count++;return GL_INVALID_OPERATION;}
if(mode==8)return GL_INVALID_OPERATION;
if(pending){GLenum e=(GLenum)pending;pending=0;return e;}return GL_NO_ERROR;
}
const GLubyte *GL_APIENTRY glGetString(GLenum e) {if(mode==25)pending=GL_INVALID_ENUM;return (const GLubyte *)(e==GL_SHADING_LANGUAGE_VERSION?"OpenGL ES GLSL ES 3.20 FakeGL":version);}
void GL_APIENTRY glGetIntegerv(GLenum e,GLint *v) {
if(mode==6)unsupported++;
if(mode==2 && e==GL_READ_FRAMEBUFFER_BINDING)pending=GL_INVALID_ENUM;
switch(e) {
case GL_MAJOR_VERSION:*v=mode==19?2:3;if(mode==26)pending=GL_INVALID_ENUM;break;
case GL_MINOR_VERSION:*v=mode==27?1:2;break;
#define FIELD(e,f) case e:*v=state.f;break
case GL_READ_BUFFER:*v=mode==16?GL_NONE:GL_COLOR_ATTACHMENT0;break;
FIELD(GL_READ_FRAMEBUFFER_BINDING,read_fbo);FIELD(GL_DRAW_FRAMEBUFFER_BINDING,draw_fbo);
FIELD(GL_RENDERBUFFER_BINDING,rbo);FIELD(GL_PACK_ALIGNMENT,pack);FIELD(GL_PIXEL_PACK_BUFFER_BINDING,pbo);
FIELD(GL_PACK_ROW_LENGTH,row);FIELD(GL_PACK_SKIP_ROWS,skip_rows);FIELD(GL_PACK_SKIP_PIXELS,skip_pixels);
FIELD(GL_CURRENT_PROGRAM,program);FIELD(GL_ACTIVE_TEXTURE,active);FIELD(GL_TEXTURE_BINDING_2D,texture);
case GL_VIEWPORT:memcpy(v,state.viewport,sizeof(state.viewport));break;
case GL_SCISSOR_BOX:memcpy(v,state.scissor,sizeof(state.scissor));break;
default:unsupported++;pending=GL_INVALID_ENUM;break;
}
}
GLboolean GL_APIENTRY glIsEnabled(GLenum e) {(void)e;return state.scissor_on;}
void GL_APIENTRY glBindFramebuffer(GLenum e,GLuint v) {if(e==GL_READ_FRAMEBUFFER)state.read_fbo=(GLint)v;else state.draw_fbo=(GLint)v;}
void GL_APIENTRY glBindRenderbuffer(GLenum e,GLuint v) {(void)e;state.rbo=(GLint)v;}
void GL_APIENTRY glBindBuffer(GLenum e,GLuint v) {(void)e;if(mode==11&&read_done&&v==15)pending=GL_INVALID_OPERATION;if(!(mode==4&&read_done))state.pbo=(GLint)v;}
void GL_APIENTRY glPixelStorei(GLenum e,GLint v) {
if(mode==9&&e==GL_PACK_ALIGNMENT&&v==1)pending=GL_INVALID_OPERATION;
switch(e) {case GL_PACK_ALIGNMENT:state.pack=v;break;case GL_PACK_ROW_LENGTH:state.row=v;break;
case GL_PACK_SKIP_ROWS:state.skip_rows=v;break;case GL_PACK_SKIP_PIXELS:state.skip_pixels=v;break;default:unsupported++;}
}
void GL_APIENTRY glGetFramebufferAttachmentParameteriv(GLenum t,GLenum a,GLenum e,GLint *v) {
(void)t;(void)a;if(mode==10)pending=GL_INVALID_ENUM;*v=e==GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE?(mode==7?GL_TEXTURE:GL_RENDERBUFFER):e==GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME?44:0;
}
void GL_APIENTRY glGetRenderbufferParameteriv(GLenum t,GLenum e,GLint *v) {
(void)t;*v=e==GL_RENDERBUFFER_WIDTH?2:e==GL_RENDERBUFFER_HEIGHT?3:e==GL_RENDERBUFFER_INTERNAL_FORMAT?GL_RGBA8:e==GL_RENDERBUFFER_SAMPLES?(mode==15?4:0):8;
}
GLenum GL_APIENTRY glCheckFramebufferStatus(GLenum e) {(void)e;return mode==28?GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:GL_FRAMEBUFFER_COMPLETE;}
void GL_APIENTRY glReadPixels(GLint x,GLint y,GLsizei w,GLsizei h,GLenum f,GLenum t,void *p) {
(void)x;(void)y;assert(f==GL_RGBA&&t==GL_UNSIGNED_BYTE);assert(state.pbo==0&&state.pack==1&&state.row==0);
read_calls++;read_done=true;if(mode==3)pending=GL_INVALID_OPERATION;else memset(p,0,(size_t)w*(size_t)h*4);
}
int main(void) {
struct rlimit r={0,0};assert(!setrlimit(RLIMIT_CORE,&r));assert(!prctl(PR_SET_DUMPABLE,0));
const char *reasons[]={"READ_SUCCESS","PREEXISTING_GL_ERRORS","STATE_QUERY_ERROR","READ_GL_ERROR","RESTORE_FAILED","UNKNOWN_CONTEXT","GLES_STATE_NOT_SUPPORTED","TEXTURE_ATTACHMENT_DIMENSIONS_NOT_PROVEN","PREEXISTING_GL_ERRORS","PACK_QUERY_ERROR","ATTACHMENT_QUERY_ERROR","RESTORE_FAILED","UNKNOWN_CONTEXT","EGL_QUERY_FAILED","GLES_STATE_NOT_SUPPORTED","ATTACHMENT_UNSUPPORTED","ATTACHMENT_UNSUPPORTED","RESTORE_FAILED","READ_SUCCESS","VERSION_INCONSISTENT","GLES_STATE_NOT_SUPPORTED","GLES_STATE_NOT_SUPPORTED","GLES_STATE_NOT_SUPPORTED","GLES_STATE_NOT_SUPPORTED","UNKNOWN_CONTEXT","VERSION_QUERY_ERROR","CAPABILITY_QUERY_ERROR","VERSION_INCONSISTENT","ATTACHMENT_UNSUPPORTED"};
for(int i=0;i<29;i++) {
reset(i);struct g2_state before=state;struct g2_sample s=g2_read(99,2,3,0);
assert(s.success==(i==0||i==18));assert(!strcmp(s.reason,reasons[i]));assert(!unsupported);
if(i==0||i==3||i==7)assert(!memcmp(&state,&before,sizeof(state)));
if(i==0||i==18){assert(s.version_queried&&s.state_saved&&s.bound&&s.read_attempted&&s.restore_attempted);assert(read_calls==1&&s.bytes==24);for(size_t n=0;n<s.bytes;n++)assert(s.pixels[n]==0);}
if(i==1||i==2||i==5||i==6||i==7||i==8||i==9||i==10||i==12||i==13||i==14||i==15||i==16)assert(!read_calls);
if(i==8)assert(s.pre.exhausted&&s.pre.count==G2_ERRORS);
if(!s.state_saved)assert(!s.bound&&!s.read_attempted&&!s.restore_attempted);
if(i>=19)assert(!read_calls);
printf("PASS exact g2_read mode=%d reason=%s\n",i,s.reason);free(s.pixels);
}
GLint minor=0;char too_long[170];memset(too_long,'x',sizeof(too_long));memcpy(too_long,"OpenGL ES 3.2 ",14);too_long[169]=0;
assert(!g2_version(NULL,&minor));assert(!g2_version(too_long,&minor));assert(!g2_version("OpenGL ES 3.2\nforged",&minor));
assert(g2_version("OpenGL ES 3.0",&minor)&&minor==0);assert(g2_version("OpenGL ES 3.1 vendor",&minor)&&minor==1);
size_t n=0;assert(!g2_size(INT_MAX,INT_MAX,0,&n));assert(!g2_size(2,3,G2_CAP-23,&n));assert(!g2_size(-1,3,0,&n));
assert(g2_size(2,3,G2_CAP-24,&n)&&n==24);
reset(0);struct g2_sample s=g2_read(99,INT_MAX,INT_MAX,0);assert(!s.success&&!read_calls);free(s.pixels);
puts("PASS size/overflow/exact-cap; black RGBA alpha zero is valid, no sentinel gate");return 0;
}

View file

@ -0,0 +1,30 @@
"""Wrong native variants must fail the unchanged exact-body FakeGL tests."""
import ctypes
import os
from pathlib import Path
import resource
import shutil
import subprocess
import tempfile
resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
assert ctypes.CDLL(None).prctl(4, 0, 0, 0, 0) == 0
KIT=Path(__file__).resolve().parent
header=(KIT/'g2_gl.h').read_text()
variants={
'major-contradiction':('s.major!=3 || s.minor!=parsed_minor','s.minor!=parsed_minor'),
'minor-contradiction':('s.major!=3 || s.minor!=parsed_minor','s.major!=3'),
'false-read-lifecycle':('s.read_attempted=true;','s.read_attempted=false;'),
'missing-pack-restore':('glPixelStorei(GL_PACK_ALIGNMENT,s->pack);','/* deliberately broken restore */'),
}
with tempfile.TemporaryDirectory(prefix='g3-mutants-',dir=os.environ['TMPDIR']) as name:
work=Path(name)
shutil.copyfile(KIT/'test_gl.c',work/'test_gl.c')
for label,(old,new) in variants.items():
assert header.count(old)==1
(work/'g2_gl.h').write_text(header.replace(old,new))
binary=work/label
subprocess.run(['cc','-std=c17','-Wall','-Wextra','-Werror','-I'+str(work),str(work/'test_gl.c'),'-o',str(binary)],check=True)
result=subprocess.run([str(binary)],capture_output=True,text=True,timeout=5)
assert result.returncode != 0, label
print(f'PASS wrong native variant rejected {label}: raw_exit={result.returncode}')

View file

@ -0,0 +1,86 @@
#include <GLES3/gl3.h> /* private G2 state observability */
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <pixman.h>
#include <time.h>
#include <unistd.h>
#include <wlr/render/allocator.h>
#include <wlr/render/drm_syncobj.h>
#include <wlr/util/transform.h>
#include <wlr/util/log.h>
#include <wlr/util/region.h>
#include "render/egl.h"
#include "render/fx_renderer/fx_renderer.h"
#include "render/fx_renderer/shaders.h"
#include "render/pass.h"
#include "render/tracy.h"
#include "scenefx/render/fx_renderer/fx_offscreen_buffers.h"
#include "scenefx/render/fx_renderer/fx_renderer.h"
#include "scenefx/types/fx/blur_data.h"
#include "util/matrix.h"
#include "g2_native.h"
static struct wlr_dmabuf_attributes controlled_dmabuf;
bool wlr_buffer_get_dmabuf(struct wlr_buffer *buffer, struct wlr_dmabuf_attributes *a) {
(void)buffer; *a=controlled_dmabuf;return true;
}
#include <sys/resource.h>
#include <sys/prctl.h>
int main(void) {
struct rlimit r={0,0};assert(!setrlimit(RLIMIT_CORE,&r));assert(!prctl(PR_SET_DUMPABLE,0));
/* WHY: DCC auxiliary pitch is not an RGBA image pitch. CONTRACT:
* exercise the exact native predicate with borrowed real descriptors. */
FILE *plane=tmpfile();assert(plane);int fd=fileno(plane);
controlled_dmabuf=(struct wlr_dmabuf_attributes){.width=800,.height=600,
.format=DRM_FORMAT_ABGR8888,.modifier=UINT64_C(0x020000001046bb04),.n_planes=2,
.stride={3584,1024},.offset={0,2293760},.fd={fd,fd}};
struct wlr_buffer buffer={.width=800,.height=600};struct fx_framebuffer fb={.buffer=&buffer};
assert(g2_dmabuf_supported(&fb));assert(fcntl(fd,F_GETFD)>=0);
controlled_dmabuf.stride[1]=0;assert(!g2_dmabuf_supported(&fb));controlled_dmabuf.stride[1]=1024;
controlled_dmabuf.modifier=UINT64_MAX;assert(!g2_dmabuf_supported(&fb));
controlled_dmabuf.modifier=0;controlled_dmabuf.n_planes=1;assert(g2_dmabuf_supported(&fb));
controlled_dmabuf.stride[0]=3199;assert(!g2_dmabuf_supported(&fb));controlled_dmabuf.stride[0]=3584;
controlled_dmabuf.fd[0]=-1;assert(!g2_dmabuf_supported(&fb));
assert(!fclose(plane));puts("PASS exact C DMA-BUF DCC/linear metadata and borrowed FD; allocation bounds NOT_PROVEN");
struct fx_gles_render_pass passes[9]={0};
struct wlr_output h1={.name="HEADLESS-1",.scale=1,.transform=WL_OUTPUT_TRANSFORM_NORMAL};
struct wlr_output h2={.name="HEADLESS-2",.scale=1,.transform=WL_OUTPUT_TRANSFORM_NORMAL};
for(unsigned i=0;i<9;i++) {
g2_track_output(&passes[i],i%2?&h2:&h1);
assert(g2_intended_output(&passes[i])==(i%2==0));
g2_forget_output(&passes[i]);assert(!g2_intended_output(&passes[i]));
}
assert(!g2.stopped);
g2_track_output(&passes[0],&h1);assert(g2_intended_output(&passes[0]));
g2_track_output(&passes[0],&h2);assert(!g2_intended_output(&passes[0]));
g2_forget_output(&passes[0]);g2_track_output(&passes[0],&h1);assert(g2_intended_output(&passes[0]));g2_forget_output(&passes[0]);
for(unsigned i=0;i<9;i++)g2_track_output(&passes[i],i%2?&h2:&h1);
assert(g2.stopped && !g2_intended_output(&passes[8]));
for(unsigned i=0;i<9;i++) {g2_forget_output(&passes[i]);}
g2.stopped=false;
puts("PASS nine sequential passes/output forget, pointer reuse H1/H2 and nine-simultaneous fail-closed overflow");
struct g2_sample s={.width=800,.height=600,.bytes=800*600*4};s.pixels=calloc(1,s.bytes);assert(s.pixels);
for(int y=0;y<600;y++)for(int x=0;x<800;x++) {
const unsigned char a[2][3]={{245,245,245},{15,25,45}};
memcpy(s.pixels+((size_t)y*800+(size_t)x)*4,a[(x/8)%2],3);
}
assert(g2_expected_source(&s,1));assert(g2_expected_source(&s,3));assert(!g2_expected_source(&s,2));
s.pixels[4]^=1;assert(!g2_expected_source(&s,1));s.pixels[4]^=1;
for(int y=0;y<600;y++)for(int x=0;x<800;x++) {
const unsigned char b[2][3]={{32,192,224},{176,32,64}};
memcpy(s.pixels+((size_t)y*800+(size_t)x)*4,b[(x/8)%2],3);
}
assert(g2_expected_source(&s,2));assert(!g2_expected_source(&s,3));free(s.pixels);
assert(g2_crc((const unsigned char *)"123456789",9)==UINT32_C(0xcbf43926));
FILE *f=tmpfile();assert(f && g2_owned(fileno(f),0600));assert(g2_write(fileno(f),"frame",5));assert(!fseek(f,0,SEEK_SET));char b[6]={0};assert(fread(b,1,5,f)==5 && !strcmp(b,"frame"));assert(!fclose(f));
assert(!setenv("MOSAIC_G3_DIR","/tmp",1));assert(!setenv("MOSAIC_G3_NONCE","aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",1));
assert(!g2_open() && g2.stopped && g2.dir==-1);
puts("PASS exact native source epoch RGB admission A/B/A-return and negative substitution");
puts("PASS exact native CRC framing/writer/owned FD cleanup and wrong realpath refusal");
return 0;
}

View file

@ -0,0 +1,110 @@
"""Controlled synthetic framing and scenario tests; NO GPU/native evidence."""
import resource,ctypes
resource.setrlimit(resource.RLIMIT_CORE,(0,0))
assert ctypes.CDLL(None).prctl(4,0,0,0,0)==0
import json,os,shutil,tempfile,zlib,copy
from pathlib import Path
from unittest.mock import patch
from PIL import Image
import protocol,scenario
from validate_source import Rejected
NONCE='a'*64
BASE=Path(os.environ['TMPDIR'])
def write_epoch(d,e,total):
raw=bytes(([32,192,224,255] if e==2 else [245,245,245,255])*6);total+=len(raw)*10
rows=[dict(kind='begin',schema='G3-followup-v1',epoch=e,nonce=NONCE,label=('A','B','A-return')[e-1],output='HEADLESS-1',intrusive_error_queue=True,error_queue_restorable=False)]
for p,stage in enumerate(protocol.STAGES):
if 1<=p<=6:rows.append(dict(kind='uniform',point=p,type=0x8b50,program=5,location=3,shader_default_float='mediump',varying='mediump',sampler=[9729,9729,33071,33071],precision_capability=[15,15,10],native_texture_dimensions=[2,3],actual_bits=[1,2]))
filename=f'epoch-{e}-point-{p}.rgba';protocol.exclusive(d/filename,raw)
rows.append(dict(kind='sample',epoch=e,point=p,stage=stage,pass_sequence=e,context='5',result='READ_SUCCESS',read_success=True,published=True,before={'pack':8},after={'pack':8},pre_errors={'exhausted':False,'codes':[]},query_errors={'exhausted':False,'codes':[]},read_errors={'exhausted':False,'codes':[]},restore_errors={'exhausted':False,'codes':[]},client_version=2,gl_major=3,gl_minor=2,glsl_hex=b'OpenGL ES GLSL ES 3.20'.hex(),version_queried=True,state_saved=True,bound=True,read_attempted=True,restore_attempted=True,version_errors={'exhausted':False,'codes':[]},capability_errors={'exhausted':False,'codes':[]},state_errors={'exhausted':False,'codes':[]},gl_version_hex=b'OpenGL ES 3.2 ControlledFake'.hex(),attachment=[0x8d41,44,0,2,3,0x8058],channels=[8,8,8,8],status=0x8cd5,read_buffer=0x8ce0,samples=0,native={'dimensions':[2,3],'fbo_rbo_tex':[11,44,22],'fx':'1','wlr':'2','image':'3'},dmabuf={'borrowed':True,'dimensions':[2,3],'format':int.from_bytes(b'AB24','little'),'modifier':'0000000000000000','planes':[{'stride':8,'offset':0,'fd':4,'fstat_ok':True,'dev':1,'ino':2}]},bytes=len(raw),file=filename,crc32=f'{zlib.crc32(raw):08x}',fresh_rectangles=[[0,0,2,3]]))
rows.append(dict(kind='end',epoch=e,points=10,accepted=True,raw_total=total))
protocol.exclusive(d/f'epoch-{e}.jsonl',b''.join((json.dumps(row,separators=(',',':'))+'\n').encode() for row in rows));(None if (d/f'epoch-{e}.consumed').exists() else protocol.exclusive(d/f'epoch-{e}.consumed',f'{NONCE} {e}\n'.encode()))
return rows,total
def framing(d):
rows,total=write_epoch(d,1,0);assert protocol.epoch(d,1,NONCE)['raw_total']==240
tests=[('early-return',lambda x:x[1].update(read_attempted=False)),('fake-version',lambda x:x[1].update(gl_minor=1)),('unqualified-es',lambda x:x[1].update(gl_version_hex=b'OpenGL ES 3.3'.hex())),('capability-error',lambda x:x[1]['capability_errors']['codes'].append(0x500)),('read-error',lambda x:x[1]['read_errors']['codes'].append(0x502)),('restore',lambda x:x[1]['after'].update(pack=1)),('context',lambda x:x[1].update(context='0')),('path',lambda x:x[1].update(file='../escape')),('budget',lambda x:x[1].update(bytes=protocol.CAP+1)),('incomplete',lambda x:x.pop()),('stage',lambda x:x[1].update(stage='presented-buffer'))]
p=d/'epoch-1.jsonl'
for label,mutate in tests:
changed=copy.deepcopy(rows);mutate(changed);p.write_text(''.join(json.dumps(x)+'\n' for x in changed));p.chmod(0o600)
try:protocol.epoch(d,1,NONCE)
except (Rejected,KeyError,AssertionError):print('PASS controlled framing rejection',label)
else:raise AssertionError(label)
p.write_text(''.join(json.dumps(x)+'\n' for x in rows));p.chmod(0o600)
q=d/'epoch-1-point-0.rgba';q.write_bytes(b'bad')
try:protocol.epoch(d,1,NONCE)
except Rejected:print('PASS raw truncation/CRC rejection')
else:raise AssertionError('truncation')
q.unlink();q.symlink_to('/dev/null')
try:protocol.bounded(q,10)
except Rejected:print('PASS symlink refusal')
else:raise AssertionError('symlink')
try:protocol.exclusive(p,b'overwrite')
except FileExistsError:print('PASS exclusive publication refusal')
else:raise AssertionError('exclusive')
assert scenario.exact_rgb(Image.new('RGB',(2,3)),Image.new('RGB',(2,3)))['equal']
a=Image.new('RGB',(2,3));b=a.copy();b.putpixel((1,2),(1,0,0));assert not scenario.exact_rgb(a,b)['equal']
print('PASS whole H1/H2 all-pixel zero RGB tolerance')
def token_test(d):
with patch.object(protocol,'gate',lambda *args:None):
protocol.arm(d,1,NONCE,d/'gate',d/'binary')
assert protocol.bounded(d/'epoch-1.arm',80)==f'{NONCE} 1\n'.encode()
for e in (1,2):
try:protocol.arm(d,e,NONCE,d/'gate',d/'binary')
except (Rejected,FileNotFoundError):pass
else:raise AssertionError('retry or B before complete A')
os.link(d/'epoch-1.arm',d/'epoch-1.consumed');(d/'epoch-1.arm').unlink()
write_epoch(d,1,0)
protocol.arm(d,2,NONCE,d/'gate',d/'binary')
try:protocol.arm(d,3,NONCE,d/'gate',d/'binary')
except (Rejected,FileNotFoundError):pass
else:raise AssertionError('return before complete B')
print('PASS exact arm token nonce/exclusive/replay and first A completion gate, controlled directory')
class Session:
def __init__(self,d,fail=False):
self.d=d;self.fail=fail;self.commands=[];self.captures=0;self.total=0;self.env={};self.spec={'id':'GPU-G3','binary':'gpu'};self.auth={'binary':str(d/'fake-binary'),'manifest_sha256':'controlled'};self.proc={};self.data={};self.saved={}
def start(self):self.data['started']=True
def cmd(self,*a,**kw):pass
def spawn(self,name,argv):
parent=self
class Input:
def write(self,text):
parent.commands.append(text.strip());e=len(parent.commands)
if parent.fail:protocol.exclusive(parent.d/f'epoch-{e}.jsonl',b'{"kind":"rejected"}\n')
else:_,parent.total=write_epoch(parent.d,e,parent.total)
def flush(self):pass
self.proc[name]=type('Process',(),{'stdin':Input()})()
def wait(self,fn):assert fn()
def find(self,name):return {'id':1}
def log(self,name):return 'BACKGROUND_CONFIGURED '+' '.join('G3_SOURCE_BYTES command='+x+' ' for x in self.commands)
def debit(self,*a):pass
def record(self,*a,**kw):pass
def capture(self,*a):self.captures+=1;return Image.new('RGB',(2,3))
def pair_capture(self,*a):self.captures+=2;return [Image.new('RGB',(2,3)),Image.new('RGB',(2,3))]
def snapshot(self,*a):pass
def save(self,name,result):self.saved[name]=result
def close_client(self,*a):pass
def scenario_test(d,fail):
s=Session(d,fail);binary=d/'fake-binary';binary.write_bytes(b'controlled')
binding=dict(G3_ledger_reserved=True,binary=str(binary),source_manifest_sha256='controlled',gate_file=str(d/'gate'),client=str(binary),client_sha256=__import__('hashlib').sha256(binary.read_bytes()).hexdigest(),nonce=NONCE)
armed=[]
with patch.object(scenario,'ROOT',d),patch.object(scenario,'gate',lambda *args:None),patch.object(scenario,'arm',lambda root,e,*args:armed.append(e)),patch.object(scenario.time,'sleep',lambda seconds:None):
if fail:
try:scenario.run(s,binding)
except RuntimeError:pass
else:raise AssertionError('rejected A continued')
assert s.commands==['G3-A'] and armed==[1] and s.captures==2
print('PASS rejected first A stops B/A-return: exact executable adapter, fake Session only')
else:
r=scenario.run(s,binding);assert s.commands==['G3-A','G3-B','G3-A-return'] and armed==[1,2,3] and s.captures==8 and r['whole_H1_A_Areturn']['equal']
print('PASS one-shot A/B/A adapter flow, eight captures with whole H1/H2 and H2 baseline, controlled framing')
if __name__=='__main__':
with tempfile.TemporaryDirectory(prefix='protocol-controlled-',dir=BASE) as temp:
p=Path(temp);p.chmod(0o700)
for name in ('framing','accepted','rejected','token'):(p/name).mkdir(mode=0o700)
with patch.object(protocol,'FRAME',(2,3)):
framing(p/'framing');token_test(p/'token');scenario_test(p/'accepted',False);scenario_test(p/'rejected',True)

View file

@ -0,0 +1,53 @@
"""New G2 offline controls. Historical G1 stays HISTORICAL_REPORTED_ONLY."""
import resource,ctypes
resource.setrlimit(resource.RLIMIT_CORE,(0,0))
assert ctypes.CDLL(None).prctl(4,0,0,0,0)==0
import hashlib,json,struct
from pathlib import Path
HEADER_SHA='992f33804acda60b0b46f1a272942c3592484ea3217d469239f1cb12249de2eb'
BPP={int.from_bytes(s,'little'):4 for s in (b'AB24',b'XB24',b'AR24',b'XR24')}
AMD='020000001046bb04'
class Rejected(ValueError):pass
def need(x,msg):
if not x:raise Rejected(msg)
def metadata(o):
need(type(o['width']) is int and type(o['height']) is int and 0<o['width']<=2**31-1 and 0<o['height']<=2**31-1,'dimensions')
need(o['format'] in BPP,'unknown FOURCC')
need(o['modifier'] in ('0000000000000000',AMD),'unknown modifier')
planes=o['planes'];need(len(planes)==(2 if o['modifier']==AMD else 1),'plane count')
for p in planes:
need(all(type(p[k]) is int and 0<=p[k]<=2**32-1 for k in ('stride','offset')) and p['stride']>0,'plane uint32')
need(p['offset']+p['stride']<=2**64-1,'overflow')
p=planes[0];need(p['stride']>=o['width']*BPP[o['format']],'image stride')
end=p['offset']+p['stride']*(o['height']-1)+o['width']*BPP[o['format']]
need(end<=2**64-1,'image overflow')
# No fabricated DCC extent or same-allocation restriction. fstat sizes are
# recorded as descriptor metadata, not interpreted as allocation length.
return {'image_stride':'PASS','auxiliary_stride':'NOT_AN_IMAGE_STRIDE' if len(planes)>1 else 'N/A','allocation_bounds':'NOT_PROVEN','fd_ownership':'BORROWED' if o.get('borrowed') is True else 'NOT_PROVEN','decode_support':'NOT_PROVEN'}
def f32(x):return struct.unpack('<f',struct.pack('<f',x))[0]
def bits(x):return struct.unpack('<I',struct.pack('<f',x))[0]
def halfpixel(width,height,down,observed):
need(width>0 and height>0 and len(observed)==2 and all(type(x)is int and 0<=x<=2**32-1 for x in observed),'uniform framing')
divisor=f32(2.0)
ideal=[f32(f32(.5)/f32(f32(n)/divisor if down else f32(n)*divisor)) for n in (width,height)]
return {'observed_bits':observed,'cpu_float32_ideal_bits':[bits(x) for x in ideal],'comparison_gate':'NONE','effective_arithmetic':'NOT_PROVEN','capability_is_per_operation_precision':False}
def test():
from copy import deepcopy
need(hashlib.sha256(Path('/usr/include/libdrm/drm_fourcc.h').read_bytes()).hexdigest()==HEADER_SHA,'header pin')
o={'width':800,'height':600,'format':int.from_bytes(b'AB24','little'),'modifier':AMD,'planes':[{'stride':3584,'offset':0},{'stride':1024,'offset':2293760}],'borrowed':True}
r=metadata(o);need(r['auxiliary_stride']=='NOT_AN_IMAGE_STRIDE','AMD aux')
# Controlled fixtures, never actual allocation evidence or historical PASS.
for label,fn in [('unknown-format',lambda x:x.update(format=1)),('short-image',lambda x:x['planes'][0].update(stride=3199)),('plane-count',lambda x:x['planes'].pop()),('unknown-modifier',lambda x:x.update(modifier='ffffffffffffffff')),('negative-offset',lambda x:x['planes'][1].update(offset=-1)),('zero-aux',lambda x:x['planes'][1].update(stride=0))]:
x=deepcopy(o);fn(x)
try:metadata(x)
except Rejected:print('PASS controlled rejection',label)
else:raise AssertionError(label)
u=halfpixel(800,600,True,[bits(1/800),bits(1/600)])
v=halfpixel(800,600,True,[bits(.0012493133544921875),bits(.0016660690307617188)])
need(u['cpu_float32_ideal_bits']==v['cpu_float32_ideal_bits'] and u['observed_bits']!=v['observed_bits'] and v['comparison_gate']=='NONE','no mediump equality or fit')
try:halfpixel(800,600,False,[2**32,0])
except Rejected:pass
else:raise AssertionError('uniform framing')
print('PASS controlled AMD/linear policy and separate halfpixel bits; HISTORICAL_REPORTED_ONLY')
if __name__=='__main__':test()

View file

@ -0,0 +1,172 @@
# Focused native closure graphics fixture
EXPERIMENTAL SOURCE ONLY. This is a new derivation of the source-only graphics
fixture at Git `d46f854b3026e911659528fe94792aa13837a1d6`, not a modification or
re-admission of P1/C7/C8/O1/G2. No production code, package or UX contract changes.
The closed historical LAB is read-only. All native results remain NOT_RUN until
independent final-byte review, source publication and individual admission.
The eight nonfungible IDs are FS-P(12 captures), FS-C(16), DY-C(16), OM-ROOT(10),
OM-NESTED(10), GPU-G3(12), OM-EXTRA(12), GPU-CTRL(12): sum100, global110, at most
8 runs /150s. No reset/refund/retry API. Graphics source leaves GPU-CTRL and OM-EXTRA NOT_ADMITTED. GPU-G3 is
NOT_ADMITTED by default and has a distinct executable branch requiring B4 and
independent exact-byte/source/build receipts; no gate is inferred from a plan.
WHY: the former fullscreen insertion placed peer outside owner group; migration
ignored focus reveal; root OOM closure required nonexistent B. CONTRACT: exact
owner+peer direct children and measured400/400, current-data clamp then focus
reveal, scenario-created child-first closure. INVARIANT: whole-field expectations
are calculated before acquisition, every action is debited first, raw failure
and mechanical rescue remain visible and cannot establish scientific cleanup.
`fullscreen.prepare_group` uses an existing filler (container_split does not wrap
a singleton H/V child), wraps owner, marks the real group, creates peer on ws3,
then explicitly moves it to that mark. The source route is pinned
`commands/split.c:do_split`, `tree/container.c:container_split`,
`commands/move.c:container_move_to_container` (nonleaf destination adds child).
No empirical runtime adjustment or retry exists. A failed topology/crossing is
NOT_EXERCISED. Both binaries use the same fullscreen-client/protocol XML, explicit
positioner adjustment0 and actual configured rectangles. A/B input, explicit
close B with A surviving, fullscreen exit/splith return and final H2 are sampled.
The complete H2 field composes A then B before each capture; A-only is predicted
after B closes. FS-P uses12 captures; FS-C uses the same12 within16.
IPC `fullscreen_mode` is explicitly **pending** (`ipc-json.c` serializes
`c->pending.fullscreen_mode`); workspace marker1 is not fullscreen proof. A
post-command toplevel configure/ACK/MAP plus measured group geometry, paints and
fresh input corroborates sampled applied fullscreen. Direct observation of the
ancestor's `current.fullscreen_mode` is NOT_OBSERVABLE with these unchanged ELFs;
atomic current-state generation is NOT_PROVEN. The validator rejects a
pending-only or mismatched-owner/workspace certificate. Do not report a native
current-bit measurement that these binaries do not expose.
`cases.dynamic_geometry` derives row extent and uniform gap from current actual
child rectangles and pinned default640 sizing, validates row identity/order and
focused owner, then clamps old scroll to current bounds and performs native
left/right reveal for migration. The audited route is
`commands/move.c:cmd_move_workspace` → arrange old/new output → seat_set_focus →
`input/seat.c` row focus/reveal → `tree/arrange.c:apply_mosaic_layout` and
`tree/mosaic.c:mosaic_row_clamp_scroll`. Neither800 nor1120 is a universal expected
camera. Every resize/migration invalidates previous input/camera/origin; new
painted interior and pointer-local delivery are required, or actual dismissal
records input NOT_EXERCISED. Opening6 + four mutation pairs8 + final pair2=16.
OOM root closes A→TOP, nested failure closes B→A→TOP after mapped rootA; no_memory
requires actual wl_display.error code1 and client ENOMEM/EPROTO, separately from
requested error0 close. Every created object gets one wire destroy in reverse
hierarchy; release accounting and requested close are required. Failed popup
must never MAP. Native role/PID/nonce/counters and the same-XDG NULL return bind
the failure. The diagnostic's `children` is **existing parent children**, not
children owned by the failed tree (`wl_list_length(&parent->children)`); retain
its raw value. Initialized/listeners/published0 plus the audited prepublication
NULL path establish the scoped failed node, without inventing a new child-count
counter or demanding nested parent_children0. RootA native success, real wire
grab/configure/ACK/SHM/map and clipped pre-injection paint qualify nested start.
Availability identity (actual /proc exe hash, BuildID, mappings, exact wlroots,
static SceneFX), tree/outputs and fresh exclusive neighbor press/release are
saved **before cleanup**. A surviving painted rootA receives fresh right-button
input before any neighbor click, checked against its actual configure, updated
owner origin, exclusive wire pair and pointer-local coordinates. Actual root
dismissal and ENOMEM disconnect remain distinct NOT_EXERCISED input outcomes.
H2 pre/post/final must equal the whole reference. Failed-popup color is absent
on both outputs before/post injection; final owner close rejects both A and B
on both outputs. Surviving A is allowed after nested B failure.
Fail-closed and availability are separate observations; full damage, GPU
retirement, presented-buffer identity and atomic generation remain NOT_PROVEN.
OM-NESTED needs qualified root outcome and scientific cleanup, or a new explicit
independent dependency review with rationale and no common native fault.
Preparation never executes clients or compositors. All generated protocols,
ELFs, pycache, caches, tests and receipts belong to a fresh canonical owned0700
LAB directly below /var/tmp. Run every command using an allowlist environment
with TMPDIR/TMP/TEMP=LAB/scratch, XDG_CACHE_HOME=LAB/cache, XDG_DATA_HOME=LAB/data,
PYTHONPYCACHEPREFIX=LAB/pycache, CCACHE_DIR=LAB/cache/ccache, SMX_LAB=LAB. Source
may reside in a fresh Git extraction; no historical absolute path is required.
```sh
python graphics/prepare.py --lab "$LAB"
python graphics/validate_offline.py --lab "$LAB"
python graphics/freeze.py --lab "$LAB" --author author-identity
```
The optional `validate_offline.py --historical-c7 <read-only-run-directory>`
replays preserved resized-back and migrated-H2 snapshots through the same live
validator and rejects a stale-camera mutation. It does not upgrade old C7.
The manifest goes to LAB/followup-work/source-manifest.json and contains only
relative hashes and modes for the **whole followup source kit**. Review/test/publication
and runtime identities stay outside source. Review is never generated by these
commands. `freeze` refuses replacement; changed source requires a new explicitly
reviewed freeze path/version using `--output "$LAB/followup-work/source-v2.json"`,
not an automatic rewrite at launch.
Independent review JSON binds ticket, manifest_sha256, independent:true,
reviewer different from manifest.author, decision:ACCEPTED, runs[ID] with
ACCEPTED/client_build_review and client_build_receipt path/SHA. `gates[NAME]`
binds decision:ACCEPTED, manifest_sha256 and receipt path/SHA: the receipt requires
status PASS, tests_run>0, failures0 and source_digest of the manifest files.
Required names: FULLSCREEN_COMPARABILITY_OFFLINE, MIGRATION_REFOCUS_OFFLINE,
OOM_ROOT_NESTED_ORACLE_OFFLINE, separately by scenario. Runtime identity file
is external via review.runtime_manifest / runtime_manifest_sha256, with ticket
and identities[pristine/core/oom]: binary path/SHA/BuildID, wlroots path/SHA,
build_review and source_qualification ACCEPTED; pristine also binds its shared
SceneFX path/SHA. OOM runs additionally require oom_source_review ACCEPTED,
variant_identity COREC_V2_NATIVE_OOM_DIAGNOSTIC and source_manifest_sha256 in
runs[ID]. Clients are newly built in LAB/followup-work/client-build. Source,
protocol and ELF hashes are reconciled before admission and actions.
Publication JSON binds ticket, decision VERIFIED, source_commit, manifest SHA,
source_digest, origin_sha/github_sha equal to that commit, offline_test_only:false
and direct remote_observations origin/github {argv:[git,ls-remote,remote,
refs/heads/main],exit:0,stdout:<commit + ref>}. The real preflight also hashes
`git show <commit>:<source-path>` bytes for every source entry before reservation.
The same gate reads `git ls-tree -rz --full-tree <commit> -- <kit-prefix>`
without a shell, parses NUL-delimited paths and requires the exact inventory,
blob type and matching 100644/100755 modes. Local freeze/refreeze rejects
nonportable modes: source files use 0644 (or explicitly reviewed 0755); receipt
0600 permissions are never represented as portable source permissions.
Matching remote strings alone do not bind a source kit to Git. Ledger init is
an explicit parent operation `Ledger(LAB).initialize()` after provenance gate;
no child/test creates a real ledger. Example single-case CLI after publication:
```sh
python graphics/run.py FS-P --lab "$LAB" \
--manifest "$LAB/followup-work/source-manifest.json" \
--review "$LAB/reviews/independent-graphics.json" \
--publication "$LAB/reviews/pre-run-publication.json"
```
Ledger state/lock enforce nofollow, regular file, owner, mode0600, single link,
stable flock inode and atomic fsync replacement. Offline tests use only
OFFLINE_TEST_ONLY ledgers and can never launch Session. Missing, stale or
superficial input rejects before debit. Logs/actions are bounded; an independent
watchdog TERM143/KILL≤146 seconds owns only registered process groups. Runtime
creation probes quota with a write in a new /tmp/smx-*0700 directory and falls
back on EDQUOT/ENOSPC to /var/tmp/smx-* if UNIX path fits. Runtime is socket-only.
All IPC uses explicit private swaymsg -s. Daily display/socket/loader variables
are excluded by allowlist. Prlimit sets core0 for every owned child; fixture
clients and Python explicitly set PR_SET_DUMPABLE0. Linux may reset dumpable on
exec of the unchanged compositor ELF: this is a stated limitation, never an
asserted native dumpable0 measurement. No system coredump cleanup is attempted.
The GPU-G3 adapter imports only the frozen sibling gpu/ modules and validates
every module origin and the exact acquisition ROOT. Its Session identity stays
GPU-G3/gpu. The adapter redirects MOSAIC_G3_DIR/NONCE and WLR_RENDERER exclusively
to compositor_environment; ordinary helpers and clients receive no hook vars.
Each epoch debits mutation+hook before arm. Rejected/incomplete A stops before
B and A-return. Native protocol validation and GL queries are owned by gpu/,
not reimplemented here. GPU background closure checks its real layer-shell role;
reference/glass check XDG, all with actual error0 exit/SHM release/wire destroys.
Additional GPU review fields: gates.B4 with the ordinary test semantic receipt;
runs.GPU-G3 gpu_source_review/gl_observability_preflight/build_review ACCEPTED,
each with *_receipt and *_receipt_sha256 whose content is independent:true,
decision ACCEPTED and exact manifest_sha256; native_gl_status NOT_RUN;
gpu_source_manifest(+SHA) schema G3-relative-source-freeze-v1; gpu_client_build_receipt
(+SHA) {status:PASS,source_sha256:<gpu/clients-g2.c>,client_sha256:<new ELF>};
gate_file(+SHA); reviewed_scene_sha256; binding {binary,client,client_sha256,
source_manifest_sha256,gate_file,reviewed_scene_sha256}. The native gate separately
binds source_review ACCEPTED, gl_observability PASS, runtime_authorized true,
diagnostic GPU-G3, binary_sha256 and exact acquisition_root. Every receipt must
be canonical owned0600 below the new LAB. Runtime identity gpu binary must differ
from every other identity. These are input contracts, never auto-written accepts.

View file

@ -0,0 +1,175 @@
"""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.spec['axis'],s.spec['boundary'])
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,mutation='resize',expected_focus=None):
# WHY: migration move.c arranges both outputs, then seat_set_focus reveals
# the current selected cell. CONTRACT: derive clamp/reveal from current row
# extents, gaps, viewport and focus; no universal800/1120 expected value.
# INVARIANT: IPC is a candidate; paint/input after each mutation qualify only
# sampled applied behavior, never a native atomic generation.
need(snap.get('state_source')!='pending-as-current','pending mixed with current')
ws=workspace(snap['tree'],'1');need(ws.get('layout','mosaic')=='mosaic','dynamic layout')
rows=ws['mosaic']['rows'];need(len(rows)==1,'runtime Row0 only');row=rows[0]
children=ws['nodes'];ids=[c['id'] for c in children]
need(len(children)>=2 and row['children']==ids and len(set(ids))==len(ids),'dynamic row order/identity')
viewport=[ws['rect'][k] for k in ('x','y','width','height')];width=viewport[2]
need(width>0,'dynamic viewport width')
widths=[c['rect']['width'] for c in children]
gaps=[b['rect']['x']-a['rect']['x']-a['rect']['width'] for a,b in zip(children,children[1:])]
need(len(set(widths))==1 and widths[0]>=640 and len(set(gaps))==1 and gaps[0]>=0,'pinned equal cell / gap geometry')
gap=gaps[0];fitting=(width-(len(children)-1)*gap)//len(children)
need(widths[0]==max(640,fitting),'native default640 sizing mismatch')
need(children[0]['rect']['x']==viewport[0],'row start differs from viewport')
content=children[-1]['rect']['x']+widths[-1]-viewport[0];maximum=max(0,content-width)
scroll=row['scroll_x'];need(0<=scroll<=maximum,'camera outside native clamp')
focused=[c for c in children if c.get('focused')]
need(len(focused)==1 and row.get('focused')==focused[0]['id'],'missing/ambiguous row refocus')
selected=focused[0]
if expected_focus is not None:need(selected['id']==expected_focus,'refocus selected another owner')
result=dict(workspace_id=ws['id'],output=ws['output'],children=ids,viewport=viewport,minimum_width=640,cell_width=widths[0],gap=gap,content_width=content,min_scroll=0,max_scroll=maximum,scroll=scroll,focused=selected['id'],geometry_status='IPC_PENDING_CANDIDATE; native generation NOT_OBSERVABLE')
if previous is not None:
need(ws['id']==previous['workspace_id'] and ids==previous['children'],'workspace/order changed')
need(mutation in ('resize','migration'),'unknown camera mutation')
candidate=max(0,min(previous['scroll'],maximum));expected=candidate
if mutation=='migration':
lo=selected['rect']['x']-viewport[0];hi=lo+selected['rect']['width']
if lo<expected:expected=max(0,lo)
elif hi>expected+width:expected=hi-width
expected=max(0,min(expected,maximum))
need(scroll==expected,'native reconstructed '+mutation+' camera mismatch')
result.update(expected_scroll=expected,resize_candidate=candidate,mutation=mutation,clamp='PASS')
return result
def dynamic_pixels(images,outputs,frames,viewport,reference=None):
# WHY/CONTRACT: each mutation must reject old-output and outside-clip paint.
# INVARIANT: sampled RGB exclusion cannot prove complete native damage.
for image,out in zip(images,outputs):
for y in range(image.height):
for x in range(image.width):
if image.getpixel((x,y)) in (COLORS['POPUP_A'],COLORS['POPUP_B']):
need(contains(viewport,png_global((x,y),out,frames[out['name']])),'dynamic popup pixel outside current viewport')
if reference is not None:exact(reference,images[1])
return dict(exclusion='PASS',whole_H2='PASS' if reference is not None else 'NOT_APPLICABLE: current owner workspace on H2',complete_damage='NOT OBSERVABLE')
def dynamic_campaign(s):
init_reference(s,2);s.cmd('gaps inner 0');state,baseline,p,g,left,right=open_chain(s)
owner_id=state['owner']['id'];previous=dynamic_geometry(state['snapshot'],expected_focus=owner_id);stages=[]
mutations=[('output HEADLESS-1 resolution 960x600 position -960 0','resized960','HEADLESS-1'),
('output HEADLESS-1 resolution 800x600 position -800 0','resized-back','HEADLESS-1'),
('move workspace to output HEADLESS-2','migrated-H2','HEADLESS-2'),
('move workspace to output HEADLESS-1','migrated-back-H1','HEADLESS-1')]
for command,tag,out in mutations:
s.cmd('workspace 1');s.record('invalidate-mutation-coordinates',tag=tag,old_viewport=previous['viewport'],old_camera=previous['scroll'],old_focus=previous['focused']);s.cmd(command,True);s.expected_output=out
if tag=='resized960':s.outputs[0]={**s.outputs[0],'logical':[-960,0,960,600],'mode':[960,600]}
elif tag=='resized-back':s.outputs[0]=dict(s.spec['outputs'][0])
time.sleep(.35);snap=s.snapshot(tag);current=dynamic_geometry(snap,previous,"migration" if tag.startswith("migrated") else "resize",owner_id)
need(current['output']==out and s.find('owner')['id']==owner_id,'dynamic exact owner/output')
expected_viewport=next(o['logical'] for o in s.outputs if o['name']==out)
need(current['viewport']==expected_viewport,'dynamic viewport/output reconstruction')
pictures=s.pair_capture(tag)
pixels=dynamic_pixels(pictures,s.outputs,s.frames,current['viewport'],baseline if out=='HEADLESS-1' else None)
# An alive chain must supply fresh paint and local pointer input; an
# actual XDG dismissal is recorded without injecting on destroyed popups.
if 'POPUP_DONE ' not in s.log('owner'):
fresh=s.owner('owner');geo=geometry(s.log('owner'),fresh['origin'],Provenance(owner_id,current['workspace_id'],'NOT OBSERVABLE','fresh mutation IPC + paint + pointer'))
index=next(i for i,o in enumerate(s.outputs) if o['name']==out)
point=solid_point(pictures[index],COLORS['POPUP_B'],s.outputs[index],s.frames[out],geo['B'])
tails,buttons=s.click('owner',point);s.delivery('owner',geo['B'],point,tails,buttons,'POPUP_B')
need(validators.grab(s.log('owner'))['ids']==p['ids'],'dynamic popup IDs changed')
input_proof=dict(status='PASS',point=point)
else:input_proof=dict(status='NOT_EXERCISED',reason='XDG dismissal; no injection on destroyed popup')
stages.append(dict(tag=tag,geometry=current,pixels=pixels,input=input_proof));previous=current
s.save('dynamic-ownership',dict(status='PASS',stages=stages,ids_before=p['ids'],atomic_generation='NOT PROVEN',complete_damage='NOT OBSERVABLE'))
s.close_client('owner')
for name in list(s.proc):
if name.startswith('filler'):s.close_client(name,'close')
final=s.pair_capture('dynamic-final');exact(baseline,final[1]);need(not any(count(im,COLORS[n]) for im in final for n in ('POPUP_A','POPUP_B')),'dynamic final popup residual');s.save('dynamic-final-restoration',dict(status='PARTIALLY_FEASIBLE',H2='EXACT',captured_after_close=True));return 'PARTIALLY_FEASIBLE'

View file

@ -0,0 +1,310 @@
#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <wayland-client.h>
#include "xdg-shell-client.h"
/* EXPERIMENTAL CONTRACT: every popup owns distinct XDG objects. B's actual
* parent is A's xdg_surface; logs name object IDs as well as colors. */
struct surface {
const char *name;
struct wl_surface *wl;
struct xdg_surface *xdg;
struct xdg_popup *popup;
bool configured, mapped, done;
int width, height, anchor_x, anchor_y;
uint32_t color;
};
static struct wl_display *display;
static struct wl_compositor *compositor;
static struct wl_shm *shm;
static struct xdg_wm_base *wm;
static struct wl_seat *seat;
static struct wl_pointer *pointer;
static struct xdg_toplevel *top_role;
static struct surface top = {.name="TOPLEVEL", .width=420, .height=320, .color=0x002344cc};
static struct surface a = {.name="POPUP_A", .width=300, .height=170, .color=0x00e0c020};
static struct surface b = {.name="POPUP_B", .width=300, .height=130, .color=0x0020d0a0};
static volatile sig_atomic_t running = 1;
static struct surface *entered;
/* Observe client-side SHM release separately from native GPU retirement.
* Keep protocol buffer alive until release; no backing memory is reused. */
static unsigned shm_created, shm_released;
static void buffer_release(void *data, struct wl_buffer *buffer) {
(void)data;
++shm_released;
fprintf(stderr,"SHM_RELEASE buffer=%u released=%u created=%u\n",
wl_proxy_get_id((struct wl_proxy *)buffer),shm_released,shm_created);
wl_buffer_destroy(buffer);
}
static const struct wl_buffer_listener buffer_listener = {.release=buffer_release};
static void draw(struct surface *s) {
size_t bytes = (size_t)s->width * (size_t)s->height * 4;
char path[4096];
const char *scratch = getenv("TMPDIR");
const char *lab = getenv("SMX_LAB");
/* WHY: SHM belongs to LAB, never the short socket-only runtime.
* CONTRACT: launcher supplies canonical LAB/scratch; fail before mkstemp. */
char expected[4096];
int expected_len = lab ? snprintf(expected, sizeof(expected), "%s/scratch", lab) : -1;
int path_len = scratch ? snprintf(path, sizeof(path), "%s/smx-shm-XXXXXX", scratch) : -1;
if (!lab || strncmp(lab, "/var/tmp/", 9) || strstr(lab, "/../") ||
expected_len < 0 || expected_len >= (int)sizeof(expected) ||
!scratch || strcmp(scratch, expected) || path_len < 0 ||
path_len >= (int)sizeof(path)) {
fprintf(stderr, "invalid LAB scratch for SHM\n");
exit(1);
}
int fd = mkstemp(path);
if (fd < 0 || ftruncate(fd, (off_t)bytes) < 0) { perror("buffer"); exit(1); }
unlink(path);
uint32_t *pixels = mmap(NULL, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (pixels == MAP_FAILED) { perror("mmap"); exit(1); }
for (size_t i=0; i<bytes/4; ++i) pixels[i] = s->color;
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, (int)bytes);
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, s->width, s->height,
s->width*4, WL_SHM_FORMAT_XRGB8888);
++shm_created;
wl_buffer_add_listener(buffer,&buffer_listener,NULL);
wl_surface_attach(s->wl, buffer, 0, 0);
wl_surface_damage_buffer(s->wl, 0, 0, s->width, s->height);
wl_surface_commit(s->wl);
wl_shm_pool_destroy(pool);
munmap(pixels, bytes);
close(fd);
s->mapped = true;
fprintf(stderr, "%s MAP wl=%u xdg=%u\n", s->name,
wl_proxy_get_id((struct wl_proxy *)s->wl), wl_proxy_get_id((struct wl_proxy *)s->xdg));
}
static void on_ping(void *data, struct xdg_wm_base *base, uint32_t serial) {
xdg_wm_base_pong(base, serial);
}
static const struct xdg_wm_base_listener wm_listener = {.ping=on_ping};
static void on_xdg_configure(void *data, struct xdg_surface *xdg, uint32_t serial) {
struct surface *s=data;
fprintf(stderr, "%s XDG_CONFIGURE serial=%u ack\n", s->name, serial);
xdg_surface_ack_configure(xdg, serial);
s->configured=true;
if (!s->done) draw(s); /* fresh native commit after every configure */
}
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);
/* WHY/CONTRACT/INVARIANT: explicit identical adjustment in both controls. */
xdg_positioner_set_constraint_adjustment(pos, 0);
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 &top;
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) {
/* WHY/CONTRACT/INVARIANT: no core dump or dumpable client in this LAB. */
struct rlimit no_core = {0, 0};
if (setrlimit(RLIMIT_CORE, &no_core) || prctl(PR_SET_DUMPABLE, 0)) return 1;
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. */
/* WHY/CONTRACT/INVARIANT: fullscreen group geometry is measured; legal
* anchors and wide buffers request crossing without forcing native placement. */
if (!strcmp(placement,"fullscreen_cross")) {
a.anchor_x=350; a.anchor_y=80; a.width=900;
b.anchor_x=100; b.anchor_y=60; b.width=900;
} else 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,&registry_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;
}

View file

@ -0,0 +1,303 @@
#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <wayland-client.h>
#include "xdg-shell-client.h"
/* EXPERIMENTAL CONTRACT: every popup owns distinct XDG objects. B's actual
* parent is A's xdg_surface; logs name object IDs as well as colors. */
struct surface {
const char *name;
struct wl_surface *wl;
struct xdg_surface *xdg;
struct xdg_popup *popup;
bool configured, mapped, done;
int width, height, anchor_x, anchor_y;
uint32_t color;
};
static struct wl_display *display;
static struct wl_compositor *compositor;
static struct wl_shm *shm;
static struct xdg_wm_base *wm;
static struct wl_seat *seat;
static struct wl_pointer *pointer;
static struct xdg_toplevel *top_role;
static struct surface top = {.name="TOPLEVEL", .width=420, .height=320, .color=0x002344cc};
static struct surface a = {.name="POPUP_A", .width=300, .height=170, .color=0x00e0c020};
static struct surface b = {.name="POPUP_B", .width=300, .height=130, .color=0x0020d0a0};
static volatile sig_atomic_t running = 1;
static struct surface *entered;
/* Observe client-side SHM release separately from native GPU retirement.
* Keep protocol buffer alive until release; no backing memory is reused. */
static unsigned shm_created, shm_released;
static void buffer_release(void *data, struct wl_buffer *buffer) {
(void)data;
++shm_released;
fprintf(stderr,"SHM_RELEASE buffer=%u released=%u created=%u\n",
wl_proxy_get_id((struct wl_proxy *)buffer),shm_released,shm_created);
wl_buffer_destroy(buffer);
}
static const struct wl_buffer_listener buffer_listener = {.release=buffer_release};
static void draw(struct surface *s) {
size_t bytes = (size_t)s->width * (size_t)s->height * 4;
char path[4096];
const char *scratch = getenv("TMPDIR");
const char *lab = getenv("SMX_LAB");
/* WHY: SHM belongs to LAB, never the short socket-only runtime.
* CONTRACT: launcher supplies canonical LAB/scratch; fail before mkstemp. */
char expected[4096];
int expected_len = lab ? snprintf(expected, sizeof(expected), "%s/scratch", lab) : -1;
int path_len = scratch ? snprintf(path, sizeof(path), "%s/smx-shm-XXXXXX", scratch) : -1;
if (!lab || strncmp(lab, "/var/tmp/", 9) || strstr(lab, "/../") ||
expected_len < 0 || expected_len >= (int)sizeof(expected) ||
!scratch || strcmp(scratch, expected) || path_len < 0 ||
path_len >= (int)sizeof(path)) {
fprintf(stderr, "invalid LAB scratch for SHM\n");
exit(1);
}
int fd = mkstemp(path);
if (fd < 0 || ftruncate(fd, (off_t)bytes) < 0) { perror("buffer"); exit(1); }
unlink(path);
uint32_t *pixels = mmap(NULL, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (pixels == MAP_FAILED) { perror("mmap"); exit(1); }
for (size_t i=0; i<bytes/4; ++i) pixels[i] = s->color;
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, (int)bytes);
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, s->width, s->height,
s->width*4, WL_SHM_FORMAT_XRGB8888);
++shm_created;
wl_buffer_add_listener(buffer,&buffer_listener,NULL);
wl_surface_attach(s->wl, buffer, 0, 0);
wl_surface_damage_buffer(s->wl, 0, 0, s->width, s->height);
wl_surface_commit(s->wl);
wl_shm_pool_destroy(pool);
munmap(pixels, bytes);
close(fd);
s->mapped = true;
fprintf(stderr, "%s MAP wl=%u xdg=%u\n", s->name,
wl_proxy_get_id((struct wl_proxy *)s->wl), wl_proxy_get_id((struct wl_proxy *)s->xdg));
}
static void on_ping(void *data, struct xdg_wm_base *base, uint32_t serial) {
xdg_wm_base_pong(base, serial);
}
static const struct xdg_wm_base_listener wm_listener = {.ping=on_ping};
static void on_xdg_configure(void *data, struct xdg_surface *xdg, uint32_t serial) {
struct surface *s=data;
fprintf(stderr, "%s XDG_CONFIGURE serial=%u ack\n", s->name, serial);
xdg_surface_ack_configure(xdg, serial);
s->configured=true;
if (!s->mapped && !s->done) draw(s);
}
static const struct xdg_surface_listener xdg_listener = {.configure=on_xdg_configure};
static void on_top_configure(void *data, struct xdg_toplevel *role, int32_t w,
int32_t h, struct wl_array *states) {
fprintf(stderr, "TOPLEVEL ROLE_CONFIGURE w=%d h=%d\n", w,h);
}
static void on_top_close(void *data, struct xdg_toplevel *role) {
fprintf(stderr, "TOPLEVEL CLOSE_REQUEST\n"); running=0;
}
static const struct xdg_toplevel_listener top_listener = {
.configure=on_top_configure, .close=on_top_close,
};
static void on_popup_configure(void *data, struct xdg_popup *role, int32_t x,
int32_t y, int32_t w, int32_t h) {
struct surface *s=data;
fprintf(stderr, "%s ROLE_CONFIGURE x=%d y=%d w=%d h=%d\n",s->name,x,y,w,h);
}
static void on_popup_done(void *data, struct xdg_popup *role) {
struct surface *s=data;
s->done=true;
s->mapped=false;
fprintf(stderr, "%s POPUP_DONE role=%u\n",s->name,wl_proxy_get_id((struct wl_proxy *)role));
}
static const struct xdg_popup_listener popup_listener = {
.configure=on_popup_configure, .popup_done=on_popup_done,
};
/* WHY: XDG requires child-first destruction even if the compositor sends
* dismissals for multiple levels in one dispatch batch. */
static void destroy_popup(struct surface *s) {
if (!s->popup) return;
fprintf(stderr,"%s DESTROY role=%u xdg=%u wl=%u\n",s->name,
wl_proxy_get_id((struct wl_proxy *)s->popup),
wl_proxy_get_id((struct wl_proxy *)s->xdg),
wl_proxy_get_id((struct wl_proxy *)s->wl));
xdg_popup_destroy(s->popup);
xdg_surface_destroy(s->xdg);
wl_surface_destroy(s->wl);
s->popup=NULL; s->xdg=NULL; s->wl=NULL; s->mapped=false;
if (entered==s) entered=NULL;
}
static void collect_dismissals(void) {
if (b.done) destroy_popup(&b);
if (a.done) {
destroy_popup(&b);
destroy_popup(&a);
}
}
static void open_popup(struct surface *s, struct surface *parent, uint32_t serial) {
if (s->popup || !parent->mapped || !serial) return;
s->wl=wl_compositor_create_surface(compositor);
s->xdg=xdg_wm_base_get_xdg_surface(wm,s->wl);
xdg_surface_add_listener(s->xdg,&xdg_listener,s);
struct xdg_positioner *pos=xdg_wm_base_create_positioner(wm);
xdg_positioner_set_size(pos,s->width,s->height);
xdg_positioner_set_anchor_rect(pos,s->anchor_x,s->anchor_y,1,1);
xdg_positioner_set_anchor(pos,XDG_POSITIONER_ANCHOR_TOP_LEFT);
xdg_positioner_set_gravity(pos,XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
s->popup=xdg_surface_get_popup(s->xdg,parent->xdg,pos);
xdg_popup_add_listener(s->popup,&popup_listener,s);
fprintf(stderr,"%s CREATE wl=%u xdg=%u role=%u parent=%s parent_xdg=%u\n",
s->name,wl_proxy_get_id((struct wl_proxy *)s->wl),
wl_proxy_get_id((struct wl_proxy *)s->xdg),
wl_proxy_get_id((struct wl_proxy *)s->popup),parent->name,
wl_proxy_get_id((struct wl_proxy *)parent->xdg));
fprintf(stderr,"%s GRAB_REQUEST origin=wl_pointer.button target=%s serial=%u used=%u\n",
s->name,parent->name,serial,serial);
xdg_popup_grab(s->popup,seat,serial);
wl_surface_commit(s->wl); /* Initial bufferless commit requests configure. */
xdg_positioner_destroy(pos);
}
static struct surface *which(struct wl_surface *wl) {
if (wl==top.wl) return &top;
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) {
/* WHY/CONTRACT/INVARIANT: no core dump or dumpable client in this LAB. */
struct rlimit no_core = {0, 0};
if (setrlimit(RLIMIT_CORE, &no_core) || prctl(PR_SET_DUMPABLE, 0)) return 1;
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,&registry_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;
}

View file

@ -0,0 +1,62 @@
#define _DEFAULT_SOURCE
#include <linux/input-event-codes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <wayland-client.h>
#include "virtual-pointer-client.h"
static struct zwlr_virtual_pointer_manager_v1 *manager;
static void global(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version) {
if (!strcmp(interface, zwlr_virtual_pointer_manager_v1_interface.name))
manager=wl_registry_bind(registry,name,&zwlr_virtual_pointer_manager_v1_interface,
version < 2 ? version : 2);
}
static void removed(void *data, struct wl_registry *registry, uint32_t name) {}
int main(void) {
/* WHY/CONTRACT/INVARIANT: no core dump or dumpable client in this LAB. */
struct rlimit no_core = {0, 0};
if (setrlimit(RLIMIT_CORE, &no_core) || prctl(PR_SET_DUMPABLE, 0)) return 1;
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;
}

View file

@ -0,0 +1,258 @@
#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <wayland-client.h>
#include "xdg-shell-client.h"
/* WHY: the compositor must wait on a real, responsive XDG client.
* CONTRACT: hold only a received configure; ACK the latest received serial.
* INVARIANT: Wayland dispatch and ping replies continue while ACK is held. */
static struct wl_display *display;
static struct wl_compositor *compositor;
static struct wl_shm *shm;
static struct xdg_wm_base *wm;
static struct wl_surface *surface;
static struct xdg_surface *xdg;
static struct xdg_toplevel *toplevel;
static struct wl_seat *seat;
static struct wl_pointer *pointer;
static volatile sig_atomic_t running = 1;
static bool mapped, hold_next, held;
static uint32_t latest_serial;
static int32_t width = 420, height = 320, configured_width = 420, configured_height = 320;
static unsigned generation;
static long long stamp(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (long long)ts.tv_sec * 1000000000LL + ts.tv_nsec;
}
/* Observe client-side SHM release separately from native GPU retirement.
* Keep protocol buffer alive until release; no backing memory is reused. */
static unsigned shm_created, shm_released;
static void buffer_release(void *data, struct wl_buffer *buffer) {
(void)data;
++shm_released;
fprintf(stderr,"SHM_RELEASE buffer=%u released=%u created=%u\n",
wl_proxy_get_id((struct wl_proxy *)buffer),shm_released,shm_created);
wl_buffer_destroy(buffer);
}
static const struct wl_buffer_listener buffer_listener = {.release=buffer_release};
static void draw(void) {
if (configured_width > 0) width = configured_width;
if (configured_height > 0) height = configured_height;
if (width <= 0 || height <= 0 || width > 4096 || height > 4096) {
fprintf(stderr, "INVALID_SIZE %d %d\n", width, height);
exit(2);
}
size_t bytes = (size_t)width * (size_t)height * 4;
char name[4096];
const char *scratch = getenv("TMPDIR");
const char *lab = getenv("SMX_LAB");
/* WHY: SHM belongs to LAB, never the short socket-only runtime.
* CONTRACT: launcher supplies canonical LAB/scratch; fail before mkstemp. */
char expected[4096];
int expected_len = lab ? snprintf(expected, sizeof(expected), "%s/scratch", lab) : -1;
int path_len = scratch ? snprintf(name, sizeof(name), "%s/smx-shm-XXXXXX", scratch) : -1;
if (!lab || strncmp(lab, "/var/tmp/", 9) || strstr(lab, "/../") ||
expected_len < 0 || expected_len >= (int)sizeof(expected) ||
!scratch || strcmp(scratch, expected) || path_len < 0 ||
path_len >= (int)sizeof(name)) {
fprintf(stderr, "invalid LAB scratch for SHM\n");
exit(1);
}
int fd = mkstemp(name);
if (fd < 0 || ftruncate(fd, (off_t)bytes) < 0) { perror("shm"); exit(2); }
unlink(name);
uint32_t *pixels = mmap(NULL, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (pixels == MAP_FAILED) { perror("mmap"); exit(2); }
uint32_t color = (generation++ % 2) ? 0x0020d0a0 : 0x002344cc;
#ifdef STATIC_REFERENCE
/* CONTRACT: H2 uses one opaque SHM color across every real configure. */
color = 0x00303038;
#endif
for (size_t i = 0; i < bytes / 4; ++i) pixels[i] = color;
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, (int)bytes);
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, width, height,
width * 4, WL_SHM_FORMAT_XRGB8888);
++shm_created;
wl_buffer_add_listener(buffer,&buffer_listener,NULL);
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_damage_buffer(surface, 0, 0, width, height);
wl_surface_commit(surface);
fprintf(stderr, "COMMIT ns=%lld serial=%u size=%dx%d color=%08x\n",
stamp(), latest_serial, width, height, color);
wl_shm_pool_destroy(pool);
munmap(pixels, bytes);
close(fd);
mapped = true;
}
static void ack_and_commit(void) {
if (!latest_serial) return;
xdg_surface_ack_configure(xdg, latest_serial);
fprintf(stderr, "ACK ns=%lld serial=%u size=%dx%d\n",
stamp(), latest_serial, configured_width, configured_height);
draw();
held = false;
}
static void ping(void *data, struct xdg_wm_base *base, uint32_t serial) {
xdg_wm_base_pong(base, serial);
fprintf(stderr, "PONG ns=%lld serial=%u\n", stamp(), serial);
}
static const struct xdg_wm_base_listener wm_listener = {.ping = ping};
static void role_configure(void *data, struct xdg_toplevel *role,
int32_t w, int32_t h, struct wl_array *states) {
if (w > 0) configured_width = w;
if (h > 0) configured_height = h;
fprintf(stderr, "ROLE_CONFIGURE ns=%lld size=%dx%d\n", stamp(), w, h);
}
static void role_close(void *data, struct xdg_toplevel *role) { running = 0; }
static const struct xdg_toplevel_listener role_listener = {
.configure = role_configure, .close = role_close,
};
static void configure(void *data, struct xdg_surface *s, uint32_t serial) {
latest_serial = serial;
fprintf(stderr, "XDG_CONFIGURE ns=%lld serial=%u size=%dx%d hold=%d previous=%d\n",
stamp(), serial, configured_width, configured_height, hold_next || held, held);
if (hold_next || held) {
held = true;
hold_next = false;
fprintf(stderr, "HELD ns=%lld serial=%u\n", stamp(), serial);
} else {
ack_and_commit();
}
}
static const struct xdg_surface_listener surface_listener = {.configure = configure};
static void pointer_enter(void *data, struct wl_pointer *p, uint32_t serial,
struct wl_surface *s, wl_fixed_t x, wl_fixed_t y) {
fprintf(stderr, "ENTER ns=%lld x=%.2f y=%.2f\n", stamp(),
wl_fixed_to_double(x), wl_fixed_to_double(y));
}
static void pointer_leave(void *data, struct wl_pointer *p, uint32_t serial,
struct wl_surface *s) { fprintf(stderr, "LEAVE ns=%lld\n", stamp()); }
static void pointer_motion(void *data, struct wl_pointer *p, uint32_t time,
wl_fixed_t x, wl_fixed_t y) {
fprintf(stderr, "MOTION ns=%lld x=%.2f y=%.2f\n", stamp(),
wl_fixed_to_double(x), wl_fixed_to_double(y));
}
static void pointer_button(void *data, struct wl_pointer *p, uint32_t serial,
uint32_t time, uint32_t button, uint32_t state) {
fprintf(stderr, "BUTTON ns=%lld serial=%u button=%u state=%u\n",
stamp(), serial, button, state);
}
static void pointer_axis(void *data, struct wl_pointer *p, uint32_t t,
uint32_t axis, wl_fixed_t value) {}
static void pointer_frame(void *data, struct wl_pointer *p) {}
static const struct wl_pointer_listener pointer_listener = {
.enter = pointer_enter, .leave = pointer_leave, .motion = pointer_motion,
.button = pointer_button, .axis = pointer_axis, .frame = pointer_frame,
};
static void capabilities(void *data, struct wl_seat *s, uint32_t caps) {
if ((caps & WL_SEAT_CAPABILITY_POINTER) && !pointer) {
pointer = wl_seat_get_pointer(s);
wl_pointer_add_listener(pointer, &pointer_listener, NULL);
}
}
static void seat_name(void *data, struct wl_seat *s, const char *name) {}
static const struct wl_seat_listener seat_listener = {
.capabilities = capabilities, .name = seat_name,
};
static void global(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version) {
if (!strcmp(interface, wl_compositor_interface.name))
compositor = wl_registry_bind(registry, name, &wl_compositor_interface, version < 4 ? version : 4);
else if (!strcmp(interface, wl_shm_interface.name))
shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
else if (!strcmp(interface, xdg_wm_base_interface.name))
wm = wl_registry_bind(registry, name, &xdg_wm_base_interface, version < 3 ? version : 3);
else if (!strcmp(interface, wl_seat_interface.name)) {
seat = wl_registry_bind(registry, name, &wl_seat_interface, version < 5 ? version : 5);
wl_seat_add_listener(seat, &seat_listener, NULL);
}
}
static void global_remove(void *data, struct wl_registry *registry, uint32_t name) {}
static const struct wl_registry_listener registry_listener = {
.global = global, .global_remove = global_remove,
};
static void stop(int signum) { running = 0; }
int main(void) {
/* WHY/CONTRACT/INVARIANT: no core dump or dumpable client in this LAB. */
struct rlimit no_core = {0, 0};
if (setrlimit(RLIMIT_CORE, &no_core) || prctl(PR_SET_DUMPABLE, 0)) return 1;
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, &registry_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;
}

View file

@ -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"

View file

@ -0,0 +1,313 @@
#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <wayland-client.h>
#include "xdg-shell-client.h"
/* EXPERIMENTAL CONTRACT: every popup owns distinct XDG objects. B's actual
* parent is A's xdg_surface; logs name object IDs as well as colors. */
struct surface {
const char *name;
struct wl_surface *wl;
struct xdg_surface *xdg;
struct xdg_popup *popup;
bool configured, mapped, done;
int width, height, anchor_x, anchor_y;
uint32_t color;
};
static struct wl_display *display;
static struct wl_compositor *compositor;
static struct wl_shm *shm;
static struct xdg_wm_base *wm;
static struct wl_seat *seat;
static struct wl_pointer *pointer;
static struct xdg_toplevel *top_role;
static struct surface top = {.name="TOPLEVEL", .width=420, .height=320, .color=0x002344cc};
static struct surface a = {.name="POPUP_A", .width=300, .height=170, .color=0x00e0c020};
static struct surface b = {.name="POPUP_B", .width=300, .height=130, .color=0x0020d0a0};
static volatile sig_atomic_t running = 1;
static struct surface *entered;
/* Observe client-side SHM release separately from native GPU retirement.
* Keep protocol buffer alive until release; no backing memory is reused. */
static unsigned shm_created, shm_released;
static void buffer_release(void *data, struct wl_buffer *buffer) {
(void)data;
++shm_released;
fprintf(stderr,"SHM_RELEASE buffer=%u released=%u created=%u\n",
wl_proxy_get_id((struct wl_proxy *)buffer),shm_released,shm_created);
wl_buffer_destroy(buffer);
}
static const struct wl_buffer_listener buffer_listener = {.release=buffer_release};
static void draw(struct surface *s) {
size_t bytes = (size_t)s->width * (size_t)s->height * 4;
char path[4096];
const char *scratch = getenv("TMPDIR");
const char *lab = getenv("SMX_LAB");
/* WHY: SHM belongs to LAB, never the short socket-only runtime.
* CONTRACT: launcher supplies canonical LAB/scratch; fail before mkstemp. */
char expected[4096];
int expected_len = lab ? snprintf(expected, sizeof(expected), "%s/scratch", lab) : -1;
int path_len = scratch ? snprintf(path, sizeof(path), "%s/smx-shm-XXXXXX", scratch) : -1;
if (!lab || strncmp(lab, "/var/tmp/", 9) || strstr(lab, "/../") ||
expected_len < 0 || expected_len >= (int)sizeof(expected) ||
!scratch || strcmp(scratch, expected) || path_len < 0 ||
path_len >= (int)sizeof(path)) {
fprintf(stderr, "invalid LAB scratch for SHM\n");
exit(1);
}
int fd = mkstemp(path);
if (fd < 0 || ftruncate(fd, (off_t)bytes) < 0) { perror("buffer"); exit(1); }
unlink(path);
uint32_t *pixels = mmap(NULL, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (pixels == MAP_FAILED) { perror("mmap"); exit(1); }
for (size_t i=0; i<bytes/4; ++i) pixels[i] = s->color;
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, (int)bytes);
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, s->width, s->height,
s->width*4, WL_SHM_FORMAT_XRGB8888);
++shm_created;
wl_buffer_add_listener(buffer,&buffer_listener,NULL);
wl_surface_attach(s->wl, buffer, 0, 0);
wl_surface_damage_buffer(s->wl, 0, 0, s->width, s->height);
wl_surface_commit(s->wl);
wl_shm_pool_destroy(pool);
munmap(pixels, bytes);
close(fd);
s->mapped = true;
fprintf(stderr, "%s MAP wl=%u xdg=%u\n", s->name,
wl_proxy_get_id((struct wl_proxy *)s->wl), wl_proxy_get_id((struct wl_proxy *)s->xdg));
}
static void on_ping(void *data, struct xdg_wm_base *base, uint32_t serial) {
xdg_wm_base_pong(base, serial);
}
static const struct xdg_wm_base_listener wm_listener = {.ping=on_ping};
static void on_xdg_configure(void *data, struct xdg_surface *xdg, uint32_t serial) {
struct surface *s=data;
fprintf(stderr, "%s XDG_CONFIGURE serial=%u ack\n", s->name, serial);
xdg_surface_ack_configure(xdg, serial);
s->configured=true;
if (!s->mapped && !s->done) draw(s);
}
static const struct xdg_surface_listener xdg_listener = {.configure=on_xdg_configure};
static void on_top_configure(void *data, struct xdg_toplevel *role, int32_t w,
int32_t h, struct wl_array *states) {
fprintf(stderr, "TOPLEVEL ROLE_CONFIGURE w=%d h=%d\n", w,h);
}
static void on_top_close(void *data, struct xdg_toplevel *role) {
fprintf(stderr, "TOPLEVEL CLOSE_REQUEST\n"); running=0;
}
static const struct xdg_toplevel_listener top_listener = {
.configure=on_top_configure, .close=on_top_close,
};
static void on_popup_configure(void *data, struct xdg_popup *role, int32_t x,
int32_t y, int32_t w, int32_t h) {
struct surface *s=data;
fprintf(stderr, "%s ROLE_CONFIGURE x=%d y=%d w=%d h=%d\n",s->name,x,y,w,h);
}
static void on_popup_done(void *data, struct xdg_popup *role) {
struct surface *s=data;
s->done=true;
s->mapped=false;
fprintf(stderr, "%s POPUP_DONE role=%u\n",s->name,wl_proxy_get_id((struct wl_proxy *)role));
}
static const struct xdg_popup_listener popup_listener = {
.configure=on_popup_configure, .popup_done=on_popup_done,
};
/* WHY: XDG requires child-first destruction even if the compositor sends
* dismissals for multiple levels in one dispatch batch. */
static void destroy_popup(struct surface *s) {
if (!s->popup) return;
fprintf(stderr,"%s DESTROY role=%u xdg=%u wl=%u\n",s->name,
wl_proxy_get_id((struct wl_proxy *)s->popup),
wl_proxy_get_id((struct wl_proxy *)s->xdg),
wl_proxy_get_id((struct wl_proxy *)s->wl));
xdg_popup_destroy(s->popup);
xdg_surface_destroy(s->xdg);
wl_surface_destroy(s->wl);
s->popup=NULL; s->xdg=NULL; s->wl=NULL; s->mapped=false;
if (entered==s) entered=NULL;
}
static void collect_dismissals(void) {
if (b.done) destroy_popup(&b);
if (a.done) {
destroy_popup(&b);
destroy_popup(&a);
}
}
static void open_popup(struct surface *s, struct surface *parent, uint32_t serial) {
if (s->popup || !parent->mapped || !serial) return;
s->wl=wl_compositor_create_surface(compositor);
s->xdg=xdg_wm_base_get_xdg_surface(wm,s->wl);
xdg_surface_add_listener(s->xdg,&xdg_listener,s);
struct xdg_positioner *pos=xdg_wm_base_create_positioner(wm);
xdg_positioner_set_size(pos,s->width,s->height);
xdg_positioner_set_anchor_rect(pos,s->anchor_x,s->anchor_y,1,1);
xdg_positioner_set_anchor(pos,XDG_POSITIONER_ANCHOR_TOP_LEFT);
xdg_positioner_set_gravity(pos,XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
s->popup=xdg_surface_get_popup(s->xdg,parent->xdg,pos);
xdg_popup_add_listener(s->popup,&popup_listener,s);
fprintf(stderr,"%s CREATE wl=%u xdg=%u role=%u parent=%s parent_xdg=%u\n",
s->name,wl_proxy_get_id((struct wl_proxy *)s->wl),
wl_proxy_get_id((struct wl_proxy *)s->xdg),
wl_proxy_get_id((struct wl_proxy *)s->popup),parent->name,
wl_proxy_get_id((struct wl_proxy *)parent->xdg));
fprintf(stderr,"%s GRAB_REQUEST origin=wl_pointer.button target=%s serial=%u used=%u\n",
s->name,parent->name,serial,serial);
xdg_popup_grab(s->popup,seat,serial);
wl_surface_commit(s->wl); /* Initial bufferless commit requests configure. */
xdg_positioner_destroy(pos);
}
static struct surface *which(struct wl_surface *wl) {
if (wl==top.wl) return &top;
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) {
/* WHY/CONTRACT/INVARIANT: no core dump or dumpable client in this LAB. */
struct rlimit no_core = {0, 0};
if (setrlimit(RLIMIT_CORE, &no_core) || prctl(PR_SET_DUMPABLE, 0)) return 1;
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,&registry_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;
}

View file

@ -0,0 +1,345 @@
"""Bounded owned headless session, usable only after the separate matrix review gate."""
import errno,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,disable_dump,durable
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)}
def storage_guard(lab):
# WHY/CONTRACT/INVARIANT: stop before new action at 9GiB or <20GiB free;
# never purge old evidence to manufacture space.
v=os.statvfs(lab);need(v.f_bavail*v.f_frsize>=20*1024**3,'SSD free space below20GiB')
total=sum(p.lstat().st_size for p in lab.rglob('*') if p.is_file() and not p.is_symlink())
need(total<=9*1024**3,'new LAB exceeds9GiB prudent stop')
def private_runtime():
# WHY: tmpfs may have user quota exhaustion despite statvfs free space.
# CONTRACT: probe an owned 0700 directory by writing, then fall back only
# on EDQUOT/ENOSPC. INVARIANT: socket-only path stays UNIX-length-safe.
for base in ('/tmp','/var/tmp'):
runtime=None
try:
v=os.statvfs(base)
if v.f_bavail*v.f_frsize<1024*1024:raise OSError(errno.ENOSPC,'runtime free space')
runtime=Path(tempfile.mkdtemp(prefix='smx-',dir=base));runtime.chmod(0o700)
need(len(os.fsencode(runtime/'sway-ipc.4294967295.4294967295.sock'))<108,'UNIX socket path too long')
fd=os.open(runtime/'quota-probe',os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600)
try:os.write(fd,b'X'*4096);os.fsync(fd)
finally:os.close(fd)
(runtime/'quota-probe').unlink();return runtime
except OSError as exc:
if runtime is not None:
p=runtime/'quota-probe'
if p.exists():p.unlink()
runtime.rmdir()
if exc.errno not in (errno.EDQUOT,errno.ENOSPC):raise
if base=='/var/tmp':raise
raise ValueError('no safe private runtime')
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)
disable_dump();self.runtime=private_runtime()
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.transient={};self.tool_serial=0;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()
if spec['id'] in ('OM-ROOT','OM-NESTED'):
from oom import process_environment
self.oom_env=process_environment(self.lab,spec['id'],auth['token'])
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'] in ('core','oom','gpu'):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/'followup-work/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):
durable(self.run/(name+'.json'),value)
def debit(self,*kinds):
storage_guard(self.lab);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')
# WHY/CONTRACT/INVARIANT: diagnostics are compositor-private and
# never enter the environment shared with capture, IPC or clients.
child_env={**self.env,**({'WAYLAND_DEBUG':'1'} if debug else {})}
need(not any(k.startswith(('SMX_OOM_','MOSAIC_G2_','MOSAIC_G3_')) for k in child_env),'diagnostic environment leaked into common environment')
if name=='compositor':
child_env.update(getattr(self,'oom_env',{}));child_env.update(getattr(self,'compositor_environment',{}))
p=subprocess.Popen(actual_argv,stdin=subprocess.PIPE,stdout=f,stderr=subprocess.STDOUT,text=True,start_new_session=True,env=child_env)
self.proc[name]=p
self.record('spawn',name=name,pid=p.pid,requested_argv=requested_argv,actual_argv=actual_argv);return p
def log(self,name):return (self.run/(name+'.log')).read_text(errors='replace')
def wait(self,fn,seconds=5):
end=min(self.started+143,time.monotonic()+seconds)
while time.monotonic()<end:
need(not self.expired,'watchdog')
if hasattr(self,'comp'):need(self.comp.poll() is None,'unexpected compositor exit')
v=fn()
if v:return v
time.sleep(.02)
raise Ambiguous('bounded wait timeout')
def remaining(self,deadline=146):
return max(.001,self.started+deadline-time.monotonic())
def signal_owned(self,sig):
with getattr(self,'proc_lock',threading.RLock()):
for p in [*self.proc.values(),*self.transient.values()]:
try:os.killpg(p.pid,sig)
except ProcessLookupError:pass
def watchdog(self):
# WHY: main-thread protocol closure may block or fail.
# CONTRACT: autonomous TERM at143, KILL by146, below150s/run.
# INVARIANT: only registered private session groups are signalled;
# the main cleanup shares this absolute deadline, never N grace periods.
while not self.finished.wait(.02):
if time.monotonic()>=self.started+143 or any(p.stat().st_size>32*1024*1024 for p in self.run.glob('*.log')):
with getattr(self,'proc_lock',threading.RLock()):self.expired=True
self.signal_owned(signal.SIGTERM)
grace_end=min(self.started+146,time.monotonic()+2)
if not self.finished.wait(max(0,grace_end-time.monotonic())):
self.signal_owned(signal.SIGKILL)
return
def command_process(self,argv,timeout=4):
# WHY: capture/IPC helpers also belong to the autonomous watchdog.
# CONTRACT: bounded file output and owned session groups for every tool.
# INVARIANT: debit precedes spawn; raw tool status remains independent.
self.debit('tool');self.tool_serial+=1;tag='tool-'+str(self.tool_serial)
outpath=self.run/(tag+'.stdout.log');errpath=self.run/(tag+'.stderr.log')
with outpath.open('w') as stdout,errpath.open('w') as stderr:
with self.proc_lock:
need(not self.expired,'expired before helper spawn')
argv=list(map(str,argv));actual=['prlimit','--core=0:0','--',*argv]
p=subprocess.Popen(actual,env=self.env,stdout=stdout,stderr=stderr,start_new_session=True)
self.transient[p.pid]=p
try:
try:p.wait(timeout=min(timeout,self.remaining()))
except subprocess.TimeoutExpired:
os.killpg(p.pid,signal.SIGKILL);p.wait(timeout=1);raise
finally:
with self.proc_lock:self.transient.pop(p.pid,None)
need(outpath.stat().st_size<=32*1024*1024 and errpath.stat().st_size<=32*1024*1024,'helper log bound')
result=subprocess.CompletedProcess(argv,p.returncode,outpath.read_text(errors='replace'),errpath.read_text(errors='replace'))
self.record('tool-exit',argv=argv,pid=p.pid,exit=p.returncode,stdout_path=str(outpath),stderr_path=str(errpath))
return result
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=self.command_process(['swaymsg','-s',str(self.sock),*args])
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):
build=self.lab/'followup-work/client-build'
p=self.spawn(name,[build/client,*args],True);self.wait(lambda:self.find(name));time.sleep(.25)
if client=='fullscreen-client':
exe=Path(f'/proc/{p.pid}/exe');need(exe.resolve()==build/client and sha(exe)==self.auth['client_binary_sha256'],'actual fullscreen client executable')
self.record('fullscreen-client-identity',pid=p.pid,binary=str(exe.resolve()),sha256=sha(exe))
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=self.command_process(['grim','-s',str(spec['scale']),'-o',out,str(p)])
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',scenario=None):
# 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,scenario or getattr(self,'closure_scenarios',{}).get(name,'observed'));self.wait(lambda:self.find(name) is None)
proof['status']='PASS'
except Exception as error:
proof['failure']=f'{type(error).__name__}: {error}'
proof.setdefault('exit',p.poll())
raise
finally:
self.closed.append(proof);self.record('client-closed',**proof)
def cleanup(self,measurement_status,failure=None):
terminal=None;rescue=False;closures=[]
# Close test clients while their Wayland server still exists. Mechanical
# rescue and terminal IPC can never substitute for scientific closure.
for name in reversed(list(self.proc)):
if name in ('compositor','pointer') or any(c['pid']==self.proc[name].pid for c in self.closed):continue
if self.expired or time.monotonic()>=self.started+143:break
try:
self.close_client(name,'close_owner' if name=='owner' and self.spec['client']!='saved-client' and self.spec['id']!='C6' else 'close')
except Exception:pass # close_client retains the explicit validation failure.
# WHY/CONTRACT/INVARIANT: archived and already-exited clients count equally;
# an empty list of newly closed processes must not erase prior failures.
closures=[dict(c) for c in self.closed]
tracked=[p.pid for n,p in self.proc.items() if n not in ('compositor','pointer')]
closure_coverage=all(any(c['pid']==pid for c in closures) for pid in tracked)
if hasattr(self,'comp') and self.comp.poll() is None and hasattr(self,'sock') and self.sock.is_socket() and not self.expired and time.monotonic()<self.started+145:
try:
self.debit('configuration')
p=self.command_process(['swaymsg','-s',str(self.sock),'exit'],timeout=2)
terminal=dict(ipc_exit=p.returncode,stdout=p.stdout,stderr=p.stderr)
self.save('terminal-ipc',terminal)
except Exception as e:
terminal=dict(ipc_error=str(e));self.save('terminal-ipc',terminal)
if terminal is not None and 'ipc_exit' in terminal:
try:self.comp.wait(timeout=min(1,self.remaining()))
except Exception as e:
terminal['wait_error']=str(e);self.save('terminal-ipc',terminal)
codes=[dict(name=c['name'],pid=c['pid'],exit=c['exit']) for c in self.closed if not any(p.pid==c['pid'] for p in self.proc.values())]
# One shared cleanup deadline prevents N owned clients multiplying waits.
cleanup_end=min(self.started+146,time.monotonic()+2)
if self.pointer and self.pointer.poll() is None and self.pointer.stdin and not self.pointer.stdin.closed:self.pointer.stdin.close()
for name,p in reversed(list(self.proc.items())):
if p.poll() is None:
try:p.wait(timeout=max(.01,cleanup_end-time.monotonic()))
except subprocess.TimeoutExpired:pass
residual=[p for p in self.proc.values() if p.poll() is None]
if residual:
rescue=True
for p in residual:
try:os.killpg(p.pid,signal.SIGKILL)
except ProcessLookupError:pass
kill_end=min(self.started+147,time.monotonic()+1)
for p in residual:p.wait(timeout=max(.01,kill_end-time.monotonic()))
for name,p in self.proc.items():
codes.append(dict(name=name,pid=p.pid,exit=p.returncode))
if p.stdin and not p.stdin.closed:p.stdin.close()
self.finished.set();
if hasattr(self,'watch'):self.watch.join(timeout=1)
for h in self.handles:h.close()
for p in self.proc.values():
try:os.killpg(p.pid,0)
except ProcessLookupError:pass
else:rescue=True;os.killpg(p.pid,signal.SIGKILL)
reaped=all(not Path('/proc',str(p.pid)).exists() for p in self.proc.values());need(reaped,'owned process residual');shutil.rmtree(self.runtime)
seconds=time.monotonic()-self.started
protocol_errors=[n for n in self.proc if n not in ('compositor','pointer') and re.search(r'wl_display[^\n]*\.error\(|protocol error|invalid_popup_parent|not_the_topmost_popup',self.log(n),re.I)]
cleanup_pass=bool(closure_coverage and all(c['status']=='PASS' for c in closures) and not rescue and reaped and seconds<=150 and not self.expired and all(any(c['name']==n and c.get('expected_enomem') for c in closures) for n in protocol_errors) and all(p['exit']==0 or any(c['pid']==p['pid'] and c.get('expected_enomem') for c in closures) for p in codes))
transport_pass=validators.terminal_transport(terminal,self.comp.returncode if hasattr(self,'comp') else None)
final_status=measurement_status if failure is None and cleanup_pass and transport_pass else 'INCONCLUSIVE'
self.save('outcome',dict(status=final_status,measurements_pass=measurement_status,architecture_verdict='INCONCLUSIVE',evidence_provenance=self.auth,untested_matrix_cells=['ATOMIC_GENERATION','COMPLETE_DAMAGE','NATIVE_BUFFER_IDENTITY','GPU_LIFETIME'],scientific_cleanup='PASS' if cleanup_pass else 'INCONCLUSIVE',runtime_exit=codes,measurements_status=measurement_status,failure=failure,terminal=terminal,helper_exits=getattr(self,'helper_exits',[]),explicit_client_closures=self.closed,mechanical_cleanup='PASS' if reaped and not rescue and not self.runtime.exists() else 'INCONCLUSIVE',client_closures=closures,processes=codes,rescue=rescue,reaped=reaped,runtime_removed=not self.runtime.exists(),seconds=seconds,watchdog=self.expired,protocol_errors=protocol_errors,cleanup_pass=cleanup_pass,transport_status='PASS' if transport_pass else 'INCONCLUSIVE'))
self.ledger.complete(self.auth,dict(status=final_status,runner_failure=failure,terminal=terminal,processes=codes,scientific_cleanup=cleanup_pass,mechanical_rescue=rescue));return final_status

View file

@ -0,0 +1,15 @@
#!/usr/bin/env python3
"""Portable source-only freeze, external to immutable source; never admits runs."""
import argparse
from pathlib import Path
from gate import canonical_lab,source_files,source_modes,durable,TICKET
from plans import RUNS,LIMITS
def freeze(lab,author,output=None):
lab=canonical_lab(lab);work=lab/'followup-work';work.mkdir(mode=0o700,exist_ok=True)
result=dict(schema='native-closure-source-v1',ticket=TICKET,author=author,files=source_files(),modes=source_modes(),plans=RUNS,limits=LIMITS,runtime='NOT_RUN',independent_review='NOT_PROVEN')
destination=Path(output) if output else work/'source-manifest.json'
if destination.resolve()!=destination or destination.parent!=work:raise ValueError('freeze output must be canonical followup-work path')
durable(destination,result,exclusive=True);return result
if __name__=='__main__':
p=argparse.ArgumentParser();p.add_argument('--lab',required=True);p.add_argument('--author',required=True);p.add_argument('--output');a=p.parse_args();freeze(a.lab,a.author,a.output)

View file

@ -0,0 +1,146 @@
"""EXPERIMENTAL comparable nonleaf fullscreen, whole-field predictions first."""
import re,time
from PIL import ImageDraw
import cases,validators
from engine import COLORS
from geometry import need,crossing,solid_point,exact,count
from ipc_predicates import ancestry
from model import Provenance
from predicates import geometry
def group_candidate(state,peer_id,group_id,fullscreen=True,applied=None):
# WHY: IPC serializes pending.fullscreen_mode, workspace marker is always1.
# CONTRACT: never turn that field into a direct current-state observation.
# INVARIANT: sampled applied qualification requires post-command configure /
# ACK / MAP plus measured geometry; native current bit remains NOT_OBSERVABLE.
path=ancestry(state['snapshot']['tree'],state['owner']['id'])
rows=[n for n in path[:-1] if n.get('type')=='con' and n.get('id')==group_id]
need(len(rows)==1,'actual nonleaf group absent');group=rows[0]
need(group.get('fullscreen_mode',0)==int(fullscreen),'group fullscreen IPC candidate mismatch')
need(group.get('layout')=='splith' and len(group.get('nodes',[]))==2,'exact horizontal two-child group')
need({n['id'] for n in group['nodes']}=={state['owner']['id'],peer_id},'owner/peer must be direct children')
need(not group.get('floating_nodes') and not any((n.get('floating') or '').endswith('on') for n in group['nodes']),'floating group child')
if fullscreen:
out=next(o for o in state['snapshot']['outputs'] if o['name']==state['workspace']['output'])['rect']
need(group['rect']==out,'fullscreen group does not cover output')
need(out['width']==800 and [n['rect']['width'] for n in group['nodes']]==[400,400],'fullscreen measured400/400 required')
need(applied and applied.get('source')=='post-command-xdg-configure-ack-map' and applied.get('group_id')==group_id and applied.get('owner_id')==state['owner']['id'] and applied.get('workspace_id')==state['workspace']['id'],'pending-only ancestor is not sampled applied evidence')
return group
def applied_configure(raw,cut,owner_id,workspace_id,group_id):
tail=raw[cut:]
configs=re.findall(r'TOPLEVEL XDG_CONFIGURE serial=(\d+) ack',tail)
need(configs and 'TOPLEVEL MAP ' in tail,'post-fullscreen configure/ACK/map absent')
# Actual wire association and SHM mapping are independently parsed by grab().
need(re.search(r'xdg_surface#\d+\.ack_configure\('+configs[-1]+r'\)',tail),'post-fullscreen actual ACK wire absent')
return dict(source='post-command-xdg-configure-ack-map',group_id=group_id,owner_id=owner_id,workspace_id=workspace_id,serial=int(configs[-1]),native_current_bit='NOT_OBSERVABLE')
def positioner_adjust(raw):
values=[]
for popup in ('POPUP_A','POPUP_B'):
ids=re.search(rf'{popup} CREATE wl=(\d+) xdg=(\d+) role=(\d+)',raw);need(ids is not None,'popup role absent')
create=re.search(rf'xdg_surface#{ids[2]}\.get_popup\(new id xdg_popup#{ids[3]}, xdg_surface#\d+, xdg_positioner#(\d+)\)',raw);need(create is not None,'actual positioner absent')
# IDs may be reused after destroy: bind the latest positioner declaration
# and all setters before this exact get_popup request.
before=raw[:create.start()];decl=list(re.finditer(rf'create_positioner\(new id xdg_positioner#{create[1]}\)',before));need(decl,'positioner creation wire absent')
segment=before[decl[-1].start():]
adjust=re.findall(rf'xdg_positioner#{create[1]}\.set_constraint_adjustment\((\d+)\)',segment)
need(adjust==['0'],'different/unobserved positioner adjustment');values.append(int(adjust[0]))
return values
def comparable(pristine,core):
for r in (pristine,core):
need(r.get('fresh') and crossing(r['popup_A'],0,0) and crossing(r['popup_B'],0,0),'actual fresh crossing not exercised')
need(r['child_widths']==[400,400] and r['applied']['source']=='post-command-xdg-configure-ack-map','pending/noncomparable group')
keys=('client_sha256','client_source_sha256','protocol_sha256','positioner_adjust','roles','top_configure','child_rects','child_widths','group_rect','popup_A','popup_B','configured_A','configured_B','origin','outputs')
need(all(pristine[k]==core[k] for k in keys),'pristine/core source/role/actual geometry differs')
return dict(status='PARTIALLY_FEASIBLE',native_current_bit='NOT_OBSERVABLE',atomic_current_state='NOT_PROVEN')
def expected_h2(baseline,g,output,frame,colors,active_roles=('POPUP_A','POPUP_B'),require_crossing=False):
# WHY/CONTRACT/INVARIANT: predict whole RGB before acquisition. Standard /
# fullscreen popup overflow is composed A then B, without a learned mask.
need(frame=='logical' and output['scale']==1 and output['transform']=='normal','unsupported H2 frame')
ox,oy,ow,oh=output['logical'];need(baseline.size==(ow,oh),'H2 baseline size')
expected=baseline.copy();draw=ImageDraw.Draw(expected);parent=g['origin']
for key,name in (('A','POPUP_A'),('B','POPUP_B')):
cfg=g['configured_'+key];rect=g[key]
need(len(cfg)==4 and cfg[2]>0 and cfg[3]>0 and rect==[parent[0]+cfg[0],parent[1]+cfg[1],cfg[2],cfg[3]],'actual configure/global rectangle differs')
if require_crossing:need(crossing(rect,0,0),'actual popup noncrossing')
l,t,r,b=max(rect[0],ox),max(rect[1],oy),min(rect[0]+rect[2],ox+ow),min(rect[1]+rect[3],oy+oh)
if name in active_roles and l<r and t<b:draw.rectangle((l-ox,t-oy,r-ox-1,b-oy-1),fill=colors[name])
parent=rect
return expected
def measured_pair(s,tag,baseline,g,active=('POPUP_A','POPUP_B'),cross=False):
predicted=expected_h2(baseline,g,s.outputs[1],s.frames['HEADLESS-2'],COLORS,active,cross)
s.record('whole-H2-prediction',tag=tag,rgb_sha256=__import__('hashlib').sha256(predicted.tobytes()).hexdigest(),geometry=g,roles=active)
images=s.pair_capture(tag);exact(predicted,images[1]);return images
def fresh_geometry(s,state):
return geometry(s.log('owner'),state['origin'],Provenance(state['owner']['id'],state['workspace']['id'],'NOT_OBSERVABLE','fresh paint + pointer-local corroboration'))
def input_target(s,images,g,target,key):
for index,output in enumerate(s.outputs):
try:point=solid_point(images[index],COLORS[target],output,s.frames[output['name']],g[key]);break
except ValueError:continue
else:raise ValueError('no freshly painted input interior')
tails,buttons=s.click('owner',point);s.delivery('owner',g[key],point,tails,buttons,target)
def prepare_group(s):
# Native container_split ignores singleton H/V parents. Existing filler
# guarantees siblings>1; peer is created on ws3, then explicitly inserted
# in the marked nonleaf group. No post-failure empirical repair is allowed.
s.debit('chain');s.launch_client('owner','fullscreen-client','fullscreen_cross')
oid=s.find('owner')['id'];s.cmd(f'[con_id={oid}] focus');s.cmd(f'[con_id={oid}] split h')
path=ancestry(s.snapshot('group-wrapper')['tree'],oid);group=path[-2]
need(group.get('type')=='con' and [n['id'] for n in group.get('nodes',[])]==[oid],'split did not create owner wrapper')
gid=group['id'];s.cmd(f'[con_id={gid}] mark --add smx-followup-group')
s.cmd('workspace 3');s.launch_client('fullscreen-peer','static-reference');peer=s.find('fullscreen-peer')['id']
s.cmd(f'[con_id={peer}] move container to mark smx-followup-group');s.cmd('workspace 1');s.cmd(f'[con_id={oid}] focus')
snap=s.snapshot('fullscreen-before');path=ancestry(snap['tree'],oid)
group=next(n for n in path if n.get('id')==gid)
need({n['id'] for n in group.get('nodes',[])}=={oid,peer},'peer outside exact owner group')
return oid,peer,gid
def run(s):
cases.init_reference(s,1)
try:oid,peer,gid=prepare_group(s)
except ValueError as exc:
s.save('fullscreen-not-exercised',dict(status='NOT_EXERCISED',reason=str(exc)));return 'NOT_EXERCISED'
baseline=cases.stable_reference(s);cut=len(s.log('owner'))
s.cmd(f'[con_id={gid}] fullscreen enable');s.cmd(f'[con_id={oid}] focus')
s.wait(lambda:'TOPLEVEL XDG_CONFIGURE ' in s.log('owner')[cut:] and 'TOPLEVEL MAP ' in s.log('owner')[cut:]);time.sleep(.2)
state=s.owner('owner');applied=applied_configure(s.log('owner'),cut,oid,state['workspace']['id'],gid)
group=group_candidate(state,peer,gid,applied=applied)
top=s.capture('fullscreen-owner','HEADLESS-1');xy=solid_point(top,COLORS['TOPLEVEL'],s.outputs[0],'logical',[*state['origin'],420,320])
ta,ba=s.click('owner',xy,'left',True);s.wait(lambda:'POPUP_A MAP ' in s.log('owner'))
root=s.capture('fullscreen-A','HEADLESS-1');xyA=solid_point(root,COLORS['POPUP_A'],s.outputs[0],'logical')
tb,bb=s.click('owner',xyA,'left',True);s.wait(lambda:'POPUP_B MAP ' in s.log('owner'));time.sleep(.2)
p=validators.grab(s.log('owner'));adjust=positioner_adjust(s.log('owner'));g=fresh_geometry(s,state)
s.delivery('owner',[*state['origin'],420,320],xy,ta,ba,'TOPLEVEL',p['grabs'][0]['serial']);s.delivery('owner',g['A'],xyA,tb,bb,'POPUP_A',p['grabs'][1]['serial'])
if not all(crossing(g[k],0,0) for k in ('A','B')):
s.save('fullscreen-not-exercised',dict(status='NOT_EXERCISED',geometry=g));return 'NOT_EXERCISED'
images=measured_pair(s,'fullscreen-mapped',baseline,g,cross=True);input_target(s,images,g,'POPUP_B','B')
m=__import__('json').loads(__import__('pathlib').Path(s.auth['manifest']).read_text())
record=dict(client_sha256=s.auth['client_binary_sha256'],client_source_sha256=m['files']['graphics/clients/fullscreen-client.c'],protocol_sha256=m['files']['graphics/protocols/xdg-shell.xml'],positioner_adjust=adjust,fresh=True,applied=applied,roles=p['hierarchy'],top_configure=re.findall(r'TOPLEVEL ROLE_CONFIGURE w=(\d+) h=(\d+)',s.log('owner'))[-1],child_rects=[c['rect'] for c in group['nodes']],child_widths=[c['rect']['width'] for c in group['nodes']],group_rect=group['rect'],popup_A=g['A'],popup_B=g['B'],configured_A=g['configured_A'],configured_B=g['configured_B'],origin=g['origin'],outputs=s.outputs,owner_id=oid,group_id=gid,peer_id=peer,protocol=p)
s.save('fullscreen-comparability',record)
before=s.log('owner');start=time.monotonic_ns();s.client_cmd('owner','close_b','COMMAND close_b DONE')
from lifecycle import explicit_child_close
explicit_child_close(before,s.log('owner'),p['ids'],start,time.monotonic_ns());time.sleep(.15)
state=s.owner('owner');group_candidate(state,peer,gid,applied=applied);g=fresh_geometry(s,state)
images=measured_pair(s,'fullscreen-B-closed',baseline,g,('POPUP_A',));need(not any(count(im,COLORS['POPUP_B']) for im in images),'B residual');input_target(s,images,g,'POPUP_A','A')
s.cmd(f'[con_id={gid}] fullscreen disable',True);s.cmd(f'[con_id={state["workspace"]["id"]}] layout splith',True);s.expected_layout='splith';time.sleep(.35)
state=s.owner('owner');need(state['owner']['id']==oid,'owner changed on standard return');group_candidate(state,peer,gid,fullscreen=False)
g=fresh_geometry(s,state);images=measured_pair(s,'fullscreen-standard-A',baseline,g,('POPUP_A',));input_target(s,images,g,'POPUP_A','A')
s.close_client('owner',scenario='nested');final=s.pair_capture('fullscreen-final');exact(baseline,final[1])
return 'PARTIALLY_FEASIBLE'

View file

@ -0,0 +1,212 @@
"""WHY: a new ticket cannot borrow historical reservations or source reviews.
CONTRACT: freeze + independent review + publication precede every reservation.
INVARIANT: no reset/refund; failed actions remain debited, bindings never repaired.
"""
import ctypes,fcntl,hashlib,json,os,re,resource,stat,subprocess,time,uuid
from pathlib import Path
from plans import RUNS,LIMITS
TICKET='SWAYFX_MOSAIC_STEP20C_COREC_V2_FULLSCREEN_OOM_GLES_NATIVE_CLOSURE_BATCH_V1'
WL_SHA='7edca9a7d65855addda68e29ba5db94d57c803eb5fa7d0f86db3136b90ca9837'
SOURCE_ROOT=Path(__file__).resolve().parent.parent
def need(v,m):
if not v:raise ValueError(m)
def sha(p):return hashlib.sha256(Path(p).read_bytes()).hexdigest()
def encoded(v):return (json.dumps(v,sort_keys=True,indent=2)+'\n').encode()
def digest(v):return hashlib.sha256(encoded(v)).hexdigest()
def canonical_lab(lab):
p=Path(lab);need(p.is_absolute() and p.resolve()==p and p.parent==Path('/var/tmp'),'canonical direct /var/tmp LAB required')
s=p.lstat();need(stat.S_ISDIR(s.st_mode) and s.st_uid==os.getuid() and stat.S_IMODE(s.st_mode)==0o700,'LAB mode/owner/type');return p
def clean_environment(lab):
lab=canonical_lab(lab)
for name in ('scratch','cache','data','pycache'):
p=lab/name;need(p.resolve()==p and p.is_dir() and p.stat().st_uid==os.getuid(),'owned scratch/cache/data required')
e={k:os.environ[k] for k in ('PATH','LANG','LC_ALL','TZ') if k in os.environ}
e.update(TMPDIR=str(lab/'scratch'),TMP=str(lab/'scratch'),TEMP=str(lab/'scratch'),SMX_LAB=str(lab),XDG_CACHE_HOME=str(lab/'cache'),XDG_DATA_HOME=str(lab/'data'),PYTHONPYCACHEPREFIX=str(lab/'pycache'),CCACHE_DIR=str(lab/'cache/ccache'))
return e
def disable_dump():
resource.setrlimit(resource.RLIMIT_CORE,(0,0))
need(ctypes.CDLL(None).prctl(4,0,0,0,0)==0,'PR_SET_DUMPABLE failed')
def secure_read(path):
fd=os.open(path,os.O_RDONLY|os.O_NOFOLLOW|os.O_NONBLOCK)
try:
s=os.fstat(fd);need(stat.S_ISREG(s.st_mode) and s.st_uid==os.getuid() and s.st_nlink==1 and not s.st_mode&0o077,'receipt/ledger owner/type/link/mode')
need(s.st_size<=16*1024*1024,'receipt size bound')
with os.fdopen(fd,'r',closefd=False) as f:return json.load(f)
finally:os.close(fd)
def durable(path,value,exclusive=False):
p=Path(path);need(p.parent.resolve()==p.parent and p.parent.is_dir(),'canonical output parent')
if p.exists() or p.is_symlink():
s=p.lstat();need(stat.S_ISREG(s.st_mode) and s.st_uid==os.getuid() and s.st_nlink==1 and not s.st_mode&0o077,'unsafe existing output')
tmp=p.with_name(p.name+'.tmp-'+uuid.uuid4().hex)
fd=os.open(tmp,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600)
try:
with os.fdopen(fd,'wb') as f:f.write(encoded(value));f.flush();os.fsync(f.fileno())
if exclusive:os.link(tmp,p);tmp.unlink()
else:os.replace(tmp,p)
d=os.open(p.parent,os.O_RDONLY|os.O_DIRECTORY)
try:os.fsync(d)
finally:os.close(d)
finally:
if tmp.exists():tmp.unlink()
def source_files(root=None):
root=root or SOURCE_ROOT;result={}
for p in sorted(root.rglob('*')):
need(not p.is_symlink(),'source symlink forbidden')
if p.is_file():
rel=p.relative_to(root).as_posix();need('__pycache__' not in p.parts and p.suffix not in ('.pyc','.png','.o','.so','.a'),'generated source artifact')
result[rel]=sha(p)
need(result,'empty source kit');return result
def source_modes(root=None):
root=root or SOURCE_ROOT
files=source_files(root);result={}
for rel in files:
mode=stat.S_IMODE((root/rel).stat().st_mode)
need(mode in (0o644,0o755),'source mode not portable Git 0644/0755')
result[rel]=oct(mode)
return result
def git_inventory(raw,prefix,files,modes):
# WHY: identical blob bytes do not prove file type, mode, or full inventory.
# CONTRACT/INVARIANT: exact Git subtree, canonical portable modes, blobs only.
need(set(files)==set(modes),'manifest mode inventory')
need(all(v in ('0o644','0o755') for v in modes.values()),'invalid manifest modes')
prefix=prefix.encode()+b'/';seen={}
need(not raw or raw.endswith(b'\0'),'truncated Git tree')
for entry in raw.split(b'\0')[:-1]:
meta,path=entry.split(b'\t',1);mode,kind,oid=meta.split(b' ')
need(path.startswith(prefix),'Git tree wrong prefix')
rel=path[len(prefix):].decode('utf-8',errors='strict')
need(rel in files and rel not in seen,'Git tree added/duplicate path')
need(kind==b'blob' and mode in (b'100644',b'100755'),'Git tree type/mode')
need(mode=={'0o644':b'100644','0o755':b'100755'}[modes[rel]],'Git mode differs')
need(re.fullmatch(b'[0-9a-f]{40}',oid),'Git object identity')
seen[rel]=oid.decode()
need(set(seen)==set(files),'Git tree missing path')
return seen
def published_sources(top,commit,prefix,files,modes,env):
tree=subprocess.run(['git','-C',str(top),'ls-tree','-rz','--full-tree',commit,'--',str(prefix)],capture_output=True,env=env,timeout=4)
need(tree.returncode==0,'published Git tree unavailable')
git_inventory(tree.stdout,str(prefix),files,modes)
for rel,h in files.items():
blob=subprocess.run(['git','-C',str(top),'show',f'{commit}:{prefix/rel}'],capture_output=True,env=env,timeout=4)
need(blob.returncode==0 and hashlib.sha256(blob.stdout).hexdigest()==h,'commit does not contain frozen bytes')
def _receipt(lab,path,expected):
p=Path(path);need(p.resolve()==p and p.is_relative_to(lab),'runtime receipt outside new LAB')
value=secure_read(p);need(sha(p)==expected,'receipt SHA mismatch');return value
def validate_review(lab,manifest,review,publication,rid,offline=False,check_git=True):
lab=canonical_lab(lab);need(rid in RUNS,'historical/unknown run ID');need(RUNS[rid]['admission']!='NOT_ADMITTED' or rid=='GPU-G3','scenario NOT_ADMITTED')
manifest,review,publication=map(Path,(manifest,review,publication))
need(all(p.resolve()==p and p.is_relative_to(lab) for p in (manifest,review,publication)),'external admission receipt outside new LAB')
m=secure_read(manifest);r=secure_read(review);p=secure_read(publication)
need(m.get('ticket')==TICKET and m.get('schema')=='native-closure-source-v1' and m.get('plans')==RUNS and m.get('limits')==LIMITS,'manifest ticket/plans/schema/limits')
need(m.get('files')==source_files() and m.get('modes')==source_modes(),'source changed/added/removed/mode after freeze')
need(r.get('decision')=='ACCEPTED' and r.get('independent') is True and r.get('reviewer') and r['reviewer']!=m.get('author'),'independent review absent')
need(r.get('manifest_sha256')==sha(manifest) and r.get('ticket')==TICKET,'review stale/ticket')
case=r.get('runs',{}).get(rid,{})
need(case.get('decision')=='ACCEPTED' and case.get('client_build_review')=='ACCEPTED','per-run client/source review absent')
for gate in {'FS-P':('FULLSCREEN_COMPARABILITY_OFFLINE',),'FS-C':('FULLSCREEN_COMPARABILITY_OFFLINE',),'DY-C':('MIGRATION_REFOCUS_OFFLINE',),'OM-ROOT':('OOM_ROOT_NESTED_ORACLE_OFFLINE',),'OM-NESTED':('OOM_ROOT_NESTED_ORACLE_OFFLINE',),'GPU-G3':('B4',)}[rid]:
proof=r.get('gates',{}).get(gate,{})
need(proof.get('decision')=='ACCEPTED' and proof.get('manifest_sha256')==sha(manifest),'required gate not independently accepted')
receipt=_receipt(lab,proof.get('receipt',''),proof.get('receipt_sha256'))
need(receipt.get('status')=='PASS' and receipt.get('tests_run',0)>0 and receipt.get('failures')==0 and receipt.get('source_digest')==digest(m['files']),'superficial/stale offline gate')
need(p.get('ticket')==TICKET and p.get('decision')=='VERIFIED' and p.get('offline_test_only') is offline,'publication namespace/ticket')
commit=p.get('source_commit','');need(re.fullmatch('[0-9a-f]{40}',commit),'publication commit')
need(p.get('manifest_sha256')==sha(manifest) and p.get('source_digest')==digest(m['files']),'publication manifest binding')
need(p.get('origin_sha')==p.get('github_sha')==commit,'source kit unpublished')
for name in ('origin','github'):
obs=p.get('remote_observations',{}).get(name,{})
need(obs.get('exit')==0 and obs.get('argv')==['git','ls-remote',name,'refs/heads/main'] and obs.get('stdout','').split()==[commit,'refs/heads/main'],'direct remote observation absent')
if not offline and check_git:
# Git bytes independently bind the remote commit to this portable kit.
repo=SOURCE_ROOT
root=subprocess.run(['git','-C',str(repo),'rev-parse','--show-toplevel'],capture_output=True,text=True,env=clean_environment(lab),timeout=4)
need(root.returncode==0,'published Git repository unavailable');top=Path(root.stdout.strip());prefix=SOURCE_ROOT.relative_to(top)
published_sources(top,commit,prefix,m['files'],m['modes'],clean_environment(lab))
runtime=_receipt(lab,r.get('runtime_manifest',''),r.get('runtime_manifest_sha256'))
need(runtime.get('ticket')==TICKET,'runtime identity ticket')
identity=runtime['identities'][RUNS[rid]['binary']]
need(sha(identity['binary'])==identity['binary_sha256'],'binary changed')
if not offline and check_git:
elf=subprocess.run(['readelf','-n',identity['binary']],capture_output=True,text=True,env=clean_environment(lab),timeout=4)
need(elf.returncode==0 and re.findall(r'Build ID: ([0-9a-f]+)',elf.stdout)==[identity.get('build_id')],'binary BuildID mismatch')
need(sha(identity['wlroots'])==identity['wlroots_sha256']==WL_SHA,'wlroots changed')
need(identity.get('build_review')=='ACCEPTED' and identity.get('source_qualification')=='ACCEPTED','identity/source build unreviewed')
build=_receipt(lab,case.get('client_build_receipt',''),case.get('client_build_receipt_sha256'))
need(build.get('status')=='PASS' and build.get('runtime')=='NOT_RUN','client compile receipt')
for name in ('pointer-daemon','static-reference',RUNS[rid]['client']):
binary=lab/'followup-work/client-build'/name
need(sha(binary)==build['generated'][name] and build['inputs']['clients/'+name+'.c']==m['files']['graphics/clients/'+name+'.c'],'client generated/source mismatch')
need(build['inputs']['protocols/xdg-shell.xml']==m['files']['graphics/protocols/xdg-shell.xml'] and build['inputs']['protocols/wlr-virtual-pointer-unstable-v1.xml']==m['files']['graphics/protocols/wlr-virtual-pointer-unstable-v1.xml'],'protocol source binding')
if rid=='GPU-G3':
need(all(identity['binary_sha256']!=other.get('binary_sha256') for name,other in runtime['identities'].items() if name!='gpu'),'GPU new distinct ELF required')
from gpu_handler import validate_binding
validate_binding(lab,case,identity,sha(manifest))
if rid in ('OM-ROOT','OM-NESTED'):
need(case.get('oom_source_review')=='ACCEPTED' and case.get('variant_identity')=='COREC_V2_NATIVE_OOM_DIAGNOSTIC' and case.get('source_manifest_sha256')==sha(manifest),'OOM native/source variant review absent')
if rid=='OM-NESTED' and case.get('root_dependency_override'):
v=case['root_dependency_override'];need(v.get('independent') is True and v.get('reviewer')!=m['author'] and v.get('decision')=='ACCEPTED' and v.get('manifest_sha256')==sha(manifest) and v.get('rationale') and v.get('no_common_native_fault') is True,'invalid independent nested dependency review')
return dict(identity,prospective=case,manifest=str(manifest),review=str(review),publication=str(publication),manifest_sha256=sha(manifest),review_sha256=sha(review),publication_sha256=sha(publication),runtime_manifest_sha256=r['runtime_manifest_sha256'],source_digest=digest(m['files']),client_binary_sha256=build['generated'][RUNS[rid]['client']])
class Ledger:
def __init__(self,lab,path=None,offline=False):
self.lab=canonical_lab(lab);self.offline=offline;self.path=Path(path) if path else self.lab/'native-closure-budget.json'
need(self.path.parent.resolve()==self.path.parent and self.path.parent.is_relative_to(self.lab),'ledger outside new LAB/canonical parent')
need(not self.path.is_symlink(),'ledger symlink')
if offline:need('OFFLINE_TEST_ONLY' in self.path.parts,'offline namespace')
else:need(self.path==self.lab/'native-closure-budget.json','real ledger fixed new path')
self.lock=self.path.with_name(self.path.name+'.lock')
def transaction(self,fn,initialize=False):
fd=os.open(self.lock,os.O_RDWR|os.O_CREAT|os.O_NOFOLLOW,0o600)
try:
s=os.fstat(fd);need(stat.S_ISREG(s.st_mode) and s.st_uid==os.getuid() and s.st_nlink==1 and not s.st_mode&0o077,'lock owner/type/link/mode')
fcntl.flock(fd,fcntl.LOCK_EX);need(self.lock.lstat().st_ino==s.st_ino,'lock inode changed')
if initialize:need(not self.path.exists(),'ledger already initialized');d=None
else:
d=secure_read(self.path);need(d.get('ticket')==TICKET and d.get('lab')==str(self.lab) and d.get('uid')==os.getuid(),'old ticket/LAB/UID')
need(d.get('namespace')==('OFFLINE_TEST_ONLY' if self.offline else 'REAL') and d.get('limits')==LIMITS,'namespace/limits')
result,new=fn(d);durable(self.path,new,exclusive=initialize);return result
finally:os.close(fd)
def initialize(self):
return self.transaction(lambda _:(None,dict(ticket=TICKET,lab=str(self.lab),uid=os.getuid(),namespace='OFFLINE_TEST_ONLY' if self.offline else 'REAL',limits=LIMITS,runs=[])),True)
def admit(self,rid,manifest,review,publication):
auth=validate_review(self.lab,manifest,review,publication,rid,offline=self.offline)
def reserve(d):
spec=RUNS[rid];need(not any(x['id']==rid for x in d['runs']),'consumed run; no retry')
if rid=='OM-NESTED':
roots=[x for x in d['runs'] if x['id']=='OM-ROOT'];good=len(roots)==1 and roots[0]['status']=='COMPLETED' and roots[0].get('outcome',{}).get('status')=='PARTIALLY_FEASIBLE' and roots[0].get('outcome',{}).get('scientific_cleanup') is True
need(good or auth['prospective'].get('root_dependency_override'),'root unresolved; independent dependency review required')
if rid=='FS-C':
controls=[x for x in d['runs'] if x['id']=='FS-P'];need(len(controls)==1 and controls[0]['status']=='COMPLETED' and controls[0].get('outcome',{}).get('status')=='PARTIALLY_FEASIBLE','fullscreen control incomplete')
need(len(d['runs'])<LIMITS['runs'] and sum(x['capture_limit'] for x in d['runs'])+spec['capture_limit']<=LIMITS['captures'],'global cap')
token=uuid.uuid4().hex;r=dict(id=rid,token=token,started_ns=time.monotonic_ns(),capture_limit=spec['capture_limit'],action_limits=spec['action_limits'],actions={},status='RESERVED',bindings=auth)
d['runs'].append(r);return dict(auth,token=token,run_id=rid,lab=str(self.lab),offline=self.offline),d
return self.transaction(reserve)
def action_debit(self,auth,*kinds):
fresh=validate_review(self.lab,auth['manifest'],auth['review'],auth['publication'],auth['run_id'],offline=self.offline,check_git=False)
def update(d):
rows=[x for x in d['runs'] if x['id']==auth['run_id']];need(len(rows)==1,'missing reservation');r=rows[0]
need(r['token']==auth['token'] and r['bindings']==fresh and r['status']=='RESERVED','historical token/stale binding/completed')
need(0<=time.monotonic_ns()-r['started_ns']<150_000_000_000,'duration exhausted')
actions=dict(r['actions'])
for kind in kinds:
limit=r['capture_limit'] if kind=='capture' else r['action_limits'].get(kind,0)
need(actions.get(kind,0)<limit,'local action exhausted: '+kind);actions[kind]=actions.get(kind,0)+1
total=sum(x['actions'].get('capture',0) for x in d['runs'])+actions.get('capture',0)-r['actions'].get('capture',0)
need(total<=LIMITS['captures'],'global capture cap');r['actions']=actions;return None,d
return self.transaction(update)
def complete(self,auth,outcome):
def finish(d):
rows=[x for x in d['runs'] if x['id']==auth['run_id']];need(len(rows)==1,'missing reservation');r=rows[0]
need(r['token']==auth['token'] and r['status']=='RESERVED','historical token/completed');r.update(status='COMPLETED',finished_ns=time.monotonic_ns(),outcome=outcome);return None,d
self.transaction(finish)

View file

@ -0,0 +1,62 @@
"""Explicit logical global, IPC assignment, physical mode and PNG-frame adapters."""
from dataclasses import dataclass
import re
from PIL import Image
class Ambiguous(ValueError):pass
def need(ok,msg):
if not ok:raise Ambiguous(msg)
def box_end(r):return [r[0]+r[2],r[1]+r[3]]
def contains(r,p):return r[0]<=p[0]<r[0]+r[2] and r[1]<=p[1]<r[1]+r[3]
def crossing(r,axis,boundary):return r[axis]<boundary<r[axis]+r[axis+2]
def intersection(a,b):
x=max(a[0],b[0]);y=max(a[1],b[1]);return [x,y,max(0,min(a[0]+a[2],b[0]+b[2])-x),max(0,min(a[1]+a[3],b[1]+b[3])-y)]
def assigned_origin(owner):
# IPC rect already excludes top decoration (ipc-json.c899–906).
return [owner['rect']['x']+owner['window_rect']['x'],owner['rect']['y']+owner['window_rect']['y']]
def exterior_ipc(owner):
r=owner['rect'];d=owner['deco_rect']['height'];return [r['x'],r['y']-d,r['width'],r['height']+d]
MARKERS={'TL':(255,32,32),'TR':(32,255,32),'BL':(32,32,255),'BR':(255,255,32),'C':(192,32,255)}
def marker_frame(image,scale,rotated):
positions={k:[] for k in MARKERS};lookup={v:k for k,v in MARKERS.items()}
for y in range(image.height):
for x in range(image.width):
k=lookup.get(image.getpixel((x,y)))
if k:positions[k].append((x,y))
need(all(len(v)>=100 for v in positions.values()),'marker color missing')
c={k:[sum(p[i] for p in v)/len(v) for i in (0,1)] for k,v in positions.items()}
logical=image.size==(round(800*scale),round(600*scale)) and c['TL'][0]<c['TR'][0] and c['TL'][1]<c['BL'][1]
physical=rotated and image.size==(round(600*scale),round(800*scale)) and c['TL'][0]>c['BL'][0] and c['TL'][1]<c['TR'][1]
need(logical or physical,'PNG orientation unestablished')
return dict(frame='logical' if logical else 'physical90',centers=c,counts={k:len(v) for k,v in positions.items()},size=list(image.size))
def png_global(px,output,frame):
scale=output['scale'];x,y=px;ox,oy,w,h=output['logical']
if frame=='physical90':x,y=y,round(h*scale)-1-x
need(frame in ('logical','physical90'),'unknown PNG frame')
# Sample integer logical centers only; resample at the chosen center later.
return [ox+int(x/scale),oy+int(y/scale)]
def global_png(p,output,frame):
x=(p[0]-output['logical'][0])*output['scale'];y=(p[1]-output['logical'][1])*output['scale']
if frame=='physical90':x,y=output['logical'][3]*output['scale']-1-y,x
return [round(x),round(y)]
def solid_point(image,color,output,frame,rectangle=None,margin=3):
for y in range(margin,image.height-margin,3):
for x in range(margin,image.width-margin,3):
if image.getpixel((x,y))!=color:continue
p=png_global((x,y),output,frame)
if rectangle is not None and not contains(rectangle,p):continue
pp=global_png(p,output,frame)
if not (margin<=pp[0]<image.width-margin and margin<=pp[1]<image.height-margin):continue
if all(image.getpixel((xx,yy))==color for yy in range(pp[1]-margin,pp[1]+margin+1) for xx in range(pp[0]-margin,pp[0]+margin+1)):
return p
raise Ambiguous('no measured solid logical interior')
def count(image,color):return sum(v==color for v in image.get_flattened_data())
def exact(a,b):need(a.size==b.size and a.tobytes()==b.tobytes(),'whole RGB differs');return dict(equal=True,pixels=a.width*a.height)
def expected_outputs(outputs,spec):
active=[o for o in outputs if o['active']];need(len(active)==2,'active output count')
for want in spec:
rows=[o for o in active if o['name']==want['name']];need(len(rows)==1,'output identity')
o=rows[0];need([o['rect'][k] for k in ('x','y','width','height')]==want['logical'],'logical output geometry')
need(o['scale']==want['scale'] and o['transform']==want['transform'],'scale/IPC transform string')
need([o['current_mode'][k] for k in ('width','height')]==want['mode'],'physical mode')
return active

View file

@ -0,0 +1,86 @@
"""New G3 wiring only. Import never arms a hook, reserves or launches a client."""
import importlib,json,os,re,stat,sys
from pathlib import Path
import gate
from engine import Session
from gate import need,sha
def validate_binding(lab,case,identity,manifest_sha):
# WHY: G2 qualification cannot authorize a new native G3 ELF or source.
# CONTRACT: independent source/capability/build receipts bind final bytes.
# INVARIANT: no ACCEPTED status is manufactured by this adapter.
need(case.get('gpu_source_review')=='ACCEPTED' and case.get('gl_observability_preflight')=='ACCEPTED' and case.get('build_review')=='ACCEPTED' and case.get('native_gl_status')=='NOT_RUN','GPU source/capability/build review absent')
for name in ('gpu_source_review','gl_observability_preflight','build_review'):
receipt=gate._receipt(lab,case.get(name+'_receipt',''),case.get(name+'_receipt_sha256'))
need(receipt.get('decision')=='ACCEPTED' and receipt.get('independent') is True and receipt.get('manifest_sha256')==manifest_sha,'GPU independent exact-byte receipt missing')
source=gate._receipt(lab,case.get('gpu_source_manifest',''),case.get('gpu_source_manifest_sha256'))
need(source.get('schema')=='G3-relative-source-freeze-v1' and source.get('files'),'GPU new relative freeze')
root=gate.SOURCE_ROOT/'gpu'
actual={str(p.relative_to(root)) for p in root.rglob('*') if p.is_file()}
need(actual==set(source['files']),'GPU source added/removed')
for rel,record in source['files'].items():
p=root/rel;need(not Path(rel).is_absolute() and p.resolve()==p and p.is_relative_to(root),'GPU source path')
need(sha(p)==record['sha256'] and oct(stat.S_IMODE(p.stat().st_mode))==record['mode'],'GPU frozen source changed')
binding=case.get('binding',{});client=Path(binding.get('client',''))
need(client.resolve()==client and client.is_relative_to(lab) and sha(client)==binding.get('client_sha256'),'GPU newly built client bytes')
need(binding.get('binary')==identity['binary'] and binding.get('source_manifest_sha256')==manifest_sha,'GPU binary/source binding')
build=gate._receipt(lab,case.get('gpu_client_build_receipt',''),case.get('gpu_client_build_receipt_sha256'))
need(build.get('status')=='PASS' and build.get('source_sha256')==sha(root/'clients-g2.c') and build.get('client_sha256')==sha(client),'GPU generated client/source receipt')
gatefile=Path(case.get('gate_file',''));native=gate._receipt(lab,gatefile,case.get('gate_file_sha256'))
acquisition=lab/'gpu-work/acquisition'
need(acquisition.resolve()==acquisition and acquisition.is_dir() and acquisition.stat().st_uid==os.getuid() and acquisition.stat().st_mode&0o777==0o700,'GPU acquisition root/mode')
need(native.get('source_review')=='ACCEPTED' and native.get('gl_observability')=='PASS' and native.get('runtime_authorized') is True and native.get('diagnostic')=='GPU-G3' and native.get('acquisition_root')==str(acquisition) and native.get('binary_sha256')==identity['binary_sha256'],'GPU native external authorization')
need(binding.get('gate_file')==str(gatefile) and binding.get('reviewed_scene_sha256')==case.get('reviewed_scene_sha256') and re.fullmatch('[0-9a-f]{64}',case.get('reviewed_scene_sha256','')),'GPU final scene/binding missing')
return binding
def modules(lab):
path=gate.SOURCE_ROOT/'gpu';previous=os.environ.get('MOSAIC_G3_DIR');os.environ['MOSAIC_G3_DIR']=str(lab/'gpu-work/acquisition');sys.path.insert(0,str(path))
try:
protocol=importlib.import_module('protocol');scenario=importlib.import_module('scenario');validator=importlib.import_module('validate_source')
need(all(Path(m.__file__).resolve()==path/(name+'.py') for m,name in ((protocol,'protocol'),(scenario,'scenario'),(validator,'validate_source'))),'GPU module origin mismatch')
need(protocol.ROOT==lab/'gpu-work/acquisition','GPU imported wrong acquisition root')
return scenario
finally:
sys.path.remove(str(path))
if previous is None:os.environ.pop('MOSAIC_G3_DIR',None)
else:os.environ['MOSAIC_G3_DIR']=previous
class CompositorEnvironment:
def __init__(self,session):self.session=session
def update(self,**fields):
need(set(fields)=={'MOSAIC_G3_DIR','MOSAIC_G3_NONCE','WLR_RENDERER'},'GPU compositor-only environment scope')
need(fields['MOSAIC_G3_DIR']==str(self.session.lab/'gpu-work/acquisition') and re.fullmatch('[0-9a-f]{64}',fields['MOSAIC_G3_NONCE']) and fields['WLR_RENDERER']=='gles2','GPU private acquisition environment')
self.session.compositor_environment=fields
class AdapterSession:
def __init__(self,session):
need(session.spec['id']=='GPU-G3' and session.spec['binary']=='gpu','real GPU-G3 Session identity')
self.session=session;self.spec=session.spec;self.env=CompositorEnvironment(session)
def __getattr__(self,name):return getattr(self.session,name)
def debit(self,*kinds):
need(kinds==('mutation',),'GPU adapter unexpected direct debit');self.session.debit('mutation','hook')
def spawn(self,name,argv,debug=False):
need(name in ('g2-reference','g2-background','g2-glass'),'GPU client role');return self.session.spawn(name,argv,True)
class GPUSceneSession(Session):
def close_client(self,name,command='close',scenario=None):
need(name in ('g2-reference','g2-background','g2-glass') and command=='close','GPU scientific close role')
try:
super().close_client(name,command)
raw=self.log(name);proof=self.closed[-1]
mode={'g2-reference':'reference','g2-background':'background','g2-glass':'glass'}[name]
role='zwlr_layer_surface_v1' if mode=='background' else 'xdg_toplevel'
need(re.search(rf'EXIT ns=\d+ mode={mode} error=0 commits=\d+',raw),'GPU exact client exit role')
need(re.search(rf'{role}#\d+\.destroy\(\)',raw) and re.search(r'wl_surface#\d+\.destroy\(\)',raw),'GPU native role wire destruction')
proof.update(role=role,scientific_client=True,meaning='actual layer closure' if mode=='background' else 'actual GPU XDG closure')
except Exception as exc:
if self.closed and self.closed[-1]['pid']==self.proc[name].pid:self.closed[-1].update(status='INCONCLUSIVE',failure=str(exc))
raise
def run(s):
case=s.auth['prospective'];binding=validate_binding(s.lab,case,s.auth,s.auth['manifest_sha256'])
result=modules(s.lab).run(AdapterSession(s),dict(binding,nonce=s.auth['token']*2,G3_ledger_reserved=True))
need(result['native_source_A_Areturn_exact'] and result['whole_H1_A_Areturn']['equal'] and all(x['equal'] for x in result['whole_H2_A_B_Areturn']),'GPU measured return mismatch')
return 'PARTIALLY_FEASIBLE'

View file

@ -0,0 +1,21 @@
"""Pure tree identity helpers only; IPC geometry is pending candidate."""
def nodes(node):
yield node
for child in node.get('nodes', []) + node.get('floating_nodes', []):
yield from nodes(child)
def workspace(tree, name):
rows = [n for n in nodes(tree) if n.get('type') == 'workspace' and n.get('name') == name]
assert len(rows) == 1, 'workspace identity'
return rows[0]
def ancestry(tree, con_id, path=()):
if tree.get('id') == con_id:
return path + (tree,)
for child in tree.get('nodes', []) + tree.get('floating_nodes', []):
found = ancestry(child, con_id, path + (tree,))
if found:
return found
return ()

View file

@ -0,0 +1,64 @@
"""Pure E2 input/lifecycle predicates shared with offline negative tests."""
import re
from model import require
from predicates import COLORS, paint, equal, pair, point
def opening_pair(lines,target,serial):
actual=[]
for line in lines:
m=re.fullmatch(r'POINTER_BUTTON target=(\w+) serial=(\d+) button=(\d+) state=(\d+)',line)
require(m is not None,'opening-button-format');actual.append([m[1],*map(int,m.groups()[1:])])
require(len(actual)==2 and all(e[0]==target for e in actual) and
[e[2:] for e in actual]==[[272,1],[272,0]] and actual[0][1]==serial and
0<actual[0][1]<actual[1][1]<=0xffffffff,'real-left-opening-pair')
return actual
def explicit_child_close(before,after,ids,request_ns,done_ns):
"""Client destroy must follow a recorded explicit request; no popup_done."""
require(type(request_ns) is int and request_ns<done_ns,'explicit-close-request-order')
require(after.startswith(before),'append-only-client-protocol')
tail=after[len(before):]
require('POPUP_DONE ' not in after,'close-must-not-be-dismissal')
b=ids['POPUP_B'];a=ids['POPUP_A']
destroy=f'POPUP_B DESTROY role={b[2]} xdg={b[1]} wl={b[0]}'
require(tail.count(destroy)==1 and tail.count('COMMAND close_b DONE')==1 and
tail.index(destroy)<tail.index('COMMAND close_b DONE'),'explicit-child-destroy-DONE')
for obj,stem in [(b[2],'xdg_popup'),(b[1],'xdg_surface'),(b[0],'wl_surface')]:
require(tail.count(f'{stem}#{obj}.destroy()')==1,'child-protocol-destroy')
require(after.count(f'POPUP_A MAP wl={a[0]} xdg={a[1]}')==1 and
'POPUP_A DESTROY ' not in after and 'TOPLEVEL DESTROY' not in after,'same-A-remains-mapped')
return dict(status='PASS',request_ns=request_ns,done_ns=done_ns,B=b,A=a,
meaning='explicit client close, no compositor popup_done')
def a_survival(image,before,after,ids,buttons,global_point,rect):
a=ids['POPUP_A']
require(paint(image,'POPUP_B')['count']==0 and paint(image,'POPUP_A')['count']>0,'child-zero-residual-A-painted')
require(before.startswith('') and after.startswith(before),'A-input-protocol-appended')
require('POPUP_A DESTROY ' not in after and 'POPUP_A POPUP_DONE ' not in after and
after.count(f'POPUP_A MAP wl={a[0]} xdg={a[1]}')==1,'same-A-live-after-input')
require(rect[0]<=global_point[0]<rect[0]+rect[2] and rect[1]<=global_point[1]<rect[1]+rect[3], 'A-recomputed-point-bounds')
return dict(status='PASS',pair=pair(buttons,'POPUP_A'),ids=a,point=global_point)
def normal_owner_close(before,after,ids,code):
require(code==0 and after.startswith(before),'normal-client-exit')
tail=after[len(before):]
require(tail.count('COMMAND close_owner')==1 and tail.count('TOPLEVEL DESTROY')==1 and
'POPUP_DONE ' not in tail,'normal-owner-close-command')
a=ids['POPUP_A'];top=ids['TOPLEVEL']
require(tail.count(f'POPUP_A DESTROY role={a[2]} xdg={a[1]} wl={a[0]}')==1 and
tail.index('COMMAND close_owner')<tail.index('POPUP_A DESTROY ')<tail.index('TOPLEVEL DESTROY'),
'normal-owner-child-first-destruction')
for obj,stem in [(a[2],'xdg_popup'),(a[1],'xdg_surface'),(a[0],'wl_surface'),
(top[2],'xdg_toplevel'),(top[1],'xdg_surface'),(top[0],'wl_surface')]:
require(tail.count(f'{stem}#{obj}.destroy()')==1,'owner-protocol-destroy')
return dict(status='PASS',client_exit=code,ids=ids)
def final_restoration(baseline,final):
cmp=equal(baseline,final)
require(cmp['equal'] and all(paint(final,n)['count']==0 for n in ('POPUP_A','POPUP_B')),'whole-H2-final-exact')
return dict(status='PASS',comparison=cmp)

View file

@ -0,0 +1,194 @@
"""EXPERIMENTAL typed half-open geometry. Pure: no processes or sockets.
Source contracts are recorded in the offline gate, not inferred from RGB.
Every conversion is explicit and keeps assignment/owner/generation provenance.
"""
from dataclasses import dataclass
from enum import Enum
class Invalid(ValueError):
pass
class Space(str, Enum):
NATIVE = 'NativeSHM'
CONFIGURE = 'XDGConfigure'
CONTENT = 'AppliedContent'
GLOBAL = 'SceneGlobal'
POPUP = 'PopupLocal'
VIEWPORT = 'GlobalViewport'
PNG = 'OutputPNG'
def require(value, contract):
if not value:
raise Invalid(contract)
@dataclass(frozen=True)
class Provenance:
owner: int
workspace: int
generation: str
evidence: str
state: str = 'applied'
@dataclass(frozen=True)
class Rect:
space: Space
x: int
y: int
width: int
height: int
provenance: Provenance
def __post_init__(self):
require(isinstance(self.space, Space), 'tag-required')
require(all(type(v) is int for v in (self.x, self.y, self.width, self.height)), 'integer-geometry')
require(self.width >= 0 and self.height >= 0, 'nonnegative-size')
require(bool(self.provenance.evidence), 'evidence-required')
@property
def end(self):
return (self.x + self.width, self.y + self.height)
def contains(self, xy):
return self.x <= xy[0] < self.end[0] and self.y <= xy[1] < self.end[1]
def values(self):
return [self.x, self.y, self.width, self.height]
@dataclass(frozen=True)
class Native:
rectangle: Rect
buffer_id: int
stride: int
format: int
commit_serial: int
@dataclass(frozen=True)
class Applied:
scene: Rect
window: Rect
configured: Rect
native: Native
border_left: int
border_top: int
titlebar: int
configure_ack: int
view_offset: tuple = (0, 0)
@dataclass(frozen=True)
class Output:
name: str
rectangle: Rect
scale: float
transform: str
def same(a, b):
require(a.owner == b.owner, 'owner-identity')
require(a.workspace == b.workspace, 'workspace-identity')
require(a.generation == b.generation, 'applied-generation')
require(a.state == b.state == 'applied', 'pending-not-applied')
def content(applied):
s, w, c, n = applied.scene, applied.window, applied.configured, applied.native
require(s.space is Space.GLOBAL and w.space is Space.CONTENT and c.space is Space.CONFIGURE,
'assignment-spaces')
require(n.rectangle.space is Space.NATIVE, 'native-space')
for r in (w, c, n.rectangle):
same(s.provenance, r.provenance)
require(n.buffer_id > 0 and n.stride == n.rectangle.width * 4 and n.format in (0, 1), 'native-buffer-identity')
require(0 < n.commit_serial <= 0xffffffff, 'commit-serial')
require(0 < applied.configure_ack <= 0xffffffff, 'assigned-configure-ack')
require((w.width, w.height) == (c.width, c.height), 'configure-assignment')
require(applied.border_left == w.x and applied.border_top == applied.titlebar + w.y,
'titlebar-content-offset')
require(w.width + 2 * applied.border_left == s.width and
w.height + applied.border_top + applied.border_left == s.height, 'assigned-destination')
return Rect(Space.GLOBAL, s.x + applied.border_left + applied.view_offset[0],
s.y + applied.border_top + applied.view_offset[1], w.width, w.height, s.provenance)
def intersect(rect, boundary):
require(rect.space is Space.GLOBAL and boundary.space in (Space.GLOBAL, Space.VIEWPORT), 'global-intersection')
same(rect.provenance, boundary.provenance)
x, y = max(rect.x, boundary.x), max(rect.y, boundary.y)
return Rect(Space.GLOBAL, x, y, max(0, min(rect.end[0], boundary.end[0]) - x),
max(0, min(rect.end[1], boundary.end[1]) - y), rect.provenance)
def to_png(rect, output):
require(rect.space is Space.GLOBAL and output.rectangle.space is Space.GLOBAL, 'global-to-png-spaces')
require(output.scale == 1.0, 'unmeasured-output-scale')
require(output.transform == 'normal', 'unmeasured-output-transform')
same(rect.provenance, output.rectangle.provenance)
visible = intersect(rect, output.rectangle)
return Rect(Space.PNG, min(max(visible.x - output.rectangle.x,0),output.rectangle.width), min(max(visible.y - output.rectangle.y,0),output.rectangle.height),
visible.width, visible.height, rect.provenance)
def from_png(rect, output):
require(rect.space is Space.PNG and output.rectangle.space is Space.GLOBAL, 'png-to-global-spaces')
require(output.scale == 1.0 and output.transform == 'normal', 'unmeasured-png-transform')
same(rect.provenance, output.rectangle.provenance)
require(0 <= rect.x <= rect.end[0] <= output.rectangle.width and
0 <= rect.y <= rect.end[1] <= output.rectangle.height, 'png-bounds')
return Rect(Space.GLOBAL, rect.x + output.rectangle.x, rect.y + output.rectangle.y,
rect.width, rect.height, rect.provenance)
def verify_paint(rect, output, bbox, count, clip=None):
expected = to_png(intersect(rect, clip) if clip is not None else rect, output)
actual = [] if bbox is None else list(bbox)
box=[expected.x, expected.y, *expected.end] if expected.width and expected.height else []
require(actual == box, 'paint-origin-intersection')
require(count == expected.width * expected.height, 'paint-area')
return expected
def popup(parent, configured, parent_xdg, expected_parent_xdg, ack, mapped):
require(parent.space is Space.GLOBAL and configured.space is Space.POPUP, 'popup-spaces')
same(parent.provenance, configured.provenance)
require(parent_xdg == expected_parent_xdg and expected_parent_xdg > 0, 'xdg-parent')
require(0 < ack <= 0xffffffff, 'popup-ack')
require(mapped is True, 'popup-map')
return Rect(Space.GLOBAL, parent.x + configured.x, parent.y + configured.y,
configured.width, configured.height, parent.provenance)
def crossing(rect, boundary_x):
require(rect.space is Space.GLOBAL, 'crossing-space')
return 'EXERCISED' if rect.x < boundary_x < rect.end[0] else 'NOT EXERCISED'
def local_clip_to_global(local, origin, provenance):
require(local.space is Space.CONTENT, 'clip-local-space')
same(local.provenance, provenance)
return Rect(Space.VIEWPORT, local.x + origin[0], local.y + origin[1],
local.width, local.height, provenance)
def pointer_local(xy, rect, actual):
require(rect.space is Space.GLOBAL and rect.contains(xy), 'pointer-global-bounds')
require(list(actual) == [xy[0] - rect.x, xy[1] - rect.y], 'pointer-local-correlation')
return list(actual)
def fresh_chain(old, new):
require(old['owner'] != new['owner'] and old['pid'] != new['pid'], 'fresh-E2-identity')
require(len(set(old['serials'] + new['serials'])) == 4 and
max(old['serials']) < min(new['serials']), 'fresh-E2-serials')
def fresh_standard(mosaic, standard):
require(mosaic.provenance.owner == standard.provenance.owner and
mosaic.provenance.workspace == standard.provenance.workspace, 'standard-owner')
require(mosaic.provenance.generation != standard.provenance.generation, 'fresh-standard-generation')
return standard
def constraints(flags, requested, configured):
require(type(flags) is int and 0 <= flags <= 63, 'positioner-flags')
require(flags != 0 or requested == configured, 'zero-flags-no-adjustment')
return {'flags': flags, 'request': requested, 'actual': configured, 'prediction_only': True}

View file

@ -0,0 +1,332 @@
"""OFFLINE_TEST_ONLY: real final predicates; no headless spawn or real budget."""
import copy,json,os,sys,tempfile,time,unittest
from pathlib import Path
from unittest.mock import patch
sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
import cases,engine,fullscreen,gate,oom,oom_handler,plans,validators
from geometry import assigned_origin,exterior_ipc,exact
from PIL import Image,ImageDraw
LAB=Path(os.environ['SMX_LAB']);KIT=Path(__file__).resolve().parents[1]
class Scientific(unittest.TestCase):
def rejects(self,fn,*args,**kwargs):
with self.assertRaises((ValueError,AssertionError,RuntimeError,KeyError)):fn(*args,**kwargs)
def snapshot(self,width=800,scroll=800,output='HEADLESS-1',focus=3,count=3,gap=0):
x=-width if output=='HEADLESS-1' else 0;cell=max(640,(width-(count-1)*gap)//count)
return dict(tree=dict(type='workspace',layout='mosaic',name='1',id=20,output=output,rect=dict(x=x,y=0,width=width,height=600),mosaic=dict(rows=[dict(children=list(range(1,count+1)),scroll_x=scroll,focused=focus)]),nodes=[dict(id=i+1,focused=i+1==focus,rect=dict(x=x+i*(cell+gap),y=25,width=cell,height=575)) for i in range(count)]))
def state(self):
rect=dict(x=-800,y=0,width=800,height=600)
owner=dict(type='con',id=40,pid=123,rect=dict(x=-800,y=0,width=400,height=600),nodes=[],floating=None)
peer=dict(type='con',id=41,pid=124,rect=dict(x=-400,y=0,width=400,height=600),nodes=[])
group=dict(type='con',id=30,layout='splith',fullscreen_mode=1,nodes=[owner,peer],rect=rect,floating_nodes=[])
ws=dict(type='workspace',id=20,name='1',output='HEADLESS-1',layout='splith',nodes=[group],fullscreen_mode=1)
return dict(owner=owner,workspace=ws,snapshot=dict(tree=ws,outputs=[dict(name='HEADLESS-1',rect=rect)]))
def applied(self):return dict(source='post-command-xdg-configure-ack-map',group_id=30,owner_id=40,workspace_id=20)
def test_fullscreen_exact_group(self):
st=self.state();fullscreen.group_candidate(st,41,30,applied=self.applied())
changes=[lambda g:g.update(fullscreen_mode=0),lambda g:g.update(nodes=g['nodes'][:1]),lambda g:g['nodes'][1].update(id=99),lambda g:g['nodes'][0]['rect'].update(width=800),lambda g:g.update(layout='splitv')]
for change in changes:
bad=copy.deepcopy(st);change(bad['snapshot']['tree']['nodes'][0]);self.rejects(fullscreen.group_candidate,bad,41,30,applied=self.applied())
def test_fullscreen_peer_at_workspace(self):
bad=self.state();g=bad['workspace']['nodes'][0];bad['workspace']['nodes'].append(g['nodes'].pop());self.rejects(fullscreen.group_candidate,bad,41,30,applied=self.applied())
def test_pending_or_other_owner_workspace_not_applied(self):
for mutation in ({'source':'pending'},{'owner_id':99},{'workspace_id':99},{'group_id':99}):
a=self.applied();a.update(mutation);self.rejects(fullscreen.group_candidate,self.state(),41,30,applied=a)
self.rejects(fullscreen.group_candidate,self.state(),41,30)
def test_fullscreen_marker_alone_not_enough(self):
st=self.state();st['workspace']['nodes'][0]['fullscreen_mode']=0;self.rejects(fullscreen.group_candidate,st,41,30,applied=self.applied())
def field(self):
baseline=Image.new('RGB',(800,600),(48,48,56));g=dict(origin=[-798,25],configured_A=[350,80,900,170],configured_B=[100,60,900,130],A=[-448,105,900,170],B=[-348,165,900,130])
return baseline,g,plans.H[1]
def test_whole_field_prediction_occlusion_and_A_only(self):
b,g,o=self.field();pred=fullscreen.expected_h2(b,g,o,'logical',engine.COLORS,require_crossing=True)
self.assertEqual(pred.getpixel((0,110)),engine.COLORS['POPUP_A']);self.assertEqual(pred.getpixel((0,170)),engine.COLORS['POPUP_B'])
one=fullscreen.expected_h2(b,g,o,'logical',engine.COLORS,('POPUP_A',));self.assertEqual(one.getpixel((0,170)),engine.COLORS['POPUP_A'])
for xy,color in [((799,599),(1,2,3)),((0,170),engine.COLORS['POPUP_A']),((0,110),(48,48,56))]:
wrong=pred.copy();wrong.putpixel(xy,color);self.rejects(exact,pred,wrong)
bad=copy.deepcopy(g);bad['A'][0]=10;self.rejects(fullscreen.expected_h2,b,bad,o,'logical',engine.COLORS,require_crossing=True)
def test_migration_reveal_and_stale_camera(self):
prior=cases.dynamic_geometry(self.snapshot(),expected_focus=3)
moved=self.snapshot(scroll=1120,output='HEADLESS-2');actual=cases.dynamic_geometry(moved,prior,'migration',3);self.assertEqual(actual['expected_scroll'],1120)
self.rejects(cases.dynamic_geometry,self.snapshot(scroll=800,output='HEADLESS-2'),prior,'migration',3)
self.rejects(cases.dynamic_geometry,moved,prior,'resize',3)
def test_current_geometry_varies_count_gap_and_viewport(self):
prior=cases.dynamic_geometry(self.snapshot(scroll=500,count=4,gap=7,focus=4),expected_focus=4)
moved=self.snapshot(width=960,scroll=4*640+3*7-960,output='HEADLESS-2',count=4,gap=7,focus=4)
actual=cases.dynamic_geometry(moved,prior,'migration',4);self.assertEqual(actual['expected_scroll'],1621)
def test_resize800960800_and_input_invalidation(self):
a=cases.dynamic_geometry(self.snapshot(scroll=1100),expected_focus=3)
b=cases.dynamic_geometry(self.snapshot(width=960,scroll=960),a,'resize',3)
c=cases.dynamic_geometry(self.snapshot(scroll=960),b,'resize',3);self.assertEqual(c['expected_scroll'],960)
self.rejects(cases.dynamic_geometry,self.snapshot(width=960,scroll=1100),a,'resize',3)
def test_missing_or_other_refocus_order_bounds_pending(self):
prior=cases.dynamic_geometry(self.snapshot(),expected_focus=3)
for mutate in [lambda w:w['nodes'][2].update(focused=False),lambda w:w['mosaic']['rows'][0].update(focused=1),lambda w:w['mosaic']['rows'][0].update(scroll_x=9999),lambda w:w['mosaic']['rows'][0].update(children=[3,2,1]),lambda w:w['nodes'][2].update(focused=False)]:
snap=self.snapshot(scroll=1120,output='HEADLESS-2');mutate(snap['tree']);self.rejects(cases.dynamic_geometry,snap,prior,'migration',3)
bad=self.snapshot(scroll=0,focus=1);self.rejects(cases.dynamic_geometry,bad,prior,'migration',3)
bad=self.snapshot(scroll=1120);bad['state_source']='pending-as-current';self.rejects(cases.dynamic_geometry,bad,prior,'migration',3)
def close_log(self,roles=('TOPLEVEL','POPUP_A'),maps=()):
out=[]
for i,name in enumerate(roles):
a=10+i*3;out.append(f'{name} CREATE wl={a} xdg={a+1} role={a+2}')
if name in maps:out.append(name+' MAP wl='+str(a))
out+=['CLOSE_RECEIVED','COMMAND close_owner']
for i,name in reversed(list(enumerate(roles))):
a=10+i*3;out += [f'{name} DESTROY role={a+2} xdg={a+1} wl={a}',f'{"xdg_toplevel" if i==0 else "xdg_popup"}#{a+2}.destroy()',f'xdg_surface#{a+1}.destroy()',f'wl_surface#{a}.destroy()']
return '\n'.join(out+['SHM_CLOSURE created=1 released=1','EXIT error=0'])
def test_root_only_close_is_valid_and_distinct(self):
raw=self.close_log();validators.client_exit(raw,0,'root_failure');self.rejects(validators.client_exit,raw,0,'nested')
self.rejects(validators.client_exit,raw+'\nPOPUP_B CREATE wl=99 xdg=98 role=97',0,'root_failure')
self.rejects(validators.client_exit,raw.replace('CLOSE_RECEIVED',''),0,'root_failure')
def test_nested_failure_A_mapped_B_unmapped(self):
raw=self.close_log(('TOPLEVEL','POPUP_A','POPUP_B'),('POPUP_A',));validators.client_exit(raw,0,'nested_failure')
self.rejects(validators.client_exit,raw+'\nPOPUP_B MAP wl=16',0,'nested_failure')
self.rejects(validators.client_exit,raw.replace('POPUP_A MAP wl=13',''),0,'nested_failure')
def test_close_negative_hierarchy_error_release_signal(self):
raw=self.close_log()
for bad in [raw.replace('POPUP_A DESTROY','TOPLEVEL DESTROY'),raw.replace('released=1','released=0'),raw.replace('error=0','error=12'),raw.replace('xdg_popup#15.destroy()',''),raw+'\nwl_display#1.error(wl_display#1, 1, "no memory")',raw+'\nPOPUP_A MAP wl=13']:
self.rejects(validators.client_exit,bad,0,'root_failure')
self.rejects(validators.client_exit,raw,-15,'root_failure')
def test_wire_real_grabs_and_partial_maps(self):
raw=(KIT/'wire-fixture.txt').read_text();validators.grab(raw)
import re
grab=re.search(r'POPUP_A GRAB_REQUEST[^\n]+',raw)[0];serial=re.search(r'serial=(\d+)',grab)[1]
changes=[raw.replace('used='+serial,'used=999',1),raw.replace('POPUP_B MAP ','POPUP_B NOT_MAP ',1),re.sub(r'(POPUP_B CREATE[^\n]+parent_xdg=)\d+',r'\g<1>999',raw),re.sub(r'xdg_popup#\d+\.grab',r'xdg_popup#999.grab',raw,count=1)]
for bad in changes:self.assertNotEqual(raw,bad);self.rejects(validators.grab,bad)
def test_failed_popup_real_wire_no_map(self):
raw=(KIT/'wire-fixture.txt').read_text();oom_handler.opening_before_map(raw,'POPUP_A','TOPLEVEL')
grab=__import__('re').search(r'POPUP_A GRAB_REQUEST[^\n]+',raw)[0];serial=__import__('re').search(r'serial=(\d+)',grab)[1]
self.rejects(oom_handler.opening_before_map,raw.replace('used='+serial,'used=999',1),'POPUP_A','TOPLEVEL')
def test_no_memory_requires_wire(self):
raw='wl_display#1.error(wl_display#1, 1, "no memory")\nEXIT error=12\n';oom_handler.expected_disconnect(raw,0)
for bad in [raw.replace('.error(','.no_error('),raw.replace('error=12','error=0'),raw.replace(', 1,',', 2,')]:self.rejects(oom_handler.expected_disconnect,bad,0)
def test_native_counter_and_NULL_association(self):
env=dict(SMX_OOM_NONCE='a'*32,SMX_OOM_ROLE='root_popup')
raw='SMX_OOM ENOMEM role=root_popup nonce='+env['SMX_OOM_NONCE']+' pid=123 xdg=0x10 xdg_parent=0x20 parent=0x30 tree=0x40 children=0 matched=1 failures=1 successes=0 initialized=0 listeners=0 published=0\nSMX_OOM result role=root_popup pid=123 xdg=0x10 xdg_parent=0x20 parent=0x30 tree=(nil) successes=0 failures=1'
oom.native_failure(raw,env,123)
for old,new in [('listeners=0','listeners=1'),('published=0','published=1'),('initialized=0','initialized=1'),('pid=123','pid=999'),('tree=(nil)','tree=0x40'),('xdg_parent=0x20','xdg_parent=0x99'),('matched=1','matched=2'),('nonce='+env['SMX_OOM_NONCE'],'nonce='+'b'*32)]:
bad=raw.replace(old,new,1);self.rejects(oom.native_failure,bad,env,123)
def test_B5_native_SHM_and_destination_titlebar_distinct(self):
owner=dict(rect=dict(x=-800,y=25,width=640,height=575),window_rect=dict(x=2,y=0,width=636,height=573),deco_rect=dict(height=25))
self.assertEqual(assigned_origin(owner),[-798,25]);self.assertEqual(exterior_ipc(owner),[-800,0,640,600])
self.assertNotEqual([420,320],[owner['window_rect']['width'],owner['window_rect']['height']])
def test_floating_null_tiled_and_actual_floating_rejected(self):
st=self.state();path=[st['workspace'],st['workspace']['nodes'][0],st['owner']];st['workspace']['layout']='mosaic'
self.assertTrue(validators.camera_eligible(path,st['workspace']) is False) # fullscreen exemption
path[1]['fullscreen_mode']=0;self.assertTrue(validators.camera_eligible(path,st['workspace']))
path[1]['floating_nodes']=[st['owner']];self.assertFalse(validators.camera_eligible(path,st['workspace']))
def test_B5_terminal_raw_codes_not_rewritten(self):
terminal=dict(ipc_exit=1,stdout='',stderr='Unable to receive IPC response')
self.assertTrue(validators.terminal_transport(terminal,0));self.assertEqual(terminal['ipc_exit'],1)
self.assertFalse(validators.terminal_transport(terminal,1));self.assertFalse(validators.terminal_transport(dict(ipc_exit=1),0))
self.assertFalse(validators.terminal_transport(dict(ipc_exit=1,stdout='',stderr=''),0))
def test_positioner_adjust_same_actual_wire_required(self):
raw=(KIT/'wire-fixture.txt').read_text()
import re
adapted=re.sub(r'(xdg_wm_base#\d+\.create_positioner\(new id xdg_positioner#(\d+)\))',lambda m:m[0]+'\nxdg_positioner#'+m[2]+'.set_constraint_adjustment(0)',raw)
self.assertEqual(fullscreen.positioner_adjust(adapted),[0,0])
self.rejects(fullscreen.positioner_adjust,adapted.replace('set_constraint_adjustment(0)','set_constraint_adjustment(1)',1))
self.rejects(fullscreen.positioner_adjust,raw)
def test_native_availability_missing_identity_IPC_input_socket(self):
expected=dict(pid=123,binary='/private/sway',binary_sha256='a'*64,build_id='b'*40,wlroots='/usr/lib/libwlroots-0.20.so')
ident=dict(pid=123,exe=expected['binary'],sha256=expected['binary_sha256'],build_id=expected['build_id'],wlroots=[expected['wlroots']],maps='mapped '+expected['wlroots'],swaysock='/tmp/smx-offline/sway-ipc.123.sock',captured_before_cleanup=True)
snap=dict(tree=dict(type='root',nodes=[dict(id=1)]),outputs=[dict(active=True),dict(active=True)])
buttons=dict(reference=['BUTTON ns=100 serial=11 button=273 state=1','BUTTON ns=101 serial=12 button=273 state=0'])
raw='wl_pointer#9.button(11, 100, 273, 1)\nwl_pointer#9.button(12, 101, 273, 0)'
oom_handler.availability_certificate(ident,expected,snap,raw,buttons)
for change in ({'pid':999},{'sha256':'c'*64},{'build_id':'c'*40},{'maps':'wrong'},{'captured_before_cleanup':False},{'swaysock':'/run/user/1000/sway.sock'}):
bad=dict(ident,**change);self.rejects(oom_handler.availability_certificate,bad,expected,snap,raw,buttons)
self.rejects(oom_handler.availability_certificate,ident,expected,dict(tree={},outputs=[]),raw,buttons)
self.rejects(oom_handler.availability_certificate,ident,expected,snap,raw,{})
self.rejects(oom_handler.availability_certificate,ident,expected,snap,raw.replace('273, 0','273, 1'),buttons)
def test_predictions_precede_runtime_captures(self):
# The real acquisition helper calls the production oracle first; a capture
# side-effect changes the geometry to prove it cannot influence prediction.
b,g,o=self.field();expected=fullscreen.expected_h2(b,g,o,'logical',engine.COLORS)
from types import SimpleNamespace
events=[]
def capture(tag):
events.append('capture');g['A'][0]=999;return [b,expected]
session=SimpleNamespace(outputs=plans.H,frames={'HEADLESS-2':'logical'},record=lambda *args,**kwargs:events.append('prediction'),pair_capture=capture)
fullscreen.measured_pair(session,'offline',b,g);self.assertEqual(events,['prediction','capture'])
class RepairPredicates(unittest.TestCase):
rejects=Scientific.rejects
def test_oom_pixels_both_outputs_and_final(self):
b=Image.new('RGB',(40,40),(48,48,56));clean=[b.copy(),b.copy()]
for failed in ('POPUP_A','POPUP_B'):
oom_handler.popup_pixels(clean,b,failed)
for output in (0,1):
bad=[im.copy() for im in clean];bad[output].putpixel((7,9),engine.COLORS[failed])
self.rejects(oom_handler.popup_pixels,bad,b,failed)
alive=[b.copy(),b.copy()];alive[0].putpixel((7,9),engine.COLORS['POPUP_A'])
oom_handler.popup_pixels(alive,b,'POPUP_B')
for role in ('POPUP_A','POPUP_B'):
for output in (0,1):
bad=[im.copy() for im in clean];bad[output].putpixel((7,9),engine.COLORS[role])
self.rejects(oom_handler.popup_pixels,bad,b,final=True)
oom_handler.popup_pixels(clean,b,final=True)
def test_root_input_real_delivery_wrong_wire_owner_point(self):
from types import SimpleNamespace
raw=(KIT/'wire-fixture.txt').read_text()
# Real fixture establishes configured root identity; use its measured rectangle.
proof=oom_handler.mapped_root(raw);x,y,w,h=proof['configure']
origin=[-x,-y];image=Image.new('RGB',(800,600),engine.COLORS['POPUP_A'])
def exercise(wrong=None):
def click(name,point,action):
self.assertEqual((name,action),('owner','right'))
local=[point[0],point[1]]
if wrong=='point':local[0]+=1
target='TOPLEVEL' if wrong=='role' else 'POPUP_A'
lines=[f'POINTER_BUTTON target={target} serial={i+10} button=273 state={v}' for i,v in enumerate((1,0))]
tail=f'POINTER_MOTION target=POPUP_A x={local[0]} y={local[1]}\n'+'\n'.join(f'wl_pointer#9.button({i+10}, 100, 273, {v})' for i,v in enumerate((1,0)))
if wrong=='wire':tail=tail.replace('273, 0','273, 1')
buttons={'owner':lines,'reference':lines if wrong=='owner' else []}
return {'owner':tail},buttons
obj=SimpleNamespace(log=lambda name:raw,owner=lambda name:dict(origin=origin),outputs=[dict(scale=1,logical=[0,0,800,600])],frames={'HEADLESS-1':'logical'},click=click)
obj.delivery=lambda *args:engine.Session.delivery(obj,*args)
return oom_handler.surviving_root_input(obj,[image,image])
self.assertEqual(exercise()['status'],'PARTIALLY_FEASIBLE')
for wrong in ('wire','owner','point','role'):self.rejects(exercise,wrong)
def test_git_actual_tree_inventory_and_modes(self):
import subprocess
top=KIT.parents[3];prefix=Path('tooling/probes/step20c-applied-state-input')
raw=subprocess.run(['git','-C',str(top),'ls-tree','-rz','--full-tree','HEAD','--',str(prefix)],check=True,capture_output=True).stdout
self.assertTrue(raw)
files={};modes={}
for row in raw.split(b'\0')[:-1]:
meta,path=row.split(b'\t',1);rel=path[len(str(prefix).encode())+1:].decode();files[rel]=gate.sha(top/path.decode());modes[rel]='0o755' if meta.startswith(b'100755') else '0o644'
gate.git_inventory(raw,str(prefix),files,modes)
gate.published_sources(top,'HEAD',prefix,files,modes,gate.clean_environment(LAB))
first=raw.split(b'\0')[0];meta,path=first.split(b'\t',1);mode,kind,oid=meta.split(b' ')
for replacement in (b'100755' if mode==b'100644' else b'100644',b'120000'):
bad=raw.replace(first,replacement+b' '+kind+b' '+oid+b'\t'+path,1)
self.rejects(gate.git_inventory,bad,str(prefix),files,modes)
self.rejects(gate.git_inventory,raw+first.replace(path,path+b'-extra')+b'\0',str(prefix),files,modes)
self.rejects(gate.git_inventory,raw[len(first)+1:],str(prefix),files,modes)
self.rejects(gate.git_inventory,raw,str(prefix)+'-wrong',files,modes)
for invalid in ('0644','0o600','100644',644):
bad=dict(modes);bad[next(iter(bad))]=invalid;self.rejects(gate.git_inventory,raw,str(prefix),files,bad)
def test_source_modes_reject_nonportable_and_symlink(self):
root=Path(tempfile.mkdtemp(dir=os.environ['TMPDIR']));p=root/'file';p.write_text('source');p.chmod(0o644)
self.assertEqual(gate.source_modes(root),{'file':'0o644'})
p.chmod(0o755);self.assertEqual(gate.source_modes(root),{'file':'0o755'})
p.chmod(0o600);self.rejects(gate.source_modes,root)
p.unlink();p.symlink_to('/dev/null');self.rejects(gate.source_modes,root)
class Admission(Scientific):
def setUp(self):
self.root=Path(tempfile.mkdtemp(prefix='gate-',dir=os.environ.get('TMPDIR',str(LAB/'scratch'))));self.ns=self.root/'OFFLINE_TEST_ONLY';self.ns.mkdir(mode=0o700)
self.manifest=self.ns/'manifest.json';self.review=self.ns/'review.json';self.publication=self.ns/'publication.json'
self.files=gate.source_files();self.m=dict(ticket=gate.TICKET,schema='native-closure-source-v1',author='fixture-author',files=self.files,modes=gate.source_modes(),plans=plans.RUNS,limits=plans.LIMITS)
gate.durable(self.manifest,self.m)
elf=self.ns/'fake-offline-ELF';elf.write_bytes(b'OFFLINE_TEST_ONLY never executed')
identity=dict(binary=str(elf),binary_sha256=gate.sha(elf),wlroots='/usr/lib/libwlroots-0.20.so',wlroots_sha256=gate.WL_SHA,build_review='ACCEPTED',source_qualification='ACCEPTED',build_id='offline')
runtime=self.ns/'runtime.json';gate.durable(runtime,dict(ticket=gate.TICKET,identities={name:identity for name in ('pristine','core','oom')}))
# Fake payloads are explicitly OFFLINE; only validation and budget state are
# exercised. No receipt can authorize Session (offline namespace is rejected).
self.build=LAB/'followup-work/client-build';self.generated={name:gate.sha(self.build/name) for name in ('fullscreen-client','nested-client','static-reference','pointer-daemon')}
compile=self.ns/'compile.json';gate.durable(compile,dict(status='PASS',runtime='NOT_RUN',generated=self.generated,inputs={str(p.relative_to(KIT)):gate.sha(p) for folder in ('clients','protocols') for p in (KIT/folder).iterdir()}))
test=self.ns/'tests.json';gate.durable(test,dict(status='PASS',tests_run=1,failures=0,source_digest=gate.digest(self.files)))
case=dict(oom_source_review='ACCEPTED',variant_identity='COREC_V2_NATIVE_OOM_DIAGNOSTIC',source_manifest_sha256=gate.sha(self.manifest),decision='ACCEPTED',client_build_review='ACCEPTED',client_build_receipt=str(compile),client_build_receipt_sha256=gate.sha(compile))
self.r=dict(ticket=gate.TICKET,decision='ACCEPTED',independent=True,reviewer='offline-independent-fixture',manifest_sha256=gate.sha(self.manifest),runtime_manifest=str(runtime),runtime_manifest_sha256=gate.sha(runtime),runs={rid:case for rid in plans.RUNS},gates={name:dict(decision='ACCEPTED',manifest_sha256=gate.sha(self.manifest),receipt=str(test),receipt_sha256=gate.sha(test)) for name in ('FULLSCREEN_COMPARABILITY_OFFLINE','MIGRATION_REFOCUS_OFFLINE','OOM_ROOT_NESTED_ORACLE_OFFLINE')})
gate.durable(self.review,self.r);commit='a'*40
self.p=dict(ticket=gate.TICKET,decision='VERIFIED',offline_test_only=True,source_commit=commit,origin_sha=commit,github_sha=commit,manifest_sha256=gate.sha(self.manifest),source_digest=gate.digest(self.files),remote_observations={name:dict(exit=0,argv=['git','ls-remote',name,'refs/heads/main'],stdout=commit+'\trefs/heads/main\n') for name in ('origin','github')});gate.durable(self.publication,self.p)
self.ledger=gate.Ledger(LAB,self.ns/'budget.json',offline=True);self.ledger.initialize()
def validate(self,rid='OM-ROOT'):return gate.validate_review(LAB,self.manifest,self.review,self.publication,rid,offline=True)
def admit(self,rid='OM-ROOT'):return self.ledger.admit(rid,self.manifest,self.review,self.publication)
def test_real_validator_offline_namespace_and_unique_reservation(self):
self.validate();a=self.admit();self.ledger.action_debit(a,'capture','hook');self.rejects(self.admit)
self.rejects(self.ledger.action_debit,a,'hook');self.ledger.complete(a,dict(status='INCONCLUSIVE'));self.rejects(self.ledger.action_debit,a,'capture')
self.rejects(engine.Session,plans.RUNS['OM-ROOT'],a)
def test_historical_ids_and_tokens(self):
for rid in ('O1','O2','C7','P1','C8','G2'):self.rejects(self.validate,rid)
auth=self.admit();bad=dict(auth,token='historic-token');self.rejects(self.ledger.action_debit,bad,'capture')
def test_self_review_and_wrong_SHA_reject_before_reservation(self):
for mutate in [lambda r:r.update(reviewer='fixture-author'),lambda r:r.update(decision='PASS'),lambda r:r.update(manifest_sha256='0'*64),lambda r:r.update(gates={})]:
bad=copy.deepcopy(self.r);mutate(bad);gate.durable(self.review,bad);self.rejects(self.admit)
self.assertEqual(json.loads(self.ledger.path.read_text())['runs'],[])
def test_publication_remote_observation_and_clone_bytes_binding(self):
for mutate in [lambda p:p.update(github_sha='b'*40),lambda p:p.update(remote_observations={}),lambda p:p.update(source_digest='0'*64),lambda p:p.update(offline_test_only=False)]:
bad=copy.deepcopy(self.p);mutate(bad);gate.durable(self.publication,bad);self.rejects(self.admit)
def test_stale_source_manifest_and_superficial_gate(self):
self.m['files']['graphics/plans.py']='0'*64;gate.durable(self.manifest,self.m);self.rejects(self.admit)
gate.durable(self.manifest,{**self.m,'files':self.files});self.r['gates']['OOM_ROOT_NESTED_ORACLE_OFFLINE']['decision']='PASS';gate.durable(self.review,self.r);self.rejects(self.admit)
def test_root_dependency_unresolved_cannot_consume_nested(self):
self.rejects(self.admit,'OM-NESTED');root=self.admit();self.ledger.complete(root,dict(status='INCONCLUSIVE',scientific_cleanup=True));self.rejects(self.admit,'OM-NESTED')
self.assertEqual(len(json.loads(self.ledger.path.read_text())['runs']),1)
def test_root_qualified_allows_one_nested(self):
root=self.admit();self.ledger.complete(root,dict(status='PARTIALLY_FEASIBLE',scientific_cleanup=True));self.admit('OM-NESTED');self.rejects(self.admit,'OM-NESTED')
def test_optional_gpu_never_auto_admitted(self):
for rid in ('GPU-G3','GPU-CTRL','OM-EXTRA'):self.rejects(self.admit,rid)
def test_caps_duration_and_failed_reservation_spent(self):
auth=self.admit()
for _ in range(10):self.ledger.action_debit(auth,'capture')
self.rejects(self.ledger.action_debit,auth,'capture')
def expire(d):d['runs'][0]['started_ns']=time.monotonic_ns()-151_000_000_000;return None,d
self.ledger.transaction(expire);self.rejects(self.ledger.action_debit,auth,'move');self.rejects(self.admit)
def test_mode_symlink_hardlink_lock_attacks(self):
self.ledger.path.chmod(0o644);self.rejects(self.admit);self.ledger.path.chmod(0o600)
alias=self.ns/'hardlink';os.link(self.ledger.path,alias);self.rejects(self.admit);alias.unlink()
lock=self.ledger.lock;lock.unlink();lock.symlink_to(self.manifest)
with self.assertRaises(OSError):self.admit()
def test_clean_environment_no_daily_authority(self):
with patch.dict(os.environ,dict(SWAYSOCK='/run/user/daily',WAYLAND_DISPLAY='daily',DISPLAY=':0',LD_PRELOAD='evil',LD_LIBRARY_PATH='daily',WLR_RENDERER='daily',SMX_OOM_NONCE='daily')):
e=gate.clean_environment(LAB)
self.assertFalse(set(e)&{'SWAYSOCK','WAYLAND_DISPLAY','DISPLAY','LD_PRELOAD','LD_LIBRARY_PATH','WLR_RENDERER','SMX_OOM_NONCE'})
self.assertTrue(all(Path(e[k]).is_relative_to(LAB) for k in ('TMPDIR','TMP','TEMP','XDG_CACHE_HOME','XDG_DATA_HOME','PYTHONPYCACHEPREFIX','CCACHE_DIR')))
def test_concurrent_reservation_single_winner(self):
from concurrent.futures import ThreadPoolExecutor
def attempt(_):
try:self.admit();return 'reserved'
except ValueError:return 'refused'
with ThreadPoolExecutor(max_workers=2) as pool:out=list(pool.map(attempt,range(2)))
self.assertEqual(sorted(out),['refused','reserved']);self.assertEqual(len(json.loads(self.ledger.path.read_text())['runs']),1)
def test_source_addition_and_mutation_after_freeze(self):
import shutil
source=self.ns/'portable-source';shutil.copytree(gate.SOURCE_ROOT,source)
with patch.object(gate,'SOURCE_ROOT',source):
self.validate();(source/'unexpected.py').write_text('unreviewed=True\n');self.rejects(self.admit)
(source/'unexpected.py').unlink();(source/'graphics/plans.py').chmod(0o755);self.rejects(self.admit);(source/'graphics/plans.py').chmod(0o644);(source/'graphics/plans.py').write_text('# mutated\n');self.rejects(self.admit)
def test_real_watchdog_independently_kills_owned_child(self):
import signal,subprocess,threading
from types import SimpleNamespace
child=subprocess.Popen(['prlimit','--core=0:0','--',sys.executable,'-B','-c','import signal,time; signal.signal(signal.SIGTERM,signal.SIG_IGN); print("READY",flush=True); time.sleep(20)'],stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True,start_new_session=True,env=gate.clean_environment(LAB))
self.assertEqual(child.stdout.readline().strip(),'READY')
obj=SimpleNamespace(finished=threading.Event(),started=time.monotonic()-142.9,run=self.ns,proc_lock=threading.RLock(),expired=False,proc={'offline-owned-child':child},transient={})
obj.signal_owned=lambda sig:engine.Session.signal_owned(obj,sig)
thread=threading.Thread(target=engine.Session.watchdog,args=(obj,));thread.start()
try:self.assertEqual(child.wait(timeout=4),-signal.SIGKILL);thread.join(timeout=1);self.assertTrue(obj.expired);self.assertFalse(thread.is_alive())
finally:
if child.poll() is None:os.killpg(child.pid,signal.SIGKILL);child.wait(timeout=1)
obj.finished.set();thread.join(timeout=1);child.stdout.close();child.stderr.close()
def test_GPU_environment_and_same_identity_adapter(self):
import gpu_handler
from types import SimpleNamespace
calls=[];session=SimpleNamespace(lab=LAB,spec=plans.RUNS['GPU-G3'],env=gate.clean_environment(LAB),debit=lambda *args:calls.append(args))
adapter=gpu_handler.AdapterSession(session);self.assertEqual(adapter.spec['binary'],'gpu')
adapter.env.update(MOSAIC_G3_DIR=str(LAB/'gpu-work/acquisition'),MOSAIC_G3_NONCE='a'*64,WLR_RENDERER='gles2')
self.assertNotIn('MOSAIC_G3_DIR',session.env);self.assertEqual(session.compositor_environment['MOSAIC_G3_NONCE'],'a'*64)
adapter.debit('mutation');self.assertEqual(calls,[('mutation','hook')]);self.rejects(adapter.debit,'hook')
self.rejects(adapter.env.update,MOSAIC_G3_DIR='/run/user/daily',MOSAIC_G3_NONCE='a'*64,WLR_RENDERER='gles2')
session.spec=dict(session.spec,binary='core');self.rejects(gpu_handler.AdapterSession,session)
def test_GPU_gate_requires_independent_B4(self):
self.rejects(self.admit,'GPU-G3');self.assertEqual(json.loads(self.ledger.path.read_text())['runs'],[])
self.r['gates']['B4']=dict(status='PASS');gate.durable(self.review,self.r);self.rejects(self.admit,'GPU-G3')
def test_GPU_binding_rejects_empty_or_unreviewed(self):
import gpu_handler
for case in ({},{'gpu_source_review':'ACCEPTED'},{'gpu_source_review':'ACCEPTED','gl_observability_preflight':'ACCEPTED','build_review':'ACCEPTED','native_gl_status':'FEASIBLE'}):
self.rejects(gpu_handler.validate_binding,LAB,case,{},gate.sha(self.manifest))
def test_GPU_module_origin_attack(self):
import gpu_handler
from types import SimpleNamespace
from contextlib import ExitStack
with ExitStack() as stack:
for name in ('protocol','scenario','validate_source'):stack.enter_context(patch.dict(sys.modules,{name:SimpleNamespace(__file__='/foreign/'+name+'.py')}))
self.rejects(gpu_handler.modules,LAB)
if __name__=='__main__':unittest.main(verbosity=2)

View file

@ -0,0 +1,59 @@
"""Private source-only adapter to the independently reviewed graphics-kit Session.
No native run entry point. O1/O2 remain NOT_ADMITTED until separate review.
"""
import os,re
from pathlib import Path
PLANS={
'OM-ROOT':dict(status='NOT_ADMITTED',role='root_popup',max_captures=8,max_injections=1,requires_rootA_stable=False),
'OM-NESTED':dict(status='NOT_ADMITTED',role='nested_popup',max_captures=8,max_injections=1,requires_rootA_stable=True),
}
CAPTURES=('reference-H2-a','reference-H2-b','pre-H1','pre-H2','post-H1','post-H2','final-H1','final-H2')
def process_environment(lab,run,nonce):
assert run in PLANS and re.fullmatch('[0-9a-f]{32}',nonce)
lab=Path(lab);assert lab.is_absolute() and lab.resolve()==lab and lab.stat().st_uid==os.getuid()
# Parent must use this update solely in Session's compositor spawn environment.
# Do not put it into Session.env inherited by scientific clients/helpers.
path=lab/'graphics-runs'/run/'run'/'oom-token'
assert not path.exists() and not path.is_symlink()
return dict(SMX_OOM_TOKEN_PATH=str(path),SMX_OOM_NONCE=nonce,SMX_OOM_ROLE=PLANS[run]['role'])
def arm(session,env,admission,stable):
run=session.spec['id'];assert run in PLANS
# Separate reviewed admission must bind variant binary + OOM source freeze,
# graphics engine freeze, independent R-OOM, build review and publication.
assert admission==session.auth['prospective']
assert admission['decision']=='ACCEPTED' and admission['source_manifest_sha256']==session.auth['manifest_sha256']
assert admission.get('oom_source_review')=='ACCEPTED' and session.auth['build_review']=='ACCEPTED'
assert session.auth['publication_sha256'] and admission['variant_identity']=='COREC_V2_NATIVE_OOM_DIAGNOSTIC'
assert stable['startup'] and stable['owner'] and stable['neighbor'] and stable['reference_H2_twice']
if PLANS[run]['requires_rootA_stable']:assert stable['rootA'] and stable['rootA_native_success']
assert env['SMX_OOM_ROLE']==PLANS[run]['role']
path=Path(env['SMX_OOM_TOKEN_PATH']);assert path.parent.resolve()==session.run.resolve()
assert path.name=='oom-token' and not path.is_symlink()
# The parent's reviewed OOM ledger extension must provide hook cap=1. Debit
# precedes O_EXCL write; failed arm stays spent, with no retry/refund.
session.debit('hook')
fd=os.open(path,os.O_CREAT|os.O_EXCL|os.O_WRONLY|os.O_NOFOLLOW,0o600)
try:
os.fchmod(fd,0o600)
token=f"SMXOOM1 {env['SMX_OOM_NONCE']} {env['SMX_OOM_ROLE']}\n".encode('ascii')
assert os.write(fd,token)==len(token);os.fsync(fd)
finally:os.close(fd)
session.record('oom-arm',role=env['SMX_OOM_ROLE'],nonce=env['SMX_OOM_NONCE'],path=str(path))
def native_failure(log,env,pid):
lines=[line for line in log.splitlines() if 'SMX_OOM ENOMEM ' in line]
assert len(lines)==1
fields=dict(re.findall(r'([a-z_]+)=([^ ]+)',lines[0]))
for key,value in dict(role=env['SMX_OOM_ROLE'],nonce=env['SMX_OOM_NONCE'],pid=str(pid),matched='1',failures='1',initialized='0',listeners='0',published='0').items():assert fields.get(key)==value,(key,fields)
assert all(fields.get(k) not in (None,'(nil)','0x0') for k in ['xdg','xdg_parent','parent','tree'])
assert fields.get('successes')==('0' if env['SMX_OOM_ROLE']=='root_popup' else '1')
assert re.fullmatch(r'\d+',fields.get('children',''))
# WHY: native children counts existing PARENT children, not the failed tree.
# CONTRACT: preserve it without demanding impossible nested parent_children0.
# INVARIANT: NULL result must bind same role/PID/xdg/parent, unpublished node.
results=[line for line in log.splitlines() if 'SMX_OOM result ' in line and 'role='+env['SMX_OOM_ROLE']+' ' in line]
assert len(results)==1,'unique targeted NULL caller result'
result=dict(re.findall(r'([a-z_]+)=([^ ]+)',results[0]))
for key in ('role','pid','xdg','xdg_parent','parent','successes','failures'):assert result.get(key)==fields[key]
assert result.get('tree') in ('(nil)','0x0'),'failed tree returned non-NULL'
fields.update(parent_children=fields['children'],children_scope='existing parent children, not failed tree',caller_result=result)
return fields

View file

@ -0,0 +1,173 @@
"""EXPERIMENTAL prospective O1/O2 handler. No allocator or admission policy."""
import errno,re,time,subprocess
from pathlib import Path
import cases,oom
from gate import sha,need
from geometry import solid_point,exact,count
from engine import COLORS
def opening_before_map(raw,popup,parent):
# WHY: native no-memory can precede configure/MAP. CONTRACT/INVARIANT:
# validate actual serial/grab/parent wire, never await the failed popup MAP.
rows=re.findall(rf'{popup} CREATE wl=(\d+) xdg=(\d+) role=(\d+) parent={parent} parent_xdg=(\d+)',raw)
need(len(rows)==1,'unique failed popup identity');wl,xdg,role,parent_xdg=map(int,rows[0])
parent_ids=re.findall(rf'{parent} CREATE wl=(\d+) xdg=(\d+) role=(\d+)',raw)
need(len(parent_ids)==1 and int(parent_ids[0][1])==parent_xdg,'failed popup exact parent')
grabs=re.findall(rf'{popup} GRAB_REQUEST origin=wl_pointer.button target={parent} serial=(\d+) used=(\d+)',raw)
need(len(grabs)==1 and grabs[0][0]==grabs[0][1] and int(grabs[0][0])>0,'failed popup real grab')
serial=int(grabs[0][0]);button=raw.index(f'POINTER_BUTTON target={parent} serial={serial} button=272 state=1')
wire=re.search(rf'wl_pointer#\d+\.button\({serial}, \d+, 272, 1\)',raw)
create=re.search(rf'xdg_surface#{xdg}\.get_popup\(new id xdg_popup#{role}, xdg_surface#{parent_xdg}, xdg_positioner#\d+\)',raw)
grab=re.search(rf'xdg_popup#{role}\.grab\(wl_seat#\d+, {serial}\)',raw)
need(wire is not None and create is not None and grab is not None and wire.start()<button<create.start()<grab.start(),'failed grab wire association')
return dict(popup=popup,parent=parent,serial=serial,wl=wl,xdg=xdg,role=role,parent_xdg=parent_xdg)
def mapped_root(raw):
proof=opening_before_map(raw,'POPUP_A','TOPLEVEL')
need(raw.count('POPUP_A MAP ')==1 and 'POPUP_A DESTROY ' not in raw and 'POPUP_A POPUP_DONE ' not in raw,'O2 same rootA mapped')
cfg=re.findall(r'POPUP_A ROLE_CONFIGURE x=(-?\d+) y=(-?\d+) w=(\d+) h=(\d+)',raw)
ack=re.findall(r'POPUP_A XDG_CONFIGURE serial=(\d+) ack',raw)
need(len(cfg)==len(ack)==1,'O2 unique rootA configure/ACK')
x,y,w,h=map(int,cfg[0]);serial=int(ack[0]);xdg=proof['xdg'];role=proof['role'];wl=proof['wl']
rolewire=re.search(rf'xdg_popup#{role}\.configure\({x}, {y}, {w}, {h}\)',raw)
ackwire=re.search(rf'xdg_surface#{xdg}\.ack_configure\({serial}\)',raw)
nativecfg=re.search(rf'xdg_surface#{xdg}\.configure\({serial}\)',raw)
attach=re.search(rf'wl_surface#{wl}\.attach\(wl_buffer#(\d+), 0, 0\)',raw)
commits=list(re.finditer(rf'wl_surface#{wl}\.commit\(\)',raw))
need(all((rolewire,ackwire,nativecfg,attach)) and len(commits)==2,'O2 rootA native configure/map evidence')
buffer=attach[1]
created=re.search(rf'create_buffer\(new id wl_buffer#{buffer}, 0, {w}, {h}, {w*4}, 1\)',raw)
need(created is not None and commits[0].start()<rolewire.start()<nativecfg.start()<ackwire.start()<created.start()<attach.start()<commits[1].start()<raw.index('POPUP_A MAP '),'O2 rootA native ACK/SHM order')
return dict(proof,configure=[x,y,w,h],ack=serial,buffer=int(buffer))
def expected_disconnect(raw,code):
# WHY/CONTRACT/INVARIANT: retain raw no_memory code1 and client errno;
# arbitrary errors, server death and missing EXIT never become normal close.
errors=re.findall(r'wl_display#\d+\.error\([^\n]*?, (\d+), [^\n]*\)',raw)
exits=re.findall(r'EXIT error=(\d+)',raw)
need(code in (0,1) and errors==['1'] and len(exits)==1 and int(exits[0]) in (errno.ENOMEM,errno.EPROTO),'expected no-memory disconnect not established')
return dict(exit=code,wire_error=errors,display_errno=exits,expected_enomem=True)
def retire_enomem(s):
p=s.proc['owner'];raw=s.log('owner');proof=expected_disconnect(raw,p.poll())
need(s.find('owner') is None,'disconnected owner remains in IPC')
proof.update(name='owner',pid=p.pid,status='PASS',meaning='EXPECTED_ENOMEM_DISCONNECT; not ordinary close',raw_log=raw)
s.closed.append(proof);s.record('owner-enomem-disconnect',**proof);return proof
def availability(s):
need(s.comp.poll() is None,'critical compositor death')
exe=Path(f'/proc/{s.comp.pid}/exe')
need(exe.resolve()==Path(s.auth['binary']).resolve() and sha(exe)==s.auth['binary_sha256'],'critical compositor PID/executable changed')
maps=Path(f'/proc/{s.comp.pid}/maps').read_text()
paths={str(Path(line.split()[-1]).resolve()) for line in maps.splitlines() if len(line.split())>=6 and line.split()[-1].startswith('/') and 'libwlroots' in line.split()[-1]}
need(paths=={s.auth['wlroots']} and sha(s.auth['wlroots'])==s.auth['wlroots_sha256'],'critical mapped wlroots changed')
build=s.command_process(['readelf','-n',str(exe)])
ids=re.findall(r'Build ID: ([0-9a-f]+)',build.stdout)
need(build.returncode==0 and len(ids)==1 and ids[0]==s.auth['build_id'],'actual availability BuildID mismatch')
need(not any('libscenefx' in line for line in maps.splitlines()),'unexpected dynamic SceneFX in OOM binary')
return dict(pid=s.comp.pid,exe=str(exe.resolve()),sha256=sha(exe),build_id=ids[0],wlroots=list(paths),maps=maps,scenefx_link='static',swaysock=str(s.sock),captured_before_cleanup=True)
def availability_certificate(identity,expected,snapshot,neighbor_raw,buttons):
# WHY: a final exit0 is not a post-injection live availability observation.
# CONTRACT: exact surviving PID/exe/BuildID/maps + private IPC + fresh wire
# press/release, saved before cleanup. INVARIANT: no superficial PASS receipt.
need(identity.get('captured_before_cleanup') is True and identity['pid']==expected['pid'],'availability PID/time scope')
need(identity['sha256']==expected['binary_sha256'] and identity['build_id']==expected['build_id'] and identity['exe']==expected['binary'],'availability ELF identity')
need(identity['wlroots']==[expected['wlroots']] and expected['wlroots'] in identity['maps'] and 'libscenefx' not in identity['maps'],'availability mappings')
sock=Path(identity['swaysock']);need(sock.parent.name.startswith('smx-') and sock.parent.parent in (Path('/tmp'),Path('/var/tmp')),'daily/foreign socket')
need(snapshot['tree'].get('type')=='root' and snapshot['tree'].get('nodes') and len([o for o in snapshot['outputs'] if o.get('active')])==2,'live IPC tree/outputs absent')
lines=buttons.get('reference',[]);events=[re.search(r'BUTTON ns=\d+ serial=(\d+) button=(\d+) state=(\d+)',line) for line in lines]
need(len(events)==2 and all(events) and [list(map(int,e.groups()[1:])) for e in events]==[[273,1],[273,0]] and int(events[0][1])<int(events[1][1]),'neighbor fresh press/release absent')
need(not any(v for name,v in buttons.items() if name!='reference'),'wrong client got neighbor input')
for e in events:need(re.search(rf'wl_pointer#\d+\.button\({e[1]}, \d+, 273, {e[3]}\)',neighbor_raw),'neighbor actual wire absent')
return dict(status='PARTIALLY_FEASIBLE',captured_before_cleanup=True,identity=identity,snapshot=snapshot,neighbor_buttons=buttons,neighbor_wire=neighbor_raw)
def native_root_success(raw,pid):
lines=[line for line in raw.splitlines() if 'SMX_OOM result ' in line and 'role=root_popup' in line and 'tree=' in line and 'failures=0' in line]
need(len(lines)==1,'O2 rootA native success identity missing/ambiguous')
fields=dict(re.findall(r'([a-z_]+)=([^ ]+)',lines[0]))
need(fields.get('pid')==str(pid) and fields.get('tree') not in (None,'(nil)','0x0'),'O2 native rootA success')
return fields
def popup_pixels(pair,baseline,failed=None,final=False):
# WHY: exact H2 alone misses stale failed/retired popup pixels on H1.
# CONTRACT/INVARIANT: inspect both outputs; only surviving A is allowed post B.
exact(baseline,pair[1])
forbidden=('POPUP_A','POPUP_B') if final else (failed,)
for role in forbidden:
need(role in ('POPUP_A','POPUP_B'),'failed role required')
need(all(count(image,COLORS[role])==0 for image in pair),'failed/retired popup pixels '+role)
return dict(status='PASS',outputs=['HEADLESS-1','HEADLESS-2'],forbidden=list(forbidden),H2='EXACT')
def surviving_root_input(s,post):
# WHY: a neighbor click can dismiss the old grab before A input is measured.
# CONTRACT/INVARIANT: fresh painted A, actual configure plus updated owner,
# exclusive wire pair and pointer-local agreement, before outside dismissal.
raw=s.log('owner');proof=mapped_root(raw);state=s.owner('owner')
x,y,w,h=proof['configure'];rect=[state['origin'][0]+x,state['origin'][1]+y,w,h]
point=solid_point(post[0],COLORS['POPUP_A'],s.outputs[0],s.frames['HEADLESS-1'],rect)
tails,buttons=s.click('owner',point,'right')
pair=s.delivery('owner',rect,point,tails,buttons,'POPUP_A')
return dict(status='PARTIALLY_FEASIBLE',point=point,rectangle=rect,owner=state,wire=proof,pair=pair,tails=tails,buttons=buttons)
def run(s):
s.closure_scenarios={'owner':'root_failure' if s.spec['id']=='OM-ROOT' else 'nested_failure'}
cases.init_reference(s);s.debit('chain');s.launch_client('owner',s.spec['client'],s.spec['placement']);s.owner('owner')
s.cmd(f'[con_id={s.find("owner")["id"]}] focus');state=s.calibrate('owner')
baseline=cases.stable_reference(s);root_success=None
if s.spec['id']=='OM-NESTED':
# Owner origin is corroborated below by actual pointer-local delivery.
origin=state['origin'];xy=[origin[0]+10,origin[1]+100]
tails,buttons=s.click('owner',xy,'left',True)
s.wait(lambda:'POPUP_A MAP ' in s.log('owner'));time.sleep(.15)
root_success=native_root_success(s.log('compositor'),s.comp.pid)
proof=mapped_root(s.log('owner'))
s.delivery('owner',[*origin,420,320],xy,tails,buttons,'TOPLEVEL',proof['serial'])
pre=s.pair_capture('oom-pre');popup_pixels(pre,baseline,'POPUP_A' if root_success is None else 'POPUP_B');before=s.snapshot('oom-before')
if root_success is not None:
need(count(pre[0],COLORS['POPUP_A'])>0 and count(pre[1],COLORS['POPUP_A'])==0,'nested precondition rootA sampled clip absent')
s.save('oom-rootA-before-injection',dict(native_success=root_success,wire=proof,painted_H1=True,H2='EXACT',clip_scope='sampled; native generation NOT_PROVEN'))
target='TOPLEVEL' if s.spec['id']=='OM-ROOT' else 'POPUP_A';popup='POPUP_A' if target=='TOPLEVEL' else 'POPUP_B'
xy=solid_point(pre[0],COLORS[target],s.outputs[0],s.frames['HEADLESS-1'])
stable=dict(startup=True,owner=True,neighbor=s.find('reference') is not None,reference_H2_twice=True,rootA=root_success is not None,rootA_native_success=root_success is not None)
oom.arm(s,s.oom_env,s.auth['prospective'],stable)
tails,buttons=s.click('owner',xy,'left',True)
s.wait(lambda:'SMX_OOM ENOMEM ' in s.log('compositor'))
wire=opening_before_map(s.log('owner'),popup,target)
native=oom.native_failure(s.log('compositor'),s.oom_env,s.comp.pid)
results=[line for line in s.log('compositor').splitlines() if 'SMX_OOM result ' in line and 'role='+s.oom_env['SMX_OOM_ROLE']+' ' in line]
need(len(results)==1 and 'tree=(nil)' in results[0] and 'failures=1' in results[0],'native NULL caller outcome absent')
ident=availability(s);s.save('oom-availability-identity',ident);after=s.snapshot('oom-after');s.save('oom-availability-ipc',dict(identity=ident,snapshot=after,captured_before_cleanup=True))
need(popup+' MAP ' not in s.log('owner'),'failed popup unexpectedly mapped')
owner_raw_exit=s.proc['owner'].poll();disconnected=None
if owner_raw_exit is None:
time.sleep(.15);owner_raw_exit=s.proc['owner'].poll()
if owner_raw_exit is not None:disconnected=retire_enomem(s)
post=s.pair_capture('oom-post');post_pixels=popup_pixels(post,baseline,popup)
old_root=dict(status='NOT_EXERCISED',reason='O1 has no old rootA' if root_success is None else ('EXPECTED_ENOMEM_DISCONNECT' if disconnected else 'ROOT_A_DISMISSED'),raw=s.log('owner'))
if root_success is not None and disconnected is None and 'POPUP_A DESTROY ' not in s.log('owner') and 'POPUP_A POPUP_DONE ' not in s.log('owner'):
need(count(post[0],COLORS['POPUP_A'])>0 and count(post[1],COLORS['POPUP_A'])==0,'surviving rootA sampled clipping')
old_root=surviving_root_input(s,post);old_root['native_success']=root_success
s.save('oom-rootA-post-input',old_root)
neighbor=s.find('reference');need(neighbor is not None,'surviving neighbor absent')
xy=solid_point(post[1],(48,48,56),s.outputs[1],s.frames['HEADLESS-2'])
tails,buttons=s.click('reference',xy)
# WHY/CONTRACT/INVARIANT: a surviving real popup grab may consume the
# outside click to dismiss; only a measured dismissal permits a fresh
# neighbor click. This does not claim universal click-through.
if not buttons.get('reference'):
need('POPUP_DONE ' in tails.get('owner',''),'neighbor input missing without measured outside dismissal')
s.save('oom-neighbor-outside-dismissal',dict(tails=tails,buttons=buttons))
tails,buttons=s.click('reference',xy)
lines=buttons.get('reference',[])
events=[re.search(r'BUTTON ns=\d+ serial=(\d+) button=(\d+) state=(\d+)',line) for line in lines]
need(len(events)==2 and all(events) and [list(map(int,e.groups()[1:])) for e in events]==[[273,1],[273,0]],'fresh neighbor button pair')
for e in events:need(re.search(rf'wl_pointer#\d+\.button\({e[1]}, \d+, 273, {e[3]}\)',tails['reference']) is not None,'neighbor real wire')
need(not any(v for k,v in buttons.items() if k!='reference'),'neighbor button not exclusive')
live=availability_certificate(ident,dict(s.auth,pid=s.comp.pid),after,tails['reference'],buttons)
s.save('oom-availability-live',live)
if disconnected is None:s.close_client('owner',scenario=s.closure_scenarios['owner'])
final=s.pair_capture('oom-final');final_pixels=popup_pixels(final,baseline,final=True)
s.save('oom-measurements',dict(post_pixels=post_pixels,final_pixels=final_pixels,native=native,wire=wire,rootA_success=root_success,old_rootA=old_root,before=before,after=after,availability=ident,owner_raw_exit=owner_raw_exit,owner_disconnect=disconnected,neighbor_buttons=buttons,H2_pre_post_final='EXACT',captures=8,closure_scenario=s.closure_scenarios['owner'],complete_damage='NOT_PROVEN',native_stale_listeners='NOT_PROVEN',native_no_partial_popup='NOT_PROVEN; IPC has no popup-node schema',atomic_generation='NOT_PROVEN'))
return 'PARTIALLY_FEASIBLE'

View file

@ -0,0 +1,21 @@
"""Exact binary RGB masks with explicit output origin, clip and occlusion.
Screenshots prove color/visible geometry only, never native-buffer identity.
"""
from PIL import Image,ImageChops,ImageDraw
from model import *
COLORS=dict(TOPLEVEL=(35,68,204),POPUP_A=(224,192,32),POPUP_B=(32,208,160))
def color_geometry(im,target,rect,output,clip,occluders=()):
require(im.size==(output.rectangle.width,output.rectangle.height),'replay-output-size')
visible=intersect(rect,clip) if clip else rect
expected=Image.new('L',im.size,0);draw=ImageDraw.Draw(expected)
box=to_png(visible,output)
if box.width and box.height:draw.rectangle((box.x,box.y,box.end[0]-1,box.end[1]-1),fill=255)
for occ in occluders:
ob=to_png(occ,output)
if ob.width and ob.height:draw.rectangle((ob.x,ob.y,ob.end[0]-1,ob.end[1]-1),fill=0)
actual=Image.new('L',im.size);actual.putdata([255 if px==COLORS[target] else 0 for px in im.get_flattened_data()])
require(ImageChops.difference(expected,actual).getbbox() is None,'replay-paint-'+target)
return dict(count=sum(actual.histogram()[1:]),bbox=actual.getbbox())

View file

@ -0,0 +1,17 @@
"""EXPERIMENTAL new-ticket intent; no plan grants admission by itself."""
from copy import deepcopy
CAPS={'FS-P':12,'FS-C':16,'DY-C':16,'OM-ROOT':10,'OM-NESTED':10,'GPU-G3':12,'OM-EXTRA':12,'GPU-CTRL':12}
LIMITS=dict(runs=8,captures=110,captures_per_run=18,duration_s=150)
H=[dict(name='HEADLESS-1',logical=[-800,0,800,600],mode=[800,600],scale=1,transform='normal'),dict(name='HEADLESS-2',logical=[0,0,800,600],mode=[800,600],scale=1,transform='normal')]
def config(spec):
return '\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'])+'\nworkspace 1 output HEADLESS-1\nworkspace 2 output HEADLESS-2\nfocus_follows_mouse no\nseat seat0 hide_cursor 100\nshadows disable\nblur disable\n'
RUNS={rid:dict(id=rid,binary='pristine' if rid=='FS-P' else 'core',mode='pristine' if rid=='FS-P' else 'mosaic',outputs=deepcopy(H),client='nested-client',placement='static_cross',axis=0,boundary=0,capture_limit=cap,action_limits=dict(spawn=12,tool=160,configuration=80,mutation=26,move=24,click=12,grab=4,camera=6,chain=2,source_marker=1),admission='INDEPENDENT_REVIEW_REQUIRED') for rid,cap in CAPS.items()}
for rid in ('FS-P','FS-C'):RUNS[rid].update(client='fullscreen-client',placement='fullscreen_cross')
for rid in ('OM-ROOT','OM-NESTED','OM-EXTRA'):
RUNS[rid]['binary']='oom';RUNS[rid]['action_limits']['hook']=1
for rid in ('GPU-G3','GPU-CTRL'):
RUNS[rid].update(binary='gpu',admission='NOT_ADMITTED',reason='B4 and separately reviewed GPU adapter required')
RUNS[rid]['action_limits']['hook']=3
RUNS['OM-EXTRA'].update(admission='NOT_ADMITTED',reason='Distinct question, handler and additional independent review required')
for spec in RUNS.values():spec['config']=config(spec)
assert len(RUNS)==8 and sum(CAPS.values())==100 and max(CAPS.values())<=18

View file

@ -0,0 +1,124 @@
"""Shared offline/live predicates: facts precede scientific classification.
CONTRACT: full RGB comparisons have no mask/tolerance; grabs require real logs.
INVARIANT: dismissal, client destruction, clipping and layout remain distinct.
"""
import re
from model import Rect,Space,Provenance,Output,popup,crossing,local_clip_to_global,require
from pixels import color_geometry,COLORS
from PIL import Image,ImageChops
class Defect(RuntimeError):
"""A directly measured scientific contract failure, distinct from fixture ambiguity."""
def paint(im,target):
im=im.convert('RGB');assert im.size==(800,600)
mask=Image.new('L',im.size);mask.putdata([255 if p==COLORS[target] else 0 for p in im.get_flattened_data()])
return dict(count=sum(mask.histogram()[1:]),bbox=mask.getbbox())
def point(im,target,origin):
box=paint(im,target)['bbox'];assert box,('no measured paint',target)
# WHY: A may be occluded by B; bbox center alone is not a valid hit point.
x1,y1,x2,y2=box
for y in range(y1+10,y2-10,5):
for x in range(x1+10,x2-10,5):
if all(im.getpixel((xx,yy))==COLORS[target] for yy in range(y-8,y+9) for xx in range(x-8,x+9)):
return [x+origin,y]
raise AssertionError('no solid measured interior '+target)
def equal(a,b):
assert a.size==b.size==(800,600)
rows=[(i%800,i//800,x,y) for i,(x,y) in enumerate(zip(a.get_flattened_data(),b.get_flattened_data())) if x!=y]
return dict(equal=not rows,changed=len(rows),bbox=ImageChops.difference(a,b).getbbox(),pixels=rows)
def reference(a,b):
result=equal(a,b)
assert result['equal'] and all(c==(48,48,56) for c in a.get_flattened_data()),'unstable reference'
return result
def configured(text,name):
rows=re.findall(rf'{name} ROLE_CONFIGURE x=(-?\d+) y=(-?\d+) w=(\d+) h=(\d+)',text)
assert rows,(name,'no configure')
return list(map(int,rows[-1]))
def geometry(text,origin,provenance=None,axis=0,boundary=0):
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_axis=axis,crossing_boundary=boundary,crossing={key:(result[key][axis]<boundary<result[key][axis]+result[key][axis+2]) for key in ('A','B')})
return result
def mapped(text):
for name in ['POPUP_A','POPUP_B']:
assert text.count(name+' MAP ')==1 and name+' POPUP_DONE ' not in text and name+' DESTROY ' not in text,(name,'not mapped')
def protocol(text):
assert not re.search(r'wl_display[^\n]*\.error\(|protocol error|invalid_popup_parent|not_the_topmost_popup',text,re.I),'protocol error'
mapped(text)
ids={}
for name in ['TOPLEVEL','POPUP_A','POPUP_B']:
m=re.search(rf'{name} CREATE wl=(\d+) xdg=(\d+) role=(\d+)',text);assert m
ids[name]=list(map(int,m.groups()))
assert len({n for v in ids.values() for n in v})==9,'object identities reused'
grabs=[]
for name,parent in [('POPUP_A','TOPLEVEL'),('POPUP_B','POPUP_A')]:
create=re.search(rf'{name} CREATE .*parent={parent} parent_xdg=(\d+)',text);assert create
assert int(create[1])==ids[parent][1], 'wrong XDG parent'
debug_parent=re.search(rf'xdg_surface#{ids[name][1]}\.get_popup\(new id xdg_popup#{ids[name][2]}, xdg_surface#{ids[parent][1]}, xdg_positioner#\d+\)',text)
require(debug_parent is not None,'debug-parent-request')
m=re.search(rf'{name} GRAB_REQUEST origin=wl_pointer.button target={parent} serial=(\d+) used=(\d+)',text);assert m
serial,used=map(int,m.groups());assert serial==used and 0<serial<=0xffffffff
button=f'POINTER_BUTTON target={parent} serial={serial} button=272 state=1'
assert text.index(button)<m.start(),'grab serial not from earlier button'
debug_button=re.search(rf'wl_pointer#\d+\.button\({serial}, \d+, 272, 1\)',text)
require(debug_button is not None and debug_button.start()<m.start(),'debug-real-button-serial')
role=text.index(name+' ROLE_CONFIGURE ');cfg=re.search(rf'{name} XDG_CONFIGURE serial=(\d+) ack',text);assert cfg
mp=text.index(name+' MAP ')
assert m.start()<role<cfg.start()<mp,'configure ACK map order'
popup_role=ids[name][2];xdg=ids[name][1];wl=ids[name][0]
# WAYLAND_DEBUG is independent corroboration, not just fixture claims.
request=re.search(rf'xdg_popup#{popup_role}\.grab\(wl_seat#\d+, {serial}\)',text);assert request
ack=re.search(rf'xdg_surface#{xdg}\.ack_configure\({cfg[1]}\)',text);assert ack
attach=re.search(rf'wl_surface#{wl}\.attach\(',text);assert attach
commits=list(re.finditer(rf'wl_surface#{wl}\.commit\(\)',text));assert len(commits)>=2
assert request.start()<commits[0].start()<role<ack.start()<attach.start()<commits[1].start()<mp
actual=configured(text,name)
require(re.search(rf'xdg_popup#{popup_role}\.configure\({actual[0]}, {actual[1]}, {actual[2]}, {actual[3]}\)',text) is not None,'debug-popup-configure')
require(re.search(rf'xdg_surface#{xdg}\.configure\({cfg[1]}\)',text) is not None,'debug-xdg-configure')
native_attach=re.search(rf'wl_surface#{wl}\.attach\(wl_buffer#(\d+), 0, 0\)',text[ack.start():mp])
require(native_attach is not None,'popup-native-attach')
native_buffer=int(native_attach[1])
native_create=re.search(rf'create_buffer\(new id wl_buffer#{native_buffer}, 0, {actual[2]}, {actual[3]}, {actual[2]*4}, 1\)',text[role:mp])
require(native_create is not None,'popup-native-configured-size')
if parent=='POPUP_A':assert text.index('POPUP_A MAP ')<text.index(button)
grabs.append(dict(popup=name,parent=parent,serial=serial,used=used,origin='wl_pointer.button',ids=ids[name],ack=int(cfg[1]),native_buffer=native_buffer,native_size=actual[2:]))
assert grabs[0]['serial']<grabs[1]['serial'], 'distinct ordered serials'
return dict(ids=ids,grabs=grabs,hierarchy='TOPLEVEL -> POPUP_A -> POPUP_B',status='PASS')
def pair(lines,target):
events=[]
for line in lines:
m=re.fullmatch(r'POINTER_BUTTON target=(\w+) serial=(\d+) button=(\d+) state=(\d+)',line);assert m
events.append([m[1],*map(int,m.groups()[1:])])
if len(events)!=2 or any(e[0]!=target for e in events) or [e[2:] for e in events]!=[[273,1],[273,0]]:
raise Defect('wrong actual button recipient/pair: '+str(events))
assert 0<events[0][1]<events[1][1]<=0xffffffff
return events
def dismissal(text,buttons):
if any('target=POPUP_A ' in b or 'target=POPUP_B ' in b for b in buttons):
raise Defect('invisible popup received actual button')
done=re.findall(r'(POPUP_[AB]) POPUP_DONE ',text)
destroyed=re.findall(r'(POPUP_[AB]) DESTROY ',text)
assert done==['POPUP_B','POPUP_A'] and destroyed==['POPUP_B','POPUP_A'],('dismissal order',done,destroyed)
return dict(done=done,destroyed=destroyed,invisible_buttons=buttons,status='PASS',meaning='outside dismissal distinct from invisible hit')

View file

@ -0,0 +1,25 @@
#!/usr/bin/env python3
"""Generate protocols and compile C17 clients, without executing any client."""
import argparse,json,shlex,subprocess
from pathlib import Path
from gate import canonical_lab,clean_environment,durable,sha,need,disable_dump
def prepare(lab):
disable_dump();lab=canonical_lab(lab);src=Path(__file__).resolve().parent;build=lab/"followup-work/client-build";build.mkdir(parents=True,exist_ok=False)
env=clean_environment(lab);commands=[]
def invoke(argv):
r=subprocess.run(list(map(str,argv)),env=env,capture_output=True,text=True,timeout=30)
commands.append(dict(argv=list(map(str,argv)),exit=r.returncode,stdout=r.stdout,stderr=r.stderr))
durable(lab/"followup-work/client-compile.json",dict(status="PENDING" if r.returncode==0 else "FAIL",commands=commands,runtime="NOT_RUN"))
need(r.returncode==0,"client build command failed");return r.stdout.strip()
versions={name:invoke([name,"--version"]) for name in ("cc","wayland-scanner","pkg-config")}
flags=shlex.split(invoke(["pkg-config","--cflags","--libs","wayland-client"]))
for protocol,xml in (("xdg-shell","xdg-shell.xml"),("virtual-pointer","wlr-virtual-pointer-unstable-v1.xml")):
invoke(["wayland-scanner","client-header",src/"protocols"/xml,build/(protocol+"-client.h")])
invoke(["wayland-scanner","private-code",src/"protocols"/xml,build/(protocol+"-protocol.c")])
for name,protocol in (("fullscreen-client","xdg-shell"),("nested-client","xdg-shell"),("vertical-client","xdg-shell"),("saved-client","xdg-shell"),("static-reference","xdg-shell"),("pointer-daemon","virtual-pointer")):
invoke(["cc","-std=c17","-O2","-Wall","-Wextra","-I",build,src/"clients"/(name+".c"),build/(protocol+"-protocol.c"),"-o",build/name,*flags])
durable(lab/"followup-work/client-compile.json",dict(status="PASS",commands=commands,versions=versions,inputs={str(p.relative_to(src)):sha(p) for folder in ("clients","protocols") for p in (src/folder).iterdir()},generated={str(p.relative_to(build)):sha(p) for p in build.iterdir()},runtime="NOT_RUN"))
if __name__=="__main__":
p=argparse.ArgumentParser();p.add_argument("--lab",required=True);a=p.parse_args();prepare(a.lab)

View file

@ -0,0 +1,152 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="wlr_virtual_pointer_unstable_v1">
<copyright>
Copyright © 2019 Josef Gajdusek
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<interface name="zwlr_virtual_pointer_v1" version="2">
<description summary="virtual pointer">
This protocol allows clients to emulate a physical pointer device. The
requests are mostly mirror opposites of those specified in wl_pointer.
</description>
<enum name="error">
<entry name="invalid_axis" value="0"
summary="client sent invalid axis enumeration value" />
<entry name="invalid_axis_source" value="1"
summary="client sent invalid axis source enumeration value" />
</enum>
<request name="motion">
<description summary="pointer relative motion event">
The pointer has moved by a relative amount to the previous request.
Values are in the global compositor space.
</description>
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
<arg name="dx" type="fixed" summary="displacement on the x-axis"/>
<arg name="dy" type="fixed" summary="displacement on the y-axis"/>
</request>
<request name="motion_absolute">
<description summary="pointer absolute motion event">
The pointer has moved in an absolute coordinate frame.
Value of x can range from 0 to x_extent, value of y can range from 0
to y_extent.
</description>
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
<arg name="x" type="uint" summary="position on the x-axis"/>
<arg name="y" type="uint" summary="position on the y-axis"/>
<arg name="x_extent" type="uint" summary="extent of the x-axis"/>
<arg name="y_extent" type="uint" summary="extent of the y-axis"/>
</request>
<request name="button">
<description summary="button event">
A button was pressed or released.
</description>
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
<arg name="button" type="uint" summary="button that produced the event"/>
<arg name="state" type="uint" enum="wl_pointer.button_state" summary="physical state of the button"/>
</request>
<request name="axis">
<description summary="axis event">
Scroll and other axis requests.
</description>
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
<arg name="axis" type="uint" enum="wl_pointer.axis" summary="axis type"/>
<arg name="value" type="fixed" summary="length of vector in touchpad coordinates"/>
</request>
<request name="frame">
<description summary="end of a pointer event sequence">
Indicates the set of events that logically belong together.
</description>
</request>
<request name="axis_source">
<description summary="axis source event">
Source information for scroll and other axis.
</description>
<arg name="axis_source" type="uint" enum="wl_pointer.axis_source" summary="source of the axis event"/>
</request>
<request name="axis_stop">
<description summary="axis stop event">
Stop notification for scroll and other axes.
</description>
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
<arg name="axis" type="uint" enum="wl_pointer.axis" summary="the axis stopped with this event"/>
</request>
<request name="axis_discrete">
<description summary="axis click event">
Discrete step information for scroll and other axes.
This event allows the client to extend data normally sent using the axis
event with discrete value.
</description>
<arg name="time" type="uint" summary="timestamp with millisecond granularity"/>
<arg name="axis" type="uint" enum="wl_pointer.axis" summary="axis type"/>
<arg name="value" type="fixed" summary="length of vector in touchpad coordinates"/>
<arg name="discrete" type="int" summary="number of steps"/>
</request>
<request name="destroy" type="destructor" since="1">
<description summary="destroy the virtual pointer object"/>
</request>
</interface>
<interface name="zwlr_virtual_pointer_manager_v1" version="2">
<description summary="virtual pointer manager">
This object allows clients to create individual virtual pointer objects.
</description>
<request name="create_virtual_pointer">
<description summary="Create a new virtual pointer">
Creates a new virtual pointer. The optional seat is a suggestion to the
compositor.
</description>
<arg name="seat" type="object" interface="wl_seat" allow-null="true"/>
<arg name="id" type="new_id" interface="zwlr_virtual_pointer_v1"/>
</request>
<request name="destroy" type="destructor" since="1">
<description summary="destroy the virtual pointer manager"/>
</request>
<!-- Version 2 additions -->
<request name="create_virtual_pointer_with_output" since="2">
<description summary="Create a new virtual pointer">
Creates a new virtual pointer. The seat and the output arguments are
optional. If the seat argument is set, the compositor should assign the
input device to the requested seat. If the output argument is set, the
compositor should map the input device to the requested output.
</description>
<arg name="seat" type="object" interface="wl_seat" allow-null="true"/>
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
<arg name="id" type="new_id" interface="zwlr_virtual_pointer_v1"/>
</request>
</interface>
</protocol>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,63 @@
#!/usr/bin/env python3
"""WHY/CONTRACT/INVARIANT: one new ID, reviewed source, no retry or auto-review."""
import argparse,json,os,time
from pathlib import Path
from gate import Ledger,need,disable_dump,validate_review
from plans import RUNS
from engine import Session,storage_guard
import cases
def normal_remaining(s):
for name in list(s.proc):
if name not in ('owner','compositor','pointer') and s.proc[name].poll() is None:s.close_client(name,'close')
def execute(rid,lab,manifest,review,publication):
disable_dump();storage_guard(Path(lab))
# WHY: missing replay inputs must not consume a run. The independent gate is
# validated again inside admit, then before every action. Never repair a freeze.
validate_review(lab,manifest,review,publication,rid)
need(not (Path(lab)/'graphics-runs'/rid).exists(),'existing run directory; no overwrite/retry')
auth=Ledger(lab).admit(rid,manifest,review,publication)
s=None;failure=None;status='INCONCLUSIVE'
try:
if rid=='GPU-G3':
from gpu_handler import GPUSceneSession,run as gpu_run
s=GPUSceneSession(RUNS[rid],auth);status=gpu_run(s)
else:
s=Session(RUNS[rid],auth);s.start()
if rid=='GPU-G3':pass
elif rid in ('FS-P','FS-C'):
from fullscreen import run,comparable
status=run(s)
if rid=='FS-C' and status=='PARTIALLY_FEASIBLE':
control=s.lab/'graphics-runs/FS-P/run/fullscreen-comparability.json'
need(control.is_file(),'FS-P measured comparable source missing')
s.save('fullscreen-comparison',comparable(json.loads(control.read_text()),json.loads((s.run/'fullscreen-comparability.json').read_text())))
elif rid=='DY-C':status=cases.dynamic_campaign(s)
elif rid in ('OM-ROOT','OM-NESTED'):
from oom_handler import run
status=run(s)
else:raise ValueError('NOT_ADMITTED: independent diagnostic adapter absent')
normal_remaining(s)
except Exception as exc:
failure=f'{type(exc).__name__}: {exc}';status='INCONCLUSIVE'
if s is not None:
s.save('partial-failure',dict(status=status,failure=failure,events=s.events,captures_observed=sum(e['kind']=='capture' for e in s.events),compositor_exit=s.comp.poll() if hasattr(s,'comp') else None,owner_exit=s.proc['owner'].poll() if 'owner' in s.proc else None,critical_or_unresolved=True,no_retry=True))
finally:
if s is None:Ledger(lab).complete(auth,dict(status='INCONCLUSIVE',failure=failure,session_constructed=False))
else:
try:status=s.cleanup(status,failure)
except Exception as exc:
# Cleanup exceptions remain failures; autonomous watchdog still owns groups.
failure=(failure or '')+f'; cleanup {type(exc).__name__}: {exc}'
Ledger(lab).complete(auth,dict(status='INCONCLUSIVE',failure=failure,scientific_cleanup=False))
status='INCONCLUSIVE'
return dict(status=status,failure=failure,runner_exit=0 if status=='PARTIALLY_FEASIBLE' else 1)
if __name__=='__main__':
p=argparse.ArgumentParser(description='One independent new-ticket run, never retry/all')
p.add_argument('run_id',choices=RUNS)
for name in ('lab','manifest','review','publication'):p.add_argument('--'+name,required=True)
a=p.parse_args();result=execute(a.run_id,a.lab,a.manifest,a.review,a.publication);print(json.dumps(result));raise SystemExit(result['runner_exit'])

View file

@ -0,0 +1,28 @@
#!/usr/bin/env python3
"""Run exact offline validators; report is never an independent acceptance."""
import argparse,copy,importlib.util,json,sys,unittest
from pathlib import Path
from gate import canonical_lab,clean_environment,disable_dump,durable,source_files,digest,sha
def historical_replay(directory):
import cases
root=Path(directory);a=root/'resized-back-snapshot.json';b=root/'migrated-H2-snapshot.json'
before=json.loads(a.read_text());after=json.loads(b.read_text());previous=cases.dynamic_geometry(before)
current=cases.dynamic_geometry(after,previous,'migration',previous['focused'])
mutant=copy.deepcopy(after)
from ipc_predicates import workspace
workspace(mutant['tree'],'1')['mosaic']['rows'][0]['scroll_x']=previous['scroll']
try:cases.dynamic_geometry(mutant,previous,'migration',previous['focused'])
except ValueError:negative=True
else:raise AssertionError('stale historical C7 camera accepted')
return dict(status='PASS',scope='historical IPC replay only; no runtime upgrade',inputs={str(a):sha(a),str(b):sha(b)},positive=current,negative_stale_camera_rejected=negative)
def validate(lab,historical=None):
disable_dump();lab=canonical_lab(lab);clean_environment(lab)
path=Path(__file__).parent/'offline/test_followup.py';spec=importlib.util.spec_from_file_location('test_followup',path);module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module)
suite=unittest.defaultTestLoader.loadTestsFromModule(module);result=unittest.TextTestRunner(verbosity=2).run(suite)
replay=historical_replay(historical) if historical else dict(status='NOT_RUN',reason='historical replay input not supplied')
receipt=dict(status='PASS' if result.wasSuccessful() else 'FAIL',tests_run=result.testsRun,failures=len(result.failures)+len(result.errors),skipped=len(result.skipped),source_digest=digest(source_files()),runtime='NOT_RUN',real_reservations=0,independent_review='NOT_PROVEN',historical_C7=replay)
durable(lab/'followup-work/offline-tests.json',receipt);return result.wasSuccessful()
if __name__=='__main__':
p=argparse.ArgumentParser();p.add_argument('--lab',required=True);p.add_argument('--historical-c7');a=p.parse_args();raise SystemExit(0 if validate(a.lab,a.historical_c7) else 1)

View file

@ -0,0 +1,99 @@
"""Imported live/offline scientific predicates, not a parallel test model."""
import re
from geometry import need,exact,crossing,expected_outputs
from predicates import protocol
def client_exit(log,code,scenario='observed'):
# WHY: root OOM creates A, never B. CONTRACT: hierarchy-sensitive close;
# no normal error0 close is substituted for a wire-proven no_memory path.
# INVARIANT: every created role is destroyed exactly once, child first.
need(scenario in ('observed','root_failure','nested_failure','nested','toplevel'),'unknown closure scenario')
if 'TOPLEVEL CREATE ' in log:
created=re.findall(r'^(POPUP_[AB]|TOPLEVEL) CREATE ',log,re.M)
need(len(created)==len(set(created)) and created and created[0]=='TOPLEVEL','ambiguous created hierarchy')
expected={'root_failure':['TOPLEVEL','POPUP_A'],'nested_failure':['TOPLEVEL','POPUP_A','POPUP_B'],'nested':['TOPLEVEL','POPUP_A','POPUP_B'],'toplevel':['TOPLEVEL']}.get(scenario,created)
need(created==expected,'scenario created hierarchy differs')
order=re.findall(r'^(POPUP_[AB]|TOPLEVEL) DESTROY',log,re.M)
need(order==list(reversed(created)),'actual created hierarchy child-first destruction')
failed={'root_failure':'POPUP_A','nested_failure':'POPUP_B'}.get(scenario)
if failed:need(failed+' MAP ' not in log,'failed popup mapped despite NULL')
if scenario=='nested_failure':need('POPUP_A MAP ' in log,'nested failure without mapped root')
need('CLOSE_RECEIVED' in log and log.index('CLOSE_RECEIVED')<log.index('TOPLEVEL DESTROY'),'requested close before top destruction')
# Wire destroys bind to the objects actually created, even if popup_done
# legitimately retired a popup before the later explicit owner close.
for name in created:
match=re.search(rf'{name} CREATE wl=(\d+) xdg=(\d+) role=(\d+)',log);need(match is not None,'created role IDs absent')
wl,xdg,role=map(int,match.groups());stem='xdg_toplevel' if name=='TOPLEVEL' else 'xdg_popup'
need(all(log.count(f'{obj}#{ident}.destroy()')==1 for obj,ident in ((stem,role),('xdg_surface',xdg),('wl_surface',wl))),'actual role wire destruction absent')
need('CLOSE_RECEIVED' in log,'explicit close receipt absent')
releases=re.findall(r'SHM_CLOSURE created=(\d+) released=(\d+)',log)
need(len(releases)==1 and int(releases[0][0])>0 and releases[0][0]==releases[0][1],'client SHM release incomplete/unobserved')
errors=re.findall(r'EXIT[^\n]*error=(\d+)',log)
need(code==0 and len(errors)==1 and errors==['0'],'normal client exit/error differs')
need(not re.search(r'protocol error|wl_display[^\n]*\.error\(',log,re.I),'wire error cannot be ordinary close')
return dict(status='PASS',scenario=scenario,exit=code,wire_error=False)
def window(start,end,events):
need(start<end and len(events)==1 and start<=events[0]<=end,'ambiguous event window')
def owner(node,owner_id,workspace_id,path):
need(node['id']==owner_id and [n['id'] for n in path if n['type']=='workspace']==[workspace_id],'wrong owner ancestry')
def rgb(reference,actual,masked=False):
need(not masked,'H2 occlusion/mask forbidden');return exact(reference,actual)
def grab(log):return protocol(log)
def applied(geometry,paint_origin,pointer_origin,state):
need(state=='applied' and geometry==paint_origin==pointer_origin,'pending/uncorroborated origin')
def crosses(rect,axis,boundary):need(crossing(rect,axis,boundary),'crossing not exercised')
def shadow(before,after):need(before.tobytes()!=after.tobytes(),'shadow not exercised')
def saved_identity(identity,binary_sha):
need(identity['binary_sha256']==binary_sha,'wrong binary saved identity')
need(identity.get('native') is True,'saved identity not observable')
def camera_eligible(path,ws):
# Null flags are legitimate on IPC descriptors; branch membership remains
# authoritative even when a child omits its floating flag.
floating=any((n.get('floating') or 'auto_off').endswith('on') for n in path)
floating |= any(c in p.get('floating_nodes',[]) for p,c in zip(path,path[1:]))
return ws.get('layout')=='mosaic' and not floating and not any(n.get('type')=='con' and n.get('fullscreen_mode') for n in path)
def owner_context(node,path,pid,workspace_name,output,layout):
need(path and path[-1]==node and node.get('pid')==pid,'owner path/pid mismatch')
ws=[n for n in path if n.get('type')=='workspace'];outs=[n for n in path if n.get('type')=='output']
need(len(ws)==len(outs)==1,'owner ancestry must include workspace/output')
need(ws[0].get('name')==workspace_name and ws[0].get('output')==output and outs[0].get('name')==output and ws[0].get('layout')==layout,'wrong mapped workspace/output/layout')
need(not any(c in p.get('floating_nodes',[]) for p,c in zip(path,path[1:])),'owner floating ancestry')
need(not any((n.get('floating') or 'auto_off').endswith('on') for n in path),'owner floating flag')
owner(node,node['id'],ws[0]['id'],path)
return ws[0]
def selected_workspace(snapshot,name,output,layout):
from ipc_predicates import workspace
ws=workspace(snapshot['tree'],name)
need(ws.get('output')==output and ws.get('layout')==layout,'pre-map workspace/output/layout')
rows=[r for r in snapshot['workspaces'] if r.get('name')==name]
need(len(rows)==1 and rows[0].get('focused') and rows[0].get('output')==output,'pre-map workspace not selected')
return ws
def fullscreen_ancestor(path):
rows=[n for n in path[:-1] if n.get('type')=='con' and n.get('fullscreen_mode') and n.get('nodes')]
need(rows,'nonleaf current fullscreen ancestor NOT EXERCISED')
return rows[-1]
def hidden_survival(log,initial,injections):
need(not any(e.get('action')!='move' for e in injections),'hide/return contaminated by dismissal click')
fresh=grab(log)
need(fresh['ids']==initial['ids'] and fresh['grabs']==initial['grabs'],'same live grabbed chain required')
return fresh
def standard_spill(images,colors):
# Native clip identity is unavailable on CORE: demonstrate effective removal
# through both continued popup paints, then freshly corroborated pointer input.
need(all(any(px==colors[n] for px in images[1].get_flattened_data()) for n in ('POPUP_A','POPUP_B')),'standard continuation for both A/B NOT EXERCISED')
def evidence_identity(record,binary_sha):
need(record.get('binary_sha256')==binary_sha and record.get('measurement_kind')=='raw','wrong SHA or superficial status')
def terminal_transport(terminal,compositor_exit):
# WHY: swaymsg may see EOF after the compositor processed exit successfully.
# CONTRACT/INVARIANT: preserve raw codes/logs; no status-only terminal receipt.
return bool(terminal is not None and terminal.get('ipc_exit') in (0,1) and compositor_exit==0 and isinstance(terminal.get('stdout'),str) and isinstance(terminal.get('stderr'),str) and (terminal['ipc_exit']==0 or terminal['stdout'] or terminal['stderr']) and 'wait_error' not in terminal and 'ipc_error' not in terminal)

View file

@ -0,0 +1,221 @@
[22:46:38.282527] {Default Queue} -> wl_display#1.get_registry(new id wl_registry#2)
[22:46:38.282546] {Default Queue} -> wl_display#1.sync(new id wl_callback#3)
[22:46:38.282664] {Display Queue} wl_display#1.delete_id(3)
[22:46:38.282677] {Default Queue} wl_registry#2.global(1, "wl_fixes", 1)
[22:46:38.282685] {Default Queue} wl_registry#2.global(2, "wl_shm", 2)
[22:46:38.282692] {Default Queue} -> wl_registry#2.bind(2, "wl_shm", 1, new id [unknown]#4)
[22:46:38.282699] {Default Queue} wl_registry#2.global(3, "zwp_linux_dmabuf_v1", 5)
[22:46:38.282703] {Default Queue} wl_registry#2.global(4, "wp_linux_drm_syncobj_manager_v1", 1)
[22:46:38.282708] {Default Queue} wl_registry#2.global(5, "wl_compositor", 6)
[22:46:38.282712] {Default Queue} -> wl_registry#2.bind(5, "wl_compositor", 4, new id [unknown]#5)
[22:46:38.282716] {Default Queue} wl_registry#2.global(6, "wl_subcompositor", 1)
[22:46:38.282720] {Default Queue} wl_registry#2.global(7, "wl_data_device_manager", 3)
[22:46:38.282724] {Default Queue} wl_registry#2.global(8, "zwlr_gamma_control_manager_v1", 1)
[22:46:38.282728] {Default Queue} wl_registry#2.global(9, "zxdg_output_manager_v1", 3)
[22:46:38.282732] {Default Queue} wl_registry#2.global(10, "ext_idle_notifier_v1", 2)
[22:46:38.282736] {Default Queue} wl_registry#2.global(11, "zwp_idle_inhibit_manager_v1", 1)
[22:46:38.282740] {Default Queue} wl_registry#2.global(12, "zwlr_layer_shell_v1", 5)
[22:46:38.282744] {Default Queue} wl_registry#2.global(13, "xdg_wm_base", 5)
[22:46:38.282751] {Default Queue} -> wl_registry#2.bind(13, "xdg_wm_base", 3, new id [unknown]#6)
[22:46:38.282756] {Default Queue} wl_registry#2.global(14, "zwp_tablet_manager_v2", 1)
[22:46:38.282760] {Default Queue} wl_registry#2.global(15, "org_kde_kwin_server_decoration_manager", 1)
[22:46:38.282764] {Default Queue} wl_registry#2.global(16, "zxdg_decoration_manager_v1", 1)
[22:46:38.282768] {Default Queue} wl_registry#2.global(17, "zwp_relative_pointer_manager_v1", 1)
[22:46:38.282772] {Default Queue} wl_registry#2.global(18, "zwp_pointer_constraints_v1", 1)
[22:46:38.282776] {Default Queue} wl_registry#2.global(19, "wp_presentation", 2)
[22:46:38.282780] {Default Queue} wl_registry#2.global(20, "wp_alpha_modifier_v1", 1)
[22:46:38.282784] {Default Queue} wl_registry#2.global(21, "zwlr_output_manager_v1", 4)
[22:46:38.282788] {Default Queue} wl_registry#2.global(22, "zwlr_output_power_manager_v1", 1)
[22:46:38.282792] {Default Queue} wl_registry#2.global(23, "zwp_input_method_manager_v2", 1)
[22:46:38.282796] {Default Queue} wl_registry#2.global(24, "zwp_text_input_manager_v3", 1)
[22:46:38.282799] {Default Queue} wl_registry#2.global(25, "ext_foreign_toplevel_list_v1", 1)
[22:46:38.282803] {Default Queue} wl_registry#2.global(26, "zwlr_foreign_toplevel_manager_v1", 3)
[22:46:38.282807] {Default Queue} wl_registry#2.global(27, "ext_session_lock_manager_v1", 1)
[22:46:38.282812] {Default Queue} wl_registry#2.global(28, "ext_workspace_manager_v1", 1)
[22:46:38.282816] {Default Queue} wl_registry#2.global(29, "zwlr_export_dmabuf_manager_v1", 1)
[22:46:38.282820] {Default Queue} wl_registry#2.global(30, "zwlr_screencopy_manager_v1", 3)
[22:46:38.282824] {Default Queue} wl_registry#2.global(31, "ext_image_copy_capture_manager_v1", 1)
[22:46:38.282828] {Default Queue} wl_registry#2.global(32, "ext_output_image_capture_source_manager_v1", 1)
[22:46:38.282832] {Default Queue} wl_registry#2.global(33, "zwlr_data_control_manager_v1", 2)
[22:46:38.282836] {Default Queue} wl_registry#2.global(34, "ext_data_control_manager_v1", 1)
[22:46:38.282840] {Default Queue} wl_registry#2.global(35, "wp_security_context_manager_v1", 1)
[22:46:38.282844] {Default Queue} wl_registry#2.global(36, "wp_viewporter", 1)
[22:46:38.282848] {Default Queue} wl_registry#2.global(37, "wp_single_pixel_buffer_manager_v1", 1)
[22:46:38.282852] {Default Queue} wl_registry#2.global(38, "wp_content_type_manager_v1", 1)
[22:46:38.282856] {Default Queue} wl_registry#2.global(39, "wp_fractional_scale_manager_v1", 1)
[22:46:38.282860] {Default Queue} wl_registry#2.global(40, "ext_foreign_toplevel_image_capture_source_manager_v1", 1)
[22:46:38.282872] {Default Queue} wl_registry#2.global(41, "wp_tearing_control_manager_v1", 1)
[22:46:38.282886] {Default Queue} wl_registry#2.global(42, "zxdg_exporter_v1", 1)
[22:46:38.282890] {Default Queue} wl_registry#2.global(43, "zxdg_importer_v1", 1)
[22:46:38.282894] {Default Queue} wl_registry#2.global(44, "zxdg_exporter_v2", 1)
[22:46:38.282898] {Default Queue} wl_registry#2.global(45, "zxdg_importer_v2", 1)
[22:46:38.282903] {Default Queue} wl_registry#2.global(46, "xdg_activation_v1", 1)
[22:46:38.282907] {Default Queue} wl_registry#2.global(47, "xdg_toplevel_tag_manager_v1", 1)
[22:46:38.282911] {Default Queue} wl_registry#2.global(48, "wp_cursor_shape_manager_v1", 2)
[22:46:38.282915] {Default Queue} wl_registry#2.global(49, "wp_color_representation_manager_v1", 1)
[22:46:38.282919] {Default Queue} wl_registry#2.global(50, "zwp_virtual_keyboard_manager_v1", 1)
[22:46:38.282923] {Default Queue} wl_registry#2.global(51, "zwlr_virtual_pointer_manager_v1", 2)
[22:46:38.282927] {Default Queue} wl_registry#2.global(52, "zwp_keyboard_shortcuts_inhibit_manager_v1", 1)
[22:46:38.282931] {Default Queue} wl_registry#2.global(53, "zwp_pointer_gestures_v1", 3)
[22:46:38.282935] {Default Queue} wl_registry#2.global(54, "ext_transient_seat_manager_v1", 1)
[22:46:38.282939] {Default Queue} wl_registry#2.global(55, "wl_seat", 9)
[22:46:38.282944] {Default Queue} -> wl_registry#2.bind(55, "wl_seat", 5, new id [unknown]#7)
[22:46:38.282948] {Default Queue} wl_registry#2.global(57, "zwp_primary_selection_device_manager_v1", 1)
[22:46:38.282953] {Default Queue} wl_registry#2.global(58, "wl_output", 4)
[22:46:38.282957] {Default Queue} wl_registry#2.global(59, "wl_output", 4)
[22:46:38.282961] {Default Queue} wl_callback#3.done(16)
[22:46:38.282966] {Default Queue} -> wl_display#1.sync(new id wl_callback#3)
[22:46:38.283205] {Display Queue} wl_display#1.delete_id(3)
[22:46:38.283217] {Default Queue} discarded wl_shm#4.format(0)
[22:46:38.283222] {Default Queue} discarded wl_shm#4.format(1)
[22:46:38.283227] {Default Queue} discarded wl_shm#4.format(875709016)
[22:46:38.283231] {Default Queue} discarded wl_shm#4.format(875708993)
[22:46:38.283235] {Default Queue} discarded wl_shm#4.format(875710274)
[22:46:38.283239] {Default Queue} discarded wl_shm#4.format(842094674)
[22:46:38.283242] {Default Queue} discarded wl_shm#4.format(842088786)
[22:46:38.283246] {Default Queue} discarded wl_shm#4.format(892426322)
[22:46:38.283249] {Default Queue} discarded wl_shm#4.format(892420434)
[22:46:38.283253] {Default Queue} discarded wl_shm#4.format(909199186)
[22:46:38.283257] {Default Queue} discarded wl_shm#4.format(808665688)
[22:46:38.283260] {Default Queue} discarded wl_shm#4.format(808665665)
[22:46:38.283264] {Default Queue} discarded wl_shm#4.format(1213351746)
[22:46:38.283268] {Default Queue} discarded wl_shm#4.format(1211384408)
[22:46:38.283271] {Default Queue} discarded wl_shm#4.format(1211384385)
[22:46:38.283275] {Default Queue} discarded wl_shm#4.format(942950210)
[22:46:38.283279] {Default Queue} discarded wl_shm#4.format(942948952)
[22:46:38.283282] {Default Queue} discarded wl_shm#4.format(942948929)
[22:46:38.283286] {Default Queue} wl_seat#7.name("seat0")
[22:46:38.283291] {Default Queue} wl_seat#7.capabilities(1)
[22:46:38.283296] {Default Queue} -> wl_seat#7.get_pointer(new id wl_pointer#8)
[22:46:38.283300] {Default Queue} wl_callback#3.done(16)
[22:46:38.283305] {Default Queue} -> wl_compositor#5.create_surface(new id wl_surface#3)
[22:46:38.283310] {Default Queue} -> xdg_wm_base#6.get_xdg_surface(new id xdg_surface#9, wl_surface#3)
[22:46:38.283319] {Default Queue} -> xdg_surface#9.get_toplevel(new id xdg_toplevel#10)
[22:46:38.283324] {Default Queue} -> xdg_toplevel#10.set_title("Mosaic Nested Grab Probe")
[22:46:38.283328] {Default Queue} -> xdg_toplevel#10.set_app_id("org.mosaic.NestedGrabProbe")
TOPLEVEL CREATE wl=3 xdg=9 role=10 placement=v_both_cross
[22:46:38.283338] {Default Queue} -> wl_surface#3.commit()
[22:46:38.283679] {Default Queue} xdg_wm_base#6.ping(17)
[22:46:38.283698] {Default Queue} -> xdg_wm_base#6.pong(17)
[22:46:38.283708] {Default Queue} xdg_toplevel#10.configure(0, 0, array[0])
TOPLEVEL ROLE_CONFIGURE w=0 h=0
[22:46:38.283716] {Default Queue} xdg_surface#9.configure(18)
TOPLEVEL XDG_CONFIGURE serial=18 ack
[22:46:38.283724] {Default Queue} -> xdg_surface#9.ack_configure(18)
[22:46:38.284502] {Default Queue} -> wl_shm#4.create_pool(new id wl_shm_pool#11, fd 5, 966000)
[22:46:38.284513] {Default Queue} -> wl_shm_pool#11.create_buffer(new id wl_buffer#12, 0, 420, 575, 1680, 1)
[22:46:38.284520] {Default Queue} -> wl_surface#3.attach(wl_buffer#12, 0, 0)
[22:46:38.284525] {Default Queue} -> wl_surface#3.damage_buffer(0, 0, 420, 575)
[22:46:38.284530] {Default Queue} -> wl_surface#3.commit()
[22:46:38.284534] {Default Queue} -> wl_buffer#12.destroy()
[22:46:38.284538] {Default Queue} -> wl_shm_pool#11.destroy()
TOPLEVEL MAP wl=3 xdg=9
[22:46:38.285508] discarded [unknown]#12.[event 0](0 fd, 8 byte)
[22:46:38.285514] {Display Queue} wl_display#1.delete_id(12)
[22:46:38.285519] {Display Queue} wl_display#1.delete_id(11)
[22:46:38.285524] {Default Queue} xdg_toplevel#10.configure(636, 573, array[20])
TOPLEVEL ROLE_CONFIGURE w=636 h=573
[22:46:38.285532] {Default Queue} xdg_surface#9.configure(19)
TOPLEVEL XDG_CONFIGURE serial=19 ack
[22:46:38.285539] {Default Queue} -> xdg_surface#9.ack_configure(19)
[22:46:38.583097] {Default Queue} wl_pointer#8.enter(24, wl_surface#3, 238.00000000, 275.00000000)
POINTER_ENTER target=TOPLEVEL wl=3 serial=24 x=238.00 y=275.00
[22:46:38.583124] {Default Queue} wl_pointer#8.frame()
[22:46:38.583129] {Default Queue} wl_pointer#8.leave(25, wl_surface#3)
POINTER_LEAVE target=TOPLEVEL serial=25
[22:46:38.583137] {Default Queue} wl_pointer#8.frame()
[22:46:39.867771] {Default Queue} wl_pointer#8.enter(30, wl_surface#3, 3.00000000, 5.00000000)
POINTER_ENTER target=TOPLEVEL wl=3 serial=30 x=3.00 y=5.00
[22:46:39.867787] {Default Queue} wl_pointer#8.frame()
[22:46:39.867792] {Default Queue} wl_pointer#8.frame()
[22:46:39.968604] {Default Queue} wl_pointer#8.leave(31, wl_surface#3)
POINTER_LEAVE target=TOPLEVEL serial=31
[22:46:39.968649] {Default Queue} wl_pointer#8.frame()
[22:46:40.117908] {Default Queue} wl_pointer#8.enter(32, wl_surface#3, 3.00000000, 5.00000000)
POINTER_ENTER target=TOPLEVEL wl=3 serial=32 x=3.00 y=5.00
[22:46:40.117939] {Default Queue} wl_pointer#8.frame()
[22:46:40.117943] {Default Queue} wl_pointer#8.button(33, 1002, 272, 1)
POINTER_BUTTON target=TOPLEVEL serial=33 button=272 state=1
[22:46:40.117951] {Default Queue} -> wl_compositor#5.create_surface(new id wl_surface#11)
[22:46:40.117956] {Default Queue} -> xdg_wm_base#6.get_xdg_surface(new id xdg_surface#12, wl_surface#11)
[22:46:40.117960] {Default Queue} -> xdg_wm_base#6.create_positioner(new id xdg_positioner#13)
[22:46:40.117964] {Default Queue} -> xdg_positioner#13.set_size(250, 170)
[22:46:40.117967] {Default Queue} -> xdg_positioner#13.set_anchor_rect(50, 480, 1, 1)
[22:46:40.117970] {Default Queue} -> xdg_positioner#13.set_anchor(5)
[22:46:40.117973] {Default Queue} -> xdg_positioner#13.set_gravity(8)
[22:46:40.117976] {Default Queue} -> xdg_surface#12.get_popup(new id xdg_popup#14, xdg_surface#9, xdg_positioner#13)
POPUP_A CREATE wl=11 xdg=12 role=14 parent=TOPLEVEL parent_xdg=9
POPUP_A GRAB_REQUEST origin=wl_pointer.button target=TOPLEVEL serial=33 used=33
[22:46:40.117984] {Default Queue} -> xdg_popup#14.grab(wl_seat#7, 33)
[22:46:40.117988] {Default Queue} -> wl_surface#11.commit()
[22:46:40.117991] {Default Queue} -> xdg_positioner#13.destroy()
[22:46:40.117994] {Default Queue} wl_pointer#8.frame()
[22:46:40.118126] {Display Queue} wl_display#1.delete_id(13)
[22:46:40.118129] {Default Queue} xdg_popup#14.configure(50, 480, 250, 170)
POPUP_A ROLE_CONFIGURE x=50 y=480 w=250 h=170
[22:46:40.118135] {Default Queue} xdg_surface#12.configure(34)
POPUP_A XDG_CONFIGURE serial=34 ack
[22:46:40.118140] {Default Queue} -> xdg_surface#12.ack_configure(34)
[22:46:40.118286] {Default Queue} -> wl_shm#4.create_pool(new id wl_shm_pool#13, fd 5, 170000)
[22:46:40.118298] {Default Queue} -> wl_shm_pool#13.create_buffer(new id wl_buffer#15, 0, 250, 170, 1000, 1)
[22:46:40.118313] {Default Queue} -> wl_surface#11.attach(wl_buffer#15, 0, 0)
[22:46:40.118317] {Default Queue} -> wl_surface#11.damage_buffer(0, 0, 250, 170)
[22:46:40.118320] {Default Queue} -> wl_surface#11.commit()
[22:46:40.118322] {Default Queue} -> wl_buffer#15.destroy()
[22:46:40.118325] {Default Queue} -> wl_shm_pool#13.destroy()
POPUP_A MAP wl=11 xdg=12
[22:46:40.118959] discarded [unknown]#15.[event 0](0 fd, 8 byte)
[22:46:40.118962] {Display Queue} wl_display#1.delete_id(15)
[22:46:40.118965] {Display Queue} wl_display#1.delete_id(13)
[22:46:40.197365] {Default Queue} wl_pointer#8.button(35, 1003, 272, 0)
POINTER_BUTTON target=TOPLEVEL serial=35 button=272 state=0
[22:46:40.197412] {Default Queue} wl_pointer#8.frame()
[22:46:40.298184] {Default Queue} wl_pointer#8.leave(36, wl_surface#3)
POINTER_LEAVE target=TOPLEVEL serial=36
[22:46:40.298225] {Default Queue} wl_pointer#8.frame()
[22:46:40.585174] {Default Queue} wl_pointer#8.enter(37, wl_surface#3, 3.00000000, 5.00000000)
POINTER_ENTER target=TOPLEVEL wl=3 serial=37 x=3.00 y=5.00
[22:46:40.585201] {Default Queue} wl_pointer#8.frame()
[22:46:40.585208] {Default Queue} wl_pointer#8.leave(38, wl_surface#3)
POINTER_LEAVE target=TOPLEVEL serial=38
[22:46:40.585221] {Default Queue} wl_pointer#8.frame()
[22:46:40.585227] {Default Queue} wl_pointer#8.enter(39, wl_surface#11, 4.00000000, 5.00000000)
POINTER_ENTER target=POPUP_A wl=11 serial=39 x=4.00 y=5.00
[22:46:40.585240] {Default Queue} wl_pointer#8.frame()
[22:46:40.585246] {Default Queue} wl_pointer#8.frame()
[22:46:40.685838] {Default Queue} wl_pointer#8.leave(40, wl_surface#11)
POINTER_LEAVE target=POPUP_A serial=40
[22:46:40.685909] {Default Queue} wl_pointer#8.frame()
[22:46:40.835646] {Default Queue} wl_pointer#8.enter(41, wl_surface#11, 4.00000000, 5.00000000)
POINTER_ENTER target=POPUP_A wl=11 serial=41 x=4.00 y=5.00
[22:46:40.835675] {Default Queue} wl_pointer#8.frame()
[22:46:40.835682] {Default Queue} wl_pointer#8.button(42, 1005, 272, 1)
POINTER_BUTTON target=POPUP_A serial=42 button=272 state=1
[22:46:40.835696] {Default Queue} -> wl_compositor#5.create_surface(new id wl_surface#13)
[22:46:40.835704] {Default Queue} -> xdg_wm_base#6.get_xdg_surface(new id xdg_surface#15, wl_surface#13)
[22:46:40.835712] {Default Queue} -> xdg_wm_base#6.create_positioner(new id xdg_positioner#16)
[22:46:40.835719] {Default Queue} -> xdg_positioner#16.set_size(170, 130)
[22:46:40.835726] {Default Queue} -> xdg_positioner#16.set_anchor_rect(20, 80, 1, 1)
[22:46:40.835733] {Default Queue} -> xdg_positioner#16.set_anchor(5)
[22:46:40.835738] {Default Queue} -> xdg_positioner#16.set_gravity(8)
[22:46:40.835745] {Default Queue} -> xdg_surface#15.get_popup(new id xdg_popup#17, xdg_surface#12, xdg_positioner#16)
POPUP_B CREATE wl=13 xdg=15 role=17 parent=POPUP_A parent_xdg=12
POPUP_B GRAB_REQUEST origin=wl_pointer.button target=POPUP_A serial=42 used=42
[22:46:40.835761] {Default Queue} -> xdg_popup#17.grab(wl_seat#7, 42)
[22:46:40.835768] {Default Queue} -> wl_surface#13.commit()
[22:46:40.835775] {Default Queue} -> xdg_positioner#16.destroy()
[22:46:40.835781] {Default Queue} wl_pointer#8.frame()
[22:46:40.835930] {Display Queue} wl_display#1.delete_id(16)
[22:46:40.835937] {Default Queue} xdg_popup#17.configure(20, 80, 170, 130)
POPUP_B ROLE_CONFIGURE x=20 y=80 w=170 h=130
[22:46:40.835949] {Default Queue} xdg_surface#15.configure(43)
POPUP_B XDG_CONFIGURE serial=43 ack
[22:46:40.835960] {Default Queue} -> xdg_surface#15.ack_configure(43)
[22:46:40.836118] {Default Queue} -> wl_shm#4.create_pool(new id wl_shm_pool#16, fd 5, 88400)
[22:46:40.836128] {Default Queue} -> wl_shm_pool#16.create_buffer(new id wl_buffer#18, 0, 170, 130, 680, 1)
[22:46:40.836135] {Default Queue} -> wl_surface#13.attach(wl_buffer#18, 0, 0)
[22:46:40.836142] {Default Queue} -> wl_surface#13.damage_buffer(0, 0, 170, 130)
[22:46:40.836149] {Default Queue} -> wl_surface#13.commit()
[22:46:40.836155] {Default Queue} -> wl_buffer#18.destroy()
[22:46:40.836168] {Default Queue} -> wl_shm_pool#16.destroy()
POPUP_B MAP wl=13 xdg=15