Implement generic Xorg active-window metadata #66

Merged
lost-rob0t merged 2 commits from agent/issue-14-generic-xorg-metadata into main 2026-08-12 01:22:10 +00:00
lost-rob0t commented 2026-08-12 01:18:42 +00:00 (Migrated from github.com)

Summary

  • implement GenericXorgMetadataSource behind the existing MetadataSource port with source ID xorg-generic
  • add a typed, injected Xorg reader/runner boundary for EWMH active-window, WM_CLASS, optional title, workspace, and optional geometry collection
  • connect the generic-Xorg strategy probe to a content-free operational capability check
  • add conservative, independently validated window-title configuration and document the source contract

Adapter choice and bounded execution

The adapter uses fixed xprop and optional xwininfo executable identities rather than adding a Python X11 dependency whose Python 3.14 packaging would widen the dependency surface. Executables are resolved once and invoked only with argument vectors through create_subprocess_exec; there is no shell or captured-value interpolation.

Each invocation has a 0.5-second timeout and a 64-KiB bound on each output stream. Limit or timeout violations kill the child process. Window identifiers must be nonzero 32-bit integers and are reformatted by the adapter before use. xprop is required; xwininfo adds geometry when available without blocking application/title/workspace collection on minimal installations.

Collection and race policy

The source reads _NET_ACTIVE_WINDOW, collects only reviewed properties for that validated window, and reads _NET_ACTIVE_WINDOW again. A focus change discards the entire attempt. One fixed retry is allowed by default; repeated churn returns focus-changed. Destroyed windows return window-unavailable after the same bounded retry policy. Missing active windows, malformed properties, wrong-window responses, oversized output, timeouts, and missing executables use fixed sanitized reason codes.

Output fields are lexically ordered: application, window.height, window.id, optional window.title, window.width, window.x, window.y, and workspace. Every field carries xorg-generic provenance, adapter revision ewmh-xprop-v1, one timezone-aware observation timestamp, and documented confidence. Application values normalize from the second WM_CLASS component with first-component fallback.

Privacy and security

metadata.window_titles_enabled defaults to false. When disabled or unrequested, title properties are omitted from the reviewed xprop query and window.title cannot be emitted. Status/probe paths only check executable availability and never read active-window properties.

Command output, exception text, executable paths, display values, and captured metadata are absent from public failures and status. Raw metadata remains in-memory and source adapters have no persistence capability. The integration test carries synthetic source-collected application/title metadata through the existing deterministic redaction stage and verifies sensitive fields are dropped before the encryption processor or persistence sink.

Validation

  • focused: ./scripts/run-pytest tests/unit/metadata/test_xorg.py tests/unit/config/test_models.py tests/unit/config/test_loader.py tests/unit/session tests/unit/test_package.py tests/unit/redaction tests/integration/pipeline/test_redaction_pipeline.py — 111 passed
  • canonical: ./scripts/check — 297 passed; 8 failure modes verified; formatting, Ruff, Pyright, and repository policy checks passed
  • sanitized real-session smoke: success with source xorg-generic, titles disabled; emitted field names/count only

Closes #14

## Summary - implement `GenericXorgMetadataSource` behind the existing `MetadataSource` port with source ID `xorg-generic` - add a typed, injected Xorg reader/runner boundary for EWMH active-window, `WM_CLASS`, optional title, workspace, and optional geometry collection - connect the generic-Xorg strategy probe to a content-free operational capability check - add conservative, independently validated window-title configuration and document the source contract ## Adapter choice and bounded execution The adapter uses fixed `xprop` and optional `xwininfo` executable identities rather than adding a Python X11 dependency whose Python 3.14 packaging would widen the dependency surface. Executables are resolved once and invoked only with argument vectors through `create_subprocess_exec`; there is no shell or captured-value interpolation. Each invocation has a 0.5-second timeout and a 64-KiB bound on each output stream. Limit or timeout violations kill the child process. Window identifiers must be nonzero 32-bit integers and are reformatted by the adapter before use. `xprop` is required; `xwininfo` adds geometry when available without blocking application/title/workspace collection on minimal installations. ## Collection and race policy The source reads `_NET_ACTIVE_WINDOW`, collects only reviewed properties for that validated window, and reads `_NET_ACTIVE_WINDOW` again. A focus change discards the entire attempt. One fixed retry is allowed by default; repeated churn returns `focus-changed`. Destroyed windows return `window-unavailable` after the same bounded retry policy. Missing active windows, malformed properties, wrong-window responses, oversized output, timeouts, and missing executables use fixed sanitized reason codes. Output fields are lexically ordered: `application`, `window.height`, `window.id`, optional `window.title`, `window.width`, `window.x`, `window.y`, and `workspace`. Every field carries `xorg-generic` provenance, adapter revision `ewmh-xprop-v1`, one timezone-aware observation timestamp, and documented confidence. Application values normalize from the second `WM_CLASS` component with first-component fallback. ## Privacy and security `metadata.window_titles_enabled` defaults to `false`. When disabled or unrequested, title properties are omitted from the reviewed `xprop` query and `window.title` cannot be emitted. Status/probe paths only check executable availability and never read active-window properties. Command output, exception text, executable paths, display values, and captured metadata are absent from public failures and status. Raw metadata remains in-memory and source adapters have no persistence capability. The integration test carries synthetic source-collected application/title metadata through the existing deterministic redaction stage and verifies sensitive fields are dropped before the encryption processor or persistence sink. ## Validation - focused: `./scripts/run-pytest tests/unit/metadata/test_xorg.py tests/unit/config/test_models.py tests/unit/config/test_loader.py tests/unit/session tests/unit/test_package.py tests/unit/redaction tests/integration/pipeline/test_redaction_pipeline.py` — 111 passed - canonical: `./scripts/check` — 297 passed; 8 failure modes verified; formatting, Ruff, Pyright, and repository policy checks passed - sanitized real-session smoke: success with source `xorg-generic`, titles disabled; emitted field names/count only Closes #14
Sign in to join this conversation.
No description provided.