# TURNKEY W2 — lane 4 (#210), Ruleset presentation — JIT plan

**Lane:** `.worktrees/fix-210`, branch `fix/210-ruleset-presentation`, based at main `c62904e7`
(= `origin/main`; the ROOT checkout is on `fix/w3-comms-text` and local `main` is 67 behind —
never read this lane's sources from the root tree).

**Issue:** BigscreenVR/spt-bs-releases#210 (state: WIP, type: CHANGE). Two asks: (1) the Ruleset
presents as a markdown table, not a list; (2) node subjects present the node NAME, not a hash.

**Status: AUTHORED, NOT BUILT, NOT COMMITTED.** No cargo has fired in this lane — doyle asked us
to hold pool fire until his #209 gate lands, and that ping has not arrived.

## doyle's rulings (2026-08-21, all three as proposed — do NOT re-ask)

- **Q1 renderer shape CONFIRMED.** ONE renderer, markdown pipe table with space-padded cells:
  valid markdown in the chat surface, still column-aligned in a terminal, refuses the
  one-composer-two-renderers fork by construction. **Rider:** guard cell content against embedded
  pipes (escape or refuse at the row edge) — no subject can carry one today, and that is the class.
- **Q2 seam CONFIRMED.** Resolver injection `Fn(&str) -> Option<String>` on the ruleset-rows seam
  (the `derive_attached_node` precedent); real lookup from spt / spt-daemon, fixture map in pins.
  **Degrade CONFIRMED: the FULL 64-hex** — never truncated (the truncation IS the filed defect),
  never blank, never an error. Resolved rows render `node:NAME`.
- **Q3 JSON CONFIRMED.** `subject` stays the identity (full hex); additive resolved-label field,
  skip-if-none. Docs amendment rides the same commit IF the site documents the drill schema —
  **measured: it does not** (no JSON schema for the drill rows anywhere in `docs-site/src`), so the
  docs work here is the prose + example, not a schema amendment.
- **REQ CONFIRMED: two concerns, two reqs, do not fold** — amend `REQ-ER-RULESET-TABLE`'s table
  clause, mint a NEW req for name-resolution-and-degrade.

## Census (read at `c62904e7`, not the root tree)

- ONE composer: `spt_store::briefing::ruleset_rows_where` → `RulesetRow`. ONE table renderer:
  `render_ruleset_table`. `render_ruleset_drilldown` wraps it and appends removal commands.
- TWO render consumers, no fork: `compose_briefing` (spt-store) and `cmd_access_view`'s drill arm
  (`crates/spt/src/cli.rs:19163`). The drill's `--json` serializes the SAME `RulesetRow` structs.
- `subject_word` emits the FULL 64-hex for a node subject. The issue's `node:14efb80c…` is a
  retelling, not our literal — so both halves of unreadability are ours to close.
- LAYERING: `spt-store` has NO `spt-net` edge. Node labels live in the gossiped registry
  (`node_labels`) and in `spt_store::roster::RosterStore` (the same `node_label` lease). Hence the
  injected resolver, with the store's own roster+hostname resolver as the default source.

## What is authored (uncommitted, in the lane worktree)

- `traceable-reqs.toml` — `REQ-ER-RULESET-NODE-NAMES` minted, stages `["doc","impl","unit"]`,
  activated; int deliberately NOT activated (no integration leg is claimed for a render).
  `REQ-ER-RULESET-TABLE`'s title amended with the markdown clause + the pipe-escape rider. Both
  reqs sit in the `cli-output-surface` and `engine-room-governance` groups. toml re-parses, 796 reqs.
- `CONTEXT.md` — the engine-room ruleset sentence amended by replacement (markdown table, node
  names, full-hex degrade, identity unchanged), tagged for both reqs.
- `docs/adr/0052-…md` — decision 3's "presents access rulesets as tables" amended by replacement,
  tagged for the new req.
- `docs-site/src/networking/access-viewing.md` — drill-down section: markdown-table prose, a
  rendered example showing `node:ENLYZEAM`, and the full-hex degrade + `--json` identity note.
- `crates/spt-store/src/briefing.rs` — `RulesetRow.subject_label` (additive, skip-if-none);
  `subject_label_of`; `row_of` takes the resolver; `ruleset_rows_with` / `ruleset_rows_where_with`
  (the old pure forms delegate with a no-op resolver = the degrade path);
  `roster_node_label_resolver()` (roster labels + own hostname, `load_existing` so a render never
  MINTS an identity); `bringup_briefing` uses it; **11 pins appended.**
- `crates/spt/src/cli.rs` — drill arm passes a layered resolver (roster first, then the gossiped
  registry labels via `api::reporting::resolve_node_label`, which is now `pub(crate)`).

**DELIBERATE AND LOAD-BEARING:** `render_ruleset_table`'s BODY is still the OLD space-aligned one.
That is what makes the markdown/name pins a witnessed RED against the renderer the fix will own,
instead of a green that proves nothing. Same two-commit shape doyle approved for #206.

## Next steps, in order, once doyle pings that #209's gate has landed

1. `cargo run -p xtask -- pool-claim --pool <lane target> --label fix-210` **from the lane
   worktree** (the claim records the lane's git identity). `df` first; count other live pools as
   budget spent; reap finished pools before firing.
2. Witness the RED: `cargo test -p spt-store briefing` — expect the markdown/name pins to fail
   against the old body. Do not skip it, do not repair the body first.
3. Commit 1 = pins + seam + reqs + docs (RED).
4. Apply commit 2's body: `python <scratchpad>/render210.py` (holds the ruled renderer verbatim —
   `md_cell` escaping, `RulesetRow::subject_display`, char-width padding, the separator row, the
   explicit empty line preserved).
5. `cargo test -p spt-store`, then `cargo test -p spt` (the drill arm compiles against the new
   seam), `cargo clippy`, `traceable-reqs check`.
6. Commit 2 = the renderer body (GREEN). Ship the sha to doyle.

## After #210

Lane 2 (#206) is parked authored-and-uncommitted in `.worktrees/fix-206` — same blocker, and its
red comes first if the pool only frees once. Lane 1 (#208) needs its own writer census before any
build. Lane 3 (#200) is last and may need to build on #209's lane tip.
