# Phase-0 AT-SPI spike

De-risks the Linux semantic-control agent ([docs/LINUX_VM_AX_AGENT.md](../../../docs/LINUX_VM_AX_AGENT.md))
before any agent code is written. It proves, on Ubuntu/Debian arm64, that **AT-SPI2** can back the
Linux `ax_*` ops:

1. **`ax_dump`** — read the accessibility tree and emit it as the *same* `node` JSON the macOS agent
   produces (`ref`/`role`/`title`/`value`/`enabled`/`focused`/`frame`/`actions`/`children`), so the
   host side needs no protocol change.
2. **Act by identity** — `ax_press` (`AtspiAction.do_action`), `ax_set_value`
   (`AtspiEditableText.set_text_contents`), `ax_focus` (`AtspiComponent.grab_focus`).
3. **SCREEN coordinates are real** — the load-bearing assumption behind running the automation base on
   **X11 rather than Wayland**.

## Files

- `gtk_probe_app.py` — a tiny deterministic GTK3 target (entry + button + label), named "Nucleic AX
  Probe" so it can be looked up by identity.
- `ax_dump_spike.py` — connects via `pyatspi`, dumps the tree as `node` JSON, and (with `--act`)
  exercises the three primitives, asserting the entry text and the button-driven label change.
- `run_spike.sh` — brings up a headless AT-SPI env (Xvfb + session bus + `at-spi-bus-launcher`),
  launches the probe, and runs the dump. CI-friendly; exits non-zero on failure.

## Run it

**On a normally-installed system** (the Nucleic Linux build container with the packages installed, or a
provisioned guest):

```sh
apt-get install -y --no-install-recommends \
    xvfb at-spi2-core libatk-adaptor python3-gi python3-pyatspi gir1.2-gtk-3.0 dbus-x11 xterm
guest/nucleic-linux-agent/spike/run_spike.sh
```

Expected tail:

```
[spike] dumped 6 nodes
[spike] SCREEN coordinates look real (non-zero origins present) — X11 assumption holds
[spike] set_text -> 'hello from at-spi'
[spike] pressed button (action 'click') -> status text 'clicked' (was 'unclicked')
[spike] ACT RESULT: PASS
=== spike exit: 0 ===
```

On a **real provisioned guest** the X + AT-SPI session is already up, so you can skip `run_spike.sh` and
point `ax_dump_spike.py --app <name> --act` straight at a live application.

## Result (2026-07 spike run)

Ran green on Debian 12 (bookworm) arm64 — same toolkit stack as the Ubuntu 24.04 guest. All three
primitives passed and SCREEN extents resolved to real geometry (child widgets at their laid-out
coordinates), confirming the AT-SPI approach and the X11 decision.

### Note: running without root (extraction sandbox)

The spike ran in a container where packages couldn't be `dpkg`-installed (no root). They were extracted
with `dpkg-deb -x` into a prefix and pointed at via `LD_LIBRARY_PATH` / `GI_TYPELIB_PATH` /
`PYTHONPATH` / `XDG_DATA_DIRS`. Several components hardcode absolute helper paths that a prefix doesn't
satisfy, so small shim wrappers were needed at the hardcoded locations:

- `/usr/bin/xkbcomp` — Xvfb execs this by absolute path to compile the keymap.
- `/usr/bin/dbus-daemon` — `at-spi-bus-launcher` execs this by absolute path to spawn the a11y bus.
- `/usr/libexec/at-spi-bus-launcher`, `/usr/libexec/at-spi2-registryd` — D-Bus activation targets.
- Symlinks: `/usr/share/defaults/at-spi2` and `/usr/share/dbus-1/accessibility-services` (a11y bus
  config + registryd service dir).

**None of this is needed on a real system** where the packages are installed normally — the hardcoded
paths exist. It's documented only so the container run is reproducible. The gsettings schemas must be
compiled once (`glib-compile-schemas <prefix>/usr/share/glib-2.0/schemas`) or the launcher aborts with
"Cannot get the default GSettingsSchemaSource".
</content>
