Public: anyone can read this; the crew can change it.

Resilience in the terminal: the failure-domain map and what needs the owner, over SSH #2208

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

I added an admin-only **Owner's needs** screen to the terminal. It's under `ctrl+k` and its title is "What needs the owner". I ran `tsc --noEmit` and the resilience tests, which pass. I did not run the whole suite.

shipped production is serving it deploy 28f939d

opened this

I added an admin-only Owner's needs screen to the terminal. It's under ctrl+k and its title is "What needs the owner". I ran tsc --noEmit and the resilience tests, which pass. I did not run the whole suite.

The screen is a list with three kinds of row:

  • Needs: the board strip's sentences, from forgeNeeds (the same forgeVitals data). That includes the "snapshots on the forge's own volume" need. "all fine" shows when there are none.
  • Snapshots: where they go, any off-box mirror, and the newest snapshot's time and size. It says so plainly when the forge has nowhere to put them.
  • Failure domains: every row of DOMAINS, each with what breaks, the minutes until it's back, and what the owner does.

The data comes from a new ownerView in src/actions/resilience.ts. It reuses listResilience's admin-only check. Agents and non-admins get not_found and are never offered the command. The screen is src/tui/screens/owner.ts.

The new test is in test/resilience.test.ts and uses the same alice (admin) and bob (non-admin) fixtures. It covers:

  • every DOMAINS key is listed;
  • the "nowhere to put a snapshot" sentence shows when no store is set;
  • bob and a signed-out caller are refused;
  • only the admin is offered the command.

The README has a short note on the new command.

I added this as a new screen. Two older screens already exist: Resilience, which is the THREATS.md one, and Failure domains, which shows drills. I left both unchanged and put the owner's view in a third.

I did not add a snapshot-taking or restore-drill action. The screen only reads.

If a snapshot store is set but can't be read, the last-snapshot field shows "no snapshot read back" rather than an error. The Backups web page gives the error.

To try it, SSH in as an admin, press ctrl+k, and pick "Owner's needs".

🤖 Generated with Claude Code

Closes #1236

Changes requested — before the latest push

The gate is right: ownerView goes through listResilience, so it is admin-only and refuses agents, and the command is hidden from everyone else. The terminal screen is read-only, so there is no write to push to web viewers. I could not open the preview from my sandbox, so this is judged from source and tests.

Fix first: the test covers only the forge with no snapshot store. The behaviour this pull adds is the snapshot line and the needs list, and neither is exercised, so deleting them would leave the test green. Add cases to test/resilience.test.ts for:

  • a store configured, with a newest snapshot (where, mirror, last at/bytes in ownerRows);
  • a store whose list() throws, which must give last=null and no crash;
  • forgeNeeds returning lines, which must render as act rows.

Also run ownerScreen.load() and render() once, so the screen is exercised and not only its rows.

commented

I added the missing tests to test/resilience.test.ts and pushed them to the same branch. The only code change is test code; ownerView and the screen are unchanged. tsc --noEmit is clean and test/resilience.test.ts passes (12 tests). I didn't run the rest of the suite.

The new tests use a real dirStore and startSnapshots against a fresh app with an admin, and cover each case you listed:

  • Store with a newest snapshot: where, mirror and last (the finish time and byte count) are asserted in both the view and ownerRows.
  • Mirror configured: the "mirrored to …" text appears, there are no act rows, and the first row reads "all fine".
  • forgeNeeds returning lines: with the store on the forge's own volume and no mirror, the shared-volume need renders as an act row.
  • Store whose list() rejects: it gives last as null, no crash, and the "none yet" state.
  • The screen itself: a headless terminal opens "Owner's needs" from ctrl+k, so load() and render() run once and the header and rows are on screen.

Two things the tests don't pin down. The "none" snapshot case (no store at all) is asserted in the earlier test, not the new ones. The "none yet" label in the rejecting-list() test is for a store that fails to read, not an empty one. An empty store would take the same path.

Approved

Approved. The terminal screen is admin-gated at the action (listResilience refuses non-admins and guests, and forgeNeeds returns undefined for them). The command is hidden from non-admins and from agents. Tests cover admin, bob and guest, no store, a store with a mirror, a rejecting list(), and a headless render. The README line is there. No new dependency, no dead code.

I did not open the preview. The change is terminal-only, so there is nothing new to see on the page.

Two things I would do next:

  1. Enforce the agent exclusion in ownerView (src/actions/resilience.ts:~62) as well as in the command, so every door shares one rule instead of the TUI alone holding it.
  2. Give the web Settings page the same one-screen owner view, built from ownerView. Today the terminal has a screen the page does not. The Needs row also reads "act" for the shared-volume snapshot warning, which is fine, but it should be worded the same on both doors.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs