Public: anyone can read this; the crew can change it.
The docs and /settings/origins say plainly that a forge older than 0.7.346 is connected from the newer side, and that upgrading it lets it connect outward too #9638
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/9626-the-docs-and-settings-origins-say-plainl → main 3 commits fork 1 thread · 1 unresolved
4 files
changed +42
−3
docs/features/forge-origin-compat.md +1 −1
| @@ -8,7 +8,7 @@ Written by `scripts/forge-compat-lab.sh`; run it on any docker host to get this | ||
| 8 | 8 | |
| 9 | 9 | **Absent, and what this cannot show.** `absent` is a thing not built, or not checkable here, said plainly. |
| 10 | 10 | |
| 11 | - An old forge cannot connect another forge at all: it has no forge provider, so that direction asks for an upgrade, not a fix here. | |
| 11 | - An old forge cannot connect another forge at all: it has no forge provider, so that direction asks for an upgrade, not a fix here. By release: a forge older than 0.7.346 connects outward never (connect it from the newer side, see [](forge-origin-older.md#connecting-from-an-older-forge)); 0.7.346 or later connects outward. | |
| 12 | 12 | - A webhook needs `admin` scope at the origin, so a `ro` or `rw` token and every sign-in register none; issues and pulls come back on a sync or the sweep. With an admin token the hook registers, but the lab's source refuses to deliver to a private address, as its SSRF guard should, so a delivery end to end is unproven here. |
| 13 | 13 | - The live 0.5.29 was read anonymously only: its OAuth metadata, health, OpenAPI and 401s. Nothing was signed in to it. |
| 14 | 14 | - Not carried by design or not built: how the app runs, and its data and secrets. |
docs/features/forge-origin-older.md +4
| @@ -10,6 +10,10 @@ A forge from before `/api/v1/version` (0.5.x) is recognised by two answers no ot | ||
| 10 | 10 | |
| 11 | 11 | Measured against real old code in containers, and compared with the live forge-apps.benjivers.com by anonymous reads only: [](forge-origin-compat.md). There is no "needs a newer release" error either: a forge that matches neither fingerprint is refused as unrecognised. |
| 12 | 12 | |
| 13 | ## Connecting from an older forge | |
| 14 | ||
| 15 | A forge older than 0.7.346 has no forge provider, so it cannot start a connection to another forge. Connect it from the newer forge instead: that forge's `/settings/origins` → **Another forge** reaches the older one today. Upgrading the older forge to 0.7.346 or later lets it connect outward too. `/settings/origins` says the same in one line under Another forge (`test/web-origins.test.ts`). | |
| 16 | ||
| 13 | 17 | ## Tokens and plain http |
| 14 | 18 | |
| 15 | 19 | A token is sent to the forge named by the link, so `http:` links are refused for it (page and terminal) unless `FORGE_DEV=1`; the one-click sign-in still works. `connectSite` in `src/origins/bring.ts` decides. |
src/web/pages/origins.ts +15 −2
| @@ -600,6 +600,17 @@ function tokenSection(values: ConnectValues): Html { | ||
| 600 | 600 | </form>`; |
| 601 | 601 | } |
| 602 | 602 | |
| 603 | const OLDER_FORGE_DOC = "/trevato/forge/blob/main/docs/features/forge-origin-older.md#connecting-from-an-older-forge"; | |
| 604 | ||
| 605 | /** The one line for a forge older than 0.7.346 (#9626): it cannot start a connection, this side can. */ | |
| 606 | function olderForgeLine(): Html { | |
| 607 | return html`<p class="muted small" id="older-forge-line"> | |
| 608 | A forge older than 0.7.346 cannot connect outward: connect it from here, and | |
| 609 | upgrade it to connect both ways. | |
| 610 | <a href="${OLDER_FORGE_DOC}">Connecting from an older forge</a> | |
| 611 | </p>`; | |
| 612 | } | |
| 613 | ||
| 603 | 614 | /** |
| 604 | 615 | * The path a friend's forge needs, first on the page: its address and one |
| 605 | 616 | * button. Only a forge that cannot name its own callback asks for a token here, |
| @@ -624,7 +635,8 @@ function forgeSection(ctx: Ctx, values: ConnectValues): Html { | ||
| 624 | 635 | ...(values.hostError ? { error: values.hostError } : {}), |
| 625 | 636 | })} |
| 626 | 637 | <div class="form-actions">${btn("Sign in there", { variant: "primary", icon: "external" })}</div> |
| 627 | </form>`; | |
| 638 | </form> | |
| 639 | ${olderForgeLine()}`; | |
| 628 | 640 | const site = |
| 629 | 641 | values.tokenHost === "" |
| 630 | 642 | ? null |
| @@ -664,7 +676,8 @@ function forgeSection(ctx: Ctx, values: ConnectValues): Html { | ||
| 664 | 676 | <div class="form-actions"> |
| 665 | 677 | ${btn("Connect that forge", { variant: "primary", icon: "globe" })} |
| 666 | 678 | </div> |
| 667 | </form>`; | |
| 679 | </form> | |
| 680 | ${olderForgeLine()}`; | |
| 668 | 681 | } |
| 669 | 682 | |
| 670 | 683 | /** The admin's list of hosts an origin may reach inside the network. */ |
test/web-origins.test.ts +22
| @@ -203,6 +203,28 @@ describe("origins on the web", () => { | ||
| 203 | 203 | expect(response.headers.get("location")).toStartWith("/signin?next="); |
| 204 | 204 | }); |
| 205 | 205 | |
| 206 | test("under Another forge, one line says an older forge is connected from here, linking the docs (#9626)", async () => { | |
| 207 | const html = await (await asUser(alice, "/settings/origins")).text(); | |
| 208 | expect(html).toContain("A forge older than 0.7.346 cannot connect outward"); | |
| 209 | expect(html).toContain("upgrade it to connect both ways"); | |
| 210 | expect(html).toContain( | |
| 211 | 'href="/trevato/forge/blob/main/docs/features/forge-origin-older.md#connecting-from-an-older-forge"', | |
| 212 | ); | |
| 213 | expect(html.indexOf("older-forge-line")).toBeGreaterThan(html.indexOf("Another forge")); | |
| 214 | expect(html.split("older-forge-line").length - 1).toBe(1); | |
| 215 | }); | |
| 216 | ||
| 217 | test("the line also shows where the forge cannot name its callback and asks for a token (#9626)", async () => { | |
| 218 | const html = await ( | |
| 219 | await asUser(alice, "/settings/origins", { headers: { host: "unvouched.example.test" } }) | |
| 220 | ).text(); | |
| 221 | expect(html).toContain("Connect that forge"); | |
| 222 | expect(html).not.toContain("Sign in there"); | |
| 223 | expect(html).toContain("A forge older than 0.7.346 cannot connect outward"); | |
| 224 | expect(html).toContain("#connecting-from-an-older-forge"); | |
| 225 | expect(html.split("older-forge-line").length - 1).toBe(1); | |
| 226 | }); | |
| 227 | ||
| 206 | 228 | test("the origins page starts empty and offers the three ways in", async () => { |
| 207 | 229 | const html = await (await asUser(alice, "/settings/origins")).text(); |
| 208 | 230 | expect(html).toContain("No origins connected"); |
The docblock above ("The path a friend's forge needs, first on the page...") belongs to forgeSection, but this constant now sits between it and the function. Move OLDER_FORGE_DOC and olderForgeLine above that comment. Principle 1: nothing says the wrong thing.