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

A writer's shell reaches the repository's own production and previews when its machine says `apps`, and nothing else #9943

Merged opened this

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

Merged

A person's shell now reaches the repository's own production and its previews when its machine says `apps`. Before this, the shell's cell could reach only the forge.

shipped production is serving it deploy f21b126

opened this

A person's shell now reaches the repository's own production and its previews when its machine says apps. Before this, the shell's cell could reach only the forge.

What changed

  • src/machines/shell.ts: a new pure function, shellReach(forgeUrl, machine, appUrls), decides what the cell may open: the forge, plus reachableApps(machine, ownAppUrls(ownApp(app, repo))). That is the same list of the repository's own app URLs the crew's runs get. shellCell takes those URLs, and start() passes them in. A machine with reach: [] gets the forge alone.
  • The URLs go into the cell's reach, which openCell turns into exact host-and-port origins in the egress proxy. No patterns are allowed. The egress list stays empty and hide still covers the forge's data whatever the machine says. src/apps/egress.ts, src/apps/contain.ts and src/crew/machines_read.ts did not need changes.
  • docs/features/machines.md: the shell's "Where" bullet now says what it reaches, and the clause is gone from "Not done yet".

Two departures from the issue

  • standard gets apps reach. The issue says nothing should change for standard, but the built-in standard machine declares reach: ["apps"] (src/crew/machines.ts). I followed the issue's own rule that what the machine declares draws the boundary, so a shell on standard now reaches its repository's apps too. If standard shells should stay forge-only, standard's declaration has to change. This PR doesn't change it.
  • Private addresses aren't checked for these hosts. As origins, the app hosts skip the egress proxy's check that refuses private addresses, just as the forge's own URL does. Hosts on the forge's apps domain can resolve to private addresses inside the forge's network, and the public-only check would refuse them. The cost: if a verified custom production domain pointed at a private address, the shell could reach that address on the domain's exact port.

Tests

  • New tests in test/crew-app-hosts.test.ts use the existing two-app fixture (arm, plus armory belonging to another repository). They check the cell's reach for apps (the repository's own production and preview, nothing from armory, data still hidden), for reach: [] (the forge alone) and for standard. They fail on main because shellReach doesn't exist there.
  • Passing: bun scripts/gate.ts, bun x tsc --noEmit, and eight test files covering machines, the shell, contain and features.
  • In that combined run, one test in test/machine-shell.test.ts failed once ("a reload's first size redraws the screen…"). The file passed 3 of 3 times on its own with this change. It's a timing test with nothing to do with reach, so I read it as a flake under load.
  • The cell suites are skipped under FORGE_TEST_SOCKET, so the forge's check is what will run them.
  • scripts/fast.ts listed 1,511 test files, far over its budget, so I ran only the files that cover this change.

To try it: on this pull's preview, sign in as a writer of an app repository and open /<owner>/<repo>/settings/machines. Press Open shell on standard and curl the production or a preview URL; it answers. Another repository's app is refused. In this sandbox the shell runs uncontained, so I couldn't check this end to end.

🤖 Generated with Claude Code

Closes #9934

commented

A person's shell now reaches the repository's own production and its previews when its machine says apps. Before this, the shell's cell could reach only the forge.

What changed

  • src/machines/shell.ts: a new pure function, shellReach(forgeUrl, machine, appUrls), decides what the cell may open: the forge, plus reachableApps(machine, ownAppUrls(ownApp(app, repo))). That is the same list of the repository's own app URLs the crew's runs get. shellCell takes those URLs, and start() passes them in. A machine with reach: [] gets the forge alone.
  • The URLs go into the cell's reach, which openCell turns into exact host-and-port origins in the egress proxy. No patterns are allowed. The egress list stays empty and hide still covers the forge's data whatever the machine says. src/apps/egress.ts, src/apps/contain.ts and src/crew/machines_read.ts did not need changes.
  • docs/features/machines.md: the shell's "Where" bullet now says what it reaches, and the clause is gone from "Not done yet".

Two departures from the issue

  • standard gets apps reach. The issue says nothing should change for standard, but the built-in standard machine declares reach: ["apps"] (src/crew/machines.ts). I followed the issue's own rule that what the machine declares draws the boundary, so a shell on standard now reaches its repository's apps too. If standard shells should stay forge-only, standard's declaration has to change. This PR doesn't change it.
  • Private addresses aren't checked for these hosts. As origins, the app hosts skip the egress proxy's check that refuses private addresses, just as the forge's own URL does. Hosts on the forge's apps domain can resolve to private addresses inside the forge's network, and the public-only check would refuse them. The cost: if a verified custom production domain pointed at a private address, the shell could reach that address on the domain's exact port.

Tests

  • New tests in test/crew-app-hosts.test.ts use the existing two-app fixture (arm, plus armory belonging to another repository). They check the cell's reach for apps (the repository's own production and preview, nothing from armory, data still hidden), for reach: [] (the forge alone) and for standard. They fail on main because shellReach doesn't exist there.
  • Passing: bun scripts/gate.ts, bun x tsc --noEmit, and eight test files covering machines, the shell, contain and features.
  • In that combined run, one test in test/machine-shell.test.ts failed once ("a reload's first size redraws the screen…"). The file passed 3 of 3 times on its own with this change. It's a timing test with nothing to do with reach, so I read it as a flake under load.
  • The cell suites are skipped under FORGE_TEST_SOCKET, so the forge's check is what will run them.
  • scripts/fast.ts listed 1,511 test files, far over its budget, so I ran only the files that cover this change.

To try it: on this pull's preview, sign in as a writer of an app repository and open /<owner>/<repo>/settings/machines. Press Open shell on standard and curl the production or a preview URL; it answers. Another repository's app is refused. In this sandbox the shell runs uncontained, so I couldn't check this end to end.

🤖 Generated with Claude Code

commented

A person's shell can now reach the repository's own production and its previews when its machine says apps. Before this, the shell's cell could reach only the forge. This push also fixes a flaky test that failed the last check.

What changed

  • src/machines/shell.ts: a new pure function, shellReach(forgeUrl, machine, appUrls), decides what the cell may open: the forge, plus reachableApps(machine, ownAppUrls(ownApp(app, repo))). That is the same list of the repository's own app URLs the crew's runs get. shellCell takes those URLs and start() passes them in, so a machine with reach: [] gets the forge alone. The URLs go into the cell's reach, which openCell turns into exact host-and-port origins in the egress proxy, with no patterns. egress stays empty, and hide still covers the forge's data whatever the machine says.
  • docs/features/machines.md: the shell's "Where" bullet says what it reaches, and the clause is gone from "Not done yet".
  • test/crew-app-hosts.test.ts: covers the forge alone for reach: [], the repository's own production and previews for apps, and never another repository's app.
  • test/web-commits-check.test.ts: this file made seven anonymous requests for deep pages (/commits, /commit/…). Strangers get 30 of those per minute per address, cut to 7 while the event loop runs late (src/http/crawl.ts). In a loaded 40-file shard, the eighth request was refused, which broke "the list has no card markup…" and "a commit's page has no card…". The test was wrong, not the code: it now reads as the signed-in owner, and stays anonymous only where the private-repository test needs a stranger. Forcing the "behind" state through setCrawlClock makes the old file fail those same two tests; the new file passes.
  • The branch merges the current main.

Not done: the built-in standard machine declares reach: ["apps"] (src/crew/machines.ts), so its shells get apps reach too, although the issue says standard should not change. I followed the declaration rather than add a special case.

Checks: bun scripts/gate.ts and bun x tsc --noEmit pass, and both test files above pass (15 tests). scripts/fast.ts named 1512 files after the merge with main, too broad to run here, so the full suite is left to the forge's check.

To try it: open a shell on a machine whose reach includes apps and curl the repository's production or a preview URL. It answers; another repository's app and other hosts are refused.

🤖 Generated with Claude Code

Approved

Shell reach is a pure function (shellReach), fed by the same ownAppUrls the crew uses, exact origins only, egress stays empty, data stays hidden; docs updated in machines.md (not README). Tests fail on main. I did not exercise a contained shell: the cell suites skip here. Two next things: (1) decide on standard: it declares reach apps, so its shells now reach the repo apps, which contradicts box 1 ("standard: nothing changes"); either change standard or amend the issue, and add a test pinning the choice. (2) The web-commits-check change is an unrelated flake fix riding in this pull; split such fixes out next time. Also rewrap the header comment in shell.ts, which now has an over-long line.

commented

Left #9934 open: 1 unticked item remain (When the default branch's forge.json gives the shell's role a machine with reach: ["apps"], the shell's cell can open its repository's production and preview hosts through the same egress allowance the crew's runs use (src/apps/egress.ts). With reach: [] or standard, nothing changes.).

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs