Compare commits
29
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7c38a6aa5 | ||
|
|
ef0939e351 | ||
|
|
57bcdebf97 | ||
|
|
1c7b32bd9d | ||
|
|
9563fbc8b8 | ||
|
|
1da28a7bd1 | ||
|
|
f1e8cda074 | ||
|
|
e9fc105ac9 | ||
|
|
a5330386c4 | ||
|
|
1e47b09198 | ||
|
|
f4e2cb804f | ||
|
|
56701f715d | ||
|
|
b285e581ea | ||
|
|
efe3e46973 | ||
|
|
9e2689dffb | ||
|
|
b24d917ec9 | ||
|
|
61ebf2c34d | ||
|
|
d1f6f2b199 | ||
|
|
c65fb34ee6 | ||
|
|
94cde633e2 | ||
|
|
669cdf42c4 | ||
|
|
98614187ce | ||
|
|
c8a9bcf946 | ||
|
|
58b2bbb3a5 | ||
|
|
95d4991d20 | ||
|
|
966ee31ea8 | ||
|
|
696ea3a3af | ||
|
|
0ce22dde65 | ||
|
|
36056a5dd9 |
No files matched your search
@@ -5567,6 +5567,14 @@ claim. Receipts and limitations in agents/lena/work/queue-296/BUILD.md and run-r
|
||||
|
||||
2026-10-24 | Fethry | Row328 sage4214/Mos1599 option A GREEN on06c57a7db:8x50 M1-50 produced374 passes/26 expected library-sync-busy assertion failures, exact restore/full diff+porcelain clean, same8x50 fixed400/400, whole library-credential6/6, no skips/cancels or unexpected errors/reruns. Node nice16/19 observed after absolute19; loads and801 receipts retained. Old test staysapproved+4/-3, assertions/product unchanged. Own process sweep empty. Evidence push/slot return and owner review move next; Boyd/Dewey then Sage full bus/scripts gate pending.
|
||||
|
||||
2026-10-24T19:44:50Z | Sage | Row 257 (#1762, Q198 S1 pressure parser and rules evaluator, Morgana, Filbert) LANDED 73692e1bb, pushed 17:41:58Z 10-24. Before: refactor at 487500a16. After: Morgana's four commits cherry-picked onto 487500a16 with unchanged patches; the 9 packages/supervisor paths match Filbert's round 2 manifest b176a88f. Filbert r1 c30188 (N1/N2 fixed in r2), r2 APPROVE c30190 rev 3037, request c30189 posted by Sage. Slot result on 1261fa2d8: three files whole 15/0, 29/0, 1/0, supervisor 45/0, M1-M8 killed by assertion. Sage gate: first run RED 17:20:42Z, webui 441/1 on the old row278 sign-out test (timeout at load about 8.5, the row touches no webui path); the webui-only rerun 442/0 was not counted; the full rerun was GREEN 17:41:11Z: bus 998/0, business 283/0, webui 442/0, supervisor 45/0, cli 989 plus the allowed FED R7 skip, all 12 scripts rc 0. Correction: the row278 timing red is an old test, so any change to it goes to Mos (mos-1272). Follow-up: Morgana's carry (sage-3003, CLEAR_HELD entry and a clear-path hold test) binds the row that wires a Q198 hold into a launch refusal. Not re-pinned.
|
||||
|
||||
2026-10-24T19:44:50Z | Sage | Row 266 (#1771, C2-10c Console room view, Fethry on Codex, Boyd and Dewey) LANDED 3e5041b43, pushed 18:00:49Z 10-24. Before: refactor at 73692e1bb. After: Fethry's four commits cherry-picked, every patch-id equal to the seat branch; tree is manifest 5f816042 with no other change. Boyd APPROVE c31609 rev 3983, Dewey APPROVE c31610 rev 3985 (N1-N3 non-blocking), request c31608 posted by Sage. Sage gate GREEN first run 18:00:08Z: bus 1000/0, business 283/0, webui 458/0, supervisor 45/0, cli 989 plus the allowed FED R7 skip, all 12 scripts rc 0. Dewey's N2, the old T9 library-sync-busy race, went to Mos and became row 328. Not re-pinned.
|
||||
|
||||
2026-10-24T19:44:50Z | Sage | Row 224 (#1725, C2-05b, Della on Codex, Dewey) LANDED 5e77cdd09, pushed 19:18Z 10-24. Before: refactor at 7fe32d722. After: Della's 13 commits cherry-picked onto 7fe32d722. Sage gate GREEN 19:16:37Z on 27624aa66 (base 06f507dbc): bus 1011/0, business 283/0, harness 176/0, cli 990/0 plus the allowed FED R7 skip, webui 458/0, all scripts rc 0. Drift between the gated base and 7fe32d722 is only docs/plans/2026-10-24_model-proxy-rows.md, which no test reads (mos-1601 condition); manifest-r9 952482b4 matches the landed tree. Dewey APPROVE c31619 rev 3999. #1725 closed c31623; note rev 4015, done rev 4016. Correction: the first done move passed --evidence and was refused (rc 2, the round closes on its recorded verdicts); rerun without it. Follow-up: Dewey's N3, the old ask helper can't serve a library business, goes to a later row. Not re-pinned.
|
||||
|
||||
2026-10-24T19:44:50Z | Sage | Row 328 (#1826, Vikunja 622, old T9 timer race, Fethry on Codex, Boyd and Dewey) LANDED 8bb5b3c73, pushed 19:40Z 10-24. Before: refactor at 5e77cdd09. After: Fethry's 8 commits cherry-picked onto 5e77cdd09 (same patches); T9's second start passes intervalMs 2147483647 (mos-1594), test-only. Under 8x50 concurrent loops M1 (50 ms back) gave 26 expected failures and the fix 400/400. Boyd APPROVE c31621 rev 4002, Dewey APPROVE c31622 rev 4003. Sage gate GREEN 19:38:54Z on 8bb5b3c73 itself, no drift: bus 1011/0, business 283/0, harness 176/0, cli 990/0 plus the allowed FED R7 skip, webui 458/0, every scripts/test-*.sh rc 0 (Dewey N1). Manifest 1e010468. #1826 closed c31625, Vikunja 622 done (comment 1071), note rev 4019, done rev 4020. Not re-pinned.
|
||||
|
||||
2026-10-24 — Magica row329 P1 (#1827, Vikunja237): beginning pinned model-proxy fetch/init/client/unit build at 7fe32d722, under mos-1600/1601. Acceptance uses only fresh temp roots and unshare -rn; live instance and host proxy excluded. Evidence agents/magica/work/queue-329/BUILD.md.
|
||||
|
||||
2026-10-24 — Magica row329 P1 (#1827): final builder checks green: model-proxy61/61, bus1018/1018, CLI999pass+1listed federation skip, pinned-copy10/10, config55/55 and federation runner1/1. Final unit-config fix verified by two new tests and all direct CLI readers. Original F7 red, Mos1604 diagnostic and Mos1605 unchanged rerun are preserved; cause unknown, landing independent. Q250 drops the panel forward; PIN8.0.42/panel1.26.7 unchanged. Evidence agents/magica/work/queue-329/BUILD.md.
|
||||
@@ -5574,3 +5582,15 @@ claim. Receipts and limitations in agents/lena/work/queue-296/BUILD.md and run-r
|
||||
2026-10-24 — Magica row329 round2 (#1827): Boyd B1 tightens management keys to printable non-space ASCII before init creates state; new whole-tree refusal cases first reproduced the defect. Header check retained, no old assertions edited. N1 outer Docker count correction, N3 stale-lock recovery and N4 34-code coverage docs included. Cleared dependency-reader gate running; two stand-in over-selections disclosed and removed from the plan per sage-4277, F7 cause unknown. Evidence agents/magica/work/queue-329/BUILD.md.
|
||||
|
||||
2026-10-24 — Magica row329 R2 (#1827): B1 ASCII-only management key fix verified with complete-tree refusals and positive auth control; package66/66,30bus readers261/261,49CLI readers726/726,config55/55, no required skip/failure. N1 corrects R1 bus total to1019; N3 stale-lock guidance and N4 34-code inventory corrected. Two over-selected file runs are recorded separately, with no F7 inference; cause unknown/Vikunja625. Old502 test/helper files byte-identical, slot returned to Sage. Evidence agents/magica/work/queue-329/BUILD.md.
|
||||
2026-10-24T23:37:46Z | Sage | Row 329 (#1827, P1 pinned model proxy, Magica) LANDED 61ebf2c34, pushed 23:31:27Z 10-24. Before: refactor at 669cdf42c. After: Magica's 4 commits (94cde633e, c65fb34ee, d1f6f2b19, 61ebf2c34) cherry-picked onto 669cdf42c, diff vs the gated tree limited to drift paths, range-diff in scratch. Gate: one unchanged full landing gate in /mnt/storage/scratch/sage-r329l/wt (gate.sh 8b4978887 c8a9bcf94), started 23:08:12Z at load 14.35, GATE GREEN 23:29:14Z at load 8.67, no rerun, no test or timeout edit (mos-1634). Host note: two Codex workers cleaned T3 worktrees from about 23:14Z (mos-1639, Vikunja 632). Approvals: Dewey r2 c31643 rev 4047, Boyd r2 c31642 rev 4048. #1827 comment 31650, closed. Vikunja 237 comment 1109 (stays open for P2-P7); C4 P10 red filed as Vikunja 631. Queue: note rev 4063, done rev 4064, committed b24d917ec. DEFERRED: N2 and R2-N1 bound to row 331, N1 bound to P7. Overlap: row 284 (packages/bus/src/broker.mjs) and row 157 (scripts/mosaic-config.mjs) rebase onto 61ebf2c34 before their gates.
|
||||
## 2026-10-24 — row332 P3 proxy harness configuration: investigation/test placement
|
||||
|
||||
Fethry at c8a9bcf94, per Sage sage-4292 and mos-1616 pi-route amendment. Before: every managed session receives instance-wide provider credentials; harnesses have no transient per-session proxy context; pi preflight checks only its binary. Intended after: proxy-mode isolation, Claude proxy env, pi private run-local route config using environment references only, credential preflight, and business proxy account settings. Design/test plan: agents/fethry/work/queue-332/BUILD.md. Initial tests placed before production changes. No tests run: Sage CLEAR is required and has not arrived. No success/acceptance claim, live change, or dependency landing. Existing proxy issue number requested from Sage; not yet received.
|
||||
|
||||
## 2026-10-24 — row332 crash stop, documentation-only checkpoint
|
||||
|
||||
Fethry stopped under Mos mos-1630/Jason's T3 crash instruction. No production implementation, tests, probes, mutants or acceptance runs occurred; Sage CLEAR never arrived. Explicit route metadata per sage-4293 is recorded in agents/fethry/work/queue-332/BUILD.md; full state and resumption boundaries are in HANDOFF.md there. Safe crash checkpoint commits documentation/historical plain-text import manifest only; four unrun tests and the import-audit script remain uncommitted. No long jobs restarted, protected branch changes, merges, live changes or further thread messages.
|
||||
|
||||
2026-10-24 — Fethry row332 P3 UNRUN implementation checkpoint: optional proxy route per sage-4325 (default Anthropic), transient preflight/bundle/spawn context and serialization split, pi private model file/provider credential requirement. Existing initial RED evidence precedes source changes; only specifically authorized old fixtures updated. GPT wire API value PENDING until separate offline CLEAR/evidence; P2 seam remains injected context, no real proxy/auth access. Not GREEN, not accepted or landed. Details agents/fethry/work/queue-332/BUILD.md.
|
||||
|
||||
2026-10-24 — Fethry row332 P3 checkpoint ef0939e35: required explicit route restored per sage-4293/4328; openai-responses chosen by Sage sage-4331 as reasoning-based tie-break, not evidence-decided (both offline shapes passed). Rehashed pinned8.0.42 single active-tool isolated GLM turn confirms thinking/tool_stream survive to stand-in;bundle7/7,proxy-harness5/5,no skips. Real Codex auth proof remains row209 phaseA step7. P2 landing/rebase,mutants,reviews and full gate still pending; no landing claim. Plain evidence/limitations in agents/fethry/work/queue-332/BUILD.md.
|
||||
@@ -0,0 +1,299 @@
|
||||
# Row 332 — P3 harness config from the model proxy
|
||||
|
||||
## Assignment and boundaries
|
||||
|
||||
2026-10-24: Fethry, thread 65673bd9-3bfa-412b-85d5-9b364187d8b6, branch fethry/row332, baseline c8a9bcf94. Sage assignment sage-4292; accepted brief plus full pi-routes amendment c8a9bcf94 (mos-1616). Gladstone first review, Dewey landing review; landing waits for P1/329 and P2/331, rebase on both and Sage's gate. No tests before Sage's CLEAR. No live proxy, host credential directories, real proxy data, management key, cutover, policy edits, or Codex harness changes.
|
||||
|
||||
Queue next in canonical checkout returned `nothing` during assignment. Starting row with op fethry-332-start met Sage's queue update lock; retry through the queue only. Root npm ci --ignore-scripts --no-audit --no-fund completed in this worktree (128 packages); no canonical node_modules use.
|
||||
|
||||
## Design note (line numbers at c8a9bcf94)
|
||||
|
||||
- packages/seat/src/session.mjs:242-247 defines ENV_ALLOW; keep it unchanged. sessionEnv:256-262 gains optional proxy context and drops CLAUDE_CODE_OAUTH_TOKEN, every passed ANTHROPIC_* variable, OPENAI_API_KEY, OPENROUTER_API_KEY and ZAI_API_KEY before adding harness env. Non-proxy results retain current behavior exactly.
|
||||
- packages/plugins/harness/claude-code/index.mjs:56-67 preflight accepts the session proxy credential; env:119-126 sets ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN from that context.
|
||||
- packages/plugins/harness/pi/index.mjs:33-36 preflight requires its own provider credential or proxy key, never a Claude OAuth token. env:63-70 sets only the session's proxy key as ANTHROPIC_API_KEY and selects the generated provider. Fresh run-local home/.pi/agent/models.json is private (0600; parent directories 0700). Every apiKey is the literal reference $ANTHROPIC_API_KEY; recursively forbid values beginning with !. Nothing stores the key in models.json or a manifest.
|
||||
- packages/harness/src/bundle.mjs:313 buildBundle accepts transient proxy context; :379 harness.env call needs it plus run location and launch.models. Context must never be added to session metadata, source digests, or manifest. The run-local file regenerates on each launch.
|
||||
- packages/cli/src/launcher.mjs:391,400 preflight, :412 account checks, :529 bundle creation and :592 child env must agree on the same session credential. Coordinate its acquisition with P2; build/test now against the specified { baseUrl, key } stand-in, not a management client or a key-store implementation.
|
||||
|
||||
Exact business setting keys remain launch.models.<selection>.provider, launch.models.<selection>.model, launch.account.name and launch.account.credential (launch.mjs:12-24,81-89). Provider already accepts proxy. Proposed account credential sentinel is the literal proxy (add alongside existing uppercase environment-set syntax), with launch.account.name a human-readable id such as proxy. No new fields. Existing provider anthropic selects the Anthropic route, zai selects GLM, and proxy selects the stack-owned GPT route; send this interpretation to Sage before implementation because the brief doesn't specify route encoding for provider proxy.
|
||||
|
||||
## Build-time API choice and pinned binary
|
||||
|
||||
P1's committed magica/row329 has CLIProxyAPI 8.0.42 in packages/model-proxy/PIN. Use that commit's fetchPinned({dataRoot:<fresh temporary root>}) implementation, copied from Git objects to this seat's temp tooling directory with its relative dependencies and PIN, until P1 lands. Never inspect another seat's worktree/scratch. It fetches only pinned GitHub release assets and checks archive, executable and panel hashes. No production instance config is needed and nothing under a real dataRoot/proxy is read.
|
||||
|
||||
After CLEAR, try actual pinned pi requests with both openai-responses and openai-completions through that binary under unshare -rn, against stand-in upstreams with made-up credentials under 24 characters. Record version, API path, safe request fields, response status and turn result. Choose one shape once from those results and hard-code it; no runtime fallback. Real Codex OAuth cannot be simulated as a real authenticated turn: first real Codex turn remains row209 phase A step7. Distinguish the offline compatibility evidence from that deferred real-auth proof.
|
||||
|
||||
The separate offline GLM acceptance script (outside scripts/test-*.sh) uses pinned pi and proxy, checks the upstream sees the model id from /v1/models, and records whether thinkingFormat/zaiToolStream-derived fields survive translation. All configs/auth placeholders belong to its fresh temp root.
|
||||
|
||||
## Implementation and test sequence
|
||||
|
||||
1. Add tests first for env isolation, Claude proxy env and preflight, pi credential refusal and each generated route/file invariant, and business account acceptance. Do not run until CLEAR; first new-test red is reported afterward, old-test red or second red is reported before changing anything.
|
||||
2. Implement harness/bundle/env handling against P2's transient interface. Add launcher boundary integration once its exact P2 acquisition seam is agreed. Keep secrets out of session.json and bundle manifests.
|
||||
3. With CLEAR, run the offline API-shape probe and GLM turn, then whole touched test files. Before each whole-file run report the list derived by parsing imports and traversing local dependencies; include harness plug-in dynamic loading explicitly. packages/cli/tests/case1-accept.test.mjs remains excluded without Mos approval.
|
||||
4. Kill named mutants; restore production source and verify. Record exact output and nice. Use a fresh shell mktemp -d /mnt/storage/scratch/tmp/XXXXXX TMPDIR for each run, absolute nice 19, and timeout -k 10 for each scripts/test-*.sh. No hidden skips; Sage owns the package/script landing gate.
|
||||
5. After dependency landings, rebase, complete review and gate stages, update queue only through its CLI, and append session/build receipts. Push only fethry/* when ready for authorized review; no force-push or attribution trailers.
|
||||
|
||||
Planned tests:
|
||||
- Each forbidden environment variable is absent in proxy mode; all existing allowed env is identical without a proxy; two launches receive distinct own keys and no other agent's key.
|
||||
- Claude env has exactly the supplied proxy base URL/auth token; proxy credential satisfies preflight without host upstream credentials.
|
||||
- pi refuses no-session-credential for no key or Claude OAuth alone, accepts an own provider key and a proxy key.
|
||||
- models.json covers Anthropic anthropic-messages at root URL, GLM openai-completions at /v1, and a stack-owned GPT provider at /v1 with the chosen API; routes use configured model ids.
|
||||
- Every apiKey is $ANTHROPIC_API_KEY; no key value occurs in file; recursively no value starts with !; mode 0600 and private parents; next launch uses changed port/models/key reference with no stale content.
|
||||
- Business accepts provider proxy and account credential proxy while preserving existing valid and invalid account behavior.
|
||||
- Launcher/bundle integration keeps context transient and passes only the launch's own key.
|
||||
- Every new refusal code gets a separate test.
|
||||
- Offline GLM pinned-pi turn and z.ai compat observation; both GPT API-shape requests recorded with limitations.
|
||||
|
||||
Named gate mutants:
|
||||
1. Proxy mode still passes CLAUDE_CODE_OAUTH_TOKEN (original brief).
|
||||
2. models.json receives the key value instead of $ANTHROPIC_API_KEY (amendment).
|
||||
|
||||
No tests have run. API-shape choice is pending evidence, not claimed.
|
||||
|
||||
## Progress 22:27Z
|
||||
|
||||
Placed initial RED tests (not executed): packages/seat/tests/proxy-session-env.test.mjs and packages/plugins/tests/proxy-harness.test.mjs. These exercise real sessionEnv and loader-loaded harness exports. Production source remains untouched pending the first authorized RED run. Removed no assertions and ran no tests. Correct baseline bundle harness.env line is 376 (not 379 in the initial design note).
|
||||
|
||||
Sage reporting attempts fethry-0001 (including same-key retry) and fethry-0002 all returned `T3 GET /api/orchestration/threads/<sage> timed out after 30s`. Delivery is unconfirmed. Queue move retry also met a Sage queue update lock (sage-333-reviewers-1024a); no bypass.
|
||||
|
||||
Additional test-first placement: packages/business/tests/proxy-launch.test.mjs covers existing proxy model acceptance, proposed proxy account credential literal, preserved environment account forms, and rejection of mixed/literal credential strings. Pending Sage's route/account interpretation and CLEAR; no production edits or tests run.
|
||||
|
||||
## Import audit and queue status
|
||||
|
||||
The non-evaluating import audit uses Node vm.SourceTextModule.dependencySpecifiers, not a text-path grep, plus explicit computed-import edges from the plug-in loader to both harness modules. Source: import-run-list.mjs; output: IMPORT-RUN-LIST.txt (211 entries, including the explicitly EXCLUDED case1-accept file). This is a candidate impact list for the planned source changes, not an executed test list. Before a whole-file run, report the actual authorized subset to Sage. Parsing executes no repository modules and runs no tests. The first audit invocation had a path-depth setup error (ENOENT); corrected the audit's own repository root and regenerated successfully.
|
||||
|
||||
At 22:28:19Z the canonical start operation returned `queue: only the owner (sage) may start row 332`. Per assignment, wait at least two minutes before repeating this same op. No queue state was edited manually.
|
||||
|
||||
## Design correction after D9 re-read
|
||||
|
||||
The initial route-encoding proposal (provider proxy for GPT only) is not sufficient: design D9 explicitly defines provider proxy as any model through the instance proxy. Do not implement the earlier proposal. Two viable encodings need resolving before route-dependent tests/production: exact model-id lookup in pinned pi's provider catalogs (no new business fields, but refuses proxy-only aliases/unknown models), or explicit route metadata in launch.models (additive schema, needs Sage to confirm it is within the approved brief). Recommend exact catalog membership if all intended configured model ids are in pinned pi; otherwise ask Sage to define/approve an explicit route field. Never guess by model prefix or silently choose a wire API. Independent env/preflight tests already placed remain valid.
|
||||
|
||||
Reporting recovered: fethry-0004 was accepted by t3_send_message at approximately 22:29:51Z. It delivered the D9 route correction, route-encoding/P2-seam question, import-manifest location and no-tests status to Sage. Earlier failed-message deliveries remain unconfirmed.
|
||||
|
||||
22:32:18Z: The start retry issued at 22:30:40Z (over two minutes after first owner refusal) also exited 2: `queue: only the owner (sage) may start row 332`. This is an ownership boundary; row was not started and no code/test execution proceeds through it. Sage's assignment must take effect before the same op can succeed. The full design report fethry-0005 again timed out; fethry-0004 is the confirmed delivered report. git diff --check passed; no test, probe, commit or push has run.
|
||||
|
||||
## Route ruling accepted — sage-4293, 22:37Z
|
||||
|
||||
Sage settled explicit metadata, superseding both initial encoding proposals and the catalog proposal. Exact new setting: launch.models.<selection>.route; permitted values anthropic, glm, codex, required only when provider is proxy. Nonproxy entries still allow provider/model only. Missing route refuses proxy-route-missing; present unknown/nonstring route refuses proxy-route-unknown. Both get independent code assertions; each valid value gets a test using a proxy-only model id absent from any catalog. The GPT route's generated pi provider will be mosaic-proxy-codex, never openai-codex. Its wire API remains a build-time choice after the pinned-binary probe. P2 passes only {baseUrl,key}; no route belongs to that seam.
|
||||
|
||||
A key persistence hazard found during source inspection: launcher.mjs:571 writes bundle.env as session.harnessEnv. Adding the key to harness.env must not retain this behavior for credentials. P3 will keep generated key env transient for spawnSession and omit those credential values from serialized harnessEnv. runner.mjs:390 already inherits the spawn environment before applying nonsecret harnessEnv. Test the on-disk session/bundle boundary, not only models.json.
|
||||
|
||||
The old launch-settings.test.mjs already uses provider proxy fixtures without route. Update those fixtures to explicit anthropic routes before any full-file run; preserve their existing authorization assertions. This is the new approved validation shape, not permission to loosen tests. No tests have run and no production edits exist.
|
||||
|
||||
Additional test-first placement after route ruling: packages/harness/tests/proxy-bundle.test.mjs exercises real buildBundle with explicit provider proxy entries and proxy-only model ids, three generated providers, run-local file mode, environment references, recursive no-! values, no key in bundle files/manifest, fresh model/port/key on next launch. The exact GPT API assertion must be added from the authorized pinned-binary probe before this test file's first run; it deliberately makes no unverified wire-shape claim now. No skip is introduced.
|
||||
|
||||
Canonical queue show returned owner sage and after=331 at 22:40Z; it also warned `view unknown` for the rendered table. No repair or queue bypass attempted; wait for Sage's announced batch. Queue metadata identifies shared proxy Gitea issue #1830.
|
||||
|
||||
## STOPPED — mos-1630, 2026-10-24T22:48Z
|
||||
|
||||
Jason's relayed crash instruction supersedes active work: no long-job restart, commit/push safe documentation only, handoff in this worktree, no other thread messages, then stop. See HANDOFF.md for complete state, next steps, uncommitted tests/audit script, and reruns needed. No tests or probes ever ran; no production implementation exists. Latest design is explicit launch.models route metadata per sage-4293; earlier catalog/GPT-only proposals are superseded. The original 211-entry import manifest predates the fourth new test file and is historical, not run authorization. Full inline resends still hit T3 timeouts. Crash commit will exclude every unrun test and the audit script.
|
||||
|
||||
## Resumed — sage-4298, 2026-10-24T23:12Z
|
||||
|
||||
Resume authorized; crash-stop state above is historical. Sage accepts route spellings, refusal codes, mosaic-proxy-codex and launch.account.credential=proxy. Added third named mutant: **proxy key serialized into session.json**. Added an unrun real-launcher integration test in packages/cli/tests/launcher.test.mjs: a stand-in `{baseUrl,key}` enters via proposed createLauncher proxy option; real harness generation, session writer and runner must pass the key transiently while disk session/bundle files omit it. Adapter alone is fake. This catches restoring harnessEnv:bundle.env without credential separation. Existing test assertions are unchanged.
|
||||
|
||||
Regenerated IMPORT-RUN-LIST.txt with the non-evaluating import audit (static module dependencies plus explicit computed harness-loader edges). It now includes proxy-bundle.test.mjs; case1-accept remains explicitly EXCLUDED (mos-1612). Full manifest, touched tests and all three named mutants delivered inline to Sage using t3_code t3_thread_send, clientRequestId fethry-row332-resume-report-001, delivery steered. No unresolved route question remains. GPT API selection awaits CLEAR and pinned-binary evidence. Production implementation remains held for required first RED; tests/probes/fetches remain held for CLEAR. git diff --check clean.
|
||||
|
||||
Canonical queue next fethry started without timeout; no start retry until Sage confirms after-none write. Initial local-worktree queue invocation refused because queue requires canonical checkout; rerouted to canonical as required, no state bypass.
|
||||
|
||||
## Queue start retry — sage-4301, 2026-10-24T23:15Z
|
||||
|
||||
Sage reports after=none at rev4057. Retried canonical `scripts/mosaic queue move 332 in-progress --op fethry-332-start --by fethry` without a timeout. Awaiting its result; no duplicate command or manual queue edit. Lane B remains held and all tests/probes/fetches remain forbidden until CLEAR. Static review of test fixtures confirms launcher test uses the existing real-harness fake-adapter setup; initial RED remains unrun.
|
||||
|
||||
Start result: `ok fethry-332-start rev 4059 row 332 briefed→in-progress` (exit0). No tests/probes/fetches run.
|
||||
|
||||
## P1 rebase and pre-run reader stop — sage-4312/4313
|
||||
|
||||
Rebased onto 9e2689dff; current documentation checkpoint efe3e4697. Saved/restored all uncommitted notes/tests with named stash (retained for recovery). BUILD-LOG.md and SESSIONS.md append conflicts preserve both upstream and row332 entries. No production edits or force-push. git diff --check clean.
|
||||
|
||||
CLEAR received in sage-4313, conditional on pre-step reader check. STOP condition found before any test/probe/fetch: packages/cli/tests/launcher.test.mjs imports ./helpers.mjs, whose line10 imports BusError/Broker from ../../bus/src/broker.mjs (P1-changed path). The P3 test also starts the real broker through startHost. Report delivered to Sage as fethry-resume-0003, clientRequestId fethry-row332-p1-reader-stop. Await specific reader-chain authorization. No runs, counts or mutant outcomes exist. Mutant execution requires implementation reaching GREEN first; baseline RED will not be claimed as mutant evidence.
|
||||
|
||||
## First execution — sage-4316/4319, lane B, 2026-10-24
|
||||
|
||||
Reader chain authorized by sage-4316. Every run below used fresh shell `mktemp -d /mnt/storage/scratch/tmp/gXXX`, absolute nice19 (recorded), timeout -k10 (180s new files/300s whole launcher). No probes/fetches/mutants or case1-accept run. Logs are plain text in red/ beside this note.
|
||||
|
||||
| File | UTC start/end | pass/fail/skip | Classification |
|
||||
| --- | --- | --- | --- |
|
||||
| business proxy-launch | 23:41:37/23:41:37 | 2/4/0 | Expected missing route validation/account support |
|
||||
| harness proxy-bundle | 23:41:42/23:41:42 | 0/5/0 | Fixture ERROR: assignment to frozen resolved.vars, not feature RED |
|
||||
| plugins proxy-harness | 23:41:44/23:41:44 | 0/5/0 | Fixture ERROR: missing createLoader options, not feature RED |
|
||||
| seat proxy-session-env | 23:41:50/23:41:50 | 1/1/0 | Expected instance OAuth leak |
|
||||
| cli launcher (whole) | 23:41:55/23:42:25 | 34/1/0 | New P3 route rejected; all34 old tests pass |
|
||||
|
||||
Per sage-4319, fixed only the two new-file fixtures and committed b285e581e `fixture fix, untested` before rerunning. resolvedFor uses its mutation callback; createLoader receives {log(){}}. Existing launcher assertions were not changed.
|
||||
|
||||
| Corrected file | UTC start/end | pass/fail/skip | Classification |
|
||||
| --- | --- | --- | --- |
|
||||
| harness proxy-bundle | 23:42:41/23:42:41 | 0/5/0 | Another fixture ERROR: pm selects a/z/g absent from fixture launch.models; not valid feature RED |
|
||||
| plugins proxy-harness | 23:42:41/23:42:41 | 1/4/0 | Valid feature REDs: missing Claude proxy URL/key and preflight; pi missing-credential refusals absent. Acceptance test already passes because existing pi preflight only checks binary; not proof of proxy credential behavior |
|
||||
|
||||
Stopped after exactly one authorized corrected run each. No edits/reruns after second harness fixture error; report to Sage before changes. Proposed follow-up: preserve valid fixture business routing, then overlay a test-local resolved vars object for buildBundle, or supply a coherent model table in callback. GPT API assertion still awaits authorized evidence and must precede a GREEN claim.
|
||||
|
||||
Stash created before rebase: 69ad35ec79805a0303ede4454f8d6815ddfa7bb3. Shared refs/stash must not be used again; entry remains untouched for Sage to clear. Future checkpoints use branch WIP commits.
|
||||
|
||||
Own-worktree node/chromium process sweep at23:42:57Z found only six tool-service processes predating tests (started23:12:08–23:12:10Z), no test node/chromium leftovers. Preserved tool services. No process killed. Lane B returned; mutants deferred until RED→fix→GREEN and later CLEAR with tip.
|
||||
|
||||
## Corrected bundle first valid RED — sage-4320
|
||||
|
||||
Only proxy-bundle.test.mjs edited: resolve a valid existing fixture business first, then copy resolved and vars locally, overlay harness/model, and assert selection exists in the supplied declared models table. Frozen original is untouched. Commit56701f715 `fixture fix, untested`, directly atop b285e581e, before execution.
|
||||
|
||||
Exactly one whole proxy-bundle run at23:44:40Z (start/end), fresh gXXX TMPDIR, nice19, timeout-k10/180s: 5tests,0pass,5fail,0skip,exit1. All five now reach real buildBundle and fail ENOENT on the required run/home/.pi/agent/models.json: missing production generation, valid feature RED rather than fixture errors. Earlier frozen-vars and unknown-model errors remain separately recorded. Log red/harness-corrected-2.txt. No production edits, further runs, mutants, probes or fetches. Lane B free.
|
||||
|
||||
Rebase made original origin/fethry/row332 diverge from local; publish current history on fethry/row332-p1 without force, preserving original remote checkpoint. Shared stash69ad35ec79805a0303ede4454f8d6815ddfa7bb3 remains untouched. Remaining uncommitted business/seat tests, launcher addition and audit source are not included in this two-fixture checkpoint.
|
||||
|
||||
## P3 implementation checkpoint — sage-4323/4325, UNRUN
|
||||
|
||||
Scope correction: sage-4322's P1 meant P3; no P1 code changed. Source edits authorized by sage-4323; all runs remain held. This is an implementation checkpoint, not GREEN or acceptance. Executing the established plan inline; user lane restrictions supersede skill test-run/commit defaults for this checkpoint.
|
||||
|
||||
Latest route ruling sage-4325 supersedes sage-4293/4298 and all earlier route-required notes: omitted proxy route means Anthropic and preserves the stored provider/model shape. Explicit anthropic/glm/codex routes remain accepted; unknown/present-invalid route refuses proxy-route-unknown. proxy-route-missing is withdrawn, not implemented. Other providers keep provider/model-only schema. Own business test updated for default; new bundle test exercises default generation without changing existing explicit-route assertions. launch-settings.test.mjs remains byte-unchanged.
|
||||
|
||||
Implementation in six production files:
|
||||
- business launch validates optional proxy route, preserves omission, accepts account credential literal proxy.
|
||||
- seat sessionEnv leaves ENV_ALLOW unchanged and filters provider variables only with proxy context.
|
||||
- Claude accepts proxy credential preflight and returns proxy base/auth env; pi requires own provider key or proxy key, never Claude OAuth.
|
||||
- bundle carries transient proxy/models to harness calls and propagates proxyRoute separately from primary/fallback route; no context in manifest.
|
||||
- pi groups declared routes into run-local private models.json, references only $ANTHROPIC_API_KEY, refuses executable-looking model ids/key collisions, exclusive create and0700 parents/0600 file. No host auth reads. Generated file digest included in bundle manifest. Models use pi's documented default sizes/costs and reasoning support, with explicit z.ai compat for GLM; no catalog membership inference.
|
||||
- launcher accepts the specified context stand-in or a per-agent factory, copies only baseUrl/key, carries it through repeated preflight checks and final bundle/spawn. Factory receives business/instance; P2 owns acquisition and may be called twice due to existing launch recheck. No P2 store/client calls invented. Provider credential variables split out at actual session.json serialization and delivered only through spawn env. Account proxy source validates against the supplied context, not host upstream variables.
|
||||
|
||||
### Old-test changes authorized by sage-4325 (baseline1e47b0919)
|
||||
|
||||
P3 item4 (rows brief pi preflight bullet, lines250-252) mandates refusal without proxy/own credential:
|
||||
- plugins/tests/harness.test.mjs:102 before `assert.equal(pi.preflight({ env: {} }), null)`; after asserts `.code === no-session-credential`. Other assertions unchanged.
|
||||
- cli/tests/launcher.test.mjs prepare():84 before only fake Claude OAuth; after additionally supplies ZAI_API_KEY=fx332-pi only when chief primary/fallback names pi. Other tests' assertions unchanged; Claude-only preparation does not receive it.
|
||||
- cli/tests/launcher.test.mjs old no-credential pi test (sage reference697): before deletes OAuth then expects launch/pi and launch-log event; after also deletes newly supplied fake ZAI key, expects no-session-credential refusal, refused log code and no registered session. Test renamed to reflect mandated behavior. Existing no-session-credential Claude test unchanged.
|
||||
|
||||
No other old test was changed. Static review found these conflicts before execution and authorization preceded edits. No old-test RED occurred; baseline whole launcher had34 old passes.
|
||||
|
||||
### GPT wire API: PENDING
|
||||
|
||||
Single build-time value PROXY_CODEX_API=null in pi/index.mjs. A declared codex route refuses generation with a plain pending-evidence error until that value is settled; no guessed wire API, environment switch or runtime fallback. Consequently existing all-three-route bundle tests are not expected GREEN yet. P2 integration, full tests, pinned evidence, mutants and reviews remain pending.
|
||||
|
||||
Planned separate 4c evidence run (outside scripts/test-*.sh), requiring its own lane B CLEAR:
|
||||
1. Use landed P1 fetchPinned only under a fresh owned temp root for the exact PIN (8.0.42). Fetch is a separately approved preparation, not part of offline networking. Verify hashes via P1; never use host installation or another seat's scratch.
|
||||
2. Run the pinned proxy and local stand-in upstream inside unshare -rn with loopback enabled, empty private HOME/auth dir and a sanitized environment. No real auth file or network. All fake credentials under24 chars. Private test-only proxy config points only at the stand-in.
|
||||
3. Generate two private probe-only pi model configs, one openai-responses and one openai-completions, both baseUrl=the isolated proxy/v1. Run pinned pi turns and record safe path/body shape fields, statuses, stream result and upstream observations, omitting auth/header values. These probe configs do not select the production constant.
|
||||
4. Separately run the GLM completions turn under the same isolation and record whether thinkingFormat=zai/zaiToolStream-derived fields survive proxy translation.
|
||||
5. If the offline stand-in cannot distinguish the two GPT shapes for the Codex-login route, report inconclusive evidence to Sage for Mos; do not select a value. A compatibility route cannot be reported as a real Codex-auth turn. First real Codex turn remains row209 phaseA step7.
|
||||
|
||||
No tests, probes, binary invocation or fetch ran during implementation. Import audit below is source parsing only, never repository evaluation. New run list includes the old harness.test.mjs touched under ruling; case1-accept remains EXCLUDED. Shared stash already cleared by Sage after1e47b0919 confirmation; no further stash operation used.
|
||||
|
||||
## Six whole files on a5330386c — sage-4326 lane B
|
||||
|
||||
Each ran exactly once, in requested order, no edits between runs and no reruns. Fresh shell gXXX TMPDIR per file; every log records nice19; timeout-k10/180s (launcher300s). No mutants/probes/fetches or extra impact files. Logs green-attempt/*.txt.
|
||||
|
||||
| File | pass/fail/skip | exit | UTC start/end |
|
||||
| --- | --- | --- | --- |
|
||||
| business | 6/0/0 | 0 | 23:57:06Z/23:57:06Z |
|
||||
| seat | 2/0/0 | 0 | 23:57:06Z/23:57:06Z |
|
||||
| proxy-harness | 5/0/0 | 0 | 23:57:06Z/23:57:06Z |
|
||||
| harness | 9/0/0 | 0 | 23:57:12Z/23:57:12Z |
|
||||
| proxy-bundle | 1/5/0 | 1 | 23:57:12Z/23:57:12Z |
|
||||
| launcher | 34/1/0 | 1 | 23:57:12Z/23:57:42Z |
|
||||
|
||||
Expected-PENDING failures (each shared declared table includes codex), all stop before assertions with `Error: proxy GPT API is PENDING build-time evidence`:
|
||||
- P3 Anthropic route uses messages at proxy root and the declared model id
|
||||
- P3 GLM route uses completions at proxy /v1 with explicit z.ai compatibility
|
||||
- P3 GPT route selects a stack-owned provider instead of pi Codex OAuth
|
||||
- P3 models file is private, references the one session key, and stores no executable values or keys
|
||||
- P3 every launch regenerates proxy port, declared models and own key without caching
|
||||
|
||||
The omitted-route-only bundle case passed. Five pending failures do not establish the remaining route/file assertions; no claim of GREEN.
|
||||
|
||||
Real old-test RED: `row 65 a failed primary preflight launches the fallback on a fixture harness module, recording the route and the reason` (launcher.test.mjs:714). First expected operation `await launcher.launchPm()` throws before the result assertions: `PM launch refused: no-session-credential (fallback fixture: pi needs its own provider key or a session proxy key (primary down: fixture preflight fails))`. Fixture harness re-exports real pi; prepare() recognized literal pi primary/fallback only, so it did not supply the needed own key. Reported to Sage as fethry-resume-0012 before any edit; no fix or rerun authorized/performed. P3 serialization test and all other launcher tests passed.
|
||||
|
||||
After all runs finished, changed only harness.test.mjs:100 title per sage-4326 to `row 65 pi preflight refuses missing credentials, and its version is the package pin`; assertion unchanged, no rerun for title. Sweep after end23:57:42Z found the same six pre-existing tool services (2699602,2699605,2700525,2700526,2700531,2700931), no test node/chromium leftovers; no process killed. Lane B free. No other source/test fixes in this follow-up.
|
||||
|
||||
## Fixture correction authorized — sage-4327
|
||||
|
||||
Old-test list addition: launcher.test.mjs row65 fallback test (line714 at e9fc105ac), before: no own pi credential before setup; after: this test alone sets given.f.env.ZAI_API_KEY=fx332-pi immediately before setup. All assertions unchanged; prepare() condition unchanged. Reason: P3 item4 requires pi own/proxy credential; this fixture inherits pi preflight. Prior real RED and first thrown error preserved above.
|
||||
|
||||
New proxy-bundle tests now supply explicit per-test declared tables: Anthropic only, omitted/default only, GLM only, GPT only; privacy/regeneration use Anthropic+GLM and retain every key/mode/no-executable assertion applicable to those routes. Regeneration additionally checks changed GLM endpoint/model. Added expected refusal test for declared Codex with pending build-time API; no production source changes. Only GPT generation test should remain expected-PENDING. Fixture fixes committed untested before exactly two authorized whole-file runs.
|
||||
|
||||
### Two-file fixture verification — sage-4327
|
||||
|
||||
Fixture checkpoint f1e8cda07, no source change. Exactly one run each, no edits between runs or reruns. nice19, fresh gXXX TMPDIR per file, timeout-k10/180s bundle and300s launcher. Logs fixture-r3/*.txt.
|
||||
- proxy-bundle23:59:16Z:7tests,6pass,1fail,0skip,exit1. Sole expected-PENDING failure: `P3 GPT route selects a stack-owned provider instead of pi Codex OAuth`, error `proxy GPT API is PENDING build-time evidence`. All other route/privacy/regeneration cases and explicit pending-refusal case pass.
|
||||
- launcher23:59:16–23:59:45Z:35tests,35pass,0fail,0skip,exit0. Row65 fallback assertions unchanged and now pass; P3 transient serialization case passes.
|
||||
|
||||
No failure other than expected GPT generation; GPT wire evidence, P2 integration/rebase, mutants and reviews still pending. Sweep shows only same six pre-existing node/chrome tool services, no test leftovers. Lane B free after23:59:45Z. No probes/fetches/mutants or extra tests run.
|
||||
|
||||
## Route-required correction — sage-4328 restores sage-4293
|
||||
|
||||
sage-4325 default-Anthropic ruling withdrawn. Explicit route required again: missing proxy-route-missing, present invalid proxy-route-unknown, distinct own tests. Removed generation/session-model defaults; bundle omitted-route test now asserts missing-route code. Active plug-in documentation corrected. No catalog/prefix inference.
|
||||
|
||||
Old-test list (baseline1da28a7bd): launch-settings.test.mjs41,154,161,188,198 object fixtures before provider:proxy/model only; after identical objects plus route:anthropic. Line45 before assignment models.opus.provider=proxy; after adds models.opus.route=anthropic. Every assertion byte-unchanged; no assertion relaxed. Authority/reason sage-4293 restored by sage-4328: D9 proxy is any upstream route and omission cannot select Anthropic silently.
|
||||
|
||||
Pre-run git-grep audit scoped 'packages/*/tests/*', saved route-required/proxy-fixture-grep.txt:16 model/assignment lines in four files. Positive controls matched launch-settings41 literal,45 assignment and intentional missing inputs proxy-launch14/proxy-bundle47. Broader quoted-proxy literal audit saved proxy-literal-grep.txt, manually reviewed for multiline entries. Remaining route omissions only intentional refusal cases (proxy-launch14,proxy-bundle47); unknown-route test uses route variable. All six old fixtures explicit; launcher155 and remaining bundle model literals explicit. Other broad hits are account sentinels, CLI argv or filesystem strings, not model table entries. Findings sent inline as fethry-resume-0015 BEFORE any tests. No other missing-route positive fixture found.
|
||||
|
||||
Committed untested per instruction; four whole-file runs follow under lane B CLEAR, no reruns or extra files. GPT offline run and P1 fetch remain unstarted and require separate explicit go.
|
||||
|
||||
### Required-route four-file verification — sage-4328
|
||||
|
||||
Tested9563fbc8b, exactly once per whole file in authorized order. No edits between runs, no reruns/extra tests. Each fresh gXXX TMPDIR,nice19,timeout-k10/180s (launcher300s). Plain logs route-required/*.txt.
|
||||
|
||||
| File | pass/fail/skip | exit | UTC start/end (2026-10-25) |
|
||||
| --- | --- | --- | --- |
|
||||
| proxy-launch | 6/0/0 | 0 | 00:01:47Z/00:01:47Z |
|
||||
| launch-settings | 18/0/0 | 0 | 00:01:55Z/00:01:56Z |
|
||||
| proxy-bundle | 6/1/0 | 1 | 00:01:56Z/00:01:56Z |
|
||||
| launcher | 35/0/0 | 0 | 00:01:56Z/00:02:27Z |
|
||||
|
||||
Only failure: `P3 GPT route selects a stack-owned provider instead of pi Codex OAuth`, expected-PENDING error before assertions: proxy GPT API is PENDING build-time evidence. No other failures; missing-route and unknown-route separate-code tests pass; six old launch-settings fixture metadata changes preserve all assertions. All35 launcher tests pass.
|
||||
|
||||
Sweep after00:02:27Z: only same six pre-existing tool services; no test node/chromium leftovers and no process killed. Lane B free. GPT wire-shape evidence and P1 fetch NOT started; each still needs Sage explicit go/CLEAR. No mutants.
|
||||
|
||||
## GPT offline attempt1 and static diagnosis — sage-4329/4330
|
||||
|
||||
One authorized landed-P1 fetchPinned invocation succeeded, exact PIN8.0.42 executable SHA74661f8cf949977870231359e8bf4344e07946766a673c08fa25a3e5f72fc95e, panel1.26.7 SHA8c9c6b7d3fb311e6aad5d06e1993e53a2d28389e640448a1bc6304ed20b057b5. Fresh owned root named in evidence/root.txt, no alternate binary/new fetch. Fetch entry required nice19, end log observed16 due host renice; disclosed before continuing. Static-inspection invocation then failed entry nice guard before any binary read/launch; no turn. Main isolated attempt1 setPriority19 explicitly, but static binary tag scan again observed host renice16; subsequent child starts explicitly set19.
|
||||
|
||||
Attempt1 unshare-rn showed interfaces[lo], empty private HOME/auth and sanitized env. Proxy booted version8.0.42 commitbc064340, built2026-10-24T14:21:38Z. GET/v1/models response200 with modelIds[], script refused `stand-in routes absent from proxy catalog`; zero GPT-responses/GPT-completions/GLM pi turns ran, so zero compatibility evidence. Cleanup remainingManagedChildren0. No rerun under that CLEAR; reported0018. Raw sanitized receipt evidence/offline.txt preserved.
|
||||
|
||||
Startup network finding for P7/unit, plain text from isolated proxy log:
|
||||
`failed to fetch latest Grok CLI version from npm, keeping fallback/cached version error=npm registry request failed: Get https://registry.npmjs.org/@xai-official/grok/latest: dial tcp [2606:4700::6810:122]:443: connect: network is unreachable`.
|
||||
The namespace blocked external egress. Panel auto-update was disabled; this separate Grok version check still attempted startup egress. No real auth or host proxy touched.
|
||||
|
||||
### Static cause recorded BEFORE attempt2 (sage-4330)
|
||||
|
||||
No executable/probe invocation for diagnosis. Read the hash-pinned bundled management.html's config reader/serializer and P1 initialConfig in packages/model-proxy/src/settings.mjs. P1 produces nested server/management/access/oauth/observability/routing but adds no upstream provider config. Bundled panel Ip explicitly reads `api-keys.openai-compatibility`; zee reads e['api-keys'][provider]; Fp rejects groups without array group.keys; Np reads group name/base-url/keys/models; xv serializes a group with name,base-url,keys; hv serializes each key as api-key. Therefore the version8 payload is ROOT `api-keys: { openai-compatibility: [{name,base-url,keys:[{api-key}],models:[{name}]}] }`, distinct from the client allowlist under access.api-keys. Source excerpts/character offsets in evidence/bundled-panel-shape.txt; no header/auth values in receipts. This is actual pinned bundled UI reader/writer source, not a guessed provider nesting or an external/unpinned example.
|
||||
|
||||
Cause of first setup failure: my test used providers.openai-compatibility with api-key-entries, neither the root key nor the group-key field expected by that pinned writer. Initial readiness also stopped at first200 instead of waiting for required models; fixed to30s bound,1s intervals, model count logged per poll. Under sage-4330 one further isolated attempt only: reuse fetched binary and re-hash with P1 verifyInstall before launch; separate attempt2 test roots; actual pi turns each once, retry disabled in pi settings; API constant remains PENDING. Record/restore niceness for own descendants when host policy changes it. No actual turn has yet run as of this pre-attempt note.
|
||||
|
||||
## Offline attempt2 evidence — sage-4330, API STILL PENDING
|
||||
|
||||
Static diagnosis above preceded this one extra attempt. No new fetch. P1 verifyInstall recomputed existing executable/panel hashes before use and matched PIN8.0.42/panel1.26.7. unshare-rn verified only lo, empty private HOME/auth, sanitized env. Test config uses bundled writer's root api-keys.openai-compatibility/groups.keys shape. Readiness poll1 not-ready/modelCount0; poll2 status200/modelCount2; proxy lists gpt-fixture and glm-fixture. No setup failure on attempt2.
|
||||
|
||||
A transparent loopback HTTP tap captures pi's method/path and a whitelist of body shape fields; forwards original body bytes to pinned proxy; no header/auth values recorded. Upstream stand-in receives proxy output. Both remain inside network namespace. Each actual pi invocation once,50s timeout-k10; retry.enabled=false and provider.maxRetries=0 in private pi settings. Proxy has request-retry0. Outer isolation240s timeout-k10; no reruns. All three turn exits0 and marker P332_OFFLINE_OK observed:
|
||||
|
||||
| Turn | Pi request to proxy through transparent tap | Proxy response | Stand-in upstream observation |
|
||||
| --- | --- | --- | --- |
|
||||
| GPT responses | POST/v1/responses, modelgpt-fixture,streamtrue,reasoning={effort:low,summary:auto} |200,response.completed event, marker |POST/v1/chat/completions,reasoning_effort=low,max_tokens=16384,streamtrue |
|
||||
| GPT completions | POST/v1/chat/completions, modelgpt-fixture,roles[developer,user],reasoning_effort=low,max_completion_tokens=16384 |200,completion stream marker |POST/v1/chat/completions,roles[developer,user],reasoning_effort=low,max_tokens=16384 |
|
||||
| GLM completions | POST/v1/chat/completions,modelglm-fixture,thinking={type:enabled,clear_thinking:false},reasoning_effort=low,max_completion_tokens=16384 |200,completion stream marker |POST/v1/chat/completions,same thinking object and reasoning_effort retained,max_tokens=16384 |
|
||||
|
||||
GLM compatibility finding: thinkingFormat=zai-generated thinking.type=enabled/clear_thinking=false survives proxy translation, as does reasoning_effort=low. zaiToolStream-derived tool_stream was NOT emitted, because this turn disabled tools; its survival is NOT proven. Static pinned pi source node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js:612-618 sets params.tool_stream=true only with active tools and compat.zaiToolStream. No tool-call stream was exercised. This limitation is reported, not silently interpreted as pass or drop.
|
||||
|
||||
Both GPT shapes pass on the stand-in openai-compatibility credential route. This does not distinguish them for the Codex-login route; no Codex OAuth/auth file exists in this experiment. Evidence is INCONCLUSIVE for the production GPT choice. PROXY_CODEX_API remains null/PENDING; no source or API selection changed. Sage rules or takes to Mos per plan item5; first real Codex-auth turn stays row209 phaseA step7.
|
||||
|
||||
Niceness: attempt2 entry/emitted turn values19, explicit setPriority at entry/child start and100ms restore over owned descendants. Host policy temporarily changed processes to16 eight times; corrections restored19, disclosed here. Initial fetch end16 and failed inspection guard are retained separately, not represented as clean19 throughout. Full safe request/response receipts in evidence/offline-attempt2.txt; no auth/header value recorded. Process cleanup reports remainingManagedChildren0. Sweep over worktree and owned evidence root found same six pre-existing tool services only, no proxy/pi/timeout/chromium test leftovers. Lane B free. No mutants or other test files run.
|
||||
|
||||
## API choice and active-tool follow-up — sage-4331
|
||||
|
||||
Sage's ruling: PROXY_CODEX_API=openai-responses. The evidence does not separate the two shapes: both pass through pinned8.0.42. This is a tie broken on reasoning; it is NOT an evidence-decided pick. Sage's reason: Codex upstream speaks Responses natively and Responses carries pi's reasoning summary field, while completions drops that field; expected fewer translations/lost fields on the real route. This reasoning is a lead decision, not a claim of an authenticated Codex experiment. Row209 phaseA step7 verifies first real Codex turn; change the single constant if it fails there, with no runtime fallback/env switch. Earlier PENDING logs/notes remain historical evidence.
|
||||
|
||||
Set constant/remove pending refusal; GPT route test asserts openai-responses on mosaic-proxy-codex. Replaced temporary pending-refusal test with generated single-provider/responses/key-reference assertions per ruling. No other production change. Active plug-in docs updated.
|
||||
|
||||
Evidence script adds glm-tool mode: one GLM turn only, same pinned verified binary and isolated setup, distinct attempt3-tool root, active built-in read tool but stand-in still returns text only. Capture tool_stream and tool names before/after proxy, never header/auth values. No actual tool execution is demanded by stand-in, so this checks wire-field survival, not arbitrary tool-call semantics. Rehash before launch, no fetch,30s catalog readiness with counts,50s timeout-k10 for one turn,own descendants restored nice19. Commit untested before cleared run; then proxy-bundle and proxy-harness each whole once. No other runs/mutants.
|
||||
|
||||
### sage-4331 single active-tool GLM turn and two-file results
|
||||
|
||||
Tested ef0939e35. No fetch. P1 verifyInstall rehashed existing binary/panel and matched both PIN hashes before use. One isolated glm-tool run, unshare-rn interfaces[lo],empty private HOME/auth,sanitized env. Readiness poll1 not-ready/count0;poll2 status200/count2. One pi invocation with one active builtin read tool,exit0,markerP332_OFFLINE_OK; no rerun. Outer timeout-k10/140s,inner120s,pi50s;nice19 explicitly at entry/child starts and guard restores observed16→19 three times. No production API chosen by script.
|
||||
|
||||
Plain request/response evidence (full safe receipt evidence/glm-tool.txt):
|
||||
- pi POST/v1/chat/completions modelglm-fixture,streamtrue,tools=[read],tool_stream=true,thinking={type:enabled,clear_thinking:false},reasoning_effort=low,max_completion_tokens=16384.
|
||||
- pinned proxy200 completion stream,marker.
|
||||
- stand-in receives POST/v1/chat/completions,tools=[read],tool_stream=true and same thinking/reasoning_effort fields;max_tokens=16384. Thus zaiToolStream-derived tool_stream survives this proxy route. No tool invocation or tool-call delta sequence was requested by the stand-in; this proves field survival with active tool, not every tool-call streaming semantic.
|
||||
|
||||
Then exactly once each whole file, fresh gXXX TMPDIR,nice19,timeout-k10/180s:
|
||||
- packages/harness/tests/proxy-bundle.test.mjs00:14:20Z:7pass,0fail,0skip,exit0.
|
||||
- packages/plugins/tests/proxy-harness.test.mjs00:14:20Z:5pass,0fail,0skip,exit0.
|
||||
|
||||
No failures, no extra runs/mutants. Cleanup managed children0. Own-worktree/evidence-root sweep found only same six pre-existing tool services,no proxy/pi/test/timeout/chromium leftovers. Lane B free. P3 now waits for P2/row331 landing before rebase,mutants,Gladstone review and further gates; no landing/merge requested or performed.
|
||||
@@ -0,0 +1,51 @@
|
||||
# STOPPED — row 332 (P3), T3 crash handoff
|
||||
|
||||
Stopped on 2026-10-24 under Mos mos-1630, relaying Jason's instruction: do not restart long jobs; commit/push safe work, leave a handoff, then stop. No further thread messages, tests, probes, queue operations, or implementation will run in this session.
|
||||
|
||||
## Branch and completed work
|
||||
|
||||
- Worktree: /home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332
|
||||
- Branch: fethry/row332, based on refactor c8a9bcf94. No implementation commits, rebases, merges or production source edits.
|
||||
- Read P3 brief, rules, full pi amendment, gate template, design and named source files. Detailed notes in BUILD.md beside this handoff.
|
||||
- Root npm ci --ignore-scripts --no-audit --no-fund completed in this worktree only (128 packages). No canonical node_modules used.
|
||||
- Initial test files written before implementation; none executed. Sage has not sent CLEAR. No proxy fetch, binary probe, offline turn, mutant, or suite has run.
|
||||
- No real proxy, management key, host auth directory or live instance was accessed. No production service was started/stopped.
|
||||
- Canonical queue next returned nothing during the assignment. Queue start op fethry-332-start refused on locks, then owner sage, including a retry after two minutes. Latest completed queue show still had owner sage, state briefed, after 331; it also warned that the rendered table was unknown. Did not repair or bypass it. Sage sage-4293 says an assign/after=none batch is running. Queue metadata identifies shared Gitea issue #1830.
|
||||
|
||||
## Latest accepted design
|
||||
|
||||
Sage sage-4293 supersedes all earlier route proposals: explicit route metadata, never catalog membership or model-prefix inference. Selected spelling: launch.models.<selection>.route = anthropic | glm | codex, required only with provider proxy. Missing route: proxy-route-missing; unknown/nonstring route: proxy-route-unknown. Other providers keep provider/model-only entries. Proposed launch.account.credential literal proxy; existing name/credential field names retained. GPT generated provider: mosaic-proxy-codex, never openai-codex. GPT wire API is still undecided pending the authorized pinned-binary requests. P2's seam stays exactly { baseUrl, key }.
|
||||
|
||||
Important implementation finding: launcher.mjs:571 serializes bundle.env into session.harnessEnv. Harness env will contain the session key in proxy mode, so credentials must instead reach spawnSession through transient environment variables and be omitted from serialized harnessEnv. runner.mjs:390 already inherits the spawn environment. Preserve ENV_ALLOW; filter instance provider variables in proxy mode only.
|
||||
|
||||
## Uncommitted work, intentionally left for the next session
|
||||
|
||||
These are unrun, incomplete tests/audit code, excluded from the documentation-only crash commit:
|
||||
|
||||
- packages/business/tests/proxy-launch.test.mjs — explicit routes and refusal codes, proxy account credential, existing nonproxy/account behavior.
|
||||
- packages/plugins/tests/proxy-harness.test.mjs — Claude env/preflight, own-key isolation, pi preflight and OAuth-only refusal.
|
||||
- packages/seat/tests/proxy-session-env.test.mjs — instance credential filtering and unchanged nonproxy env.
|
||||
- packages/harness/tests/proxy-bundle.test.mjs — real bundle generation, all three providers, key reference/no-key/no-! invariants, file modes and launch freshness. Exact GPT API assertion deliberately pending probe evidence; add before its first run.
|
||||
- agents/fethry/work/queue-332/import-run-list.mjs — non-evaluating static import audit using vm.SourceTextModule plus explicit computed loader edges. It ran successfully after correcting its own root-depth setup error. It is not a test runner.
|
||||
|
||||
IMPORT-RUN-LIST.txt is a historical 211-entry candidate impact list, generated before proxy-bundle.test.mjs was added. It includes case1-accept as explicitly EXCLUDED (mos-1612). Regenerate the list after resuming and report the actual authorized run list inline before any whole-file run. Do not treat the historical manifest as a current run authorization.
|
||||
|
||||
## Reporting status
|
||||
|
||||
T3 reporting repeatedly failed during GET with a 30-second timeout. fethry-0004 was confirmed delivered. Some later short numeric keys returned reused:true unexpectedly; do not rely on them as delivery evidence. A row-scoped detailed design resend and the inline 211-line import manifest also timed out, including the manifest's same-key retry. Sage may not have those messages. This handoff and BUILD.md are being pushed so they can be read with git show; no new thread message is sent under mos-1630.
|
||||
|
||||
## Next session, after restart authorization
|
||||
|
||||
1. Read this handoff/BUILD.md and recover queue state from canonical checkout. Wait for owner fethry and no after, then retry the same start op. Never bypass a refusal.
|
||||
2. Recover Sage's slot status. Still NO tests or probes until Sage sends CLEAR. No job needs restarting merely to preserve current state.
|
||||
3. Review the four uncommitted test files. Add the pinned-proxy API probe and exact GPT assertion before the first relevant run. Initial RED has not happened; production implementation must follow the authorized first RED.
|
||||
4. Rebuild the import-derived run list and send it inline or in a reviewed pushed commit before runs. case1-accept remains off without Mos approval. Existing launch-settings.test.mjs proxy fixtures need explicit route metadata with their authorization assertions preserved; no old-test red has occurred.
|
||||
5. Use P1's committed fetchPinned code under an owned fresh temporary root (8.0.42 at the inspected magica/row329 revision), never live proxy assets or another seat's scratch. After CLEAR, try pi's responses and completions requests under unshare -rn against stand-in upstream, record evidence, choose one GPT API at build time. Separately prove offline GLM and record z.ai compat survival. Real Codex auth proof remains row209 phase A step7.
|
||||
6. Implement transient P2 context, model validation/route propagation, harness config, preflight, spawn env/serialization separation and tests. Named mutants: OAuth token leak; literal key in models.json. No policy or real credentials involved.
|
||||
7. Land only after 329 and 331 land, rebasing on both, Gladstone first review, Dewey landing review and Sage's required gate. No force-push or merge authority is added by the crash instruction.
|
||||
|
||||
All future tests use a fresh shell-created short TMPDIR, absolute nice 19, and timeout -k 10 for scripts/test-*.sh. First new-test red is reportable afterward; old-test red or a second red goes to Sage before changes/reruns. No test outcomes are claimed here.
|
||||
|
||||
## Crash commit
|
||||
|
||||
Only documentation and the historical plain-text import manifest are committed. The final STOPPED reply records the exact pushed commit. The uncommitted files above remain deliberately available in this worktree because they have never run and test-slot authorization has not arrived.
|
||||
@@ -0,0 +1,213 @@
|
||||
packages/bus/tests/access-revoke.test.mjs
|
||||
packages/bus/tests/broker.test.mjs
|
||||
packages/bus/tests/channel-proof.test.mjs
|
||||
packages/bus/tests/chief-of-staff.test.mjs
|
||||
packages/bus/tests/citation-guard.test.mjs
|
||||
packages/bus/tests/citation-runtime.test.mjs
|
||||
packages/bus/tests/creative.test.mjs
|
||||
packages/bus/tests/credential-verbs.test.mjs
|
||||
packages/bus/tests/grant-scope-runtime.test.mjs
|
||||
packages/bus/tests/host-run.test.mjs
|
||||
packages/bus/tests/land.test.mjs
|
||||
packages/bus/tests/library-credential.test.mjs
|
||||
packages/bus/tests/library-lookups.test.mjs
|
||||
packages/bus/tests/library-sync.test.mjs
|
||||
packages/bus/tests/library.test.mjs
|
||||
packages/bus/tests/lifecycle-revoke.test.mjs
|
||||
packages/bus/tests/limit-checks.test.mjs
|
||||
packages/bus/tests/messages.test.mjs
|
||||
packages/bus/tests/processing-runtime.test.mjs
|
||||
packages/bus/tests/project-runtime.test.mjs
|
||||
packages/bus/tests/reload.test.mjs
|
||||
packages/bus/tests/tasks.test.mjs
|
||||
packages/bus/tests/tester-writes.test.mjs
|
||||
packages/bus/tests/tester.test.mjs
|
||||
packages/bus/tests/usage-runtime.test.mjs
|
||||
packages/bus/tests/usage.test.mjs
|
||||
packages/bus/tests/workflow-roles.test.mjs
|
||||
packages/business/tests/access-levels.test.mjs
|
||||
packages/business/tests/access.test.mjs
|
||||
packages/business/tests/agent-identity.test.mjs
|
||||
packages/business/tests/assistant.test.mjs
|
||||
packages/business/tests/business.test.mjs
|
||||
packages/business/tests/case1-business-file.test.mjs
|
||||
packages/business/tests/chief-library-credentials.test.mjs
|
||||
packages/business/tests/chief-of-staff.test.mjs
|
||||
packages/business/tests/cli.test.mjs
|
||||
packages/business/tests/credentials.test.mjs
|
||||
packages/business/tests/csuite.test.mjs
|
||||
packages/business/tests/grant-scope.test.mjs
|
||||
packages/business/tests/hard-waits.test.mjs
|
||||
packages/business/tests/hosts.test.mjs
|
||||
packages/business/tests/instance-credential-transitions.test.mjs
|
||||
packages/business/tests/instance-settings.test.mjs
|
||||
packages/business/tests/launch-settings.test.mjs
|
||||
packages/business/tests/library-projects-protected.test.mjs
|
||||
packages/business/tests/library-seats.test.mjs
|
||||
packages/business/tests/lifecycle-settings.test.mjs
|
||||
packages/business/tests/optional-tracker.test.mjs
|
||||
packages/business/tests/plugin-settings.test.mjs
|
||||
packages/business/tests/project-repo.test.mjs
|
||||
packages/business/tests/project.test.mjs
|
||||
packages/business/tests/proxy-launch.test.mjs
|
||||
packages/business/tests/record-grants.test.mjs
|
||||
packages/business/tests/reply-limits.test.mjs
|
||||
packages/business/tests/resolve.test.mjs
|
||||
packages/business/tests/role.test.mjs
|
||||
packages/business/tests/token-limit-settings.test.mjs
|
||||
packages/business/tests/vars.test.mjs
|
||||
packages/business/tests/workflow-roles.test.mjs
|
||||
packages/cli/tests/access-boot.test.mjs
|
||||
packages/cli/tests/access.test.mjs
|
||||
packages/cli/tests/app.test.mjs
|
||||
packages/cli/tests/boot-status.test.mjs
|
||||
EXCLUDED (mos-1612) packages/cli/tests/case1-accept.test.mjs
|
||||
packages/cli/tests/case1-hostcheck.test.mjs
|
||||
packages/cli/tests/case2-install.test.mjs
|
||||
packages/cli/tests/case2-persona-import.test.mjs
|
||||
packages/cli/tests/case2-unit.test.mjs
|
||||
packages/cli/tests/channel-cli.test.mjs
|
||||
packages/cli/tests/channel-recovery.test.mjs
|
||||
packages/cli/tests/cli.test.mjs
|
||||
packages/cli/tests/compactions-launch.test.mjs
|
||||
packages/cli/tests/config.test.mjs
|
||||
packages/cli/tests/credentials.test.mjs
|
||||
packages/cli/tests/federation-acceptance.test.mjs
|
||||
packages/cli/tests/follow.test.mjs
|
||||
packages/cli/tests/git-socket.test.mjs
|
||||
packages/cli/tests/grants-scope.test.mjs
|
||||
packages/cli/tests/grants.test.mjs
|
||||
packages/cli/tests/host.test.mjs
|
||||
packages/cli/tests/hosts.test.mjs
|
||||
packages/cli/tests/idle-stop-race.test.mjs
|
||||
packages/cli/tests/instances.test.mjs
|
||||
packages/cli/tests/launch-account.test.mjs
|
||||
packages/cli/tests/launch-limit-checks.test.mjs
|
||||
packages/cli/tests/launch-limits.test.mjs
|
||||
packages/cli/tests/launch-routes.test.mjs
|
||||
packages/cli/tests/launcher.test.mjs
|
||||
packages/cli/tests/lifecycle.test.mjs
|
||||
packages/cli/tests/limits-tokens.test.mjs
|
||||
packages/cli/tests/limits.test.mjs
|
||||
packages/cli/tests/mark-read.test.mjs
|
||||
packages/cli/tests/model-proxy.test.mjs
|
||||
packages/cli/tests/names.test.mjs
|
||||
packages/cli/tests/notifier.test.mjs
|
||||
packages/cli/tests/optional-tracker.test.mjs
|
||||
packages/cli/tests/other-tree.test.mjs
|
||||
packages/cli/tests/processing-cli.test.mjs
|
||||
packages/cli/tests/project-name-repo.test.mjs
|
||||
packages/cli/tests/project.test.mjs
|
||||
packages/cli/tests/publish-socket.test.mjs
|
||||
packages/cli/tests/reply-limits.test.mjs
|
||||
packages/cli/tests/revoke-window.test.mjs
|
||||
packages/cli/tests/session-clear.test.mjs
|
||||
packages/cli/tests/session-cli.test.mjs
|
||||
packages/cli/tests/session-launch.test.mjs
|
||||
packages/cli/tests/stack-copy.test.mjs
|
||||
packages/cli/tests/stack-instances.test.mjs
|
||||
packages/cli/tests/tester-reads.test.mjs
|
||||
packages/cli/tests/tester-threads.test.mjs
|
||||
packages/cli/tests/tester.test.mjs
|
||||
packages/cli/tests/thread-cli.test.mjs
|
||||
packages/cli/tests/thread-launch.test.mjs
|
||||
packages/cli/tests/thread-revoke.test.mjs
|
||||
packages/cli/tests/trackers-boot.test.mjs
|
||||
packages/cli/tests/transport.test.mjs
|
||||
packages/cli/tests/usage.test.mjs
|
||||
packages/cli/tests/verbs.test.mjs
|
||||
packages/control-board/tests/creative-hidden.test.mjs
|
||||
packages/control-board/tests/discord.test.mjs
|
||||
packages/control-board/tests/serve.test.mjs
|
||||
packages/conversation/tests/flows.test.mjs
|
||||
packages/harness/tests/bundle.test.mjs
|
||||
packages/harness/tests/claude-gate.test.mjs
|
||||
packages/harness/tests/claude-session.test.mjs
|
||||
packages/harness/tests/creative.test.mjs
|
||||
packages/harness/tests/gate.test.mjs
|
||||
packages/harness/tests/git-tools.test.mjs
|
||||
packages/harness/tests/library-tools.test.mjs
|
||||
packages/harness/tests/mcp-server.test.mjs
|
||||
packages/harness/tests/pi-session.test.mjs
|
||||
packages/harness/tests/proxy-bundle.test.mjs
|
||||
packages/harness/tests/raise-domain.test.mjs
|
||||
packages/harness/tests/runner.test.mjs
|
||||
packages/harness/tests/skills.test.mjs
|
||||
packages/harness/tests/spec-tools.test.mjs
|
||||
packages/harness/tests/tools.test.mjs
|
||||
packages/plugins/tests/channel.test.mjs
|
||||
packages/plugins/tests/harness-compactions.test.mjs
|
||||
packages/plugins/tests/harness.test.mjs
|
||||
packages/plugins/tests/loader.test.mjs
|
||||
packages/plugins/tests/proxy-harness.test.mjs
|
||||
packages/plugins/tests/searxng.test.mjs
|
||||
packages/processing-control/tests/task.test.mjs
|
||||
packages/seat/tests/compactions-budget.test.mjs
|
||||
packages/seat/tests/compactions-ledger.test.mjs
|
||||
packages/seat/tests/compactions.test.mjs
|
||||
packages/seat/tests/proxy-session-env.test.mjs
|
||||
packages/seat/tests/sandbox.test.mjs
|
||||
packages/seat/tests/session-workspace.test.mjs
|
||||
packages/seat/tests/session.test.mjs
|
||||
packages/tasks/tests/fields.test.mjs
|
||||
packages/webui/tests/agents-browser.test.mjs
|
||||
packages/webui/tests/agents.test.mjs
|
||||
packages/webui/tests/assistant.test.mjs
|
||||
packages/webui/tests/board-held.test.mjs
|
||||
packages/webui/tests/browser-edge.test.mjs
|
||||
packages/webui/tests/browser.test.mjs
|
||||
packages/webui/tests/bus-browser.test.mjs
|
||||
packages/webui/tests/bus.test.mjs
|
||||
packages/webui/tests/channel-chat.test.mjs
|
||||
packages/webui/tests/chat-browser.test.mjs
|
||||
packages/webui/tests/chat-instances.test.mjs
|
||||
packages/webui/tests/chat-replies-browser.test.mjs
|
||||
packages/webui/tests/chat.test.mjs
|
||||
packages/webui/tests/conversation.test.mjs
|
||||
packages/webui/tests/decide-browser.test.mjs
|
||||
packages/webui/tests/decide-sso.test.mjs
|
||||
packages/webui/tests/discord.test.mjs
|
||||
packages/webui/tests/docs-browser.test.mjs
|
||||
packages/webui/tests/editor-browser.test.mjs
|
||||
packages/webui/tests/editor.test.mjs
|
||||
packages/webui/tests/granted-user.test.mjs
|
||||
packages/webui/tests/history-return-flow.test.mjs
|
||||
packages/webui/tests/instances-browser.test.mjs
|
||||
packages/webui/tests/instances.test.mjs
|
||||
packages/webui/tests/library-cli.test.mjs
|
||||
packages/webui/tests/library-routes.test.mjs
|
||||
packages/webui/tests/limits-browser.test.mjs
|
||||
packages/webui/tests/limits-route.test.mjs
|
||||
packages/webui/tests/messages.test.mjs
|
||||
packages/webui/tests/pinned-copy.test.mjs
|
||||
packages/webui/tests/preview.test.mjs
|
||||
packages/webui/tests/processing-browser.test.mjs
|
||||
packages/webui/tests/processing.test.mjs
|
||||
packages/webui/tests/project-file.test.mjs
|
||||
packages/webui/tests/proof-cli.test.mjs
|
||||
packages/webui/tests/proof.test.mjs
|
||||
packages/webui/tests/reads.test.mjs
|
||||
packages/webui/tests/relaunch.test.mjs
|
||||
packages/webui/tests/return-flow.test.mjs
|
||||
packages/webui/tests/room-browser.test.mjs
|
||||
packages/webui/tests/room-http.test.mjs
|
||||
packages/webui/tests/routes-browser.test.mjs
|
||||
packages/webui/tests/routes.test.mjs
|
||||
packages/webui/tests/send-browser.test.mjs
|
||||
packages/webui/tests/send.test.mjs
|
||||
packages/webui/tests/serve.test.mjs
|
||||
packages/webui/tests/settings-form.test.mjs
|
||||
packages/webui/tests/settings-write.test.mjs
|
||||
packages/webui/tests/shell.test.mjs
|
||||
packages/webui/tests/sso-browser.test.mjs
|
||||
packages/webui/tests/sso.test.mjs
|
||||
packages/webui/tests/tester-browser.test.mjs
|
||||
packages/webui/tests/tester-cli.test.mjs
|
||||
packages/webui/tests/tester-listener.test.mjs
|
||||
packages/webui/tests/thread-claims-browser.test.mjs
|
||||
packages/webui/tests/thread-claims.test.mjs
|
||||
packages/webui/tests/thread-sharing.test.mjs
|
||||
packages/webui/tests/usage-browser.test.mjs
|
||||
packages/webui/tests/usage.test.mjs
|
||||
packages/webui/tests/user-option-swap.test.mjs
|
||||
packages/webui/tests/views.test.mjs
|
||||
@@ -0,0 +1,19 @@
|
||||
00:14:20Z
|
||||
nice=19
|
||||
✔ P3 Anthropic route uses messages at proxy root and the declared model id (12.070131ms)
|
||||
✔ P3 omitted route refuses proxy-route-missing before model config generation (2.197623ms)
|
||||
✔ P3 GLM route uses completions at proxy /v1 with explicit z.ai compatibility (3.390562ms)
|
||||
✔ P3 GPT route selects a stack-owned provider instead of pi Codex OAuth (4.240948ms)
|
||||
✔ P3 a declared Codex route generates only the single build-time responses provider (3.37448ms)
|
||||
✔ P3 models file is private, references the one session key, and stores no executable values or keys (3.305299ms)
|
||||
✔ P3 every launch regenerates proxy port, declared models and own key without caching (6.287121ms)
|
||||
ℹ tests 7
|
||||
ℹ suites 0
|
||||
ℹ pass 7
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 128.759611
|
||||
exit=0
|
||||
00:14:20Z
|
||||
@@ -0,0 +1,17 @@
|
||||
00:14:20Z
|
||||
nice=19
|
||||
✔ P3 Claude env receives the session proxy URL and key, without another agent or host credential (3.096286ms)
|
||||
✔ P3 Claude preflight accepts a proxy key without a host provider credential (1.305258ms)
|
||||
✔ P3 pi refuses no-session-credential with no provider key (1.23137ms)
|
||||
✔ P3 pi refuses no-session-credential with only a Claude OAuth token (3.659409ms)
|
||||
✔ P3 pi accepts its own provider key or a session proxy key (0.590987ms)
|
||||
ℹ tests 5
|
||||
ℹ suites 0
|
||||
ℹ pass 5
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 68.648537
|
||||
exit=0
|
||||
00:14:20Z
|
||||
@@ -0,0 +1,17 @@
|
||||
Ip=e=> @ char 1054606:
|
||||
Ip=e=>{let t={raw:Dc(e)?e:{}};if(!Dc(e))return t;let n=t=>t.split(`.`).reduce((e,t)=>Dc(e)?e[t]:void 0,e);t.debug=wp(n(`observability.logs.debug`)),t.requestLog=wp(n(`observability.logs.request-log`)),t.loggingToFile=wp(n(`observability.logs.logging-to-file`));let r=n(`observability.logs.logs-max-total-size-mb`);typeof r==`number`&&(t.logsMaxTotalSizeMb=r);let i=n(`requests.proxy-url`);typeof i==`string`&&(t.proxyUrl=i);let a=n(`routing.retry.request-retry`);typeof a==`number`&&(t.requestRetry=a),t.wsAuth=wp(n(`oauth.providers.aistudio.ws-auth`))??!0,t.forceModelPrefix=wp(n(`routing.force-model-prefix`));let o=n(`routing.strategy`);typeof o==`string`&&(t.routingStrategy=o);let s=n(`access.api-keys`);return t.apiKeys=Array.isArray(s)?s.filter(e=>typeof e==`string`):[],t.quotaExceeded={antigravityCredits:wp(n(`oauth.providers.antigravity.antigravity-credits`))??!1},t.providerGroups=Dc(e[`api-keys`])?e[`api-keys`]:{},t.geminiApiKeys=Fp(n(`api-keys.gemini`)),t.interactionsApiKeys=Fp(n(`api-keys.interactions`)),t.codexApiKeys=Fp(n(`api-keys.codex`)),t.metaApiKeys=Fp(n(`api-keys.meta`)),t.xaiApiKeys=Fp(n(`api-keys.xai`)),t.claudeApiKeys=Fp(n(`api-keys.claude`)),t.vertexApiKeys=Fp(n(`api-keys.vertex`)),t.openaiCompatibility=Fp(n(`api-keys.openai-compatibility`),!0),t.oauthExcludedModels=Pp(n(`oauth.excluded-models`)),t},Lp={async getConfig(){return Ip(await W.get(`/config`))},updateRequestLog:e=>W.put(`/config/observability/logs/request-log`,e)},Rp=0,zp=null,Bp=null,Vp=()=>Bp!==null&&Date.now()-Bp.timestamp<3e4,Hp=Tc((e,t)=>({config:null,fetchConfig:async(t=!1)=>{if(!t&&Bp&&Vp())return Bp.data;if(zp)return zp.promise;let n=Rp+=1;try{let t=Lp.getConfig();zp={id:n,promise:t};let r
|
||||
|
||||
Fp=(e,t= @ char 1054198:
|
||||
Fp=(e,t=!1)=>Array.isArray(e)?e.flatMap((n,r)=>{if(!Dc(n)||!Array.isArray(n.keys))return[];if(t){let t=Np(n,r);return t?[{...t,source:{groupIndex:r,group:n,groups:e}}]:[]}return n.keys.flatMap((t,i)=>{if(!Dc(t))return[];let a={...n};delete a.keys,delete a.name,Object.entries(t).forEach(([e,t])=>{t!==null&&(a[e]=t)});let o=Mp(a);return o?[{...o,source:{groupIndex:r,keyIndex:i,group:n,groups:e}}]:[]})}):[],Ip=e=>{let t={raw:Dc(e)?e:{}};if(!Dc(e))return t;let n=t=>t.split(`.`).reduce((e,t)=>Dc(e)?e[t]:void 0,e);t.debug=wp(n(`observability.logs.debug`)),t.requestLog=wp(n(`observability.logs.reques
|
||||
|
||||
Np=(e,t)=> @ char 1053331:
|
||||
Np=(e,t)=>{if(!Dc(e))return null;let n=e.name,r=e[`base-url`];if(!n||!r)return null;let i=Array.isArray(e.keys)?e.keys.map((e,t)=>{let n=Ap(e);return n?{...n,sourceIndex:t}:null}).filter(Boolean):[],a=Ep(e.headers),o=Tp(e.models),s=e.priority,c={name:String(n),baseUrl:String(r),apiKeyEntries:i,...jp(e)},l=wp(e[`support-prompt-cache-key`]);l!==void 0&&(c.supportPromptCacheKey=l);let u=wp(e.disabled);u!==void 0&&(c.disabled=u);let d=wp(e[`disable-cooling`]);d!==void 0&&(c.disableCooling=d);let f=Op(e.prefix);f&&(c.prefix=f),a&&(c.headers=a),o.length&&(c.models=o),s!==void 0&&(c.priority=Number(s));let p=kp(e[`auth-index`]);return p&&(c.authIndex=p),t!==void 0&&(c.sourceIndex=t),c},Pp=e=>{if(!Dc(e))return;let t=e;if(!Dc(t))return;let n={};return Object.entries(t).forEach(([e,t])=>{let r=String(e||``).trim();if(!r)return;let i=Dp(t);n[r.toLow
|
||||
|
||||
xv=e=> @ char 1129493:
|
||||
xv=e=>{let t={name:e.name,"base-url":e.baseUrl,...gv(e),keys:Array.isArray(e.apiKeyEntries)?e.apiKeyEntries.map(e=>hv(e)):[]};e.prefix?.trim()&&(t.prefix=e.prefix.trim()),e.disabled!==void 0&&(t.disabled=e.disabled),e.supportPromptCacheKey!==void 0&&(t[`support-prompt-cache-key`]=e.supportPromptCacheKey);let n=pv(e.headers);n&&(t.headers=n);let r=mv(e.models,!0);return r&&r.length&&(t.models=r),e.priority!==void 0&&(t.priority=e.priority),e.disableCooling!==void 0&&(t[`disable-cooling`]=e.disableCooling),t},Sv=(e,t)=>{if(e===t)return!0;if(Array.isArray(e)&&Array.isArray(t))return e.length===t.length&&e.every((e,n)=>Sv(e,t[n]));if(Dc(e)&&Dc(t)){let n=Object.keys(e).filter(t=>e[t]!==void 0);re
|
||||
|
||||
hv=e=> @ char 1126395:
|
||||
hv=e=>{let t={"api-key":e.apiKey};return e.proxyUrl&&(t[`proxy-url`]=e.proxyUrl),e.weight!==void 0&&(t.weight=e.weight),t},gv=(e,t=!0)=>{let n={};return e.requestRetry!==void 0&&(n[`request-retry`]=e.requestRetry),t&&e.r
|
||||
|
||||
zee=(e,t)=> @ char 1130380:
|
||||
zee=(e,t)=>{if(!Dc(e)||!Dc(e[`api-keys`]))return[];let n=e[`api-keys`][t];if(n===void 0)return[];if(!Array.isArray(n)||!n.every(e=>Dc(e)&&Array.isArray(e.keys)))throw Cv();return n},wv=async e=>{let t=cv(),n=Nm.getState(),r=await W.get(`/config`);t();let i=Nm.getState();if(n.apiBase!==i.apiBase||n.managementKey!==i.managementKey||n.isAuthenticated!==i.isAuthenticated)throw Cv();return zee(r,e)},Tv
|
||||
@@ -0,0 +1,11 @@
|
||||
file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/agents/fethry/work/queue-332/proxy-evidence.mjs:9
|
||||
if (getPriority(0) !== 19) throw Error('nice19 required');
|
||||
^
|
||||
|
||||
Error: nice19 required
|
||||
at file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/agents/fethry/work/queue-332/proxy-evidence.mjs:9:34
|
||||
at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
|
||||
at async node:internal/modules/esm/loader:650:26
|
||||
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)
|
||||
|
||||
Node.js v26.8.1
|
||||
@@ -0,0 +1 @@
|
||||
{"phase":"fetch","version":"8.0.42","binarySha256":"74661f8cf949977870231359e8bf4344e07946766a673c08fa25a3e5f72fc95e","panelVersion":"1.26.7","panelSha256":"8c9c6b7d3fb311e6aad5d06e1993e53a2d28389e640448a1bc6304ed20b057b5","nice":16,"verified":true}
|
||||
@@ -0,0 +1,8 @@
|
||||
{"phase":"isolation","interfaces":["lo"],"enteredNice":19,"nice":19,"version":"8.0.42","binarySha256":"74661f8cf949977870231359e8bf4344e07946766a673c08fa25a3e5f72fc95e","panelSha256":"8c9c6b7d3fb311e6aad5d06e1993e53a2d28389e640448a1bc6304ed20b057b5","reverified":true}
|
||||
{"phase":"readiness","poll":1,"status":"not-ready","modelCount":0}
|
||||
{"phase":"readiness","poll":2,"status":200,"modelCount":2}
|
||||
{"phase":"catalog","modelIds":["gpt-fixture","glm-fixture"]}
|
||||
{"phase":"turn","name":"glm-completions-tool","api":"openai-completions","exit":0,"signal":null,"marker":true,"piRequests":[{"method":"POST","path":"/v1/chat/completions","keys":["max_completion_tokens","messages","model","reasoning_effort","store","stream","stream_options","thinking","tool_stream","tools"],"model":"glm-fixture","stream":true,"roles":["developer","user"],"inputTypes":"undefined","thinking":{"type":"enabled","clear_thinking":false},"reasoning_effort":"low","tool_stream":true,"toolNames":["read"],"stream_options":{"include_usage":true},"max_completion_tokens":16384}],"proxyResponses":[{"request":0,"status":200,"events":[],"marker":true}],"upstreamRequests":[{"method":"POST","path":"/v1/chat/completions","keys":["max_tokens","messages","model","reasoning_effort","store","stream","stream_options","thinking","tool_stream","tools"],"model":"glm-fixture","stream":true,"roles":["developer","user"],"inputTypes":"undefined","thinking":{"type":"enabled","clear_thinking":false},"reasoning_effort":"low","tool_stream":true,"toolNames":["read"],"stream_options":{"include_usage":true},"max_tokens":16384}],"nice":19}
|
||||
{"phase":"conclusion","limitation":"Stand-in openai-compatibility credential only; no Codex OAuth credential or real-auth turn. This script never selects the production API."}
|
||||
{"phase":"nice-corrections","restoredTo":19,"observedBeforeRestore":{"16":3}}
|
||||
{"phase":"cleanup","remainingManagedChildren":0}
|
||||
@@ -0,0 +1,10 @@
|
||||
{"phase":"isolation","interfaces":["lo"],"enteredNice":19,"nice":19,"version":"8.0.42","binarySha256":"74661f8cf949977870231359e8bf4344e07946766a673c08fa25a3e5f72fc95e","panelSha256":"8c9c6b7d3fb311e6aad5d06e1993e53a2d28389e640448a1bc6304ed20b057b5","reverified":true}
|
||||
{"phase":"readiness","poll":1,"status":"not-ready","modelCount":0}
|
||||
{"phase":"readiness","poll":2,"status":200,"modelCount":2}
|
||||
{"phase":"catalog","modelIds":["gpt-fixture","glm-fixture"]}
|
||||
{"phase":"turn","name":"gpt-responses","api":"openai-responses","exit":0,"signal":null,"marker":true,"piRequests":[{"method":"POST","path":"/v1/responses","keys":["include","input","max_output_tokens","model","prompt_cache_key","reasoning","store","stream"],"model":"gpt-fixture","stream":true,"inputTypes":[null,null],"reasoning":{"effort":"low","summary":"auto"}}],"proxyResponses":[{"request":0,"status":200,"events":["response.created","response.in_progress","response.output_item.added","response.content_part.added","response.output_text.delta","response.output_text.done","response.content_part.done","response.output_item.done","response.completed"],"marker":true}],"upstreamRequests":[{"method":"POST","path":"/v1/chat/completions","keys":["max_tokens","messages","model","reasoning_effort","stream","stream_options"],"model":"gpt-fixture","stream":true,"roles":["user","user"],"inputTypes":"undefined","reasoning_effort":"low","stream_options":{"include_usage":true},"max_tokens":16384}],"nice":19}
|
||||
{"phase":"turn","name":"gpt-completions","api":"openai-completions","exit":0,"signal":null,"marker":true,"piRequests":[{"method":"POST","path":"/v1/chat/completions","keys":["max_completion_tokens","messages","model","reasoning_effort","store","stream","stream_options"],"model":"gpt-fixture","stream":true,"roles":["developer","user"],"inputTypes":"undefined","reasoning_effort":"low","stream_options":{"include_usage":true},"max_completion_tokens":16384}],"proxyResponses":[{"request":1,"status":200,"events":[],"marker":true}],"upstreamRequests":[{"method":"POST","path":"/v1/chat/completions","keys":["max_tokens","messages","model","reasoning_effort","store","stream","stream_options"],"model":"gpt-fixture","stream":true,"roles":["developer","user"],"inputTypes":"undefined","reasoning_effort":"low","stream_options":{"include_usage":true},"max_tokens":16384}],"nice":19}
|
||||
{"phase":"turn","name":"glm-completions","api":"openai-completions","exit":0,"signal":null,"marker":true,"piRequests":[{"method":"POST","path":"/v1/chat/completions","keys":["max_completion_tokens","messages","model","reasoning_effort","store","stream","stream_options","thinking"],"model":"glm-fixture","stream":true,"roles":["developer","user"],"inputTypes":"undefined","thinking":{"type":"enabled","clear_thinking":false},"reasoning_effort":"low","stream_options":{"include_usage":true},"max_completion_tokens":16384}],"proxyResponses":[{"request":2,"status":200,"events":[],"marker":true}],"upstreamRequests":[{"method":"POST","path":"/v1/chat/completions","keys":["max_tokens","messages","model","reasoning_effort","store","stream","stream_options","thinking"],"model":"glm-fixture","stream":true,"roles":["developer","user"],"inputTypes":"undefined","thinking":{"type":"enabled","clear_thinking":false},"reasoning_effort":"low","stream_options":{"include_usage":true},"max_tokens":16384}],"nice":19}
|
||||
{"phase":"conclusion","limitation":"Stand-in openai-compatibility credential only; no Codex OAuth credential or real-auth turn. API value remains PENDING."}
|
||||
{"phase":"nice-corrections","restoredTo":19,"observedBeforeRestore":{"16":8}}
|
||||
{"phase":"cleanup","remainingManagedChildren":0}
|
||||
@@ -0,0 +1,4 @@
|
||||
{"phase":"isolation","interfaces":["lo"],"enteredNice":19,"nice":16,"version":"8.0.42","configTags":["ntptr; X0 []string } \"*struct { F uintptr; X0 net.Conn } Models yaml:\"models\" json:\"models\" \"*config.DisableImageGene","ubscriptionCommand $*struct { F uintptr; R *api.Server } APIKey yaml:\"api-key\" json:\"api-key\" $EffectiveSignInWithChat","on:\\\"type\\\"\" } '*struct { F uintptr; X0 *bufio.Reader } BaseURL yaml:\"base-url\" json:\"base-url\" '*config.CodexWebSearch","ise-management\" json:\"advertise-management\" OpenAICompatibility7yaml:\"openai-compatibility\" json:\"openai-compatibility\" K*struct { "]}
|
||||
{"phase":"catalog","modelIds":[]}
|
||||
{"phase":"error","message":"stand-in routes absent from proxy catalog","proxyDiagnostic":"CLIProxyAPI Version: 8.0.42, Commit: bc064340, BuiltAt: 2026-10-24T14:21:38Z\n[2026-10-24 19:07:27] [--------] [info ] [main.go:641] CLIProxyAPI Version: 8.0.42, Commit: bc064340, BuiltAt: 2026-10-24T14:21:38Z\n[2026-10-24 19:07:27] [--------] [info ] [main.go:752] Local model mode: using embedded catalogs unless an explicit catalog source is configured\n[2026-10-24 19:07:27] [--------] [info ] [antigravity_version.go:71] periodic antigravity version refresh started (interval=3h0m0s)\n[2026-10-24 19:07:27] [--------] [info ] [service_lifecycle.go:103] core auth auto-refresh started (interval=15m0s)\n[2026-10-24 19:07:27] [--------] [info ] [server_management.go:22] management routes registered after secret key configuration\n[2026-10-24 19:07:27] [--------] [warn ] [xai_version.go:96] failed to fetch latest Grok CLI version from npm, keeping fallback/cached version error=npm registry request failed: Get \"https://registry.npmjs.org/@xai-official/grok/latest\": dial tcp [2606:4700::6810:122]:443: connect: network is unreachable\n[2026-10-24 19:07:27] [--------] [info ] [xai_version.go:79] periodic Grok CLI version refresh started (interval=3h0m0s)\n[2026-10-24 19:07:27] [b7852b37] [info ] [gin_logger.go:113] 200 | 0s | 127.0.0.1 | GET \"/v1/models\"\n"}
|
||||
{"phase":"cleanup","remainingManagedChildren":0}
|
||||
@@ -0,0 +1 @@
|
||||
/mnt/storage/scratch/tmp/g2Ta
|
||||
@@ -0,0 +1,63 @@
|
||||
23:59:16Z
|
||||
nice=19
|
||||
✔ C2-04: launcher workspace clones code but never the library (1450.400805ms)
|
||||
✔ P3 proxy key reaches the spawned runner but never session.json or the bundle (1347.117393ms)
|
||||
✔ bus start --pm: the launcher instance (the chief of staff) runs under its own PID namespace, registered, with a manifest (1362.533548ms)
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r5c4bfddc2d03","instance":"chief","turn":"2ce6282a-5e4f-4276-851e-3d8dd2d53a7a","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"rf9b4b14789bc","instance":"coder","turn":"d7a0db99-cce3-417f-8e7d-9910641f0860","source":"@none","code":"baseline-unknown"}
|
||||
✔ the chief of staff launches a coder through its launch tool; the coder answers; refusals name their code (842.489432ms)
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"rd5fa76df3fb0","instance":"chief","turn":"f739ef1e-c01f-401f-9a89-cf45bc15b976","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r2c414ea1d43f","instance":"pm","turn":"8852a197-c1be-4108-a3af-34dad0232264","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"rd5fa76df3fb0","instance":"chief","turn":"6ccde05e-202a-43af-8556-43481f552676","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"rd5fa76df3fb0","instance":"chief","turn":"4a8f168b-8676-4b54-9404-174a2a703fcf","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r2c414ea1d43f","instance":"pm","turn":"67ee96fd-1338-48aa-a4fc-edbb7e0aef0e","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r2c414ea1d43f","instance":"pm","turn":"ead68527-7bf4-49a1-8fd7-0107b9c6dfdf","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"rd5fa76df3fb0","instance":"chief","turn":"c1cc3a5e-1afe-4994-aea4-a3ea811a8f30","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r2c414ea1d43f","instance":"pm","turn":"e60723f6-187d-4b55-bd81-66189ae014c0","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"rd5fa76df3fb0","instance":"chief","turn":"1b7721ee-a337-4623-9fcc-1e05277336ce","source":"@none","code":"baseline-unknown"}
|
||||
✔ a launched pm's request_staff reaches the chief of staff; no other session has the tool and the launch limits still hold (1152.112093ms)
|
||||
✔ each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or fetched and logged (4210.704487ms)
|
||||
✔ a role the broker says holds a merge decision gets land_work, its prompt line, its landing and the git socket; without it, none (713.043698ms)
|
||||
✔ REQ-LAUNCH-1 the tick starts a listed merge decision holder once, with land_work, the Landing line and its landing turn; an unlisted holder starts nothing (456.475273ms)
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r0e259f41ad4d","instance":"coder","turn":"52bb41d8-5eb6-4c72-9c5c-0b8abf8eae42","source":"@none","code":"baseline-unknown"}
|
||||
✔ a holder running since before the resolution is stopped once the broker releases it, land-relaunch, and the next tick starts it: landing turn first, then the message that waited (838.095383ms)
|
||||
✔ a landing run that ended isn't started again for its decision, a spent decision starts nothing, and a new decision starts the holder (721.283915ms)
|
||||
✔ after a restart, the new host's first tick starts a holder whose decision is still held (963.365086ms)
|
||||
✔ a refused landing start is logged once per code with its decision, isn't a start, and the next tick with room starts the holder (708.305295ms)
|
||||
✔ a project clone that can't be made refuses checkout-failed, from --chief and from a session, with no run and no clone left (300.51414ms)
|
||||
✔ a runner that stops at once ends its launch with the runner's reason (380.448961ms)
|
||||
✔ a Claude Code launch with no session credential refuses before it starts (206.153531ms)
|
||||
✔ a pi launch with no own credential refuses no-session-credential (207.22057ms)
|
||||
usage: {"event":"plugin-refused","kind":"harness","name":"down","code":"missing","message":"/home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/harness/down is missing","kept":[]}
|
||||
plugins: {"event":"plugin-refused","kind":"harness","name":"down","code":"missing","message":"/home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/harness/down is missing","kept":[]}
|
||||
✔ row 65 a failed primary preflight launches the fallback on a fixture harness module, recording the route and the reason (398.352887ms)
|
||||
✔ a runner killed from the host ends its launch as killed, not failed (418.567314ms)
|
||||
✔ a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback (211.208923ms)
|
||||
✔ row 64 a role skill that isn't enabled refuses skill-not-enabled, and one that isn't a plain directory skill-refused, before anything starts (215.670064ms)
|
||||
✔ row 213 a creative instance's persona is read from <configDir>/personas: off its digest the launch fails with the mismatch as its reason (321.498853ms)
|
||||
✔ row 64 on Claude Code a role skill that isn't enabled refuses the same way, before anything starts (208.543617ms)
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r1f065863b517","instance":"chief","turn":"2c19dcc1-343e-4c12-93e3-4cd6dd65c4d9","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"reae1621f7ddf","instance":"coder","turn":"03cc3667-aab3-4f55-9e57-bf637e1be0dc","source":"@none","code":"baseline-unknown"}
|
||||
✔ a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold (1566.080686ms)
|
||||
✔ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (1164.65123ms)
|
||||
✔ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (4189.297007ms)
|
||||
✔ bus start --no-launch leaves an earlier host's live session alone: no signal, no end, sessions.json as it was (258.381919ms)
|
||||
✔ bus start --no-launch refuses every launch: the PM launch and a session's request, each logged, nothing spawned (219.378242ms)
|
||||
✔ bus start --no-launch starts no thread tick; the same host with launches runs it (1236.400078ms)
|
||||
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (198.129142ms)
|
||||
✔ an over-long launch request is refused at once, not at the 10 s idle timeout (200.659094ms)
|
||||
✔ a launch client that never closes its side doesn't hold the host's close (211.221353ms)
|
||||
✔ a runner that ignores SIGTERM is killed when the host closes (1398.582123ms)
|
||||
✔ kill switch refuses chief and hire before launch work and a clear permits a later request (373.807885ms)
|
||||
✔ kill switch stops an actual managed namespace and records the halt reason without rewriting its bundle (372.1022ms)
|
||||
✔ a launch waiting across halt and resume cannot use its stale admission (236.835216ms)
|
||||
ℹ tests 35
|
||||
ℹ suites 0
|
||||
ℹ pass 35
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 29398.149477
|
||||
exit=0
|
||||
23:59:45Z
|
||||
@@ -0,0 +1,35 @@
|
||||
23:59:16Z
|
||||
nice=19
|
||||
✔ P3 Anthropic route uses messages at proxy root and the declared model id (9.965114ms)
|
||||
✔ P3 omitted route generates the Anthropic provider at proxy root (3.050792ms)
|
||||
✔ P3 GLM route uses completions at proxy /v1 with explicit z.ai compatibility (2.833686ms)
|
||||
✖ P3 GPT route selects a stack-owned provider instead of pi Codex OAuth (2.980797ms)
|
||||
✔ P3 a declared Codex route refuses generation while the build-time API is PENDING (4.028363ms)
|
||||
✔ P3 models file is private, references the one session key, and stores no executable values or keys (3.814909ms)
|
||||
✔ P3 every launch regenerates proxy port, declared models and own key without caching (7.841047ms)
|
||||
ℹ tests 7
|
||||
ℹ suites 0
|
||||
ℹ pass 6
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 117.036157
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/harness/tests/proxy-bundle.test.mjs:68:1
|
||||
✖ P3 GPT route selects a stack-owned provider instead of pi Codex OAuth (2.980797ms)
|
||||
Error: proxy GPT API is PENDING build-time evidence
|
||||
at Module.bundle (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/harness/pi/index.mjs:78:62)
|
||||
at buildBundle (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/src/bundle.mjs:354:23)
|
||||
at build (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:20:18)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:69:13)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
|
||||
exit=1
|
||||
23:59:16Z
|
||||
@@ -0,0 +1,18 @@
|
||||
23:57:06Z
|
||||
nice=19
|
||||
✔ P3 proxy model validation preserves each explicit wire route and the proxy model id (1.161459ms)
|
||||
✔ P3 a proxy model without route preserves its stored shape for the default Anthropic route (0.134804ms)
|
||||
✔ P3 an unknown proxy route refuses proxy-route-unknown (0.823069ms)
|
||||
✔ P3 nonproxy models retain their provider/model-only shape and reject route metadata (0.170037ms)
|
||||
✔ P3 launch.account accepts the proxy credential source without naming host variables (0.132947ms)
|
||||
✔ P3 existing environment-set accounts retain their validated representation (0.200363ms)
|
||||
ℹ tests 6
|
||||
ℹ suites 0
|
||||
ℹ pass 6
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 52.197696
|
||||
exit=0
|
||||
23:57:06Z
|
||||
@@ -0,0 +1,21 @@
|
||||
23:57:12Z
|
||||
nice=19
|
||||
✔ row 65 both harness modules load through the loader with every export the kind names (7.907431ms)
|
||||
✔ row 65 each module answers for its own harness: adapter, levels, account, web, turn end and session files (0.745524ms)
|
||||
✔ row 65 claude-code preflight: a missing credential, then a missing or relative binary, each refuse with their code (3.501824ms)
|
||||
✔ row 65 claude-code version asks MOSAIC_CLAUDE_BIN when set, else claude on PATH, else null (3.715209ms)
|
||||
✔ row 65 pi preflight passes with the repository's pi, and its version is the package pin (0.570287ms)
|
||||
✔ row 65 env passes the thinking level to both adapters, and claude-code's bundle writes its hook and MCP files (1.263458ms)
|
||||
✔ row 65 a route's thinking reaches pi as --thinking and claude as --effort, through each module's env and adapter (14.961799ms)
|
||||
✔ row 65 no core source outside packages/plugins names a harness, apart from the listed exceptions (14.014352ms)
|
||||
✔ row 65 the grep's pattern catches a quoted name and a regex alternation, and not a word in prose (0.144087ms)
|
||||
ℹ tests 9
|
||||
ℹ suites 0
|
||||
ℹ pass 9
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 95.780618
|
||||
exit=0
|
||||
23:57:12Z
|
||||
@@ -0,0 +1,74 @@
|
||||
23:57:12Z
|
||||
nice=19
|
||||
✔ C2-04: launcher workspace clones code but never the library (1409.48354ms)
|
||||
✔ P3 proxy key reaches the spawned runner but never session.json or the bundle (1360.002763ms)
|
||||
✔ bus start --pm: the launcher instance (the chief of staff) runs under its own PID namespace, registered, with a manifest (1355.188394ms)
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r77c2d6253086","instance":"chief","turn":"492fcf7a-ee92-4637-b1ee-de6d29c5f536","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r0caee85973df","instance":"coder","turn":"a8f6e869-e911-4e61-8913-9bcb8c84bca5","source":"@none","code":"baseline-unknown"}
|
||||
✔ the chief of staff launches a coder through its launch tool; the coder answers; refusals name their code (844.874148ms)
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"rdd1124f17d39","instance":"chief","turn":"99cbfb5d-a0b1-4de7-b94b-855285416cea","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r3ee360b6ea6f","instance":"pm","turn":"3299c709-0616-4d80-878e-589213b2833a","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"rdd1124f17d39","instance":"chief","turn":"aaef40d5-2c04-4a58-8019-3a9fed2740d3","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"rdd1124f17d39","instance":"chief","turn":"f7697cc5-383e-41e4-8f48-ca8032cb501f","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r3ee360b6ea6f","instance":"pm","turn":"b6f5d1de-a58e-40de-9d2c-9e79472bc8f5","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r3ee360b6ea6f","instance":"pm","turn":"7a8278f1-c934-418d-a62a-34528048a50c","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"rdd1124f17d39","instance":"chief","turn":"c8f0554c-b415-4b2c-aa8e-8eebf3253c12","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r3ee360b6ea6f","instance":"pm","turn":"d5084f6b-43d9-48b4-ada8-14c78f9a6472","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"rdd1124f17d39","instance":"chief","turn":"2b8f60d0-5e87-4823-a4c8-e7a13fdffae2","source":"@none","code":"baseline-unknown"}
|
||||
✔ a launched pm's request_staff reaches the chief of staff; no other session has the tool and the launch limits still hold (1499.426496ms)
|
||||
✔ each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or fetched and logged (4214.335578ms)
|
||||
✔ a role the broker says holds a merge decision gets land_work, its prompt line, its landing and the git socket; without it, none (731.541713ms)
|
||||
✔ REQ-LAUNCH-1 the tick starts a listed merge decision holder once, with land_work, the Landing line and its landing turn; an unlisted holder starts nothing (452.219971ms)
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r8dca21c7af83","instance":"coder","turn":"b0a65410-42a5-495d-a881-d7828ea5dc69","source":"@none","code":"baseline-unknown"}
|
||||
✔ a holder running since before the resolution is stopped once the broker releases it, land-relaunch, and the next tick starts it: landing turn first, then the message that waited (859.92414ms)
|
||||
✔ a landing run that ended isn't started again for its decision, a spent decision starts nothing, and a new decision starts the holder (813.442124ms)
|
||||
✔ after a restart, the new host's first tick starts a holder whose decision is still held (1026.204746ms)
|
||||
✔ a refused landing start is logged once per code with its decision, isn't a start, and the next tick with room starts the holder (698.000891ms)
|
||||
✔ a project clone that can't be made refuses checkout-failed, from --chief and from a session, with no run and no clone left (333.63354ms)
|
||||
✔ a runner that stops at once ends its launch with the runner's reason (392.682058ms)
|
||||
✔ a Claude Code launch with no session credential refuses before it starts (219.352153ms)
|
||||
✔ a pi launch with no own credential refuses no-session-credential (202.834512ms)
|
||||
usage: {"event":"plugin-refused","kind":"harness","name":"down","code":"missing","message":"/home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/harness/down is missing","kept":[]}
|
||||
plugins: {"event":"plugin-refused","kind":"harness","name":"down","code":"missing","message":"/home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/harness/down is missing","kept":[]}
|
||||
✖ row 65 a failed primary preflight launches the fallback on a fixture harness module, recording the route and the reason (214.464488ms)
|
||||
✔ a runner killed from the host ends its launch as killed, not failed (437.720138ms)
|
||||
✔ a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback (240.940882ms)
|
||||
✔ row 64 a role skill that isn't enabled refuses skill-not-enabled, and one that isn't a plain directory skill-refused, before anything starts (220.344629ms)
|
||||
✔ row 213 a creative instance's persona is read from <configDir>/personas: off its digest the launch fails with the mismatch as its reason (288.705681ms)
|
||||
✔ row 64 on Claude Code a role skill that isn't enabled refuses the same way, before anything starts (211.010194ms)
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r31e317a9ff60","instance":"chief","turn":"bf22f39d-2bf0-48ec-849b-bf8665a262af","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"rcb1b05afaad6","instance":"coder","turn":"40f65cf7-8938-4265-949a-c816ef7084cd","source":"@none","code":"baseline-unknown"}
|
||||
✔ a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold (1532.310766ms)
|
||||
✔ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (1163.295788ms)
|
||||
✔ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (4129.096956ms)
|
||||
✔ bus start --no-launch leaves an earlier host's live session alone: no signal, no end, sessions.json as it was (253.012747ms)
|
||||
✔ bus start --no-launch refuses every launch: the PM launch and a session's request, each logged, nothing spawned (210.12322ms)
|
||||
✔ bus start --no-launch starts no thread tick; the same host with launches runs it (1206.988059ms)
|
||||
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (200.617792ms)
|
||||
✔ an over-long launch request is refused at once, not at the 10 s idle timeout (208.31407ms)
|
||||
✔ a launch client that never closes its side doesn't hold the host's close (210.5977ms)
|
||||
✔ a runner that ignores SIGTERM is killed when the host closes (1396.242105ms)
|
||||
✔ kill switch refuses chief and hire before launch work and a clear permits a later request (356.328128ms)
|
||||
✔ kill switch stops an actual managed namespace and records the halt reason without rewriting its bundle (411.659934ms)
|
||||
✔ a launch waiting across halt and resume cannot use its stale admission (248.122427ms)
|
||||
ℹ tests 35
|
||||
ℹ suites 0
|
||||
ℹ pass 34
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 29678.41987
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/cli/tests/launcher.test.mjs:714:1
|
||||
✖ row 65 a failed primary preflight launches the fallback on a fixture harness module, recording the route and the reason (214.464488ms)
|
||||
Error [CliError]: PM launch refused: no-session-credential (fallback fixture: pi needs its own provider key or a session proxy key (primary down: fixture preflight fails))
|
||||
at file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/cli/src/launcher.mjs:1189:23
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5) {
|
||||
exitCode: 3,
|
||||
code: 'no-session-credential'
|
||||
}
|
||||
exit=1
|
||||
23:57:42Z
|
||||
@@ -0,0 +1,88 @@
|
||||
23:57:12Z
|
||||
nice=19
|
||||
✖ P3 Anthropic route uses messages at proxy root and the declared model id (8.82863ms)
|
||||
✔ P3 omitted route generates the Anthropic provider at proxy root (3.493006ms)
|
||||
✖ P3 GLM route uses completions at proxy /v1 with explicit z.ai compatibility (2.169226ms)
|
||||
✖ P3 GPT route selects a stack-owned provider instead of pi Codex OAuth (2.321456ms)
|
||||
✖ P3 models file is private, references the one session key, and stores no executable values or keys (2.285532ms)
|
||||
✖ P3 every launch regenerates proxy port, declared models and own key without caching (2.062519ms)
|
||||
ℹ tests 6
|
||||
ℹ suites 0
|
||||
ℹ pass 1
|
||||
ℹ fail 5
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 94.962091
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/harness/tests/proxy-bundle.test.mjs:35:1
|
||||
✖ P3 Anthropic route uses messages at proxy root and the declared model id (8.82863ms)
|
||||
Error: proxy GPT API is PENDING build-time evidence
|
||||
at Module.bundle (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/harness/pi/index.mjs:78:62)
|
||||
at buildBundle (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/src/bundle.mjs:354:23)
|
||||
at build (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:20:18)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:36:13)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.start (node:internal/test_runner/test:1262:17)
|
||||
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
|
||||
test at packages/harness/tests/proxy-bundle.test.mjs:57:1
|
||||
✖ P3 GLM route uses completions at proxy /v1 with explicit z.ai compatibility (2.169226ms)
|
||||
Error: proxy GPT API is PENDING build-time evidence
|
||||
at Module.bundle (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/harness/pi/index.mjs:78:62)
|
||||
at buildBundle (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/src/bundle.mjs:354:23)
|
||||
at build (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:20:18)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:58:13)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
|
||||
|
||||
test at packages/harness/tests/proxy-bundle.test.mjs:68:1
|
||||
✖ P3 GPT route selects a stack-owned provider instead of pi Codex OAuth (2.321456ms)
|
||||
Error: proxy GPT API is PENDING build-time evidence
|
||||
at Module.bundle (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/harness/pi/index.mjs:78:62)
|
||||
at buildBundle (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/src/bundle.mjs:354:23)
|
||||
at build (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:20:18)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:69:13)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
|
||||
|
||||
test at packages/harness/tests/proxy-bundle.test.mjs:79:1
|
||||
✖ P3 models file is private, references the one session key, and stores no executable values or keys (2.285532ms)
|
||||
Error: proxy GPT API is PENDING build-time evidence
|
||||
at Module.bundle (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/harness/pi/index.mjs:78:62)
|
||||
at buildBundle (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/src/bundle.mjs:354:23)
|
||||
at build (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:20:18)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:81:13)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
|
||||
|
||||
test at packages/harness/tests/proxy-bundle.test.mjs:94:1
|
||||
✖ P3 every launch regenerates proxy port, declared models and own key without caching (2.062519ms)
|
||||
Error: proxy GPT API is PENDING build-time evidence
|
||||
at Module.bundle (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/harness/pi/index.mjs:78:62)
|
||||
at buildBundle (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/src/bundle.mjs:354:23)
|
||||
at build (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:20:18)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:95:17)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
|
||||
exit=1
|
||||
23:57:12Z
|
||||
@@ -0,0 +1,17 @@
|
||||
23:57:06Z
|
||||
nice=19
|
||||
✔ P3 Claude env receives the session proxy URL and key, without another agent or host credential (2.459573ms)
|
||||
✔ P3 Claude preflight accepts a proxy key without a host provider credential (1.019636ms)
|
||||
✔ P3 pi refuses no-session-credential with no provider key (0.965529ms)
|
||||
✔ P3 pi refuses no-session-credential with only a Claude OAuth token (0.264623ms)
|
||||
✔ P3 pi accepts its own provider key or a session proxy key (0.345262ms)
|
||||
ℹ tests 5
|
||||
ℹ suites 0
|
||||
ℹ pass 5
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 56.359357
|
||||
exit=0
|
||||
23:57:06Z
|
||||
@@ -0,0 +1,14 @@
|
||||
23:57:06Z
|
||||
nice=19
|
||||
✔ P3 proxy sessionEnv removes every instance provider credential without widening ENV_ALLOW (0.976855ms)
|
||||
✔ P3 no proxy retains the full existing allowlist and run environment (0.214793ms)
|
||||
ℹ tests 2
|
||||
ℹ suites 0
|
||||
ℹ pass 2
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 47.349955
|
||||
exit=0
|
||||
23:57:06Z
|
||||
@@ -0,0 +1,42 @@
|
||||
// Parse static imports without evaluating any repository module. This is an audit tool, not a test runner.
|
||||
// Usage: node --experimental-vm-modules agents/fethry/work/queue-332/import-run-list.mjs
|
||||
import { readdirSync, readFileSync } from 'node:fs';
|
||||
import { dirname, join, relative, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { SourceTextModule } from 'node:vm';
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), '../../../..');
|
||||
const files = [];
|
||||
function walk(dir) {
|
||||
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
||||
if (['node_modules', '.git', 'dist'].includes(entry.name)) continue;
|
||||
const path = join(dir, entry.name);
|
||||
if (entry.isDirectory()) walk(path);
|
||||
else if (entry.isFile() && path.endsWith('.mjs')) files.push(path);
|
||||
}
|
||||
}
|
||||
for (const dir of ['packages', 'scripts', 'adapters']) walk(join(root, dir));
|
||||
const graph = new Map();
|
||||
for (const file of files) {
|
||||
const module = new SourceTextModule(readFileSync(file, 'utf8'), { identifier: file });
|
||||
graph.set(file, module.dependencySpecifiers.filter(spec => spec.startsWith('.')).map(spec => resolve(dirname(file), spec)));
|
||||
}
|
||||
// The loader imports the configured harness via a computed path. Explicit runtime import edges:
|
||||
const loader = join(root, 'packages/plugins/src/loader.mjs');
|
||||
graph.get(loader).push(...['claude-code', 'pi'].map(name => join(root, 'packages/plugins/harness', name, 'index.mjs')));
|
||||
const changed = new Set([
|
||||
'packages/seat/src/session.mjs', 'packages/business/src/launch.mjs',
|
||||
'packages/plugins/harness/claude-code/index.mjs', 'packages/plugins/harness/pi/index.mjs',
|
||||
'packages/harness/src/bundle.mjs', 'packages/cli/src/launcher.mjs',
|
||||
].map(file => join(root, file)));
|
||||
function affected(file, seen = new Set()) {
|
||||
if (changed.has(file)) return true;
|
||||
if (seen.has(file)) return false;
|
||||
seen.add(file);
|
||||
return (graph.get(file) ?? []).some(dep => affected(dep, seen));
|
||||
}
|
||||
const excluded = 'packages/cli/tests/case1-accept.test.mjs';
|
||||
for (const file of files.filter(file => file.endsWith('.test.mjs')).sort()) {
|
||||
if (!affected(file)) continue;
|
||||
const path = relative(root, file);
|
||||
console.log(`${path === excluded ? 'EXCLUDED (mos-1612) ' : ''}${path}`);
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
// Row332 evidence. The only network-capable phase is the single authorized fetch.
|
||||
// offline runs in unshare -rn; a transparent local tap records pi payload shapes
|
||||
// without retaining headers, then pipes bytes unchanged to the pinned proxy.
|
||||
import { mkdirSync, readFileSync, readlinkSync, writeFileSync } from 'node:fs';
|
||||
import { dirname, join, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { getPriority, setPriority, networkInterfaces } from 'node:os';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { once } from 'node:events';
|
||||
import http from 'node:http';
|
||||
import { fetchPinned } from '../../../../packages/model-proxy/src/fetch.mjs';
|
||||
import { PIN } from '../../../../packages/model-proxy/src/common.mjs';
|
||||
import { initialConfig, verifyInstall } from '../../../../packages/model-proxy/src/settings.mjs';
|
||||
const root = process.env.P3_EVIDENCE_ROOT;
|
||||
if (!root || !root.startsWith('/mnt/storage/scratch/') || resolve(root) !== root) throw Error('owned evidence root required');
|
||||
const enteredNice = getPriority(0);
|
||||
setPriority(0, 19);
|
||||
const repo = resolve(dirname(fileURLToPath(import.meta.url)), '../../../..');
|
||||
const mode = process.argv[2];
|
||||
const fakeKeys = ['fx332-client', 'fx332-upstream', 'fx332-manage'];
|
||||
function redact(value) {
|
||||
let text = String(value);
|
||||
for (const key of fakeKeys) text = text.split(key).join('[redacted]');
|
||||
return text;
|
||||
}
|
||||
const emit = (value) => console.log(redact(JSON.stringify(value)));
|
||||
if (mode === 'fetch') {
|
||||
const dataRoot = join(root, 'data');
|
||||
mkdirSync(dataRoot, { mode: 0o700 });
|
||||
const installed = await fetchPinned({ dataRoot, pin: PIN });
|
||||
writeFileSync(join(root, 'installed.json'), JSON.stringify(installed) + '\n', { mode: 0o600, flag: 'wx' });
|
||||
emit({ phase: 'fetch', version: PIN.version, binarySha256: PIN.binary.sha256,
|
||||
panelVersion: PIN.panel.version, panelSha256: PIN.panel.sha256, enteredNice, nice: getPriority(0), verified: true });
|
||||
} else if (mode === 'offline' || mode === 'glm-tool') {
|
||||
const withTool = mode === 'glm-tool';
|
||||
if (readlinkSync('/proc/self/ns/net') === process.env.P3_HOST_NET_NS) throw Error('network namespace was not isolated');
|
||||
if (Object.keys(networkInterfaces()).some(name => name !== 'lo')) throw Error('loopback-only namespace required');
|
||||
// Re-hash the already fetched assets; this function performs no network I/O.
|
||||
const installed = verifyInstall(join(root, 'data'), PIN);
|
||||
const attemptRoot = join(root, withTool ? 'attempt3-tool' : 'attempt2');
|
||||
mkdirSync(attemptRoot, { mode: 0o700 });
|
||||
setPriority(0, 19);
|
||||
emit({ phase: 'isolation', interfaces: Object.keys(networkInterfaces()), enteredNice, nice: getPriority(0), version: PIN.version,
|
||||
binarySha256: PIN.binary.sha256, panelSha256: PIN.panel.sha256, reverified: true });
|
||||
const requests = [], responses = [], upstreamRequests = [], children = new Set();
|
||||
const managed = (args, options = {}) => {
|
||||
setPriority(0, 19);
|
||||
const child = spawn('/usr/bin/timeout', ['-k', '10', ...args], { env: {
|
||||
PATH: '/usr/bin:/bin', HOME: join(root, 'home'), TMPDIR: process.env.TMPDIR,
|
||||
LANG: 'C.UTF-8', ...options.env,
|
||||
}, cwd: root, stdio: ['ignore', 'pipe', 'pipe'] });
|
||||
children.add(child);
|
||||
setPriority(child.pid, 19);
|
||||
child.once('close', () => children.delete(child));
|
||||
return child;
|
||||
};
|
||||
const summary = body => ({ keys: Object.keys(body).sort(), model: body.model, stream: body.stream,
|
||||
roles: Array.isArray(body.messages) ? body.messages.map(message => message.role) : undefined,
|
||||
inputTypes: Array.isArray(body.input) ? body.input.map(item => item.type) : typeof body.input,
|
||||
...(Object.hasOwn(body, 'reasoning') ? { reasoning: body.reasoning } : {}),
|
||||
...(Object.hasOwn(body, 'thinking') ? { thinking: body.thinking } : {}),
|
||||
...(Object.hasOwn(body, 'reasoning_effort') ? { reasoning_effort: body.reasoning_effort } : {}),
|
||||
...(Object.hasOwn(body, 'enable_thinking') ? { enable_thinking: body.enable_thinking } : {}),
|
||||
...(Object.hasOwn(body, 'tool_stream') ? { tool_stream: body.tool_stream } : {}),
|
||||
...(Array.isArray(body.tools) ? { toolNames: body.tools.map(tool => tool.function?.name) } : {}),
|
||||
...(Object.hasOwn(body, 'stream_options') ? { stream_options: body.stream_options } : {}),
|
||||
...(Object.hasOwn(body, 'max_tokens') ? { max_tokens: body.max_tokens } : {}),
|
||||
...(Object.hasOwn(body, 'max_completion_tokens') ? { max_completion_tokens: body.max_completion_tokens } : {}) });
|
||||
const payload = async req => { let text = ''; for await (const part of req) text += part; return JSON.parse(text || '{}'); };
|
||||
const listen = async server => { await new Promise((ok, no) => { server.once('error', no); server.listen(0, '127.0.0.1', ok); }); return server.address().port; };
|
||||
const marker = 'P332_OFFLINE_OK';
|
||||
const upstream = http.createServer(async (req, res) => {
|
||||
const body = await payload(req);
|
||||
upstreamRequests.push({ method: req.method, path: req.url, ...summary(body) });
|
||||
const item = { id: 'msg_fixture', type: 'message', role: 'assistant', status: 'completed', content: [{ type: 'output_text', text: marker, annotations: [] }] };
|
||||
if (req.url.endsWith('/chat/completions')) {
|
||||
res.writeHead(200, { 'Content-Type': 'text/event-stream' });
|
||||
const chunk = (delta, finish_reason = null) => res.write(`data: ${JSON.stringify({ id: 'chat_fixture', object: 'chat.completion.chunk', created: 1, model: body.model, choices: [{ index: 0, delta, finish_reason }] })}\n\n`);
|
||||
chunk({ role: 'assistant', content: marker }); chunk({}, 'stop');
|
||||
res.write(`data: ${JSON.stringify({ id: 'chat_fixture', object: 'chat.completion.chunk', created: 1, model: body.model, choices: [], usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 } })}\n\n`);
|
||||
res.end('data: [DONE]\n\n');
|
||||
} else if (req.url.endsWith('/responses')) {
|
||||
res.writeHead(200, { 'Content-Type': 'text/event-stream' });
|
||||
const event = (type, more) => res.write(`event: ${type}\ndata: ${JSON.stringify({ type, ...more })}\n\n`);
|
||||
const response = { id: 'resp_fixture', object: 'response', model: body.model, status: 'in_progress', output: [] };
|
||||
event('response.created', { response });
|
||||
event('response.output_item.added', { output_index: 0, item: { ...item, status: 'in_progress', content: [] } });
|
||||
event('response.content_part.added', { item_id: item.id, output_index: 0, content_index: 0, part: { type: 'output_text', text: '', annotations: [] } });
|
||||
event('response.output_text.delta', { item_id: item.id, output_index: 0, content_index: 0, delta: marker });
|
||||
event('response.output_text.done', { item_id: item.id, output_index: 0, content_index: 0, text: marker });
|
||||
event('response.output_item.done', { output_index: 0, item });
|
||||
event('response.completed', { response: { ...response, status: 'completed', output: [item], usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 } } });
|
||||
res.end();
|
||||
} else { res.writeHead(404); res.end('{"error":"stand-in path refused"}'); }
|
||||
});
|
||||
let proxy, tap;
|
||||
let proxyText = '';
|
||||
const corrections = {};
|
||||
const restoreNice = pid => {
|
||||
try {
|
||||
const priority = getPriority(pid);
|
||||
if (priority !== 19) { corrections[priority] = (corrections[priority] ?? 0) + 1; setPriority(pid, 19); }
|
||||
const descendants = readFileSync(`/proc/${pid}/task/${pid}/children`, 'utf8').trim().split(/\s+/).filter(Boolean);
|
||||
for (const child of descendants) restoreNice(Number(child));
|
||||
} catch {}
|
||||
};
|
||||
const keepNice = setInterval(() => restoreNice(process.pid), 100);
|
||||
keepNice.unref();
|
||||
try {
|
||||
const upstreamPort = await listen(upstream);
|
||||
const reservation = http.createServer(); const proxyPort = await listen(reservation);
|
||||
await new Promise(ok => reservation.close(ok));
|
||||
if (proxyPort === 8317 || upstreamPort === 8317) throw Error('reserved host-proxy port excluded');
|
||||
const dataRoot = join(attemptRoot, 'offline');
|
||||
mkdirSync(dataRoot, { mode: 0o700 });
|
||||
mkdirSync(join(dataRoot, 'proxy'), { mode: 0o700 });
|
||||
mkdirSync(join(dataRoot, 'proxy', 'auth'), { mode: 0o700 });
|
||||
const config = initialConfig({ dataRoot, port: proxyPort, key: fakeKeys[2], sentinel: fakeKeys[0] });
|
||||
config.management['disable-control-panel'] = true;
|
||||
config.observability.logs['logging-to-file'] = false;
|
||||
// Bundled panel1.26.7: Ip reads api-keys.openai-compatibility;
|
||||
// Fp/Np require group.keys; xv/hv serialize keys:[{"api-key":...}].
|
||||
config['api-keys'] = { 'openai-compatibility': [{ name: 'p332-standin', 'base-url': `http://127.0.0.1:${upstreamPort}/v1`,
|
||||
keys: [{ 'api-key': fakeKeys[1] }], models: [{ name: 'gpt-fixture' }, { name: 'glm-fixture' }] }] };
|
||||
const configFile = join(dataRoot, 'config.yaml');
|
||||
writeFileSync(configFile, JSON.stringify(config, null, 2) + '\n', { mode: 0o600, flag: 'wx' });
|
||||
proxy = managed(['200', installed.binary, '-config', configFile, '-local-model'], { env: { MANAGEMENT_STATIC_PATH: dirname(installed.panel) } });
|
||||
proxy.stdout.on('data', bytes => { proxyText += bytes; });
|
||||
proxy.stderr.on('data', bytes => { proxyText += bytes; });
|
||||
let catalog, ids = [];
|
||||
const readinessDeadline = Date.now() + 30000;
|
||||
for (let poll = 1; Date.now() < readinessDeadline; poll++) {
|
||||
if (proxy.exitCode !== null) throw Error('proxy exited before readiness');
|
||||
try {
|
||||
const result = await fetch(`http://127.0.0.1:${proxyPort}/v1/models`, { headers: { Authorization: `Bearer ${fakeKeys[0]}` }, signal: AbortSignal.timeout(1000) });
|
||||
catalog = result.ok ? await result.json() : null;
|
||||
ids = (catalog?.data ?? []).map(model => model.id);
|
||||
emit({ phase: 'readiness', poll, status: result.status, modelCount: ids.length });
|
||||
if (['gpt-fixture', 'glm-fixture'].every(id => ids.includes(id))) break;
|
||||
} catch { emit({ phase: 'readiness', poll, status: 'not-ready', modelCount: 0 }); }
|
||||
await new Promise(ok => setTimeout(ok, 1000));
|
||||
}
|
||||
if (!catalog) throw Error('proxy readiness failed');
|
||||
emit({ phase: 'catalog', modelIds: ids });
|
||||
if (!['gpt-fixture', 'glm-fixture'].every(id => ids.includes(id))) throw Error('stand-in routes absent from proxy catalog');
|
||||
tap = http.createServer(async (req, res) => {
|
||||
let raw = ''; for await (const part of req) raw += part;
|
||||
const body = JSON.parse(raw || '{}');
|
||||
const index = requests.push({ method: req.method, path: req.url, ...summary(body) }) - 1;
|
||||
const outgoing = http.request({ hostname: '127.0.0.1', port: proxyPort, method: req.method, path: req.url,
|
||||
headers: { ...req.headers, host: `127.0.0.1:${proxyPort}` } }, incoming => {
|
||||
const chunks = [];
|
||||
res.writeHead(incoming.statusCode, incoming.headers);
|
||||
incoming.on('data', bytes => { chunks.push(bytes); res.write(bytes); });
|
||||
incoming.on('end', () => {
|
||||
const text = Buffer.concat(chunks).toString();
|
||||
const events = text.split('\n').filter(line => line.startsWith('event: ')).map(line => line.slice(7));
|
||||
responses.push({ request: index, status: incoming.statusCode, events, marker: text.includes(marker),
|
||||
...(incoming.statusCode >= 400 ? { errorBody: redact(text).slice(0, 800) } : {}) });
|
||||
res.end();
|
||||
});
|
||||
});
|
||||
outgoing.on('error', () => { res.writeHead(502); res.end(); });
|
||||
// Preserve exact body bytes and never log a header value.
|
||||
outgoing.end(raw);
|
||||
});
|
||||
const tapPort = await listen(tap);
|
||||
if (tapPort === 8317) throw Error('reserved host-proxy port excluded');
|
||||
const scenarios = [
|
||||
['gpt-responses', 'openai-responses', 'gpt-fixture', null],
|
||||
['gpt-completions', 'openai-completions', 'gpt-fixture', null],
|
||||
['glm-completions', 'openai-completions', 'glm-fixture', { thinkingFormat: 'zai', zaiToolStream: true }],
|
||||
];
|
||||
for (const [name, api, model, compat] of (withTool ? scenarios.filter(([name]) => name === 'glm-completions') : scenarios)) {
|
||||
const turnHome = join(attemptRoot, name, 'home');
|
||||
const agentDir = join(turnHome, '.pi', 'agent');
|
||||
mkdirSync(agentDir, { recursive: true, mode: 0o700 });
|
||||
const models = { providers: { 'p332-probe': { baseUrl: `http://127.0.0.1:${tapPort}/v1`, api,
|
||||
apiKey: '$ANTHROPIC_API_KEY', ...(compat ? { compat } : {}), models: [{ id: model, reasoning: true }] } } };
|
||||
writeFileSync(join(agentDir, 'models.json'), JSON.stringify(models) + '\n', { mode: 0o600, flag: 'wx' });
|
||||
writeFileSync(join(agentDir, 'settings.json'), JSON.stringify({ retry: { enabled: false, maxRetries: 0, provider: { maxRetries: 0 } } }) + '\n', { mode: 0o600, flag: 'wx' });
|
||||
const reqStart = requests.length, upstreamStart = upstreamRequests.length, responseStart = responses.length;
|
||||
const pi = managed(['50', '/usr/bin/node', join(repo, 'node_modules/@earendil-works/pi-coding-agent/dist/cli.js'),
|
||||
'--no-extensions', '--no-skills', '--no-prompt-templates', '--no-themes', '--no-context-files', '--no-session',
|
||||
...(withTool ? ['--tools', 'read'] : ['--no-tools']),
|
||||
'--provider', 'p332-probe', '--model', model, '--thinking', 'low', '-p', 'Reply P332_OFFLINE_OK only.'],
|
||||
{ env: { HOME: turnHome, PI_CODING_AGENT_DIR: agentDir, ANTHROPIC_API_KEY: fakeKeys[0] } });
|
||||
let stdout = '', stderr = '';
|
||||
pi.stdout.on('data', bytes => { stdout += bytes; });
|
||||
pi.stderr.on('data', bytes => { stderr += bytes; });
|
||||
const [exit, signal] = await once(pi, 'close');
|
||||
emit({ phase: 'turn', name: withTool ? `${name}-tool` : name, api, exit, signal, marker: stdout.includes(marker),
|
||||
piRequests: requests.slice(reqStart), proxyResponses: responses.slice(responseStart), upstreamRequests: upstreamRequests.slice(upstreamStart),
|
||||
...(exit !== 0 ? { stderr: redact(stderr).slice(0, 1200), stdout: redact(stdout).slice(0, 1200) } : {}), nice: getPriority(0) });
|
||||
}
|
||||
emit({ phase: 'conclusion', limitation: 'Stand-in openai-compatibility credential only; no Codex OAuth credential or real-auth turn. This script never selects the production API.' });
|
||||
emit({ phase: 'nice-corrections', restoredTo: 19, observedBeforeRestore: corrections });
|
||||
} catch (error) {
|
||||
emit({ phase: 'error', message: error.message, proxyDiagnostic: redact(proxyText).slice(-3000) });
|
||||
process.exitCode = 1;
|
||||
} finally {
|
||||
clearInterval(keepNice);
|
||||
for (const child of [...children]) child.kill('SIGTERM');
|
||||
for (const child of [...children]) {
|
||||
await Promise.race([once(child, 'close'), new Promise(ok => setTimeout(ok, 1200))]);
|
||||
if (child.exitCode === null) child.kill('SIGKILL');
|
||||
}
|
||||
for (const server of [tap, upstream].filter(Boolean)) { server.closeAllConnections(); await new Promise(ok => server.close(ok)); }
|
||||
emit({ phase: 'cleanup', remainingManagedChildren: children.size });
|
||||
}
|
||||
} else { throw Error('fetch, offline or glm-tool phase required'); }
|
||||
@@ -0,0 +1,104 @@
|
||||
23:41:37Z
|
||||
nice=19
|
||||
✖ P3 proxy model validation preserves each explicit wire route and the proxy model id (0.843397ms)
|
||||
✖ P3 a proxy model without route refuses proxy-route-missing (0.621133ms)
|
||||
✖ P3 an unknown proxy route refuses proxy-route-unknown (1.448927ms)
|
||||
✔ P3 nonproxy models retain their provider/model-only shape and reject route metadata (0.541212ms)
|
||||
✖ P3 launch.account accepts the proxy credential source without naming host variables (0.159973ms)
|
||||
✔ P3 existing environment-set accounts retain their validated representation (0.209852ms)
|
||||
ℹ tests 6
|
||||
ℹ suites 0
|
||||
ℹ pass 2
|
||||
ℹ fail 4
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 58.617716
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/business/tests/proxy-launch.test.mjs:6:1
|
||||
✖ P3 proxy model validation preserves each explicit wire route and the proxy model id (0.843397ms)
|
||||
Error [BusinessError]: unsupported launch.models.selected key: "route"
|
||||
at refuse (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/src/errors.mjs:12:9)
|
||||
at rejectUnknownKeys (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/src/util.mjs:17:33)
|
||||
at file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/src/launch.mjs:18:5
|
||||
at Array.map (<anonymous>)
|
||||
at checkModels (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/src/launch.mjs:15:51)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/tests/proxy-launch.test.mjs:9:22)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.start (node:internal/test_runner/test:1262:17)
|
||||
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
|
||||
exitCode: 2
|
||||
}
|
||||
|
||||
test at packages/business/tests/proxy-launch.test.mjs:13:1
|
||||
✖ P3 a proxy model without route refuses proxy-route-missing (0.621133ms)
|
||||
AssertionError [ERR_ASSERTION]: Missing expected exception.
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/tests/proxy-launch.test.mjs:14:10)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: undefined,
|
||||
expected: { code: 'proxy-route-missing' },
|
||||
operator: 'throws',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/business/tests/proxy-launch.test.mjs:19:1
|
||||
✖ P3 an unknown proxy route refuses proxy-route-unknown (1.448927ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
+ Comparison {}
|
||||
- Comparison {
|
||||
- code: 'proxy-route-unknown'
|
||||
- }
|
||||
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/tests/proxy-launch.test.mjs:21:12)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: BusinessError: unsupported launch.models.selected key: "route"
|
||||
at refuse (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/src/errors.mjs:12:9)
|
||||
at rejectUnknownKeys (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/src/util.mjs:17:33)
|
||||
at file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/src/launch.mjs:18:5
|
||||
at Array.map (<anonymous>)
|
||||
at checkModels (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/src/launch.mjs:15:51)
|
||||
at assert.throws.code (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/tests/proxy-launch.test.mjs:21:25)
|
||||
at getActual (node:assert:580:5)
|
||||
at strict.throws (node:assert:728:24)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/tests/proxy-launch.test.mjs:21:12)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14),
|
||||
expected: { code: 'proxy-route-unknown' },
|
||||
operator: 'throws',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/business/tests/proxy-launch.test.mjs:34:1
|
||||
✖ P3 launch.account accepts the proxy credential source without naming host variables (0.159973ms)
|
||||
Error [BusinessError]: launch.account.credential must name environment variables, joined with "+"
|
||||
at refuse (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/src/errors.mjs:12:9)
|
||||
at checkAccount (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/src/launch.mjs:86:5)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/tests/proxy-launch.test.mjs:35:20)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
exitCode: 2
|
||||
}
|
||||
exit=1
|
||||
23:41:37Z
|
||||
@@ -0,0 +1,107 @@
|
||||
23:44:40Z
|
||||
nice=19
|
||||
✖ P3 Anthropic route uses messages at proxy root and the declared model id (10.834783ms)
|
||||
✖ P3 GLM route uses completions at proxy /v1 with explicit z.ai compatibility (3.078049ms)
|
||||
✖ P3 GPT route selects a stack-owned provider instead of pi Codex OAuth (2.606176ms)
|
||||
✖ P3 models file is private, references the one session key, and stores no executable values or keys (2.917109ms)
|
||||
✖ P3 every launch regenerates proxy port, declared models and own key without caching (2.778195ms)
|
||||
ℹ tests 5
|
||||
ℹ suites 0
|
||||
ℹ pass 0
|
||||
ℹ fail 5
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 117.630612
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/harness/tests/proxy-bundle.test.mjs:35:1
|
||||
✖ P3 Anthropic route uses messages at proxy root and the declared model id (10.834783ms)
|
||||
Error: ENOENT: no such file or directory, open '/mnt/storage/scratch/tmp/gxWP/p332-ddGpq6/run/home/.pi/agent/models.json'
|
||||
at readFileSync (node:fs:621:20)
|
||||
at build (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:28:43)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:36:13)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.start (node:internal/test_runner/test:1262:17)
|
||||
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/mnt/storage/scratch/tmp/gxWP/p332-ddGpq6/run/home/.pi/agent/models.json'
|
||||
}
|
||||
|
||||
test at packages/harness/tests/proxy-bundle.test.mjs:45:1
|
||||
✖ P3 GLM route uses completions at proxy /v1 with explicit z.ai compatibility (3.078049ms)
|
||||
Error: ENOENT: no such file or directory, open '/mnt/storage/scratch/tmp/gxWP/p332-7vqUkh/run/home/.pi/agent/models.json'
|
||||
at readFileSync (node:fs:621:20)
|
||||
at build (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:28:43)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:46:13)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/mnt/storage/scratch/tmp/gxWP/p332-7vqUkh/run/home/.pi/agent/models.json'
|
||||
}
|
||||
|
||||
test at packages/harness/tests/proxy-bundle.test.mjs:56:1
|
||||
✖ P3 GPT route selects a stack-owned provider instead of pi Codex OAuth (2.606176ms)
|
||||
Error: ENOENT: no such file or directory, open '/mnt/storage/scratch/tmp/gxWP/p332-xqR4OI/run/home/.pi/agent/models.json'
|
||||
at readFileSync (node:fs:621:20)
|
||||
at build (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:28:43)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:57:13)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/mnt/storage/scratch/tmp/gxWP/p332-xqR4OI/run/home/.pi/agent/models.json'
|
||||
}
|
||||
|
||||
test at packages/harness/tests/proxy-bundle.test.mjs:67:1
|
||||
✖ P3 models file is private, references the one session key, and stores no executable values or keys (2.917109ms)
|
||||
Error: ENOENT: no such file or directory, open '/mnt/storage/scratch/tmp/gxWP/p332-jAVWvp/run/home/.pi/agent/models.json'
|
||||
at readFileSync (node:fs:621:20)
|
||||
at build (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:28:43)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:69:13)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/mnt/storage/scratch/tmp/gxWP/p332-jAVWvp/run/home/.pi/agent/models.json'
|
||||
}
|
||||
|
||||
test at packages/harness/tests/proxy-bundle.test.mjs:82:1
|
||||
✖ P3 every launch regenerates proxy port, declared models and own key without caching (2.778195ms)
|
||||
Error: ENOENT: no such file or directory, open '/mnt/storage/scratch/tmp/gxWP/p332-yUgdwP/run/home/.pi/agent/models.json'
|
||||
at readFileSync (node:fs:621:20)
|
||||
at build (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:28:43)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:83:17)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
errno: -2,
|
||||
code: 'ENOENT',
|
||||
syscall: 'open',
|
||||
path: '/mnt/storage/scratch/tmp/gxWP/p332-yUgdwP/run/home/.pi/agent/models.json'
|
||||
}
|
||||
exit=1
|
||||
23:44:40Z
|
||||
@@ -0,0 +1,99 @@
|
||||
23:42:41Z
|
||||
nice=19
|
||||
✖ P3 Anthropic route uses messages at proxy root and the declared model id (6.017482ms)
|
||||
✖ P3 GLM route uses completions at proxy /v1 with explicit z.ai compatibility (1.65735ms)
|
||||
✖ P3 GPT route selects a stack-owned provider instead of pi Codex OAuth (1.23753ms)
|
||||
✖ P3 models file is private, references the one session key, and stores no executable values or keys (1.202284ms)
|
||||
✖ P3 every launch regenerates proxy port, declared models and own key without caching (1.210998ms)
|
||||
ℹ tests 5
|
||||
ℹ suites 0
|
||||
ℹ pass 0
|
||||
ℹ fail 5
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 88.02888
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/harness/tests/proxy-bundle.test.mjs:35:1
|
||||
✖ P3 Anthropic route uses messages at proxy root and the declared model id (6.017482ms)
|
||||
Error [BusinessError]: /mnt/storage/scratch/tmp/gQE5/mosaic-harness-biz-FaxPH3/businesses/acme.json launch: instance pm selects unknown model a
|
||||
at refuse (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/src/errors.mjs:12:9)
|
||||
at checkLaunch (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/src/business.mjs:204:125)
|
||||
at validateBusinessDocument (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/src/business.mjs:310:57)
|
||||
at resolvedFor (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/helpers.mjs:46:20)
|
||||
at build (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:17:20)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:36:13)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.start (node:internal/test_runner/test:1262:17)
|
||||
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
|
||||
exitCode: 2
|
||||
}
|
||||
|
||||
test at packages/harness/tests/proxy-bundle.test.mjs:45:1
|
||||
✖ P3 GLM route uses completions at proxy /v1 with explicit z.ai compatibility (1.65735ms)
|
||||
Error [BusinessError]: /mnt/storage/scratch/tmp/gQE5/mosaic-harness-biz-rtXOnL/businesses/acme.json launch: instance pm selects unknown model z
|
||||
at refuse (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/src/errors.mjs:12:9)
|
||||
at checkLaunch (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/src/business.mjs:204:125)
|
||||
at validateBusinessDocument (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/src/business.mjs:310:57)
|
||||
at resolvedFor (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/helpers.mjs:46:20)
|
||||
at build (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:17:20)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:46:13)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19) {
|
||||
exitCode: 2
|
||||
}
|
||||
|
||||
test at packages/harness/tests/proxy-bundle.test.mjs:56:1
|
||||
✖ P3 GPT route selects a stack-owned provider instead of pi Codex OAuth (1.23753ms)
|
||||
Error [BusinessError]: /mnt/storage/scratch/tmp/gQE5/mosaic-harness-biz-IGXaeo/businesses/acme.json launch: instance pm selects unknown model g
|
||||
at refuse (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/src/errors.mjs:12:9)
|
||||
at checkLaunch (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/src/business.mjs:204:125)
|
||||
at validateBusinessDocument (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/src/business.mjs:310:57)
|
||||
at resolvedFor (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/helpers.mjs:46:20)
|
||||
at build (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:17:20)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:57:13)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19) {
|
||||
exitCode: 2
|
||||
}
|
||||
|
||||
test at packages/harness/tests/proxy-bundle.test.mjs:67:1
|
||||
✖ P3 models file is private, references the one session key, and stores no executable values or keys (1.202284ms)
|
||||
Error [BusinessError]: /mnt/storage/scratch/tmp/gQE5/mosaic-harness-biz-z5VTgc/businesses/acme.json launch: instance pm selects unknown model a
|
||||
at refuse (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/src/errors.mjs:12:9)
|
||||
at checkLaunch (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/src/business.mjs:204:125)
|
||||
at validateBusinessDocument (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/src/business.mjs:310:57)
|
||||
at resolvedFor (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/helpers.mjs:46:20)
|
||||
at build (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:17:20)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:69:13)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19) {
|
||||
exitCode: 2
|
||||
}
|
||||
|
||||
test at packages/harness/tests/proxy-bundle.test.mjs:82:1
|
||||
✖ P3 every launch regenerates proxy port, declared models and own key without caching (1.210998ms)
|
||||
Error [BusinessError]: /mnt/storage/scratch/tmp/gQE5/mosaic-harness-biz-83xrrf/businesses/acme.json launch: instance pm selects unknown model a
|
||||
at refuse (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/src/errors.mjs:12:9)
|
||||
at checkLaunch (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/src/business.mjs:204:125)
|
||||
at validateBusinessDocument (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/business/src/business.mjs:310:57)
|
||||
at resolvedFor (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/helpers.mjs:46:20)
|
||||
at build (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:17:20)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:83:17)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19) {
|
||||
exitCode: 2
|
||||
}
|
||||
exit=1
|
||||
23:42:41Z
|
||||
@@ -0,0 +1,77 @@
|
||||
23:41:42Z
|
||||
nice=19
|
||||
✖ P3 Anthropic route uses messages at proxy root and the declared model id (8.181278ms)
|
||||
✖ P3 GLM route uses completions at proxy /v1 with explicit z.ai compatibility (1.652506ms)
|
||||
✖ P3 GPT route selects a stack-owned provider instead of pi Codex OAuth (1.297337ms)
|
||||
✖ P3 models file is private, references the one session key, and stores no executable values or keys (1.318875ms)
|
||||
✖ P3 every launch regenerates proxy port, declared models and own key without caching (1.353193ms)
|
||||
ℹ tests 5
|
||||
ℹ suites 0
|
||||
ℹ pass 0
|
||||
ℹ fail 5
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 104.116628
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/harness/tests/proxy-bundle.test.mjs:34:1
|
||||
✖ P3 Anthropic route uses messages at proxy root and the declared model id (8.181278ms)
|
||||
TypeError: Cannot assign to read only property 'vars' of object '#<Object>'
|
||||
at build (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:18:17)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:35:13)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.start (node:internal/test_runner/test:1262:17)
|
||||
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
|
||||
test at packages/harness/tests/proxy-bundle.test.mjs:44:1
|
||||
✖ P3 GLM route uses completions at proxy /v1 with explicit z.ai compatibility (1.652506ms)
|
||||
TypeError: Cannot assign to read only property 'vars' of object '#<Object>'
|
||||
at build (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:18:17)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:45:13)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
|
||||
|
||||
test at packages/harness/tests/proxy-bundle.test.mjs:55:1
|
||||
✖ P3 GPT route selects a stack-owned provider instead of pi Codex OAuth (1.297337ms)
|
||||
TypeError: Cannot assign to read only property 'vars' of object '#<Object>'
|
||||
at build (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:18:17)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:56:13)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
|
||||
|
||||
test at packages/harness/tests/proxy-bundle.test.mjs:66:1
|
||||
✖ P3 models file is private, references the one session key, and stores no executable values or keys (1.318875ms)
|
||||
TypeError: Cannot assign to read only property 'vars' of object '#<Object>'
|
||||
at build (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:18:17)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:68:13)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
|
||||
|
||||
test at packages/harness/tests/proxy-bundle.test.mjs:81:1
|
||||
✖ P3 every launch regenerates proxy port, declared models and own key without caching (1.353193ms)
|
||||
TypeError: Cannot assign to read only property 'vars' of object '#<Object>'
|
||||
at build (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:18:17)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:82:17)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
|
||||
exit=1
|
||||
23:41:42Z
|
||||
@@ -0,0 +1,78 @@
|
||||
23:41:55Z
|
||||
nice=19
|
||||
✔ C2-04: launcher workspace clones code but never the library (1499.521161ms)
|
||||
✖ P3 proxy key reaches the spawned runner but never session.json or the bundle (40.623657ms)
|
||||
✔ bus start --pm: the launcher instance (the chief of staff) runs under its own PID namespace, registered, with a manifest (1361.267044ms)
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r61b9e08ebd95","instance":"chief","turn":"a017a278-0466-41ec-b7d4-d05d7bb2b4e8","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r786eccbac72a","instance":"coder","turn":"5f857a62-f7e6-4662-a745-852935445baf","source":"@none","code":"baseline-unknown"}
|
||||
✔ the chief of staff launches a coder through its launch tool; the coder answers; refusals name their code (853.066ms)
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"raa425c1d9f99","instance":"chief","turn":"94eec92a-4189-4188-abf0-c0dd27eb3f7a","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r91d263cb44fe","instance":"pm","turn":"b3030547-e499-4465-ba6f-387ca92e7b2e","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"raa425c1d9f99","instance":"chief","turn":"2bb5f36b-953e-4f27-8368-d3cb77b0e68d","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r91d263cb44fe","instance":"pm","turn":"f393963d-90db-4cc3-ada0-9749bbe898e9","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r91d263cb44fe","instance":"pm","turn":"0fca073a-da7d-4cf2-953f-d590f37c6a9c","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"raa425c1d9f99","instance":"chief","turn":"9afffc56-5aca-4ac9-a8e9-ef50b859e54e","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r91d263cb44fe","instance":"pm","turn":"555642ca-00ee-4665-89ab-e68eee143f82","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"raa425c1d9f99","instance":"chief","turn":"1dad33fc-cbe9-404b-8752-3b68753ab199","source":"@none","code":"baseline-unknown"}
|
||||
✔ a launched pm's request_staff reaches the chief of staff; no other session has the tool and the launch limits still hold (1303.262398ms)
|
||||
✔ each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or fetched and logged (4218.830472ms)
|
||||
✔ a role the broker says holds a merge decision gets land_work, its prompt line, its landing and the git socket; without it, none (747.37249ms)
|
||||
✔ REQ-LAUNCH-1 the tick starts a listed merge decision holder once, with land_work, the Landing line and its landing turn; an unlisted holder starts nothing (463.557222ms)
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r18be47008dae","instance":"coder","turn":"52cd9cf6-57bd-41bf-96c0-fedd9ff9c092","source":"@none","code":"baseline-unknown"}
|
||||
✔ a holder running since before the resolution is stopped once the broker releases it, land-relaunch, and the next tick starts it: landing turn first, then the message that waited (763.066104ms)
|
||||
✔ a landing run that ended isn't started again for its decision, a spent decision starts nothing, and a new decision starts the holder (706.701053ms)
|
||||
✔ after a restart, the new host's first tick starts a holder whose decision is still held (956.687674ms)
|
||||
✔ a refused landing start is logged once per code with its decision, isn't a start, and the next tick with room starts the holder (721.85447ms)
|
||||
✔ a project clone that can't be made refuses checkout-failed, from --chief and from a session, with no run and no clone left (395.757947ms)
|
||||
✔ a runner that stops at once ends its launch with the runner's reason (463.349261ms)
|
||||
✔ a Claude Code launch with no session credential refuses before it starts (235.420433ms)
|
||||
✔ a pi launch needs no session credential: pi logs in through its own ENV_ALLOW variables (443.687348ms)
|
||||
usage: {"event":"plugin-refused","kind":"harness","name":"down","code":"missing","message":"/home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/harness/down is missing","kept":[]}
|
||||
plugins: {"event":"plugin-refused","kind":"harness","name":"down","code":"missing","message":"/home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/harness/down is missing","kept":[]}
|
||||
✔ row 65 a failed primary preflight launches the fallback on a fixture harness module, recording the route and the reason (477.875324ms)
|
||||
✔ a runner killed from the host ends its launch as killed, not failed (491.276085ms)
|
||||
✔ a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback (257.904073ms)
|
||||
✔ row 64 a role skill that isn't enabled refuses skill-not-enabled, and one that isn't a plain directory skill-refused, before anything starts (271.706852ms)
|
||||
✔ row 213 a creative instance's persona is read from <configDir>/personas: off its digest the launch fails with the mismatch as its reason (374.032091ms)
|
||||
✔ row 64 on Claude Code a role skill that isn't enabled refuses the same way, before anything starts (266.897065ms)
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r505be704086e","instance":"chief","turn":"d3144fd2-644c-4bf1-95b5-5914f45dd040","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r505be704086e","instance":"chief","turn":"c61a5cf2-9714-4e2a-a335-a48d0d1dc430","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r96a1ff87bc5e","instance":"coder","turn":"bc58500e-35cc-47b5-b849-a56aa0438ace","source":"@none","code":"baseline-unknown"}
|
||||
✔ a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold (1866.534461ms)
|
||||
✔ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (1438.787679ms)
|
||||
✔ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (4207.748283ms)
|
||||
✔ bus start --no-launch leaves an earlier host's live session alone: no signal, no end, sessions.json as it was (275.473348ms)
|
||||
✔ bus start --no-launch refuses every launch: the PM launch and a session's request, each logged, nothing spawned (235.254265ms)
|
||||
✔ bus start --no-launch starts no thread tick; the same host with launches runs it (1246.476055ms)
|
||||
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (188.603222ms)
|
||||
✔ an over-long launch request is refused at once, not at the 10 s idle timeout (194.710702ms)
|
||||
✔ a launch client that never closes its side doesn't hold the host's close (204.602445ms)
|
||||
✔ a runner that ignores SIGTERM is killed when the host closes (1400.602264ms)
|
||||
✔ kill switch refuses chief and hire before launch work and a clear permits a later request (396.558108ms)
|
||||
✔ kill switch stops an actual managed namespace and records the halt reason without rewriting its bundle (362.427202ms)
|
||||
✔ a launch waiting across halt and resume cannot use its stale admission (218.641499ms)
|
||||
ℹ tests 35
|
||||
ℹ suites 0
|
||||
ℹ pass 34
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 29700.689398
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/cli/tests/launcher.test.mjs:150:1
|
||||
✖ P3 proxy key reaches the spawned runner but never session.json or the bundle (40.623657ms)
|
||||
Error [CliError]: unsupported /mnt/storage/scratch/tmp/gmDV/mosaic-launch-PVUXgr/config/businesses/acme.json launch.models.claude-opus-5-5 key: "route"
|
||||
at business (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/cli/src/config.mjs:45:47)
|
||||
at bootConfig (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/cli/src/config.mjs:126:10)
|
||||
at setup (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/cli/tests/launcher.test.mjs:99:16)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/cli/tests/launcher.test.mjs:156:34)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
exitCode: 3
|
||||
}
|
||||
exit=1
|
||||
23:42:25Z
|
||||
@@ -0,0 +1,98 @@
|
||||
23:42:41Z
|
||||
nice=19
|
||||
✖ P3 Claude env receives the session proxy URL and key, without another agent or host credential (3.24785ms)
|
||||
✖ P3 Claude preflight accepts a proxy key without a host provider credential (1.57681ms)
|
||||
✖ P3 pi refuses no-session-credential with no provider key (1.085777ms)
|
||||
✖ P3 pi refuses no-session-credential with only a Claude OAuth token (0.464216ms)
|
||||
✔ P3 pi accepts its own provider key or a session proxy key (0.351842ms)
|
||||
ℹ tests 5
|
||||
ℹ suites 0
|
||||
ℹ pass 1
|
||||
ℹ fail 4
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 71.435137
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/plugins/tests/proxy-harness.test.mjs:24:1
|
||||
✖ P3 Claude env receives the session proxy URL and key, without another agent or host credential (3.24785ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||
+ actual - expected
|
||||
|
||||
+ undefined
|
||||
- 'http://127.0.0.1:12345'
|
||||
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/tests/proxy-harness.test.mjs:30:12)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: undefined,
|
||||
expected: 'http://127.0.0.1:12345',
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/plugins/tests/proxy-harness.test.mjs:39:1
|
||||
✖ P3 Claude preflight accepts a proxy key without a host provider credential (1.57681ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||
+ actual - expected
|
||||
|
||||
+ {
|
||||
+ code: 'no-session-credential',
|
||||
+ message: "the bus host has none of CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN with ANTHROPIC_BASE_URL; the bus unit's EnvironmentFile sets one when the bus starts"
|
||||
+ }
|
||||
- null
|
||||
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/tests/proxy-harness.test.mjs:44:10)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: { code: 'no-session-credential', message: "the bus host has none of CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN with ANTHROPIC_BASE_URL; the bus unit's EnvironmentFile sets one when the bus starts" },
|
||||
expected: null,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/plugins/tests/proxy-harness.test.mjs:49:1
|
||||
✖ P3 pi refuses no-session-credential with no provider key (1.085777ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||
+ actual - expected
|
||||
|
||||
+ undefined
|
||||
- 'no-session-credential'
|
||||
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/tests/proxy-harness.test.mjs:51:10)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: undefined,
|
||||
expected: 'no-session-credential',
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/plugins/tests/proxy-harness.test.mjs:54:1
|
||||
✖ P3 pi refuses no-session-credential with only a Claude OAuth token (0.464216ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||
+ actual - expected
|
||||
|
||||
+ undefined
|
||||
- 'no-session-credential'
|
||||
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/tests/proxy-harness.test.mjs:56:10)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: undefined,
|
||||
expected: 'no-session-credential',
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
exit=1
|
||||
23:42:41Z
|
||||
@@ -0,0 +1,82 @@
|
||||
23:41:44Z
|
||||
nice=19
|
||||
✖ P3 Claude env receives the session proxy URL and key, without another agent or host credential (0.739622ms)
|
||||
✖ P3 Claude preflight accepts a proxy key without a host provider credential (0.187719ms)
|
||||
✖ P3 pi refuses no-session-credential with no provider key (0.161237ms)
|
||||
✖ P3 pi refuses no-session-credential with only a Claude OAuth token (0.154227ms)
|
||||
✖ P3 pi accepts its own provider key or a session proxy key (0.168061ms)
|
||||
ℹ tests 5
|
||||
ℹ suites 0
|
||||
ℹ pass 0
|
||||
ℹ fail 5
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 52.551274
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/plugins/tests/proxy-harness.test.mjs:24:1
|
||||
✖ P3 Claude env receives the session proxy URL and key, without another agent or host credential (0.739622ms)
|
||||
TypeError: Cannot destructure property 'log' of 'undefined' as it is undefined.
|
||||
at createLoader (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/src/loader.mjs:88:32)
|
||||
at harness (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/tests/proxy-harness.test.mjs:11:18)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/tests/proxy-harness.test.mjs:25:24)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.start (node:internal/test_runner/test:1262:17)
|
||||
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
|
||||
test at packages/plugins/tests/proxy-harness.test.mjs:39:1
|
||||
✖ P3 Claude preflight accepts a proxy key without a host provider credential (0.187719ms)
|
||||
TypeError: Cannot destructure property 'log' of 'undefined' as it is undefined.
|
||||
at createLoader (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/src/loader.mjs:88:32)
|
||||
at harness (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/tests/proxy-harness.test.mjs:11:18)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/tests/proxy-harness.test.mjs:40:24)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
|
||||
|
||||
test at packages/plugins/tests/proxy-harness.test.mjs:49:1
|
||||
✖ P3 pi refuses no-session-credential with no provider key (0.161237ms)
|
||||
TypeError: Cannot destructure property 'log' of 'undefined' as it is undefined.
|
||||
at createLoader (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/src/loader.mjs:88:32)
|
||||
at harness (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/tests/proxy-harness.test.mjs:11:18)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/tests/proxy-harness.test.mjs:50:20)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
|
||||
|
||||
test at packages/plugins/tests/proxy-harness.test.mjs:54:1
|
||||
✖ P3 pi refuses no-session-credential with only a Claude OAuth token (0.154227ms)
|
||||
TypeError: Cannot destructure property 'log' of 'undefined' as it is undefined.
|
||||
at createLoader (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/src/loader.mjs:88:32)
|
||||
at harness (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/tests/proxy-harness.test.mjs:11:18)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/tests/proxy-harness.test.mjs:55:20)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
|
||||
|
||||
test at packages/plugins/tests/proxy-harness.test.mjs:59:1
|
||||
✖ P3 pi accepts its own provider key or a session proxy key (0.168061ms)
|
||||
TypeError: Cannot destructure property 'log' of 'undefined' as it is undefined.
|
||||
at createLoader (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/src/loader.mjs:88:32)
|
||||
at harness (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/tests/proxy-harness.test.mjs:11:18)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/tests/proxy-harness.test.mjs:60:20)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
|
||||
exit=1
|
||||
23:41:44Z
|
||||
@@ -0,0 +1,35 @@
|
||||
23:41:50Z
|
||||
nice=19
|
||||
✖ P3 proxy sessionEnv removes every instance provider credential without widening ENV_ALLOW (1.019646ms)
|
||||
✔ P3 no proxy retains the full existing allowlist and run environment (0.564867ms)
|
||||
ℹ tests 2
|
||||
ℹ suites 0
|
||||
ℹ pass 1
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 46.658896
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/seat/tests/proxy-session-env.test.mjs:16:1
|
||||
✖ P3 proxy sessionEnv removes every instance provider credential without widening ENV_ALLOW (1.019646ms)
|
||||
AssertionError [ERR_ASSERTION]: CLAUDE_CODE_OAUTH_TOKEN
|
||||
|
||||
true !== false
|
||||
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/seat/tests/proxy-session-env.test.mjs:19:40)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.start (node:internal/test_runner/test:1262:17)
|
||||
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: true,
|
||||
expected: false,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
exit=1
|
||||
23:41:50Z
|
||||
@@ -0,0 +1,30 @@
|
||||
00:01:55Z
|
||||
nice=19
|
||||
✔ REQ-LIMIT-1 schema: every scope unset or zero, adjustable above four, bad values and unknown models refuse (22.211777ms)
|
||||
✔ REQ-LIMIT-1 counts each exact scope at/over limit, unlimited and old tuple after model reassignment (0.666965ms)
|
||||
✔ REQ-ROLE-3 writer: atomic validated revision, immutable previous bytes and authenticated actor/old/new receipt (19.091287ms)
|
||||
✔ REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery records commit, conflicting recovery refuses (15.98515ms)
|
||||
✔ REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent (4.713343ms)
|
||||
✔ REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor (20.35829ms)
|
||||
✔ REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts (2.568186ms)
|
||||
✔ REQ-ROLE-3 refuses live writer locks, symlink targets and uncertain recovery without changing bytes (9.763289ms)
|
||||
✔ Q169 assistants: vars and definition change and log the actor; tracker null leaves no tracker (16.756637ms)
|
||||
✔ Q169 assistants: refuses other instances, other definitions, holder and credentials, empty entries, a tracker the definition has no credential for and a stale revision (19.737439ms)
|
||||
✔ Q169 assistants: a definition with a vikunja credential takes a tracker change and refuses its removal or a used bot (12.898928ms)
|
||||
✔ row 65 routes lists every instance with harness, model, thinking and fallback, the switched-on harnesses, the model keys and each harness's levels (2.552837ms)
|
||||
✔ row 65 setRoutes saves a harness, model, thinking and whole fallback, reads each back from the file, logs the actor, and a null fallback clears all three (16.235475ms)
|
||||
✔ row 65 setRoutes refuses a partial fallback, a switched-off harness, a level the harness doesn't apply, an unloaded module, an unknown model or instance, a malformed route, no change and a stale revision, with the file unchanged (20.272464ms)
|
||||
✔ row 65 a file with harness vars and no plugins.harness loads and its views read; a model change still saves, and a route change refuses harness-off (10.564122ms)
|
||||
✔ B7 kept instance: the models keys it names and the instances key are the human's (26.878317ms)
|
||||
✔ B7 kept instance: its route is the human's (10.786018ms)
|
||||
✔ M10 creative instance: models keys it names, the instances key and its route are the human's (35.64373ms)
|
||||
ℹ tests 18
|
||||
ℹ suites 0
|
||||
ℹ pass 18
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 331.736961
|
||||
exit=0
|
||||
00:01:56Z
|
||||
@@ -0,0 +1,62 @@
|
||||
00:01:56Z
|
||||
nice=19
|
||||
✔ C2-04: launcher workspace clones code but never the library (1457.042303ms)
|
||||
✔ P3 proxy key reaches the spawned runner but never session.json or the bundle (1365.062806ms)
|
||||
✔ bus start --pm: the launcher instance (the chief of staff) runs under its own PID namespace, registered, with a manifest (1374.325436ms)
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"ra78486da0ed8","instance":"chief","turn":"9402e726-ac22-4423-aea9-cf7dcc35c134","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r49e89e37520e","instance":"coder","turn":"6bf40609-ae81-49b3-96d0-de2d5811b135","source":"@none","code":"baseline-unknown"}
|
||||
✔ the chief of staff launches a coder through its launch tool; the coder answers; refusals name their code (836.256914ms)
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"rf8408ce6a5b4","instance":"chief","turn":"e773abb1-268c-4404-a5da-81acd979905f","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r42524dd77612","instance":"pm","turn":"58e2bf95-0793-4495-bcdc-b27aa651caa1","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"rf8408ce6a5b4","instance":"chief","turn":"cb53fe60-e24f-4760-b284-d3b1d3620050","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r42524dd77612","instance":"pm","turn":"7460fc5d-aea2-4343-98a5-00d063c730c5","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r42524dd77612","instance":"pm","turn":"1ee4cbc7-90b7-4275-b3ac-4cbbd0394912","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"rf8408ce6a5b4","instance":"chief","turn":"1573dcc7-1d85-4783-b8c9-1a25c6ff71ac","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r42524dd77612","instance":"pm","turn":"bcb2aa05-7af5-4d9e-b085-c631754554ae","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"rf8408ce6a5b4","instance":"chief","turn":"9c77b01a-8f82-4d76-8e86-3eccdf5c05bc","source":"@none","code":"baseline-unknown"}
|
||||
✔ a launched pm's request_staff reaches the chief of staff; no other session has the tool and the launch limits still hold (1399.028095ms)
|
||||
✔ each launch makes sure the workspace has a clone of the project: cloned, fast-forwarded, or fetched and logged (4193.990785ms)
|
||||
✔ a role the broker says holds a merge decision gets land_work, its prompt line, its landing and the git socket; without it, none (707.949813ms)
|
||||
✔ REQ-LAUNCH-1 the tick starts a listed merge decision holder once, with land_work, the Landing line and its landing turn; an unlisted holder starts nothing (438.968553ms)
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r33b3a0a89f29","instance":"coder","turn":"54d42057-6aeb-4460-bbbb-11bb7d96f6a0","source":"@none","code":"baseline-unknown"}
|
||||
✔ a holder running since before the resolution is stopped once the broker releases it, land-relaunch, and the next tick starts it: landing turn first, then the message that waited (784.946481ms)
|
||||
✔ a landing run that ended isn't started again for its decision, a spent decision starts nothing, and a new decision starts the holder (733.626616ms)
|
||||
✔ after a restart, the new host's first tick starts a holder whose decision is still held (1104.180428ms)
|
||||
✔ a refused landing start is logged once per code with its decision, isn't a start, and the next tick with room starts the holder (766.099638ms)
|
||||
✔ a project clone that can't be made refuses checkout-failed, from --chief and from a session, with no run and no clone left (314.836965ms)
|
||||
✔ a runner that stops at once ends its launch with the runner's reason (380.228742ms)
|
||||
✔ a Claude Code launch with no session credential refuses before it starts (206.698491ms)
|
||||
✔ a pi launch with no own credential refuses no-session-credential (194.63649ms)
|
||||
usage: {"event":"plugin-refused","kind":"harness","name":"down","code":"missing","message":"/home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/harness/down is missing","kept":[]}
|
||||
plugins: {"event":"plugin-refused","kind":"harness","name":"down","code":"missing","message":"/home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/harness/down is missing","kept":[]}
|
||||
✔ row 65 a failed primary preflight launches the fallback on a fixture harness module, recording the route and the reason (395.760187ms)
|
||||
✔ a runner killed from the host ends its launch as killed, not failed (404.215037ms)
|
||||
✔ a missing or failing bwrap refuses sandbox-unavailable before anything starts; there is no unconfined fallback (204.482756ms)
|
||||
✔ row 64 a role skill that isn't enabled refuses skill-not-enabled, and one that isn't a plain directory skill-refused, before anything starts (223.681469ms)
|
||||
✔ row 213 a creative instance's persona is read from <configDir>/personas: off its digest the launch fails with the mismatch as its reason (294.218836ms)
|
||||
✔ row 64 on Claude Code a role skill that isn't enabled refuses the same way, before anything starts (216.464613ms)
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r4b699771c38c","instance":"chief","turn":"77969cf2-7fc8-41ff-8d52-7569e054b6f8","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r6e6168e49a89","instance":"coder","turn":"e6dfb039-1843-41b7-8111-7992cfddac3d","source":"@none","code":"baseline-unknown"}
|
||||
✔ a real Claude Code coder's shell can't read a stand-in secret or anything else its sandbox doesn't hold (2155.647443ms)
|
||||
✔ a host that died hard leaves its sessions to the next host, which kills them and ends their runs so the role can be launched again (1620.990125ms)
|
||||
✔ a host that died hard leaves its sessions to the next host, which finds one already exited (23) and ends their runs so the role can be launched again (4336.758535ms)
|
||||
✔ bus start --no-launch leaves an earlier host's live session alone: no signal, no end, sessions.json as it was (279.143242ms)
|
||||
✔ bus start --no-launch refuses every launch: the PM launch and a session's request, each logged, nothing spawned (229.339371ms)
|
||||
✔ bus start --no-launch starts no thread tick; the same host with launches runs it (1236.511266ms)
|
||||
✔ a malformed sessions.json refuses the host start with exit 3 and stays as it was (212.151607ms)
|
||||
✔ an over-long launch request is refused at once, not at the 10 s idle timeout (201.563393ms)
|
||||
✔ a launch client that never closes its side doesn't hold the host's close (203.041695ms)
|
||||
✔ a runner that ignores SIGTERM is killed when the host closes (1399.240682ms)
|
||||
✔ kill switch refuses chief and hire before launch work and a clear permits a later request (362.776444ms)
|
||||
✔ kill switch stops an actual managed namespace and records the halt reason without rewriting its bundle (400.610758ms)
|
||||
✔ a launch waiting across halt and resume cannot use its stale admission (221.918115ms)
|
||||
ℹ tests 35
|
||||
ℹ suites 0
|
||||
ℹ pass 35
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 30999.3545
|
||||
exit=0
|
||||
00:02:27Z
|
||||
@@ -0,0 +1,35 @@
|
||||
00:01:56Z
|
||||
nice=19
|
||||
✔ P3 Anthropic route uses messages at proxy root and the declared model id (11.574291ms)
|
||||
✔ P3 omitted route refuses proxy-route-missing before model config generation (2.072933ms)
|
||||
✔ P3 GLM route uses completions at proxy /v1 with explicit z.ai compatibility (2.699568ms)
|
||||
✖ P3 GPT route selects a stack-owned provider instead of pi Codex OAuth (2.510206ms)
|
||||
✔ P3 a declared Codex route refuses generation while the build-time API is PENDING (2.334352ms)
|
||||
✔ P3 models file is private, references the one session key, and stores no executable values or keys (3.084202ms)
|
||||
✔ P3 every launch regenerates proxy port, declared models and own key without caching (5.343868ms)
|
||||
ℹ tests 7
|
||||
ℹ suites 0
|
||||
ℹ pass 6
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 108.561079
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/harness/tests/proxy-bundle.test.mjs:62:1
|
||||
✖ P3 GPT route selects a stack-owned provider instead of pi Codex OAuth (2.510206ms)
|
||||
Error: proxy GPT API is PENDING build-time evidence
|
||||
at Module.bundle (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/plugins/harness/pi/index.mjs:78:62)
|
||||
at buildBundle (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/src/bundle.mjs:359:23)
|
||||
at build (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:20:18)
|
||||
at TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332/packages/harness/tests/proxy-bundle.test.mjs:63:13)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
|
||||
exit=1
|
||||
00:01:56Z
|
||||
@@ -0,0 +1,16 @@
|
||||
packages/business/tests/launch-settings.test.mjs:41: assert.throws(()=>migrateLaunch(old,{...mapping,families:{opus:'missing',sonnet:'sonnet'}},f.file,{rolesDir:f.rolesDir}));assert.throws(()=>migrateLaunch(old,{...mapping,families:{opus:'opus',sonnet:'opus'}},f.file,{rolesDir:f.rolesDir}),/ambiguous/);assert.throws(()=>migrateLaunch(old,{...mapping,models:{...mapping.models,proxy:{provider:'proxy',route:'anthropic',model:'claude-opus-5-5'}},instances:{pm:'proxy'}},f.file,{rolesDir:f.rolesDir}),/ambiguous/);const onlyOpus=structuredClone(old);onlyOpus.launch.max={opus:4};assert.throws(()=>migrateLaunch(onlyOpus,mapping,f.file,{rolesDir:f.rolesDir}),/unknown or ambiguous/);const allOpus=structuredClone(old);for(const r of Object.values(allOpus.roles))r.vars.model='claude-opus-5-5';assert.throws(()=>migrateLaunch(allOpus,{...mapping,families:{opus:'opus',sonnet:'opus'}},f.file,{rolesDir:f.rolesDir}),/ambiguous/);assert.equal(readFileSync(f.file,'utf8'),bytes);assert.equal(statSync(f.file).mtimeMs,mtime);
|
||||
packages/business/tests/launch-settings.test.mjs:45: const result=settings.set('acme',{revision:view.revision,limits:{total:0},instances:{coder:'opus'}},'jason');assert.equal(result.limits.total,0);assert.equal(result.instances.coder.modelKey,'opus');const models=structuredClone(result.models);models.opus.provider='proxy';models.opus.route='anthropic';const updated=settings.set('acme',{revision:result.revision,models},'jason');assert.equal(updated.instances.pm.provider,'proxy');assert.throws(()=>settings.set('acme',{revision:result.revision,instances:{coder:'ghost'}},'jason'));assert.throws(()=>settings.set('acme',{revision:result.revision,roles:{}},'jason'));
|
||||
packages/business/tests/launch-settings.test.mjs:154: const moved={...models,[key]:{provider:'proxy',route:'anthropic',model:'claude-sonnet-5-5'}};
|
||||
packages/business/tests/launch-settings.test.mjs:161: ({revision}=settings.set('acme',{revision,models:{...models,spare:{provider:'proxy',route:'anthropic',model:'claude-opus-5-5'}}},chief));
|
||||
packages/business/tests/launch-settings.test.mjs:188: const sonnet={...models,sonnet:{provider:'proxy',route:'anthropic',model:'claude-sonnet-5-5'}};
|
||||
packages/business/tests/launch-settings.test.mjs:198: ({revision}=settings.set('acme',{revision,models:{...models,opus:{provider:'proxy',route:'anthropic',model:'claude-opus-5-5'}}},chief));
|
||||
packages/business/tests/proxy-launch.test.mjs:8: const models = { selected: { provider: 'proxy', model: 'proxy-only-id', route } };
|
||||
packages/business/tests/proxy-launch.test.mjs:14: assert.throws(() => checkModels({ selected: { provider: 'proxy', model: 'proxy-only-id' } }), {
|
||||
packages/business/tests/proxy-launch.test.mjs:21: assert.throws(() => checkModels({ selected: { provider: 'proxy', model: 'proxy-only-id', route } }), {
|
||||
packages/cli/tests/launcher.test.mjs:155: doc.launch.models['claude-opus-5-5'] = { provider: 'proxy', model: 'claude-fixture', route: 'anthropic' };
|
||||
packages/harness/tests/proxy-bundle.test.mjs:10: a: { provider: 'proxy', model: 'claude-fixture', route: 'anthropic' },
|
||||
packages/harness/tests/proxy-bundle.test.mjs:11: z: { provider: 'proxy', model: 'glm-fixture', route: 'glm' },
|
||||
packages/harness/tests/proxy-bundle.test.mjs:12: g: { provider: 'proxy', model: 'gpt-fixture', route: 'codex' },
|
||||
packages/harness/tests/proxy-bundle.test.mjs:47: a: { provider: 'proxy', model: 'claude-fixture' },
|
||||
packages/harness/tests/proxy-bundle.test.mjs:97: a: { provider: 'proxy', model: 'changed-model-id', route: 'anthropic' },
|
||||
packages/harness/tests/proxy-bundle.test.mjs:98: z: { provider: 'proxy', model: 'changed-glm-id', route: 'glm' },
|
||||
@@ -0,0 +1,18 @@
|
||||
00:01:47Z
|
||||
nice=19
|
||||
✔ P3 proxy model validation preserves each explicit wire route and the proxy model id (1.21495ms)
|
||||
✔ P3 a proxy model without route refuses proxy-route-missing (0.356994ms)
|
||||
✔ P3 an unknown proxy route refuses proxy-route-unknown (0.767073ms)
|
||||
✔ P3 nonproxy models retain their provider/model-only shape and reject route metadata (0.17626ms)
|
||||
✔ P3 launch.account accepts the proxy credential source without naming host variables (0.179844ms)
|
||||
✔ P3 existing environment-set accounts retain their validated representation (0.312195ms)
|
||||
ℹ tests 6
|
||||
ℹ suites 0
|
||||
ℹ pass 6
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 57.913873
|
||||
exit=0
|
||||
00:01:47Z
|
||||
@@ -0,0 +1,31 @@
|
||||
packages/business/tests/launch-settings.test.mjs:41: assert.throws(()=>migrateLaunch(old,{...mapping,families:{opus:'missing',sonnet:'sonnet'}},f.file,{rolesDir:f.rolesDir}));assert.throws(()=>migrateLaunch(old,{...mapping,families:{opus:'opus',sonnet:'opus'}},f.file,{rolesDir:f.rolesDir}),/ambiguous/);assert.throws(()=>migrateLaunch(old,{...mapping,models:{...mapping.models,proxy:{provider:'proxy',route:'anthropic',model:'claude-opus-5-5'}},instances:{pm:'proxy'}},f.file,{rolesDir:f.rolesDir}),/ambiguous/);const onlyOpus=structuredClone(old);onlyOpus.launch.max={opus:4};assert.throws(()=>migrateLaunch(onlyOpus,mapping,f.file,{rolesDir:f.rolesDir}),/unknown or ambiguous/);const allOpus=structuredClone(old);for(const r of Object.values(allOpus.roles))r.vars.model='claude-opus-5-5';assert.throws(()=>migrateLaunch(allOpus,{...mapping,families:{opus:'opus',sonnet:'opus'}},f.file,{rolesDir:f.rolesDir}),/ambiguous/);assert.equal(readFileSync(f.file,'utf8'),bytes);assert.equal(statSync(f.file).mtimeMs,mtime);
|
||||
packages/business/tests/launch-settings.test.mjs:45: const result=settings.set('acme',{revision:view.revision,limits:{total:0},instances:{coder:'opus'}},'jason');assert.equal(result.limits.total,0);assert.equal(result.instances.coder.modelKey,'opus');const models=structuredClone(result.models);models.opus.provider='proxy';models.opus.route='anthropic';const updated=settings.set('acme',{revision:result.revision,models},'jason');assert.equal(updated.instances.pm.provider,'proxy');assert.throws(()=>settings.set('acme',{revision:result.revision,instances:{coder:'ghost'}},'jason'));assert.throws(()=>settings.set('acme',{revision:result.revision,roles:{}},'jason'));
|
||||
packages/business/tests/launch-settings.test.mjs:154: const moved={...models,[key]:{provider:'proxy',route:'anthropic',model:'claude-sonnet-5-5'}};
|
||||
packages/business/tests/launch-settings.test.mjs:161: ({revision}=settings.set('acme',{revision,models:{...models,spare:{provider:'proxy',route:'anthropic',model:'claude-opus-5-5'}}},chief));
|
||||
packages/business/tests/launch-settings.test.mjs:188: const sonnet={...models,sonnet:{provider:'proxy',route:'anthropic',model:'claude-sonnet-5-5'}};
|
||||
packages/business/tests/launch-settings.test.mjs:198: ({revision}=settings.set('acme',{revision,models:{...models,opus:{provider:'proxy',route:'anthropic',model:'claude-opus-5-5'}}},chief));
|
||||
packages/business/tests/proxy-launch.test.mjs:8: const models = { selected: { provider: 'proxy', model: 'proxy-only-id', route } };
|
||||
packages/business/tests/proxy-launch.test.mjs:14: assert.throws(() => checkModels({ selected: { provider: 'proxy', model: 'proxy-only-id' } }), {
|
||||
packages/business/tests/proxy-launch.test.mjs:21: assert.throws(() => checkModels({ selected: { provider: 'proxy', model: 'proxy-only-id', route } }), {
|
||||
packages/business/tests/proxy-launch.test.mjs:35: assert.deepEqual(checkAccount({ name: 'proxy', credential: 'proxy' }), { name: 'proxy', credential: 'proxy' });
|
||||
packages/business/tests/proxy-launch.test.mjs:43: assert.throws(() => checkAccount({ name: 'proxy', credential }), /launch\.account\.credential/);
|
||||
packages/cli/tests/launcher.test.mjs:155: doc.launch.models['claude-opus-5-5'] = { provider: 'proxy', model: 'claude-fixture', route: 'anthropic' };
|
||||
packages/cli/tests/model-proxy.test.mjs:11:test('proxy init agent and tester refuse before prompting or transport',async()=>{for(const args of [['proxy','init','--tester'],['proxy','init']]){const x=io(args.includes('--tester')?{}:{CODEX_THREAD_ID:'fixture'});await assert.rejects(main(args,x,{promptProxyKey:()=>{throw Error('must not prompt');}}),code('human-required'));}});
|
||||
packages/cli/tests/model-proxy.test.mjs:12:test('proxy init prompts twice through no-echo seam and sends key only through human transport',async()=>{const x=io();x.stdin.isTTY=true;x.stdout.isTTY=true;const prompts=[],calls=[];await main(['proxy','init','--business','demo','--port','18432'],x,{system:fakeSystem(),promptProxyKey:async()=>{prompts.push('hidden');return 'p1-management';},transport:b=>async(verb,args)=>{calls.push({b,verb,args});return {port:18432,binary:'/fixture'};}});assert.equal(prompts.length,2);assert.deepEqual(calls,[{b:'demo',verb:'proxy.init',args:{key:'p1-management',port:18432}}]);assert.equal(x.out.text.includes('p1-management'),false);});
|
||||
packages/cli/tests/model-proxy.test.mjs:13:test('proxy-key-mismatch: confirmation refuses without bus call',async()=>{const x=io();x.stdin.isTTY=true;x.stdout.isTTY=true;let count=0;await assert.rejects(main(['proxy','init','--business','demo'],x,{system:fakeSystem(),promptProxyKey:async()=>++count===1?'p1-one':'p1-two',transport:()=>()=>{throw Error('must not call');}}),code('proxy-key-mismatch'));});
|
||||
packages/cli/tests/model-proxy.test.mjs:14:test('proxy-terminal-required: stdin redirection refuses',async()=>{await assert.rejects(main(['proxy','init'],io(),{system:fakeSystem()}),code('proxy-terminal-required'));});
|
||||
packages/cli/tests/model-proxy.test.mjs:15:test('proxy-usage: invalid verb and repeated flags refuse',async()=>{for(const args of [['proxy','unknown'],['proxy','init','--port','18432','--port','18433']])await assert.rejects(main(args,io(),{system:fakeSystem()}),code('proxy-usage'));});
|
||||
packages/cli/tests/model-proxy.test.mjs:16:test('proxy-config-invalid: port 8317 refuses before prompting',async()=>await assert.rejects(main(['proxy','init','--port','8317'],io(),{system:fakeSystem()}),code('proxy-config-invalid')));
|
||||
packages/cli/tests/model-proxy.test.mjs:17:test('proxy-key-invalid: empty prompt refuses without printing input',async()=>{const x=io();x.stdin.isTTY=true;x.stdout.isTTY=true;await assert.rejects(main(['proxy','init','--business','demo'],x,{system:fakeSystem(),promptProxyKey:async()=>'',transport:()=>()=>{throw Error('must not call');}}),code('proxy-key-invalid'));});
|
||||
packages/harness/tests/proxy-bundle.test.mjs:10: a: { provider: 'proxy', model: 'claude-fixture', route: 'anthropic' },
|
||||
packages/harness/tests/proxy-bundle.test.mjs:11: z: { provider: 'proxy', model: 'glm-fixture', route: 'glm' },
|
||||
packages/harness/tests/proxy-bundle.test.mjs:12: g: { provider: 'proxy', model: 'gpt-fixture', route: 'codex' },
|
||||
packages/harness/tests/proxy-bundle.test.mjs:47: a: { provider: 'proxy', model: 'claude-fixture' },
|
||||
packages/harness/tests/proxy-bundle.test.mjs:97: a: { provider: 'proxy', model: 'changed-model-id', route: 'anthropic' },
|
||||
packages/harness/tests/proxy-bundle.test.mjs:98: z: { provider: 'proxy', model: 'changed-glm-id', route: 'glm' },
|
||||
packages/model-proxy/tests/config-unit.test.mjs:14:test('loader proxy absent is byte-compatible; present validates sidecar and preserves config.json',()=>{const f=fixture(),before=readFileSync(f.file);let r=run(f,'scripts/mosaic-config.mjs',['validate']);assert.equal(r.status,0,r.stderr);assert.equal(Object.hasOwn(JSON.parse(r.stdout),'proxy'),false);const value=proxy(f);r=run(f,'scripts/mosaic-config.mjs',['validate']);assert.equal(r.status,0,r.stderr);assert.deepEqual(JSON.parse(r.stdout).proxy,value);assert.deepEqual(readFileSync(f.file),before);});
|
||||
packages/model-proxy/tests/config-unit.test.mjs:23:test('historical no-proxy config loader works without new package dependency',()=>{const f=fixture();const copy=join(f.root,'historical/scripts');mkdirSync(copy,{recursive:true});writeFileSync(join(copy,'mosaic-config.mjs'),readFileSync(join(repo,'scripts/mosaic-config.mjs')));const r=spawnSync(process.execPath,[join(copy,'mosaic-config.mjs'),'validate'],{env:f.env,encoding:'utf8',timeout:10000});assert.equal(r.status,0,r.stderr);assert.equal(Object.hasOwn(JSON.parse(r.stdout),'proxy'),false);});
|
||||
packages/model-proxy/tests/management-key-encoding.test.mjs:71: assert.deepEqual(added('data'), ['proxy', 'proxy/auth', 'proxy/config.yaml', 'proxy/logs']);
|
||||
packages/model-proxy/tests/runner.test.mjs:9:test('runner verifies before spawning and supplies pinned panel and local model without ambient secrets',()=>{const {root,config,install}=fixture();const calls=[],child=new EventEmitter();startVerifiedProxy(config,{env:{HOME:join(root,'home'),MANAGEMENT_PASSWORD:'p1-management',OPENAI_API_KEY:'p1-upstream'},verify:dir=>{calls.push(['verify',dir]);return install;},spawnImpl:(...args)=>{calls.push(['spawn',...args]);return child;}});assert.equal(calls[0][0],'verify');assert.deepEqual(calls[1],['spawn',install.binary,['-config',join(config.dataRoot,'proxy/config.yaml'),'-local-model'],{cwd:join(config.dataRoot,'proxy'),stdio:'inherit',env:{HOME:join(root,'home'),PATH:'/usr/bin:/bin',MANAGEMENT_STATIC_PATH:join(root,'pin/static')}}]);});
|
||||
packages/model-proxy/tests/settings.test.mjs:19:test('exclusive initialization writes private sentinel config, never rewrites config.json or exposes key',async()=>{const f=fixture();installed(f);const key='p1-management';const result=await initializeProxy({...f,pin,key});assert.ok(result.port>=1024&&result.port!==8317);assert.equal(JSON.stringify(result).includes(key),false);const conf=JSON.parse(readFileSync(join(f.dataRoot,'proxy/config.yaml'),'utf8'));assert.equal(conf.server.host,'127.0.0.1');assert.equal(conf.management['allow-remote'],false);assert.equal(conf.management['disable-auto-update-panel'],true);assert.equal(conf.routing.strategy,'fill-first');assert.equal(conf.routing.retry['request-retry'],0);assert.match(conf.access['api-keys'][0],/^msk-.*-sentinel-/);for(const file of [join(f.configDir,'secrets/proxy-management.key'),join(f.dataRoot,'proxy/config.yaml')])assert.equal(lstatSync(file).mode&0o777,0o600);for(const dir of ['proxy','proxy/auth','proxy/logs'])assert.equal(lstatSync(join(f.dataRoot,dir)).mode&0o777,0o700);assert.equal(readFileSync(join(f.configDir,'config.json'),'utf8'),'unchanged');await assert.rejects(initializeProxy({...f,pin,key}),code('proxy-already-initialized'));});
|
||||
packages/model-proxy/tests/settings.test.mjs:23:test('proxy-already-initialized: each existing target is preserved and never followed',async()=>{for(const target of ['proxy','key','settings']){const f=fixture();installed(f);let file;if(target==='proxy'){file=join(f.dataRoot,'proxy');mkdirSync(file);writeFileSync(join(file,'marker'),'old');}else if(target==='key'){mkdirSync(join(f.configDir,'secrets'),{mode:0o700});file=join(f.configDir,'secrets/proxy-management.key');writeFileSync(file,'old');}else{file=join(f.configDir,'proxy.json');writeFileSync(file,'old');}await assert.rejects(initializeProxy({...f,pin,key:'p1-management'}),code('proxy-already-initialized'));assert.equal(readFileSync(target==='proxy'?join(file,'marker'):file,'utf8'),'old');}});
|
||||
@@ -0,0 +1,249 @@
|
||||
# Row 209 phase B research: one ChatGPT account, two separate sign-ins
|
||||
|
||||
Researcher seat, 2026-10-24. Request: sage-4291 (mos-1616 item 2). Public docs and public issues only.
|
||||
Nothing here read, copied or tested a token, an auth file, `<configDir>/secrets/`, `<dataRoot>/proxy/`,
|
||||
the 8317 host proxy, `~/.local/opt/EasyCLIProxyAPI` or `~/.mosaic-dev`. No sign-in was run.
|
||||
|
||||
Tags: **[R]** read on the cited page. **[S]** secondary summary (zread.ai or a search snippet), not
|
||||
checked on the primary page. **[I]** my inference, not stated by any source.
|
||||
|
||||
CLIProxyAPI v8.0.42 files were read at the tag through GitHub. Hermes pages were read at their current
|
||||
state, not at the version Chet runs. I do not know that version.
|
||||
|
||||
## Summary
|
||||
|
||||
| Question | Answer |
|
||||
|---|---|
|
||||
| 1. Do two separate grants coexist? | **Unknown, leaning coexist.** Two public tests say coexist. One field incident and Hermes's own docs say the older login dies. OpenAI says nothing either way. |
|
||||
| 2. Lifetimes, rotation | Access token about 10 days [R]. Refresh token rotates on every refresh [R]. A replaced token still works for at least 30 minutes, then fails by about 27 hours [R, one test]. Refresh-token lifetime: no public source. |
|
||||
| 3. Refresh on each side | Both sides have an on-demand refresh that needs no one to read a token. Waiting for a natural refresh would take about 6 days (CLIProxyAPI) to about 10 days (Hermes), too long for a window. |
|
||||
|
||||
## Question 1: coexist, revoke or unknown
|
||||
|
||||
**Verdict: unknown, leaning coexist.** Mos's expectation (the known failure comes from two clients
|
||||
sharing one copied auth file, not from separate grants) is **supported for the shared-file case and
|
||||
unconfirmed for the separate-grant case.**
|
||||
|
||||
### What is established: sharing one token family breaks it
|
||||
|
||||
- Refresh tokens rotate, and a spent one is refused. Codex reports "Your access token could not be
|
||||
refreshed because your refresh token was already used. Please log out and sign in again." [R]
|
||||
https://github.com/openai/codex/issues/10332
|
||||
- Every report I opened that names a cause involves several processes holding one token family: several
|
||||
Codex app-servers on one `~/.codex` [R] https://github.com/openai/codex-plugin-cc/issues/789 ;
|
||||
three app-servers on one login pair [R] https://github.com/keanji-x/neige-calm/issues/2314 ;
|
||||
Hermes and the Codex CLI each holding a copy after Hermes adopted the CLI's pair [R]
|
||||
https://github.com/NousResearch/hermes-agent/issues/113023 ; three Hermes profile stores with
|
||||
identical token fingerprints [R] https://github.com/NousResearch/hermes-agent/issues/87503
|
||||
- OpenAI's own CI guide says "Do not share the same file across concurrent jobs or multiple machines"
|
||||
and lists "another machine or concurrent job rotated the token first" as a reason to reseed. [R]
|
||||
https://learn.chatgpt.com/docs/auth/ci-cd-auth
|
||||
|
||||
### Evidence that separate sign-ins coexist
|
||||
|
||||
1. awaywork issue 190, run 2026-10-11 on Codex 0.162.1, free plan, throwaway `CODEX_HOME`s, device-auth
|
||||
logins on one account. Reported result, tagged "[V, tested]": "Coexist; a new login revokes none". Logging
|
||||
out one of three left the other two working. [R, issue and PR description; I did not see the
|
||||
`docs/providers.md` table or the exact steps]
|
||||
https://github.com/AndreaProvenzale/awaywork/issues/190 and
|
||||
https://github.com/AndreaProvenzale/awaywork/pull/194
|
||||
2. jyje/hermes-agent-helm issue 368, closed 2026-10-19. Three isolated Hermes v0.22.2 releases logged in
|
||||
to one **Plus** ChatGPT account within minutes of each other. `hermes auth refresh openai-codex` on each
|
||||
at login, about 1 hour, about 10 hours and about 24 hours. "No login was revoked, and no
|
||||
`refresh_token_reused` or `invalid_grant` error appeared." One 24-hour refresh hit a lock timeout on
|
||||
the auth store and succeeded on retry. [R] https://github.com/jyje/hermes-agent-helm/issues/368
|
||||
This is the closest match to Chet's setup (Hermes, Plus), but each login sat in its own release, not in
|
||||
one Hermes pool.
|
||||
3. A Codex Desktop report shows one "Active sessions" row per Desktop sign-in, with older rows kept. [R]
|
||||
https://github.com/openai/codex/issues/40632 . [I] This fits one grant per sign-in, but the page does not
|
||||
say so.
|
||||
4. [I] The mechanism is per refresh-token family, and a new sign-in starts a new family. No source states
|
||||
this for ChatGPT.
|
||||
|
||||
### Evidence that a second sign-in revokes the older one
|
||||
|
||||
1. Hermes issue 47096: one working `openai-codex` credential, then a second added by a separate device-code
|
||||
login (not a copied file). Credential 1 later showed status dead, HTTP 401, `token_revoked`. Its token
|
||||
fingerprints were unchanged, so Hermes did not overwrite it. [R] The reporter never said both logins
|
||||
were the same OpenAI account, there was no check between the add and the later inspection, and no
|
||||
control login. "OpenAI revoking the older token family when the same user logs in again" is the
|
||||
maintainer's reading. [R] https://github.com/NousResearch/hermes-agent/issues/47096
|
||||
2. Hermes's docs state it as fact: "two logins of the same account share one token family upstream, so
|
||||
OpenAI revokes the older one." [R]
|
||||
https://hermes-agent.nousresearch.com/docs/user-guide/features/credential-pools . PR 115895 added a
|
||||
warning at `hermes auth add` time. The PR's only cited basis is issue 47096. [R]
|
||||
https://github.com/NousResearch/hermes-agent/pull/115895
|
||||
3. neige-calm issue 2314 says "A new device login revokes every older pair" in passing. It gives no test,
|
||||
and its three holders were already sharing one pair. [R]
|
||||
4. Hermes issue 73667: both Hermes credentials got `401 token_revoked` seconds before Codex Desktop issued
|
||||
a new token. The timeline puts the revocations before the Desktop issue line, so it does not show the
|
||||
Desktop sign-in as the cause. [R] https://github.com/NousResearch/hermes-agent/issues/73667
|
||||
|
||||
### Reading the conflict
|
||||
|
||||
- The two coexistence tests were deliberate and report no revocation. The revocation claim rests on one
|
||||
uncontrolled incident that was then written into docs.
|
||||
- Neither side is vendor confirmed. OpenAI's auth docs do not address two sign-ins on one account.
|
||||
https://learn.chatgpt.com/docs/auth
|
||||
- Open gaps in the coexist evidence: the longest span tested is about a day (helm), against a 10-day access
|
||||
token; plan types tested are free and Plus; only one of the two tests used Hermes; nobody tested a
|
||||
CLIProxyAPI login next to a Hermes login.
|
||||
- Hermes issue 39236 shows Hermes can overwrite its own `manual:device_code` pool entries with the newest
|
||||
pair. [R] https://github.com/NousResearch/hermes-agent/issues/39236 . That is a local failure and was
|
||||
fixed (PR 42316), but it is another way a "second login" can break Hermes without OpenAI revoking
|
||||
anything.
|
||||
|
||||
### What this means for the plan
|
||||
|
||||
- Do not treat "they coexist" as settled. Mos's fallback (Chet's login goes in only after Hermes's Chet
|
||||
stops, inside the window with Jason present) fits the evidence.
|
||||
- Whichever order is used, the plan avoids the one proven failure if nothing is copied between the two.
|
||||
CLIProxyAPI's `/oauth/import` accepts only a Vertex service account in v8 (`provider=vertex`), so it
|
||||
cannot be used to move a Codex login. [R]
|
||||
https://raw.githubusercontent.com/router-for-me/CLIProxyAPI/v8.0.42/docs/management-api-v8.md
|
||||
- Hermes borrows a Codex CLI login by default. `auth.adopt_external_logins` defaults to `true` (PR 115586,
|
||||
merged 2026-09-19). Hermes then takes over and refreshes the CLI's pair when its own refresh fails. [R]
|
||||
https://github.com/NousResearch/hermes-agent/pull/115586 . CLIProxyAPI keeps its own `auth-dir` and
|
||||
does not read `~/.codex` [S]. The only shared-family risk is Hermes adopting a `~/.codex` file, so
|
||||
setting that key to `false` for Chet's Hermes closes it. [I] that Chet's account has a `~/.codex` at all.
|
||||
- Sage can settle the question in the window with one safe check, which needs no token read: after both
|
||||
sign-ins exist, force a refresh on one side, then on the other, then on the first again, and confirm all
|
||||
three succeed (see question 3). That proves the two grants are alive after one rotation each. It does not
|
||||
prove a 10-day cycle.
|
||||
|
||||
## Question 2: lifetimes and rotation
|
||||
|
||||
| Item | Finding | Source |
|
||||
|---|---|---|
|
||||
| Access token | About 10 days. JWT `iat` to `exp` was 2026-07-31 13:10 to 2026-08-10 13:10 in one Hermes report. | [R] https://github.com/NousResearch/hermes-agent/issues/87503 |
|
||||
| Access token, CLIProxyAPI | Issued 2026-09-26 about 14:39, expired 2026-10-06 about 14:39. | [R] https://github.com/router-for-me/CLIProxyAPI/issues/6742 |
|
||||
| Access token, Codex CLI | awaywork records "a 10-day JWT" and says refresh happens at expiry, not at 8 days after `last_refresh`. | [R] https://github.com/AndreaProvenzale/awaywork/pull/194 |
|
||||
| Codex CLI refresh trigger | OpenAI's CI guide says Codex refreshes when `last_refresh` is older than "about 8 days", "in the current open-source client". It gives no lifetime figure. The awaywork test disagrees. | [R] https://learn.chatgpt.com/docs/auth/ci-cd-auth |
|
||||
| Refresh token rotates | Every refresh issues a new refresh token. | [R] awaywork 194 above; Hermes PR 115586 ("single-use and rotate") |
|
||||
| Refresh token single-use | **Not strictly.** An OpenAI maintainer said the server accepts a reused refresh token for a limited window "on the order of an hour" before it is permanently invalidated. [R] https://github.com/openai/codex/issues/10332 . awaywork found a replaced token still worked after 8 seconds, about 6 minutes and about 30 minutes, and failed after about 27 hours. The exact window is between those. [R, one test] A failed reuse did not kill the live login. |
|
||||
| Whole family revoked on reuse | Hermes code comment, quoted in the issue, says OpenAI "revokes the whole token family on reuse". | [R, secondhand quote] https://github.com/NousResearch/hermes-agent/issues/113023 |
|
||||
| Refresh token lifetime when unused | **No public source found.** OpenAI's docs list "the refresh token was revoked or expired" as a reseed reason and give no figure. | [R] https://learn.chatgpt.com/docs/auth/ci-cd-auth |
|
||||
| Token endpoint | `https://auth.openai.com/oauth/token`, form POST, `grant_type=refresh_token`. CLIProxyAPI uses the same hard-coded public client ID as the Codex CLI. | [R] https://raw.githubusercontent.com/router-for-me/CLIProxyAPI/v8.0.42/internal/auth/codex/openai_auth.go |
|
||||
|
||||
A caution on weekly failures: CLIProxyAPI issues 3654 and 6742 report Codex credentials going invalid about
|
||||
every 7 days, three days before the access token's expiry. Maintainer fix feb6398 (the 96-hour lead
|
||||
below) closed 6742, and it gives no reason in writing. [R]
|
||||
https://github.com/router-for-me/CLIProxyAPI/issues/3654 ,
|
||||
https://github.com/router-for-me/CLIProxyAPI/issues/6742 . The cause is not established in public.
|
||||
|
||||
## Question 3: how each side refreshes
|
||||
|
||||
### CLIProxyAPI 8.0.42, Codex credential
|
||||
|
||||
- Background refresh. A loop checks every 5 seconds (`refreshCheckInterval`), up to 16 at a time, with a
|
||||
5-minute failure backoff and a 1 to 30 minute invalid-grant backoff. [R]
|
||||
https://github.com/router-for-me/CLIProxyAPI/blob/v8.0.42/sdk/cliproxy/auth/conductor_refresh.go
|
||||
- Lead time. Commit feb6398 (2026-10-18, closes issue 6742) raised the Codex outer refresh lead from 24
|
||||
hours to 96 hours. The commit message says "to prevent upstream token invalidation". The file at the
|
||||
v8.0.42 tag already refers to `CodexOuterRefreshLead`. [R]
|
||||
https://github.com/router-for-me/CLIProxyAPI/commit/feb6398456ccaed50d20dfc6353ad050ef714d31 ,
|
||||
https://raw.githubusercontent.com/router-for-me/CLIProxyAPI/v8.0.42/internal/runtime/executor/codex_executor_auth.go .
|
||||
The constant's definition was not on a page I read, so "96 hours at 8.0.42" is [I] from the commit and
|
||||
its updated test. A 401 on a request also schedules an immediate refresh. [R, same commit]
|
||||
- Release notes for v8.0.42 (2026-10-24) say nothing about Codex, OAuth or refresh. [R]
|
||||
https://github.com/router-for-me/CLIProxyAPI/releases/tag/v8.0.42
|
||||
- Concurrency inside CLIProxyAPI. Refreshes with the same token are collapsed (singleflight), and the file
|
||||
is re-read under a file lock before and after a refresh, so a rotation by another CLIProxyAPI process on
|
||||
that file is picked up. [R] `codex_executor_auth.go` above. A late caller holding a pre-rotation token
|
||||
can still send a second refresh. PR 6165 (five-minute result cache) is open, not merged, as read. [R]
|
||||
https://github.com/router-for-me/CLIProxyAPI/pull/6165 . In-flight overwrite (issue 6417) was fixed by
|
||||
commit 935aa6e on 2026-10-07, before 8.0.42. [R]
|
||||
https://github.com/router-for-me/CLIProxyAPI/issues/6417
|
||||
None of this locks against Hermes. CLIProxyAPI's lock covers its own credential file only. [I]
|
||||
- **On-demand refresh, no token read.** The management API has `POST /credentials/refresh` (under
|
||||
`/v8/management`). Auth is the management key, sent as `Authorization: Bearer <key>` or
|
||||
`X-Management-Key`. [R]
|
||||
https://raw.githubusercontent.com/router-for-me/CLIProxyAPI/v8.0.42/docs/management-api-v8.md
|
||||
A secondary summary says it refreshes one credential or, with `?all=true`, all of them. [S]
|
||||
https://zread.ai/router-for-me/CLIProxyAPI/17-management-api . The primary page lists no parameters, so
|
||||
how to name one credential is unconfirmed.
|
||||
A forced refresh sets a force flag in the context. In `refreshLocked` that flag sets `needOuter` true when
|
||||
an outer refresh token exists, so it skips the 96-hour check. [R] `codex_executor_auth.go` above. I could
|
||||
not read `ForceRefreshAuth` itself, so that these two are connected is [I].
|
||||
The call needs the management key, a secret. It is not an OAuth token, but the hard limits say nobody
|
||||
reads a token or auth file, and Sage should decide whether the key is covered.
|
||||
- Natural refresh timing. A fresh login has a 10-day access token and a 96-hour lead, so the first
|
||||
background refresh falls about 6 days after login. [I] from the two figures above. Waiting for it is not
|
||||
practical in a window.
|
||||
|
||||
### Hermes
|
||||
|
||||
- Refresh is lazy for `openai-codex`: the first attempt happens at expiry on an idle box, unlike Nous,
|
||||
which has a 6-hour keepalive. On a 401 the pool tries a refresh first and rotates entries only if that
|
||||
fails. [R] https://github.com/NousResearch/hermes-agent/issues/87503 ,
|
||||
https://hermes-agent.nousresearch.com/docs/user-guide/features/credential-pools
|
||||
I found no skew or lead figure for Codex in public docs. A Hermes run that sits idle may not refresh for
|
||||
about 10 days.
|
||||
- Storage and adoption. Hermes keeps its own login in `~/.hermes/auth.json`. It adopts a `~/.codex` login
|
||||
only to repair a failed refresh, and only when `auth.adopt_external_logins` is true (the default). [R]
|
||||
https://github.com/NousResearch/hermes-agent/pull/115586
|
||||
- **On-demand refresh, no token read.** `hermes auth refresh <provider> [target]` refreshes one OAuth
|
||||
credential and returns it to rotation. The docs say it "proves the grant is alive". The CLI reference
|
||||
example is `hermes auth refresh openai-codex work`. [R]
|
||||
https://hermes-agent.nousresearch.com/docs/user-guide/features/credential-pools ,
|
||||
https://hermes-agent.nousresearch.com/docs/reference/cli-commands . The helm test above used it on a
|
||||
Plus account. It needs no management key.
|
||||
|
||||
### How long a natural-refresh check would run
|
||||
|
||||
- CLIProxyAPI: about 6 days after the sign-in. [I]
|
||||
- Hermes: about 10 days after the sign-in, and only if it makes a request near expiry. [I]
|
||||
- Both are too long for the window. The forced refreshes above are the practical check. Each forced
|
||||
refresh rotates that side's refresh token once, and it does not wait for expiry.
|
||||
|
||||
## Open items nobody can answer from public sources
|
||||
|
||||
1. Whether a second sign-in on one ChatGPT account revokes the first. Two tests say no (free plan,
|
||||
Plus, up to about a day). One incident and Hermes's docs say yes. OpenAI has not said.
|
||||
2. The refresh-token lifetime when unused.
|
||||
3. The exact reuse grace window (between about 30 minutes and about 27 hours in one test; "on the order
|
||||
of an hour" from OpenAI's maintainer).
|
||||
4. How `POST /credentials/refresh` selects a single credential at 8.0.42.
|
||||
5. Hermes's Codex refresh skew, and whether the version Chet runs has `auth.adopt_external_logins`.
|
||||
6. Behavior over a full 10-day cycle with two grants live.
|
||||
|
||||
## Source list
|
||||
|
||||
OpenAI
|
||||
- https://learn.chatgpt.com/docs/auth (redirect target of https://developers.openai.com/codex/auth)
|
||||
- https://learn.chatgpt.com/docs/auth/ci-cd-auth
|
||||
- https://github.com/openai/codex/issues/10332
|
||||
- https://github.com/openai/codex/issues/40632
|
||||
- https://github.com/openai/codex-plugin-cc/issues/789
|
||||
|
||||
CLIProxyAPI (router-for-me)
|
||||
- https://github.com/router-for-me/CLIProxyAPI/releases/tag/v8.0.42
|
||||
- https://raw.githubusercontent.com/router-for-me/CLIProxyAPI/v8.0.42/docs/management-api-v8.md
|
||||
- https://raw.githubusercontent.com/router-for-me/CLIProxyAPI/v8.0.42/internal/auth/codex/openai_auth.go
|
||||
- https://raw.githubusercontent.com/router-for-me/CLIProxyAPI/v8.0.42/internal/auth/codex/refresh_patch.go
|
||||
- https://raw.githubusercontent.com/router-for-me/CLIProxyAPI/v8.0.42/internal/runtime/executor/codex_executor_auth.go
|
||||
- https://github.com/router-for-me/CLIProxyAPI/blob/v8.0.42/sdk/cliproxy/auth/conductor_refresh.go
|
||||
- https://github.com/router-for-me/CLIProxyAPI/commit/feb6398456ccaed50d20dfc6353ad050ef714d31
|
||||
- https://github.com/router-for-me/CLIProxyAPI/issues/1999 , /2385 , /3654 , /6417 , /6742 , /6288
|
||||
- https://github.com/router-for-me/CLIProxyAPI/pull/6165 , /6289
|
||||
- https://zread.ai/router-for-me/CLIProxyAPI/17-management-api [S]
|
||||
- https://zread.ai/router-for-me/CLIProxyAPI/11-multi-provider-auth-system [S] (stale: says a 24-hour Codex
|
||||
lead, superseded by feb6398)
|
||||
|
||||
Hermes (NousResearch)
|
||||
- https://hermes-agent.nousresearch.com/docs/user-guide/features/credential-pools
|
||||
- https://hermes-agent.nousresearch.com/docs/reference/cli-commands
|
||||
- https://github.com/NousResearch/hermes-agent/issues/113023 , /87503 , /73667 , /47096 , /39236
|
||||
- https://github.com/NousResearch/hermes-agent/pull/115586 , /115895
|
||||
|
||||
Third-party tests
|
||||
- https://github.com/AndreaProvenzale/awaywork/issues/190
|
||||
- https://github.com/AndreaProvenzale/awaywork/pull/194
|
||||
- https://github.com/jyje/hermes-agent-helm/issues/368
|
||||
|
||||
Other reports of the shared-family failure
|
||||
- https://github.com/keanji-x/neige-calm/issues/2314
|
||||
- https://github.com/musistudio/claude-code-router/issues/1879 [S, search snippet only]
|
||||
|
||||
Items in the first three search rounds (openclaw 26322 and 57107, pydantic-ai PR 10615, ai-shuffle 199,
|
||||
hermes-realtime 178, mnbf9rca/hermes-agent 3) were seen as search results only and are not relied on.
|
||||
@@ -0,0 +1,105 @@
|
||||
# Sage handoff after the T3 crash, 2026-10-24
|
||||
|
||||
Written 22:54Z by Sage (T3 1ef1e4f8-3ead-4208-beca-38f9f1add079) under Mos's mos-1628: "The T3 server
|
||||
crashed and killed your running processes. Do not restart long jobs." My processes died at 22:42:37Z. I
|
||||
started nothing long after that. The next Sage session resumes from this file, `scripts/mosaic queue next
|
||||
sage` and the message keys below. The next free key is sage-4297.
|
||||
|
||||
## Refs
|
||||
|
||||
- origin/refactor and the canonical HEAD were both c8a9bcf94 before this handoff commit.
|
||||
- The canonical checkout still has eight staged deletions under agents/rocko/work/queue-219 that belong to
|
||||
Rocko. Leave them as they are.
|
||||
- docs/plans/queue.json and QUEUE.md carry uncommitted queue revs, mine and other seats'. Committing them is
|
||||
queue-commit slot work, which sweeps every seat's pending revs, so I didn't run it during the crash stop.
|
||||
|
||||
## Row 329 (P1, #1827): gate red, held for Mos's ruling
|
||||
|
||||
- Both round 2 APPROVEs are recorded: Dewey c31643 at rev 4047 and Boyd c31642 at rev 4048, on candidate
|
||||
86cb5c221 with manifest 86718a1a.
|
||||
- Landing tree: /mnt/storage/scratch/sage-r329l/wt, detached at 8b4978887. That's Magica's four r2 commits
|
||||
(3a27990b4, 6a4f599df, 953773729, 8b4978887) cherry-picked onto c8a9bcf94. The BUILD-LOG.md and
|
||||
docs/SESSIONS.md conflicts were kept both ways, HEAD first. Nothing is pushed.
|
||||
- The gate (`bash gate.sh 8b4978887 c8a9bcf94` in /mnt/storage/scratch/sage-r329l) started 22:20:17Z on
|
||||
lane A. The crash killed it after `tasks` (22:40:20Z), so webui and every scripts/test-*.sh never
|
||||
reported. The output is in `r329-gate.out` here.
|
||||
- Red: cli went 1000 tests, 997 pass, 2 fail, 1 skip (FED R7). Both reds are in
|
||||
packages/cli/tests/case1-accept.test.mjs:421, the row253 C4 P10 loop. `inner` expected
|
||||
`reason=probe-inner-timeout` and `outer` expected `probe-outer-timeout`; both got `probe-inner-command`.
|
||||
`overflow` passed. The failure text is in `r329-cli-failures.txt`.
|
||||
- 329 doesn't touch case1-accept.test.mjs, case-sudo-fixture.mjs or scripts/case1-accept.sh (git diff
|
||||
--quiet c8a9bcf94..8b4978887). Magica's r2 gate had the file at 53/0.
|
||||
- Unverified hypothesis. `inner-command` is the catch-all in scripts/case1-accept.sh:228 (supervisor exit
|
||||
other than 81-86). The C4 loop arms fixtureGuard(t,files,7000), which SIGKILLs a recorded fixture pid
|
||||
after 7 s. A fault run longer than that would turn 81 or 82 into 87. The test asserts before its
|
||||
t.diagnostic line, so there's no per-mode timing. Della's 284 lane B run shared the host until about
|
||||
22:34Z. That's a timing fact, not a proven cause, and it isn't written up as load (mos-1603-1605).
|
||||
- I sent the red to Mos as sage-4296 twice. Both sends failed: T3 GET timed out after 30 s while the server
|
||||
was going down. So Mos has it only through this file and mos-1628 note 1.
|
||||
- Rerun request for Mos: a fresh full landing gate on 8b4978887 with lane B quiet, so the gate prints GREEN
|
||||
by itself (mos-1605). Before that, a Vikunja task for the P10 C4 timing, a sibling to 625. I don't
|
||||
recommend a case1-accept single-file rerun.
|
||||
- After GREEN, the landing steps:
|
||||
1. push-check, then push 8b4978887;
|
||||
2. comment on #1827 and close it, and close the row's Vikunja task;
|
||||
3. queue note, then done with --op only;
|
||||
4. DEFERRED append from `r329-deferred-draft.md` (N2 and R2-N1 bound to 331, N1 bound to the P7
|
||||
cutover);
|
||||
5. the BUILD-LOG and SESSIONS lines.
|
||||
|
||||
## Queue writes: what landed and what to redo
|
||||
|
||||
| Op | Write | State |
|
||||
|---|---|---|
|
||||
| sage-332-assign-1024a | assign 332 fethry | ok, rev 4054 |
|
||||
| sage-284-reviewers-1024b | set 284 reviewers webby | ok, rev 4055 |
|
||||
| sage-333-reviewers-1024b | set 333 reviewers boyd,duckworth | killed mid-write; 333 still shows boyd,dewey |
|
||||
| sage-332-after-1024b | set 332 after none | not run; 332 still after 331 |
|
||||
| sage-add-case2-proxy-unit-1024b | add the Case 2 proxy unit row (#1832) | not run |
|
||||
|
||||
- The crash left a dead lock: pid 2150897, op sage-333-reviewers-1024b. I removed it with `scripts/mosaic
|
||||
queue unlock` at about 22:50Z, after checking the pid was dead.
|
||||
- To redo the three writes, run `/mnt/storage/scratch/sage-r329l/batch-5.sh` without its first two lines.
|
||||
If the 333 op refuses as already used, give it a new op id.
|
||||
- The add's gate text is 258 characters (the limit is 300). It uses --issue 1832, --owner sage, --gate-owner
|
||||
sage, --after 329, --reviewer boyd and --reviewer webby. The brief is
|
||||
docs/plans/2026-10-24_case2-proxy-unit-brief.md#Brief: Case 2 proxy unit in case2-unit.sh and
|
||||
case2-install.sh (blob eddcec47 at c8a9bcf94).
|
||||
- Don't write batches with `pgrep -f` waiters. They deadlocked on 10-24, because the parent shell's command
|
||||
line contains the script text.
|
||||
|
||||
## Seats and lanes at the stop
|
||||
|
||||
- Lane A: the 329 gate, now dead. Lane B: Della finished 284 r2 at about 22:34Z (della-0059: 279 whole files,
|
||||
2501/0, 1 skip FED R7, M1-M9 killed, case1-accept not run) and said she'd commit BUILD receipts and then
|
||||
hand 284 in-review to Webby and me. Gyro got lane B CLEAR (sage-4294) and handed it back unused
|
||||
(gyro-0031). Fethry's 332 is next on lane B.
|
||||
- Fethry (65673bd9, row 332, P3): sage-4293 settled route encoding. Proxy entries in `launch.models` carry
|
||||
an explicit route key (Anthropic, GLM openai-completions, or GPT through the stack's own provider). A
|
||||
missing or unknown route refuses. Catalog-membership inference is out. Fethry picks the key name, and
|
||||
BUILD.md records it under P3 item 5. P2's `{ baseUrl, key }` seam doesn't change. Before CLEAR, Fethry
|
||||
owes his run list inline or pushed (IMPORT-RUN-LIST.txt, 211 entries, case1-accept off; I don't read
|
||||
worktrees) and a resend of fethry-0001..0003.
|
||||
- Herb (214): candidate 584ee1e20 is static CLEAN from Filbert. Herb needs a queue-write gap for his
|
||||
claimant in-review move. Then I post the request as jarvis for Filbert, and run the 214 landing gate on
|
||||
lane A after 329.
|
||||
- Researcher: row209-phase-b/REPORT.md (sha256 20421e52...ce017) is committed in this handoff commit. Its
|
||||
results still need to go to Mos and into row 209's record. Q1 is unknown, leaning coexist. Q2: the access
|
||||
token lasts about 10 days and the refresh token rotates. Q3: CLIProxyAPI's forced refresh needs the
|
||||
management key, so under D6 only a human can run it, through the P4 page. Researcher also suggests
|
||||
`auth.adopt_external_logins=false` for Hermes's Chet.
|
||||
|
||||
## Done before the stop
|
||||
|
||||
- mos-1618 / Vikunja #630: minted bot-faceless (user 15, key 15, expires 2027-01-07) into
|
||||
~/.config/mosaic-dev/secrets/mosaic-stack/faceless-vikunja.token, mode 600. Its scopes match key 12. The
|
||||
receipt is agents/sage/work/vikunja-setup/2026-10-24_mint-faceless.json, committed here. Mos received
|
||||
sage-4295. The ops-01 team add for project 33 waits until after the restart.
|
||||
|
||||
## Not yet sent to Mos (mos-1616 summary)
|
||||
|
||||
- The blobs 079debd5 and eddcec47 at c8a9bcf94, and #1832.
|
||||
- The `--proxy` flag choice and its tradeoff: root reads no config, and both wrong-flag cases fail closed.
|
||||
- The 329 request, hand-posted at c31641 (the manifest is over 60000 bytes), and both APPROVEs.
|
||||
- 229 blocked and 209's brief set; 284 to Webby; 333 to Boyd and Duckworth (the write is still pending).
|
||||
- Fethry on P3, and the Researcher results above.
|
||||
@@ -0,0 +1,95 @@
|
||||
✖ failing tests:
|
||||
|
||||
test at tests/case1-accept.test.mjs:421:49
|
||||
✖ row253 C4 P10 inner reason holds under both sudo models (11528.317041ms)
|
||||
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /P10 tester-socket-squat unnamed boundary=failed reason=probe-inner-timeout /. Input:
|
||||
|
||||
'F1 units-user pass [email protected] and mosaic-webui.service are active as mosaic (uid 997)\n' +
|
||||
'F2 console-loopback pass healthz answers, and 732 is bound to 127.0.0.1 only, by MainPID 200\n' +
|
||||
'F3 tester-reply pass chief replied to case1-accept-ec3cc0292f48\n' +
|
||||
'F4 precheck pass case2-precheck.sh --profile factory exits 0\n' +
|
||||
"F5 bwrap-label pass bwrap 300 reads 'bwrap (enforce)' and its claude descendant 302 reads 'bwrap//&unpriv_bwrap (enforce)'\n" +
|
||||
"F6 claude-pin pass /opt/mosaic-stack/claude-2.1.0/node_modules/@anthropic-ai/claude-code/bin/claude.exe prints '2.1.0 (Claude Code)'\n" +
|
||||
'F7 pm-live pass one live launch: pm run r-0002\n' +
|
||||
"F8 credential-file pass /srv/mosaic/.config/mosaic-dev/secrets/claude-session.env is 'mosaic 600 regular file'\n" +
|
||||
'F9 autoupdater-off pass /etc/claude-code/managed-settings.json is root:root 0644 with env.DISABLE_AUTOUPDATER "1"\n' +
|
||||
'F10 node-pin pass /opt/mosaic-stack/node-v24.1.0/bin/node prints v24.1.0\n' +
|
||||
'F11 no-discord pass /srv/mosaic/.mosaic-dev/discord is absent\n' +
|
||||
'P1 mosaic-sudo closed sudo -l: User mosaic is not allowed to run sudo on dev-53.\n' +
|
||||
"P2 mosaic-groups-keys closed id -G is '997'; authorized_keys is absent\n" +
|
||||
'P3 localadmin-sudo accepted Q183 sudo -l -U localadmin lists 1 NOPASSWD lines\n' +
|
||||
"P4 pve-root-pbs accepted Q182+Q183 root on pve over VM 1008, and PBS backups that hold /srv/mosaic with the credential; this script can't see pve\n" +
|
||||
'P5 jarvis-reach closed jarvis is in none of sudo, docker and lxd (groups: jarvis)\n' +
|
||||
'P6 group-members closed sudo holds only localadmin, and docker and lxd have no members\n' +
|
||||
'P7 sudoers-d closed no file under /etc/sudoers.d names mosaic\n' +
|
||||
'P8 lan-listeners closed 732 is on 127.0.0.1 only by MainPID 200; no TCP listener on 7331 or 7333; tester uses /srv/mosaic/.mosaic-dev/console/tester.sock\n' +
|
||||
'P9 bwrap-userns accepted mos-1096 hostcheck items 1-6 ok, jarvis is denied bwrap, and group mosaic has no other member\n' +
|
||||
'P10 tester-socket-squat unnamed boundary=failed reason=probe-inner-command path=/srv/mosaic/.mosaic-dev/console/tester.sock MainPID=200; hostile same-uid substitution remains\n' +
|
||||
'P11 hostcheck-before-bus closed mosaic-hostcheck.service is enabled, and its Before= names [email protected]\n' +
|
||||
'count pass 11\n' +
|
||||
'count FAIL 0\n' +
|
||||
'count closed 7\n' +
|
||||
'count accepted Q182+Q183 1\n' +
|
||||
'count accepted Q183 1\n' +
|
||||
'count accepted mos-1096 1\n' +
|
||||
'count unnamed 1\n' +
|
||||
'case1-accept: every F check passes; send the whole output to Duckworth, and each unnamed line to Mos\n'
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/sage-r329l/wt/packages/cli/tests/case1-accept.test.mjs:445:10)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: `F1 units-user pass [email protected] and mosaic-webui.service are active as mosaic (uid 997)\nF2 console-loopback pass healthz answers, and 732 is bound to 127.0.0.1 only, by MainPID 200\nF3 tester-reply pass chief replied to case1-accept-ec3cc0292f48\nF4 precheck pass case2-precheck.sh --profile factory exits 0\nF5 bwrap-label pass bwrap 300 reads 'bwrap (enforce)' and its claude descendant 302 reads 'bwrap//&unpriv_bwrap (enforce)'\nF6 claude-pin pass /opt/mosaic-stack/claude-2.1.0/node_modules/@anthropic-ai/claude-code/bin/claude.exe prints '2.1.0 (Claude Code)'\nF7 pm-live pass one live launch: pm run r-0002\nF8 credential-file pass /srv/mosaic/.config/mosaic-dev/secrets/claude-session.env is 'mosaic 600 regular file'\nF9 autoupdater-off pass /etc/claude-code/managed-settings.json is root:root 0644 with env.DISABLE_AUTOUPDATER "1"\nF10 node-pin pass /opt/mosaic-stack/node-v24.1.0/bin/node prints v24.1.0\nF11 no-discord pass /srv/mosaic/.mosaic-dev/discord is absent\nP1 mosaic-sudo closed sudo -l: User mosaic is not allowed to run sudo on dev-53.\nP2 mosaic-groups-keys closed id -G is '997'; authorized_keys is absent\nP3 localadmin-sudo accepted Q183 sudo -l -U localadmin lists 1 NOPASSWD lines\nP4 pve-root-pbs accepted Q182+Q183 root on pve over VM 1008, and PBS backups that hold /srv/mosaic with the credential; this script can't see pve\nP5 jarvis-reach closed jarvis is in none of sudo, docker and lxd (groups: jarvis)\nP6 group-members closed sudo holds only localadmin, and docker and lxd have no members\nP7 sudoers-d closed no file under /etc/sudoers.d names mosaic\nP8 lan-listeners closed 732 is on 127.0.0.1 only by MainPID 200; no TCP listener on 7331 or 7333; tester uses /srv/mosaic/.mosaic-dev/console/tester.sock\nP9 bwrap-userns accepted mos-1096 hostcheck items 1-6 ok, jarvis is denied bwrap, and group mosaic has no other member\nP10 tester-socket-squat unnamed boundary=failed reason=probe-inner-command path=/srv/mosaic/.mosaic-dev/console/tester.sock MainPID=200; hostile same-uid substitution remains\nP11 hostcheck-before-bus closed mosaic-hostcheck.service is enabled, and its Before= names [email protected]\ncount pass 11\ncount FAIL 0\ncount closed 7\ncount accepted Q182+Q183 1\ncount accepted Q183 1\ncount accepted mos-1096 1\ncount unnamed 1\ncase1-accept: every F check passes; send the whole output to Duckworth, and each unnamed line to Mos\n`,
|
||||
expected: /P10 tester-socket-squat unnamed boundary=failed reason=probe-inner-timeout /,
|
||||
operator: 'match',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at tests/case1-accept.test.mjs:421:49
|
||||
✖ row253 C4 P10 outer reason holds under both sudo models (12748.726934ms)
|
||||
AssertionError [ERR_ASSERTION]: The input did not match the regular expression /P10 tester-socket-squat unnamed boundary=failed reason=probe-outer-timeout /. Input:
|
||||
|
||||
'F1 units-user pass [email protected] and mosaic-webui.service are active as mosaic (uid 997)\n' +
|
||||
'F2 console-loopback pass healthz answers, and 732 is bound to 127.0.0.1 only, by MainPID 200\n' +
|
||||
'F3 tester-reply pass chief replied to case1-accept-d9f4f6fd8da1\n' +
|
||||
'F4 precheck pass case2-precheck.sh --profile factory exits 0\n' +
|
||||
"F5 bwrap-label pass bwrap 300 reads 'bwrap (enforce)' and its claude descendant 302 reads 'bwrap//&unpriv_bwrap (enforce)'\n" +
|
||||
"F6 claude-pin pass /opt/mosaic-stack/claude-2.1.0/node_modules/@anthropic-ai/claude-code/bin/claude.exe prints '2.1.0 (Claude Code)'\n" +
|
||||
'F7 pm-live pass one live launch: pm run r-0002\n' +
|
||||
"F8 credential-file pass /srv/mosaic/.config/mosaic-dev/secrets/claude-session.env is 'mosaic 600 regular file'\n" +
|
||||
'F9 autoupdater-off pass /etc/claude-code/managed-settings.json is root:root 0644 with env.DISABLE_AUTOUPDATER "1"\n' +
|
||||
'F10 node-pin pass /opt/mosaic-stack/node-v24.1.0/bin/node prints v24.1.0\n' +
|
||||
'F11 no-discord pass /srv/mosaic/.mosaic-dev/discord is absent\n' +
|
||||
'P1 mosaic-sudo closed sudo -l: User mosaic is not allowed to run sudo on dev-53.\n' +
|
||||
"P2 mosaic-groups-keys closed id -G is '997'; authorized_keys is absent\n" +
|
||||
'P3 localadmin-sudo accepted Q183 sudo -l -U localadmin lists 1 NOPASSWD lines\n' +
|
||||
"P4 pve-root-pbs accepted Q182+Q183 root on pve over VM 1008, and PBS backups that hold /srv/mosaic with the credential; this script can't see pve\n" +
|
||||
'P5 jarvis-reach closed jarvis is in none of sudo, docker and lxd (groups: jarvis)\n' +
|
||||
'P6 group-members closed sudo holds only localadmin, and docker and lxd have no members\n' +
|
||||
'P7 sudoers-d closed no file under /etc/sudoers.d names mosaic\n' +
|
||||
'P8 lan-listeners closed 732 is on 127.0.0.1 only by MainPID 200; no TCP listener on 7331 or 7333; tester uses /srv/mosaic/.mosaic-dev/console/tester.sock\n' +
|
||||
'P9 bwrap-userns accepted mos-1096 hostcheck items 1-6 ok, jarvis is denied bwrap, and group mosaic has no other member\n' +
|
||||
'P10 tester-socket-squat unnamed boundary=failed reason=probe-inner-command path=/srv/mosaic/.mosaic-dev/console/tester.sock MainPID=200; hostile same-uid substitution remains\n' +
|
||||
'P11 hostcheck-before-bus closed mosaic-hostcheck.service is enabled, and its Before= names [email protected]\n' +
|
||||
'count pass 11\n' +
|
||||
'count FAIL 0\n' +
|
||||
'count closed 7\n' +
|
||||
'count accepted Q182+Q183 1\n' +
|
||||
'count accepted Q183 1\n' +
|
||||
'count accepted mos-1096 1\n' +
|
||||
'count unnamed 1\n' +
|
||||
'case1-accept: every F check passes; send the whole output to Duckworth, and each unnamed line to Mos\n'
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/sage-r329l/wt/packages/cli/tests/case1-accept.test.mjs:445:10)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: `F1 units-user pass [email protected] and mosaic-webui.service are active as mosaic (uid 997)\nF2 console-loopback pass healthz answers, and 732 is bound to 127.0.0.1 only, by MainPID 200\nF3 tester-reply pass chief replied to case1-accept-d9f4f6fd8da1\nF4 precheck pass case2-precheck.sh --profile factory exits 0\nF5 bwrap-label pass bwrap 300 reads 'bwrap (enforce)' and its claude descendant 302 reads 'bwrap//&unpriv_bwrap (enforce)'\nF6 claude-pin pass /opt/mosaic-stack/claude-2.1.0/node_modules/@anthropic-ai/claude-code/bin/claude.exe prints '2.1.0 (Claude Code)'\nF7 pm-live pass one live launch: pm run r-0002\nF8 credential-file pass /srv/mosaic/.config/mosaic-dev/secrets/claude-session.env is 'mosaic 600 regular file'\nF9 autoupdater-off pass /etc/claude-code/managed-settings.json is root:root 0644 with env.DISABLE_AUTOUPDATER "1"\nF10 node-pin pass /opt/mosaic-stack/node-v24.1.0/bin/node prints v24.1.0\nF11 no-discord pass /srv/mosaic/.mosaic-dev/discord is absent\nP1 mosaic-sudo closed sudo -l: User mosaic is not allowed to run sudo on dev-53.\nP2 mosaic-groups-keys closed id -G is '997'; authorized_keys is absent\nP3 localadmin-sudo accepted Q183 sudo -l -U localadmin lists 1 NOPASSWD lines\nP4 pve-root-pbs accepted Q182+Q183 root on pve over VM 1008, and PBS backups that hold /srv/mosaic with the credential; this script can't see pve\nP5 jarvis-reach closed jarvis is in none of sudo, docker and lxd (groups: jarvis)\nP6 group-members closed sudo holds only localadmin, and docker and lxd have no members\nP7 sudoers-d closed no file under /etc/sudoers.d names mosaic\nP8 lan-listeners closed 732 is on 127.0.0.1 only by MainPID 200; no TCP listener on 7331 or 7333; tester uses /srv/mosaic/.mosaic-dev/console/tester.sock\nP9 bwrap-userns accepted mos-1096 hostcheck items 1-6 ok, jarvis is denied bwrap, and group mosaic has no other member\nP10 tester-socket-squat unnamed boundary=failed reason=probe-inner-command path=/srv/mosaic/.mosaic-dev/console/tester.sock MainPID=200; hostile same-uid substitution remains\nP11 hostcheck-before-bus closed mosaic-hostcheck.service is enabled, and its Before= names [email protected]\ncount pass 11\ncount FAIL 0\ncount closed 7\ncount accepted Q182+Q183 1\ncount accepted Q183 1\ncount accepted mos-1096 1\ncount unnamed 1\ncase1-accept: every F check passes; send the whole output to Duckworth, and each unnamed line to Mos\n`,
|
||||
expected: /P10 tester-socket-squat unnamed boundary=failed reason=probe-outer-timeout /,
|
||||
operator: 'match',
|
||||
diff: 'simple'
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
|
||||
## Row 329 N2: `mosaic proxy fetch` has no agent or human guard
|
||||
|
||||
Sage, for Dewey's round 1 N2 on row 329 (c31639, landing deferral per c31643). `mosaic proxy fetch` downloads
|
||||
the pinned CLIProxyAPI archive from GitHub and writes `<dataRoot>/opt/cliproxyapi/` for any caller that has
|
||||
the instance config. P1's brief didn't ask for a guard. The fetch is pinned and sha256-checked against
|
||||
`packages/model-proxy/PIN`, so the risk is an unasked network fetch and a disk write, not a wrong binary.
|
||||
Bound to row 331 (P2): P2 decides whether fetch needs the human or agent guard its key verbs get, and
|
||||
records the choice in its BUILD.md. If P2 leaves it, P7 decides before cutover.
|
||||
|
||||
## Row 329 R2-N1: the management-key rule lives in three places
|
||||
|
||||
Sage, for Dewey's round 2 R2-N1 on row 329 (c31643). Round 2 tightened the key rule to
|
||||
`/^[\x21-\x7e]{1,4096}$/` in `packages/model-proxy/src/common.mjs`:33, which `initializeProxy` enforces, but
|
||||
`packages/bus/src/broker.mjs`:4827 and `packages/cli/src/model-proxy.mjs`:57 still use the round 1 predicate.
|
||||
A Latin-1 or edge-space key gets past both and is refused at init with `proxy-key-invalid`, before any write,
|
||||
but only after both prompts and a bus round trip. Fix: export one predicate from the model-proxy package
|
||||
and use it in all three places, with a test at each caller. Bound to row 331 (P2), which edits the broker's
|
||||
proxy verbs.
|
||||
|
||||
## Row 329 N1: the 127.0.0.1 bind isn't observed on the real binary
|
||||
|
||||
Sage, for Dewey's round 1 N1 on row 329 (c31639). The acceptance netns had only `lo`, so P1 writes
|
||||
`server.host: 127.0.0.1` and `management.allow-remote: false`
|
||||
but never saw the real binary refuse a non-loopback client.
|
||||
Bound to P7: at cutover, with Jason present, confirm the listener is on 127.0.0.1 only (`ss -ltnp`), and
|
||||
record it in P7's receipt.
|
||||
@@ -0,0 +1,29 @@
|
||||
pi /mnt/storage/scratch/sage-r329l/wt/node_modules/@earendil-works/pi-coding-agent/dist/bundle/cli.js
|
||||
start 22:20:17Z base c8a9bcf94 cand 8b4978887 TMPDIR /mnt/storage/scratch/tmp/wCOHZ8 nice -4
|
||||
esbuild 0.28.2
|
||||
== bus rc 0 22:20:50Z ℹ tests 1 ℹ pass 1 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0 ℹ tests 1019 ℹ pass 1019 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0
|
||||
== business rc 0 22:20:54Z ℹ tests 283 ℹ pass 283 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0
|
||||
== cli rc 1 22:38:18Z ℹ tests 1000 ℹ pass 997 ℹ fail 2 ℹ cancelled 0 ℹ skipped 1 ℹ todo 0
|
||||
83:✖ row253 C4 P10 inner reason holds under both sudo models (11528.317041ms)
|
||||
84:✖ row253 C4 P10 outer reason holds under both sudo models (12748.726934ms)
|
||||
1120:✖ failing tests:
|
||||
1123:✖ row253 C4 P10 inner reason holds under both sudo models (11528.317041ms)
|
||||
1170:✖ row253 C4 P10 outer reason holds under both sudo models (12748.726934ms)
|
||||
== control-board rc 0 22:38:19Z ℹ tests 130 ℹ pass 130 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0
|
||||
== conversation rc 0 22:39:03Z ℹ tests 187 ℹ pass 187 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0
|
||||
== discord rc 0 22:39:06Z ℹ tests 200 ℹ pass 200 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0
|
||||
== harness rc 1 22:39:19Z ℹ tests 176 ℹ pass 175 ℹ fail 1 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0
|
||||
55:✖ claude: row 66 a reply with a tell gets the correction notice on the next turn, a clean reply gets none, and the counts read after (1435.593566ms)
|
||||
186:✖ failing tests:
|
||||
189:✖ claude: row 66 a reply with a tell gets the correction notice on the next turn, a clean reply gets none, and the counts read after (1435.593566ms)
|
||||
== ledger rc 0 22:39:36Z ℹ tests 89 ℹ pass 89 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0
|
||||
== library rc 0 22:39:43Z ℹ tests 149 ℹ pass 149 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0
|
||||
== model-proxy rc 0 22:39:45Z ℹ tests 66 ℹ pass 66 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0
|
||||
== mosaic rc 0 22:40:05Z ℹ tests 69 ℹ pass 69 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0
|
||||
== plugins rc 0 22:40:06Z ℹ tests 120 ℹ pass 120 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0
|
||||
== processing-control rc 0 22:40:07Z ℹ tests 62 ℹ pass 62 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0
|
||||
== project rc 0 22:40:09Z ℹ tests 41 ℹ pass 41 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0
|
||||
== runs rc 0 22:40:10Z ℹ tests 43 ℹ pass 43 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0
|
||||
== seat rc 0 22:40:18Z ℹ tests 218 ℹ pass 218 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0
|
||||
== supervisor rc 0 22:40:18Z ℹ tests 45 ℹ pass 45 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0
|
||||
== tasks rc 0 22:40:20Z ℹ tests 100 ℹ pass 100 ℹ fail 0 ℹ cancelled 0 ℹ skipped 0 ℹ todo 0
|
||||
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"at": "2026-10-24T22:38:20.521Z",
|
||||
"user": "bot-faceless",
|
||||
"problems": [],
|
||||
"permissions": {
|
||||
"projects": [
|
||||
"background",
|
||||
"background_get",
|
||||
"backgrounds_upload",
|
||||
"create",
|
||||
"projectusers",
|
||||
"read_all",
|
||||
"read_one",
|
||||
"tasks_by_index",
|
||||
"update",
|
||||
"users_search",
|
||||
"views_buckets",
|
||||
"views_buckets_post",
|
||||
"views_buckets_put",
|
||||
"views_buckets_tasks",
|
||||
"views_buckets_tasks_get"
|
||||
],
|
||||
"projects_views": [
|
||||
"create",
|
||||
"read_all",
|
||||
"read_one",
|
||||
"update"
|
||||
],
|
||||
"projects_views_tasks": [
|
||||
"read_all"
|
||||
],
|
||||
"tasks": [
|
||||
"create",
|
||||
"create_bulk",
|
||||
"position",
|
||||
"read",
|
||||
"read_all",
|
||||
"read_one",
|
||||
"update",
|
||||
"update_bulk"
|
||||
],
|
||||
"tasks_assignees": [
|
||||
"create",
|
||||
"delete",
|
||||
"read_all",
|
||||
"update_bulk"
|
||||
],
|
||||
"tasks_attachments": [
|
||||
"create",
|
||||
"read_all",
|
||||
"read_one"
|
||||
],
|
||||
"tasks_comments": [
|
||||
"create",
|
||||
"read_all",
|
||||
"read_one",
|
||||
"update"
|
||||
],
|
||||
"tasks_labels": [
|
||||
"create",
|
||||
"delete",
|
||||
"read_all",
|
||||
"update_bulk"
|
||||
],
|
||||
"tasks_relations": [
|
||||
"create",
|
||||
"delete"
|
||||
],
|
||||
"labels": [
|
||||
"create",
|
||||
"read_all",
|
||||
"read_one",
|
||||
"update"
|
||||
],
|
||||
"filters": [
|
||||
"create",
|
||||
"read_one",
|
||||
"update"
|
||||
],
|
||||
"reactions": [
|
||||
"create",
|
||||
"delete",
|
||||
"read_all"
|
||||
]
|
||||
},
|
||||
"botCreate": 201,
|
||||
"botId": 15,
|
||||
"tokenCreate": 201,
|
||||
"tokenId": 15,
|
||||
"expires": "2027-01-07T00:00:00Z",
|
||||
"startsTk": true,
|
||||
"file": "/home/jwoltje/.config/mosaic-dev/secrets/mosaic-stack/faceless-vikunja.token",
|
||||
"mode": "600",
|
||||
"inverse": "svc: DELETE /api/v1/tokens/15",
|
||||
"keysNow": [
|
||||
15
|
||||
],
|
||||
"newKeyProjects": {
|
||||
"status": 200,
|
||||
"count": 0
|
||||
},
|
||||
"newKeyTask198": 403
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"at": "2026-10-24T23:07:42.645Z",
|
||||
"project": 19,
|
||||
"created": [
|
||||
{
|
||||
"status": 201,
|
||||
"id": 631,
|
||||
"readBack": 200,
|
||||
"done": false
|
||||
}
|
||||
],
|
||||
"problems": []
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Sage, 2026-10-24: row 329 P1 (model-proxy package, Gitea 1827) landed 61ebf2c34. Comment on parent 237 only; 237 stays open for P2-P7.
|
||||
// Checks each task's title before posting. Prints no secret.
|
||||
// Usage: node comment-r329-landed.mjs https://tasks.woltje.com SAGE_TOKEN_FILE SYNC_TOKEN_FILE OUT_JSON
|
||||
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
||||
const [ORIGIN, KEY, SYNC, OUT] = process.argv.slice(2);
|
||||
if (!/^https:\/\/tasks\.woltje\.com$/.test(ORIGIN)) throw new Error("tasks.woltje.com only");
|
||||
if (!OUT || existsSync(OUT)) throw new Error("OUT_JSON missing or exists; refusing");
|
||||
const PROJECT = 19;
|
||||
const PSIG = "Posted by Sage (T3 1ef1e4f8) with Sage's own key (bot user 13).";
|
||||
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
const ps = (xs) => xs.map((x) => `<p>${esc(x)}</p>`).join("") + `<p>${esc(PSIG)}</p>`;
|
||||
const POSTS = [
|
||||
{ id: 237, title: /^Programmatic agent credentials via local proxy/, body: ["Row 329, P1 of this design (the model-proxy package, pinned CLIProxyAPI fetch, proxy init and the proxy unit, Gitea #1827), landed on refactor at 61ebf2c34, pushed 23:31:27Z 2026-10-24. Magica built it. Boyd approved it in comment 31642 (rev 4048) and Dewey in comment 31643 (rev 4047). Sage's fresh landing gate went GREEN by itself at 23:29:14Z (mos-1634): every package suite except queue at 0 failures, with 1 allowed FED R7 skip, and every scripts/test-*.sh at rc 0. #1827 is closed with landing comment 31650. The earlier red run's case1-accept C4 P10 failures are task 631. Carried to docs/plans/DEFERRED.md: N2 and R2-N1 bound to row 331 (P2), and N1 (observing the 127.0.0.1 bind on the real binary) bound to the P7 cutover. This task stays open for P2 to P7. The live stack is not re-pinned."] },
|
||||
];
|
||||
if (/LANDSHA|PUSHTIME|CLOSEID|GATE\b/.test(JSON.stringify(POSTS))) throw new Error("placeholder left; refusing");
|
||||
const key = readFileSync(KEY, "utf8").trim(), sync = readFileSync(SYNC, "utf8").trim();
|
||||
async function api(method, path, body, auth) {
|
||||
const r = await fetch(ORIGIN + path, { method, headers: { Authorization: `Bearer ${auth}`, "Content-Type": "application/json" }, body: body === undefined ? undefined : JSON.stringify(body), redirect: "error" });
|
||||
const t = await r.text(); let json = null; try { json = JSON.parse(t); } catch {}
|
||||
return { status: r.status, json };
|
||||
}
|
||||
const list = (j) => j?.items ?? (Array.isArray(j) ? j : []);
|
||||
const rec = { at: new Date().toISOString(), comments: [], problems: [] };
|
||||
const save = () => {
|
||||
const out = JSON.stringify(rec, null, 1) + "\n";
|
||||
if (out.includes(key) || out.includes(sync)) throw new Error("secret in the record; not written");
|
||||
writeFileSync(OUT, out);
|
||||
};
|
||||
for (const P of POSTS) {
|
||||
const t = await api("GET", `/api/v2/tasks/${P.id}`, undefined, sync);
|
||||
if (t.status !== 200 || t.json?.project_id !== PROJECT || !P.title.test(t.json?.title ?? "")) throw new Error(`task ${P.id}: ${t.status}, title check failed; nothing posted`);
|
||||
}
|
||||
writeFileSync(OUT, "", { flag: "wx" });
|
||||
for (const P of POSTS) {
|
||||
const c = await api("PUT", `/api/v1/tasks/${P.id}/comments`, { comment: ps(P.body) }, key);
|
||||
const e = { task: P.id, status: c.status, id: c.json?.id ?? null };
|
||||
rec.comments.push(e);
|
||||
if (c.status >= 300 || !Number.isSafeInteger(c.json?.id)) { rec.problems.push(`comment ${P.id}: ${c.status} ${c.json?.message ?? ""}`); save(); continue; }
|
||||
const back = await api("GET", `/api/v2/tasks/${P.id}/comments?per_page=50`, undefined, sync);
|
||||
const found = list(back.json).find((x) => x.id === c.json.id);
|
||||
Object.assign(e, { readBack: back.status, found: Boolean(found) });
|
||||
if (back.status !== 200 || !found) rec.problems.push(`comment ${P.id} read-back: ${back.status}`);
|
||||
save();
|
||||
}
|
||||
save();
|
||||
process.stdout.write(JSON.stringify(rec, null, 1) + "\n");
|
||||
if (rec.problems.length) process.exit(2);
|
||||
@@ -0,0 +1,62 @@
|
||||
// Sage, 2026-10-24: files the row253 C4 P10 red seen in row 329's landing gate (mos-1634 item 2) as a P19 task,
|
||||
// a sibling of 625. No relation, labels, assignees, due dates or bucket moves. Sage's own key (bot user 13);
|
||||
// the sync key reads back. Refuses if the title exists. Prints no secret.
|
||||
// Usage: node file-c4p10.mjs https://tasks.woltje.com SAGE_TOKEN_FILE SYNC_TOKEN_FILE OUT_JSON
|
||||
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
||||
const [ORIGIN, KEY, SYNC, OUT] = process.argv.slice(2);
|
||||
if (!/^https:\/\/tasks\.woltje\.com$/.test(ORIGIN)) throw new Error("tasks.woltje.com only");
|
||||
if (!OUT || existsSync(OUT)) throw new Error("OUT_JSON missing or exists; refusing");
|
||||
const PROJECT = 19;
|
||||
const SIG = "Created by Sage (T3 1ef1e4f8) with Sage's own key (bot user 13).";
|
||||
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
const ps = (sig, xs) => xs.map((x) => `<p>${esc(x)}</p>`).join("") + `<p>${esc(sig)}</p>`;
|
||||
const TASKS = [
|
||||
{
|
||||
title: "Stack: case1-accept row253 C4 P10 inner and outer timeouts reported probe-inner-command (cause unknown)",
|
||||
description: ps(SIG, [
|
||||
"Filed by Mos's ruling mos-1634 as a sibling of 625. Seen in row 329's landing gate on 8b4978887 (base c8a9bcf94), 2026-10-24 22:20-22:42Z, before the T3 crash killed the gate. The cli suite went 1000 tests, 997 pass, 2 fail, 1 skip (FED R7).",
|
||||
"Both reds are the loop at packages/cli/tests/case1-accept.test.mjs:421. 'row253 C4 P10 inner' expected reason=probe-inner-timeout and 'row253 C4 P10 outer' expected reason=probe-outer-timeout. Both got reason=probe-inner-command. 'overflow' passed. Every F check passed in both outputs. Failure text: agents/sage/work/handoff-2026-10-24/r329-cli-failures.txt (98614187c).",
|
||||
"Not row 329's code: git diff --stat c8a9bcf94 8b4978887 is empty on case1-accept.test.mjs, case-sudo-fixture.mjs and scripts/case1-accept.sh (checked by Mos). The red is on base code. Magica's r2 gate had the file at 53/0.",
|
||||
"Cause unknown. Unverified hypothesis: probe-inner-command is the catch-all in scripts/case1-accept.sh:228 for a supervisor exit other than 81-86. The C4 loop arms fixtureGuard(t,files,7000), which SIGKILLs a recorded fixture pid after 7 s; a fault run longer than that would turn exit 81 or 82 into 87. The test asserts before its t.diagnostic line, so there is no per-mode timing to confirm or refute this. This is not recorded as load.",
|
||||
"Host facts, not a cause: Della's row 284 lane B run shared the host until about 22:34Z; the 1-minute load was 7.51 at 23:03Z and 10.83 at 23:06Z. Next: the 329 rerun gate (mos-1634 item 1) reports whether it recurs; a fix that changes this old test needs Mos's OK.",
|
||||
]),
|
||||
},
|
||||
];
|
||||
const key = readFileSync(KEY, "utf8").trim(), sync = readFileSync(SYNC, "utf8").trim();
|
||||
async function api(method, path, body, auth) {
|
||||
const r = await fetch(ORIGIN + path, { method, headers: { Authorization: `Bearer ${auth}`, "Content-Type": "application/json" }, body: body === undefined ? undefined : JSON.stringify(body), redirect: "error" });
|
||||
const t = await r.text(); let json = null; try { json = JSON.parse(t); } catch {}
|
||||
return { status: r.status, json };
|
||||
}
|
||||
const list = (j) => j?.items ?? (Array.isArray(j) ? j : []);
|
||||
async function existing() {
|
||||
const out = [];
|
||||
for (let page = 1; page < 20; page++) {
|
||||
const r = await api("GET", `/api/v2/projects/${PROJECT}/tasks?per_page=50&page=${page}`, undefined, sync);
|
||||
if (r.status !== 200) throw new Error(`list tasks: ${r.status}`);
|
||||
out.push(...list(r.json));
|
||||
if (list(r.json).length < 50) return out;
|
||||
}
|
||||
throw new Error("more than 19 pages");
|
||||
}
|
||||
const rec = { at: new Date().toISOString(), project: PROJECT, created: null, problems: [] };
|
||||
const save = () => {
|
||||
const out = JSON.stringify(rec, null, 1) + "\n";
|
||||
if (out.includes(key) || out.includes(sync)) throw new Error("secret in the record; not written");
|
||||
writeFileSync(OUT, out);
|
||||
};
|
||||
const have = await existing();
|
||||
for (const T of TASKS) if (have.some((t) => t.title === T.title)) throw new Error("title exists; refusing");
|
||||
writeFileSync(OUT, "", { flag: "wx" });
|
||||
rec.created = [];
|
||||
for (const T of TASKS) {
|
||||
const r = await api("PUT", `/api/v1/projects/${PROJECT}/tasks`, { title: T.title, description: T.description }, key);
|
||||
const c = { status: r.status, id: r.json?.id ?? null };
|
||||
rec.created.push(c);
|
||||
if (r.status >= 300 || !Number.isSafeInteger(r.json?.id)) { rec.problems.push(`create: ${r.status} ${r.json?.message ?? ""}`); continue; }
|
||||
const back = await api("GET", `/api/v2/tasks/${r.json.id}`, undefined, sync);
|
||||
Object.assign(c, { readBack: back.status, done: back.json?.done });
|
||||
if (back.status !== 200 || back.json?.title !== T.title || back.json?.done) rec.problems.push(`read-back differs for ${r.json.id}`);
|
||||
}
|
||||
save();
|
||||
console.log(JSON.stringify(rec));
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"at": "2026-10-24T23:32:49.768Z",
|
||||
"comments": [
|
||||
{
|
||||
"task": 237,
|
||||
"status": 201,
|
||||
"id": 1109,
|
||||
"readBack": 200,
|
||||
"found": true
|
||||
}
|
||||
],
|
||||
"problems": []
|
||||
}
|
||||
@@ -1685,7 +1685,35 @@ are never rewritten or removed; corrections are new entries.
|
||||
2026-10-24 | Fethry14f80e3c | Row328 T9 timer race (#1826) | New fethry/row328 worktree on2ce476d54, owner start rev3992 opfethry-0037. Test-only +4/-3 under Mos1594, no assertion/product changes. Local root npm ci, no links. Static-only stress/M1 preparation and untested push; waiting for Sage CLEAR, then Boyd/Dewey review.
|
||||
|
||||
2026-10-24 | Fethry14f80e3c | Row328 T9 race acceptance evidence | sage4214 on06c57a7db:400 concurrent M1 attempts yield26 expected failures, fixed400/400, whole file6/6; exact restoration/clean checks and process sweep empty. All801 receipts/nice/load preserved. Manifest1e010468 one approved+4/-3 old test, no assertion/product change. Earlier aborted and serial non-repro receipts separate. Review handoff Boyd/Dewey, Sage landing gate pending.
|
||||
|
||||
2026-10-24 | Sage 1ef1e4f8 | Landings 257, 266, 224, 328; model-proxy rows 329-333 filed | 257 73692e1bb, 266 3e5041b43, 224 5e77cdd09, 328 8bb5b3c73 landed on GREEN gates. Rows 329-333 briefed from 7fe32d722 (mos-1600/1601); 330 blocked on Jason's subdomain answer (mos-1602). Magica holds the slot for 329's builder gate.
|
||||
2026-10-24 | Sage 1ef1e4f8 | T3 crash stop (mos-1628) | Processes died 22:42:37Z mid row 329 landing gate on 8b4978887: cli 997/2 (case1-accept row253 C4 P10 inner/outer got probe-inner-command), rerun waits on Mos; nothing pushed for 329. Minted bot-faceless user 15 key 15 (#630). Queue: 332 assign rev 4054, 284 reviewers rev 4055; 333 reviewers, 332 after and the #1832 add still to run; dead lock removed. Handoff agents/sage/work/handoff-2026-10-24/HANDOFF.md.
|
||||
2026-10-24 | magica | row329 P1 #1827 | Started isolated magica/row329 at 7fe32d722; approved brief recovered, acceptance first; suites await CLEAR.
|
||||
2026-10-24 | magica | row329 P1 #1827 | Final builder checks green with the recorded Mos1605 continuation and final unit-reader gate; preparing review manifest on magica/row329, no old assertions changed, Q250 scope recorded.
|
||||
2026-10-24 | magica | row329 round2 #1827 | In-progress rev4031; B1 ASCII-only key fix/new inventory tests and N1/N3/N4 docs, cleared reader verification running, old tests byte-identical.
|
||||
2026-10-24 | magica | row329 R2 #1827 | ASCII management-key fix and docs verified: package66/66,bus readers261/261,CLI readers726/726,config55/55; no required failures/skips, slot returned. Preparing authorized ff review candidate; old tests unchanged.
|
||||
2026-10-24 | Sage 1ef1e4f8 | row329 landing #1827 (mos-1634) | Gate GREEN 23:29:14Z unchanged, landed 61ebf2c34 pushed 23:31:27Z; #1827 closed c31650; queue note 4063, done 4064 (b24d917ec); DEFERRED N2/R2-N1 to 331, N1 to P7; Vikunja 631 filed, 237 c1109; 284 and 157 must rebase.
|
||||
2026-10-24 | fethry (65673bd9-3bfa-412b-85d5-9b364187d8b6) | row332 P3 proxy harness configuration at c8a9bcf94 | Started recovery and design; npm ci in isolated worktree; tests held for Sage CLEAR; no live proxy or credentials accessed.
|
||||
2026-10-24 | fethry (65673bd9-3bfa-412b-85d5-9b364187d8b6) | row332 crash stop (mos-1630) | Documentation-only handoff on fethry/row332; four unrun test files and import audit script left uncommitted; no tests/probes/production edits; no jobs restarted or other threads messaged.
|
||||
|
||||
2026-10-24 | fethry (65673bd9-3bfa-412b-85d5-9b364187d8b6) | row332 resume sage-4298 | Added unrun launcher persistence test and third mutant; regenerated import audit and delivered full inline report to Sage; no tests, probes, fetches or production edits; CLEAR and after-none notice pending.
|
||||
|
||||
2026-10-24 | fethry (65673bd9-3bfa-412b-85d5-9b364187d8b6) | row332 start sage-4301 | Canonical fethry-332-start succeeded rev4059 briefed→in-progress; no tests/probes/fetches pending CLEAR.
|
||||
|
||||
2026-10-24 | fethry (65673bd9-3bfa-412b-85d5-9b364187d8b6) | row332 P1 rebase/reader pre-step | Rebased to 9e2689dff, HEAD efe3e4697; uncommitted tests restored; P3 imports P1 broker through cli test helpers, stopped/reported under sage-4313 step1 before execution.
|
||||
|
||||
2026-10-24 | fethry (65673bd9-3bfa-412b-85d5-9b364187d8b6) | row332 first RED lane B | Ran four new files+whole launcher, all34 old launcher tests pass; two new fixture errors; fixes b285e581e then one rerun each: plugin valid RED1/4, bundle further fixture error0/5; reported/stopped, no mutants/probes/fetches, no test process leftovers.
|
||||
|
||||
2026-10-24 | fethry (65673bd9-3bfa-412b-85d5-9b364187d8b6) | row332 corrected bundle RED sage-4320 | Fixture-only56701f715 atop b285e581e; whole bundle0pass/5fail/0skip at23:44:40Z, all missing models.json generation; lane B returned, no production edits/mutants/probes/fetches.
|
||||
|
||||
2026-10-24 | fethry (65673bd9-3bfa-412b-85d5-9b364187d8b6) | row332 P3 implementation sage-4323/4325 | Production edits and authorized old fixtures written; optional route default Anthropic; GPT wire PENDING; no runs/probes/fetches, preparing pushed unverified checkpoint and import list.
|
||||
|
||||
2026-10-24 | fethry (65673bd9-3bfa-412b-85d5-9b364187d8b6) | row332 six-file run sage-4326 | business6/0,seat2/0,proxy-harness5/0,harness9/0,bundle1/5(PENDING),launcher34/1(old fallback lacks own pi key),zero skips; no reruns/behavior fixes, reported old RED before edit; title-only correction authorized, lane B free23:57:42Z,no test leftovers.
|
||||
|
||||
2026-10-24 | fethry (65673bd9-3bfa-412b-85d5-9b364187d8b6) | row332 fixture verification sage-4327 | f1e8cda07 bundle6/1(PENDING)/0skip,launcher35/0/0skip; no other failures, old fallback fixed in test only; lane B free23:59:45Z,no test leftovers.
|
||||
|
||||
2026-10-24 | fethry (65673bd9-3bfa-412b-85d5-9b364187d8b6) | row332 route required sage-4328 |9563fbc8b proxy-launch6/0,launch-settings18/0,bundle6/1(expected GPT PENDING),launcher35/0,zero skips; grep positive controls/old assertion preservation recorded; lane B free00:02:27Z UTC10-25,no test leftovers,no probes/fetches/mutants.
|
||||
|
||||
2026-10-24 | fethry (65673bd9-3bfa-412b-85d5-9b364187d8b6) | row332 pinned offline evidence sage-4329/4330 | One verified fetch; first setup0turns wrong provider shape; statically diagnosed bundled panel schema then one corrected isolated attempt: both GPT shapes+GLM exit0/marker,thinking retained,tool_stream untested(no tools); GPT choice inconclusive/PENDING,no real auth; process cleanup0,lane B free.
|
||||
|
||||
2026-10-24 | fethry (65673bd9-3bfa-412b-85d5-9b364187d8b6) | row332 sage-4331 API choice and GLM tool field | ef0939e35 responses constant per lead reasoning tie-break;rehash/no-fetch isolated GLM one turn0/tool_stream retained;bundle7/0/0skip,proxy-harness5/0/0skip;lane B free00:14:20Z UTC10-25,no test leftovers;wait row331 before further work.
|
||||
@@ -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
|
||||
`<node> <copy>/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.
|
||||
@@ -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.** `<run>/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:<port>/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:<port>/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).
|
||||
@@ -0,0 +1,150 @@
|
||||
# Brief: row 330 (P4), the Console proxy page
|
||||
|
||||
Written 2026-10-24 by Sage for Mos's mos-1606, which carries Jason's Q46 and Q47 answers (DECISIONS Q250).
|
||||
Mos approved it in mos-1608, adding upstream API keys (item 4) and the account id rule (item 2).
|
||||
Read the design (`docs/plans/2026-10-24_stack-model-proxy.md`, amended for Q250) and the rows doc
|
||||
(`docs/plans/2026-10-24_model-proxy-rows.md`) first. The rows doc's "Rules for every row" apply here
|
||||
unchanged. Line numbers are at origin/refactor 0ce22dde6. Check them before you edit.
|
||||
|
||||
This brief replaces two parts of the rows doc:
|
||||
- its P4 brief (the `/proxy/` forward to the upstream panel, rows doc :118-182), which is dropped with D8;
|
||||
- P1's two panel acceptance bullets (rows doc :104-108). P1's finding on the OAuth callback URL for Claude
|
||||
and Codex still counts, and this row uses it.
|
||||
|
||||
The rows doc isn't edited, because every row from 329 to 333 pins that file's blob and row 329 is near its
|
||||
landing. The P4 brief lives here instead.
|
||||
|
||||
## Brief: P4 the Console proxy page
|
||||
|
||||
**Why.** Q250: Jason enters accounts in the Console's own proxy page, not in the upstream panel. That
|
||||
covers both his OAuth accounts and his API keys (Q248 item 1, Q249, mos-1608). The page calls bus routes,
|
||||
and the bus calls the proxy's management API (D12). The management key stays in the bus (D6). No new
|
||||
hostname is added. A panel subdomain is a fallback only if our page can't keep up with the
|
||||
management API across pinned proxy upgrades, and that call is Jason's (Q46).
|
||||
|
||||
**What ships.**
|
||||
|
||||
1. **Bus verbs.** New broker verbs for the page, for example `proxy.accounts`, `proxy.login.start`,
|
||||
`proxy.login.submit`, `proxy.login.status`, `proxy.login.cancel`, `proxy.apikey.add`,
|
||||
`proxy.priority.set` and `proxy.keys`. Settle the names in BUILD.md.
|
||||
- Every verb is human-only. A tester, agent or granted-user caller refuses `human-required`, the way
|
||||
the grants admin verbs do (`packages/bus/src/broker.mjs`:3721-3731).
|
||||
- With no proxy in the validated config (P1 item 3), every verb refuses `not-configured`.
|
||||
- Each verb calls the management client from P1 (`packages/model-proxy/src/client.mjs`). P4 adds the
|
||||
calls it needs under P1's rules: the key goes only in the `Authorization` header, only to loopback,
|
||||
and each call writes a receipt with no body and no secret.
|
||||
- A proxy that doesn't answer refuses `proxy-down`, with no retry.
|
||||
2. **Accounts.** `proxy.accounts` lists the upstream accounts from the pinned version's credentials
|
||||
endpoint (`/credentials` in the 8.0.27 doc,
|
||||
`agents/researcher/work/i1-proxy-test/docs/management-api-v8.0.27.md`:101).
|
||||
- The bus copies only allowlisted fields: provider, a display name, status and priority, plus an
|
||||
account id. Every other field is dropped, so no token can pass through even if the proxy returns one.
|
||||
- The account id is the first 12 hex of the sha256 of the proxy's own credential identifier, never of a
|
||||
token or key value (mos-1608).
|
||||
- Record in BUILD.md which endpoint and fields the pinned version has.
|
||||
3. **OAuth sign-in** through `oauth/auth-url`, `oauth/callback` and `oauth/status`
|
||||
(management-api-v8.0.27.md:108-138).
|
||||
- **Start.** The page picks a provider from an allowlist: `claude` and `codex`. Adding a provider is a
|
||||
reviewed change. The bus calls `auth-url` and returns the authorization URL and the `state`. The page
|
||||
opens the URL in a new tab with `noopener` and `noreferrer`.
|
||||
- **Return.** After Jason signs in, the provider redirects to the callback URL that the proxy named.
|
||||
P1's acceptance records that URL for each provider. If the redirect reaches the proxy directly, the
|
||||
status poll shows the result. If it doesn't, Jason pastes the full URL from the address bar into the
|
||||
page, and the bus POSTs it to `oauth/callback` as `redirect_url`.
|
||||
- **Checks on a pasted URL.** It must parse as a URL, stay under a size limit, and carry a `state` that
|
||||
this bus started and that hasn't finished. Anything else refuses `login-state-unknown` or
|
||||
`callback-url-invalid`. The URL carries the authorization code, so it is never logged, stored or
|
||||
echoed back.
|
||||
- **Status.** Only an `ok` from `oauth/status` counts as signed in. An accepted callback with status
|
||||
`wait` doesn't, because the doc says acceptance isn't completion (:136-138). The bus stops polling
|
||||
on `ok`, `error`, or after a timeout setting (default 300 s).
|
||||
- **Cancel** calls `DELETE oauth/session`.
|
||||
4. **Upstream API keys** (mos-1608). A human-only verb (for example `proxy.apikey.add`; name it in
|
||||
BUILD.md) adds one upstream API key.
|
||||
- The provider allowlist starts with z.ai only. Adding a provider is a reviewed change. BUILD.md records
|
||||
which management endpoint the pinned 8.0.42 uses for it.
|
||||
- The Console route has the same checks as the other writes (item 8). The page's input is
|
||||
`type=password` with `autocomplete=off`.
|
||||
- The value is write-only. Neither the Console nor the bus logs it, stores it, caches it, or puts it in
|
||||
a receipt or an error. No part of it is echoed, not even the last four characters. The bus passes it
|
||||
straight to the management API in the request body and then drops it.
|
||||
- After the add, the key shows in `proxy.accounts` through the same allowlisted fields, and priority
|
||||
(item 5) works on it.
|
||||
- An empty value, one over a size limit, or one outside a charset refuses `apikey-invalid`. A provider
|
||||
outside the allowlist refuses `provider-not-allowed`.
|
||||
5. **Priority.** `proxy.priority.set` sets one account's priority, an integer in a range the builder
|
||||
records. Use the pinned version's credential-field endpoint (`/credentials/fields` PATCH in the 8.0.27
|
||||
doc). Confirm it on the stand-in and record what it changes. A bad account id or value refuses
|
||||
`priority-invalid`.
|
||||
6. **Client keys, list only.** `proxy.keys` reads `GET /config/access/api-keys`, maps each value to its key
|
||||
id in memory and drops the value (D15).
|
||||
- It joins P2's record to show agent, issue time and state for each key id.
|
||||
- The sentinel is labelled. A key without this instance's prefix shows as "not stack-managed".
|
||||
- The page has no key write. Issue and revoke stay with hire and fire (P2).
|
||||
7. **Usage.** P5's Console view (rows doc P5 item 3) sits on this page. Whichever of P4 and P5 lands
|
||||
second puts it there. P4 adds no usage source.
|
||||
8. **The page and its Console routes.** An owner-only `/proxy` page in `packages/webui`, built like the
|
||||
existing pages, with routes under `/api/proxy/`.
|
||||
- Each route makes one call over the owner's human transport, the way the Agents section does
|
||||
(`AGENT_ROUTES` at `packages/webui/src/serve.mjs`:451, `agentCaller` at
|
||||
`packages/webui/src/console-bus.mjs`:12). `consoleBus` gets a `PROXY_VERBS` list, and the caller
|
||||
refuses any other verb.
|
||||
- Writes need a signed-in owner, the Console's own `Origin`, `sec-fetch-site: same-origin`, `--send`
|
||||
and a named host, as `agentSend` checks (`serve.mjs`:459-470). The routes are refused for a granted
|
||||
user (`userRoute`), on the tester listener and on a loopback Host.
|
||||
- The Console never holds the management key, never reads `proxy` from the config, and never opens a
|
||||
connection to the proxy port.
|
||||
- Logs record method, route, status and user only. They never record the authorization URL, a pasted
|
||||
callback URL, a state, an API key or a body.
|
||||
9. **Upgrade check.** An acceptance script, beside P1's and outside the gate's `scripts/test-*.sh` set,
|
||||
runs the bus verbs against the pinned binary under `unshare -rn`. It uses a made-up config, key and
|
||||
data root in a fresh temp root, the same way P1's run does.
|
||||
- With no network, it checks what can be checked offline. The account list starts empty. `auth-url`
|
||||
returns a URL and a state for `claude` and for `codex`. A callback with a made-up code never reports
|
||||
`ok`. A made-up z.ai API key added through the verb shows in the account list, and a priority set on
|
||||
it reads back. The key list returns ids only.
|
||||
- It prints the endpoints and fields the page relies on, so a version change shows up as a diff.
|
||||
- `packages/model-proxy/README.md` records the rule: a PIN change lands only with this script's output
|
||||
for the new version in that row's BUILD.md, and Sage's landing gate checks for it.
|
||||
- If the script fails on a new version and the row can't fix the page, the row stops. Sage takes Q46's
|
||||
fallback to Jason through Mos.
|
||||
|
||||
**Tests.** Cover each of these:
|
||||
- every verb against a fake management server;
|
||||
- each refusal code by its own test (mos-1590): `not-configured`, `human-required` for the tester, an
|
||||
agent and a granted user, `provider-not-allowed` for sign-in and for an API key, `login-state-unknown`,
|
||||
`callback-url-invalid`, `apikey-invalid` (empty, over the size limit and outside the charset, each its
|
||||
own case), `priority-invalid` and `proxy-down`;
|
||||
- an API-key canary: a unique made-up value goes through the Console route against the fake management
|
||||
server. Afterwards every Console and bus log, receipt, response, store file and error output from that
|
||||
run has zero matches for it, and the fake received it exactly once;
|
||||
- an added API key listed by `proxy.accounts` with allowlisted fields only, and priority set on it;
|
||||
- account ids derived from the credential identifier, with a fake whose token values never appear in an id;
|
||||
- a fake that puts token-like values in every credential field, where no value reaches a response, log or
|
||||
receipt;
|
||||
- a fake key list with full values, where only ids come back, the sentinel is labelled and a foreign key
|
||||
is marked;
|
||||
- no authorization URL, callback URL, code or state in any log or receipt;
|
||||
- an accepted callback with status `wait` not reported as signed in;
|
||||
- the Console routes: owner allowed, and a granted user, the tester listener, a loopback Host, a
|
||||
signed-out request, a cross-origin request and a request without `sec-fetch-site` each refused, and
|
||||
every write read-only without `--send`;
|
||||
- no `packages/webui` module imports the model-proxy client or reads the `proxy` config.
|
||||
|
||||
**Out of scope.**
|
||||
- Removing or disabling an account. Mos is filing a Vikunja task for a later row (mos-1608).
|
||||
- Every routing setting other than account priority.
|
||||
- Issuing or revoking client keys (P2), and any usage source (P5).
|
||||
- The upstream panel, any forward to the proxy port, and the subdomain fallback.
|
||||
- Traefik, DNS, Authentik settings, and the .54 Console's provider (Q218).
|
||||
- Any live instance, the management key, and the 8317 proxy.
|
||||
|
||||
**Order.** P4 starts when P1 lands, because it needs P1's client and config contract. It rebases on P2
|
||||
before its gate, for the key join, and lands after P2.
|
||||
|
||||
**Gate.** Gladstone gives the first Codex review, and Filbert reviews the landing candidate. The model-proxy,
|
||||
bus and webui suites run whole, webui with `MOSAIC_REQUIRE_EDITOR_BUILD=1`. Every touched test file runs
|
||||
whole. The named mutants below are killed. Every package suite but queue plus every `scripts/test-*.sh` is
|
||||
green on Sage's landing gate. BUILD.md has the upgrade-check output on the pinned version. Mutants: the
|
||||
accounts verb returns an unfiltered field; the callback verb skips the state check; a `/api/proxy/` route
|
||||
accepts a granted user; the API-key verb echoes the value in an error or a log.
|
||||
@@ -0,0 +1,109 @@
|
||||
# Brief: row 209, the .54 window with its own proxy
|
||||
|
||||
Written 2026-10-24 by Sage for Mos's mos-1615, which carries Jason's "Q54: yes" (DECISIONS Q253). Q253:
|
||||
.54 waits for its own model proxy. Bean's Z.ai key and Chet's ChatGPT/Codex login both go in through the P4
|
||||
Console proxy page. Chet runs GPT through his Codex login for 10-27, not GLM, which replaces Q186.
|
||||
|
||||
This brief replaces row 209's old brief (`docs/plans/2026-10-20_federation-r1-r2-r5.md`, "Brief: FED R8, the
|
||||
hub window on 10.1.1.54"). That section isn't edited. Phase D below runs it unchanged. Read the model-proxy
|
||||
design (`docs/plans/2026-10-24_stack-model-proxy.md`, sections 1, 5, 9 and 10), the rows doc
|
||||
(`docs/plans/2026-10-24_model-proxy-rows.md`) and the P4 brief (`docs/plans/2026-10-24_proxy-page-brief.md`)
|
||||
first. Line numbers are at origin/refactor 95d4991d2. Check them before you rely on them.
|
||||
|
||||
## Brief: row 209, the .54 window: proxy, Bean's and Chet's accounts, then FED R8
|
||||
|
||||
**What this row is.** A live window on 10.1.1.54, not a code row. Jason and Mos run it. Nothing here
|
||||
authorizes a step on any host. Each step waits for the window's go, and Console start and restart stay
|
||||
with Jason or Mos (Q173).
|
||||
|
||||
**Secrets.** The rows doc's rules apply on .54 too. No agent reads `<configDir>/secrets/proxy-management.key`
|
||||
or anything under .54's `<dataRoot>/proxy/` (`config.yaml`, `auth/`, `logs/`), and no agent reads Hermes's
|
||||
auth files or Chet's login in any form (D6, mos-1600 ruling 2). Agents read only what a command prints:
|
||||
hashes, unit state, refusal codes and the proxy page's allowlisted fields.
|
||||
|
||||
### Entry checks, all before phase A
|
||||
|
||||
1. P1 (row 329), P2 (331), P3 (332) and P4 (330) have landed and passed Sage's landing gate. P3 carries the
|
||||
amendment for pi's GLM and GPT routes that Mos rules on (sage-4285). Without it P3 proves only the
|
||||
Anthropic route.
|
||||
2. **Gap to close first.** `.54` installs through `scripts/case2-install.sh` and `scripts/case2-unit.sh`.
|
||||
P1 adds `mosaic-proxy.service` to `scripts/instance.sh` only (rows doc P1 item 7). `case2-unit.sh` writes
|
||||
no model-proxy unit today, and its bus unit has no `Wants=` or `After=` on one. A code row has to add the
|
||||
proxy unit to the Case 2 install, with the same sha256 refusal, before phase A. Sage files it once Mos
|
||||
rules (sage-4285).
|
||||
3. The .54 stack copy is re-pinned to a commit that holds rows 329 to 332 and the item 2 row.
|
||||
4. .54's Console signs Jason in through Authentik (Q218). The proxy page's writes need a signed-in owner
|
||||
(P4 item 8).
|
||||
5. Phase B has a recorded result, or Chet's login waits for phase C as phase B says.
|
||||
|
||||
### Phase A: the proxy on .54
|
||||
|
||||
Design section 9, for .54 as the `mosaic` user under `/srv/mosaic` (D1).
|
||||
|
||||
1. **Fetch.** P1's pinned fetch runs on .54. Sage compares the printed sha256 values with PIN.
|
||||
2. **Config and key.** Jason adds the `proxy` section to .54's `config.json` in an editor (J2), runs
|
||||
`mosaic proxy init` in his own terminal (J3) and starts the proxy unit.
|
||||
3. **Bean's key.** Jason pastes Bean's Z.ai API key into .54's proxy page (J4, P4 item 4) and sets its
|
||||
priority. The page never shows it again.
|
||||
4. **Chet's login.** Only after phase B passes. Jason signs the proxy in to Chet's ChatGPT/Codex account
|
||||
through the page's own OAuth flow (P4 item 3). The proxy holds a sign-in of its own. Nobody copies
|
||||
Hermes's auth file.
|
||||
5. **Business settings.** Bean's and Chet's `launch.models` name provider `proxy` (P3 item 5): Bean on his
|
||||
GLM model, Chet on his GPT model, with the model ids the proxy's `/v1/models` lists. The edit goes in
|
||||
through the human path that P3's BUILD.md names.
|
||||
6. **Restart.** Jason or Mos restarts the bus and Console. The bus reconciles, issues a key for each agent
|
||||
(P2) and runs D17's preflight for each one. Any refusal stops phase A. The fix is to remove the section
|
||||
and restart.
|
||||
7. **One real turn per route.** The tester actor launches one pi session for Bean (GLM through the Z.ai key)
|
||||
and one for Chet (GPT through the Codex login). Each makes one real turn through the proxy. Jason
|
||||
watches the proxy page's usage section and confirms each request landed on the account he expects (J5).
|
||||
The result goes into the row's record with who ran it.
|
||||
8. **Unit cleanup.** Once both turns pass, the bus unit drops the provider env file per design section 9
|
||||
step 7. The file stays for the 7-day rollback window.
|
||||
|
||||
Rollback at any point: remove the `proxy` section and restart.
|
||||
|
||||
### Phase B: two clients on one ChatGPT login
|
||||
|
||||
Hermes holds Chet's ChatGPT login until #423 step 5. Before that login goes into the .54 proxy (phase A
|
||||
step 4), a check has to show that two clients on one ChatGPT login don't revoke each other's refresh tokens
|
||||
(mos-1615 item 3). Row 229's brief named the same risk for a copied token.
|
||||
|
||||
- Two separate sign-ins, never a copied file: Hermes keeps its own, and the proxy gets its own through the
|
||||
page.
|
||||
- Pass: each client goes through at least one token refresh, and afterwards Hermes still answers on Chet's
|
||||
login and the proxy page still lists the account as active. Fail: either one drops out or needs a new
|
||||
sign-in.
|
||||
- The check is Jason's. It uses the real account, and no agent reads either client's token or auth file.
|
||||
Agents record what Jason reports and what the page lists.
|
||||
- **Open before the window.** I don't know how to make each client refresh on demand without someone reading
|
||||
a token, or how long ChatGPT's access tokens last. Researcher answers from the pinned 8.0.42 docs and
|
||||
Hermes's docs: how each side refreshes, whether either can be made to refresh, and how long the check has
|
||||
to run. The answer goes in this row's record.
|
||||
- If the check fails, or there's no safe way to run it, Chet's login goes in after #423 step 5, when Hermes
|
||||
has let go of it. Phase A step 7's GPT turn waits for that, and Chet doesn't go live on GPT until it passes.
|
||||
|
||||
### Phase C: Case 2 go-live, then Hermes stops
|
||||
|
||||
Bean and Chet go live in Case 2 on the proxy (#416). Hermes stops after that (#423 step 5, mos-1615), and
|
||||
that stays Melanie's choice (`docs/plans/2026-10-20_case2-rows.md`:1231). If phase B didn't pass, Chet's login goes into the proxy here and phase A step 7's GPT turn
|
||||
runs before Chet goes live.
|
||||
|
||||
### Phase D: FED R8
|
||||
|
||||
The old brief, unchanged: entry checks, build notes N1 and N2, and its done condition
|
||||
(`docs/plans/2026-10-20_federation-r1-r2-r5.md`, "Brief: FED R8, the hub window on 10.1.1.54").
|
||||
|
||||
### Done when
|
||||
|
||||
Phase A's two real turns are recorded, each on the account Jason expected. Phase B's result is recorded
|
||||
along with who ran it. Bean and Chet are live on the proxy. FED R8's own done condition holds.
|
||||
|
||||
### Out of scope
|
||||
|
||||
- Any code change. The item 2 gap and the P3 amendment are code rows of their own.
|
||||
- The dragon-lin default instance and .53. Their cutovers are P7 and row 246.
|
||||
- Hermes's server and profiles, beyond Jason's phase B check.
|
||||
- The 8317 host proxy and `~/.local/opt/EasyCLIProxyAPI`.
|
||||
- Row 229 (Vikunja 428). The proxy route supersedes it, and the queue parks it with that reason. Its live
|
||||
check is phase B.
|
||||
@@ -5,6 +5,12 @@ becomes the stack's credential and routing layer, with Q116 and Q127 still in fo
|
||||
Mos rules it before any code. Its build rows go to new Codex gpt-6.1-sol threads (Q246), and each gets its own
|
||||
brief once Mos has ruled. Line numbers are at origin/refactor 2ce476d54.
|
||||
|
||||
**Amendments.** Mos's mos-1600, mos-1601 and mos-1602 changes live in the row briefs
|
||||
(`docs/plans/2026-10-24_model-proxy-rows.md`), which win over this design where they differ. Jason's Q46
|
||||
and Q47 answers (DECISIONS Q250, mos-1606, 2026-10-24) are written into this file: accounts are entered in
|
||||
the Console's own proxy page, and the `/proxy/` forward to the upstream panel (D8) is dropped. The P4 brief
|
||||
for that page is `docs/plans/2026-10-24_proxy-page-brief.md`. Text marked "(Q250)" below is that change.
|
||||
|
||||
Not to be confused with queue row 237 ("one decision lands once", `2026-10-20_agent-credentials.md`). This is
|
||||
Vikunja task 237.
|
||||
|
||||
@@ -75,11 +81,13 @@ whose sha256 matches PIN. The control panel asset is pinned the same way, with
|
||||
(`config.example.v8.0.27.yaml`:94-103), and that would be unreviewed code behind Authentik. The stack never
|
||||
copies the 8317 binary. The first fetch of the release asset counts as external access, so it is Jason's
|
||||
step J1 or a row Mos approves. For containerized installs the binary and panel go into the proxy image
|
||||
(section 8).
|
||||
(section 8). Q250: nothing serves the panel to the Console now. P1 keeps the pin as built, and
|
||||
removing it is a later cleanup.
|
||||
|
||||
D5. **Initial config, then hands off.** The stack writes `config.yaml` once, when `<dataRoot>/proxy/` is
|
||||
first created, from a template in `packages/model-proxy/`, and never writes the file again. After that the
|
||||
stack changes the proxy only through the management API, and Jason changes it only through the panel.
|
||||
stack changes the proxy only through the management API, and Jason changes it only through the Console's
|
||||
proxy page, which calls the bus, which calls the management API (Q250).
|
||||
Row 96 point 2 (every management write rewrites the whole file and reorders it) then stops mattering,
|
||||
because nobody edits the file by hand. The template sets the defaults in section 3 and section 7,
|
||||
`request-retry: 0` so no hidden retries happen (as in row 96), the client-key list holding only the
|
||||
@@ -93,31 +101,23 @@ prompt doesn't echo the key. It writes the key to `<configDir>/secrets/proxy-man
|
||||
the initial `config.yaml`. The proxy hashes it in place on its first start (row 96 point 6). The stack reads
|
||||
only the secrets file, never the hashed value in config. The holder is the bus process alone (D12). The
|
||||
sandbox doesn't mount `<configDir>/secrets`, so no session can read the key, and no verb returns it. Jason
|
||||
types the same key into the panel to sign in. An agent never runs the prompt (Q173).
|
||||
types the key only at that prompt. The Console's proxy page needs only his Console sign-in, and the bus adds
|
||||
the key to its own management calls (Q250). An agent never runs the prompt (Q173).
|
||||
|
||||
D7. **Upstream credentials.** Jason adds every upstream account in the proxy's own panel: OAuth sign-ins
|
||||
(Claude, Codex and the others CLIProxyAPI supports) and API keys (for example z.ai). They live in the
|
||||
D7. **Upstream credentials.** Accounts are entered in the Console's proxy page (Q250). It covers OAuth
|
||||
sign-ins for Claude and Codex through the management API's `oauth/auth-url`, `oauth/callback` and
|
||||
`oauth/status`, upstream API keys (z.ai first, write-only and never echoed, mos-1608), and account
|
||||
priority. The page shows each account by an id, the first 12 hex of the sha256 of the proxy's own
|
||||
credential identifier, never of a token or key value (mos-1608). The credentials live in the
|
||||
proxy's `auth/` directory and config and nowhere else. There is no per-seat credential file. The stack never
|
||||
reads, copies or logs an upstream credential.
|
||||
|
||||
D8. **Panel path.** The Console forwards `https://mosaic.woltje.com/proxy/` to `127.0.0.1:<proxy.port>`, for
|
||||
the panel route and the management routes only. It forwards only after its existing auth-header and
|
||||
allowed-user check (decision 103, `packages/webui/src/serve.mjs`:453-469) passes. The Console never adds or
|
||||
stores the management key. Jason types it in the panel, and it passes through the forward. This adds no DNS
|
||||
record and no subdomain, as Q116 requires. Traefik and the tunnel already deliver mosaic.woltje.com to the
|
||||
Console (7332), so no infra change is needed. The forward gives no new authority. The management key
|
||||
is the barrier, and loopback processes can already reach the proxy port. Authentik is the outer layer. The
|
||||
#385 hole (a loopback process forging the Host and header) applies here the same way and is no worse,
|
||||
since the management key is still required.
|
||||
|
||||
A second Console (.54) forwards to its own instance's proxy, and its Authentik provider settings are Jason's
|
||||
(Q218).
|
||||
|
||||
Checks for the build row, on a stand-in under `unshare -rn` as in row 96:
|
||||
- the pinned panel works under the `/proxy/` sub-path (rewrite or base-path setting);
|
||||
- an OAuth sign-in done from the panel through the forward completes. If the callback can't return through
|
||||
the forward, the fallback is Jason running the proxy's CLI login in his own shell (`-claude-login
|
||||
-no-browser` and similar), and J3 says so.
|
||||
D8. **Dropped (Q250).** The design had the Console forward `https://mosaic.woltje.com/proxy/` to the
|
||||
proxy's own panel. Jason's Q46 and Q47 answers replace it with the Console's own proxy page (D7). The page
|
||||
calls bus routes, the bus calls the management API (D12), and the management key stays in the bus (D6). No
|
||||
new hostname is added. A panel subdomain is a fallback only if our page can't keep up with the management
|
||||
API across pinned proxy upgrades, and that call is Jason's. Each pinned upgrade runs the page's acceptance
|
||||
script against the new version (P4 brief item 9).
|
||||
|
||||
## 3. Priority routing, and how the business routing table maps onto it
|
||||
|
||||
@@ -131,10 +131,12 @@ The proxy picks which upstream account serves that model. The two don't overlap:
|
||||
- Account failover inside one model is the proxy's job. The stack doesn't model accounts per session.
|
||||
|
||||
D10. **Priority settings.** Defaults set in the template are listed below (Q222: settings with defaults).
|
||||
Jason can change all of them in the panel.
|
||||
Jason sets account priority in the Console's proxy page. The other routing settings keep the template
|
||||
defaults, and a page control for them is a later row (Q250).
|
||||
- `routing.strategy: fill-first`. A subscription account is used until it is rate-limited or out of quota, then
|
||||
the next one (`config.example`:150).
|
||||
- Each account gets a `priority` (larger first, `config.example`:480). Jason sets the order in the panel (J4).
|
||||
- Each account gets a `priority` (larger first, `config.example`:480). Jason sets the order in the Console's
|
||||
proxy page (J4).
|
||||
- `routing.session-affinity: true`, so a session stays on one account while it works and keeps its prompt
|
||||
cache (:154-171). Default `session-affinity-rebind-on: quota`, so a transient 5xx doesn't move a session.
|
||||
- No model aliases or alias pools by default. A pool that maps one name to several upstream models
|
||||
@@ -270,7 +272,7 @@ D20. **Settings and who changes them.** Every number in this design is a setting
|
||||
`rotateBy` 30 d, `proxy.rotateGraceMinutes` 120, `proxy.firstOutputSeconds` 120, `usage.cliproxyQueue.pollSeconds`
|
||||
15, routing strategy fill-first, affinity on. The per-key daily quota and the rotation period guard the human
|
||||
from agents, so only a human can change them (Q222). The routing settings live in the proxy and only Jason
|
||||
changes them, in the panel.
|
||||
changes them. Account priority is in the Console's proxy page, and the rest keep their defaults (Q250).
|
||||
|
||||
## 8. Row 157 re-scope (#1652, Vikunja 617)
|
||||
|
||||
@@ -297,7 +299,7 @@ changes. Cutover, per instance, with Jason present (Q127):
|
||||
2. Jason fetches and verifies the pinned binary and panel (J1). ops-01 or Sage checks the sha256 against PIN.
|
||||
3. Jason adds the `proxy` section to the instance config (J2), runs `mosaic proxy init` (D6), and starts
|
||||
`mosaic-proxy.service`. The bus isn't restarted yet.
|
||||
4. Jason signs in to the panel and adds the accounts (J3, J4). No session uses the proxy yet.
|
||||
4. Jason adds the accounts in the Console's proxy page (J3, J4). No session uses the proxy yet.
|
||||
5. A Console or bus restart (Jason or Mos, per Q173) picks up the section. The bus reconciles, issues a key
|
||||
for each listed agent, and runs D17's preflight for each one. Any refusal stops the cutover, and the fix
|
||||
is to remove the section and restart.
|
||||
@@ -318,15 +320,14 @@ accounts (J7). The 8317 host proxy and its clients are not part of any step.
|
||||
- **J2.** Add `"proxy": { "port": <port from the cutover note>, "binary": "<path>" }` to the instance's
|
||||
`config.json`, in an editor.
|
||||
- **J3.** In a terminal of your own, run `mosaic proxy init` and type a new management key twice. Keep it in your
|
||||
password manager. Then open `https://mosaic.woltje.com/proxy/` and sign in to Authentik as jason.woltje.
|
||||
Paste the management key into the panel's login box.
|
||||
- **J4.** In the panel's auth or OAuth page, sign in to each subscription account you want this instance to use
|
||||
(Claude, Codex, and so on). On the API keys page, paste each API key you want it to have (for example z.ai).
|
||||
Set each account's priority. The highest one is used first, and the next one only when the first is
|
||||
limited. If an OAuth sign-in doesn't return to the panel, run the CLI login the cutover note gives, in your
|
||||
own terminal.
|
||||
- **J5.** Watch the Console while the two test sessions run (cutover step 6), and confirm the panel's usage page
|
||||
shows their requests on the account you expect.
|
||||
password manager. You don't type it anywhere else (Q250).
|
||||
- **J4.** Open the Console's proxy page and sign in to Authentik as jason.woltje. Sign in to each subscription
|
||||
account you want this instance to use (Claude, Codex). If the sign-in tab doesn't return on its own, copy
|
||||
the full address from that tab and paste it into the page. Set each account's priority. The highest one is
|
||||
used first, and the next one only when the first is limited. Paste each API key you want it to have (for
|
||||
example z.ai) into the page's API-key field. The page never shows it again.
|
||||
- **J5.** Watch the Console while the two test sessions run (cutover step 6), and confirm the proxy page's usage
|
||||
section shows their requests on the account you expect.
|
||||
- **J6.** After 7 days on the proxy, delete `claude-session.env` for that instance.
|
||||
- **J7.** For Melanie's .54 hub, decide which accounts it uses: hers, yours, or new ones. Then do J2 to J4 on .54
|
||||
through its own Console's Authentik provider (Q218). Only accounts you entered for that hub go into its
|
||||
@@ -339,10 +340,10 @@ credentials, so a Claude reviewer (Dewey) gives the second review. Sage runs the
|
||||
|
||||
| Row | Piece | Depends on |
|
||||
|---|---|---|
|
||||
| P1 | `packages/model-proxy/`: PIN, template, management client, `mosaic proxy init`, the `mosaic-proxy` unit, config `proxy` section, stand-in tests (empty list, `/v1/models` local, 401 shape, panel sub-path, OAuth through the forward) | Mos's ruling on D3 |
|
||||
| P1 | `packages/model-proxy/`: PIN, template, management client, `mosaic proxy init`, the `mosaic-proxy` unit, config `proxy` section, stand-in tests (empty list, `/v1/models` local, 401 shape, OAuth callback URL; the panel checks are dropped, Q250) | Mos's ruling on D3 |
|
||||
| P2 | Per-agent keys: `model` store kind, issue, rotate, revoke, reconcile, D17 preflight | P1, rows 194 and 199 |
|
||||
| P3 | Harness config: Claude Code env, pi `models.json` and preflight, `sessionEnv` proxy mode | P2 |
|
||||
| P4 | Console `/proxy/` forward behind the existing auth check | P1 |
|
||||
| P4 | The Console proxy page and its bus verbs (Q250, `2026-10-24_proxy-page-brief.md`) | P1; lands after P2 |
|
||||
| P5 | `usage` plug-in kind and the `cliproxy-queue` module; key ids in `launch.limits.tokens` | P2 |
|
||||
| P6 | Row 157 brief amendment (section 8) | Row 157 resuming |
|
||||
| P7 | Cutover note and per-instance window (section 9) | P1 to P4 landed; Jason present |
|
||||
@@ -353,7 +354,7 @@ The Codex harness config (D16) goes with the Codex harness row, which isn't on t
|
||||
|
||||
1. D1/D3: the `proxy` section in the instance config (a schema change), and the proxy as its own user unit.
|
||||
2. D6: the management key set by Jason at a human-only prompt and held only by the bus process.
|
||||
3. D8: the Console forwards `/proxy/` rather than a new Traefik route.
|
||||
3. D8: ruled. Jason's Q46 and Q47 (Q250) drop the forward for the Console's own proxy page.
|
||||
4. D13: an implicit per-agent model key with no role-file change.
|
||||
5. D15: plaintext keys at rest accepted under those limits.
|
||||
6. D16: proxy mode drops the instance-wide provider variables from every session.
|
||||
|
||||
@@ -96,6 +96,12 @@ suite by design and runs through a script puts that entry in the brief's
|
||||
Gate and in BUILD.md with the row, and the lead adds it to the landing
|
||||
gate's list. Any unlisted skip stays RED.
|
||||
|
||||
A builder's list of whole test files to run for a row comes from imports,
|
||||
not from a text grep for a path, and goes in the builder's report before
|
||||
the run. A file on the mos-1592 list (`packages/cli/tests/case1-accept.test.mjs`
|
||||
for now) stays off the list unless Mos has approved that run (mos-1612,
|
||||
after row 329's round 2 grep matched fixture text and reran the F7 file).
|
||||
|
||||
Any row that builds businessDoc() with a library project sets the chief's
|
||||
gitea ref in its fixture (mos-1584, after row 285 gave the chief of staff
|
||||
a read-scoped gitea credential that the validator requires when a business
|
||||
|
||||
@@ -768,3 +768,31 @@ Sage, for Duckworth's N1 on row 208 (c31496, landed f58049de0). packages/webui/s
|
||||
re-renders the user view on a 5 s timer while the tab is visible, whether or not anything changed. That's a
|
||||
steady client and server load per open tab, not a correctness or guard issue. Fix: render on change, or poll the bus only for a change
|
||||
marker. Bound to the next row that edits user.js.
|
||||
|
||||
|
||||
## Row 329 N2: `mosaic proxy fetch` has no agent or human guard
|
||||
|
||||
Sage, for Dewey's round 1 N2 on row 329 (c31639, landing deferral per c31643). `mosaic proxy fetch` downloads
|
||||
the pinned CLIProxyAPI archive from GitHub and writes `<dataRoot>/opt/cliproxyapi/` for any caller that has
|
||||
the instance config. P1's brief didn't ask for a guard. The fetch is pinned and sha256-checked against
|
||||
`packages/model-proxy/PIN`, so the risk is an unasked network fetch and a disk write, not a wrong binary.
|
||||
Bound to row 331 (P2): P2 decides whether fetch needs the human or agent guard its key verbs get, and
|
||||
records the choice in its BUILD.md. If P2 leaves it, P7 decides before cutover.
|
||||
|
||||
## Row 329 R2-N1: the management-key rule lives in three places
|
||||
|
||||
Sage, for Dewey's round 2 R2-N1 on row 329 (c31643). Round 2 tightened the key rule to
|
||||
`/^[\x21-\x7e]{1,4096}$/` in `packages/model-proxy/src/common.mjs`:33, which `initializeProxy` enforces, but
|
||||
`packages/bus/src/broker.mjs`:4827 and `packages/cli/src/model-proxy.mjs`:57 still use the round 1 predicate.
|
||||
A Latin-1 or edge-space key gets past both and is refused at init with `proxy-key-invalid`, before any write,
|
||||
but only after both prompts and a bus round trip. Fix: export one predicate from the model-proxy package
|
||||
and use it in all three places, with a test at each caller. Bound to row 331 (P2), which edits the broker's
|
||||
proxy verbs.
|
||||
|
||||
## Row 329 N1: the 127.0.0.1 bind isn't observed on the real binary
|
||||
|
||||
Sage, for Dewey's round 1 N1 on row 329 (c31639). The acceptance netns had only `lo`, so P1 writes
|
||||
`server.host: 127.0.0.1` and `management.allow-remote: false`
|
||||
but never saw the real binary refuse a non-loopback client.
|
||||
Bound to P7: at cutover, with Jason present, confirm the listener is on 127.0.0.1 only (`ss -ltnp`), and
|
||||
record it in P7's receipt.
|
||||
+19
-12
@@ -33,7 +33,7 @@ Gaps found while working go to `docs/plans/DEFERRED.md`, not here.
|
||||
|
||||
<!-- mosaic-queue:begin -->
|
||||
|
||||
Generated from `docs/plans/queue.json` revision 3945 by `queue render`. Do not edit between the markers; change the queue with `scripts/mosaic queue`.
|
||||
Generated from `docs/plans/queue.json` revision 4064 by `queue render`. Do not edit between the markers; change the queue with `scripts/mosaic queue`.
|
||||
|
||||
| # | Piece | Owner | Issues | State | After | Gate | Brief | Note |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
@@ -245,7 +245,7 @@ Generated from `docs/plans/queue.json` revision 3945 by `queue render`. Do not e
|
||||
| 206 | 193b2: thread shares, schema 3k-3l after the #436b build (#1691; live needs a --migrate window with Jason) | launchpad; reviewers duckworth | #1704 | done | 205 done, 234 done, 236 done, 275 done | Duckworth approves the manifest; bus suite, cli suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193b2, thread shares (schema 3l) | Landed 0cbe2e9b3 fast-forward on 17f415792 (10 commits, 22 paths), pushed 19:47Z. Duckworth APPROVE rev 3322 c30795 on 0cbe2e9b3; request rev 3320/3321 c30793 posted as jarvis. Gate (Launchpad, 9538e0fc8, tip differs in BUILD.md only): bus 850/0, cli 869/0, ten scripts/test-*.sh rc=0, touched files whole green, M2-M4 M7-M9 and Q2 killed. One placed fixture red (mos-1272). Schema 3l to 3m; live --migrate is Mos's go under Q173. |
|
||||
| 207 | 193c: mosaic access user verbs and --level viewer (#1691) | megavolt; reviewers duckworth | #1705 | done | 202 done | Duckworth approves the manifest; cli suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193c, mosaic access user verbs and --level viewer | LANDED c4f77373f 22:49Z 10-22 (fast-forward from f0e3dcdf4). Rebuild of gated 9bb406c6c: range-diff 4/4 =, patch-ids equal, 4 manifest blobs equal. Duckworth APPROVE c30849 rev 3356. Gate on 712f79c0a under sage-3041: access.test 31/31, M1-M8 killed, cli 894/894, ten scripts green. N1 DEFERRED (next access.test row). No live change. |
|
||||
| 208 | 193d: Console shared list, share controls and viewer read-only (#1691; also after FED R6 when filed) | poe; reviewers duckworth, gladstone | #1706 | done | 192 done, 206 done, 294 done | Gladstone (Codex, first review) and Duckworth approve the manifest; webui suite and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_human-users-build.md` § Brief: 193d, the Console's shared list, share controls and viewer read-only | LANDED f58049de0 (build 4a4bef6c1, Duckworth note f58049de0), combined with row 326 on one full gate 04:36-04:51Z: all package suites except queue 0 fail 0 skip (webui 418, plugins 119, cli 986), every scripts/test-*.sh green. Gladstone c31495, Duckworth c31496. #1706 closed c31504. N1 user.js 5 s re-render to DEFERRED. Bundle 9fc7754a. |
|
||||
| 209 | FED R8: the hub window on 10.1.1.54, a recorded acceptance (#414, r4.11 b93f098f0; live window run by Jason and Mos, after the Case 2 install #416, Bean and Chet live and both Hermes gateways stopped) | sage; reviewers duckworth | — | queued | — | Every entry check's output is in the R8 record with who ran it; Jason signs in to the hub and sends one message, Melanie revokes, his next request is refused; Duckworth checks the record (sage) | `docs/plans/2026-10-20_federation-r1-r2-r5.md` § Brief: FED R8, the hub window on 10.1.1.54 | Stays queued until its entry gates near: Case 2 install on .54 (#416, by 2026-10-27), Bean and Chet live, both Hermes gateways on .54 stopped, R7, #412 phase 2, #419 answer. Filed per mos-1073 after duckworth-0034 (Sage) |
|
||||
| 209 | FED R8: the hub window on 10.1.1.54, a recorded acceptance (#414, r4.11 b93f098f0; live window run by Jason and Mos, after the Case 2 install #416, Bean and Chet live and both Hermes gateways stopped) | sage; reviewers duckworth | — | queued | — | Every entry check's output is in the R8 record with who ran it; Jason signs in to the hub and sends one message, Melanie revokes, his next request is refused; Duckworth checks the record (sage) | `docs/plans/2026-10-24_row209-hub-54-window.md` § Brief: row 209, the .54 window: proxy, Bean's and Chet's accounts, then FED R8 | Stays queued until its entry gates near: Case 2 install on .54 (#416, by 2026-10-27), Bean and Chet live, both Hermes gateways on .54 stopped, R7, #412 phase 2, #419 answer. Filed per mos-1073 after duckworth-0034 (Sage) |
|
||||
| 210 | Agent host access with sudo through the credential store: grant per agent and host, the stack does ssh and sudo, the agent never sees the key (#1707, Vikunja #424, Q183/Q184, due 10-27; chained on row 199's candidate) | gizmoduck; reviewers duckworth | #1707 | done | — | Duckworth approves DESIGN.md, then the manifest; bus, business, cli suites and every scripts/test-*.sh green on Sage's gate; the mutants killed (sage) | `docs/plans/2026-10-20_host-credentials.md` § Brief: agent host access with sudo through the credential store (#424) | Landed f5753c0e8 on refactor (base 9d165be8c), pushed. Patch-id 723b1c83 matches git diff ad95c888c..0485384e9 on the 23 manifest paths; landed files pass r5 manifest 688d38b9. Duckworth approve r2 rev 2592, r3 29851. Mutants 52/53, M22b equivalent (rev 2584). Gate on ad95c888c+patch: packages 2796/0, scripts 429/0; landed blobs equal the gate tree. |
|
||||
| 211 | Force-push guard: scripts/git-hooks/pre-push refuses non-fast-forward and delete pushes from every worktree, and scripts/slot-busy.sh exits 1 on BUSY (#1709, Vikunja #426, mos-1078/1083) | darkwing; reviewers dewey | #1709 | in-progress | — | Dewey approves the manifest; the new or changed scripts/test-*.sh and every other scripts/test-*.sh green on Sage's gate; the mutants killed; hook installed in the canonical checkout with a scripted refused force-push (sage) | `docs/plans/2026-10-20_force-push-guard.md` § Brief: a pre-push hook that refuses force-pushes, and a busy check that fails on BUSY (#426) | — |
|
||||
| 212 | Case 2 C2-01 packages/library: mapping, scope, reader, resolver, R-rules, renderItems, survey | gosalyn; reviewers webby | #1713 | done | — | Webby approves the manifest. The library tests and the cli suite are green on Sage's gate, along with every `scripts/test-*.sh`. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-01 packages/library: mapping, scope, reader, resolver, R-rules, renderItems, survey | Landed ee7b2f137 (pushed 09:13Z) on f6c70f944, alone per mos-1162: the 21 manifest paths from a2c72ac95. Patch-id ea84d3e35470 equals the candidate's; blobs and modes match; verify-commit ok (21 paths). No 212 path changed after c771a36f4. Gate: Sage's landing gate 08:51-09:03Z on 4ced5e63d plus 212's and 197's paths, GATE_PASSED, node 981/0, selftests 429/0. Gosalyn r3 slot 451/0, test-queue 28/0. Row 197 waits for r6 (lane proof stopped, sage-1881). |
|
||||
@@ -260,12 +260,12 @@ Generated from `docs/plans/queue.json` revision 3945 by `queue render`. Do not e
|
||||
| 221 | Case 2 C2-20 Bean and Chet persona import, with a count-only dry run | gosalyn; reviewers webby | #1722 | done | — | Webby approves the manifest. The test file runs green on its own and the cli suite is green on Sage's gate, along with every `scripts/test-*.sh`. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-20 Bean and Chet persona import, with a count-only dry run | LANDED 300766450, pushed. Cand 8f403d0d4, manifest 20f05e3a6ad5, 22 paths, verify-commit ok, patch-id dfbb754c6fb4 equals 62ddc96cc..8f403d0d4. Gate 11:07:50-11:19Z on b1fece6f0: test file alone 11/0, harness 140/0, cli 677/0, all scripts green. Brief Gate met: webby-0073 approve, test file alone, cli, scripts. No live run in the Gate; the import itself waits on Jason. N1 N2 N4 go to C2-20b, N3 to DEFERRED. |
|
||||
| 222 | Case 2 C2-03a fetch-only sync | megavolt; reviewers dewey | #1723 | done | — | Dewey approves the manifest. The library and bus suites are green on Sage's gate, along with every `scripts/test-*.sh`, and the mutant is killed. A search of the test run's output for the fixture token finds nothing. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-03a fetch-only sync | sage-2024 on megavolt-0089: core.createObject=rename in the fetch argv is accepted as a brief departure, since C2-04 inspectMirror refuses nlink 2. Dewey checks it. The askpass env keys, export SAFE in project/src/git.mjs, bus/tests/library-sync.test.mjs and the SURVEY-RUN user line are accepted. The 5 s client timeout residual goes in BUILD.md and Dewey rules fix or defer. Slot after 197 proof 3 and the 249 gate. |
|
||||
| 223 | Case 2 C2-07 runner retry on a guard refusal, and the runner's notices | researcher; reviewers filbert | #1724 | blocked (from briefed): Q226: C2-07 folded into C2-06 (row 217). No work here; Jason parks it. | — | Filbert approves the manifest. The harness suite is green on Sage's gate, along with every `scripts/test-*.sh`. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-07 folded into C2-06 | — |
|
||||
| 224 | Case 2 C2-05b broker lookup verbs and typed tools | della; reviewers dewey | #1725 | in-progress | 216 done | Dewey approves the manifest. The bus and harness suites are green on Sage's gate, along with every `scripts/test-*.sh`. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-05b broker lookup verbs and typed tools | — |
|
||||
| 225 | Case 2 C2-10b Console chat: replies, citations, write notices and the status line | fethry; reviewers dewey | #1726 | briefed | 217 done | Dewey approves the manifest. The webui suite is green on Sage's gate, along with every `scripts/test-*.sh`. Grep for hash pins on `bus.js` and `serve.mjs` before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-10b Console chat: replies, citations, write notices and the status line | — |
|
||||
| 224 | Case 2 C2-05b broker lookup verbs and typed tools | della; reviewers dewey | #1725 | done | 216 done | Dewey approves the manifest. The bus and harness suites are green on Sage's gate, along with every `scripts/test-*.sh`. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-05b broker lookup verbs and typed tools | LANDED 5e77cdd09 19:18Z 10-24 (13 Della commits on 7fe32d722). Gate GREEN 19:16:37Z on 27624aa66, drift docs-only. Dewey APPROVE c31619. #1725 closed c31623. |
|
||||
| 225 | Case 2 C2-10b Console chat: replies, citations, write notices and the status line | fethry; reviewers dewey | #1726 | done | 217 done | Dewey approves the manifest. The webui suite is green on Sage's gate, along with every `scripts/test-*.sh`. Grep for hash pins on `bus.js` and `serve.mjs` before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-10b Console chat: replies, citations, write notices and the status line | LANDED 9b6345466 on refactor, pushed 15:55:22Z: fethry r2 c1f9dfd3c cherry-picked onto c63801959, 8 commits equal by range-diff and 3 differ only in log hunks (both sides kept), 13 manifest paths match 517af41b. Dewey APPROVE c31599 rev 3960, request c31600. Sage gate GREEN 15:54:58Z: every package suite but queue 0 fail (webui 442, cli 988+1 allowed FED R7 skip), 12 scripts rc 0. #1726 closed c31604. |
|
||||
| 226 | Case 2 C2-13 install script and precheck for the host | gosalyn; reviewers webby | #1727 | done | 215 done, 221 done, 222 done, 230 done | Webby approves the manifest. The test file and the cli suite are green on Sage's gate, along with every `scripts/test-*.sh`. The run on the host is a separate window with Jason's go. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-13b install script and library precheck | landed 661890e90 (replay of bfed0aa6c 893b4fd25 f3fabf51b onto 8352da88c, patch-ids equal, four blobs and modes equal to f3fabf51b) and fd3d2a99f (Darkwing 8a6321790 replay, mos-1519), tree 78fc5ad21 equals merge-tree. Webby APPROVE rev 3641. Sage gate GREEN 19:56Z on f3fabf51b: 17 package suites and 10 scripts, 0 fail. Webby N1-N4 DEFERRED e2b873390. #1727 closed. |
|
||||
| 227 | Case 2 C2-14 end-to-end acceptance on a scratch root | gizmoduck; reviewers dewey | #1728 | in-progress | — | Dewey runs the script and approves. It is green on Sage's gate with every `scripts/test-*.sh`. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-14 end-to-end acceptance on a scratch root | — |
|
||||
| 227 | Case 2 C2-14 end-to-end acceptance on a scratch root | steelbeak; reviewers dewey | #1728 | in-progress | — | Dewey runs the script and approves. It is green on Sage's gate with every `scripts/test-*.sh`. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-14 end-to-end acceptance on a scratch root | — |
|
||||
| 228 | Row A: ZAI_API_KEY passes through ENV_ALLOW to sessions, README line, pass-through test (XS, Haiku high) | researcher; reviewers webby | #1729 | done | — | Webby approves the manifest. The mutant is killed. The seat suite and the cli suite are green on Sage's gate with every scripts/test-*.sh. (sage) | `docs/plans/2026-10-20_case2-rows.md` § Brief: row A, ZAI_API_KEY in the session env allowlist | Landed 86249ac55 (pushed 07:25Z) on 59f817c32: the 3 manifest paths from cf18c42b9. Patch-id b6ffe31feb0d equals the candidate's; modes and blobs match; verify-commit ok (3 paths). Sage's combined 228+238 gate on c515273db plus both rows' 6 paths, 07:11:30Z to 07:23:34Z: seat 165/0, cli 640/0, all ten scripts/test-*.sh 585/0. |
|
||||
| 229 | Row B: per-instance pi agent dir for Chet's Codex login, design only, starts after 10-27 (#428) | gosalyn; reviewers webby | #1730 | briefed | — | A live check, with Jason's or Mos's go, that two clients on one ChatGPT login don't revoke each other's refresh tokens. No build row starts before it passes. Held until after 10-27. (sage) | `docs/plans/2026-10-20_case2-rows.md` § Brief: row B, a per-instance pi agent dir for Chet's Codex login (#428) | — |
|
||||
| 229 | Row B: per-instance pi agent dir for Chet's Codex login, design only, starts after 10-27 (#428) | gosalyn; reviewers webby | #1730 | blocked (from briefed): superseded by the proxy route (Q253, mos-1615/1616): Chet's Codex login goes in through the P4 proxy page; the live check moved to row 209 phase B | — | A live check, with Jason's or Mos's go, that two clients on one ChatGPT login don't revoke each other's refresh tokens. No build row starts before it passes. Held until after 10-27. (sage) | `docs/plans/2026-10-20_case2-rows.md` § Brief: row B, a per-instance pi agent dir for Chet's Codex login (#428) | — |
|
||||
| 230 | Case 2 C2-13a unit render and host precheck (case2-unit.sh, case2-precheck.sh) | gosalyn; reviewers webby | #1732 | done | — | Webby approves the manifest. The test file and the cli suite are green on Sage's gate, along with every scripts/test-*.sh. A run on a host is a separate window with Jason's go. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-13a unit render and host precheck | Landed c8bc5cf4c on refactor (base 390ea5261), pushed. Patch-id f4b49eb68997 matches git diff 722136772..96768766c on the 5 manifest paths (manifest fbb0d7e42c67, Webby approve r2 rev 2563). packages/cli/README.md 3-way with row 199, hunks apart. Gosalyn gate on 96768766c: cli 541/0, scripts 429/0, mutants 10/10. Merged tree check: case2-unit 30/0, host 42/0. |
|
||||
| 231 | Case 2 C2-01b survey escapes, list item lines and attachment embeds (row 212 N1-N3) | gosalyn; reviewers webby | #1733 | done | 212 done | Webby approves the manifest. The library tests are green on Sage's gate, along with every scripts/test-*.sh. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-01b survey escapes, list item lines and attachment embeds | LANDED 29ce1e360 (sage gate 10:05:31-10:07:52Z GREEN on 62ddc96cc: library 45/0, every scripts/test-*.sh). verify-commit ok, 9 paths; patch-id 59b98b1b3e00 equals fefd54e13..f37a65857. Pushed. Webby N4 stays as restored (sage-1917), doc-only. |
|
||||
| 232 | Installer I1: stack-release external units and stack-copy bundle copies (MVP, Q187, Vikunja #432) | darkwing; reviewers filbert | #1734 | done | — | Filbert approves the manifest. The cli suite and every scripts/test-*.sh are green on Sage's gate, with the mutants killed. (sage) | `docs/plans/2026-10-20_installer-rows.md` § I1 stack-release external units and bundle copies | Landed e65a3ab4d on refactor (base 0444acd08), pushed. Patch-id 5adfbab9 matches git diff 8fede5fc6..da2da2f6c on the 9 manifest paths; tree entries (modes too) equal da2da2f6c, files pass manifest 7af3c943. Filbert approve r1 29829, r2 29865. Darkwing's landing gate on da2da2f6c: cli 593/0, scripts 429/0 (+156 queue node tests). Mutants 17/17. |
|
||||
@@ -293,7 +293,7 @@ Generated from `docs/plans/queue.json` revision 3945 by `queue render`. Do not e
|
||||
| 254 | Library sync follow-up: client timeout, STATUS codes, README (222 deferrals, #1759) | megavolt; reviewers dewey | #1759 | done | 196 done | Dewey approves the manifest; bus, library and webui suites whole and every scripts/test-*.sh green on Sage's slot; the timeout and STATUS mutants killed (sage) | `docs/plans/2026-10-21_library-sync-followup.md` § Gate | Landed 331597709 (candidate b31300d79 blobs and modes on 41b0cf755; patch-id 96216216 equal, range-diff differs only in the message) plus doc eb9672c91 (Dewey N1 README sentence closed; N2 four 502 codes to DEFERRED, binding the Console library routes row). Merged-tree rerun on Sage's slot 23:44-23:55Z: library 77/0, bus 628/0, webui 305/0 with 1 skip, cli 749/0. Megavolt's gate megavolt-0102 covers scripts and both mutants. |
|
||||
| 255 | Bundled Pocket ID for users with no OIDC provider, design (Q203, #1760) | launchpad; reviewers webby | — | queued | 252 done | Webby approves DESIGN.md; design only, build rows follow after Sage checks it against Q203, O6, REQ-WEB-2 and REQ-INST-1 (sage) | `docs/plans/2026-10-21_bundled-pocket-id.md` § Gate | Vikunja 458, Gitea #1760, mos-1202 item 2. REQ-INST-2 lands in PRD 1.10 as post-v1 (mos-1204). Design only: no install, no download, no .53/.54, dragon-lin keeps Authentik. |
|
||||
| 256 | the human deletes a creative instance; any other actor refused, fire unchanged (REQ-COS-3, before the v1 tag) | honker; reviewers webby | #1761 | done | 194 done | Webby approves the manifest; touched test files whole plus business, bus, cli and webui suites and every scripts/test-*.sh green on Sage's slot; both mutants killed (sage) | `docs/plans/2026-10-21_creative-delete-human.md` § Brief: the human deletes a creative instance (REQ-COS-3, after row 194) | LANDED f12441d4f ff from 0165be266 (4 Honker commits, patch-id b28e6374). Webby APPROVE manifest-r3 01eb75aa (10 paths), request c30518. Gate 10:33-10:52Z: 4 touched files 31/0, 18 packages 3757/0 (bus 788, business 231, cli 832, webui 335), 10 scripts rc=0, mutants M1-M4b killed; skips named, env only (webui esbuild pin, test-queue root, test-task live x2). Overlap 13/257 clean. Lands before the 10-27 v1 tag. |
|
||||
| 257 | Q198 S1: pressure parser and pure rules evaluator (post-v1) | morgana; reviewers filbert | #1762 | in-review, round 2, request posted | — | Reviewer approves the manifest; touched test files whole plus the package suite green on Sage's slot; both brief mutants killed (post-v1, MVP keeps slot priority) (sage) | `docs/plans/2026-10-21_post-v1-row-briefs.md` § Brief: Q198 S1 pressure parser and pure rules evaluator | — |
|
||||
| 257 | Q198 S1: pressure parser and pure rules evaluator (post-v1) | morgana; reviewers filbert | #1762 | done | — | Reviewer approves the manifest; touched test files whole plus the package suite green on Sage's slot; both brief mutants killed (post-v1, MVP keeps slot priority) (sage) | `docs/plans/2026-10-21_post-v1-row-briefs.md` § Brief: Q198 S1 pressure parser and pure rules evaluator | Landed 73692e1bb, pushed 17:41:58Z 10-24: Morgana's 4 commits cherry-picked onto 487500a16, patches unchanged; 9 supervisor paths match Filbert r2 manifest b176a88f (APPROVE c30190, rev 3037). Gate RED 17:20Z (webui row278 sign-out timing, old test, row touches no webui), full rerun GREEN 17:41:11Z, cli skip FED R7 allowed. push-check clean. #1762 c31613, closed. Carry 447286192 lands doc-only. |
|
||||
| 258 | Email E01: mail PRD addendum draft and schemas (post-v1) | ludwig; reviewers della | #1763 | in-progress | — | Reviewer approves the manifest; the row's named tests whole plus the mail suite and touched suites green on Sage's slot; reviewer-named mutants killed (post-v1, MVP keeps slot priority) (sage) | `docs/plans/2026-10-21_post-v1-row-briefs.md` § Brief: E01 mail PRD addendum draft and schemas | — |
|
||||
| 259 | Email E03: mail ledger, state and quotas (post-v1) | unassigned | #1764 | queued | 258 done | Reviewer approves the manifest; the row's named tests whole plus the mail suite and touched suites green on Sage's slot; reviewer-named mutants killed (post-v1, MVP keeps slot priority) (sage) | `docs/plans/2026-10-21_post-v1-row-briefs.md` § Brief: E03 mail ledger, state and quotas | — |
|
||||
| 260 | Email E04: bounded MIME parsing and quarantine (post-v1) | unassigned | #1765 | queued | 258 done | Reviewer approves the manifest; the row's named tests whole plus the mail suite and touched suites green on Sage's slot; reviewer-named mutants killed (post-v1, MVP keeps slot priority) (sage) | `docs/plans/2026-10-21_post-v1-row-briefs.md` § Brief: E04 bounded MIME parsing and quarantine | — |
|
||||
@@ -302,7 +302,7 @@ Generated from `docs/plans/queue.json` revision 3945 by `queue render`. Do not e
|
||||
| 263 | Email E07: address and rule evaluator (post-v1) | unassigned | #1768 | queued | 258 done, 259 done, 260 done | Reviewer approves the manifest; the row's named tests whole plus the mail suite and touched suites green on Sage's slot; reviewer-named mutants killed (post-v1, MVP keeps slot priority) (sage) | `docs/plans/2026-10-21_post-v1-row-briefs.md` § Brief: E07 address and rule evaluator | — |
|
||||
| 264 | T1 item 7: independent sessions, compaction counts and clear context | gizmoduck; reviewers duckworth | #1769 | done | — | The named reviewer approves the manifest. The design's T1-T7 acceptance tests run whole plus every touched suite on Sage's slot; T3 waits on Vikunja 466 (N2, Claude's compaction count format and the managed dataRoot reader). The design's section 6 mutants are killed. (sage) | `docs/plans/2026-10-21_t1-sessions.md` § T1 item 7: independent sessions, compaction counts and clear context | LANDED 84f53540d ff on e479521d3 14:18:23Z, 21 commits = gizmoduck/row264-r13 tip (no rewrite, range-diff identity). Manifest 257408e9 approved Duckworth rev 3214 (c30640). Gate r13 2cc34aef8: 20 files, bus 827, cli 837, webui 345/1 skip, 10 shells; M1-M4 assertion kills (mos-1279 B, M2 4 files). Overlap none (13, 257). N1 actor_run binds row 269; N2 esbuild skip later gate (sage-2695). |
|
||||
| 265 | Case 2 C2-09b rooms: room.open, room.send with @tags, room.read | goldie; reviewers boyd | #1770 | done | 219 done | Boyd approves the manifest. The brief's tests run whole plus the bus suite and every touched suite on Sage's slot. Grep for hash pins on changed files before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-09b rooms: room.open, room.send with @tags, room.read | LANDED 200dd396c (cherry-pick of ef4c93853, 50 blobs equal, log appends equal) 05:13Z on full gate 04:57-05:12Z: all package suites except queue 0 fail 0 skip (bus 957, cli 986, seat 218), every scripts/test-*.sh green. Boyd c31501 incl mos-1567 mount read. 22 gzip logs scanned decompressed, clean. #1770 closed c31510. Bundle 02c3c286. |
|
||||
| 266 | Case 2 C2-10c Console room view: merged timeline and @name completion | researcher; reviewers dewey | #1771 | queued | 225 done, 265 done | Dewey approves the manifest. The webui suite is green on Sage's gate, along with every touched suite. Grep for hash pins on changed files before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-10c Console room view: merged timeline and @name completion | Haiku high subagent under decision 129. |
|
||||
| 266 | Case 2 C2-10c Console room view: merged timeline and @name completion | fethry; reviewers boyd, dewey | #1771 | done | 225 done, 265 done | Dewey approves the manifest. The webui suite is green on Sage's gate, along with every touched suite. Grep for hash pins on changed files before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-10c Console room view: merged timeline and @name completion | LANDED 3e5041b43 18:00:49Z 10-24, patch-ids equal to Fethry b8b26ae4d..6418ec063; gate GREEN 18:00:08Z first run (bus 1000/0, webui 458/0, cli 989+1 FED R7 allowed, 12 scripts rc 0); Boyd c31609, Dewey c31610; #1771 c31615 closed; Dewey N2 T9 race goes to Mos |
|
||||
| 267 | Case 2 C2-15 hybrid universes: exclusions, pins and the end-to-end crossover | morgana; reviewers filbert | #1772 | queued | 216 done, 217 done, 224 done | Filbert approves the manifest. The brief's tests and the end-to-end crossover run whole plus every touched suite on Sage's slot. Grep for hash pins on changed files before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-15 hybrid universes: exclusions, pins and the end-to-end crossover | R8 accepted (sage-2218). Webby's R8 noise note (gosalyn-0127) is for the builder. |
|
||||
| 268 | Case 2 C2-01c playground levels: universe-level prefixes and draft universes | gizmoduck; reviewers webby | #1773 | done | — | Webby approves the manifest. The brief's tests run whole plus the library suite and every touched suite on Sage's slot; the brief's gate grep over packages and docs/case2 finds no library-playground-outside-book. Grep for hash pins on changed files before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-01c playground levels: universe-level prefixes and draft universes | Landed c27207f6e fast-forward on ce6a96ad6 (1 commit, 9 paths, digest caa0fa4e). r4 is a carry of r3 aca53c4f0: range-diff =, patch-id 2942acc8 equal, blobs equal. Webby APPROVE round 2 on ecd9b35eb stands; carries webby-0112 (r3) and webby-0113 (r4, notes 34e1ab2bf). Gate: Gizmoduck r4 rerun brief 14/14, library 83/83, business 209/209, 0 skips; gate grep empty. 194 rebases next (business.test.mjs). |
|
||||
| 269 | 264b T1 item 7: per-session native store, workspace and single-writer fence | gizmoduck; reviewers duckworth | #1774 | done | 264 done | Duckworth approves this slice's manifest. Its design tests run whole plus every touched suite on Sage's slot, and its mutants are killed (RULINGS Q1). Tests T1 run half, T2 workspace and path mutants; whichever of 191 and 264b lands second wires 191's admission checks (Q5). (sage) | `docs/plans/2026-10-21_t1-sessions.md` § T1 item 7: independent sessions, compaction counts and clear context | Landed efcc9ea8d fast-forward on 94c33a43c (13 commits, 12 paths, manifest 455c12ee). r3 is a carry of r2 aafcc857c, range-diff = (Duckworth 0194, c30756, rev 3307). Gate r3 18:37-18:50Z all green, 0 skips: bus 834, seat 172, cli 869, case2-unit 43, singles 38. M1-M12 kills carry. Re-pin needs the Console restart by Jason or Mos right after the host. A 264a rollback refuses a v2 sessions.json, see BUILD.md Rollback. |
|
||||
@@ -320,13 +320,13 @@ Generated from `docs/plans/queue.json` revision 3945 by `queue render`. Do not e
|
||||
| 281 | A fire or a delete revokes the instance's host and Gitea grants (194/236 follow-up, MVP, mos-1271) | megavolt; reviewers dewey | #1786 | done | 236 done | Dewey approves the design, then the manifest. Bus, business, cli and every touched test file run whole, plus the Docker gitea-grant test and every scripts/test-*.sh, green on Sage's gate. M1-M4 killed. Lands before .53 Case 1 hires a granted instance. Red goes to Sage, then Mos. (sage) | `docs/plans/2026-10-22_fire-revokes-grants.md` § Brief: no grant outlives its instance | LANDED e479521d3 ff on 385a7194c (17 commits, manifest b73d4c81). Slot on 7979fb41e green: bus 819, business 231, cli 837, dewey-bus 76 with gitea-grant-docker run, shells green under 3x limits, M1-M22 killed. M1 hang fixed in b4a (mos-1273). Skips: test-task row 244 live-provider pair, test-queue canonical root. DEFERRED: N1 host-unknown hint verb, N2 fire tool text, to the follow-up row. Dewey APPROVE c30614. |
|
||||
| 282 | PRD REQ-LIB-6 rewritten to Q223 and Q226 (mos-1320 item 8) | gosalyn; reviewers webby | #1787 | done | — | Webby approves against the ruling. requirements.test.mjs and every scripts/test-*.sh that reads the PRD or requirements.json are green on Sage's gate. Jason gets a report. (sage) | `docs/plans/2026-10-20_case2-rows.md` § PRD REQ-LIB-6 rewritten to Q223 and Q226 (mos-1320 item 8) | Landed 647424442 on 86a665bdf: 1841f7d4e and 6b5b06819 rebased, patch-ids equal (4e50b4b6, 16a7a02f), range-diff =, tree 4f30ed10a = Gosalyn's gated trial tree. Webby APPROVE rev 3308 c30764 on 6b5b06819. Gate 19:06Z on the tip tree: bus+tasks requirements and tasks verbs 37/0/0, 0 skips; no scripts/test-*.sh reads the PRD (control ok). PRD 1.15. 1.16 is row 291, waits for Jason. |
|
||||
| 283 | Case 2 C2-01d supporting folders, the supporting scope, the editor field and supportingPath (M12) | honker; reviewers webby | #1788 | done | 268 done, 282 done | Webby approves the manifest. The library and business suites are green on Sage's gate, along with every scripts/test-*.sh. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-01d supporting folders, the supporting scope, the editor field and supportingPath (M12) | LANDED 8d2856ee3 (replay of 0bf308dba onto 59226f878, 13 patch-ids equal) plus DEFERRED N1 93694c518. Webby APPROVE r1 c31338 rev 3677, r2 c31342 rev 3683 (mos-1528 G1 mapping.test:52). Sage full gate GREEN 20:37:16Z: all package suites but queue plus every scripts/test-*.sh, 0 fail, library 98/0, webui npm ci, editor build required. supporting.test first-run red placed (mos-1272, 54d9e07bb). #1788 closed c31358. Live unchanged 45dca69f6. |
|
||||
| 284 | Case 2 C2-05c the supporting scope and blob in the lookups | della; reviewers dewey | #1789 | briefed | 224 done, 283 done | Dewey approves the manifest. Library, bus and harness suites and every scripts/test-*.sh green on Sage's gate. Grep packages/*/tests for the scope enum pin and ['canon', 'playground'] before in-review; update each. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-05c the supporting scope and blob in the lookups (sage-2833) | — |
|
||||
| 284 | Case 2 C2-05c the supporting scope and blob in the lookups | della; reviewers webby | #1789 | in-review, round 1, request posted | 224 done, 283 done | Dewey approves the manifest. Library, bus and harness suites and every scripts/test-*.sh green on Sage's gate. Grep packages/*/tests for the scope enum pin and ['canon', 'playground'] before in-review; update each. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-05c the supporting scope and blob in the lookups (sage-2833) | — |
|
||||
| 285 | Case 2 C2-08b role changes for agent writes and the chief's read grant | magica; reviewers poe, webby | #1790 | done | 213 done, 291 done | Webby approves the manifest. Business suite, test-config.sh and every scripts/test-*.sh green on Sage's gate. Grep for CREATIVE_KINDS and 64-hex pins of changed role files. Sage checks it against mos-1309 item 5. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-08b role changes for agent writes and the chief's read grant (mos-1309 item 5) | LANDED 1f3d0350f 13:33:42Z: candidate 2e4503dc7 (14 commits) cherry-picked onto 25874bc86. Logs kept both sides; otherwise the tree equals the candidate except on 7 base paths. Poe c31568 rev 3919, Webby c31569 rev 3920. Sage gate GREEN 13:28:46Z with its own npm ci: 4236 package tests, 0 fail, 1 allowed FED R7 skip; 12 scripts rc 0. No CREATIVE_KINDS, no role-file hash pins. Roles match mos-1309 item 5. 286 rebases as r5. |
|
||||
| 286 | Case 2 C2-03b push and per-seat credentials for the library mirror (with R4, mos-1323) | rockerduck; reviewers boyd, dewey | #1791 | done | 222 done | Dewey approves the manifest. Library and bus suites and every scripts/test-*.sh green on Sage's gate, mutant killed. A search of the run output for the fixture token finds nothing. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-03b push and per-seat credentials for the library mirror | LANDED 5d7801dc1 14:37:02Z: r6 75f6d56b1 (19 commits) picked onto d55944edb; logs kept both sides, code equals the candidate. Boyd c31587 rev 3942, Dewey c31588 rev 3943 (r2 c31584/c31585). Sage gate GREEN 14:36:42Z, own npm ci: 4251 tests, 0 fail, 1 allowed FED R7 skip; 12 scripts rc 0; fixture values in 0 of 29 outputs; B285-M1 kills T7/T9. r6 shortened two fixtures after push-check STOP secrets (sage-4112). Chief seed per sage-4100. |
|
||||
| 287 | Case 2 C2-21 the grant executor for a library project | megavolt; reviewers dewey | #1792 | briefed | 226 done, 285 done | Dewey approves the manifest. Bus suite, Gitea Docker test, install test and every scripts/test-*.sh green on Sage's gate. A search of the run output for the fixture admin and seat tokens finds nothing. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-21 the grant executor for a library project | — |
|
||||
| 287 | Case 2 C2-21 the grant executor for a library project | steelbeak; reviewers boyd, dewey | #1792 | done | 226 done, 285 done | Dewey approves the manifest. Bus suite, Gitea Docker test, install test and every scripts/test-*.sh green on Sage's gate. A search of the run output for the fixture admin and seat tokens finds nothing. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-21 the grant executor for a library project | LANDED 0e935776e 16:12:35Z 10-24 (cand c2338ed58 + a5370b198 on 9b6345466). Boyd APPROVE c31602 rev 3967, Dewey APPROVE c31603 rev 3968. Sage gate GREEN 16:11:38Z: bus 998/0 (Gitea Docker F-A/F-B + X-G3, minted 4 hits 0), business 283/0, cli 989 +1 allowed FED R7 skip, webui 442/0, 12 scripts rc 0. F-C negative record-only follow-up. Dewey N1-N3 non-blocking. #1792 closed c31606. No P19 task. |
|
||||
| 288 | Case 2 C2-02 agent writes, the notice, revert and her Console saves | bubba; reviewers webby | #1793 | briefed | 215 done, 282 done, 283 done, 284 done, 285 done, 286 done | Webby approves the manifest. Library, bus and harness suites and every scripts/test-*.sh green on Sage's gate, mutants killed. A search of the run output for the fixture tokens finds nothing. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-02 agent writes, the notice, revert and her Console saves | A2 revert contract (morgana-0199, sage-3596): library.revert is a human verb. Args {project, commit}, strict keys; project is the business.projects key (the chat repo link), not a UUID. Result {project, path, reverted, commit}. Refusals: 409 stale-base, and 409 library-revert-not-agent for an agent caller. Owner Bubba under Q245 (mos-1506). |
|
||||
| 289 | Case 2 C2-11b Console library pages and her saves (Haiku subagent, decision 129) | researcher; reviewers filbert | #1794 | briefed | 214 done, 288 done | Filbert approves the manifest. The webui suite and every scripts/test-*.sh are green on Sage's gate. Grep for hash pins on bus.js and serve.mjs before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-11b Console library pages and her saves | — |
|
||||
| 290 | Case 2 C2-12b the setup sheet rewrite for free chat, supporting folders and saves | louie; reviewers webby | #1795 | briefed | 217 done, 218 done, 285 done, 291 done | Doc only. Webby approves it against the ruling. Before landing, grep packages/*/tests and scripts/test-*.sh to confirm no test reads the file. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-12b the setup sheet rewrite for free chat, supporting folders and saves | — |
|
||||
| 290 | Case 2 C2-12b the setup sheet rewrite for free chat, supporting folders and saves | louie; reviewers webby | #1795 | done | 217 done, 218 done, 285 done, 291 done | Doc only. Webby approves it against the ruling. Before landing, grep packages/*/tests and scripts/test-*.sh to confirm no test reads the file. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-12b the setup sheet rewrite for free chat, supporting folders and saves | LANDED 3d7dff345 on refactor, pushed 14:55:11Z: cherry-pick of 963303403..22510988d onto 18334d713, four commits equal by patch-id and range-diff, SETUP-SHEET.md blob 37b451c6 as approved. Gate per brief (doc only): Webby approve c31596 rev 3954; grep of packages/*/tests, scripts/test-*.sh and tests/ for SETUP-SHEET finds nothing (positive control hits). No suite run: no test reads the file (decision 137). #1795 closed c31597. |
|
||||
| 291 | PRD 1.16 library.write routes under REQ-LIB-6 (mos-1328) | gosalyn; reviewers webby | #1796 | done | 282 done | Webby approves the text. Jason says yes to the exact text (Mos asks him, mos-1328 step 3); no landing before that. requirements.test.mjs and every scripts/test-*.sh that reads the PRD or requirements.json are green on Sage's gate. Mos gets the landing commit. (sage) | `docs/plans/2026-10-22_prd-1-16-brief.md` § PRD 1.16 library.write routes under REQ-LIB-6 (mos-1328) | Landed 4874d1e83 on e909ef51a: 482e865ac replayed, patch-id equal (cef7de74), range-diff =. Webby APPROVE c30848 on 482e865ac; Jason approved the text in Q229. Gate 16:15Z on the tip tree: every package suite but queue, 0 fail 0 skipped (tasks 100, bus 891, cli 941, plugins 119, webui 392, business 231), every scripts/test-*.sh rc 0 (queue-root and row 244 skips only). PRD 1.16. 1.17 is row 308. |
|
||||
| 292 | stack-release.sh config_checks runs the target loader on any config (row 270 rollback hazard) | gizmoduck; reviewers duckworth | #1797 | done | 203 done, 270 done, 271 done | Duckworth approves the manifest. The touched test files run whole, the cli suite and every scripts/test-*.sh are green on Sage's gate, and the mutants are killed. (sage) | `docs/plans/2026-10-22_config-checks-any-key-brief.md` § stack-release.sh config_checks runs the target loader on any config (row 270 rollback hazard) | Landed 6aab979e6 (cherry-pick of c902a767d..90be6cccd, range-diff all =, patch-id a45c9ba4 equal). Tested head 0d0021454: stack-release.test 224/224, cli suite r2 red only compactions-launch:229 session-invalid 22 (idle-stop race, Vikunja 588, row 302), r3 rerun 910/910 under mos-1411, ten shells green, M1-M9 killed. Duckworth APPROVE rev 3433 on 2fa7ae17. |
|
||||
| 293 | 574: maintenance-free test fixture commits (Vikunja 574) | gander; reviewers filbert | #1805 | done | — | Filbert approves. The four whole files, then the seat and cli suites, are green on Sage's gate. (sage) | `docs/plans/2026-10-23_mvp-readiness-rows.md` § Brief: 574, maintenance-free test fixture commits (Vikunja 574) | LANDED e51d11640 03:55Z 10-23 (cherry-pick of gander b23783f39..a9cae2bd6 onto 464010fbe; range-diff 6/6 equal, range patch-id equal). Filbert APPROVE c30982 cdda41f5; run 2 4 files 153/0, seat 202/0, cli 905/0, 312 flagged/0 maintenance children. N1: lifecycle.test needs TMPDIR of 50 bytes or less. Residual: queue store.mjs, discord git.mjs, harness tools.mjs unflagged. #1805 closed c31013, P19 574 c888. |
|
||||
@@ -346,7 +346,7 @@ Generated from `docs/plans/queue.json` revision 3945 by `queue render`. Do not e
|
||||
| 307 | Row 270's pi compaction reading moves behind the harness kind (REQ-PLUG-2, Vikunja 598) | gizmoduck; reviewers duckworth | #1814 | done | — | Duckworth approves the design, then the manifest; bus, cli, plugins (0 failures), webui and business suites, every scripts/test-*.sh green and the mutants killed on Sage's gate (sage) | `docs/plans/2026-10-23_harness-kind-compactions.md` § Brief: row 270's pi compaction reading moves behind the harness kind (Vikunja 598) | LANDED d243f6b25: 6 row commits replayed onto 8dc132bec, every non-log patch-id equal, candidate paths = 6fdfe3790 (manifest 17c9f8d9). Duckworth APPROVE rev 3586 c31189 round 1, C2 met. Sage gate: bus, cli 0 skipped, plugins fully green incl harness.test.mjs:192, webui, business, seat, every scripts/test-*.sh; M1-M4 killed. P19 598 commented. DEFERRED: 307 N1 store-unreadable not sticky. N3 doc-only BUILD.md red list one short, next BUILD.md change. |
|
||||
| 308 | PRD 1.17 Case 2 story files are DOCX (Q235 to Q237, Q239) | gosalyn; reviewers webby | #1815 | done | 291 done | Webby checks the PRD diff against DRAFT-d9.md at 4607e15b8 one instruction at a time and that nothing else changed. No new Jason question (Q239 approved the text). tasks whole, bus whole, cli stack-copy.test and every scripts/test-*.sh green on Sage's gate. Mos gets the landing commit (mos-1470). (sage) | `docs/plans/2026-10-23_prd-1-17-brief.md` § PRD 1.17 Case 2 story files are DOCX (Q235 to Q237, Q239) | Landed 30e6bb680: Gosalyn 45fe7b3ce replayed onto 7defb58e9, patch-id 4658c831 equal, range-diff =, four blobs equal. Webby r1 APPROVE c31224 rev 3606, d9 at 4607e15b8 applied word for word. Sage gate 17:19Z: every package suite 0 fail 0 skipped (tasks 100, bus 894, cli 942, plugins 119, webui 392), every scripts/test-*.sh rc 0. Mos gets the sha (mos-1470). 570/571 build rows next. |
|
||||
| 309 | 571 W1: reviewed local thesaurus data (post-10-27) | glomgold; reviewers boyd, duckworth | #1823 | in-review, round 1, request posted | — | Independent Opus 5.5 reviewer, not the author, approves the manifest; the brief section's Gate and G1 pass on Sage's slot: touched test files whole, affected package suites green, named mutants killed. Post-10-27; MVP rows keep slot priority (sage) | `docs/plans/2026-10-21_q195-writing-editor.md` § W1 reviewed local thesaurus data | — |
|
||||
| 310 | 571 W2: WordCraft source import and reproducible restricted web build (post-10-27) | gyro; reviewers filbert, gladstone | #1824 | in-progress | — | Independent Opus 5.5 reviewer, not the author, approves the manifest; the brief section's Gate and G1 pass on Sage's slot: touched test files whole, affected package suites green, named mutants killed. Post-10-27; MVP rows keep slot priority (sage) | `docs/plans/2026-10-21_q195-writing-editor.md` § W2 WordCraft source import and reproducible restricted web build | — |
|
||||
| 310 | 571 W2: WordCraft source import and reproducible restricted web build (post-10-27) | gyro; reviewers filbert, gladstone | #1824 | in-progress | — | Independent Opus 5.5 reviewer, not the author, approves the manifest; the brief section's Gate and G1 pass on Sage's slot: touched test files whole, affected package suites green, named mutants killed. Post-10-27; MVP rows keep slot priority (sage) | `docs/plans/2026-10-21_q195-writing-editor.md` § W2 WordCraft source import and reproducible restricted web build | Q48 (mos-1606): W3C terms accepted for cursor-icon 1.2.0 and winit 0.30.13 only; S0 general list not widened; any other W3C occurrence comes back. W2 lands after its gate. Vikunja 621 closed. |
|
||||
| 311 | 571 W3a: bus pins and human-only editor settings (post-10-27) | unassigned | — | queued | — | Independent Opus 5.5 reviewer, not the author, approves the manifest; the brief section's Gate and G1 pass on Sage's slot: touched test files whole, affected package suites green, named mutants killed. Post-10-27; MVP rows keep slot priority (sage) | `docs/plans/2026-10-21_q195-writing-editor.md` § W3a bus pins and human-only editor settings | — |
|
||||
| 312 | 571 W3b: human DOCX store and instant commits (post-10-27) | unassigned | — | queued | 311 done | Independent Opus 5.5 reviewer, not the author, approves the manifest; the brief section's Gate and G1 pass on Sage's slot: touched test files whole, affected package suites green, named mutants killed. Post-10-27; MVP rows keep slot priority (sage) | `docs/plans/2026-10-21_q195-writing-editor.md` § W3b human DOCX store and instant commits | — |
|
||||
| 313 | 571 W6: owned browser bridge and editor-only route (post-10-27) | unassigned | — | queued | 309 done, 310 done, 312 done | Independent Opus 5.5 reviewer, not the author, approves the manifest; the brief section's Gate and G1 pass on Sage's slot: touched test files whole, affected package suites green, named mutants killed. Post-10-27; MVP rows keep slot priority (sage) | `docs/plans/2026-10-21_q195-writing-editor.md` § W6 owned browser bridge and editor-only route | — |
|
||||
@@ -364,6 +364,13 @@ Generated from `docs/plans/queue.json` revision 3945 by `queue render`. Do not e
|
||||
| 325 | Case 1 business file: re-key project to stack to match .mosaic/project.json, plus a project-file test (#414 stage 2, ops-0288, mos-1563) | goldie; reviewers gladstone | #1821 | done | — | New test file and packages/business whole on CLEAR; one key mutant killed; Gladstone review; Sage full landing gate (mos-1455) (sage) | `docs/plans/2026-10-24_case1-project-key-brief.md` § Brief: re-key the Case 1 project to stack | LANDED c5f96836d (ff from 99930362e), tip 3d2569ce2. Gladstone approve c31484 rev 3788 on c5f96836d. Sage gate green 01:31Z on c5f96836d: all package suites 0 fail (business 264, cli 986), every scripts/test-*.sh rc 0. Project key is now stack, matching .mosaic/project.json; new case1-project-key test checks the real project file. Old-test line :70 under mos-1564. |
|
||||
| 326 | Host run sheet: five operator gaps (B2-2 proof format, 323 N2, pre-install key check, failed-first-install retry, backup SHA256SUMS form) | goldie; reviewers gladstone | #1822 | done | — | Gladstone approves the manifest; every new claim cites its line; no test reads the sheet; Sage's full landing gate green (sage) | `docs/plans/2026-10-24_run-sheet-doc-brief.md` § Brief: five run-sheet gaps | LANDED 33f49f7d5 (cherry-pick of a1b13c60e, patch-id match, sheet e4c14516) in f58049de0 with row 208 on one full gate 04:36-04:51Z, all suites green. Gladstone c31489. #1822 closed c31507. B2-2 closes in DEFERRED. Bundle 9fc7754a. |
|
||||
| 327 | Push check in the repository: scripts/push-check.sh passes a binary only when an approved review candidate lists its path and sha256, plus scripts/test-push-check.sh (#1825, mos-1572 A) | bradford; reviewers dewey, poe | #1825 | done | — | Poe gives the first Codex review, Dewey reviews the landing candidate; scripts/test-push-check.sh and every other scripts/test-*.sh green on Sage's gate; the mutants killed; Sage's next push runs through scripts/push-check.sh (sage) | `docs/plans/2026-10-24_push-guard-binary-manifest.md` § Brief: push-check.sh passes a binary only when the reviewed candidate lists its path and sha256 | LANDED 68c08b4e7 (pushed 07:18:03Z 10-24): 3 commits on 6a5e12511, blobs equal round 3 d858c9f8b; Poe c31525 rev 3882, Dewey c31526 rev 3883; gate GREEN 07:15:49Z with its own npm ci (first run red on empty canonical node_modules, sage-4023); 6 push-check mutants KILLED; next push 42ba0f5bc ran through scripts/push-check.sh --fetch, PUSH_READY; #1825 closed c31530. |
|
||||
| 328 | T9 library-credential test: the second broker start passes Node's TIMEOUT_MAX interval so no timer tick overlaps the verb sync; mutant fails a 50-run stress loop (#1826, Vikunja 622, mos-1594) | fethry; reviewers boyd, dewey | #1826 | done | — | Boyd gives the first Codex review, Dewey reviews the landing candidate; touched tests run whole, the 50-run stress loop passes with the fix and M1 fails it; bus and every scripts/test-*.sh green on Sage's gate (sage) | `docs/plans/2026-10-24_t9-library-sync-race.md` § Brief: the T9 verb sync on the second start runs with no timer tick able to overlap it | LANDED 8bb5b3c73 19:40Z 10-24 (8 Fethry commits on 5e77cdd09). Gate GREEN 19:38:54Z on 8bb5b3c73, no drift. Boyd APPROVE c31621, Dewey APPROVE c31622. #1826 closed c31625; Vikunja 622 done. |
|
||||
| 329 | Model proxy P1: model-proxy package, pinned fetch with sha256 refusal, mosaic proxy init, proxy unit (237, mos-1600) | magica; reviewers boyd, dewey | #1827 | done | — | Boyd first review, re-measuring both release hashes; Dewey landing review; BUILD.md has the acceptance output. Sage's landing gate: every package suite but queue plus every scripts/test-*.sh green, touched test files whole, refusals each tested, named mutants killed (brief Gate). (sage) | `docs/plans/2026-10-24_model-proxy-rows.md` § Brief: P1 the model-proxy package, pinned fetch, proxy init and the proxy unit | Landed 61ebf2c34 (pushed 23:31:27Z 10-24); Magica r2 commits cherry-picked onto 669cdf42c. Boyd c31642 rev 4048, Dewey c31643 rev 4047. Sage fresh gate GREEN 23:29:14Z (mos-1634). #1827 closed c31650; Vikunja 237 comment 1109, stays open for P2-P7. DEFERRED: N2, R2-N1 to row 331; N1 to P7. |
|
||||
| 330 | Model proxy P4: Console proxy page (OAuth sign-in, API keys, priority, key list, usage) via bus verbs (Q250, mos-1606/1608) | poe; reviewers filbert, gladstone | #1828 | briefed | — | Gladstone first review; Filbert landing review; starts now, lands after P1 rebased on it; webui with MOSAIC_REQUIRE_EDITOR_BUILD=1. Sage gate: all package suites but queue and every scripts/test-*.sh green; brief Gate. (sage) | `docs/plans/2026-10-24_proxy-page-brief.md` § Brief: P4 the Console proxy page | — |
|
||||
| 331 | Model proxy P2: per-agent model keys, reconcile and launch preflight (237, mos-1600) | sage; reviewers boyd, dewey | #1829 | briefed | 329 done | Boyd first review; Dewey landing review; three named mutants killed. Sage gate: all package suites but queue and every scripts/test-*.sh green; brief Gate. (sage) | `docs/plans/2026-10-24_model-proxy-rows.md` § Brief: P2 per-agent model keys, reconcile and the launch preflight | P2 input from magica-0020 (acceptance-probe-4): 8.0.42 revocation lags ~105ms after v8 PUT (first call 200, then 401); reconcile/revoke must poll to 401, not assume instant. Per-key model objects give 422; no per-key model restriction. |
|
||||
| 332 | Model proxy P3: harness config from the proxy (237, mos-1600) | fethry; reviewers dewey, gladstone | #1830 | in-progress | — | Gladstone first review; Dewey landing review; named mutant killed. Sage gate: all package suites but queue and every scripts/test-*.sh green; brief Gate. (sage) | `docs/plans/2026-10-24_model-proxy-rows.md` § Brief: P3 harness config from the proxy | mos-1616: amendment docs/plans/2026-10-24_p3-pi-routes-amendment.md (blob 079debd5605c, c8a9bcf94) replaces P3 item 4's non-Anthropic line and adds tests and a mutant. Starts now on a new Codex thread against P2's {baseUrl, key}; lands only after 329 and 331 land and its own gate (mos-1351). |
|
||||
| 333 | Model proxy P5: usage plug-in and per-key limits, per-key count only (237, mos-1600) | sage; reviewers boyd, duckworth | #1831 | briefed | 331 done | Boyd first review; Dewey landing review; named mutant killed. Sage gate: all package suites but queue and every scripts/test-*.sh green; brief Gate. (sage) | `docs/plans/2026-10-24_model-proxy-rows.md` § Brief: P5 usage plug-in and per-key limits | — |
|
||||
| 334 | Case 2 proxy unit: case2-unit.sh renders P1's mosaic-proxy.service and the bus Wants/After under --proxy (237, mos-1616) | sage; reviewers boyd, webby | #1832 | queued | 329 done | Codex builder after 329 lands; Boyd first review, Webby Claude review; run list from imports, case1-accept off; mutants: bus loses After= under --proxy, proxy ExecStart skips run.mjs; Sage landing gate: every package suite but queue, every scripts/test-*.sh (sage) | `docs/plans/2026-10-24_case2-proxy-unit-brief.md` § Brief: Case 2 proxy unit in case2-unit.sh and case2-install.sh | — |
|
||||
|
||||
<!-- mosaic-queue:end -->
|
||||
|
||||
|
||||
+3888
-71
File diff suppressed because it is too large.
Load diff
@@ -1,6 +1,6 @@
|
||||
// REQ-LIMIT-1: editable concurrency policy, never a compiled-in capacity ceiling.
|
||||
import { requireObject, requireId, requireString, rejectUnknownKeys } from "./util.mjs";
|
||||
import { refuse } from "./errors.mjs";
|
||||
import { BusinessError, refuse } from "./errors.mjs";
|
||||
|
||||
const name = (value, at) => requireString(value, at, { max: 200 });
|
||||
const count = (value, at) => {
|
||||
@@ -15,10 +15,19 @@ export function checkModels(value, where = "launch.models") {
|
||||
return Object.fromEntries(Object.entries(value).map(([key, entry]) => {
|
||||
requireId(key, where);
|
||||
requireObject(entry, `${where}.${key}`);
|
||||
rejectUnknownKeys(entry, ["provider", "model"], `${where}.${key}`);
|
||||
rejectUnknownKeys(entry, entry.provider === "proxy" ? ["provider", "model", "route"] : ["provider", "model"], `${where}.${key}`);
|
||||
if (entry.provider === "proxy") {
|
||||
const missing = !Object.hasOwn(entry, "route");
|
||||
if (missing || !["anthropic", "glm", "codex"].includes(entry.route)) {
|
||||
throw Object.assign(new BusinessError(`${where}.${key}.route must name anthropic, glm or codex`), {
|
||||
code: missing ? "proxy-route-missing" : "proxy-route-unknown",
|
||||
});
|
||||
}
|
||||
}
|
||||
return [key, {
|
||||
provider: name(entry.provider, `${where}.${key}.provider`),
|
||||
model: name(entry.model, `${where}.${key}.model`),
|
||||
...(entry.provider === "proxy" && Object.hasOwn(entry, "route") ? { route: entry.route } : {}),
|
||||
}];
|
||||
}));
|
||||
}
|
||||
@@ -82,7 +91,7 @@ export function checkAccount(value, where = "launch.account") {
|
||||
requireObject(value, where);
|
||||
rejectUnknownKeys(value, ["name", "credential"], where);
|
||||
const accountName = requireId(value.name, `${where}.name`);
|
||||
if (typeof value.credential !== "string" || !CREDENTIAL.test(value.credential)) {
|
||||
if (typeof value.credential !== "string" || (value.credential !== "proxy" && !CREDENTIAL.test(value.credential))) {
|
||||
refuse(`${where}.credential must name environment variables, joined with "+"`);
|
||||
}
|
||||
return { name: accountName, credential: value.credential };
|
||||
|
||||
@@ -38,11 +38,11 @@ test('REQ-ROLE-3 writer: pre-rename fault leaves old file, post-rename recovery
|
||||
test('REQ-LIMIT-1 / decision145 migration prints only, preserves values, rejects ambiguity, and is idempotent',async()=>{
|
||||
const {migrateLaunch}=await import('../src/migrate-launch.mjs');const f=fixture(),old=structuredClone(f.doc);old.roles.pm.vars.model='claude-opus-5-5';for(const [name,r] of Object.entries(old.roles))if(name!=='pm')r.vars.model='claude-sonnet-5-5';old.launch={by:'chief',instances:['coder','reviewer'],max:{opus:4,sonnet:4}};writeJson(f.file,old);const bytes=readFileSync(f.file,'utf8'),mtime=statSync(f.file).mtimeMs;
|
||||
const mapping={models:f.doc.launch.models,families:{opus:'opus',sonnet:'sonnet'}};const next=migrateLaunch(old,mapping,f.file,{rolesDir:f.rolesDir});assert.deepEqual(next.launch.limits, f.doc.launch.limits);assert.equal(next.roles.pm.vars.model,'opus');assert.equal(next.roles.coder.vars.model,'sonnet');assert.deepEqual(migrateLaunch(next,mapping,f.file,{rolesDir:f.rolesDir}),next);
|
||||
assert.throws(()=>migrateLaunch(old,{...mapping,families:{opus:'missing',sonnet:'sonnet'}},f.file,{rolesDir:f.rolesDir}));assert.throws(()=>migrateLaunch(old,{...mapping,families:{opus:'opus',sonnet:'opus'}},f.file,{rolesDir:f.rolesDir}),/ambiguous/);assert.throws(()=>migrateLaunch(old,{...mapping,models:{...mapping.models,proxy:{provider:'proxy',model:'claude-opus-5-5'}},instances:{pm:'proxy'}},f.file,{rolesDir:f.rolesDir}),/ambiguous/);const onlyOpus=structuredClone(old);onlyOpus.launch.max={opus:4};assert.throws(()=>migrateLaunch(onlyOpus,mapping,f.file,{rolesDir:f.rolesDir}),/unknown or ambiguous/);const allOpus=structuredClone(old);for(const r of Object.values(allOpus.roles))r.vars.model='claude-opus-5-5';assert.throws(()=>migrateLaunch(allOpus,{...mapping,families:{opus:'opus',sonnet:'opus'}},f.file,{rolesDir:f.rolesDir}),/ambiguous/);assert.equal(readFileSync(f.file,'utf8'),bytes);assert.equal(statSync(f.file).mtimeMs,mtime);
|
||||
assert.throws(()=>migrateLaunch(old,{...mapping,families:{opus:'missing',sonnet:'sonnet'}},f.file,{rolesDir:f.rolesDir}));assert.throws(()=>migrateLaunch(old,{...mapping,families:{opus:'opus',sonnet:'opus'}},f.file,{rolesDir:f.rolesDir}),/ambiguous/);assert.throws(()=>migrateLaunch(old,{...mapping,models:{...mapping.models,proxy:{provider:'proxy',route:'anthropic',model:'claude-opus-5-5'}},instances:{pm:'proxy'}},f.file,{rolesDir:f.rolesDir}),/ambiguous/);const onlyOpus=structuredClone(old);onlyOpus.launch.max={opus:4};assert.throws(()=>migrateLaunch(onlyOpus,mapping,f.file,{rolesDir:f.rolesDir}),/unknown or ambiguous/);const allOpus=structuredClone(old);for(const r of Object.values(allOpus.roles))r.vars.model='claude-opus-5-5';assert.throws(()=>migrateLaunch(allOpus,{...mapping,families:{opus:'opus',sonnet:'opus'}},f.file,{rolesDir:f.rolesDir}),/ambiguous/);assert.equal(readFileSync(f.file,'utf8'),bytes);assert.equal(statSync(f.file).mtimeMs,mtime);
|
||||
});
|
||||
test('REQ-LIMIT-1 shared settings validates the complete proposed selection and logs exact actor',async()=>{
|
||||
const {launchSettings}=await import('../src/launch-settings.mjs');const f=fixture(),system={environment:'development',dataRoot:join(f.root,'data'),'execution.backend':'docker','execution.provider':'anthropic','execution.model':'sonnet','execution.adapter':'pi'};const settings=launchSettings({dir:f.root,rolesDir:f.rolesDir,system,sessions:()=>[{business:'acme',provider:'anthropic',model:'claude-opus-5-5'}]});const view=settings.read('acme');assert.equal(view.usage.total,1);assert.equal(view.instances.pm.modelKey,'opus');
|
||||
const result=settings.set('acme',{revision:view.revision,limits:{total:0},instances:{coder:'opus'}},'jason');assert.equal(result.limits.total,0);assert.equal(result.instances.coder.modelKey,'opus');const models=structuredClone(result.models);models.opus.provider='proxy';const updated=settings.set('acme',{revision:result.revision,models},'jason');assert.equal(updated.instances.pm.provider,'proxy');assert.throws(()=>settings.set('acme',{revision:result.revision,instances:{coder:'ghost'}},'jason'));assert.throws(()=>settings.set('acme',{revision:result.revision,roles:{}},'jason'));
|
||||
const result=settings.set('acme',{revision:view.revision,limits:{total:0},instances:{coder:'opus'}},'jason');assert.equal(result.limits.total,0);assert.equal(result.instances.coder.modelKey,'opus');const models=structuredClone(result.models);models.opus.provider='proxy';models.opus.route='anthropic';const updated=settings.set('acme',{revision:result.revision,models},'jason');assert.equal(updated.instances.pm.provider,'proxy');assert.throws(()=>settings.set('acme',{revision:result.revision,instances:{coder:'ghost'}},'jason'));assert.throws(()=>settings.set('acme',{revision:result.revision,roles:{}},'jason'));
|
||||
});
|
||||
test('REQ-LIMIT-1 provider is table data: another provider routes separately and aliases share exact counts',async()=>{
|
||||
const {limitHit}=await import('../src/launch.mjs');const {sessionModel}=await import('../../harness/src/bundle.mjs');
|
||||
@@ -151,14 +151,14 @@ test('B7 kept instance: the models keys it names and the instances key are the h
|
||||
const chief='chief-of-staff.chief';let {revision}=settings.read('acme');
|
||||
const models=structuredClone(settings.read('acme').models);
|
||||
const key=doc.roles.coder.vars.model;
|
||||
const moved={...models,[key]:{provider:'proxy',model:'claude-sonnet-5-5'}};
|
||||
const moved={...models,[key]:{provider:'proxy',route:'anthropic',model:'claude-sonnet-5-5'}};
|
||||
for(const actor of [chief,'tester']){
|
||||
const bytes=file();
|
||||
assert.throws(()=>settings.set('acme',{revision,models:moved},actor),code('instance-kept'),actor);
|
||||
assert.throws(()=>settings.set('acme',{revision,instances:{coder:'opus'}},actor),code('instance-kept'),actor);
|
||||
assert.equal(file(),bytes,actor);
|
||||
}
|
||||
({revision}=settings.set('acme',{revision,models:{...models,spare:{provider:'proxy',model:'claude-opus-5-5'}}},chief));
|
||||
({revision}=settings.set('acme',{revision,models:{...models,spare:{provider:'proxy',route:'anthropic',model:'claude-opus-5-5'}}},chief));
|
||||
({revision}=settings.set('acme',{revision,models:{...settings.read('acme').models,[key]:moved[key]}},'jason'));
|
||||
settings.set('acme',{revision,instances:{coder:'opus'}},'jason');
|
||||
assert.equal(JSON.parse(file()).roles.coder.vars.model,'opus');
|
||||
@@ -185,7 +185,7 @@ test('M10 creative instance: models keys it names, the instances key and its rou
|
||||
const g={...f,doc};const {settings}=await routeSettings(g);const file=()=>readFileSync(f.file,'utf8');
|
||||
const chief='chief-of-staff.chief';let {revision}=settings.read('acme');
|
||||
const models=structuredClone(settings.read('acme').models);
|
||||
const sonnet={...models,sonnet:{provider:'proxy',model:'claude-sonnet-5-5'}};
|
||||
const sonnet={...models,sonnet:{provider:'proxy',route:'anthropic',model:'claude-sonnet-5-5'}};
|
||||
for(const actor of [chief,'tester']){
|
||||
const bytes=file();
|
||||
assert.throws(()=>settings.set('acme',{revision,models:sonnet},actor),code('organization-creative-human-only'),actor);
|
||||
@@ -195,7 +195,7 @@ test('M10 creative instance: models keys it names, the instances key and its rou
|
||||
assert.equal(file(),bytes,actor);
|
||||
}
|
||||
// A key no creative instance names stays the chief's.
|
||||
({revision}=settings.set('acme',{revision,models:{...models,opus:{provider:'proxy',model:'claude-opus-5-5'}}},chief));
|
||||
({revision}=settings.set('acme',{revision,models:{...models,opus:{provider:'proxy',route:'anthropic',model:'claude-opus-5-5'}}},chief));
|
||||
assert.throws(()=>settings.set('acme',{revision,instances:{bean:'opus'}},'jason'),code('organization-creative-instance'));
|
||||
({revision}=settings.set('acme',{revision,models:sonnet},'jason'));
|
||||
({revision}=settings.setRoutes('acme',{revision,routes:{bean:{harness:'pi',model:'sonnet',thinking:'high',fallback:{harness:'pi',model:'opus',thinking:'low'}}}},'jason'));
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// P3: the proxy account is a declared credential source, never a secret value.
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { checkAccount, checkModels } from '../src/launch.mjs';
|
||||
|
||||
test('P3 proxy model validation preserves each explicit wire route and the proxy model id', () => {
|
||||
for (const route of ['anthropic', 'glm', 'codex']) {
|
||||
const models = { selected: { provider: 'proxy', model: 'proxy-only-id', route } };
|
||||
assert.deepEqual(checkModels(models), models, route);
|
||||
}
|
||||
});
|
||||
|
||||
test('P3 a proxy model without route refuses proxy-route-missing', () => {
|
||||
assert.throws(() => checkModels({ selected: { provider: 'proxy', model: 'proxy-only-id' } }), {
|
||||
code: 'proxy-route-missing',
|
||||
});
|
||||
});
|
||||
|
||||
test('P3 an unknown proxy route refuses proxy-route-unknown', () => {
|
||||
for (const route of ['openai-codex', 'unknown', '', null, 7]) {
|
||||
assert.throws(() => checkModels({ selected: { provider: 'proxy', model: 'proxy-only-id', route } }), {
|
||||
code: 'proxy-route-unknown',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test('P3 nonproxy models retain their provider/model-only shape and reject route metadata', () => {
|
||||
assert.deepEqual(checkModels({ selected: { provider: 'zai', model: 'glm-fixture' } }), {
|
||||
selected: { provider: 'zai', model: 'glm-fixture' },
|
||||
});
|
||||
assert.throws(() => checkModels({ selected: { provider: 'zai', model: 'glm-fixture', route: 'glm' } }), /unsupported/);
|
||||
});
|
||||
|
||||
test('P3 launch.account accepts the proxy credential source without naming host variables', () => {
|
||||
assert.deepEqual(checkAccount({ name: 'proxy', credential: 'proxy' }), { name: 'proxy', credential: 'proxy' });
|
||||
});
|
||||
|
||||
test('P3 existing environment-set accounts retain their validated representation', () => {
|
||||
for (const credential of ['CLAUDE_CODE_OAUTH_TOKEN', 'ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN+ANTHROPIC_BASE_URL']) {
|
||||
assert.deepEqual(checkAccount({ name: 'max', credential }), { name: 'max', credential });
|
||||
}
|
||||
for (const credential of ['', 'fx332-fake', 'proxy+ANTHROPIC_API_KEY', 'PROXY+proxy']) {
|
||||
assert.throws(() => checkAccount({ name: 'proxy', credential }), /launch\.account\.credential/);
|
||||
}
|
||||
});
|
||||
@@ -157,6 +157,7 @@ import {
|
||||
runnerOf,
|
||||
SESSION_CREDENTIALS,
|
||||
sessionEnv,
|
||||
isProviderVariable,
|
||||
sessionExit,
|
||||
sessionStore,
|
||||
sessionWorkspace,
|
||||
@@ -216,6 +217,9 @@ export function createLauncher({
|
||||
host,
|
||||
system,
|
||||
env = process.env,
|
||||
// P2 supplies only {baseUrl,key}; a factory may supply the current agent's
|
||||
// context per check. P3 never reads a key store or calls the proxy itself.
|
||||
proxy = null,
|
||||
tracker = false,
|
||||
launches = true,
|
||||
makeLoader = createLoader,
|
||||
@@ -388,7 +392,18 @@ export function createLauncher({
|
||||
let harness = await harnessOf(primary);
|
||||
let route = "primary";
|
||||
let routeReason = null;
|
||||
const failed = call(primary.harness, "preflight", () => harness.preflight({ env }));
|
||||
const proxyFor = async (selected) => {
|
||||
if (selected.provider !== "proxy") return null;
|
||||
const context = typeof proxy === "function" ? await proxy({ business: b.id, instance }) : proxy;
|
||||
if (!context || typeof context.key !== "string" || !context.key
|
||||
|| typeof context.baseUrl !== "string" || !/^http:\/\/127\.0\.0\.1:[0-9]+$/.test(context.baseUrl)) {
|
||||
throw new Refusal("no-session-credential", "proxy model needs its own session proxy context");
|
||||
}
|
||||
// Copy only the agreed seam; never keep a store or management handle.
|
||||
return { baseUrl: context.baseUrl, key: context.key };
|
||||
};
|
||||
let sessionProxy = await proxyFor(primary);
|
||||
const failed = call(primary.harness, "preflight", () => harness.preflight({ env: sessionProxy ? sessionEnv(env, "preflight", sessionProxy) : env, proxy: sessionProxy }));
|
||||
if (failed) {
|
||||
if (!fallback) throw new Refusal(failed.code, failed.message);
|
||||
// The fallback only on a failed primary preflight, and only at launch (row 65). Any refusal of
|
||||
@@ -397,7 +412,8 @@ export function createLauncher({
|
||||
if (e instanceof Refusal) e.message += ` (primary ${primary.harness}: ${failed.message})`;
|
||||
throw e;
|
||||
});
|
||||
const also = call(fallback.harness, "preflight", () => second.preflight({ env }));
|
||||
sessionProxy = await proxyFor(fallback);
|
||||
const also = call(fallback.harness, "preflight", () => second.preflight({ env: sessionProxy ? sessionEnv(env, "preflight", sessionProxy) : env, proxy: sessionProxy }));
|
||||
if (also) throw new Refusal(also.code, `fallback ${fallback.harness}: ${also.message} (primary ${primary.harness}: ${failed.message})`);
|
||||
[model, harness, route, routeReason] = [fallback, second, "fallback", failed.code];
|
||||
}
|
||||
@@ -408,12 +424,18 @@ export function createLauncher({
|
||||
if (account !== null) {
|
||||
// The usage records keep this name (bus schema 3i), so it must never be a credential's value.
|
||||
const holds = [...new Set(SESSION_CREDENTIALS.flat())].filter((k) => env[k] === account);
|
||||
if (sessionProxy?.key === account) holds.push("session proxy key");
|
||||
if (holds.length) throw new Refusal("account-is-credential", `launch.account.name is the value of ${holds.join(", ")}; it names the account, never its credential`);
|
||||
const named = b.launch.account.credential.split("+");
|
||||
if (!SESSION_CREDENTIALS.some((set) => set.join("+") === named.join("+")) || !named.every((k) => Boolean(env[k])))
|
||||
throw new Refusal("account-credential-missing", `account ${account} names ${named.join(" with ")}, which the bus host doesn't hold as a session login`);
|
||||
const other = [...new Set(SESSION_CREDENTIALS.flat())].filter((k) => !named.includes(k) && Boolean(env[k]));
|
||||
if (other.length) throw new Refusal("account-credential-ambiguous", `account ${account} names ${named.join(" with ")}, but the bus host also sets ${other.join(", ")}`);
|
||||
if (sessionProxy || b.launch.account.credential === "proxy") {
|
||||
if (!sessionProxy || b.launch.account.credential !== "proxy")
|
||||
throw new Refusal("account-credential-missing", `account ${account} needs the declared proxy credential`);
|
||||
} else {
|
||||
const named = b.launch.account.credential.split("+");
|
||||
if (!SESSION_CREDENTIALS.some((set) => set.join("+") === named.join("+")) || !named.every((k) => Boolean(env[k])))
|
||||
throw new Refusal("account-credential-missing", `account ${account} names ${named.join(" with ")}, which the bus host doesn't hold as a session login`);
|
||||
const other = [...new Set(SESSION_CREDENTIALS.flat())].filter((k) => !named.includes(k) && Boolean(env[k]));
|
||||
if (other.length) throw new Refusal("account-credential-ambiguous", `account ${account} names ${named.join(" with ")}, but the bus host also sets ${other.join(", ")}`);
|
||||
}
|
||||
}
|
||||
const unavailable = sandboxProblem({ bwrap });
|
||||
if (unavailable) throw new Refusal("sandbox-unavailable", unavailable);
|
||||
@@ -432,7 +454,7 @@ export function createLauncher({
|
||||
throw new Refusal("limit-check-failed", `${may?.check ?? "bus"}: ${may?.code ?? "malformed-answer"}`);
|
||||
}
|
||||
admission(admissionGeneration);
|
||||
return { resolved, model, account, harness, route, routeReason, admissionGeneration };
|
||||
return { resolved, model, account, harness, route, routeReason, admissionGeneration, sessionProxy };
|
||||
}
|
||||
|
||||
// Bundle, spawn, bind, register, hand over the capability.
|
||||
@@ -503,8 +525,8 @@ export function createLauncher({
|
||||
if (c.outcome === "kept") record({ event: "checkout-kept", instance, project: c.project, reason: c.reason });
|
||||
else if (c.outcome === "fetched") record({ event: "checkout-fetched", instance, project: c.project, reason: c.reason });
|
||||
}
|
||||
let harness, route, routeReason;
|
||||
({resolved,model,account,harness,route,routeReason}=await check(instance,{pm:instance===b.launch.by,thread,session,human}));
|
||||
let harness, route, routeReason, sessionProxy;
|
||||
({resolved,model,account,harness,route,routeReason,sessionProxy}=await check(instance,{pm:instance===b.launch.by,thread,session,human}));
|
||||
admission(admissionGeneration);
|
||||
const { run, runDir } = newRun(dataRoot, b.id);
|
||||
const sessionDir = store ? store.session : join(runDir, "session");
|
||||
@@ -525,6 +547,7 @@ export function createLauncher({
|
||||
dir: join(runDir, "bundle"),
|
||||
resolved,
|
||||
models: b.launch.models,
|
||||
proxy: sessionProxy,
|
||||
contract: readFileSync(resolved.contract, "utf8"),
|
||||
arbiter: Object.values(b.arbiters).includes(instance),
|
||||
tracker,
|
||||
@@ -548,6 +571,12 @@ export function createLauncher({
|
||||
// Row 199 (D11): or publishes it, once a reviewed commit routes git.push.remote within-role.
|
||||
const held = [...resolved.limits.authority.withinRole, ...resolved.limits.authority.crossRole];
|
||||
const git = land || ["git.push.working", "review.verdict", "git.push.remote"].some((a) => held.includes(a)) ? gitSocketPath(dataRoot) : null;
|
||||
// Secrets go only to spawn, never session.json. Keep this split at the
|
||||
// actual serialization boundary, even if a harness adds another key.
|
||||
const harnessEnv = {}, transientEnv = {};
|
||||
for (const [name, value] of Object.entries(bundle.env)) {
|
||||
(sessionProxy && isProviderVariable(name) ? transientEnv : harnessEnv)[name] = value;
|
||||
}
|
||||
writeSessionFile(runDir, {
|
||||
...sessionDefaults,
|
||||
business: b.id,
|
||||
@@ -565,7 +594,7 @@ export function createLauncher({
|
||||
sessionDir,
|
||||
adapter,
|
||||
bundle: files,
|
||||
harnessEnv: bundle.env,
|
||||
harnessEnv,
|
||||
turnEnd,
|
||||
toolSocket,
|
||||
turnMarker,
|
||||
@@ -589,7 +618,7 @@ export function createLauncher({
|
||||
try {
|
||||
admission(admissionGeneration);
|
||||
spawnedAt.set(run, Date.now());
|
||||
child = spawnSession({ runDir, env: sessionEnv(env, run), paths, business: b.id, role: instance, bwrap });
|
||||
child = spawnSession({ runDir, env: { ...sessionEnv(env, run, sessionProxy), ...transientEnv }, paths, business: b.id, role: instance, bwrap });
|
||||
} catch (e) {
|
||||
spawnedAt.delete(run);
|
||||
unregisterRuntime(dataRoot, managed.id);
|
||||
|
||||
@@ -82,6 +82,8 @@ function prepare(t, more = (doc) => doc) {
|
||||
const f = fixture(root, "acme", (doc) => more(edit(doc)));
|
||||
for (const k of SESSION_CREDENTIALS.flat()) delete f.env[k];
|
||||
f.env.CLAUDE_CODE_OAUTH_TOKEN = "stand-in";
|
||||
if (f.doc.roles.chief.vars.harness === "pi" || f.doc.roles.chief.vars["fallback.harness"] === "pi")
|
||||
f.env.ZAI_API_KEY = "fx332-pi";
|
||||
// The project root is a repository on main, so each launch can clone it.
|
||||
const project = f.doc.projects.stack.root;
|
||||
gitIn(project, "init", "-q", "-b", "main");
|
||||
@@ -145,6 +147,29 @@ function ask(path, line) {
|
||||
|
||||
const log = (f) => readFileSync(launchLogFile(f.dataRoot, "acme"), "utf8").trim().split("\n").map((l) => JSON.parse(l));
|
||||
|
||||
// Named mutant: proxy key serialized into session.json. Keep the real launcher,
|
||||
// harness env generation, session writer and runner; only the model adapter is fake.
|
||||
test('P3 proxy key reaches the spawned runner but never session.json or the bundle', { skip, timeout: 30000 }, async t => {
|
||||
const proxy = { baseUrl: 'http://127.0.0.1:12345', key: 'fx332-agent-a' };
|
||||
const prepared = prepare(t, doc => {
|
||||
doc.launch.models['claude-opus-5-5'] = { provider: 'proxy', model: 'claude-fixture', route: 'anthropic' };
|
||||
return doc;
|
||||
});
|
||||
const { launcher, f, close } = await setup(t, doc => doc, prepared, { proxy });
|
||||
const launched = await launcher.launchPm();
|
||||
const session = readSessions(f.dataRoot).find(s => s.run === launched.run);
|
||||
assert.ok(session);
|
||||
const environ = readFileSync(`/proc/${session.runnerPid}/environ`, 'utf8').split('\0');
|
||||
assert.ok(environ.includes(`ANTHROPIC_API_KEY=${proxy.key}`));
|
||||
assert.ok(!environ.some(value => value.startsWith('CLAUDE_CODE_OAUTH_TOKEN=')));
|
||||
const stored = readFileSync(join(session.runDir, 'session.json'), 'utf8');
|
||||
assert.ok(!stored.includes(proxy.key), 'session.json must never persist the proxy key');
|
||||
const bundleFiles = readdirSync(join(session.runDir, 'bundle'), { recursive: true, withFileTypes: true })
|
||||
.filter(entry => entry.isFile()).map(entry => join(entry.parentPath, entry.name));
|
||||
for (const file of bundleFiles) assert.ok(!readFileSync(file, 'utf8').includes(proxy.key), file);
|
||||
assert.equal(await close(), 0);
|
||||
});
|
||||
|
||||
test("bus start --pm: the launcher instance (the chief of staff) runs under its own PID namespace, registered, with a manifest", { skip, timeout: 30000 }, async (t) => {
|
||||
// Row 122: uncertain write receipts cannot prevent the window's first chief launch.
|
||||
const prepared = prepare(t);
|
||||
@@ -672,14 +697,15 @@ test("a Claude Code launch with no session credential refuses before it starts",
|
||||
assert.deepEqual(readdirSync(join(f.dataRoot, "launches")), ["acme.jsonl"], "no run was made");
|
||||
});
|
||||
|
||||
test("a pi launch needs no session credential: pi logs in through its own ENV_ALLOW variables", { skip, timeout: 30000 }, async (t) => {
|
||||
test("a pi launch with no own credential refuses no-session-credential", { skip, timeout: 30000 }, async (t) => {
|
||||
const given = prepare(t);
|
||||
delete given.f.env.CLAUDE_CODE_OAUTH_TOKEN;
|
||||
delete given.f.env.ZAI_API_KEY;
|
||||
assert.deepEqual(SESSION_CREDENTIALS.flat().filter((k) => k in given.f.env), []);
|
||||
const { f, launcher } = await setup(t, undefined, given);
|
||||
const chief = await launcher.launchPm();
|
||||
assert.equal(chief.harness, "pi");
|
||||
assert.deepEqual(log(f).map((e) => [e.event, e.instance]), [["launch", "chief"]]);
|
||||
await assert.rejects(launcher.launchPm(), /PM launch refused: no-session-credential/);
|
||||
assert.deepEqual(log(f).map((e) => [e.event, e.code]), [["refused", "no-session-credential"]]);
|
||||
assert.deepEqual(readSessions(f.dataRoot), []);
|
||||
});
|
||||
|
||||
// Row 65: a harness module no core file names, switched on in the organization file, runs a real
|
||||
@@ -696,6 +722,7 @@ test("row 65 a failed primary preflight launches the fallback on a fixture harne
|
||||
write("harness", "down", `export * from ${pi};\nexport const preflight = () => ({ code: "fixture-down", message: "fixture preflight fails" });\n`);
|
||||
write("harness", "fixture", `export * from ${pi};\nexport const adapter = () => ${JSON.stringify(given.adapter)};\n`);
|
||||
});
|
||||
given.f.env.ZAI_API_KEY = "fx332-pi";
|
||||
const { f, launcher } = await setup(t, undefined, given, { makeLoader });
|
||||
const chief = await launcher.launchPm();
|
||||
assert.deepEqual({ ...chief, run: undefined }, { run: undefined, instance: "chief", harness: "fixture", model: "claude-sonnet-5-5" });
|
||||
|
||||
@@ -83,7 +83,13 @@ export function sessionModel(resolved, models = null, which = "primary") {
|
||||
if (models === null) return { harness, provider: null, model, thinking };
|
||||
const entry = Object.hasOwn(models, model) ? models[model] : null;
|
||||
if (!entry) throw new BundleError(`instance ${resolved.instance}: unknown model table selection ${model}`);
|
||||
return { harness, provider: entry.provider, model: entry.model, modelKey: model, thinking };
|
||||
if (entry.provider === "proxy") {
|
||||
const missing = !Object.hasOwn(entry, "route");
|
||||
if (missing || !["anthropic", "glm", "codex"].includes(entry.route))
|
||||
throw new BundleError("proxy model needs an explicit anthropic, glm or codex route", missing ? "proxy-route-missing" : "proxy-route-unknown");
|
||||
}
|
||||
return { harness, provider: entry.provider, model: entry.model, modelKey: model, thinking,
|
||||
...(entry.provider === "proxy" ? { proxyRoute: entry.route } : {}) };
|
||||
}
|
||||
|
||||
// Row 91 (#1585): the project clones the host made or kept in the workspace.
|
||||
@@ -309,10 +315,14 @@ function prompt({ contract, resolved, session, tools, untyped, builtins, persona
|
||||
// personaDir <configDir>/personas, where a creative instance's persona file is read from
|
||||
// (row 213); an instance with persona.sha256 and no personaDir refuses
|
||||
// Returns { manifest, files, policy, tools, env } with absolute paths; env is the module's
|
||||
// adapter variables, which the host writes into session.json.
|
||||
export function buildBundle({ dir, resolved, contract, arbiter = false, tracker = false, library = false, credentials = [], session, harness, route = "primary", routeReason = null, harnessVersion = null, models = null, land = false, skillsDir = null, personaDir = null }) {
|
||||
// adapter variables. In proxy mode the host splits credentials into its
|
||||
// transient spawn environment before writing the remaining variables to session.json.
|
||||
export function buildBundle({ dir, resolved, contract, arbiter = false, tracker = false, library = false, credentials = [], session, harness, route = "primary", routeReason = null, harnessVersion = null, models = null, land = false, skillsDir = null, personaDir = null, proxy = null }) {
|
||||
if (route !== "primary" && route !== "fallback") throw new BundleError(`route ${JSON.stringify(route)} isn't primary or fallback`);
|
||||
const model = sessionModel(resolved, models, route);
|
||||
if (model.provider === "proxy" && !proxy) throw new BundleError("proxy model needs its own session credential", "no-session-credential");
|
||||
if (proxy && (typeof proxy.key !== "string" || !proxy.key || typeof proxy.baseUrl !== "string"
|
||||
|| !/^http:\/\/127\.0\.0\.1:[0-9]+$/.test(proxy.baseUrl))) throw new BundleError("invalid session proxy context");
|
||||
const web = webPlan(resolved, model.harness, harness.builtinWeb());
|
||||
const tools = typedTools({ resolved, arbiter, tracker, library, projects: (session.checkouts ?? []).map((c) => c.project), webTyped: !web.builtin, land });
|
||||
const untyped = untypedActions(resolved, tools);
|
||||
@@ -346,7 +356,7 @@ export function buildBundle({ dir, resolved, contract, arbiter = false, tracker
|
||||
write("tools.json", JSON.stringify(tools, null, 2) + "\n"),
|
||||
];
|
||||
// The module's own files. A name the bundle already wrote refuses at write (flag wx).
|
||||
const own = harness.bundle({ dir, files: { ...files }, session, write, quote });
|
||||
const own = harness.bundle({ dir, files: { ...files }, session: { ...session, ...model }, write, quote, models, proxy });
|
||||
Object.assign(files, own.files);
|
||||
digests.push(...own.digests);
|
||||
const sources = { gate: SOURCES.gate, tools: SOURCES.tools, ...own.sources };
|
||||
@@ -373,6 +383,6 @@ export function buildBundle({ dir, resolved, contract, arbiter = false, tracker
|
||||
sources: Object.fromEntries(Object.entries(sources).map(([k, p]) => [k, { path: p, sha256: sha256(readFileSync(p)) }])),
|
||||
};
|
||||
writeFileSync(files.manifest, JSON.stringify(manifest, null, 2) + "\n", { mode: 0o600, flag: "wx" });
|
||||
const env = harness.env({ files, policy, tools, session: { ...session, ...model } });
|
||||
const env = harness.env({ files, policy, tools, session: { ...session, ...model }, models, proxy });
|
||||
return { manifest, files, policy, tools, env };
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// P3: generate real harness config through buildBundle; credentials never enter disk artifacts.
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdirSync, readFileSync, statSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { buildBundle } from '../src/bundle.mjs';
|
||||
import { HARNESS, REPO_SKILLS, resolvedFor, scratch } from './helpers.mjs';
|
||||
|
||||
const models = {
|
||||
a: { provider: 'proxy', model: 'claude-fixture', route: 'anthropic' },
|
||||
z: { provider: 'proxy', model: 'glm-fixture', route: 'glm' },
|
||||
g: { provider: 'proxy', model: 'gpt-fixture', route: 'codex' },
|
||||
};
|
||||
function build(t, selection, proxy, declared) {
|
||||
const root = scratch(t, 'p332-'), runDir = join(root, 'run');
|
||||
mkdirSync(runDir, { mode: 0o700 });
|
||||
const valid = resolvedFor(t, 'pm');
|
||||
assert.ok(Object.hasOwn(declared, selection), 'fixture selection must exist in declared launch.models');
|
||||
const resolved = { ...valid, vars: { ...valid.vars, harness: 'pi', model: selection } };
|
||||
const bundle = buildBundle({
|
||||
dir: join(runDir, 'bundle'), resolved, models: declared,
|
||||
contract: '# Fixture\n', harness: HARNESS.pi, harnessVersion: 'fixture',
|
||||
skillsDir: REPO_SKILLS, proxy,
|
||||
session: { run: 'r332', launchedBy: 'chief', workspace: join(root, 'ws'),
|
||||
toolSocket: join(runDir, 'tools.sock'), turnMarker: join(runDir, 'turn.done') },
|
||||
});
|
||||
const file = join(runDir, 'home', '.pi', 'agent', 'models.json');
|
||||
return { ...bundle, file, runDir, text: readFileSync(file, 'utf8') };
|
||||
}
|
||||
function assertSafe(value) {
|
||||
if (typeof value === 'string') assert.ok(!value.startsWith('!'), value);
|
||||
else if (value && typeof value === 'object') for (const item of Object.values(value)) assertSafe(item);
|
||||
}
|
||||
|
||||
test('P3 Anthropic route uses messages at proxy root and the declared model id', t => {
|
||||
const b = build(t, 'a', { baseUrl: 'http://127.0.0.1:12345', key: 'fx332-agent-a' }, { a: models.a });
|
||||
const { anthropic } = JSON.parse(b.text).providers;
|
||||
assert.equal(anthropic.api, 'anthropic-messages');
|
||||
assert.equal(anthropic.baseUrl, 'http://127.0.0.1:12345');
|
||||
assert.deepEqual(anthropic.models.map(m => m.id), ['claude-fixture']);
|
||||
assert.equal(b.env.PI_PROVIDER, 'anthropic');
|
||||
assert.equal(b.env.PI_MODEL, 'claude-fixture');
|
||||
});
|
||||
|
||||
test('P3 omitted route refuses proxy-route-missing before model config generation', t => {
|
||||
assert.throws(() => build(t, 'a', { baseUrl: 'http://127.0.0.1:12345', key: 'fx332-agent-a' }, {
|
||||
a: { provider: 'proxy', model: 'claude-fixture' },
|
||||
}), { code: 'proxy-route-missing' });
|
||||
});
|
||||
|
||||
test('P3 GLM route uses completions at proxy /v1 with explicit z.ai compatibility', t => {
|
||||
const b = build(t, 'z', { baseUrl: 'http://127.0.0.1:12345', key: 'fx332-agent-a' }, { z: models.z });
|
||||
const glm = JSON.parse(b.text).providers['mosaic-proxy-glm'];
|
||||
assert.equal(glm.api, 'openai-completions');
|
||||
assert.equal(glm.baseUrl, 'http://127.0.0.1:12345/v1');
|
||||
assert.deepEqual(glm.models.map(m => m.id), ['glm-fixture']);
|
||||
assert.equal(glm.compat.thinkingFormat, 'zai');
|
||||
assert.equal(glm.compat.zaiToolStream, true);
|
||||
assert.equal(b.env.PI_PROVIDER, 'mosaic-proxy-glm');
|
||||
});
|
||||
|
||||
test('P3 GPT route selects a stack-owned provider instead of pi Codex OAuth', t => {
|
||||
const b = build(t, 'g', { baseUrl: 'http://127.0.0.1:12345', key: 'fx332-agent-a' }, { g: models.g });
|
||||
const providers = JSON.parse(b.text).providers;
|
||||
assert.equal(Object.hasOwn(providers, 'openai-codex'), false);
|
||||
const gpt = providers['mosaic-proxy-codex'];
|
||||
assert.equal(gpt.api, 'openai-responses');
|
||||
assert.equal(gpt.baseUrl, 'http://127.0.0.1:12345/v1');
|
||||
assert.deepEqual(gpt.models.map(m => m.id), ['gpt-fixture']);
|
||||
assert.equal(b.env.PI_PROVIDER, 'mosaic-proxy-codex');
|
||||
});
|
||||
|
||||
test('P3 a declared Codex route generates only the single build-time responses provider', t => {
|
||||
const b = build(t, 'g', { baseUrl: 'http://127.0.0.1:12345', key: 'fx332-agent-a' }, {
|
||||
g: models.g,
|
||||
});
|
||||
const providers = JSON.parse(b.text).providers;
|
||||
assert.deepEqual(Object.keys(providers), ['mosaic-proxy-codex']);
|
||||
assert.equal(providers['mosaic-proxy-codex'].api, 'openai-responses');
|
||||
assert.equal(providers['mosaic-proxy-codex'].apiKey, '$ANTHROPIC_API_KEY');
|
||||
});
|
||||
|
||||
test('P3 models file is private, references the one session key, and stores no executable values or keys', t => {
|
||||
const key = 'fx332-agent-a';
|
||||
const b = build(t, 'a', { baseUrl: 'http://127.0.0.1:12345', key }, { a: models.a, z: models.z });
|
||||
const document = JSON.parse(b.text);
|
||||
assert.deepEqual(Object.keys(document.providers).sort(), ['anthropic', 'mosaic-proxy-glm']);
|
||||
for (const provider of Object.values(document.providers)) assert.equal(provider.apiKey, '$ANTHROPIC_API_KEY');
|
||||
assertSafe(document);
|
||||
assert.ok(!b.text.includes(key));
|
||||
assert.equal(b.env.ANTHROPIC_API_KEY, key);
|
||||
assert.equal(statSync(b.file).mode & 0o777, 0o600);
|
||||
for (const dir of ['home', 'home/.pi', 'home/.pi/agent']) assert.equal(statSync(join(b.runDir, dir)).mode & 0o777, 0o700);
|
||||
for (const file of Object.values(b.files)) assert.ok(!readFileSync(file, 'utf8').includes(key), file);
|
||||
assert.ok(!JSON.stringify(b.manifest).includes(key));
|
||||
});
|
||||
|
||||
test('P3 every launch regenerates proxy port, declared models and own key without caching', t => {
|
||||
const first = build(t, 'a', { baseUrl: 'http://127.0.0.1:12345', key: 'fx332-agent-a' }, { a: models.a, z: models.z });
|
||||
const second = build(t, 'a', { baseUrl: 'http://127.0.0.1:23456', key: 'fx332-agent-b' }, {
|
||||
a: { provider: 'proxy', model: 'changed-model-id', route: 'anthropic' },
|
||||
z: { provider: 'proxy', model: 'changed-glm-id', route: 'glm' },
|
||||
});
|
||||
assert.equal(second.env.ANTHROPIC_API_KEY, 'fx332-agent-b');
|
||||
assert.ok(!JSON.stringify(second).includes('fx332-agent-a'));
|
||||
const { providers } = JSON.parse(second.text);
|
||||
assert.deepEqual(Object.keys(providers), ['anthropic', 'mosaic-proxy-glm']);
|
||||
assert.equal(providers.anthropic.baseUrl, 'http://127.0.0.1:23456');
|
||||
assert.deepEqual(providers.anthropic.models.map(m => m.id), ['changed-model-id']);
|
||||
assert.equal(providers['mosaic-proxy-glm'].baseUrl, 'http://127.0.0.1:23456/v1');
|
||||
assert.deepEqual(providers['mosaic-proxy-glm'].models.map(m => m.id), ['changed-glm-id']);
|
||||
assert.equal(JSON.parse(first.text).providers.anthropic.baseUrl, 'http://127.0.0.1:12345');
|
||||
});
|
||||
@@ -327,3 +327,32 @@ organization file or unavailable bus.
|
||||
For the re-pin introducing this kind, Jason enables both channels through
|
||||
plugins.set immediately after activate in Mos's window, then smokes CLI thread
|
||||
listing and Console chat. No development seat performs that live step.
|
||||
|
||||
## Per-session proxy context (P3)
|
||||
|
||||
The launcher's injected `proxy` is either a `{ baseUrl, key }` context supplied by
|
||||
P2, or a factory receiving `{ business, instance }` and returning that context.
|
||||
Checks may repeat before spawn; the factory must return that agent's current
|
||||
credential. P3 does not acquire, issue or revoke keys. No context means existing
|
||||
nonproxy launch behavior; a selected proxy model without context refuses.
|
||||
|
||||
The harness `preflight`, `bundle` and `env` calls receive an optional `proxy`
|
||||
argument; `bundle` and `env` also receive the declared model table. In proxy
|
||||
mode the launcher removes instance provider variables, then separates the
|
||||
harness's credential variables from `harnessEnv` before writing `session.json`.
|
||||
Only the spawned runner's environment receives those variables.
|
||||
|
||||
A proxy `launch.models` entry requires `route: anthropic|glm|codex`. Omission
|
||||
refuses `proxy-route-missing`; unknown routes refuse `proxy-route-unknown`. Other providers
|
||||
retain provider/model-only entries. `launch.account.credential: proxy` names the
|
||||
proxy credential source. Pi needs its own provider key or a supplied proxy key;
|
||||
Claude OAuth alone cannot satisfy pi preflight.
|
||||
|
||||
Pi writes private, fresh run-local `home/.pi/agent/models.json`, with
|
||||
`$ANTHROPIC_API_KEY` references. Anthropic uses messages at the proxy root; GLM
|
||||
uses completions at `/v1` with z.ai compatibility fields. GPT uses the stack-owned
|
||||
`mosaic-proxy-codex` provider, never pi's Codex OAuth provider. Its build-time API
|
||||
value is `openai-responses` per Sage's sage-4331 ruling: both shapes passed the
|
||||
pinned offline probe, so this is a reasoning-based tie-break rather than an
|
||||
evidence-decided pick. Row209 phase A step7 verifies the first real Codex turn.
|
||||
There is no runtime API selection or fallback.
|
||||
@@ -53,8 +53,8 @@ function binary(env) {
|
||||
}
|
||||
|
||||
// A session needs a credential from the host's environment and the binary it runs.
|
||||
export function preflight({ env }) {
|
||||
if (!hasSessionCredential(env)) {
|
||||
export function preflight({ env, proxy = null }) {
|
||||
if (!(typeof proxy?.key === "string" && proxy.key) && !hasSessionCredential(env)) {
|
||||
const names = SESSION_CREDENTIALS.map((set) => set.join(" with ")).join(", ");
|
||||
return { code: "no-session-credential", message: `the bus host has none of ${names}; the bus unit's EnvironmentFile sets one when the bus starts` };
|
||||
}
|
||||
@@ -116,12 +116,13 @@ export function bundle({ dir, files: core, session, write, quote }) {
|
||||
return { files, digests, sources: { claudeGate: GATE, mcpServer: MCP_SERVER, ...UNSLOP } };
|
||||
}
|
||||
|
||||
export function env({ files, policy, session }) {
|
||||
export function env({ files, policy, session, proxy = null }) {
|
||||
return {
|
||||
MOSAIC_TOOLS: claudeBuiltins(policy).join(","),
|
||||
MOSAIC_CLAUDE_SETTINGS: files.settings,
|
||||
MOSAIC_CLAUDE_MCP_CONFIG: files.mcp,
|
||||
MOSAIC_THINKING: session.thinking,
|
||||
...(proxy ? { ANTHROPIC_BASE_URL: proxy.baseUrl, ANTHROPIC_AUTH_TOKEN: proxy.key } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
// Pi extension, which stays in packages/harness (a module can't change that boundary). The
|
||||
// launcher, the bundle and the usage reader call these on the host; the runner gets the answers
|
||||
// as data in session.json. packages/plugins/README.md lists the interface.
|
||||
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { createHash } from "node:crypto";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
export const interfaceVersion = 1;
|
||||
@@ -12,6 +13,10 @@ export const interfaceVersion = 1;
|
||||
const REPO = fileURLToPath(new URL("../../../../", import.meta.url));
|
||||
const PACKAGE = "@earendil-works/pi-coding-agent";
|
||||
const BIN = join(REPO, "node_modules", ".bin", "pi");
|
||||
// Sage sage-4331: reasoning-based tie-break; both pinned-proxy shapes passed.
|
||||
// First real Codex verification remains row209 phase A step7. No runtime switch.
|
||||
const PROXY_CODEX_API = "openai-responses";
|
||||
const PROXY_PROVIDERS = Object.freeze({ anthropic: "anthropic", glm: "mosaic-proxy-glm", codex: "mosaic-proxy-codex" });
|
||||
const EXTENSION = fileURLToPath(new URL("../../../harness/src/pi-extension.mjs", import.meta.url));
|
||||
// Row 66 (#1559): the unslop check, loaded as a second extension. It logs to MOSAIC_UNSLOP_LOG,
|
||||
// which the runner sets, and the runner tells the operator.
|
||||
@@ -30,7 +35,11 @@ export const adapter = () => join(REPO, "adapters", "pi", "adapter.sh");
|
||||
export const levels = () => LEVELS;
|
||||
|
||||
// The session runs pi from the repository's node_modules, which the sandbox mounts.
|
||||
export function preflight() {
|
||||
export function preflight({ env = {}, proxy = null } = {}) {
|
||||
if (!(typeof proxy?.key === "string" && proxy.key)
|
||||
&& !["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "OPENROUTER_API_KEY", "ZAI_API_KEY"].some(name => Boolean(env[name]))) {
|
||||
return { code: "no-session-credential", message: "pi needs its own provider key or a session proxy key" };
|
||||
}
|
||||
if (!existsSync(BIN)) return { code: "harness-binary-missing", message: `pi isn't installed at ${BIN}; run npm ci in the repository` };
|
||||
return null;
|
||||
}
|
||||
@@ -57,16 +66,52 @@ export const builtins = ({ tools }) => [...tools];
|
||||
export const typedName = (name) => name;
|
||||
export const reliesOn = () => RELIES_ON;
|
||||
export const gateTimeout = () => null;
|
||||
export const bundle = () => ({ files: {}, digests: [], sources: { piExtension: EXTENSION, ...UNSLOP } });
|
||||
export function bundle({ dir, models, proxy = null }) {
|
||||
const sources = { piExtension: EXTENSION, ...UNSLOP };
|
||||
if (!proxy) return { files: {}, digests: [], sources };
|
||||
const providers = {};
|
||||
for (const entry of Object.values(models ?? {})) {
|
||||
if (entry.provider !== "proxy") continue;
|
||||
const route = entry.route;
|
||||
if (!Object.hasOwn(PROXY_PROVIDERS, route)) throw new Error("proxy model has no supported route");
|
||||
const provider = PROXY_PROVIDERS[route];
|
||||
if (typeof entry.model !== "string" || !entry.model || entry.model.startsWith("!")) throw new Error("unsafe proxy model id");
|
||||
const api = route === "anthropic" ? "anthropic-messages"
|
||||
: route === "glm" ? "openai-completions" : PROXY_CODEX_API;
|
||||
providers[provider] ??= {
|
||||
baseUrl: proxy.baseUrl + (route === "anthropic" ? "" : "/v1"),
|
||||
api, apiKey: "$ANTHROPIC_API_KEY",
|
||||
...(route === "glm" ? { compat: { thinkingFormat: "zai", zaiToolStream: true } } : {}),
|
||||
models: [],
|
||||
};
|
||||
if (!providers[provider].models.some(model => model.id === entry.model)) {
|
||||
providers[provider].models.push({ id: entry.model, reasoning: true });
|
||||
}
|
||||
}
|
||||
const text = JSON.stringify({ providers }, null, 2) + "\n";
|
||||
// Even an accidental collision between a configured model id and the key fails closed.
|
||||
if (typeof proxy.key !== "string" || !proxy.key || text.includes(proxy.key)) throw new Error("unsafe proxy model configuration");
|
||||
const runDir = dirname(dir);
|
||||
for (const part of ["home", "home/.pi", "home/.pi/agent"]) {
|
||||
const path = join(runDir, part);
|
||||
mkdirSync(path, { recursive: true, mode: 0o700 });
|
||||
const stat = lstatSync(path);
|
||||
if (!stat.isDirectory() || (stat.mode & 0o777) !== 0o700) throw new Error("pi proxy config needs private run-local directories");
|
||||
}
|
||||
const file = join(runDir, "home", ".pi", "agent", "models.json");
|
||||
writeFileSync(file, text, { mode: 0o600, flag: "wx" });
|
||||
return { files: { proxyModels: file }, digests: [["../home/.pi/agent/models.json", createHash("sha256").update(text).digest("hex")]], sources };
|
||||
}
|
||||
|
||||
// The tool limit is the built-ins plus the typed tools, which the extension serves.
|
||||
export function env({ policy, tools, session }) {
|
||||
export function env({ policy, tools, session, proxy = null }) {
|
||||
return {
|
||||
MOSAIC_TOOLS: [...policy.tools, ...tools.map((t) => t.name)].join(","),
|
||||
MOSAIC_EXTENSIONS: [EXTENSION, UNSLOP.unslopExtension].join(","),
|
||||
PI_PROVIDER: session.provider ?? "",
|
||||
PI_PROVIDER: proxy ? PROXY_PROVIDERS[session.proxyRoute] : session.provider ?? "",
|
||||
PI_MODEL: session.model,
|
||||
MOSAIC_THINKING: session.thinking,
|
||||
...(proxy ? { ANTHROPIC_API_KEY: proxy.key } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -97,9 +97,9 @@ test("row 65 claude-code version asks MOSAIC_CLAUDE_BIN when set, else claude on
|
||||
assert.equal(claude.version({ env: { PATH: join(dir, "none") } }), null);
|
||||
});
|
||||
|
||||
test("row 65 pi preflight passes with the repository's pi, and its version is the package pin", async () => {
|
||||
test("row 65 pi preflight refuses missing credentials, and its version is the package pin", async () => {
|
||||
const pi = await load("pi");
|
||||
assert.equal(pi.preflight({ env: {} }), null);
|
||||
assert.equal(pi.preflight({ env: {} }).code, "no-session-credential");
|
||||
const pkg = JSON.parse(readFileSync(join(REPO, "package.json"), "utf8"));
|
||||
assert.equal(pi.version({ env: {} }), pkg.dependencies?.["@earendil-works/pi-coding-agent"] ?? pkg.devDependencies?.["@earendil-works/pi-coding-agent"]);
|
||||
// Row 66: the unslop extension's files, bound read-only at their own paths.
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
// P3: run real harness exports through the plug-in loader, with made-up session keys only.
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { chmodSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { createLoader } from '../src/loader.mjs';
|
||||
import { sessionEnv } from '../../seat/src/session.mjs';
|
||||
|
||||
async function harness(name) {
|
||||
const loader = createLoader({ log() {} });
|
||||
const result = await loader.load({ harness: [name] });
|
||||
assert.deepEqual(result.refused, []);
|
||||
return loader.modules('harness')[0];
|
||||
}
|
||||
function scratch(t) {
|
||||
const root = mkdtempSync(join(tmpdir(), 'p332-'));
|
||||
t.after(() => rmSync(root, { recursive: true, force: true }));
|
||||
return root;
|
||||
}
|
||||
const policy = { tools: [], typed: [] };
|
||||
const session = { model: 'claude-fixture', thinking: 'low' };
|
||||
|
||||
test('P3 Claude env receives the session proxy URL and key, without another agent or host credential', async () => {
|
||||
const claude = await harness('claude-code');
|
||||
const host = { CLAUDE_CODE_OAUTH_TOKEN: 'fx332-host', OPENAI_API_KEY: 'fx332-upstream' };
|
||||
for (const [run, key] of [['r332a', 'fx332-agent-a'], ['r332b', 'fx332-agent-b']]) {
|
||||
const proxy = { baseUrl: 'http://127.0.0.1:12345', key };
|
||||
const env = { ...sessionEnv(host, run, proxy), ...claude.env({ files: {}, policy, session, proxy }) };
|
||||
assert.equal(env.ANTHROPIC_BASE_URL, 'http://127.0.0.1:12345');
|
||||
assert.equal(env.ANTHROPIC_AUTH_TOKEN, key);
|
||||
assert.equal(Object.hasOwn(env, 'CLAUDE_CODE_OAUTH_TOKEN'), false);
|
||||
assert.equal(Object.hasOwn(env, 'OPENAI_API_KEY'), false);
|
||||
const other = run === 'r332a' ? 'fx332-agent-b' : 'fx332-agent-a';
|
||||
assert.ok(!Object.values(env).includes(other));
|
||||
}
|
||||
});
|
||||
|
||||
test('P3 Claude preflight accepts a proxy key without a host provider credential', async t => {
|
||||
const claude = await harness('claude-code');
|
||||
const root = scratch(t), binary = join(root, 'claude');
|
||||
writeFileSync(binary, '#!/bin/sh\nexit 0\n');
|
||||
chmodSync(binary, 0o700);
|
||||
assert.equal(claude.preflight({ env: { MOSAIC_CLAUDE_BIN: binary }, proxy: {
|
||||
baseUrl: 'http://127.0.0.1:12345', key: 'fx332-agent-a',
|
||||
} }), null);
|
||||
});
|
||||
|
||||
test('P3 pi refuses no-session-credential with no provider key', async () => {
|
||||
const pi = await harness('pi');
|
||||
assert.equal(pi.preflight({ env: {} })?.code, 'no-session-credential');
|
||||
});
|
||||
|
||||
test('P3 pi refuses no-session-credential with only a Claude OAuth token', async () => {
|
||||
const pi = await harness('pi');
|
||||
assert.equal(pi.preflight({ env: { CLAUDE_CODE_OAUTH_TOKEN: 'fx332-oauth' } })?.code, 'no-session-credential');
|
||||
});
|
||||
|
||||
test('P3 pi accepts its own provider key or a session proxy key', async () => {
|
||||
const pi = await harness('pi');
|
||||
for (const name of ['ANTHROPIC_API_KEY', 'OPENAI_API_KEY', 'OPENROUTER_API_KEY', 'ZAI_API_KEY']) {
|
||||
assert.equal(pi.preflight({ env: { [name]: 'fx332-own' } }), null, name);
|
||||
}
|
||||
assert.equal(pi.preflight({ env: {}, proxy: { baseUrl: 'http://127.0.0.1:12345', key: 'fx332-agent-a' } }), null);
|
||||
});
|
||||
@@ -253,9 +253,13 @@ export const ENV_ALLOW = Object.freeze([
|
||||
export const SESSION_CREDENTIALS = Object.freeze([["CLAUDE_CODE_OAUTH_TOKEN"], ["ANTHROPIC_API_KEY"], ["ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_BASE_URL"]].map((set) => Object.freeze(set)));
|
||||
export const hasSessionCredential = (env) => SESSION_CREDENTIALS.some((set) => set.every((k) => Boolean(env[k])));
|
||||
|
||||
export function sessionEnv(env, run) {
|
||||
// Used again at the serialization boundary: provider credentials are transient.
|
||||
export const isProviderVariable = (name) => name.startsWith("ANTHROPIC_")
|
||||
|| ["CLAUDE_CODE_OAUTH_TOKEN", "OPENAI_API_KEY", "OPENROUTER_API_KEY", "ZAI_API_KEY"].includes(name);
|
||||
|
||||
export function sessionEnv(env, run, proxy = null) {
|
||||
const out = {};
|
||||
for (const k of ENV_ALLOW) if (env[k] !== undefined) out[k] = env[k];
|
||||
for (const k of ENV_ALLOW) if (env[k] !== undefined && !(proxy && isProviderVariable(k))) out[k] = env[k];
|
||||
out.PATH = [join(REPO, "node_modules", ".bin"), env.PATH ?? "/usr/bin:/bin"].join(":");
|
||||
out.MOSAIC_RUN_ID = run;
|
||||
return out;
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// P3: catches the named OAuth-leak mutant and reuse of another session's key.
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { ENV_ALLOW, sessionEnv } from '../src/session.mjs';
|
||||
|
||||
const instance = {
|
||||
HOME: '/fixture-home', USER: 'fixture', PATH: '/usr/bin', LANG: 'C',
|
||||
CLAUDE_CODE_OAUTH_TOKEN: 'fx332-oauth', ANTHROPIC_API_KEY: 'fx332-anthropic',
|
||||
ANTHROPIC_AUTH_TOKEN: 'fx332-auth', ANTHROPIC_BASE_URL: 'http://upstream.invalid',
|
||||
OPENAI_API_KEY: 'fx332-openai', OPENROUTER_API_KEY: 'fx332-router', ZAI_API_KEY: 'fx332-zai',
|
||||
MOSAIC_CLAUDE_BIN: '/fixture/claude', GITEA_TOKEN: 'fx332-gitea',
|
||||
};
|
||||
const forbidden = ['CLAUDE_CODE_OAUTH_TOKEN', 'ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN',
|
||||
'ANTHROPIC_BASE_URL', 'OPENAI_API_KEY', 'OPENROUTER_API_KEY', 'ZAI_API_KEY'];
|
||||
|
||||
test('P3 proxy sessionEnv removes every instance provider credential without widening ENV_ALLOW', () => {
|
||||
const original = [...ENV_ALLOW];
|
||||
const env = sessionEnv(instance, 'r332a', { baseUrl: 'http://127.0.0.1:12345', key: 'fx332-agent-a' });
|
||||
for (const name of forbidden) assert.equal(Object.hasOwn(env, name), false, name);
|
||||
assert.equal(env.USER, 'fixture');
|
||||
assert.equal(env.MOSAIC_CLAUDE_BIN, '/fixture/claude');
|
||||
assert.equal(Object.hasOwn(env, 'GITEA_TOKEN'), false);
|
||||
assert.equal(env.MOSAIC_RUN_ID, 'r332a');
|
||||
assert.deepEqual([...ENV_ALLOW], original);
|
||||
});
|
||||
|
||||
test('P3 no proxy retains the full existing allowlist and run environment', () => {
|
||||
const env = sessionEnv(instance, 'r332b');
|
||||
assert.deepEqual(env, {
|
||||
HOME: '/fixture-home', USER: 'fixture', LANG: 'C',
|
||||
CLAUDE_CODE_OAUTH_TOKEN: 'fx332-oauth', ANTHROPIC_API_KEY: 'fx332-anthropic',
|
||||
ANTHROPIC_AUTH_TOKEN: 'fx332-auth', ANTHROPIC_BASE_URL: 'http://upstream.invalid',
|
||||
OPENAI_API_KEY: 'fx332-openai', OPENROUTER_API_KEY: 'fx332-router', ZAI_API_KEY: 'fx332-zai',
|
||||
MOSAIC_CLAUDE_BIN: '/fixture/claude', PATH: env.PATH, MOSAIC_RUN_ID: 'r332b',
|
||||
});
|
||||
assert.ok(env.PATH.endsWith('node_modules/.bin:/usr/bin'));
|
||||
});
|
||||
Reference in new issue
Block a user