Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .jagent/planning/TASKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
| [APIDRIFT-12](./tickets/APIDRIFT-12-evorium-borrows.md) | Evorium borrows: guard done in core; relatedness+similarity remain | relatedness → 11 |
| [APIDRIFT-13](./tickets/APIDRIFT-13-checkstand-breadboard.md) | checkstand borrows + breadboard | schemars sig done in 8a; breadboard later |
| [APIDRIFT-3](./tickets/APIDRIFT-3-applier.md) | call-site applier / codemod | after 5, 9 |
| [APIDRIFT-15](./tickets/APIDRIFT-15-xray-nexus-inspect-json.md) | x-ray → nexus `inspect --json` consumer pair (M2 unblock, not in flux) | first; see docs/CONSUMERS.md |
| [APIDRIFT-16](./tickets/APIDRIFT-16-exosphere-capability-shell-surfaces.md) | exosphere CapabilityType/ShellRequest/CapsuleManifest surfaces; nexus `Request` mirror | after EXO-214/215 land |
| [APIDRIFT-17](./tickets/APIDRIFT-17-nakshatra-boot-toml-nak-env.md) | nakshatra boot.toml keys + NAK_* env (C producer); nexus recipe drift incident | motivating story |
| [APIDRIFT-18](./tickets/APIDRIFT-18-nexus-sdk-wire-gui-consumer.md) | nexus WireKey/IPC/keys/guests.d; nexus-gui consumer (bro-tui↔bro-desktop shape) | after NEXUS-056..059 merge |
| [APIDRIFT-19](./tickets/APIDRIFT-19-hawk-policy-rule-event-surfaces.md) | hawk PolicyRule + SecurityEvent; YAML usage scan | cheap non-Rust consumer |
| [APIDRIFT-20](./tickets/APIDRIFT-20-wsforge-profile-shell-surface.md) | wsforge profile keys vs script reads; shell usage scan | M3 |

## Done

Expand Down
40 changes: 40 additions & 0 deletions .jagent/planning/tickets/APIDRIFT-15-xray-nexus-inspect-json.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# APIDRIFT-15 — x-ray → nexus: `xray inspect --json` as the first cross-repo consumer pair

| Field | Value |
|-------|--------|
| **Status** | backlog |
| **Milestone** | M2 |
| **Size** | M |
| **Owner** | unassigned |
| **Created** | 2026-09-16 |
| **Updated** | 2026-09-16 |

## Problem

`xray inspect --json` carries `meta.schema_version` (x-ray `crates/x-ray-tui/src/inspect.rs:78,240`,
XRAY-36) and nexus gates on it by hand (`crates/nexus-ui/src/xray_inspect.rs`:
`SUPPORTED_SCHEMA_MAJOR`, `UnsupportedSchema`). Two producer changes are queued (XRAY-37 health
snapshot in the JSON, XRAY-38 CI smoke for the guest CLI probes) and nothing classifies them
for the consumer. This is APIDRIFT-9's shape on a pair that is *not* in flux — the M2 unblock.

## Goal

x-ray commits an `api-drift/` ledger for an `inspect-json` `ProtocolSurface`; nexus pins it in
`api-drift.toml` and runs `check!` in `cargo test`; the hand-rolled major gate stays as the
runtime guard but its *value* is derived from the pinned snapshot, not typed twice.

## Scope

- in: producer sig for a JSON report (decide: derive `schemars::JsonSchema` on x-ray's report
structs and use `schema_hash`, or hash a canonical fixture — record the choice in
`docs/CONSUMERS.md`); `ledger!` test in x-ray; `check!` in nexus with a Rust usage scan of
`xray_inspect.rs` field accesses; one recorded ledger note when XRAY-37 lands.
- out: the TUI health drill itself; polydex.

## Acceptance

- [ ] `cargo test` in x-ray fails with the classified break + recording command when a
top-level `inspect --json` field is added/renamed without a note.
- [ ] `cargo test` in nexus fails when the pinned x-ray snapshot hash moves past a
Breaking entry that touches a field `xray_inspect.rs` reads; passes on Compatible ones.
- [ ] XRAY-37's field addition is recorded as the first real ledger entry (Compatible).
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# APIDRIFT-16 — exosphere: `CapabilityType` + ExoShell protocol + `CapsuleManifest` surfaces; nexus `Request` mirror as consumer

| Field | Value |
|-------|--------|
| **Status** | backlog |
| **Milestone** | M2 |
| **Size** | L |
| **Owner** | unassigned |
| **Created** | 2026-09-16 |
| **Updated** | 2026-09-16 |

## Problem

Three exosphere contracts have downstream hand copies and pending changes:
- `CapabilityType` (`crates/core/base/common/src/capability.rs:6`) — EXO-214 adds a `Tty`
family; every exhaustive `match` downstream (spawner, policy, nexus manifest tooling) breaks.
- `ShellRequest`/`ShellResponse` (`crates/exo/shell-client/src/protocol.rs:135,192`) — nexus
keeps a serde-tagged **hand copy** as `nexus_session::exosphere::Request`
(`crates/nexus-session/src/exosphere.rs:73`); EXO-215 adds fields/ops.
- `CapsuleManifest` is **defined twice** in exosphere (`daemon/src/enforcement.rs:575`,
`integration/src/api.rs:56`) and consumed by nexus `packaging/exosphere/Capsule.toml` and
nakshatra's capsule staging (NAK-14b).

## Goal

`enum_surface!` over `CapabilityType`, `protocol_surface!` over the shell wire (method names +
`schema_hash` of request/response), `catalog_surface!` over manifest keys — one ledger in
exosphere; nexus pins `shell-wire` and the ledger test fails when `Request` and `ShellRequest`
disagree. The duplicate manifest structs are held to one surface so they cannot diverge.

## Scope

- in: the three surfaces + ledger in exosphere; `check!` in nexus for `shell-wire`; EXO-214
and EXO-215 recorded as the first entries (Breaking for the enum variant, with the
`add-match-arm` suggestion; Compatible for optional protocol fields).
- out: unifying the two `CapsuleManifest` definitions (file separately if the surface proves
they already differ); exosphere's ~15 absolute path-deps problem (`.jagent/TODO.md`).

## Acceptance

- [ ] Adding a `CapabilityType` variant without a ledger note fails exosphere's tests with
`Added Variant … Breaking` and the recording command.
- [ ] nexus `cargo test` fails when its `Request` mirror lacks a method present in the pinned
`shell-wire` snapshot (and names it).
- [ ] The two `CapsuleManifest` key sets are proven equal by the catalog surface, or the
difference is recorded as a finding.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# APIDRIFT-17 — nakshatra: `boot.toml` keys + `NAK_*` env as catalog surfaces (C producer); nexus recipe + exo-init as consumers

| Field | Value |
|-------|--------|
| **Status** | backlog |
| **Milestone** | M2 |
| **Size** | M |
| **Owner** | unassigned |
| **Created** | 2026-09-16 |
| **Updated** | 2026-09-16 |

## Problem

On 2026-09-16 the nexus tty2 staging recipe (`nexus/docs/feasibility/exo-tty2-integration.md`,
`scripts/exo-tty2-image-stage.sh`) was found to write `tty = 2`, `rescue_tty = 1` into
`boot.toml` — keys `init/nak-init.c` does not parse (its set: `target, boot_caps,
capsule_policy, gen_*, readonly, seccomp, verity*`). Silently ignored; nobody noticed for a
week. That is a consumer referencing items the producer does not have — the precise case
`check!`'s usage scan exists to fail — in a **C** producer with no rustdoc.

NAK-16 adds the keys and forwards `NAK_TTY`/`NAK_RESCUE_TTY`; exo-init (EXO-214) consumes them.

## Goal

A `catalog_surface!` of `boot.toml` keys and one of `NAK_*` env names, written next to the
parser in a tiny Rust test crate under `nakshatra/tools/` (host tooling only — nothing enters
the boot TCB), with a test that greps `nak-init.c` for every listed key so the list and the
parser cannot disagree. Consumers pin it: the nexus recipe (a TOML/markdown key extractor) and
exosphere's exo-init (`NAK_*` string references).

## Scope

- in: surfaces + ledger in nakshatra; the grep-parity test; a non-Rust reference extractor
(TOML keys in a script/doc, `getenv("NAK_…")` in Rust) for `check!`; NAK-16 as the first
entry (Added, Compatible).
- out: parsing TOML in nak-init (it is a string table by design); nakshatra's diverged local
`main` (foreman).

## Acceptance

- [ ] Listing a key the parser lacks (or vice versa) fails nakshatra's host tests.
- [ ] `api-drift check` in nexus reports the recipe's `tty`/`rescue_tty` as **unknown items**
against the pre-NAK-16 snapshot and as Compatible against the post-NAK-16 one.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# APIDRIFT-18 — nexus: `WireKey` / broker IPC (`ReadTerminalGrid`) / `nexus_session::keys` / `guests.d` surfaces; nexus-gui as the consumer (the bro-tui ↔ bro-desktop shape, not in flux)

| Field | Value |
|-------|--------|
| **Status** | backlog |
| **Milestone** | M2 |
| **Size** | M |
| **Owner** | unassigned |
| **Created** | 2026-09-16 |
| **Updated** | 2026-09-16 |

## Problem

APIDRIFT-8b/9 are blocked because bro-cli's backend/frontend pair is mid-refactor. nexus has
the same pair — TUI core producing a wire contract, a new GUI frontend consuming it — and it
is *converging*, not churning: NEXUS-056 (scaffold), NEXUS-057 (`ReadTerminalGrid`
protocol extension), NEXUS-059 (toolkit-free `nexus_session::keys`) are three open PRs
(#106–#108) that each move a surface this week, with no ledger entry. Surfaces:
`nexus_sdk::wire::WireKey` (`crates/nexus-sdk/src/wire.rs:40`, enum), the broker IPC
(protocol), `nexus_session::keys` (rust-public), and `guests.d/*.toml` launcher keys
(NEXUS-004, catalog). Second consumer: out-of-process SDK panels over `NEXUS_PANEL_SOCKET`.

## Goal

Ledger in nexus over the four surfaces; `check!` in `crates/nexus-gui` (its own workspace
root — the check runs where the arniko peer is present, like its other tests) and a
`--json` `api-drift check` step in nexus CI for the SDK-panel contract.

## Scope

- in: four surfaces, ledger, gui `check!`, CI step; NEXUS-057 and NEXUS-059 recorded as the
first entries (Added op → Compatible; moved module → Breaking with `RenameCall`).
- out: bro-cli (stays APIDRIFT-8b/9); the GUI itself.

## Acceptance

- [ ] A `WireKey` variant or broker op added without a note fails nexus tests.
- [ ] nexus-gui's `check!` fails when it uses a key path the pinned snapshot no longer has.
- [ ] The three in-flight PRs' surface moves appear as three ledger entries after merge.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# APIDRIFT-19 — hawk: `PolicyRule` kinds + `SecurityEvent` sink as surfaces; `policies/*.yaml` as a YAML consumer

| Field | Value |
|-------|--------|
| **Status** | backlog |
| **Milestone** | M2 |
| **Size** | S |
| **Owner** | unassigned |
| **Created** | 2026-09-16 |
| **Updated** | 2026-09-16 |

## Problem

`hawk policy lint` is a hand-rolled surface check over the YAML `kind:` strings of
`PolicyRule` (`src/policy.rs:30`); HAWK-9 adds three kinds (`unit_restart_guard`,
`timer_fail_streak`, `zombie_parent`). The `SecurityEvent` JSON sink (`src/lib.rs:86`,
HAWK-3) is consumed by alert-hook scripts with no contract. Both are cheap, and the YAML
consumer is the first non-Rust usage-scan target.

## Goal

`enum_surface!` over `PolicyRule` (sig = the YAML fields per kind), `protocol_surface!` over
the event sink (`schema_hash` via schemars), ledger in hawk; `check!` with a YAML extractor
that reads `kind:` values from `policies/*.yaml` and fails on an unknown kind *before*
`hawk policy lint` would at runtime.

## Scope

- in: two surfaces, ledger, YAML extractor for `check!`; HAWK-9 as the first entry.
- out: the rules themselves (HAWK-9).

## Acceptance

- [ ] A `PolicyRule` variant or a field rename without a note fails hawk tests.
- [ ] A `policies/*.yaml` with a `kind:` not in the pinned snapshot fails `cargo test`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# APIDRIFT-20 — wsforge: `profiles/*.conf` variable set vs `bin/*` reads — a shell catalog surface

| Field | Value |
|-------|--------|
| **Status** | backlog |
| **Milestone** | M3 |
| **Size** | S |
| **Owner** | unassigned |
| **Created** | 2026-09-16 |
| **Updated** | 2026-09-16 |

## Problem

`profiles/nixps.conf` assigns 32 variables; `bin/wsforge-configure` alone reads 36 distinct
`$VARS`. Nobody measures the difference, and WSF-2 adds `NEXUS_VT`, `NEXUS_CONSOLE_ENTRY`,
`NEXUS_GUI_SESSION`. A profile is a data contract between a declarative file and three
scripts — the same shape as checkstand's catalog (APIDRIFT-14) with a shell consumer.

## Goal

`catalog_surface!` over profile keys (sig = type/default/required) in a tiny test crate under
`wsforge/crates/` (one already exists), ledger, and a shell `$VAR` reference extractor for
`check!` so a script reading an undeclared key, or a profile declaring an unread key, fails
`cargo test`.

## Scope

- in: surface, ledger, shell extractor; WSF-2 keys as the first entry.
- out: the provisioning work (WSF-2).

## Acceptance

- [ ] The 36-vs-32 gap is enumerated by the first `check!` run and each item is either
declared, removed, or recorded as intentional.
47 changes: 47 additions & 0 deletions docs/CONSUMERS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Consumers — where api-drift's surfaces already exist in the fleet

Survey date: 2026-09-16. api-drift's M2 (APIDRIFT-8b/9: ledger embed + consumer `check!`)
is blocked on its first customer, bro-cli, being in flux. This note records six
producer → consumer pairs found while working on nexus, x-ray, hawk, exosphere,
nakshatra and wsforge the same day, each of which already carries **hand-rolled drift
discipline or an unrecorded drift incident** — the same signal that made checkstand
(APIDRIFT-13) worth borrowing from. They are candidates to unblock M2 without waiting
for bro-cli. Tickets: APIDRIFT-15 … APIDRIFT-20.

## The map

| # | Producer surface (kind) | Consumer(s) | Evidence it is already drifting / hand-guarded | Ticket |
|---|---|---|---|---|
| 1 | **x-ray** `xray inspect --json` — `meta.schema_version` (`crates/x-ray-tui/src/inspect.rs:78,240`) | **nexus** `crates/nexus-ui/src/xray_inspect.rs` (`SUPPORTED_SCHEMA_MAJOR`, `UnsupportedSchema` error) | A hand-written major-version gate on both sides; XRAY-37 (health snapshot in `--json`) and XRAY-38 (CI smoke for `--help/--version/schema_version`) are pending shape changes with no classifier | **APIDRIFT-15** |
| 2 | **exosphere** `CapabilityType` enum (`crates/core/base/common/src/capability.rs:6`); `ShellRequest`/`ShellResponse` (`crates/exo/shell-client/src/protocol.rs:135,192`); `CapsuleManifest` (**defined twice**: `daemon/src/enforcement.rs:575`, `integration/src/api.rs:56`) | **nexus** `crates/nexus-session/src/exosphere.rs:73` `enum Request` — a serde-tagged **hand copy** of `ShellRequest`; nexus `packaging/exosphere/Capsule.toml`; nakshatra capsule staging | EXO-214 adds a `Tty` variant (exhaustive matches downstream break); EXO-215 adds protocol fields; two manifest structs can already disagree | **APIDRIFT-16** |
| 3 | **nakshatra** `boot.toml` key set (string table in `init/nak-init.c`) and the forwarded `NAK_*` env names | **nexus** staging recipe (`docs/feasibility/exo-tty2-integration.md`, `scripts/exo-tty2-image-stage.sh`); **exosphere** exo-init (reads `NAK_*`) | **Live incident:** the nexus recipe writes `tty`/`rescue_tty`, keys nak-init does not parse — a consumer referencing non-existent producer items, exactly what `check!`'s usage scan exists to fail. NAK-16 adds the keys (first ledger entry) | **APIDRIFT-17** |
| 4 | **nexus** `nexus_sdk::wire::WireKey` (enum, `crates/nexus-sdk/src/wire.rs:40`); broker IPC incl. `ReadTerminalGrid` (NEXUS-057); `nexus_session::keys` (NEXUS-059, rust-public); `guests.d/*.toml` launcher keys (NEXUS-004) | **nexus-gui** (NEXUS-056/057 — the TUI-backend ↔ GUI-frontend pair, same shape as bro-tui ↔ bro-desktop but **not in flux**); out-of-process SDK panels (`NEXUS_PANEL_SOCKET`); exosphere adapter capsule | Three open PRs (#106–#108) each move one of these surfaces this week with no ledger entry | **APIDRIFT-18** |
| 5 | **hawk** `PolicyRule` enum (`src/policy.rs:30`, the YAML `kind:` strings); `SecurityEvent` JSON sink shape (`src/lib.rs:86`, HAWK-3) | `policies/*.yaml` (a **YAML** usage-scan target, not Rust); alert-hook scripts consuming the JSON sink | HAWK-9 adds three rule kinds; `hawk policy lint` is a hand-rolled surface check | **APIDRIFT-19** |
| 6 | **wsforge** `profiles/*.conf` variable set (32 assigned) | `bin/wsforge-{partition,configure,restore}` (36 distinct `$VARS` read) | 36 ≠ 32: already-unmeasured drift between profile and scripts; WSF-2 adds `NEXUS_VT*` keys | **APIDRIFT-20** |

## Why these, in this order

- **#1 first.** Two repos, one JSON contract, both sides already invented a version gate by
hand, and two pending producer changes are queued. It is the smallest cross-repo pair
and needs only `ProtocolSurface` + `schema_hash` (shipped in 8a) on the producer and
`check!` (APIDRIFT-9's remaining half) on the consumer — i.e. it *is* M2, on a stable pair.
- **#3 is the motivating story.** A consumer shipped a recipe against keys that do not
exist, in a C producer. It proves the surface model is not Rust-only (a `catalog_surface!`
list kept next to the parser, plus a test that greps the parser for the listed keys so
list and code cannot disagree).
- **#2 and #4** are the deepest and the most active; they should follow, not lead, because
EXO-214/215 and NEXUS-056..059 are changing the surfaces this month — good first ledger
entries once the ledger exists, bad places to be debugging the ledger itself.
- **#5 and #6** are cheap non-Rust-consumer proofs (YAML, shell).

## What is still missing in api-drift for these

- `check!` + usage scan (APIDRIFT-9) — generalised so the scan target can be Rust (`syn`),
YAML `kind:` values (#5), shell `$VAR` references (#6), or a TOML/markdown key list (#3).
A tiny per-language "reference extractor" trait is enough; polydex can replace it later.
- A `json_shape` sig helper for #1: `schema_hash` over a schemars schema exists; the
`inspect --json` producer has no schema type today, so either derive `JsonSchema` on the
x-ray report structs (XRAY-37 is touching them) or hash a canonical sample. Decide in
APIDRIFT-15.
- Nothing else: `enum_surface!`, `protocol_surface!`, `catalog_surface!`, the ledger crate
and `api-drift check --json` cover the producer side of all six today.
Loading