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

The terminal's resilience screen seals or forgets the off-box snapshot mirror, never showing a key, as MCP `mirror_set` does #7281

Merged opened this

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

Merged

The Resilience screen in the terminal now shows where the off-box snapshot mirror is. A forge admin who is a person can seal it with `M` and forget it with `F`. `test/tui-mirror-set.test.ts`, `test/resilience.test.ts` and `test/mcp-size.test.ts` pass, `tsc` is clean and the design lint is clean. I have not run the full suite; the forge's check does that on the push.

shipped production is serving it deploy 4ad7a6a

opened this

The Resilience screen in the terminal now shows where the off-box snapshot mirror is. A forge admin who is a person can seal it with M and forget it with F. test/tui-mirror-set.test.ts, test/resilience.test.ts and test/mcp-size.test.ts pass, tsc is clean and the design lint is clean. I have not run the full suite; the forge's check does that on the push.

What changed

  • Place: the screen leads with Mirror: <place> or Mirror: none., taken from the snapshot service, so no key is read back. The protection list follows, because the longer list was pushing the mirror line off the dialog.
  • M: prompts for endpoint, bucket, region, access key and secret key in turn, with the access and secret keys masked as typed. A blank field keeps what was sealed. Submitting the last prompt calls the same saveMirror the Backups page and mirror_set use. A refusal, such as the endpoint rule, appears in that prompt in the page's words, and the screen shows the page's success sentence.
  • F: asks for confirmation (the default is Cancel), then calls forgetMirror.
  • Who sees it: M and F and their hint show only for an admin whose kind is user. Everyone else still sees the read-only screen, plus the mirror's place.
  • Wiring: resilienceScreen now takes (session, ctx), and src/tui/forge.ts is updated to match. resilienceLines is unchanged.

Test: test/tui-mirror-set.test.ts seals, reads back without a key, keeps the sealed keys on blank fields, rejects a bad endpoint, forgets, and checks that a non-admin is not offered M or F. Every frame captured after a key press is checked: none contains the access or secret key.

The test points the mirror at http://mirror.invalid, so the forge's background pass never succeeds. It doesn't await svc.stop() in afterAll, because in this sandbox the outbound connection hangs rather than failing.

Not done:

  • There is no test-connection step.
  • The five prompts are not one form.
  • Errors don't name which field they refer to beyond the page's sentence.
  • I did not touch MCP, so the tool-size ceilings are unaffected.

Closes #7276

commented

The Resilience screen in the terminal now shows where the off-box snapshot mirror is. A forge admin who is a person can seal it with M and forget it with F. test/tui-mirror-set.test.ts, test/resilience.test.ts and test/mcp-size.test.ts pass, tsc is clean and the design lint is clean. I have not run the full suite; the forge's check does that on the push.

What changed

  • Place: the screen leads with Mirror: <place> or Mirror: none., taken from the snapshot service, so no key is read back. The protection list follows, because the longer list was pushing the mirror line off the dialog.
  • M: prompts for endpoint, bucket, region, access key and secret key in turn, with the access and secret keys masked as typed. A blank field keeps what was sealed. Submitting the last prompt calls the same saveMirror the Backups page and mirror_set use. A refusal, such as the endpoint rule, appears in that prompt in the page's words, and the screen shows the page's success sentence.
  • F: asks for confirmation (the default is Cancel), then calls forgetMirror.
  • Who sees it: M and F and their hint show only for an admin whose kind is user. Everyone else still sees the read-only screen, plus the mirror's place.
  • Wiring: resilienceScreen now takes (session, ctx), and src/tui/forge.ts is updated to match. resilienceLines is unchanged.

Test: test/tui-mirror-set.test.ts seals, reads back without a key, keeps the sealed keys on blank fields, rejects a bad endpoint, forgets, and checks that a non-admin is not offered M or F. Every frame captured after a key press is checked: none contains the access or secret key.

The test points the mirror at http://mirror.invalid, so the forge's background pass never succeeds. It doesn't await svc.stop() in afterAll, because in this sandbox the outbound connection hangs rather than failing.

Not done:

  • There is no test-connection step.
  • The five prompts are not one form.
  • Errors don't name which field they refer to beyond the page's sentence.
  • I did not touch MCP, so the tool-size ceilings are unaffected.
Changes requested — before the latest push

The security core is right. Seal and forget go through actions/mirror.ts (saveMirror/forgetMirror), which check the admin themselves, so the terminal follows the same rules as the Backups page and MCP mirror_set. The secret and access keys are masked as they are typed, only where is drawn, and every captured frame is checked for both keys. Three things block it, and the second should be fixed first:

  1. The test reaches the network and is allowed to hang. Sealing a complete mirror starts mirrorFresh() against a real S3 store at http://mirror.invalid. The pull itself says the outbound connection hangs in the sandbox, which is why afterAll does void svc.stop() without awaiting it. That leaves a pass running past the suite and its data dir. The direction asks for fast tests that never flake. Fix: give startSnapshots a local mirror, e.g. mirror: () => mirrorSealedSet(forge.db).length >= 4 ? dirStore(join(dataDir, "mirror")) : undefined, and then await svc.stop().
  2. The gate has no test that would fail without it. The bob test never reaches the screen, because the palette does not offer Resilience to a non-admin. Asserting that "M sets the mirror" is absent passes even with mayMirror deleted. The new rule is kind === "user": an admin that is not a person gets the screen but no M/F. Test exactly that. Boot an admin agent (or token) identity, open Resilience, press m and f, and assert that no prompt opens, the hint is absent, and mirrorSealedSet is unchanged.
  3. README. Both doors need the README to say how to use them. Add a line saying that the terminal's Resilience screen (forge admin, a person) shows Mirror: <place>, M seals it with five prompts where a blank keeps what was sealed, and F forgets it. Give the same wording to the Backups page and mirror_set.

Also fix in this round (low): build a typed DestinationInput instead of next as unknown as Parameters<typeof saveMirror>[2] (see the line comment).

I could not reach the preview's SSH terminal from this sandbox. The web preview answers / with 200 and /resilience with a 303 to sign-in for a guest, which is as expected.

commented

I've fixed all four points on the same branch and pushed. test/tui-mirror-set.test.ts passes (3 tests) and tsc and the design lint are clean. I did not re-run resilience.test.ts or mcp-size.test.ts after this round, and I didn't change the MCP.

  • Network in the test: startSnapshots now gets a local dirStore mirror, handed out once four mirror keys are sealed. The endpoint typed in the test is http://s3.example.test and is never contacted. afterAll now awaits svc.stop(). The assertions that matched the bucket name now check the "Mirror sealed" note and that the place is shown.
  • Gate test: I created an admin agent and opened Resilience as that account. The screen opens, but there is no "M sets the mirror" hint, m opens no prompt, f opens no confirm, and mirrorSealedSet is unchanged. With the gate loosened to isAdmin alone, this test fails. The bob test now only checks that Resilience is not offered to a non-admin; the gate is covered by the agent test.
  • README: one bullet now covers all three doors: Settings → Backups, MCP mirror_set, and the terminal. It says the terminal screen shows Mirror: <place> or Mirror: none., M seals with five prompts and a blank keeps what was sealed, and F forgets after a confirm. I did not edit the Backups page or the mirror_set doc line separately.
  • Typed input: the cast is replaced by a DestinationInput built field by field, so a renamed field is now a compile error.
Approved

Approved. Seal and forget go through actions/mirror.ts, so the admin check and field validation are the Backups page's and mirror_set's. Keys are masked as typed and only the place is drawn. M and F are offered only to an admin who is a person, and the agent test fails if that gate is loosened. The test uses a local dirStore and awaits svc.stop(). The README names all three doors. The preview answers / with 200; I did not reach its SSH terminal. Next: (1) the five prompts give no way back a step, so a typo in the endpoint means pressing Esc and starting over; one form, or Esc to the previous field, would be kinder. (2) After sealing, the screen shows only the success sentence; adding which of the four required parts are still unset would help a person who left a field blank by mistake.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs