RAGE: implement Xorg screenshot capture backend #71

Merged
lost-rob0t merged 51 commits from agent/issue-19-xorg-capture-rage into main 2026-08-22 08:54:04 +00:00
lost-rob0t commented 2026-08-22 06:36:46 +00:00 (Migrated from github.com)

Implements #19 through the repository's issue-driven RAGE process.

Immutable scope

  • starting main: e5c6ac6cb4fc70b594fb51d5dcef40baca338560
  • consumed issue: #19 only
  • dependencies #8, #17, #18 were already satisfied and remain authoritative
  • complete RAGE evidence: rage/issue-19-xorg-capture.org

Architecture

Adversarial research superseded the initial in-process Xlib acquisition choice. The final capture stack is:

XorgCaptureBackend -> XwdSnapshotReader -> FixedXwdNativeRunner -> BoundedNativeCommandExecutor

The fixed native commands are only xwd -root -silent and xrandr --listmonitors, using supplied absolute executable paths, no shell, bounded stdout/stderr, a minimal X11 environment, request-deadline enforcement, and child termination on timeout, cancellation, or live output overflow.

xwd output remains in memory. The XWD parser strictly validates supported TrueColor layouts and normalizes to RGB8. Monitor topology is read before and after capture; changes fail closed. Focused-window mode uses one authorized root capture followed by a trusted metadata-only in-memory crop rather than a second arbitrary drawable read.

Privacy/security boundaries

  • pixel acquisition requires a typed ApprovedCaptureRequest;
  • lifecycle, capture policy, lock/idle safety and privacy mode remain the only authorization authorities;
  • no screenshot filename, tempfile, image-encoder, or plaintext persistence path exists in capture modules;
  • captured/native private values are excluded from errors/status;
  • monitor/root/crop geometry, scaling, timestamp, backend revision and capture generation are retained as typed provenance;
  • late captured work cannot be relabelled after generation invalidation.

During end-to-end acceptance testing, the last point exposed a real existing raw-ingress race: BoundedCapturePipeline.submit_raw() previously used whatever generation was current when a caller submitted bytes. A frame captured under generation N could therefore be submitted after lock/privacy invalidation and resume to N+1 and be relabelled N+1. This PR closes that boundary by requiring expected_generation on every raw submission and comparing it to the current capture permit before constructing a pipeline item. Mismatch raises StaleCaptureGeneration and scrubs the mutable raw buffers.

TDD/RAGE evidence

Accepted RED/GREEN slices are recorded in the Org ledger. Important later checkpoints:

  • GREEN 5 executor: 60f99eb757bbee36c2ed05f140002e038e7a9518, Actions 32562735169;
  • RED 6 production composition: de797180953aab4a152072639593c21c795f5431, Actions 32562854491, strict typing failed only because the composition factory was absent;
  • GREEN 6 production composition: 5f9b014a4d25ae07468ff85a7d879932b66ceb74, Actions 32562906564;
  • RED 7 producer-generation binding: 931a83b8389c2b8a9554df3b1adba91731dd9fb6, Actions 32563123294, formatting/lint/shell passed and strict typing failed specifically because submit_raw(expected_generation=...) did not exist;
  • GREEN 7: 95ea7aeb11a74d5cfa303123957d563c54805ffc, Actions 32563301554, complete success including canonical ./scripts/check with 561 collected tests.

Invalid RED/GREEN candidates that failed formatting or contained fixture-only mistakes are explicitly rejected in the ledger rather than counted as evidence.

Acceptance coverage

  • full root capture, multi-monitor/negative-origin/scaling fixtures;
  • strict XWD format, dimensions, masks, stride and payload bounds;
  • topology-change/display failure behavior;
  • deadline, cancellation and helper-output limits;
  • exact fixed command vectors and environment filtering;
  • approved capture -> bounded in-memory processing/redaction/encryption pipeline;
  • structural regression forbidding plaintext screenshot artifact capabilities in capture modules;
  • capture N -> lock invalidation -> resume N+1 -> late N submission is rejected, raw bytes scrubbed, no sink write;
  • Xorg backend/privacy/runtime-dependency documentation.

Nix packaging/runtime dependency installation is intentionally not added here: issue #40 owns reproducible NixOS packaging. #19 changes no flake/package definition, so inventing package work here would violate issue scope.

Current final gate

The latest documentation/ledger reconciliation head is fa19b6f56e9353c9d43b039350f6feda8390d274. Its exact-head CI is the final merge gate. This PR remains draft until that head is fully green and the live issue/review/mergeability state is re-read.

Implements #19 through the repository's issue-driven RAGE process. ## Immutable scope - starting `main`: `e5c6ac6cb4fc70b594fb51d5dcef40baca338560` - consumed issue: #19 only - dependencies #8, #17, #18 were already satisfied and remain authoritative - complete RAGE evidence: `rage/issue-19-xorg-capture.org` ## Architecture Adversarial research superseded the initial in-process Xlib acquisition choice. The final capture stack is: `XorgCaptureBackend -> XwdSnapshotReader -> FixedXwdNativeRunner -> BoundedNativeCommandExecutor` The fixed native commands are only `xwd -root -silent` and `xrandr --listmonitors`, using supplied absolute executable paths, no shell, bounded stdout/stderr, a minimal X11 environment, request-deadline enforcement, and child termination on timeout, cancellation, or live output overflow. `xwd` output remains in memory. The XWD parser strictly validates supported TrueColor layouts and normalizes to RGB8. Monitor topology is read before and after capture; changes fail closed. Focused-window mode uses one authorized root capture followed by a trusted metadata-only in-memory crop rather than a second arbitrary drawable read. ## Privacy/security boundaries - pixel acquisition requires a typed `ApprovedCaptureRequest`; - lifecycle, capture policy, lock/idle safety and privacy mode remain the only authorization authorities; - no screenshot filename, tempfile, image-encoder, or plaintext persistence path exists in capture modules; - captured/native private values are excluded from errors/status; - monitor/root/crop geometry, scaling, timestamp, backend revision and capture generation are retained as typed provenance; - late captured work cannot be relabelled after generation invalidation. During end-to-end acceptance testing, the last point exposed a real existing raw-ingress race: `BoundedCapturePipeline.submit_raw()` previously used whatever generation was current when a caller submitted bytes. A frame captured under generation N could therefore be submitted after lock/privacy invalidation and resume to N+1 and be relabelled N+1. This PR closes that boundary by requiring `expected_generation` on every raw submission and comparing it to the current capture permit before constructing a pipeline item. Mismatch raises `StaleCaptureGeneration` and scrubs the mutable raw buffers. ## TDD/RAGE evidence Accepted RED/GREEN slices are recorded in the Org ledger. Important later checkpoints: - GREEN 5 executor: `60f99eb757bbee36c2ed05f140002e038e7a9518`, Actions `32562735169`; - RED 6 production composition: `de797180953aab4a152072639593c21c795f5431`, Actions `32562854491`, strict typing failed only because the composition factory was absent; - GREEN 6 production composition: `5f9b014a4d25ae07468ff85a7d879932b66ceb74`, Actions `32562906564`; - RED 7 producer-generation binding: `931a83b8389c2b8a9554df3b1adba91731dd9fb6`, Actions `32563123294`, formatting/lint/shell passed and strict typing failed specifically because `submit_raw(expected_generation=...)` did not exist; - GREEN 7: `95ea7aeb11a74d5cfa303123957d563c54805ffc`, Actions `32563301554`, complete success including canonical `./scripts/check` with 561 collected tests. Invalid RED/GREEN candidates that failed formatting or contained fixture-only mistakes are explicitly rejected in the ledger rather than counted as evidence. ## Acceptance coverage - full root capture, multi-monitor/negative-origin/scaling fixtures; - strict XWD format, dimensions, masks, stride and payload bounds; - topology-change/display failure behavior; - deadline, cancellation and helper-output limits; - exact fixed command vectors and environment filtering; - approved capture -> bounded in-memory processing/redaction/encryption pipeline; - structural regression forbidding plaintext screenshot artifact capabilities in capture modules; - capture N -> lock invalidation -> resume N+1 -> late N submission is rejected, raw bytes scrubbed, no sink write; - Xorg backend/privacy/runtime-dependency documentation. Nix packaging/runtime dependency installation is intentionally not added here: issue #40 owns reproducible NixOS packaging. #19 changes no flake/package definition, so inventing package work here would violate issue scope. ## Current final gate The latest documentation/ledger reconciliation head is `fa19b6f56e9353c9d43b039350f6feda8390d274`. Its exact-head CI is the final merge gate. This PR remains draft until that head is fully green and the live issue/review/mergeability state is re-read.
Sign in to join this conversation.
No description provided.