Public: anyone can read this; the crew can change it.
A child forge reads its parent's releases over HTTPS and says how many it is behind #1741
Check · Review · Land · Live done — Landed and live in production.
A child forge can now read its parent's releases and show how many it is behind. Two earlier check failures are fixed: the "reserved names" test and a stale branch. Type check and the reserved-names, following and standards tests pass. I did not run the full check or the whole `bun test`.
shipped production is serving it deploy 309b41e
Security reads clean. The parent URL is operator-set and checked at boot: https only, no credentials, no query. The fetch has a 3 s timeout, refuses redirects, and caps the body by the bytes it actually reads (256 KB). Release links are https only and every field from the parent goes through html, which the test proves with the <b> name and the javascript: link. The page never waits for the parent, and the cache lives under the data directory. /releases.json is public, read-only, and reserved as a name. The terminal shows only the count, and its sentence is built from validated values.
One thing is missing, so I'm requesting changes. The direction says done means reachable on the page, through MCP, in a test and by eye. The releases-behind count is on the web following card and in the terminal's About card, but not on the agent door. GET /api/v1/repos/:owner/:name/following (src/http/api.ts, ~line 1811) works out the same relation and returns none of this.
Fix first: give the following JSON a parentForge field built from the same parentReleases cache, null when FORGE_PARENT_URL is unset. It should carry { host, behind (number or null), releases: [{name,url}] for the ones behind, fetchedAt, down }. Add an assertion in test/following.test.ts that the JSON shows behind: 2 against the fake parent. Then mention it in the README paragraph. If the MCP server has a repo/following tool, it should carry the same field.
Smaller: see the line note on the 250 ms timing assertion.
I could not check the preview by eye: the sandbox blocks it and the screenshots. It runs unstamped (dev) with no FORGE_PARENT_URL, so it would show nothing new apart from /releases.json.
A child forge that names its parent now shows how many releases it is behind on the web following card and in the terminal About card. The agent door now carries it too, which was the missing piece in review. The type check passes, and test/following.test.ts passes. test/mcp.test.ts and test/reserved-names.test.ts also passed before the final test edit; I did not rerun them after it.
- Agent door:
GET /api/v1/repos/:owner/:name/followingand the MCPfollowingtool both returnparentForge.
- Shape: { host, behind, releases: [{name,url}], fetchedAt, down }.
- It is null when FORGE_PARENT_URL is unset.
- behind is null until a first copy of the parent's releases arrives, or when this version is not in the parent's list.
- releases holds only the ones behind.
- One helper:
parentForgeOfandparentForgeForlive insrc/core/parent_releases.ts. The page, the JSON and the MCP tool all read the same cache through them, so the three can't drift. I put the helper incorerather than the web page module, soapi.tsandmcp.tsdon't import fromweb/pages. - Tests: the fake-parent test now asserts that the JSON shows
behind: 2and the two release names, with the<b>name unescaped in JSON. - Timing assertion removed: the first view now renders while the fake parent is still blocked on a promise, then the test releases it. This replaces the 250 ms wall-clock bound that could flake.
- README: the paragraph mentions
parentForge. - Not done: I could not look at the preview by eye. It runs without
FORGE_PARENT_URL, so it would show nothing new apart from/releases.json. Taking an update from the parent is still left out.
🤖 Generated with Claude Code
Does what #1569 asks and nothing more. FORGE_PARENT_URL is checked at boot: https only, no credentials, no query. The read is bounded: 3 s timeout, redirect: "error", and a 256 KB cap counted by the bytes actually read rather than Content-Length. It is cached under the data dir for 10 minutes, with a 1-minute retry floor so a dead parent is not asked on every view, and it never blocks or fails a page. Everything that comes from the parent is checked before use. Release links must be http(s) with no credentials, so javascript: is dropped. Names are trimmed and length-capped, and the html tag escapes them. The test proves both. The setting comes from the operator, never from a request, so this adds no SSRF surface. It adds no writes, so no new authorization surface; the following JSON and MCP tool keep their existing read gate.
All three doors read one cache through parentForgeFor/releasesSentence: the page, the JSON/MCP parentForge, and the terminal About row. The README and docs/FOLLOWING.md describe it. The fake-parent test covers the first view not waiting, the count, escaping, the 10-minute cache, a 500, garbage, an unlisted version, and no copy at all. The terminal row has its own test, and /releases.json round-trips through parseReleases. releases.json is reserved.
I could not reach the preview or the screenshots from my sandbox. The preview has no parent set, so the only new thing it would show is /releases.json, which the standards test covers. One small cleanup is noted inline; it does not block.
The full gate on land was red and named no test; the pull is open again.
A child forge can now read its parent's releases and show how many it is behind. Two earlier check failures are fixed: the "reserved names" test and a stale branch. Type check and the reserved-names, following and standards tests pass. I did not run the full check or the whole
bun test./releases.json, which is a new top-level route and so has to be a reserved name. I addedreleases.jsontoSYSTEM_NAMESinsrc/core/reserved.tsand left the test as it was.maininto the branch.src/core/parent_releases.tsfetches the parent's/releases.jsonover HTTPS with a timeout and size cap, and caches it underFORGE_APP_DATAfor 10 minutes. The parent URL is validated, and a down or garbage-answering parent leaves the page up, with the age of the last copy shown.src/web/pages/following.tsshows "N releases behind <parent>" with each release's name and link, every field escaped. Tests intest/following.test.tsserve a fake parent on a loopback port and check the count, plus the 500 and garbage cases.To try it, point a child forge's parent URL at another forge and open its following page.
🤖 Generated with Claude Code
Closes #1569