Repository navigation
P6: the gateway's GET /api/internal/hosted/modules/<id>/ensure, answered by the module core with the resident's wire contract; internal namespace same-origin even on GET - #79
Conversation
compilePlatform, runnable and linuxHost are shared by the module lifecycle tests and the gateway ensure tests of slice P6; runnable takes the declared start check's body (passing by default), otherwise unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ven on GET (launchpad-parity B5) /api/internal/* is where the gateway's ensure lives; a GET there may start an app, so it is a state-changing request: Sec-Fetch-Site same-origin and Origin equal to the entry's external origin, as in the resident (request-trust-lib.mjs:72-83). The Host rule stays one for every route: the entry's Launchpad hostname, which the gateway sends on ensure after the switch (F22 point 3); a loopback Host remains host-mismatch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…red by the module core (launchpad-parity B5) - The path, method and statuses of the Machines gateway's readiness subrequest (workloads/workspace-vm/ingress.ts:113-159) and of the resident's route (launchpad/src/server.mjs:1377): 204 with no body when the default app reports healthy, 503 while it is not (yet), 404 when the id names no runnable default app, 409 for an ambiguous id or a refused start; only GET, only in hosted mode. - ensure in src/modules/module-operations.ts: the exact lazurio.module.v1 id across the catalog's Organizations (every Organization listing it counts; two or more is module-ambiguous with the candidates), then the catalog's rules as for lazurio module; the default app only, never another declared app. - Sec-Fetch-Mode navigate or absent starts a stopped app; any other mode or a Sec-WebSocket-Key only reports (the resident's hostedRequestMayStartApp). - Waits at most 20 s for health, reading the status every 250 ms (the resident's openHealthyWaitMs/PollMs), then answers 503 and the start goes on (outcome start-pending). Requests that arrive during a start of the same app join it: a slow declared check runs once. - Refusals keep the operation that refused: ensure (resolution), status or start (the lifecycle); bodies are the lazurio module answers and carry no address. isModuleId in hosted-entry.ts is the gateway's id rule, shared with moduleLabel. - tests/launchpad-ensure.test.ts: the gateway's exact subrequest and every header dropped or changed, the Linux path over the in-memory user manager, the session path with a real app, timing and the joined start, a workstation Launchpad without the route. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…route of the hosted admission (hosted-entry.md) The request as the gateway makes it, the admission and why the Launchpad's own Host is the one rule, which app starts and when, the timing, the answer table with what the gateway shows for each status, the deviations from the resident (409 for an ambiguous id and a refused start, no dependency installation) and what was verified. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
agentrozjedemeai
left a comment
There was a problem hiding this comment.
@immakermatty APPROVED for exact head bb6f2fd (P6).
Reviewed the complete base-to-head diff and the pinned Machines gateway ab84f38 (ingress.ts:124-157; gateway-catalog.py:139-148,235) against the resident request-trust and ensure handler. 204/503/404/409/401 map to the gateway outcomes; the current gateway still sends a loopback Host and requires the declared M2 Host change before rollout. The core resolves the exact id across the catalog, refuses ambiguous/unrunnable/default-invalid modules, selects only the default app, and joins concurrent starts. Internal GET requires the recorded Host, same-origin headers and validated cookie; other GET/browser routes retain their previous rule. No new production URL is composed from a hostname convention (the existing recorded template remains the source).
Validation on this detached head: bun --smol test --max-concurrency 1 tests/launchpad-ensure.test.ts tests/launchpad-hosted-trust.test.ts tests/module-operations.test.ts 12 pass/0 fail; bun run check 901 pass/4 skip/0 fail, lint, typecheck, public guard, proof build/smoke green; git diff --check clean. Both required GitHub checks (macos-14, ubuntu-24.04) SUCCESS on this SHA, zero unresolved threads. Not tested against a real Caddy/oauth2-proxy/systemd deployment; C.5 remains. Chunked session cookie gap is tracked separately in #76. No merge performed.
Why
Slice P6 of the Launchpad parity program (#65 B5; root decision 0167): on a hosted Machine the workspace gateway asks the Launchpad to make a module's default app run when a browser opens the module's hostname. The Platform Launchpad must answer that exactly as the resident does, so the Machines switch (M2) needs only the one Host line change already decided (F22 point 3).
What changes
GET /api/internal/hosted/modules/<id>/ensure, hosted mode only (a workstation Launchpad answers 404), answered by the module core of P5: lazurio module start|stop|status|logs and /api/modules — the module lifecycle over the catalog, systemd user units with journal logs, hosted links only from the recorded entry (base-instructions-11) #73 with one new operationensure: the exactlazurio.module.v1id the gateway passes (never the label), looked up across every Organization of the catalog; default app only; start if not running and wait up to 20 s polling every 250 ms, as the resident (runtime-lib.mjs). Wire contract taken from the Machines gateway source atab84f38(ingress.ts:124-157,gateway-catalog.py:139-148,235) and the resident handler (launchpad/src/server.mjs:1377-1401,hosted-readiness-lib.mjs,request-trust-lib.mjs:72-83): bodyless GET withOrigin+Sec-Fetch-Site: same-origin+ the session cookie; 204 healthy, 503 not healthy yet / report-only / closing / busy (the gateway shows "starting" and reloads every 2 s), 404 unknown or invalid id or a module that cannot run, 409 ambiguous id or a refused start (reason in alazurio module-style body; the gateway shows bodies never), 401 admission refused./api/internal/*needs the same-origin rule even on GET, as in the resident; no fragment token, no loopback exception (the browser cannot reach the path — the gateway 404s it on every public hostname).tests/launchpad-ensure.test.ts: the gateway's exact request with each header dropped or changed, Linux path on the fake service manager, session path with a real app, timing and the joined start, workstation 404, ambiguous/unknown/non-executable/non-default cases. Docs:launchpad-development.md"Gateway ensure",hosted-entry.mdinternal route.Deliberate differences from the resident (all per B5)
Ambiguous id → 409 (resident: 404); a refused start → 409 with the reason (resident: 503 → the browser reloaded forever); Personalspace modules → 404 (the Platform catalog covers Organizations; B11); split session cookies are refused — a known gap, issue #76.
Two things to know for M2
Host: 127.0.0.1:<port>on ensure (ingress.ts:130) → 401host-mismatch→ the gateway shows 502. M2 changes that oneheader_up Hostline (C.2 step 7).prerequisites-not-readyuntil B3prepare/open.Verified
Author of the slice:
bun run check901 pass. Thread owner'sbun run checkon bb6f2fd: 901 pass, 0 fail, exit 0. Only a real hosted Machine can prove Caddy's exact headers, the absence of a proxy timeout for the 20 s wait, real systemd timing and the per-request latency of ensure.🤖 Generated with Claude Code