From c8a9bcf9466a1dfb354f96304560966d8155ea6c Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Sat, 24 Oct 2026 17:13:28 -0500 Subject: [PATCH] docs: P3 amendment for pi's GLM and GPT routes, and the Case 2 proxy unit brief (mos-1616) --- .../2026-10-24_case2-proxy-unit-brief.md | 82 +++++++++++++++++++ .../2026-10-24_p3-pi-routes-amendment.md | 76 +++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 docs/plans/2026-10-24_case2-proxy-unit-brief.md create mode 100644 docs/plans/2026-10-24_p3-pi-routes-amendment.md diff --git a/docs/plans/2026-10-24_case2-proxy-unit-brief.md b/docs/plans/2026-10-24_case2-proxy-unit-brief.md new file mode 100644 index 000000000..eddcec475 --- /dev/null +++ b/docs/plans/2026-10-24_case2-proxy-unit-brief.md @@ -0,0 +1,82 @@ +# Brief: the Case 2 proxy unit, so a hub installs P1's proxy unit + +Written 2026-10-24 by Sage for Mos's mos-1616 ruling 1, which approved this row. Mos ruled that Sage +approves the brief if it only adds P1 item 7's unit to `scripts/case2-install.sh` and +`scripts/case2-unit.sh`: the same unit text, the same sha256 refusal and the Wants/After lines. Anything +beyond that goes to Mos. Read the rows doc's P1 brief (`docs/plans/2026-10-24_model-proxy-rows.md`, P1 +item 7) and row 209's brief (`docs/plans/2026-10-24_row209-hub-54-window.md`, entry check 2) first. The +rows doc's "Rules for every row" apply here unchanged. Line numbers are at origin/refactor 58b2bbb3a, and +P1's paths are at its round 2 candidate magica/row329 86cb5c221. Check both once P1 lands. + +## Brief: Case 2 proxy unit in case2-unit.sh and case2-install.sh + +**Why.** A Case 2 hub (.54) installs through `scripts/case2-install.sh` (as the stack user) and +`scripts/case2-unit.sh` (as root, system units). P1 adds `mosaic-proxy.service` only to the user-unit path +(`scripts/instance.sh`, `scripts/proxy-service.sh`, `scripts/bus-service.sh`). `case2-unit.sh` writes the +bus and Console units and nothing else (:3-5, :56-57), and its bus unit has no `Wants=` or `After=` on a +proxy. Without this row, row 209 phase A can't run the proxy on .54. + +**What ships.** + +1. **The proxy unit, from P1's template.** `case2-unit.sh` renders a third system unit, + `mosaic-proxy.service`, from P1's `packages/cli/systemd/mosaic-proxy.service.in`, which this row doesn't + change. + - It uses the same render as the bus (`render()` :435): the copy for `@REPO@`, the unit PATH, `--node` + for `@NODE@`, and `@CONFIG_ENV@` deleted, because `case2-unit.sh` renders the default instance only. + - It gets the same added lines as the bus: `User=`, `Group=` and the four hardening lines, with no + capabilities and no `EnvironmentFile=`. `WantedBy=multi-user.target` replaces `default.target`, as for + the other two units. + - `check_unit` gets a `proxy` kind. Its fixed lines are those of P1's template: `ExecStart=` exactly + ` /packages/model-proxy/src/run.mjs`, and `NoNewPrivileges=yes`. Every check that applies to + both units today applies to this one too. + - A new test seam `CASE2_PROXY_TEMPLATE`, like `CASE2_BUS_TEMPLATE` (:44, :54). +2. **The sha256 refusal is P1's.** The unit starts P1's `run.mjs`, which checks the binary against PIN + before it spawns it. This row adds no second check and no other way to start the proxy. A test runs the + rendered `ExecStart=` in a temp root, with a made-up config and a binary that doesn't match PIN, and + checks P1's refusal code and exit status. +3. **Only when the instance has a proxy.** Root passes a new bare flag `--proxy` when the instance's config + has a `proxy` section. + - Without it, the render is byte for byte what it is today, and no proxy unit is written. + - With it, the bus unit gets `Wants=mosaic-proxy.service` and `After=mosaic-proxy.service`, in the place + where P1's `bus-service.sh` puts them, right after `Description=`. The Console unit doesn't change. + - Root still never imports copy code or reads the config (:16-21). A wrong flag fails closed: with no + proxy section, P1's runner refuses and the unit stops. With a proxy section and no flag, the proxy + doesn't run and P2's launch preflight refuses. BUILD.md checks both against the landed P1 and P2 code + and names the refusal codes. +4. **Re-install and start.** + - With `--proxy`, the three installed units must name one copy, and each must equal the render once its + copy id is the new one, the same rule as now (:557-571). + - Without `--proxy`, an installed `mosaic-proxy.service` refuses the run. The script never deletes a + unit file. + - Enable all of them, then start the proxy, then the bus, then the Console. A unit that was already + running is reported the way the bus and Console are now (:576-610), and the message names all three as + the units to restart together. +5. **case2-install.sh.** Only its closing "Next:" line (:231) changes, to tell root to add `--proxy` when the + config has a `proxy` section. If the builder finds that line needs no change, BUILD.md says why and the + file stays as it is. + +**Out of scope.** P1's template, runner and PIN; the user-unit path; any named-instance render; reading the +config from root; the proxy's config or key; `mosaic proxy init`; any live host, unit or `/etc` write +outside a test's temp root. + +**Tests**, in `packages/cli/tests/case2-unit.test.mjs` (and `case2-install.test.mjs` if item 5 changes the +script): +- with no `--proxy`, the bus and Console renders equal today's, byte for byte; +- with `--proxy`, the proxy unit's content, and the bus unit's `Wants=` and `After=` lines; +- each `check_unit` refusal on the proxy kind, by its message; +- P1's PIN refusal through the rendered `ExecStart=`; +- a re-install whose three units name two copies refuses; +- an installed proxy unit with no `--proxy` refuses and deletes nothing; +- the start order through the `CASE2_SYSTEMCTL` seam's log. + +**Gate.** +- The builder is a new Codex gpt-6.1-sol thread and worktree off refactor (Q246, mos-1542). The row starts + when P1 (row 329) lands, and the builder rebases on it before any run. +- Boyd gives the first Codex review. Webby gives the Claude review of the root-run unit boundary and the + landing. Sage runs the landing gate. +- The run list for every whole-file run comes from imports and goes in the builder's report before the run. + `packages/cli/tests/case1-accept.test.mjs` stays off unless Mos approves (mos-1612). +- Every touched test file runs whole. Every package suite but queue, plus every `scripts/test-*.sh`, is + green on Sage's landing gate. +- Named mutants: the bus unit loses its `After=` line under `--proxy`; the proxy unit's `ExecStart=` skips + `run.mjs` and starts the binary directly. diff --git a/docs/plans/2026-10-24_p3-pi-routes-amendment.md b/docs/plans/2026-10-24_p3-pi-routes-amendment.md new file mode 100644 index 000000000..079debd56 --- /dev/null +++ b/docs/plans/2026-10-24_p3-pi-routes-amendment.md @@ -0,0 +1,76 @@ +# Brief amendment: row 332 (P3), pi's GLM and GPT routes through the proxy + +Written 2026-10-24 by Sage. Mos approved items 4a to 4c in mos-1616 with three conditions, which are written +into the items below. Item 4d is settled there too. This came out of mos-1615 (DECISIONS Q253): on .54 Bean +runs GLM through a Z.ai key and Chet runs GPT through his ChatGPT/Codex login, both through the proxy. The +P3 brief in the rows doc (`docs/plans/2026-10-24_model-proxy-rows.md`, "Brief: P3 harness config from the +proxy", item 4) proves only the Anthropic route. Row 96 proved only that route for pi, too +(`agents/researcher/work/i1-proxy-test/REPORT.md`, S2-pi-A). + +The rows doc isn't edited, because rows 329 to 333 pin its blob. This file replaces one line of P3 item 4, +"A non-Anthropic route names that provider's base URL in the same file.", and adds to P3's Tests and Gate. +Everything else in P3 stands. Line numbers are at origin/refactor 58b2bbb3a. Check them before you edit. + +## Brief: P3 amendment, pi's GLM and GPT routes + +**What pi 0.85.1 does today.** These facts come from the pinned pi's docs and its built-in provider data. +- `models.json` providers take `baseUrl`, `api`, `apiKey` and `models`. A built-in provider can be + overridden with a `baseUrl`. +- `apiKey` takes a literal, a `$ENV` reference, or `!command`, which pi runs as a shell command. +- The built-in `zai` provider is `openai-completions` at `https://api.z.ai/api/coding/paas/v4`, with z.ai + compat fields (`thinkingFormat: "zai"`, `zaiToolStream`). +- The built-in `openai-codex` provider speaks `openai-codex-responses` to chatgpt.com/backend-api with pi's + own OAuth, so it can't be pointed at the proxy. + +**4a. One entry per route.** `/home/.pi/agent/models.json` gets one provider entry for each route the +agent's `launch.models` names, each at the proxy's address from P3 item 1. +- Anthropic: as P3 item 4 says now, `anthropic-messages` with no `/v1`. +- GLM: `openai-completions` at `http://127.0.0.1:/v1`, with the model id that the proxy's `/v1/models` + lists for the Z.ai key. +- GPT through the Codex login: a provider of the stack's own, never `openai-codex`, at + `http://127.0.0.1:/v1`, with the model ids that the proxy lists for the Codex credential. + - The builder picks `openai-responses` or `openai-completions` once, at build time, from what the pinned + proxy passes for pi's requests. The harness has no fallback at run time: it writes the one shape the + build chose. + - BUILD.md records the choice and its evidence: the pinned proxy version, the requests tried on each shape + and what came back. + +**4b. The key goes by reference.** +- Every entry's `apiKey` is `"$ANTHROPIC_API_KEY"`, the reference P3 item 4 already sets. `ENV_ALLOW` + (`packages/seat/src/session.mjs`:242) doesn't change. +- In proxy mode that variable holds only the session's own proxy key, because P3 item 2 drops the instance's + provider variables. +- `models.json` never holds a key value, and no value in it starts with `!`. + +**4c. Tests.** These add to P3's Tests list: +- the `models.json` content for each of the three routes; +- every `apiKey` in `models.json` is the `$ANTHROPIC_API_KEY` reference; +- no key value appears anywhere in the file; +- no value in the file starts with `!`; +- an offline pi turn through the pinned proxy under `unshare -rn` for the GLM route, against a stand-in + upstream, the way row 96 and P1's acceptance run did. It runs from a script outside the gate's + `scripts/test-*.sh` set, and its output goes in BUILD.md as plain text. + +The Codex route can't run offline without a real auth file, and no agent touches one. Its first real turn is +row 209 phase A step 7 (`docs/plans/2026-10-24_row209-hub-54-window.md`). BUILD.md says so. + +BUILD.md also records whether the z.ai compat fields survive through the proxy, or whether the proxy drops +them, from the offline GLM turn. + +**4d. One proxy per user (settled, mos-1616).** Unless P2 item 7 finds that the pinned proxy can limit a +client key to some models, Bean's key can reach Chet's ChatGPT login through the same proxy, and Chet's key +can reach Bean's Z.ai key. Mos accepted that for .54. Q249 sets one proxy per user and their agents, so both +agents and both accounts on .54 are Melanie's. If P1 found no per-key model limit, the bundle's model choice +stays the guard. If P1 found one, P2 narrows each key as P2 item 7 says. This row changes neither. + +**Gate additions.** One more named mutant: `models.json` gets the key value instead of the +`$ANTHROPIC_API_KEY` reference. The 4c tests must kill it. The rest of P3's Gate stands: Gladstone's first +Codex review, Dewey's landing review, every touched test file whole, every package suite but queue plus +every `scripts/test-*.sh` green on Sage's landing gate, and the mutant where proxy mode still passes +`CLAUDE_CODE_OAUTH_TOKEN`. + +**Start and landing (mos-1616 ruling 5).** P3 may start now on a new Codex gpt-6.1-sol thread and worktree, +building against P2's interface `{ baseUrl, key }` (P3 item 1). It lands only after P1 (row 329) and P2 +(row 331) land, and after its own gate (mos-1351). It rebases on both before that gate. The run list for +every whole-file run comes from imports and goes in the builder's report before the run. +`packages/cli/tests/case1-accept.test.mjs` stays off unless Mos approves (mos-1612).