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
Check · Review · Land · Live done — Landed and live in production.
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
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>orMirror: 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 samesaveMirrorthe Backups page andmirror_setuse. 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 callsforgetMirror.- Who sees it:
MandFand their hint show only for an admin whosekindisuser. Everyone else still sees the read-only screen, plus the mirror's place. - Wiring:
resilienceScreennow takes(session, ctx), andsrc/tui/forge.tsis updated to match.resilienceLinesis 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.
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:
- The test reaches the network and is allowed to hang. Sealing a complete mirror starts
mirrorFresh()against a real S3 store athttp://mirror.invalid. The pull itself says the outbound connection hangs in the sandbox, which is whyafterAlldoesvoid 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: givestartSnapshotsa local mirror, e.g.mirror: () => mirrorSealedSet(forge.db).length >= 4 ? dirStore(join(dataDir, "mirror")) : undefined, and thenawait svc.stop(). - 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
mayMirrordeleted. The new rule iskind === "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, pressmandf, and assert that no prompt opens, the hint is absent, andmirrorSealedSetis unchanged. - 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>,Mseals it with five prompts where a blank keeps what was sealed, andFforgets it. Give the same wording to the Backups page andmirror_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.
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:
startSnapshotsnow gets a localdirStoremirror, handed out once four mirror keys are sealed. The endpoint typed in the test ishttp://s3.example.testand is never contacted.afterAllnow awaitssvc.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,
mopens no prompt,fopens no confirm, andmirrorSealedSetis unchanged. With the gate loosened toisAdminalone, 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 showsMirror: <place>orMirror: none.,Mseals with five prompts and a blank keeps what was sealed, andFforgets after a confirm. I did not edit the Backups page or themirror_setdoc line separately. - Typed input: the cast is replaced by a
DestinationInputbuilt field by field, so a renamed field is now a compile error.
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.
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
Mand forget it withF.test/tui-mirror-set.test.ts,test/resilience.test.tsandtest/mcp-size.test.tspass,tscis 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
Mirror: <place>orMirror: 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 samesaveMirrorthe Backups page andmirror_setuse. 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 callsforgetMirror.MandFand their hint show only for an admin whosekindisuser. Everyone else still sees the read-only screen, plus the mirror's place.resilienceScreennow takes(session, ctx), andsrc/tui/forge.tsis updated to match.resilienceLinesis unchanged.Test:
test/tui-mirror-set.test.tsseals, 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 offeredMorF. 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 awaitsvc.stop()inafterAll, because in this sandbox the outbound connection hangs rather than failing.Not done:
Closes #7276