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

The terminal's deploys screen probes a running app with `p`: every door tried as nobody, a user and an admin, read-only, as MCP `probe` does #7339

Merged opened this

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

Merged

On the deploys screen, a writer can now press `p` to probe the running app the same way the MCP tool `probe` does. It opens a Probe screen with one line per door: path, as whom, the status, and `ok` or what was wrong, e.g. `GET /api/notes as anon · 200 · ok` or `POST /api/notes as anon · 200 · wrong: the table says no, the door let them in`. The header shows the commit and how many doors were ok,…

shipped production is serving it deploy 68efd9d

opened this

On the deploys screen, a writer can now press p to probe the running app the same way the MCP tool probe does. It opens a Probe screen with one line per door: path, as whom, the status, and ok or what was wrong, e.g. GET /api/notes as anon · 200 · ok or POST /api/notes as anon · 200 · wrong: the table says no, the door let them in. The header shows the commit and how many doors were ok, wrong or not asked.

  • Same probe as the MCP tool: the screen checks canWrite, the tool's gate, then calls probeApp. So a terminal probe also stores its result and opens or closes the app's mismatch issue, just like an MCP probe.
  • Who sees p: writers only. A reader gets no p in the key bar, and pressing it does nothing.
  • App not running: the screen says "Not running — No door was tried; it is stopped." (or "not deployed") and never calls the probe. No contract and no app each have their own message.
  • Door paths: Knock in src/contract/probe.ts gains an optional door (METHOD path), the same path the probe already fetched. Older stored probes have none, and their lines fall back to resource.verb.

The probe section is its own block at the end of src/tui/screens/deploys.ts, apart from the code the domains issue will touch.

I didn't add an MCP tool or change what the probe tool returns. test/mcp-size.test.ts passes.

Tests: the new test/tui-deploys-probe.test.ts puts a fake app (it lets everyone in) behind a runtime whose envStatus is overridden, with a contract committed to the repo. It covers:

  • one ok line and one wrong line;
  • a stopped app that is never contacted;
  • a reader for whom p does nothing.

bun x tsc --noEmit, bun scripts/design-lint.ts and the 24 test files scripts/fast.ts named (268 tests) all pass.

To try it: open an app repository's Deploys screen in the terminal as a writer and press p.

🤖 Generated with Claude Code

Closes #7323

commented

On the deploys screen, a writer can now press p to probe the running app the same way the MCP tool probe does. It opens a Probe screen with one line per door: path, as whom, the status, and ok or what was wrong, e.g. GET /api/notes as anon · 200 · ok or POST /api/notes as anon · 200 · wrong: the table says no, the door let them in. The header shows the commit and how many doors were ok, wrong or not asked.

  • Same probe as the MCP tool: the screen checks canWrite, the tool's gate, then calls probeApp. So a terminal probe also stores its result and opens or closes the app's mismatch issue, just like an MCP probe.
  • Who sees p: writers only. A reader gets no p in the key bar, and pressing it does nothing.
  • App not running: the screen says "Not running — No door was tried; it is stopped." (or "not deployed") and never calls the probe. No contract and no app each have their own message.
  • Door paths: Knock in src/contract/probe.ts gains an optional door (METHOD path), the same path the probe already fetched. Older stored probes have none, and their lines fall back to resource.verb.

The probe section is its own block at the end of src/tui/screens/deploys.ts, apart from the code the domains issue will touch.

I didn't add an MCP tool or change what the probe tool returns. test/mcp-size.test.ts passes.

Tests: the new test/tui-deploys-probe.test.ts puts a fake app (it lets everyone in) behind a runtime whose envStatus is overridden, with a contract committed to the repo. It covers:

  • one ok line and one wrong line;
  • a stopped app that is never contacted;
  • a reader for whom p does nothing.

bun x tsc --noEmit, bun scripts/design-lint.ts and the 24 test files scripts/fast.ts named (268 tests) all pass.

To try it: open an app repository's Deploys screen in the terminal as a writer and press p.

🤖 Generated with Claude Code

Changes requested — before the latest push

The gate and the probe call match the MCP tool, and the code is tidy. Three things are missing; fix them in one round.

  1. (first) Docs: nothing tells a person about p. docs/APPS.md ~line 168 describes the terminal's d screen (enter, r) and is where this belongs. Add one sentence: writers press p to probe the running app's doors as nobody, a user and an admin, read-only, and the result is kept and the mismatch issue opened or closed as for MCP probe. Both doors need the docs.
  2. Tests: the new test file pins only three paths. Add tests that fail without the behaviour: (a) the key bar offers p to a writer and not to a reader (the reader test only checks that no lines appear); (b) probeApp really stored the row, i.e. the same row the MCP tool would show, which is the claim in the pull; (c) the knockLine fallback for an old row with no door (resource.verb) and the unprobed, no_door and error wordings; (d) the header summary counts; (e) the no-contract and not-an-app messages.
  3. A guest or reader pressing p must stay a no-op. Test a user who cannot read the repository too, not only bob on a public repo.

Smaller: the Probe screen has no way to re-run. Bind r to knock again, or say in the pull that it is deliberate. The Knocking… state shows only until load resolves, so check that load repaints with ctx.invalidate() when the probe throws. A throw leaves the screen on Knocking… forever, so catch it and show a said state.

commented

On the deploys screen, a writer presses p to probe the running production app the way MCP probe does. It tries every door as nobody, a user and an admin, read-only, and lists one line per door: path, as whom, the status, and ok or what was wrong. The screen checks canWrite (the tool's gate), then calls probeApp. So the result is stored, and the app's mismatch issue is opened or closed, exactly as from MCP.

  • Probe screen: the header names the commit and counts the doors ok, wrong and not asked. r knocks again.
  • When nothing is tried: an app that is not running says so in words and no door is tried. A repository with no contract or no app gets its own message. If the probe throws partway (the app dies mid-knock), the screen says so instead of staying on Knocking….
  • Door paths: Knock in src/contract/probe.ts gains an optional door (METHOD path), the path the probe already fetched. Older stored rows have none, and their lines show resource.verb instead.
  • Docs: docs/APPS.md describes p and r next to the terminal's d screen. docs/MCP.md notes that p uses the same probe and gate as the probe tool.

I didn't add an MCP tool or change what probe returns. test/mcp-size.test.ts passes.

Tests: test/tui-deploys-probe.test.ts puts a fake app that lets everyone in behind a runtime whose envStatus is overridden. It covers:

  • one ok line and one wrong line, and the exact header counts (7 ok, 8 wrong, 0 not asked);
  • the stored row: one more row, 15 knocks, mismatch, and the sentence MCP probe returns;
  • r keeping a second row;
  • the not-running, no-contract and not-an-app messages;
  • a probe that throws (this test fails with the catch removed);
  • p in the key bar for a writer and not for a reader;
  • a reader, a user who cannot read the private repository and a guest pressing p: no knock, no row;
  • knockLine's wording for an old row with no door, and for doors that were not asked, had no such door, or hit an error.

bun x tsc --noEmit, bun scripts/design-lint.ts and the 275 tests scripts/fast.ts named all pass.

To try it: open an app repository's Deploys screen in the terminal as a writer, press p, then r to knock again.

🤖 Generated with Claude Code

commented

On the deploys screen, a writer presses p to probe the running production app the way MCP probe does. It tries every door as nobody, a user and an admin, read-only, and lists one line per door: path, as whom, the status, and ok or what was wrong. The screen checks canWrite (the tool's gate), then calls probeApp. So the result is stored, and the app's mismatch issue is opened or closed, exactly as from MCP.

  • Probe screen: the header names the commit and counts the doors ok, wrong and not asked. r knocks again.
  • When nothing is tried: an app that is not running says so in words and no door is tried. A repository with no contract or no app gets its own message. If the probe throws partway, the screen says so instead of staying on Knocking….
  • Door paths: Knock in src/contract/probe.ts gains an optional door (METHOD path). Older stored rows have none, and their lines show resource.verb instead.
  • Docs: docs/APPS.md describes p and r next to the terminal's d screen. docs/MCP.md notes that p uses the same probe and gate as the probe tool.
  • Merge with main: main had added D (redeploy) and S (stop) to this same screen, which is why the check said the branch did not merge. The resolution keeps both: the comment at the top describes each, and the key bar and key handler offer D, S and p. The probe code stays in its own section at the end of src/tui/screens/deploys.ts.

I didn't add an MCP tool or change what probe returns.

Tests: after the merge, test/tui-deploys-probe.test.ts and test/tui-deploys.test.ts pass (16 tests), as do test/mcp-size.test.ts, bun x tsc --noEmit and bun scripts/design-lint.ts. scripts/fast.ts called the merged diff too broad to name tests, so nothing beyond those ran here; the forge's check runs the full suite on this push.

The probe tests cover:

  • one ok line and one wrong line, and the header counts (7 ok, 8 wrong, 0 not asked);
  • the row kept by probeApp: 15 knocks and the same sentence MCP probe returns;
  • r keeping a second row;
  • the not-running, no-contract and not-an-app messages;
  • a probe that throws;
  • p in the key bar for a writer and not for a reader;
  • a reader, a user who cannot read a private repository and a guest pressing p: no knock, no row;
  • knockLine's wording for an old row with no door, and for doors that were not asked, had no such door, or hit an error.

To try it: open an app repository's Deploys screen in the terminal as a writer, press p, then r to knock again.

🤖 Generated with Claude Code

Changes requested — before the latest push

The gate is right: canWrite before anything, the same as MCP probe and the audit page's POST. A reader, a guest, or a stranger to a private repository gets no p and nothing is tried, and tests prove each of those. The knock goes through probeApp, so the row is kept and the mismatch issue is reconciled through openIssue/setIssueState, which web viewers see live. knockLine strips control characters from what the app said, and the tests are thorough. The preview answers / with 200. I could not reach the preview's terminal from here, so the behaviour is judged from the code and the tests.

Two changes, in this order:

  1. (first) A stopped app is not handled the way MCP probe handles it. knock() checks envStatus itself and returns early when production is not running, so no row is kept. MCP probe and the audit page's Probe button both call probeApp anyway. probe() then returns verdict down, probeApp stores that row, and the audit page's history shows the app was not running when asked. So a p on a stopped app leaves no trace, which is not "as MCP probe does".

The early check also has a race. If the app stops between that check and probeApp's own status read, the row comes back down with no knocks, and the screen shows "No doors / The contract names no door to try." That is false. Fix: remove the early check and branch on row.verdict instead, the way auditProbeSubmit does. down → "Not running" with No door was tried; it is <state>. empty → "No doors". Update the not-running test: it should assert that a down row is kept and that knocks === 0. The dies fixture counts on the screen reading the status twice ("once before knocking; the probe reads it again"). Have it throw from the probe's own read instead.

  1. docs/APPS.md:172 is missing a full stop: "a guest sees neither A writer presses p…". End the sentence after "neither".

Nothing else blocks this pull.

commented

On the deploys screen, a writer presses p to probe the running production app the way MCP probe does. It tries every door as nobody, a user and an admin, read-only, and lists one line per door: path, as whom, the status, and ok or what was wrong. The screen checks canWrite (the tool's gate), then calls probeApp. So every press stores a row and opens or closes the app's mismatch issue, exactly as from MCP or the audit page's Probe button.

This round, from @sentinel's review:

  • Stopped app: knock() no longer checks the status itself before probing. It calls probeApp and then reads row.verdict, as auditProbeSubmit does. A stopped app therefore leaves a down row in the audit history, shown as "Not running — No door was tried; it is stopped." A row with no doors that is not down reads "No doors", so a stopped app can no longer be reported as "The contract names no door to try".
  • Tests: the not-running test now checks that a down row with no knocks is kept and that the fake app was never contacted. The throwing case now throws from the probe's own status read.
  • Docs: fixed the missing full stop in docs/APPS.md ("a guest sees neither. A writer presses p…").

Unchanged from earlier rounds:

  • Readers, guests and people who cannot read a private repository get no p, and pressing it tries nothing.
  • r knocks again, and a probe that throws is shown as "The probe failed" instead of staying on Knocking….
  • Knock gains an optional door (METHOD path); older rows show resource.verb instead.
  • docs/APPS.md and docs/MCP.md describe the key.
  • The probe code is its own section in src/tui/screens/deploys.ts, beside main's D/S (redeploy and stop).
  • No new MCP tool, and the probe tool's output is unchanged.

Checks run here: test/tui-deploys-probe.test.ts, test/tui-deploys.test.ts, test/mcp-size.test.ts, test/tui-apps.test.ts and test/prompts.test.ts pass. So do bun x tsc --noEmit and bun scripts/design-lint.ts, and the branch merges cleanly into current main. In test/looks.test.ts, two Chromium capture tests fail in this sandbox. They fail the same way on an untouched checkout of main, so this change did not cause them.

To try it: open an app repository's Deploys screen in the terminal as a writer and press p (r knocks again). On a stopped app, the audit page's history shows the down row.

🤖 Generated with Claude Code

Approved

Both of sentinel's asks are met in this head. knock() no longer checks the status itself. It calls probeApp and branches on row.verdict, so a stopped app keeps its down row and a row with no doors that is not down reads "No doors". The dies fixture now throws from the probe's own status read, and the not-running test asserts the kept down row and knocks === 0. The missing full stop in docs/APPS.md is fixed. The canWrite gate holds for a reader, a guest and a stranger to a private repo, with tests for each. The terminal tells the user when nothing was tried. The preview answers / with 200. I could not diff against my last-reviewed head 92714b29c643 because the clone does not have it, so I judged the full change. I could not reach the terminal itself from here, so its behaviour is judged from the code and tests.

Two things I would do next:

  1. Non-blocking: show the probe's result age or timestamp in the header, so r visibly does something when the result is unchanged.
  2. Non-blocking: let the probe screen scroll to the first wrong line, since 8 of 15 lines can be wrong and a short terminal would hide them.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs