A hosted child keeps its parent's scheme and port: a child of http://forge.lan:8080 is http://kid--forge.lan:8080, not https://kid--forge.lan #47

Merged
grokbot merged 5 commits from grokbot/forge:fix/child-public-url-scheme-port into main 2026-10-17 23:46:20 +00:00
Owner

childPublicUrl built every child's address as https://<name>--<host>
from a host with its port stripped. Behind the platform (https on 443) that
is right. For any parent on plain http or another port it was wrong in two
ways: the child was told an https address nothing serves, and it lost the
port. Its config url, the ledger, /spawn answers, the claim link, and
its apps' FORGE_URL all pointed nowhere.

  • parentOrigin(config) in spawn/placement.ts names the parent's public
    origin once: its canonical url when it has one, otherwise
    http://<host>:<port> as main.ts names itself. It replaces
    parentHostOf in actions/spawn.ts, which was a copy of childHost.
  • childPublicUrl(name, parent) takes that origin and keeps its scheme and
    port (a scheme's default port drops, as URL does). A bare host still means
    https with no port, so the platform's https://<name>--forge.open-platform.sh
    and every existing caller and test are unchanged.
  • Every place a child's address is made uses it: the spawn door's placement
    and ledger seeding (door.ts), liveChildUrl, the child app's own url
    (host.ts), and its apps' forge URL (child-apps.ts). Matching a Host to a
    child (parseHostedChildName) already ignored the port and is unchanged, as
    is childHost for those host-only uses.

Tests: childPublicUrl and parentOrigin across http/https, explicit,
default and missing ports. End to end, a parent at http://forge.lan:8080
spawns kid and the spawn result, liveChildUrl, childUrlOf and the
opened child's own config.url are all http://kid--forge.lan:8080. On main
the same parent gives https://kid--forge.lan.

Overlap: #2419 (a new or spawned forge starts with signup = invite) changes
the child's signup setting, not its address. No open pull touches these files
for it, so neither rebases on the other. #41 (first-admin) does not touch
placement.

Note: the single-label shape is unchanged, so a child of http://localhost:3000
is http://kid--localhost:3000, which needs a hosts entry or wildcard DNS to
resolve, the same as before but now on the right scheme and port.

Tests run by hand (bun 1.3.14, CI env)

bun install --frozen-lockfile, bun scripts/gate.ts, tsc --noEmit and bun scripts/hollow.ts are clean.

Before, on main: childPublicUrl("kid", "forge.lan:8080") gave https://kid--forge.lan.

bun test test/spawn-placement.test.ts test/spawn-host.test.ts:

(pass) hosted child Host dispatch > parent Host still serves the parent
(pass) hosted child Host dispatch > child Host reaches the child's forge
(pass) hosted child Host dispatch > a child's requests are logged in the child's data, never the parent's
(pass) a parent on plain http with a port > its child lives at http, on the parent's port, wherever its address is read
(pass) hosted placement > places a child as a subdomain of the parent, one agent, no SSH
(pass) hosted placement > refuses beyond the children limit, and retiring frees a slot
(pass) hosted placement > refuses past the nesting depth
(pass) hosted placement > refuses a duplicate name; retire is idempotent
(pass) hosted child hostnames > derive a single-label child URL from the parent host
(pass) hosted child hostnames > keep the parent's scheme and port: a child of an http parent is not sent to https
(pass) hosted child hostnames > the parent's origin: its canonical url, else the plain host and port it serves on
(pass) hosted child hostnames > parse a child name from Host
(pass) a hosted child's app label > is bounded by one DNS label, not by the child's name
 13 pass
 0 fail
Ran 13 tests across 2 files.

Every spawn suite and every suite that spawns, places or opens a child (test/spawn*.test.ts, client-home, client-list, first-login, following, new-forge, tui-newforge, usage-count-child, apps-adversarial, hollow, openapi; 27 files): 311 tests, 0 fail.

Validation

pipeline/validate on a7daea4d: baseline in 479s — bun 1.3.14, CI-shaped env (scrubbed vars, runner PATH), exact check.yml shard + forge.json commands.

step result time
bun install --frozen-lockfile ok (exit 0) 0s
forge.json check.fast BASELINE (exit 1) 43s
check.yml gate (1) pull_request BASELINE (exit 1) 13s
check.yml gate (2) pull_request ok (exit 0) 238s
check.yml gate (3) pull_request ok (exit 0) 185s
  • ⚠️ node v20.19.2 here, v22 on CI (tsc runs under node)
  • ⚠️ forge.json check.fast stopped early at a BASELINE failure (fail-fast): its later files were not run in this step
  • ⚠️ check.yml gate (1) pull_request stopped early at a BASELINE failure (fail-fast): its later files were not run in this step
test output (failures, totals)
▶ bun install --frozen-lockfile
  ok  exit 0  0s
▶ forge.json check.fast
(fail) egress, reported: an undeclared public host goes through and is counted; a private one never does > an undeclared public host is let through and counted [1.54ms] [shard 8]
Ran 407 tests across 38 files in 30.8s; 1 failed.
classify: reran test/contain.test.ts test/pull-variants.test.ts test/web.test.ts  alone on origin/main 18917b93 and on this branch
  BASELINE  exit 1  43s
▶ check.yml gate (1) pull_request
(fail) egress, reported: an undeclared public host goes through and is counted; a private one never does > an undeclared public host is let through and counted [1.20ms] [shard 2]
Ran 65 tests across 2 files in 11.9s; 1 failed.
classify: reran test/contain.test.ts  alone on origin/main 18917b93 and on this branch
  BASELINE  exit 1  13s
▶ check.yml gate (2) pull_request
Ran 1260 tests across 128 files in 238.1s (128 shards).
  ok  exit 0  238s
▶ check.yml gate (3) pull_request
Ran 1351 tests across 128 files in 184.9s (128 shards).
  ok  exit 0  185s
`childPublicUrl` built every child's address as `https://<name>--<host>` from a host with its port stripped. Behind the platform (https on 443) that is right. For any parent on plain http or another port it was wrong in two ways: the child was told an https address nothing serves, and it lost the port. Its config `url`, the ledger, `/spawn` answers, the claim link, and its apps' `FORGE_URL` all pointed nowhere. - `parentOrigin(config)` in `spawn/placement.ts` names the parent's public origin once: its canonical `url` when it has one, otherwise `http://<host>:<port>` as `main.ts` names itself. It replaces `parentHostOf` in `actions/spawn.ts`, which was a copy of `childHost`. - `childPublicUrl(name, parent)` takes that origin and keeps its scheme and port (a scheme's default port drops, as `URL` does). A bare host still means https with no port, so the platform's `https://<name>--forge.open-platform.sh` and every existing caller and test are unchanged. - Every place a child's address is made uses it: the spawn door's placement and ledger seeding (`door.ts`), `liveChildUrl`, the child app's own `url` (`host.ts`), and its apps' forge URL (`child-apps.ts`). Matching a Host to a child (`parseHostedChildName`) already ignored the port and is unchanged, as is `childHost` for those host-only uses. Tests: `childPublicUrl` and `parentOrigin` across http/https, explicit, default and missing ports. End to end, a parent at `http://forge.lan:8080` spawns `kid` and the spawn result, `liveChildUrl`, `childUrlOf` and the opened child's own `config.url` are all `http://kid--forge.lan:8080`. On main the same parent gives `https://kid--forge.lan`. Overlap: #2419 (a new or spawned forge starts with signup = invite) changes the child's signup setting, not its address. No open pull touches these files for it, so neither rebases on the other. #41 (first-admin) does not touch placement. Note: the single-label shape is unchanged, so a child of `http://localhost:3000` is `http://kid--localhost:3000`, which needs a hosts entry or wildcard DNS to resolve, the same as before but now on the right scheme and port. ## Tests run by hand (bun 1.3.14, CI env) `bun install --frozen-lockfile`, `bun scripts/gate.ts`, `tsc --noEmit` and `bun scripts/hollow.ts` are clean. Before, on main: `childPublicUrl("kid", "forge.lan:8080")` gave `https://kid--forge.lan`. `bun test test/spawn-placement.test.ts test/spawn-host.test.ts`: ``` (pass) hosted child Host dispatch > parent Host still serves the parent (pass) hosted child Host dispatch > child Host reaches the child's forge (pass) hosted child Host dispatch > a child's requests are logged in the child's data, never the parent's (pass) a parent on plain http with a port > its child lives at http, on the parent's port, wherever its address is read (pass) hosted placement > places a child as a subdomain of the parent, one agent, no SSH (pass) hosted placement > refuses beyond the children limit, and retiring frees a slot (pass) hosted placement > refuses past the nesting depth (pass) hosted placement > refuses a duplicate name; retire is idempotent (pass) hosted child hostnames > derive a single-label child URL from the parent host (pass) hosted child hostnames > keep the parent's scheme and port: a child of an http parent is not sent to https (pass) hosted child hostnames > the parent's origin: its canonical url, else the plain host and port it serves on (pass) hosted child hostnames > parse a child name from Host (pass) a hosted child's app label > is bounded by one DNS label, not by the child's name 13 pass 0 fail Ran 13 tests across 2 files. ``` Every spawn suite and every suite that spawns, places or opens a child (`test/spawn*.test.ts`, client-home, client-list, first-login, following, new-forge, tui-newforge, usage-count-child, apps-adversarial, hollow, openapi; 27 files): 311 tests, 0 fail. <!-- pipeline:validation --> ### Validation `pipeline/validate` on `a7daea4d`: **baseline** in 479s — bun 1.3.14, CI-shaped env (scrubbed vars, runner PATH), exact check.yml shard + forge.json commands. | step | result | time | |---|---|---| | `bun install --frozen-lockfile` | ok (exit 0) | 0s | | `forge.json check.fast` | BASELINE (exit 1) | 43s | | `check.yml gate (1) pull_request` | BASELINE (exit 1) | 13s | | `check.yml gate (2) pull_request` | ok (exit 0) | 238s | | `check.yml gate (3) pull_request` | ok (exit 0) | 185s | - ⚠️ node v20.19.2 here, v22 on CI (tsc runs under node) - ⚠️ forge.json check.fast stopped early at a BASELINE failure (fail-fast): its later files were not run in this step - ⚠️ check.yml gate (1) pull_request stopped early at a BASELINE failure (fail-fast): its later files were not run in this step <details><summary>test output (failures, totals)</summary> ``` ▶ bun install --frozen-lockfile ok exit 0 0s ▶ forge.json check.fast (fail) egress, reported: an undeclared public host goes through and is counted; a private one never does > an undeclared public host is let through and counted [1.54ms] [shard 8] Ran 407 tests across 38 files in 30.8s; 1 failed. classify: reran test/contain.test.ts test/pull-variants.test.ts test/web.test.ts alone on origin/main 18917b93 and on this branch BASELINE exit 1 43s ▶ check.yml gate (1) pull_request (fail) egress, reported: an undeclared public host goes through and is counted; a private one never does > an undeclared public host is let through and counted [1.20ms] [shard 2] Ran 65 tests across 2 files in 11.9s; 1 failed. classify: reran test/contain.test.ts alone on origin/main 18917b93 and on this branch BASELINE exit 1 13s ▶ check.yml gate (2) pull_request Ran 1260 tests across 128 files in 238.1s (128 shards). ok exit 0 238s ▶ check.yml gate (3) pull_request Ran 1351 tests across 128 files in 184.9s (128 shards). ok exit 0 185s ``` </details>
A hosted child keeps its parent's scheme and port: a child of http://forge.lan:8080 is http://kid--forge.lan:8080, not https://kid--forge.lan
Some checks failed
preview / preview (pull_request) Has been skipped
check / gate (3) (pull_request) Successful in 3m43s
check / gate (2) (pull_request) Failing after 6m44s
check / gate (1) (pull_request) Successful in 10m23s
check / check (pull_request) Failing after 1s
18910174d2
`childPublicUrl` built every child's address as `https://<name>--<host>`
from a host with its port stripped. Behind the platform (https on 443) that
is right. For any parent on plain http or another port it was wrong in two
ways: the child was told an https address nothing serves, and it lost the
port. Its config `url`, the ledger, `/spawn` answers, the claim link, and
its apps' `FORGE_URL` all pointed nowhere.

- `parentOrigin(config)` in `spawn/placement.ts` names the parent's public
  origin once: its canonical `url` when it has one, otherwise
  `http://<host>:<port>` as `main.ts` names itself. It replaces
  `parentHostOf` in `actions/spawn.ts`, which was a copy of `childHost`.
- `childPublicUrl(name, parent)` takes that origin and keeps its scheme and
  port (a scheme's default port drops, as `URL` does). A bare host still means
  https with no port, so the platform's `https://<name>--forge.open-platform.sh`
  and every existing caller and test are unchanged.
- Every place a child's address is made uses it: the spawn door's placement
  and ledger seeding (`door.ts`), `liveChildUrl`, the child app's own `url`
  (`host.ts`), and its apps' forge URL (`child-apps.ts`). Matching a Host to a
  child (`parseHostedChildName`) already ignored the port and is unchanged, as
  is `childHost` for those host-only uses.

Tests: `childPublicUrl` and `parentOrigin` across http/https, explicit,
default and missing ports. End to end, a parent at `http://forge.lan:8080`
spawns `kid` and the spawn result, `liveChildUrl`, `childUrlOf` and the
opened child's own `config.url` are all `http://kid--forge.lan:8080`. On main
the same parent gives `https://kid--forge.lan`.

Overlap: #2419 (a new or spawned forge starts with signup = invite) changes
the child's signup setting, not its address. No open pull touches these files
for it, so neither rebases on the other. #41 (first-admin) does not touch
placement.

Note: the single-label shape is unchanged, so a child of `http://localhost:3000`
is `http://kid--localhost:3000`, which needs a hosts entry or wildcard DNS to
resolve, the same as before but now on the right scheme and port.
Merge branch 'main' into fix/child-public-url-scheme-port
Some checks failed
check / gate (1) (pull_request) Has been cancelled
check / gate (2) (pull_request) Has been cancelled
check / gate (3) (pull_request) Has been cancelled
check / check (pull_request) Has been cancelled
preview / preview (pull_request) Has been cancelled
c111567594
Merge branch 'main' into fix/child-public-url-scheme-port
Some checks failed
check / gate (2) (pull_request) Has been cancelled
check / gate (3) (pull_request) Has been cancelled
check / check (pull_request) Has been cancelled
check / gate (1) (pull_request) Has been cancelled
preview / preview (pull_request) Has been skipped
1e104c7a5b
The plain-http spawn test's account says its origin, now that createUser takes one (#43)
All checks were successful
check / gate (2) (pull_request) Successful in 4m59s
preview / preview (pull_request) Has been skipped
check / gate (3) (pull_request) Successful in 3m53s
check / gate (1) (pull_request) Successful in 10m47s
check / check (pull_request) Successful in 0s
f74a629d51
Merge remote-tracking branch 'origin/main' into fix/child-public-url-scheme-port
All checks were successful
check / gate (3) (pull_request) Successful in 3m53s
check / gate (1) (pull_request) Successful in 10m54s
check / gate (2) (pull_request) Successful in 17m5s
check / check (pull_request) Successful in 1s
preview / preview (pull_request) Has been skipped
a7daea4dd3
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
trevato-apps/forge!47
No description provided.