WIP row329: preserve P1 draft before required base rebase (#1827)

This commit is contained in:
jason.woltje committed 2026-10-24 18:30:11 -05:00
1 parent 669cdf42c4
commit 94cde633e2
45 files changed
+1687 -5

No files matched your search

+2
View File
@@ -5574,3 +5574,5 @@ claim. Receipts and limitations in agents/lena/work/queue-296/BUILD.md and run-r
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.
+265
View File
@@ -0,0 +1,265 @@
# Row 329 P1 model proxy — Magica
Base: 7fe32d722637418268c763fac9220827e9a1d812; branch magica/row329.
Authority: sage-4229; P1 brief at docs/plans/2026-10-24_model-proxy-rows.md,
mos-1600 and mos-1601. The brief overrides the design. Boyd first review,
Dewey landing review. No live proxy/config/key reads or changes.
## Plan and boundaries
1. Measure the latest GitHub release assets and run the pinned binary and panel
against stand-ins under unshare -rn. Send sub-path/sandbox answers to Sage
immediately, including failures; no unsandboxed Console implementation.
2. Build packages/model-proxy: pinned verified fetch, strict settings validation,
exclusive initialization/template, loopback-only management client, verified
unit runner. Keep network injection in tests local and unavailable in CLI.
3. Wire proxy.json loading beside config.json; wire the human-only CLI and
path-bound bus transport; render proxy unit and conditional bus dependencies.
4. Add tests for each refusal code and acceptance conditions. Ask Sage for CLEAR
before any test run; fresh short TMPDIR, absolute nice 19, own dependencies.
5. Run the approved gate, record counts/refusals, prepare review receipts.
Commit only after applicable checks pass; no push/queue review until authorized.
No config.json writes. Proxy.json and initial secret/config use exclusive create.
No host proxy or real instance reads, stats, probes or calls. Acceptance differs
from row96's wrapper: no host guard introspection at all. Every runtime file is
under a fresh shell mktemp -d /mnt/storage/scratch/tmp/XXXXXX.
## Start receipts
New worktree /home/jwoltje/.t3/worktrees/mosaic-stack/magica-row329.
queue next magica identified row329 (view-unknown warning reported by queue).
Requested queue start refused exit4: unknown verb start. Sage notified magica-0017;
no substitute queue mutation performed pending lead correction.
GitHub latest release redirects: CLIProxyAPI v8.0.42, panel v1.26.7.
Download hashes in download-sha256.txt (release bytes measured immediately).
Acceptance pending; these are candidates, not yet a passing PIN.
## Acceptance findings (first complete probe)
Measured CLIProxyAPI 8.0.42, not row96's 8.0.27; panel 1.26.7.
Both latest release tags were discovered through github.com/releases/latest
redirects, then explicit release asset URLs were fetched. No GitHub API token,
source clone, package registry or other external service used.
- Empty access.api-keys opens /v1/models to a request with no key (200).
Sentinel is mandatory; P2 must refuse when it is missing.
- GET /v1/models with fixture client key returned 200, upstream call delta 0.
- Wrong key: 401 body {"error":"Invalid API key"}; missing key: 401 body
{"error":"Missing API key"}. A removed client key with a sentinel remaining
gets the same wrong-key refusal. No response-shape transformation.
- Claude Code with wrong and revoked fixture keys each timed out at45s,
rc124, empty stdout/stderr. This records the observation only; it does not
establish the response body as the cause.
- v8 api-key list is an array of strings. A PUT of per-key model objects is
refused (400), so no per-client-key model restriction through this API.
- Panel's default API base ignores /proxy. With Custom Connection URL set to
http://127.0.0.1:<forward>/proxy, prefix strip works; management/model requests
stay under /proxy. Plain-mode dashboard signs in and displays state.
- With CSP sandbox allow-scripts allow-forms, no allow-same-origin, localStorage
throws SecurityError in migratePlaintextKeys/restoreSession. Login fails with
no management request. This is P4's stop condition, sent to Sage as magica-0018
and0019. P1 ships no Console forward and proposes no sandbox weakening.
- OAuth starts produce Claude redirect_uri http://localhost:54545/callback and
Codex http://localhost:1455/auth/callback, not a /proxy/ URL. No external OAuth
URL was followed. CLI login fallback in Jason's own shell is recorded below
after checking the pinned binary's help.
Plain text complete output is acceptance-probe-3.txt (all runtime paths under
fresh /mnt/storage/scratch/tmp, nice19). acceptance-probe-1/2 preserve the
initial baseline and corrected prefix-only forward. T3 preview status/open
explicitly reported no automation host; headless Chromium ran inside unshare
instead. No live proxy introspection from row96's wrapper was reused.
## Implementation choices
JSON is used as YAML-compatible initial config to avoid interpolation of key
values. Model catalog uses -local-model at unit start. Pinned panel is supplied
via MANAGEMENT_STATIC_PATH. Init holds an exclusive per-config lock and a
loopback port reservation while creating files. Reentry refuses, no secret is
returned. The source definition of proxy is strictly proxy.json; config.json
schema and bytes remain unchanged. New tests were written before source; their
first execution waits for Sage's slot CLEAR, which overrides normal per-step
TDD timing. No tests have run as of this entry.
### Acceptance correction (probe4; append-only clarification)
Probe3's immediate revoked-key request was 200, not401; its per-key-object PUT
was422, not400. The earlier summary above overstated both. Probe4 measures the
propagation explicitly: after PUT([sentinel]) the removed key got200 then401 at
105ms (100ms poll). A later request is401 {"error":"Invalid API key"}. GET list
alone is not proof of enforcement at that instant. Sent to Sage as magica-0020
for P2. Empty-list200 and initial wrong/missing401 observations stand.
The sandbox's explicit fetch issues OPTIONS with Origin:null and no SameSite=Lax
cookie, then fails. This is an additional obstacle beyond the panel's own
storage failure; it does not weaken the P4 stop condition. Through the prefix
forward, /proxy/callback with a dummy code gives400 and
/proxy/auth/callback gives404. The OAuth start URLs advertise the separate
localhost54545/1455 listeners. This acceptance does not complete external OAuth
or claim the forwarded400 is a completed Claude login.
Sage-4233 corrected queue start to queue move329 in-progress using the same op.
The authorized move is running without a timeout. Row328 holds the test slot;
all test execution remains paused. Sage requires rebase onto5e77cdd09 before
the builder gate; static rebase will preserve these worktree changes.
## Rebase receipt
Before any test run, saved all tracked/untracked row329 changes to a dedicated
Git stash, rebased the empty branch onto5e77cdd09, reapplied and dropped only that
stash. Git auto-merged broker.mjs and runtime.mjs; no conflict or source was
lost. Base is now5e77cdd09. No row329 commit yet (applicable tests await CLEAR).
Queue receipt: ok magica-329-start-0001 rev4014 row329 briefed->in-progress.
The bus constructs the management client only after Store's per-data-root writer
lock is held. Its private key never enters process IPC, a CLI return, a broker
answer or a session. P2 may consume runtime.modelProxy inside the bus. Init's
returned contract is only port/binary. Without proxy.json existing boot options
and unit renders retain the base behavior.
The original probe1 source is retained as acceptance-probe-1.mjs, matching the
initial baseline tool invocation; the current acceptance.mjs includes the
explicit sub-path interaction, enforcement-timing and Claude Code follow-ups.
Probe1's output remains unchanged. This preserves Sage-4233's stop evidence.
Static syntax checks (node --check on new package source/tests, new CLI/bus files,
touched bus source and loader; bash -n on wrapper/unit/instance scripts) passed.
These are syntax-only, no module execution or test run. No npm has run yet.
## Final acceptance plain-text output
The command was `bash agents/magica/work/queue-329/acceptance.sh <download-root>`;
wrapper fresh TMPDIR and unshare-rn; no test slot used. Actual output follows.
```text
P1 isolated acceptance root /mnt/storage/scratch/tmp/3mOycq nice 19
binary sha256 74661f8cf949977870231359e8bf4344e07946766a673c08fa25a3e5f72fc95e
pinned panel served 200 8c9c6b7d3fb311e6aad5d06e1993e53a2d28389e640448a1bc6304ed20b057b5
pinned login flags -claude-login | -codex-login | -no-browser
wrong/missing key models wrong {"status":401,"body":"{\"error\":\"Invalid API key\"}"}
wrong/missing key models missing {"status":401,"body":"{\"error\":\"Missing API key\"}"}
models local 200 upstream delta 0 body length 1739
initial key list {"status":200,"body":"[\"p1-client\"]"}
set empty {"status":200,"body":"{\"config-version\":8,\"status\":\"ok\"}"}
empty list settled {"elapsedMs":3,"statuses":[200],"body":"[models]"}
empty list missing key status 200
restore list {"status":200,"body":"{\"config-version\":8,\"status\":\"ok\"}"}
restored list settled wrong key {"elapsedMs":104,"statuses":[200,401],"body":"{\"error\":\"Invalid API key\"}"}
revoke client {"status":200,"body":"{\"config-version\":8,\"status\":\"ok\"}"}
revoked key settled {"elapsedMs":103,"statuses":[200,401],"body":"{\"error\":\"Invalid API key\"}"}
revoked key models {"status":401,"body":"{\"error\":\"Invalid API key\"}"}
per-key model object attempt {"status":422,"body":"{\"error\":\"invalid_config\",\"message\":\"parse config payload: yaml: unmarshal errors:\\n line 12: cannot unmarshal !!map into string\"}"}
final restored list settled {"elapsedMs":2,"statuses":[401],"body":"{\"error\":\"Invalid API key\"}"}
v8 key list {"status":200,"body":"[\"p1-client\"]"}
oauth start anthropic {"status":200,"body":"{\"state\":\"778dec0d73991fed459dc3fdbdd2c6ae\",\"status\":\"ok\",\"url\":\"https://claude.ai/oauth/authorize?client_id=9d1c250a-e61b-44d9-88ed-5944d1962f5e\\u0026code=true\\u0026code_challenge=0K5PIZjU0CLqYa5fwIBFgBU3UsyX9pcOh_FQMCKAaKI\\u0026code_challenge_method=S256\\u0026redirect_uri=http%3A%2F%2Flocalhost%3A54545%2Fcallback\\u0026response_type=code\\u0026scope=user%3Aprofile+user%3Ainference+user%3Asessions%3Aclaude_code+user%3Amcp_servers+user%3Afile_upload\\u0026state=778dec0d73991fed459dc3fdbdd2c6ae\"}"}
oauth start codex {"status":200,"body":"{\"state\":\"4855489df4fa7e17b77d780492a4225e\",\"status\":\"ok\",\"url\":\"https://auth.openai.com/oauth/authorize?client_id=app_EMoamEEZ73f0CkXaXp7hrann\\u0026code_challenge=dHQyyXv-Wf89z_v1sy1ts4cnFwrKNBHw2BphTLjBBd4\\u0026code_challenge_method=S256\\u0026codex_cli_simplified_flow=true\\u0026id_token_add_organizations=true\\u0026prompt=login\\u0026redirect_uri=http%3A%2F%2Flocalhost%3A1455%2Fauth%2Fcallback\\u0026response_type=code\\u0026scope=openid+email+profile+offline_access\\u0026state=4855489df4fa7e17b77d780492a4225e\"}"}
panel plain initial {"result":{"type":"object","value":{"text":"CLI\nPROXY\nAPI\nCLI Proxy API Management Center\nEnglish\nConnect to CLI Proxy API v8 or later\nCurrent URL\nhttp://127.0.0.1:18319\nThe system will automatically use the current URL for connection\nCustom Connection URL:\nManagement Key:\nRemember password\nLogin","inputs":[{"type":"checkbox","placeholder":"","value":"on"},{"type":"password","placeholder":"Enter the management key","value":""},{"type":"checkbox","placeholder":"","value":"on"}],"buttons":["English","","Login"]}}}
panel plain storage {"result":{"type":"object","value":{"available":true}}}
panel plain login interaction {"result":{"type":"object","value":[{"type":"checkbox","value":"on"},{"type":"text","value":"http://127.0.0.1:18319/proxy"},{"type":"password","value":"[fixture]"},{"type":"checkbox","value":"on"}]}}
panel plain after login {"result":{"type":"string","value":"Success:\nConnected\nCPAMC\nCLI Proxy API Console\nOPERATE\nDashboard\nQuick Start\nGATEWAY\nAI Providers\nAuth Files\nAuthentication files: 0\nOAuth Login\nOBSERVE\nQuota Management\nLogs Viewer\nCONTROL\nConfig Panel\nPlugins\nPlugin Store\nManagement Center Info\nQuiet for now.\n\nv8.0.42 · Connected\n\nManage providers\nInspect logs\n→\nREQUESTS HANDLED\nLIVE\n0\nRolling 3h 20m window · 10-minute buckets\nSuccess\n0\nFailure\n0\nSuccess rate\n—\nAcross 0 requests in the window\nCredentials\n0\n0 active · 0 not serving\nProvider keys\n1\nAPI keys configured across all brands\nModels\n19\nServed through the proxy endpoint\nLIVE TRAFFIC\nThroughput, ten minutes at a time\n\nSuccess and failure counts per 10-minute bucket across the last 3h 20m. Hover a column for its exact split.\n\nSuccess\n0\nFailure\n0\n4\n3\n2\n1\n0\n\nNo requests\n\n11:10\n12:40\n14:20\nData table\nFLEET\nEvery provider, side by side\n\nVolume and success rate per provider over the same window.\n\n01\nClaude\nCredentials: 1\n0\nrequests\n—\nHEALTH\nCredential status\n\nNo credential files found.\n\nRUNTIME\nConfiguration at a glance\nRouting strategy\nfill-first (prioritize)\nRequest retry\n0\nManagement keys\n1\nServer version\n8.0.42\nBuild date\n10/24/2026\nUpstream proxy\n—\nDebug\nNo\nFile logging\nYes\nRequest log\nNo\nWebSocket auth\nYes\nModel prefix\nNo\nEdit configuration →\nNEXT\nWhere to go from here\nAI Providers\nAdd API keys, set priorities, and test brands.\n→\nAuth Files\nUpload, refresh, and disable OAuth credentials.\n→\nConfig Panel\nTune routing, retries, logging, and access.\n→\nQuota Management\nCheck remaining quota per credential.\n→\nLogs Viewer\nTail requests and download error reports.\n→\nManagement Center Info\nVersion details and management center info.\n→"}}
panel plain requests [{"method":"GET","path":"/proxy/management.html","origin":null,"cookie":false},{"method":"GET","path":"/proxy/v8/management/config","origin":null,"cookie":true},{"method":"GET","path":"/proxy/v1/models","origin":null,"cookie":true},{"method":"GET","path":"/proxy/v8/management/observability/usage/api-keys","origin":null,"cookie":true},{"method":"GET","path":"/proxy/v8/management/credentials","origin":null,"cookie":true},{"method":"GET","path":"/proxy/v8/management/plugins","origin":null,"cookie":true},{"method":"GET","path":"/proxy/v8/management/credentials","origin":null,"cookie":true}]
panel plain exceptions []
panel sandbox initial {"result":{"type":"object","value":{"text":"CLI\nPROXY\nAPI\nCLI Proxy API Management Center\nEnglish\nConnect to CLI Proxy API v8 or later\nCurrent URL\nhttp://127.0.0.1:18319\nThe system will automatically use the current URL for connection\nCustom Connection URL:\nManagement Key:\nRemember password\nLogin","inputs":[{"type":"checkbox","placeholder":"","value":"on"},{"type":"password","placeholder":"Enter the management key","value":""},{"type":"checkbox","placeholder":"","value":"on"}],"buttons":["English","","Login"]}}}
panel sandbox storage {"result":{"type":"object","value":{"error":"SecurityError","message":"Failed to read the 'localStorage' property from 'Window': The document is sandboxed and lacks the 'allow-same-origin' flag."}}}
panel sandbox login interaction {"result":{"type":"object","value":[{"type":"checkbox","value":"on"},{"type":"text","value":"http://127.0.0.1:18319/proxy"},{"type":"password","value":"[fixture]"},{"type":"checkbox","value":"on"}]}}
panel sandbox after login {"result":{"type":"string","value":"Error:\nLogin failed: Connection failed, please check the server address, Management API, and management key\nCLI\nPROXY\nAPI\nCLI Proxy API Management Center\nEnglish\nConnect to CLI Proxy API v8 or later\nCurrent URL\nhttp://127.0.0.1:18319/proxy\nThe system will automatically use the current URL for connection\nCustom Connection URL:\nCustom Connection URL:\nBy default the current URL is used. Override it here if needed.\nManagement Key:\nRemember password\nLogin\nConnection failed, please check the server address, Management API, and management key"}}
panel sandbox requests [{"method":"GET","path":"/proxy/management.html","origin":null,"cookie":true}]
panel sandbox exceptions ["Uncaught (in promise) SecurityError: Failed to read the 'localStorage' property from 'Window': The document is sandboxed and lacks the 'allow-same-origin' flag.\n at Array.forEach (<anonymous>)\n at Object.migratePlaintextKeys (http://127.0.0.1:18319/proxy/management.html:20:667941)\n at http://127.0.0.1:18319/proxy/management.html:28:32103\n at restoreSession (http://127.0.0.1:18319/proxy/management.html:28:32640)\n at http://127.0.0.1:18319/proxy/management.html:28:38065\n at http://127.0.0.1:18319/proxy/management.html:28:38198\n at Uc (http://127.0.0.1:18319/proxy/management.html:16:92011)\n at kl (http://127.0.0.1:18319/proxy/management.html:16:107191)\n at Ol (http://127.0.0.1:18319/proxy/management.html:16:107075)\n at kl (http://127.0.0.1:18319/proxy/management.html:16:107956)"]
sandbox explicit null-origin fetch {"result":{"type":"object","value":{"error":"TypeError","message":"Failed to fetch"}}}
sandbox explicit requests [{"method":"GET","path":"/proxy/management.html","origin":null,"cookie":true},{"method":"OPTIONS","path":"/proxy/v8/management/config/access/api-keys","origin":"null","cookie":false}]
forward callback /proxy/callback?code=fixture 400
forward callback /proxy/auth/callback?code=fixture 404
Claude Code wrong/revoked key wrong {"code":124,"signal":null} output ""
```
The pinned help confirms `-claude-login`, `-codex-login`, `-no-browser`.
For Jason alone, in his own shell at cutover:
```text
<proxy.binary> -config <dataRoot>/proxy/config.yaml -claude-login -no-browser
<proxy.binary> -config <dataRoot>/proxy/config.yaml -codex-login -no-browser
```
Neither command was run here. External OAuth completion remains unmeasured.
The latest release8.0.42 passed the stand-in questions with the recorded negative
panel-sandbox and revocation-lag answers; PIN selects8.0.42, not8.0.27.
Final acceptance completed rc0. The captured output above was copied just before
its last child finished; the remaining final lines are:
```text
Claude Code wrong/revoked key p1-revoked {"code":124,"signal":null} output ""
acceptance probes complete
```
Final acceptance also confirms /management.html returns200 with the exact panel
SHA256 from PIN when MANAGEMENT_STATIC_PATH points at the pinned static directory.
All runtime files remain in the fresh acceptance root; no binaries are in Git.
## Planned builder gate (awaiting CLEAR)
Own offline npm ci --ignore-scripts --no-audit --no-fund at root and packages/webui
(no registry network; only the authorized pinned GitHub fetch used network).
New model-proxy package whole; bus and CLI suites whole because their trusted
transport/boot code changes; scripts/test-config.sh. Every test file is run
whole, serialized, with a fresh shell mktemp short TMPDIR and absolute nice19.
Every scripts/test-*.sh invocation uses timeout-k10. Stop on any old test red
before rerun, even if likely timing. Refusal inventory is refusal-inventory.txt.
The landing gate still belongs to Sage and covers every package but queue and
every scripts/test-*.sh.
No old test has been edited. New tests are only model-proxy/tests/* and
bus/tests/model-proxy.test.mjs and cli/tests/model-proxy.test.mjs.
Static closure scan found two integration requirements before testing:
- stack-copy.sh now includes model-proxy/src and PIN, since CLI/runtime import
them. No new scripts are added to COPY_PATHS, preserving the pinned-copy
scripts-list assertion. The proxy unit renderer accepts the same optional
--copy/--copies concept as bus-service, and its runner then comes from that copy.
- historical stack-release fixture copies intentionally hold the current loader
without every current package. The loader imports model-proxy only if an
actual sidecar lstat succeeds. Absence needs no new dependency; malformed,
broken-symlink or unreadable present sidecar still refuses. New regression
covers this using a scratch historical loader with no model-proxy package.
No old fixture or assertion was edited for either requirement.
The builder gate includes webui/tests/pinned-copy.test.mjs whole for the
stack-copy closure change, and test-federation-acceptance.sh for the CLI suite's
listed script-only skip. No webui full-suite run is requested by P1; Sage's
landing gate covers it. Gate driver is gate.py, inert until invoked after CLEAR.
It forces npm offline throughout, per-child nice19, per-command timeout-k10,
records rc/nice/elapsed/log and stops on any red. Its first invocation has not
occurred. Initial development runs will be the new files only; a new-file failure
is placed/fixed in its new source, never by weakening an old assertion.
The management client uses node:http to the fixed numeric127.0.0.1 address,
not an ambient fetch dispatcher; HTTP_PROXY or a global fetch override cannot
redirect its Authorization header. A new regression replaces global fetch with
a throwing function while the real local management exchange remains successful.
The downloader's separate manual redirect handling enforces GitHub release hosts.
Static pre-gate review complete; source/test syntax and git diff--check clean.
Key validation includes HTTP Authorization-header validity before initial files
are created, so a key the HTTP client cannot send never leaves partial init state.
This adds no new refusal code (proxy-key-invalid / proxy-client-invalid).
All30 new refusal codes have named coverage in new tests; execution is pending.
No old tests changed; no npm/test runs; no commit/push yet. At this point the
remaining work is the slot-gated execution and any justified fixes/review handoff.
## Gate authorization and final-base rebase
Sage-4240 CLEAR at19:40:22Z: whole model-proxy, bus and CLI suites,
webui pinned-copy, test-config.sh and federation acceptance. Rebase onto8bb5b3c73
first; row328 changed bus/tests/library-credential.test.mjs. WIP commit explicitly
authorized for this preservation/rebase despite the ordinary green-before-commit
rule. No stash used for this rebase. Earlier stash had already been auto-dropped
by stash pop: subject On magica/row329: magica-row329-rebase-static, object
4e0e3137ef17453ddbbcb8a472b20906d1435eb4. Sage notified magica-0022; no other
stash touched and no subsequent stash operations.
Mos-1602: the /proxy/ panel forward remains pending P4/Jason's subdomain decision
(Vikunja624). The acceptance baseline only demonstrates explicit prefix routing
outside the required sandbox; it is no assertion of a working Console panel.
No CORS loosening, unsandboxed forward or panel JS on the Console origin ships.
The actual sandbox-negative assertions/evidence are unchanged.
@@ -0,0 +1,40 @@
P1 isolated acceptance root /mnt/storage/scratch/tmp/3mOycq nice 19
binary sha256 74661f8cf949977870231359e8bf4344e07946766a673c08fa25a3e5f72fc95e
pinned panel served 200 8c9c6b7d3fb311e6aad5d06e1993e53a2d28389e640448a1bc6304ed20b057b5
pinned login flags -claude-login | -codex-login | -no-browser
wrong/missing key models wrong {"status":401,"body":"{\"error\":\"Invalid API key\"}"}
wrong/missing key models missing {"status":401,"body":"{\"error\":\"Missing API key\"}"}
models local 200 upstream delta 0 body length 1739
initial key list {"status":200,"body":"[\"p1-client\"]"}
set empty {"status":200,"body":"{\"config-version\":8,\"status\":\"ok\"}"}
empty list settled {"elapsedMs":3,"statuses":[200],"body":"[models]"}
empty list missing key status 200
restore list {"status":200,"body":"{\"config-version\":8,\"status\":\"ok\"}"}
restored list settled wrong key {"elapsedMs":104,"statuses":[200,401],"body":"{\"error\":\"Invalid API key\"}"}
revoke client {"status":200,"body":"{\"config-version\":8,\"status\":\"ok\"}"}
revoked key settled {"elapsedMs":103,"statuses":[200,401],"body":"{\"error\":\"Invalid API key\"}"}
revoked key models {"status":401,"body":"{\"error\":\"Invalid API key\"}"}
per-key model object attempt {"status":422,"body":"{\"error\":\"invalid_config\",\"message\":\"parse config payload: yaml: unmarshal errors:\\n line 12: cannot unmarshal !!map into string\"}"}
final restored list settled {"elapsedMs":2,"statuses":[401],"body":"{\"error\":\"Invalid API key\"}"}
v8 key list {"status":200,"body":"[\"p1-client\"]"}
oauth start anthropic {"status":200,"body":"{\"state\":\"778dec0d73991fed459dc3fdbdd2c6ae\",\"status\":\"ok\",\"url\":\"https://claude.ai/oauth/authorize?client_id=9d1c250a-e61b-44d9-88ed-5944d1962f5e\\u0026code=true\\u0026code_challenge=0K5PIZjU0CLqYa5fwIBFgBU3UsyX9pcOh_FQMCKAaKI\\u0026code_challenge_method=S256\\u0026redirect_uri=http%3A%2F%2Flocalhost%3A54545%2Fcallback\\u0026response_type=code\\u0026scope=user%3Aprofile+user%3Ainference+user%3Asessions%3Aclaude_code+user%3Amcp_servers+user%3Afile_upload\\u0026state=778dec0d73991fed459dc3fdbdd2c6ae\"}"}
oauth start codex {"status":200,"body":"{\"state\":\"4855489df4fa7e17b77d780492a4225e\",\"status\":\"ok\",\"url\":\"https://auth.openai.com/oauth/authorize?client_id=app_EMoamEEZ73f0CkXaXp7hrann\\u0026code_challenge=dHQyyXv-Wf89z_v1sy1ts4cnFwrKNBHw2BphTLjBBd4\\u0026code_challenge_method=S256\\u0026codex_cli_simplified_flow=true\\u0026id_token_add_organizations=true\\u0026prompt=login\\u0026redirect_uri=http%3A%2F%2Flocalhost%3A1455%2Fauth%2Fcallback\\u0026response_type=code\\u0026scope=openid+email+profile+offline_access\\u0026state=4855489df4fa7e17b77d780492a4225e\"}"}
panel plain initial {"result":{"type":"object","value":{"text":"CLI\nPROXY\nAPI\nCLI Proxy API Management Center\nEnglish\nConnect to CLI Proxy API v8 or later\nCurrent URL\nhttp://127.0.0.1:18319\nThe system will automatically use the current URL for connection\nCustom Connection URL:\nManagement Key:\nRemember password\nLogin","inputs":[{"type":"checkbox","placeholder":"","value":"on"},{"type":"password","placeholder":"Enter the management key","value":""},{"type":"checkbox","placeholder":"","value":"on"}],"buttons":["English","","Login"]}}}
panel plain storage {"result":{"type":"object","value":{"available":true}}}
panel plain login interaction {"result":{"type":"object","value":[{"type":"checkbox","value":"on"},{"type":"text","value":"http://127.0.0.1:18319/proxy"},{"type":"password","value":"[fixture]"},{"type":"checkbox","value":"on"}]}}
panel plain after login {"result":{"type":"string","value":"Success:\nConnected\nCPAMC\nCLI Proxy API Console\nOPERATE\nDashboard\nQuick Start\nGATEWAY\nAI Providers\nAuth Files\nAuthentication files: 0\nOAuth Login\nOBSERVE\nQuota Management\nLogs Viewer\nCONTROL\nConfig Panel\nPlugins\nPlugin Store\nManagement Center Info\nQuiet for now.\n\nv8.0.42 · Connected\n\nManage providers\nInspect logs\n→\nREQUESTS HANDLED\nLIVE\n0\nRolling 3h 20m window · 10-minute buckets\nSuccess\n0\nFailure\n0\nSuccess rate\n—\nAcross 0 requests in the window\nCredentials\n0\n0 active · 0 not serving\nProvider keys\n1\nAPI keys configured across all brands\nModels\n19\nServed through the proxy endpoint\nLIVE TRAFFIC\nThroughput, ten minutes at a time\n\nSuccess and failure counts per 10-minute bucket across the last 3h 20m. Hover a column for its exact split.\n\nSuccess\n0\nFailure\n0\n4\n3\n2\n1\n0\n\nNo requests\n\n11:10\n12:40\n14:20\nData table\nFLEET\nEvery provider, side by side\n\nVolume and success rate per provider over the same window.\n\n01\nClaude\nCredentials: 1\n0\nrequests\n—\nHEALTH\nCredential status\n\nNo credential files found.\n\nRUNTIME\nConfiguration at a glance\nRouting strategy\nfill-first (prioritize)\nRequest retry\n0\nManagement keys\n1\nServer version\n8.0.42\nBuild date\n10/24/2026\nUpstream proxy\n—\nDebug\nNo\nFile logging\nYes\nRequest log\nNo\nWebSocket auth\nYes\nModel prefix\nNo\nEdit configuration →\nNEXT\nWhere to go from here\nAI Providers\nAdd API keys, set priorities, and test brands.\n→\nAuth Files\nUpload, refresh, and disable OAuth credentials.\n→\nConfig Panel\nTune routing, retries, logging, and access.\n→\nQuota Management\nCheck remaining quota per credential.\n→\nLogs Viewer\nTail requests and download error reports.\n→\nManagement Center Info\nVersion details and management center info.\n→"}}
panel plain requests [{"method":"GET","path":"/proxy/management.html","origin":null,"cookie":false},{"method":"GET","path":"/proxy/v8/management/config","origin":null,"cookie":true},{"method":"GET","path":"/proxy/v1/models","origin":null,"cookie":true},{"method":"GET","path":"/proxy/v8/management/observability/usage/api-keys","origin":null,"cookie":true},{"method":"GET","path":"/proxy/v8/management/credentials","origin":null,"cookie":true},{"method":"GET","path":"/proxy/v8/management/plugins","origin":null,"cookie":true},{"method":"GET","path":"/proxy/v8/management/credentials","origin":null,"cookie":true}]
panel plain exceptions []
panel sandbox initial {"result":{"type":"object","value":{"text":"CLI\nPROXY\nAPI\nCLI Proxy API Management Center\nEnglish\nConnect to CLI Proxy API v8 or later\nCurrent URL\nhttp://127.0.0.1:18319\nThe system will automatically use the current URL for connection\nCustom Connection URL:\nManagement Key:\nRemember password\nLogin","inputs":[{"type":"checkbox","placeholder":"","value":"on"},{"type":"password","placeholder":"Enter the management key","value":""},{"type":"checkbox","placeholder":"","value":"on"}],"buttons":["English","","Login"]}}}
panel sandbox storage {"result":{"type":"object","value":{"error":"SecurityError","message":"Failed to read the 'localStorage' property from 'Window': The document is sandboxed and lacks the 'allow-same-origin' flag."}}}
panel sandbox login interaction {"result":{"type":"object","value":[{"type":"checkbox","value":"on"},{"type":"text","value":"http://127.0.0.1:18319/proxy"},{"type":"password","value":"[fixture]"},{"type":"checkbox","value":"on"}]}}
panel sandbox after login {"result":{"type":"string","value":"Error:\nLogin failed: Connection failed, please check the server address, Management API, and management key\nCLI\nPROXY\nAPI\nCLI Proxy API Management Center\nEnglish\nConnect to CLI Proxy API v8 or later\nCurrent URL\nhttp://127.0.0.1:18319/proxy\nThe system will automatically use the current URL for connection\nCustom Connection URL:\nCustom Connection URL:\nBy default the current URL is used. Override it here if needed.\nManagement Key:\nRemember password\nLogin\nConnection failed, please check the server address, Management API, and management key"}}
panel sandbox requests [{"method":"GET","path":"/proxy/management.html","origin":null,"cookie":true}]
panel sandbox exceptions ["Uncaught (in promise) SecurityError: Failed to read the 'localStorage' property from 'Window': The document is sandboxed and lacks the 'allow-same-origin' flag.\n at Array.forEach (<anonymous>)\n at Object.migratePlaintextKeys (http://127.0.0.1:18319/proxy/management.html:20:667941)\n at http://127.0.0.1:18319/proxy/management.html:28:32103\n at restoreSession (http://127.0.0.1:18319/proxy/management.html:28:32640)\n at http://127.0.0.1:18319/proxy/management.html:28:38065\n at http://127.0.0.1:18319/proxy/management.html:28:38198\n at Uc (http://127.0.0.1:18319/proxy/management.html:16:92011)\n at kl (http://127.0.0.1:18319/proxy/management.html:16:107191)\n at Ol (http://127.0.0.1:18319/proxy/management.html:16:107075)\n at kl (http://127.0.0.1:18319/proxy/management.html:16:107956)"]
sandbox explicit null-origin fetch {"result":{"type":"object","value":{"error":"TypeError","message":"Failed to fetch"}}}
sandbox explicit requests [{"method":"GET","path":"/proxy/management.html","origin":null,"cookie":true},{"method":"OPTIONS","path":"/proxy/v8/management/config/access/api-keys","origin":"null","cookie":false}]
forward callback /proxy/callback?code=fixture 400
forward callback /proxy/auth/callback?code=fixture 404
Claude Code wrong/revoked key wrong {"code":124,"signal":null} output ""
Claude Code wrong/revoked key p1-revoked {"code":124,"signal":null} output ""
acceptance probes complete
@@ -0,0 +1,74 @@
// P1 acceptance only: run via acceptance.sh in a fresh private user/network namespace.
import fs from 'node:fs';
import path from 'node:path';
import http from 'node:http';
import { spawn, execFileSync } from 'node:child_process';
import { createHash } from 'node:crypto';
const root = process.argv[2], assets = process.argv[3];
if (!root?.startsWith('/mnt/storage/scratch/tmp/') || process.env.P1_ISOLATED !== 'yes') throw Error('acceptance requires wrapper');
const children=[], servers=[];
const delay=ms=>new Promise(r=>setTimeout(r,ms));
const home=path.join(root,'home');fs.mkdirSync(home,{recursive:true});
const env={HOME:home,PATH:'/usr/bin:/bin',LANG:'C.UTF-8'};
const port=18318, forwardPort=18319;
let upstreamCalls=0; const forwarded=[];
async function serve(handler, p){const s=http.createServer(handler);await new Promise(r=>s.listen(p,'127.0.0.1',r));servers.push(s);return s;}
async function request(route,{method='GET',key='p1-client',body,management=false}={}){
const r=await fetch(`http://127.0.0.1:${port}${route}`,{method,headers:{...(key?{Authorization:`Bearer ${key}`} : {}),...(body?{'Content-Type':'application/json'}:{})},body:body?JSON.stringify(body):undefined,signal:AbortSignal.timeout(5000)});
const text=await r.text();return {status:r.status,body:text};
}
function launch(command,args,extra={}){const out=fs.openSync(path.join(root,`${children.length}.stdout`),'w');const c=spawn(command,args,{cwd:root,env,stdio:['ignore',out,out],...extra});fs.closeSync(out);children.push(c);return c;}
async function wait(p){for(let i=0;i<100;i++){try{await fetch(`http://127.0.0.1:${p}`,{signal:AbortSignal.timeout(300)});return;}catch{}await delay(100);}throw Error(`listener ${p} absent`);}
const config={
'config-version':8,server:{host:'127.0.0.1',port},
management:{'allow-remote':false,'secret-key':'p1-management','disable-control-panel':false,'disable-auto-update-panel':true},
access:{'api-keys':['p1-client']},routing:{strategy:'fill-first',retry:{'request-retry':0},'session-affinity':true,'session-affinity-rebind-on':'quota'},
oauth:{'auth-dir':path.join(root,'auth')},
'api-keys':{claude:[{name:'standin','base-url':'http://127.0.0.1:18401',keys:[{'api-key':'p1-upstream'}]}]},
};
fs.mkdirSync(path.join(root,'auth'),{mode:0o700});fs.mkdirSync(path.join(root,'static'));
fs.copyFileSync(path.join(assets,'management.html'),path.join(root,'static','management.html'));
fs.writeFileSync(path.join(root,'config.yaml'),JSON.stringify(config),{mode:0o600});
let sandbox=false;
try{
console.log('P1 isolated acceptance root',root,'nice',execFileSync('nice',{encoding:'utf8'}).trim());
console.log('binary sha256',createHash('sha256').update(fs.readFileSync(path.join(assets,'release','cli-proxy-api'))).digest('hex'));
await serve((q,s)=>{upstreamCalls++;s.setHeader('Content-Type','application/json');s.end(JSON.stringify({id:'msg_p1',type:'message',role:'assistant',model:'claude-sonnet-5-5',content:[{type:'text',text:'hello'}],stop_reason:'end_turn',usage:{input_tokens:1,output_tokens:1}}));},18401);
launch(path.join(assets,'release','cli-proxy-api'),['-config',path.join(root,'config.yaml'),'-local-model']);await wait(port);
for(const key of ['wrong',null])console.log('wrong/missing key models',key===null?'missing':'wrong',JSON.stringify(await request('/v1/models',{key})));
const before=upstreamCalls;const models=await request('/v1/models');console.log('models local',models.status,'upstream delta',upstreamCalls-before,'body length',models.body.length);
console.log('set empty',JSON.stringify(await request('/v8/management/config/access/api-keys',{method:'PUT',key:'p1-management',body:[]})));
console.log('empty list missing key',JSON.stringify(await request('/v1/models',{key:null})).slice(0,400));
console.log('restore list',JSON.stringify(await request('/v8/management/config/access/api-keys',{method:'PUT',key:'p1-management',body:['p1-client']})));
console.log('v8 key list',JSON.stringify(await request('/v8/management/config/access/api-keys',{key:'p1-management'})));
for(const provider of ['anthropic','codex'])console.log('oauth start',provider,JSON.stringify(await request(`/v0/management/${provider}-auth-url?is_webui=true`,{key:'p1-management'})));
await serve((q,s)=>{
forwarded.push({method:q.method,path:q.url,origin:q.headers.origin??null});
if(sandbox)s.setHeader('Content-Security-Policy','sandbox allow-scripts allow-forms');
// Minimal /proxy/ prefix strip. Cookies stay host-only SameSite=Lax for browser checks.
s.setHeader('Set-Cookie','p1-session=owner; HttpOnly; SameSite=Lax; Path=/');
const upstream=http.request({hostname:'127.0.0.1',port,path:q.url.replace(/^\/proxy\//,'/'),method:q.method,headers:{...q.headers,host:`127.0.0.1:${port}`}},r=>{s.statusCode=r.statusCode;for(const [k,v]of Object.entries(r.headers))if(!['content-security-policy','set-cookie','transfer-encoding'].includes(k))s.setHeader(k,v);r.pipe(s);});
upstream.on('error',()=>{s.statusCode=502;s.end();});q.pipe(upstream);
},forwardPort);
const chrome=launch('/usr/bin/chromium',['--headless','--no-sandbox','--disable-dev-shm-usage','--disable-background-networking','--no-first-run','--remote-allow-origins=*','--remote-debugging-port=19222',`--user-data-dir=${root}/chromium`,'about:blank']);await wait(19222);
const targets=await (await fetch('http://127.0.0.1:19222/json')).json();
const ws=new WebSocket(targets.find(t=>t.type==='page').webSocketDebuggerUrl);await new Promise((r,j)=>{ws.onopen=r;ws.onerror=j;});
let serial=0;const pending=new Map(),exceptions=[];
ws.onmessage=e=>{const m=JSON.parse(e.data);if(m.id){const p=pending.get(m.id);pending.delete(m.id);m.error?p.reject(Error(JSON.stringify(m.error))):p.resolve(m.result);}else if(m.method==='Runtime.exceptionThrown')exceptions.push(m.params.exceptionDetails.text+' '+(m.params.exceptionDetails.exception?.description??''));};
const call=(method,params={})=>new Promise((resolve,reject)=>{const id=++serial;pending.set(id,{resolve,reject});ws.send(JSON.stringify({id,method,params}));});
const evaluate=async expression=>(await call('Runtime.evaluate',{expression,returnByValue:true,awaitPromise:true}));
await call('Runtime.enable');await call('Page.enable');
for(const mode of ['plain','sandbox']){
sandbox=mode==='sandbox';exceptions.length=0;forwarded.length=0;
await call('Page.navigate',{url:`http://127.0.0.1:${forwardPort}/proxy/management.html`});await delay(2500);
console.log('panel',mode,'initial',JSON.stringify(await evaluate(`({text:document.body.innerText.slice(0,1800),inputs:[...document.querySelectorAll('input')].map(x=>({type:x.type,placeholder:x.placeholder,value:x.value})),buttons:[...document.querySelectorAll('button')].map(x=>x.innerText)})`)));
console.log('panel',mode,'storage',JSON.stringify(await evaluate(`(()=>{try{return {available:!!localStorage}}catch(e){return {error:e.name,message:e.message}}})()`)));
// React input setter; same panel base URL explicitly includes the forward prefix.
console.log('panel',mode,'login interaction',JSON.stringify(await evaluate(`(()=>{const inputs=[...document.querySelectorAll('input')];const setter=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set;const base=inputs.find(x=>x.type!=='password'&&x.type!=='checkbox');if(base){setter.call(base,'http://127.0.0.1:${forwardPort}/proxy');base.dispatchEvent(new Event('input',{bubbles:true}));}const pass=inputs.find(x=>x.type==='password');if(pass){setter.call(pass,'p1-management');pass.dispatchEvent(new Event('input',{bubbles:true}));}return inputs.map(x=>({type:x.type,value:x.type==='password'?'[fixture]':x.value}));})()`)));
await delay(300);await evaluate(`(()=>{const b=[...document.querySelectorAll('button')].find(x=>/connect|login|登录|连接/i.test(x.innerText));b?.click();return b?.innerText})()`);await delay(2500);
console.log('panel',mode,'after login',JSON.stringify(await evaluate(`document.body.innerText.slice(0,2000)`)));
console.log('panel',mode,'requests',JSON.stringify(forwarded));console.log('panel',mode,'exceptions',JSON.stringify(exceptions));
}
ws.close();chrome.kill('SIGTERM');
console.log('acceptance probes complete');
}finally{for(const child of children)child.kill('SIGTERM');for(const s of servers)s.closeAllConnections?.();for(const s of servers)s.close();}
@@ -0,0 +1,24 @@
P1 isolated acceptance root /mnt/storage/scratch/tmp/8ISEhA nice 19
binary sha256 74661f8cf949977870231359e8bf4344e07946766a673c08fa25a3e5f72fc95e
wrong/missing key models wrong {"status":401,"body":"{\"error\":\"Invalid API key\"}"}
wrong/missing key models missing {"status":401,"body":"{\"error\":\"Missing API key\"}"}
models local 200 upstream delta 0 body length 1739
set empty {"status":200,"body":"{\"config-version\":8,\"status\":\"ok\"}"}
empty list missing key {"status":200,"body":"{\"data\":[{\"created\":1790035200,\"id\":\"claude-opus-5-5\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1790553600,\"id\":\"claude-sonnet-5-5\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1771372800,\"id\":\"claude-sonnet-4-6\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1761955200,\"id\":\"claude-opus-4-5-20251101\",\"o
restore list {"status":200,"body":"{\"config-version\":8,\"status\":\"ok\"}"}
v8 key list {"status":200,"body":"[\"p1-client\"]"}
oauth start anthropic {"status":200,"body":"{\"state\":\"af67f8526eb8f4792176cc993e075918\",\"status\":\"ok\",\"url\":\"https://claude.ai/oauth/authorize?client_id=9d1c250a-e61b-44d9-88ed-5944d1962f5e\\u0026code=true\\u0026code_challenge=wGjdwRiW53ZfqkGcLHH1Zsw1uFd52D7Xa0SV3adjrrY\\u0026code_challenge_method=S256\\u0026redirect_uri=http%3A%2F%2Flocalhost%3A54545%2Fcallback\\u0026response_type=code\\u0026scope=user%3Aprofile+user%3Ainference+user%3Asessions%3Aclaude_code+user%3Amcp_servers+user%3Afile_upload\\u0026state=af67f8526eb8f4792176cc993e075918\"}"}
oauth start codex {"status":200,"body":"{\"state\":\"7e5a897426689fed007f2e9c23690c96\",\"status\":\"ok\",\"url\":\"https://auth.openai.com/oauth/authorize?client_id=app_EMoamEEZ73f0CkXaXp7hrann\\u0026code_challenge=CEFWFTzNF-gcf0IClzK7lZBoJmRqf5X49ZTczY86QTQ\\u0026code_challenge_method=S256\\u0026codex_cli_simplified_flow=true\\u0026id_token_add_organizations=true\\u0026prompt=login\\u0026redirect_uri=http%3A%2F%2Flocalhost%3A1455%2Fauth%2Fcallback\\u0026response_type=code\\u0026scope=openid+email+profile+offline_access\\u0026state=7e5a897426689fed007f2e9c23690c96\"}"}
panel plain initial {"result":{"type":"object","value":{"text":"CLI\nPROXY\nAPI\nCLI Proxy API Management Center\nEnglish\nConnect to CLI Proxy API v8 or later\nCurrent URL\nhttp://127.0.0.1:18319\nThe system will automatically use the current URL for connection\nCustom Connection URL:\nManagement Key:\nRemember password\nLogin","inputs":[{"type":"checkbox","placeholder":"","value":"on"},{"type":"password","placeholder":"Enter the management key","value":""},{"type":"checkbox","placeholder":"","value":"on"}],"buttons":["English","","Login"]}}}
panel plain storage {"result":{"type":"object","value":{"available":true}}}
panel plain login interaction {"result":{"type":"object","value":[{"type":"checkbox","value":"on"},{"type":"password","value":"[fixture]"},{"type":"checkbox","value":"on"}]}}
panel plain after login {"result":{"type":"string","value":"Success:\nConnected\nCPAMC\nCLI Proxy API Console\nOPERATE\nDashboard\nQuick Start\nGATEWAY\nAI Providers\nAuth Files\nAuthentication files: 0\nOAuth Login\nOBSERVE\nQuota Management\nLogs Viewer\nCONTROL\nConfig Panel\nPlugins\nPlugin Store\nManagement Center Info\nQuiet for now.\n\nv8.0.42 · Connected\n\nManage providers\nInspect logs\n→\nREQUESTS HANDLED\nLIVE\n0\nRolling 3h 20m window · 10-minute buckets\nSuccess\n0\nFailure\n0\nSuccess rate\n—\nAcross 0 requests in the window\nCredentials\n0\n0 active · 0 not serving\nProvider keys\n1\nAPI keys configured across all brands\nModels\n19\nServed through the proxy endpoint\nLIVE TRAFFIC\nThroughput, ten minutes at a time\n\nSuccess and failure counts per 10-minute bucket across the last 3h 20m. Hover a column for its exact split.\n\nSuccess\n0\nFailure\n0\n4\n3\n2\n1\n0\n\nNo requests\n\n11:00\n12:30\n14:10\nData table\nFLEET\nEvery provider, side by side\n\nVolume and success rate per provider over the same window.\n\n01\nClaude\nCredentials: 1\n0\nrequests\n—\nHEALTH\nCredential status\n\nNo credential files found.\n\nRUNTIME\nConfiguration at a glance\nRouting strategy\nfill-first (prioritize)\nRequest retry\n0\nManagement keys\n1\nServer version\n8.0.42\nBuild date\n10/24/2026\nUpstream proxy\n—\nDebug\nNo\nFile logging\nNo\nRequest log\nNo\nWebSocket auth\nYes\nModel prefix\nNo\nEdit configuration →\nNEXT\nWhere to go from here\nAI Providers\nAdd API keys, set priorities, and test brands.\n→\nAuth Files\nUpload, refresh, and disable OAuth credentials.\n→\nConfig Panel\nTune routing, retries, logging, and access.\n→\nQuota Management\nCheck remaining quota per credential.\n→\nLogs Viewer\nTail requests and download error reports.\n→\nManagement Center Info\nVersion details and management center info.\n→"}}
panel plain requests [{"method":"GET","path":"/proxy/management.html","origin":null},{"method":"GET","path":"/v8/management/config","origin":null},{"method":"GET","path":"/v1/models","origin":null},{"method":"GET","path":"/v8/management/observability/usage/api-keys","origin":null},{"method":"GET","path":"/v8/management/credentials","origin":null},{"method":"GET","path":"/v8/management/plugins","origin":null},{"method":"GET","path":"/v8/management/credentials","origin":null}]
panel plain exceptions []
panel sandbox initial {"result":{"type":"object","value":{"text":"CLI\nPROXY\nAPI\nCLI Proxy API Management Center\nEnglish\nConnect to CLI Proxy API v8 or later\nCurrent URL\nhttp://127.0.0.1:18319\nThe system will automatically use the current URL for connection\nCustom Connection URL:\nManagement Key:\nRemember password\nLogin","inputs":[{"type":"checkbox","placeholder":"","value":"on"},{"type":"password","placeholder":"Enter the management key","value":""},{"type":"checkbox","placeholder":"","value":"on"}],"buttons":["English","","Login"]}}}
panel sandbox storage {"result":{"type":"object","value":{"error":"SecurityError","message":"Failed to read the 'localStorage' property from 'Window': The document is sandboxed and lacks the 'allow-same-origin' flag."}}}
panel sandbox login interaction {"result":{"type":"object","value":[{"type":"checkbox","value":"on"},{"type":"password","value":"[fixture]"},{"type":"checkbox","value":"on"}]}}
panel sandbox after login {"result":{"type":"string","value":"Error:\nLogin failed: Connection failed, please check the server address, Management API, and management key\nCLI\nPROXY\nAPI\nCLI Proxy API Management Center\nEnglish\nConnect to CLI Proxy API v8 or later\nCurrent URL\nhttp://127.0.0.1:18319\nThe system will automatically use the current URL for connection\nCustom Connection URL:\nManagement Key:\nRemember password\nLogin\nConnection failed, please check the server address, Management API, and management key"}}
panel sandbox requests [{"method":"GET","path":"/proxy/management.html","origin":null}]
panel sandbox exceptions ["Uncaught (in promise) SecurityError: Failed to read the 'localStorage' property from 'Window': The document is sandboxed and lacks the 'allow-same-origin' flag.\n at Array.forEach (<anonymous>)\n at Object.migratePlaintextKeys (http://127.0.0.1:18319/proxy/management.html:20:667941)\n at http://127.0.0.1:18319/proxy/management.html:28:32103\n at restoreSession (http://127.0.0.1:18319/proxy/management.html:28:32640)\n at http://127.0.0.1:18319/proxy/management.html:28:38065\n at http://127.0.0.1:18319/proxy/management.html:28:38198\n at Uc (http://127.0.0.1:18319/proxy/management.html:16:92011)\n at kl (http://127.0.0.1:18319/proxy/management.html:16:107191)\n at Ol (http://127.0.0.1:18319/proxy/management.html:16:107075)\n at kl (http://127.0.0.1:18319/proxy/management.html:16:107956)"]
acceptance probes complete
@@ -0,0 +1,24 @@
P1 isolated acceptance root /mnt/storage/scratch/tmp/6vQIwX nice 19
binary sha256 74661f8cf949977870231359e8bf4344e07946766a673c08fa25a3e5f72fc95e
wrong/missing key models wrong {"status":401,"body":"{\"error\":\"Invalid API key\"}"}
wrong/missing key models missing {"status":401,"body":"{\"error\":\"Missing API key\"}"}
models local 200 upstream delta 0 body length 1739
set empty {"status":200,"body":"{\"config-version\":8,\"status\":\"ok\"}"}
empty list missing key {"status":200,"body":"{\"data\":[{\"created\":1784038800,\"id\":\"claude-opus-5\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1788220800,\"id\":\"claude-fable-5-1\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1790035200,\"id\":\"claude-opus-5-5\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1722945600,\"id\":\"claude-opus-4-1-20250805\",\"object
restore list {"status":200,"body":"{\"config-version\":8,\"status\":\"ok\"}"}
v8 key list {"status":200,"body":"[\"p1-client\"]"}
oauth start anthropic {"status":200,"body":"{\"state\":\"ebd42dd044fe6d4e323d3bdd811c218a\",\"status\":\"ok\",\"url\":\"https://claude.ai/oauth/authorize?client_id=9d1c250a-e61b-44d9-88ed-5944d1962f5e\\u0026code=true\\u0026code_challenge=wafpAcg4CdRK7vCe7ACV9v1KVSQCfcOeXufSGYILEAo\\u0026code_challenge_method=S256\\u0026redirect_uri=http%3A%2F%2Flocalhost%3A54545%2Fcallback\\u0026response_type=code\\u0026scope=user%3Aprofile+user%3Ainference+user%3Asessions%3Aclaude_code+user%3Amcp_servers+user%3Afile_upload\\u0026state=ebd42dd044fe6d4e323d3bdd811c218a\"}"}
oauth start codex {"status":200,"body":"{\"state\":\"72fee3ade3eea92eee1bcad0eb81f3ef\",\"status\":\"ok\",\"url\":\"https://auth.openai.com/oauth/authorize?client_id=app_EMoamEEZ73f0CkXaXp7hrann\\u0026code_challenge=9nJBzjGru1PmMjvatWfktJ6Y2OTGuEl7KgRlyMw9OA8\\u0026code_challenge_method=S256\\u0026codex_cli_simplified_flow=true\\u0026id_token_add_organizations=true\\u0026prompt=login\\u0026redirect_uri=http%3A%2F%2Flocalhost%3A1455%2Fauth%2Fcallback\\u0026response_type=code\\u0026scope=openid+email+profile+offline_access\\u0026state=72fee3ade3eea92eee1bcad0eb81f3ef\"}"}
panel plain initial {"result":{"type":"object","value":{"text":"CLI\nPROXY\nAPI\nCLI Proxy API Management Center\nEnglish\nConnect to CLI Proxy API v8 or later\nCurrent URL\nhttp://127.0.0.1:18319\nThe system will automatically use the current URL for connection\nCustom Connection URL:\nManagement Key:\nRemember password\nLogin","inputs":[{"type":"checkbox","placeholder":"","value":"on"},{"type":"password","placeholder":"Enter the management key","value":""},{"type":"checkbox","placeholder":"","value":"on"}],"buttons":["English","","Login"]}}}
panel plain storage {"result":{"type":"object","value":{"available":true}}}
panel plain login interaction {"result":{"type":"object","value":[{"type":"checkbox","value":"on"},{"type":"text","value":"http://127.0.0.1:18319/proxy"},{"type":"password","value":"[fixture]"},{"type":"checkbox","value":"on"}]}}
panel plain after login {"result":{"type":"string","value":"Success:\nConnected\nCPAMC\nCLI Proxy API Console\nOPERATE\nDashboard\nQuick Start\nGATEWAY\nAI Providers\nAuth Files\nAuthentication files: 0\nOAuth Login\nOBSERVE\nQuota Management\nLogs Viewer\nCONTROL\nConfig Panel\nPlugins\nPlugin Store\nManagement Center Info\nQuiet for now.\n\nv8.0.42 · Connected\n\nManage providers\nInspect logs\n→\nREQUESTS HANDLED\nLIVE\n0\nRolling 3h 20m window · 10-minute buckets\nSuccess\n0\nFailure\n0\nSuccess rate\n—\nAcross 0 requests in the window\nCredentials\n0\n0 active · 0 not serving\nProvider keys\n1\nAPI keys configured across all brands\nModels\n19\nServed through the proxy endpoint\nLIVE TRAFFIC\nThroughput, ten minutes at a time\n\nSuccess and failure counts per 10-minute bucket across the last 3h 20m. Hover a column for its exact split.\n\nSuccess\n0\nFailure\n0\n4\n3\n2\n1\n0\n\nNo requests\n\n11:00\n12:30\n14:10\nData table\nFLEET\nEvery provider, side by side\n\nVolume and success rate per provider over the same window.\n\n01\nClaude\nCredentials: 1\n0\nrequests\n—\nHEALTH\nCredential status\n\nNo credential files found.\n\nRUNTIME\nConfiguration at a glance\nRouting strategy\nfill-first (prioritize)\nRequest retry\n0\nManagement keys\n1\nServer version\n8.0.42\nBuild date\n10/24/2026\nUpstream proxy\n—\nDebug\nNo\nFile logging\nNo\nRequest log\nNo\nWebSocket auth\nYes\nModel prefix\nNo\nEdit configuration →\nNEXT\nWhere to go from here\nAI Providers\nAdd API keys, set priorities, and test brands.\n→\nAuth Files\nUpload, refresh, and disable OAuth credentials.\n→\nConfig Panel\nTune routing, retries, logging, and access.\n→\nQuota Management\nCheck remaining quota per credential.\n→\nLogs Viewer\nTail requests and download error reports.\n→\nManagement Center Info\nVersion details and management center info.\n→"}}
panel plain requests [{"method":"GET","path":"/proxy/management.html","origin":null,"cookie":false},{"method":"GET","path":"/proxy/v8/management/config","origin":null,"cookie":true},{"method":"GET","path":"/proxy/v1/models","origin":null,"cookie":true},{"method":"GET","path":"/proxy/v8/management/observability/usage/api-keys","origin":null,"cookie":true},{"method":"GET","path":"/proxy/v8/management/credentials","origin":null,"cookie":true},{"method":"GET","path":"/proxy/v8/management/plugins","origin":null,"cookie":true},{"method":"GET","path":"/proxy/v8/management/credentials","origin":null,"cookie":true}]
panel plain exceptions []
panel sandbox initial {"result":{"type":"object","value":{"text":"CLI\nPROXY\nAPI\nCLI Proxy API Management Center\nEnglish\nConnect to CLI Proxy API v8 or later\nCurrent URL\nhttp://127.0.0.1:18319\nThe system will automatically use the current URL for connection\nCustom Connection URL:\nManagement Key:\nRemember password\nLogin","inputs":[{"type":"checkbox","placeholder":"","value":"on"},{"type":"password","placeholder":"Enter the management key","value":""},{"type":"checkbox","placeholder":"","value":"on"}],"buttons":["English","","Login"]}}}
panel sandbox storage {"result":{"type":"object","value":{"error":"SecurityError","message":"Failed to read the 'localStorage' property from 'Window': The document is sandboxed and lacks the 'allow-same-origin' flag."}}}
panel sandbox login interaction {"result":{"type":"object","value":[{"type":"checkbox","value":"on"},{"type":"text","value":"http://127.0.0.1:18319/proxy"},{"type":"password","value":"[fixture]"},{"type":"checkbox","value":"on"}]}}
panel sandbox after login {"result":{"type":"string","value":"Error:\nLogin failed: Connection failed, please check the server address, Management API, and management key\nCLI\nPROXY\nAPI\nCLI Proxy API Management Center\nEnglish\nConnect to CLI Proxy API v8 or later\nCurrent URL\nhttp://127.0.0.1:18319/proxy\nThe system will automatically use the current URL for connection\nCustom Connection URL:\nCustom Connection URL:\nBy default the current URL is used. Override it here if needed.\nManagement Key:\nRemember password\nLogin\nConnection failed, please check the server address, Management API, and management key"}}
panel sandbox requests [{"method":"GET","path":"/proxy/management.html","origin":null,"cookie":true}]
panel sandbox exceptions ["Uncaught (in promise) SecurityError: Failed to read the 'localStorage' property from 'Window': The document is sandboxed and lacks the 'allow-same-origin' flag.\n at Array.forEach (<anonymous>)\n at Object.migratePlaintextKeys (http://127.0.0.1:18319/proxy/management.html:20:667941)\n at http://127.0.0.1:18319/proxy/management.html:28:32103\n at restoreSession (http://127.0.0.1:18319/proxy/management.html:28:32640)\n at http://127.0.0.1:18319/proxy/management.html:28:38065\n at http://127.0.0.1:18319/proxy/management.html:28:38198\n at Uc (http://127.0.0.1:18319/proxy/management.html:16:92011)\n at kl (http://127.0.0.1:18319/proxy/management.html:16:107191)\n at Ol (http://127.0.0.1:18319/proxy/management.html:16:107075)\n at kl (http://127.0.0.1:18319/proxy/management.html:16:107956)"]
acceptance probes complete
@@ -0,0 +1,29 @@
P1 isolated acceptance root /mnt/storage/scratch/tmp/lL0kGM nice 19
binary sha256 74661f8cf949977870231359e8bf4344e07946766a673c08fa25a3e5f72fc95e
wrong/missing key models wrong {"status":401,"body":"{\"error\":\"Invalid API key\"}"}
wrong/missing key models missing {"status":401,"body":"{\"error\":\"Missing API key\"}"}
models local 200 upstream delta 0 body length 1739
set empty {"status":200,"body":"{\"config-version\":8,\"status\":\"ok\"}"}
empty list missing key {"status":401,"body":"{\"error\":\"Missing API key\"}"}
restore list {"status":200,"body":"{\"config-version\":8,\"status\":\"ok\"}"}
revoke client {"status":200,"body":"{\"config-version\":8,\"status\":\"ok\"}"}
revoked key models {"status":200,"body":"{\"data\":[{\"created\":1770318000,\"id\":\"claude-opus-4-6\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1779984000,\"id\":\"claude-opus-4-8\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1782777600,\"id\":\"claude-sonnet-5\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1715644800,\"id\":\"claude-sonnet-4-20250514\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1781049600,\"id\":\"claude-fable-5\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1708300800,\"id\":\"claude-3-7-sonnet-20250219\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1790553600,\"id\":\"claude-haiku-5-5\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1759276800,\"id\":\"claude-haiku-4-5-20251001\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1759104000,\"id\":\"claude-sonnet-4-5-20250929\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1776297600,\"id\":\"claude-opus-4-7\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1788220800,\"id\":\"claude-fable-5-1\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1722945600,\"id\":\"claude-opus-4-1-20250805\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1729555200,\"id\":\"claude-3-5-haiku-20241022\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1784038800,\"id\":\"claude-opus-5\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1790553600,\"id\":\"claude-sonnet-5-5\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1761955200,\"id\":\"claude-opus-4-5-20251101\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1715644800,\"id\":\"claude-opus-4-20250514\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1790035200,\"id\":\"claude-opus-5-5\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1771372800,\"id\":\"claude-sonnet-4-6\",\"object\":\"model\",\"owned_by\":\"anthropic\"}],\"object\":\"list\"}"}
per-key model object attempt {"status":422,"body":"{\"error\":\"invalid_config\",\"message\":\"parse config payload: yaml: unmarshal errors:\\n line 12: cannot unmarshal !!map into string\"}"}
v8 key list {"status":200,"body":"[\"p1-client\"]"}
oauth start anthropic {"status":200,"body":"{\"state\":\"a0cd187b9b9942d03edb297157bfa916\",\"status\":\"ok\",\"url\":\"https://claude.ai/oauth/authorize?client_id=9d1c250a-e61b-44d9-88ed-5944d1962f5e\\u0026code=true\\u0026code_challenge=6XydCnDQEdejS9qEqjWNITiWM13vmW36zA4Ac8MPWrw\\u0026code_challenge_method=S256\\u0026redirect_uri=http%3A%2F%2Flocalhost%3A54545%2Fcallback\\u0026response_type=code\\u0026scope=user%3Aprofile+user%3Ainference+user%3Asessions%3Aclaude_code+user%3Amcp_servers+user%3Afile_upload\\u0026state=a0cd187b9b9942d03edb297157bfa916\"}"}
oauth start codex {"status":200,"body":"{\"state\":\"52ca455a3a19b8a3fa63fd60f0ffa057\",\"status\":\"ok\",\"url\":\"https://auth.openai.com/oauth/authorize?client_id=app_EMoamEEZ73f0CkXaXp7hrann\\u0026code_challenge=MQ7OWiP34eI38fQS06R495Sq-C-I-Fekol-vfUtbMpc\\u0026code_challenge_method=S256\\u0026codex_cli_simplified_flow=true\\u0026id_token_add_organizations=true\\u0026prompt=login\\u0026redirect_uri=http%3A%2F%2Flocalhost%3A1455%2Fauth%2Fcallback\\u0026response_type=code\\u0026scope=openid+email+profile+offline_access\\u0026state=52ca455a3a19b8a3fa63fd60f0ffa057\"}"}
panel plain initial {"result":{"type":"object","value":{"text":"CLI\nPROXY\nAPI\nCLI Proxy API Management Center\nEnglish\nConnect to CLI Proxy API v8 or later\nCurrent URL\nhttp://127.0.0.1:18319\nThe system will automatically use the current URL for connection\nCustom Connection URL:\nManagement Key:\nRemember password\nLogin","inputs":[{"type":"checkbox","placeholder":"","value":"on"},{"type":"password","placeholder":"Enter the management key","value":""},{"type":"checkbox","placeholder":"","value":"on"}],"buttons":["English","","Login"]}}}
panel plain storage {"result":{"type":"object","value":{"available":true}}}
panel plain login interaction {"result":{"type":"object","value":[{"type":"checkbox","value":"on"},{"type":"text","value":"http://127.0.0.1:18319/proxy"},{"type":"password","value":"[fixture]"},{"type":"checkbox","value":"on"}]}}
panel plain after login {"result":{"type":"string","value":"Success:\nConnected\nCPAMC\nCLI Proxy API Console\nOPERATE\nDashboard\nQuick Start\nGATEWAY\nAI Providers\nAuth Files\nAuthentication files: 0\nOAuth Login\nOBSERVE\nQuota Management\nLogs Viewer\nCONTROL\nConfig Panel\nPlugins\nPlugin Store\nManagement Center Info\nQuiet for now.\n\nv8.0.42 · Connected\n\nManage providers\nInspect logs\n→\nREQUESTS HANDLED\nLIVE\n0\nRolling 3h 20m window · 10-minute buckets\nSuccess\n0\nFailure\n0\nSuccess rate\n—\nAcross 0 requests in the window\nCredentials\n0\n0 active · 0 not serving\nProvider keys\n1\nAPI keys configured across all brands\nModels\n19\nServed through the proxy endpoint\nLIVE TRAFFIC\nThroughput, ten minutes at a time\n\nSuccess and failure counts per 10-minute bucket across the last 3h 20m. Hover a column for its exact split.\n\nSuccess\n0\nFailure\n0\n4\n3\n2\n1\n0\n\nNo requests\n\n11:00\n12:30\n14:10\nData table\nFLEET\nEvery provider, side by side\n\nVolume and success rate per provider over the same window.\n\n01\nClaude\nCredentials: 1\n0\nrequests\n—\nHEALTH\nCredential status\n\nNo credential files found.\n\nRUNTIME\nConfiguration at a glance\nRouting strategy\nfill-first (prioritize)\nRequest retry\n0\nManagement keys\n1\nServer version\n8.0.42\nBuild date\n10/24/2026\nUpstream proxy\n—\nDebug\nNo\nFile logging\nNo\nRequest log\nNo\nWebSocket auth\nYes\nModel prefix\nNo\nEdit configuration →\nNEXT\nWhere to go from here\nAI Providers\nAdd API keys, set priorities, and test brands.\n→\nAuth Files\nUpload, refresh, and disable OAuth credentials.\n→\nConfig Panel\nTune routing, retries, logging, and access.\n→\nQuota Management\nCheck remaining quota per credential.\n→\nLogs Viewer\nTail requests and download error reports.\n→\nManagement Center Info\nVersion details and management center info.\n→"}}
panel plain requests [{"method":"GET","path":"/proxy/management.html","origin":null,"cookie":false},{"method":"GET","path":"/proxy/v8/management/config","origin":null,"cookie":true},{"method":"GET","path":"/proxy/v1/models","origin":null,"cookie":true},{"method":"GET","path":"/proxy/v8/management/observability/usage/api-keys","origin":null,"cookie":true},{"method":"GET","path":"/proxy/v8/management/credentials","origin":null,"cookie":true},{"method":"GET","path":"/proxy/v8/management/plugins","origin":null,"cookie":true},{"method":"GET","path":"/proxy/v8/management/credentials","origin":null,"cookie":true}]
panel plain exceptions []
panel sandbox initial {"result":{"type":"object","value":{"text":"CLI\nPROXY\nAPI\nCLI Proxy API Management Center\nEnglish\nConnect to CLI Proxy API v8 or later\nCurrent URL\nhttp://127.0.0.1:18319\nThe system will automatically use the current URL for connection\nCustom Connection URL:\nManagement Key:\nRemember password\nLogin","inputs":[{"type":"checkbox","placeholder":"","value":"on"},{"type":"password","placeholder":"Enter the management key","value":""},{"type":"checkbox","placeholder":"","value":"on"}],"buttons":["English","","Login"]}}}
panel sandbox storage {"result":{"type":"object","value":{"error":"SecurityError","message":"Failed to read the 'localStorage' property from 'Window': The document is sandboxed and lacks the 'allow-same-origin' flag."}}}
panel sandbox login interaction {"result":{"type":"object","value":[{"type":"checkbox","value":"on"},{"type":"text","value":"http://127.0.0.1:18319/proxy"},{"type":"password","value":"[fixture]"},{"type":"checkbox","value":"on"}]}}
panel sandbox after login {"result":{"type":"string","value":"Error:\nLogin failed: Connection failed, please check the server address, Management API, and management key\nCLI\nPROXY\nAPI\nCLI Proxy API Management Center\nEnglish\nConnect to CLI Proxy API v8 or later\nCurrent URL\nhttp://127.0.0.1:18319/proxy\nThe system will automatically use the current URL for connection\nCustom Connection URL:\nCustom Connection URL:\nBy default the current URL is used. Override it here if needed.\nManagement Key:\nRemember password\nLogin\nConnection failed, please check the server address, Management API, and management key"}}
panel sandbox requests [{"method":"GET","path":"/proxy/management.html","origin":null,"cookie":true}]
panel sandbox exceptions ["Uncaught (in promise) SecurityError: Failed to read the 'localStorage' property from 'Window': The document is sandboxed and lacks the 'allow-same-origin' flag.\n at Array.forEach (<anonymous>)\n at Object.migratePlaintextKeys (http://127.0.0.1:18319/proxy/management.html:20:667941)\n at http://127.0.0.1:18319/proxy/management.html:28:32103\n at restoreSession (http://127.0.0.1:18319/proxy/management.html:28:32640)\n at http://127.0.0.1:18319/proxy/management.html:28:38065\n at http://127.0.0.1:18319/proxy/management.html:28:38198\n at Uc (http://127.0.0.1:18319/proxy/management.html:16:92011)\n at kl (http://127.0.0.1:18319/proxy/management.html:16:107191)\n at Ol (http://127.0.0.1:18319/proxy/management.html:16:107075)\n at kl (http://127.0.0.1:18319/proxy/management.html:16:107956)"]
Claude Code wrong/revoked key wrong {"code":124,"signal":null} output ""
Claude Code wrong/revoked key p1-revoked {"code":124,"signal":null} output ""
acceptance probes complete
@@ -0,0 +1,38 @@
P1 isolated acceptance root /mnt/storage/scratch/tmp/BHI4SH nice 19
binary sha256 74661f8cf949977870231359e8bf4344e07946766a673c08fa25a3e5f72fc95e
wrong/missing key models wrong {"status":401,"body":"{\"error\":\"Invalid API key\"}"}
wrong/missing key models missing {"status":401,"body":"{\"error\":\"Missing API key\"}"}
models local 200 upstream delta 0 body length 1739
initial key list {"status":200,"body":"[\"p1-client\"]"}
set empty {"status":200,"body":"{\"config-version\":8,\"status\":\"ok\"}"}
empty list settled {"elapsedMs":3,"statuses":[200],"body":"[models]"}
empty list missing key {"status":200,"body":"{\"data\":[{\"created\":1781049600,\"id\":\"claude-fable-5\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1790553600,\"id\":\"claude-sonnet-5-5\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1790553600,\"id\":\"claude-haiku-5-5\",\"object\":\"model\",\"owned_by\":\"anthropic\"},{\"created\":1761955200,\"id\":\"claude-opus-4-5-20251101\",\"obj
restore list {"status":200,"body":"{\"config-version\":8,\"status\":\"ok\"}"}
restored list settled wrong key {"elapsedMs":2,"statuses":[401],"body":"{\"error\":\"Invalid API key\"}"}
revoke client {"status":200,"body":"{\"config-version\":8,\"status\":\"ok\"}"}
revoked key settled {"elapsedMs":105,"statuses":[200,401],"body":"{\"error\":\"Invalid API key\"}"}
revoked key models {"status":401,"body":"{\"error\":\"Invalid API key\"}"}
per-key model object attempt {"status":422,"body":"{\"error\":\"invalid_config\",\"message\":\"parse config payload: yaml: unmarshal errors:\\n line 12: cannot unmarshal !!map into string\"}"}
final restored list settled {"elapsedMs":2,"statuses":[401],"body":"{\"error\":\"Invalid API key\"}"}
v8 key list {"status":200,"body":"[\"p1-client\"]"}
oauth start anthropic {"status":200,"body":"{\"state\":\"9fbd1cc6e8fb1dfe7ef26eeaac10b5a3\",\"status\":\"ok\",\"url\":\"https://claude.ai/oauth/authorize?client_id=9d1c250a-e61b-44d9-88ed-5944d1962f5e\\u0026code=true\\u0026code_challenge=yuyxfaZNMNV8yaScQpPVAoQHZ7x57Nplm4RILV_bcvo\\u0026code_challenge_method=S256\\u0026redirect_uri=http%3A%2F%2Flocalhost%3A54545%2Fcallback\\u0026response_type=code\\u0026scope=user%3Aprofile+user%3Ainference+user%3Asessions%3Aclaude_code+user%3Amcp_servers+user%3Afile_upload\\u0026state=9fbd1cc6e8fb1dfe7ef26eeaac10b5a3\"}"}
oauth start codex {"status":200,"body":"{\"state\":\"d025c2006ff67ef05723a546f752157c\",\"status\":\"ok\",\"url\":\"https://auth.openai.com/oauth/authorize?client_id=app_EMoamEEZ73f0CkXaXp7hrann\\u0026code_challenge=RZa2vkdSvNJgTuMi11Vn2b7uisoOimt7XdjjFClY1AE\\u0026code_challenge_method=S256\\u0026codex_cli_simplified_flow=true\\u0026id_token_add_organizations=true\\u0026prompt=login\\u0026redirect_uri=http%3A%2F%2Flocalhost%3A1455%2Fauth%2Fcallback\\u0026response_type=code\\u0026scope=openid+email+profile+offline_access\\u0026state=d025c2006ff67ef05723a546f752157c\"}"}
panel plain initial {"result":{"type":"object","value":{"text":"CLI\nPROXY\nAPI\nCLI Proxy API Management Center\nEnglish\nConnect to CLI Proxy API v8 or later\nCurrent URL\nhttp://127.0.0.1:18319\nThe system will automatically use the current URL for connection\nCustom Connection URL:\nManagement Key:\nRemember password\nLogin","inputs":[{"type":"checkbox","placeholder":"","value":"on"},{"type":"password","placeholder":"Enter the management key","value":""},{"type":"checkbox","placeholder":"","value":"on"}],"buttons":["English","","Login"]}}}
panel plain storage {"result":{"type":"object","value":{"available":true}}}
panel plain login interaction {"result":{"type":"object","value":[{"type":"checkbox","value":"on"},{"type":"text","value":"http://127.0.0.1:18319/proxy"},{"type":"password","value":"[fixture]"},{"type":"checkbox","value":"on"}]}}
panel plain after login {"result":{"type":"string","value":"Success:\nConnected\nCPAMC\nCLI Proxy API Console\nOPERATE\nDashboard\nQuick Start\nGATEWAY\nAI Providers\nAuth Files\nAuthentication files: 0\nOAuth Login\nOBSERVE\nQuota Management\nLogs Viewer\nCONTROL\nConfig Panel\nPlugins\nPlugin Store\nManagement Center Info\nQuiet for now.\n\nv8.0.42 · Connected\n\nManage providers\nInspect logs\n→\nREQUESTS HANDLED\nLIVE\n0\nRolling 3h 20m window · 10-minute buckets\nSuccess\n0\nFailure\n0\nSuccess rate\n—\nAcross 0 requests in the window\nCredentials\n0\n0 active · 0 not serving\nProvider keys\n1\nAPI keys configured across all brands\nModels\n19\nServed through the proxy endpoint\nLIVE TRAFFIC\nThroughput, ten minutes at a time\n\nSuccess and failure counts per 10-minute bucket across the last 3h 20m. Hover a column for its exact split.\n\nSuccess\n0\nFailure\n0\n4\n3\n2\n1\n0\n\nNo requests\n\n11:10\n12:40\n14:20\nData table\nFLEET\nEvery provider, side by side\n\nVolume and success rate per provider over the same window.\n\n01\nClaude\nCredentials: 1\n0\nrequests\n—\nHEALTH\nCredential status\n\nNo credential files found.\n\nRUNTIME\nConfiguration at a glance\nRouting strategy\nfill-first (prioritize)\nRequest retry\n0\nManagement keys\n1\nServer version\n8.0.42\nBuild date\n10/24/2026\nUpstream proxy\n—\nDebug\nNo\nFile logging\nNo\nRequest log\nNo\nWebSocket auth\nYes\nModel prefix\nNo\nEdit configuration →\nNEXT\nWhere to go from here\nAI Providers\nAdd API keys, set priorities, and test brands.\n→\nAuth Files\nUpload, refresh, and disable OAuth credentials.\n→\nConfig Panel\nTune routing, retries, logging, and access.\n→\nQuota Management\nCheck remaining quota per credential.\n→\nLogs Viewer\nTail requests and download error reports.\n→\nManagement Center Info\nVersion details and management center info.\n→"}}
panel plain requests [{"method":"GET","path":"/proxy/management.html","origin":null,"cookie":false},{"method":"GET","path":"/proxy/v8/management/config","origin":null,"cookie":true},{"method":"GET","path":"/proxy/v1/models","origin":null,"cookie":true},{"method":"GET","path":"/proxy/v8/management/observability/usage/api-keys","origin":null,"cookie":true},{"method":"GET","path":"/proxy/v8/management/credentials","origin":null,"cookie":true},{"method":"GET","path":"/proxy/v8/management/plugins","origin":null,"cookie":true},{"method":"GET","path":"/proxy/v8/management/credentials","origin":null,"cookie":true}]
panel plain exceptions []
panel sandbox initial {"result":{"type":"object","value":{"text":"CLI\nPROXY\nAPI\nCLI Proxy API Management Center\nEnglish\nConnect to CLI Proxy API v8 or later\nCurrent URL\nhttp://127.0.0.1:18319\nThe system will automatically use the current URL for connection\nCustom Connection URL:\nManagement Key:\nRemember password\nLogin","inputs":[{"type":"checkbox","placeholder":"","value":"on"},{"type":"password","placeholder":"Enter the management key","value":""},{"type":"checkbox","placeholder":"","value":"on"}],"buttons":["English","","Login"]}}}
panel sandbox storage {"result":{"type":"object","value":{"error":"SecurityError","message":"Failed to read the 'localStorage' property from 'Window': The document is sandboxed and lacks the 'allow-same-origin' flag."}}}
panel sandbox login interaction {"result":{"type":"object","value":[{"type":"checkbox","value":"on"},{"type":"text","value":"http://127.0.0.1:18319/proxy"},{"type":"password","value":"[fixture]"},{"type":"checkbox","value":"on"}]}}
panel sandbox after login {"result":{"type":"string","value":"Error:\nLogin failed: Connection failed, please check the server address, Management API, and management key\nCLI\nPROXY\nAPI\nCLI Proxy API Management Center\nEnglish\nConnect to CLI Proxy API v8 or later\nCurrent URL\nhttp://127.0.0.1:18319/proxy\nThe system will automatically use the current URL for connection\nCustom Connection URL:\nCustom Connection URL:\nBy default the current URL is used. Override it here if needed.\nManagement Key:\nRemember password\nLogin\nConnection failed, please check the server address, Management API, and management key"}}
panel sandbox requests [{"method":"GET","path":"/proxy/management.html","origin":null,"cookie":true}]
panel sandbox exceptions ["Uncaught (in promise) SecurityError: Failed to read the 'localStorage' property from 'Window': The document is sandboxed and lacks the 'allow-same-origin' flag.\n at Array.forEach (<anonymous>)\n at Object.migratePlaintextKeys (http://127.0.0.1:18319/proxy/management.html:20:667941)\n at http://127.0.0.1:18319/proxy/management.html:28:32103\n at restoreSession (http://127.0.0.1:18319/proxy/management.html:28:32640)\n at http://127.0.0.1:18319/proxy/management.html:28:38065\n at http://127.0.0.1:18319/proxy/management.html:28:38198\n at Uc (http://127.0.0.1:18319/proxy/management.html:16:92011)\n at kl (http://127.0.0.1:18319/proxy/management.html:16:107191)\n at Ol (http://127.0.0.1:18319/proxy/management.html:16:107075)\n at kl (http://127.0.0.1:18319/proxy/management.html:16:107956)"]
sandbox explicit null-origin fetch {"result":{"type":"object","value":{"error":"TypeError","message":"Failed to fetch"}}}
sandbox explicit requests [{"method":"GET","path":"/proxy/management.html","origin":null,"cookie":true},{"method":"OPTIONS","path":"/proxy/v8/management/config/access/api-keys","origin":"null","cookie":false}]
forward callback /proxy/callback?code=fixture 400
forward callback /proxy/auth/callback?code=fixture 404
Claude Code wrong/revoked key wrong {"code":124,"signal":null} output ""
Claude Code wrong/revoked key p1-revoked {"code":124,"signal":null} output ""
acceptance probes complete
@@ -0,0 +1,93 @@
// P1 acceptance only: run via acceptance.sh in a fresh private user/network namespace.
import fs from 'node:fs';
import path from 'node:path';
import http from 'node:http';
import { spawn, execFileSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { initialConfig } from '../../../../packages/model-proxy/src/settings.mjs';
const root = process.argv[2], assets = process.argv[3];
if (!root?.startsWith('/mnt/storage/scratch/tmp/') || process.env.P1_ISOLATED !== 'yes') throw Error('acceptance requires wrapper');
const children=[], servers=[];
const delay=ms=>new Promise(r=>setTimeout(r,ms));
const home=path.join(root,'home');fs.mkdirSync(home,{recursive:true});
const env={HOME:home,PATH:'/usr/bin:/bin',LANG:'C.UTF-8'};
const port=18318, forwardPort=18319;
let upstreamCalls=0; const forwarded=[];
const enforce=async(label,key,wanted)=>{const started=Date.now(),statuses=[];for(let i=0;i<50;i++){const r=await request('/v1/models',{key});statuses.push(r.status);if(r.status===wanted){console.log(label,JSON.stringify({elapsedMs:Date.now()-started,statuses,body:wanted===401?r.body:'[models]'}));return;}await delay(100);}throw Error(label+' enforcement absent '+JSON.stringify(statuses));};
async function serve(handler, p){const s=http.createServer(handler);await new Promise(r=>s.listen(p,'127.0.0.1',r));servers.push(s);return s;}
async function request(route,{method='GET',key='p1-client',body,management=false}={}){
const r=await fetch(`http://127.0.0.1:${port}${route}`,{method,headers:{...(key?{Authorization:`Bearer ${key}`} : {}),...(body?{'Content-Type':'application/json'}:{})},body:body?JSON.stringify(body):undefined,signal:AbortSignal.timeout(5000)});
const text=await r.text();return {status:r.status,body:text};
}
function launch(command,args,extra={}){const out=fs.openSync(path.join(root,`${children.length}.stdout`),'w');const c=spawn(command,args,{cwd:root,env,stdio:['ignore',out,out],...extra});fs.closeSync(out);children.push(c);return c;}
async function wait(p){for(let i=0;i<100;i++){try{await fetch(`http://127.0.0.1:${p}`,{signal:AbortSignal.timeout(300)});return;}catch{}await delay(100);}throw Error(`listener ${p} absent`);}
const config=initialConfig({dataRoot:root,port,key:'p1-management',sentinel:'p1-client'});
config['api-keys']={claude:[{name:'standin','base-url':'http://127.0.0.1:18401',keys:[{'api-key':'p1-upstream'}]}]};
config.oauth['auth-dir']=path.join(root,'auth');
fs.mkdirSync(path.join(root,'auth'),{mode:0o700});fs.mkdirSync(path.join(root,'static'));
fs.copyFileSync(path.join(assets,'management.html'),path.join(root,'static','management.html'));
fs.writeFileSync(path.join(root,'config.yaml'),JSON.stringify(config),{mode:0o600});
let sandbox=false;
try{
console.log('P1 isolated acceptance root',root,'nice',execFileSync('nice',{encoding:'utf8'}).trim());
console.log('binary sha256',createHash('sha256').update(fs.readFileSync(path.join(assets,'release','cli-proxy-api'))).digest('hex'));
await serve((q,s)=>{upstreamCalls++;s.setHeader('Content-Type','application/json');s.end(JSON.stringify({id:'msg_p1',type:'message',role:'assistant',model:'claude-sonnet-5-5',content:[{type:'text',text:'hello'}],stop_reason:'end_turn',usage:{input_tokens:1,output_tokens:1}}));},18401);
launch(path.join(assets,'release','cli-proxy-api'),['-config',path.join(root,'config.yaml'),'-local-model'],{env:{...env,MANAGEMENT_STATIC_PATH:path.join(root,'static')}});await wait(port);
const panelResponse=await request('/management.html',{key:null});console.log('pinned panel served',panelResponse.status,createHash('sha256').update(panelResponse.body).digest('hex'));
const cliHelp=launch(path.join(assets,'release','cli-proxy-api'),['-help']);await new Promise(r=>cliHelp.on('close',r));console.log('pinned login flags',fs.readFileSync(path.join(root,`${children.length-1}.stdout`),'utf8').split('\n').filter((l,i,a)=>/-(claude|codex)-login|-no-browser/.test(l)).join(' | '));
for(const key of ['wrong',null])console.log('wrong/missing key models',key===null?'missing':'wrong',JSON.stringify(await request('/v1/models',{key})));
const before=upstreamCalls;const models=await request('/v1/models');console.log('models local',models.status,'upstream delta',upstreamCalls-before,'body length',models.body.length);
console.log('initial key list',JSON.stringify(await request('/v8/management/config/access/api-keys',{key:'p1-management'})));
console.log('set empty',JSON.stringify(await request('/v8/management/config/access/api-keys',{method:'PUT',key:'p1-management',body:[]})));
await enforce('empty list settled',null,200);
console.log('empty list missing key status',(await request('/v1/models',{key:null})).status);
console.log('restore list',JSON.stringify(await request('/v8/management/config/access/api-keys',{method:'PUT',key:'p1-management',body:['p1-client']})));
await enforce('restored list settled wrong key','wrong',401);
console.log('revoke client',JSON.stringify(await request('/v8/management/config/access/api-keys',{method:'PUT',key:'p1-management',body:['p1-sentinel']})));
await enforce('revoked key settled','p1-client',401);
console.log('revoked key models',JSON.stringify(await request('/v1/models',{key:'p1-client'})));
console.log('per-key model object attempt',JSON.stringify(await request('/v8/management/config/access/api-keys',{method:'PUT',key:'p1-management',body:[{key:'p1-limited',models:['claude-sonnet-5-5']}]})));
await request('/v8/management/config/access/api-keys',{method:'PUT',key:'p1-management',body:['p1-client']});
await enforce('final restored list settled','wrong',401);
console.log('v8 key list',JSON.stringify(await request('/v8/management/config/access/api-keys',{key:'p1-management'})));
for(const provider of ['anthropic','codex'])console.log('oauth start',provider,JSON.stringify(await request(`/v0/management/${provider}-auth-url?is_webui=true`,{key:'p1-management'})));
await serve((q,s)=>{
forwarded.push({method:q.method,path:q.url,origin:q.headers.origin??null,cookie:!!q.headers.cookie});
if(sandbox)s.setHeader('Content-Security-Policy','sandbox allow-scripts allow-forms');
// Minimal /proxy/ prefix strip. Cookies stay host-only SameSite=Lax for browser checks.
s.setHeader('Set-Cookie','p1-session=owner; HttpOnly; SameSite=Lax; Path=/');
if(!q.url.startsWith('/proxy/')){s.statusCode=404;s.end('outside prefix');return;}
const upstream=http.request({hostname:'127.0.0.1',port,path:q.url.replace(/^\/proxy\//,'/'),method:q.method,headers:{...q.headers,host:`127.0.0.1:${port}`}},r=>{s.statusCode=r.statusCode;for(const [k,v]of Object.entries(r.headers))if(!['content-security-policy','set-cookie','transfer-encoding'].includes(k))s.setHeader(k,v);r.pipe(s);});
upstream.on('error',()=>{s.statusCode=502;s.end();});q.pipe(upstream);
},forwardPort);
const chrome=launch('/usr/bin/chromium',['--headless','--no-sandbox','--disable-dev-shm-usage','--disable-background-networking','--no-first-run','--remote-allow-origins=*','--remote-debugging-port=19222',`--user-data-dir=${root}/chromium`,'about:blank']);await wait(19222);
const targets=await (await fetch('http://127.0.0.1:19222/json')).json();
const ws=new WebSocket(targets.find(t=>t.type==='page').webSocketDebuggerUrl);await new Promise((r,j)=>{ws.onopen=r;ws.onerror=j;});
let serial=0;const pending=new Map(),exceptions=[];
ws.onmessage=e=>{const m=JSON.parse(e.data);if(m.id){const p=pending.get(m.id);pending.delete(m.id);m.error?p.reject(Error(JSON.stringify(m.error))):p.resolve(m.result);}else if(m.method==='Runtime.exceptionThrown')exceptions.push(m.params.exceptionDetails.text+' '+(m.params.exceptionDetails.exception?.description??''));};
const call=(method,params={})=>new Promise((resolve,reject)=>{const id=++serial;pending.set(id,{resolve,reject});ws.send(JSON.stringify({id,method,params}));});
const evaluate=async expression=>(await call('Runtime.evaluate',{expression,returnByValue:true,awaitPromise:true}));
await call('Runtime.enable');await call('Page.enable');
for(const mode of ['plain','sandbox']){
sandbox=mode==='sandbox';exceptions.length=0;forwarded.length=0;
await call('Page.navigate',{url:`http://127.0.0.1:${forwardPort}/proxy/management.html`});await delay(2500);
console.log('panel',mode,'initial',JSON.stringify(await evaluate(`({text:document.body.innerText.slice(0,1800),inputs:[...document.querySelectorAll('input')].map(x=>({type:x.type,placeholder:x.placeholder,value:x.value})),buttons:[...document.querySelectorAll('button')].map(x=>x.innerText)})`)));
console.log('panel',mode,'storage',JSON.stringify(await evaluate(`(()=>{try{return {available:!!localStorage}}catch(e){return {error:e.name,message:e.message}}})()`)));
await evaluate(`document.querySelector('input[type=checkbox]')?.click()`);await delay(300);
// React input setter; same panel base URL explicitly includes the forward prefix.
console.log('panel',mode,'login interaction',JSON.stringify(await evaluate(`(()=>{const inputs=[...document.querySelectorAll('input')];const setter=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set;const base=inputs.find(x=>x.type!=='password'&&x.type!=='checkbox');if(base){setter.call(base,'http://127.0.0.1:${forwardPort}/proxy');base.dispatchEvent(new Event('input',{bubbles:true}));}const pass=inputs.find(x=>x.type==='password');if(pass){setter.call(pass,'p1-management');pass.dispatchEvent(new Event('input',{bubbles:true}));}return inputs.map(x=>({type:x.type,value:x.type==='password'?'[fixture]':x.value}));})()`)));
await delay(300);await evaluate(`(()=>{const b=[...document.querySelectorAll('button')].find(x=>/connect|login|登录|连接/i.test(x.innerText));b?.click();return b?.innerText})()`);await delay(2500);
console.log('panel',mode,'after login',JSON.stringify(await evaluate(`document.body.innerText.slice(0,2000)`)));
console.log('panel',mode,'requests',JSON.stringify(forwarded));console.log('panel',mode,'exceptions',JSON.stringify(exceptions));
}
console.log('sandbox explicit null-origin fetch',JSON.stringify(await evaluate(`fetch('http://127.0.0.1:${forwardPort}/proxy/v8/management/config/access/api-keys',{headers:{Authorization:'Bearer p1-management'},credentials:'include'}).then(async r=>({status:r.status,body:await r.text()})).catch(e=>({error:e.name,message:e.message}))`)));console.log('sandbox explicit requests',JSON.stringify(forwarded));
for(const callback of ['/proxy/callback?code=fixture','/proxy/auth/callback?code=fixture']){const r=await fetch(`http://127.0.0.1:${forwardPort}${callback}`);console.log('forward callback',callback,r.status);}
ws.close();chrome.kill('SIGTERM');
const settings=path.join(root,'claude-settings.json'),mcp=path.join(root,'mcp.json');fs.writeFileSync(settings,'{}');fs.writeFileSync(mcp,JSON.stringify({mcpServers:{}}));
for(const key of ['wrong','p1-revoked']){
const chome=path.join(root,`claude-${key}`);fs.mkdirSync(chome);
const c=launch('/usr/bin/timeout',['-k','2','45','/home/jwoltje/.local/bin/claude','-p','Reply hello.','--model','claude-sonnet-5-5','--output-format','text','--restricted','--tools','','--permission-mode','dontAsk','--settings',settings,'--strict-mcp-config','--mcp-config',mcp,'--disable-slash-commands'],{env:{...env,HOME:chome,ANTHROPIC_BASE_URL:`http://127.0.0.1:${port}`,ANTHROPIC_AUTH_TOKEN:key,CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:'1'}});
const index=children.length-1;const result=await new Promise(r=>c.on('close',(code,signal)=>r({code,signal})));console.log('Claude Code wrong/revoked key',key,JSON.stringify(result),'output',JSON.stringify(fs.readFileSync(path.join(root,`${index}.stdout`),'utf8').slice(0,2000)));
}
console.log('acceptance probes complete');
}finally{for(const child of children)child.kill('SIGTERM');for(const s of servers)s.closeAllConnections?.();for(const s of servers)s.close();}
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
# Explicit acceptance, never part of scripts/test-*.sh; no host proxy introspection.
set -euo pipefail
HERE=$(cd "$(dirname "$0")" && pwd)
ASSETS=${1:?download root required}
export TMPDIR=$(mktemp -d /mnt/storage/scratch/tmp/XXXXXX)
timeout -k 10 240 nice -n $((19 - $(nice))) unshare -rn bash -c '
set -e
ip link set lo up
export P1_ISOLATED=yes
exec node "$1/acceptance.mjs" "$TMPDIR" "$2"
' bash "$HERE" "$ASSETS"
@@ -0,0 +1,3 @@
1e10566fe0042ed5a9b80306cf296bf86a83de590cd967a8b177988b896c8d31 /mnt/storage/scratch/tmp/muoz5Z/CLIProxyAPI_8.0.42_linux_amd64.tar.gz
8c9c6b7d3fb311e6aad5d06e1993e53a2d28389e640448a1bc6304ed20b057b5 /mnt/storage/scratch/tmp/muoz5Z/management.html
74661f8cf949977870231359e8bf4344e07946766a673c08fa25a3e5f72fc95e /mnt/storage/scratch/tmp/muoz5Z/release/cli-proxy-api
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env python3
# Invoke only after Sage CLEAR; each process runs whole files, serially.
import json, os, pathlib, subprocess, sys, time
REPO = pathlib.Path(__file__).resolve().parents[4]
OUT = REPO / 'agents/magica/work/queue-329/gate'
OUT.mkdir(exist_ok=True)
# One short shell-created scratch root per runner invocation, never Python tempfile.
TMP = subprocess.check_output(['mktemp', '-d', '/mnt/storage/scratch/tmp/XXXXXX'], text=True).strip()
env = dict(os.environ, TMPDIR=TMP, npm_config_offline='true', MOSAIC_REQUIRE_EDITOR_BUILD='1')
def nice19(): os.setpriority(os.PRIO_PROCESS, 0, 19)
def run(label, cmd, cwd=REPO, seconds=1800):
log = OUT / (label + '.txt')
start = time.monotonic()
with log.open('w') as f:
f.write('command: '+json.dumps(cmd)+'\nTMPDIR: '+TMP+'\n'); f.flush()
proc = subprocess.Popen(['timeout','-k','10',str(seconds),*cmd], cwd=cwd, env=env, stdout=f, stderr=subprocess.STDOUT, preexec_fn=nice19)
observed = os.getpriority(os.PRIO_PROCESS, proc.pid)
f.write('launch nice: '+str(observed)+'\n'); f.flush()
rc=proc.wait()
receipt={'label':label,'rc':rc,'nice':observed,'seconds':round(time.monotonic()-start,2),'log':str(log.relative_to(REPO))}
with (OUT/'runs.jsonl').open('a') as f: f.write(json.dumps(receipt)+'\n')
print(json.dumps(receipt),flush=True)
return rc
mode=sys.argv[1] if len(sys.argv)>1 else ''
if mode=='install':
for label,cwd in [('npm-root',REPO),('npm-webui',REPO/'packages/webui')]:
if run(label,['npm','ci','--offline','--ignore-scripts','--no-audit','--no-fund'],cwd,600): sys.exit(1)
elif mode=='new':
for p in sorted((REPO/'packages/model-proxy/tests').glob('*.test.mjs')):
if run('model-proxy-'+p.stem,['node','--test',str(p)]): sys.exit(1)
for suite in ['bus','cli']:
p=REPO/f'packages/{suite}/tests/model-proxy.test.mjs'
if run(suite+'-model-proxy.test',['node','--test',str(p)]): sys.exit(1)
elif mode=='gate':
for suite in ['model-proxy','bus','cli']:
for p in sorted((REPO/f'packages/{suite}/tests').glob('*.test.mjs')):
if run(suite+'-'+p.stem,['node','--test',str(p)]): sys.exit(1)
if run('webui-pinned-copy.test',['node','--test',str(REPO/'packages/webui/tests/pinned-copy.test.mjs')]):sys.exit(1)
for script in ['test-config.sh','test-federation-acceptance.sh']:
if run(script,['bash',str(REPO/'scripts'/script)],seconds=600):sys.exit(1)
else: raise SystemExit('choose install, new or gate only after CLEAR')
+293
View File
@@ -0,0 +1,293 @@
Static direct-reader inventory for row329 (import/literal fragments; joined paths included).
Broad bus/CLI suites and webui pinned-copy cover these direct readers where in builder scope; landing gate covers remaining packages.
packages/webui/tests/chat.test.mjs:296: const cli = fileURLToPath(new URL('../src/cli.mjs', import.meta.url));
packages/webui/tests/thread-claims.test.mjs:64: const r = spawnSync(process.execPath, [new URL('../src/cli.mjs', import.meta.url).pathname, 'serve', '--business', 'acme', '--port', '0'],
packages/webui/tests/pinned-copy.test.mjs:76: assert.deepEqual(readdirSync(join(target, 'scripts')), ['case2-persona-import.mjs', 'mosaic', 'mosaic-config.mjs']);
packages/webui/tests/pinned-copy.test.mjs:90: const config = join(target, 'scripts', 'mosaic-config.mjs');
packages/webui/tests/pinned-copy.test.mjs:149: // Row 190: the entry point runs scripts/mosaic-config.mjs to select the
packages/webui/tests/send.test.mjs:20: const CLI = fileURLToPath(new URL('../src/cli.mjs', import.meta.url));
packages/webui/tests/sso.test.mjs:22: const CLI = fileURLToPath(new URL('../src/cli.mjs', import.meta.url));
packages/webui/tests/serve.test.mjs:109: const result = spawnSync(process.execPath, [fileURLToPath(new URL('../src/cli.mjs', import.meta.url)), ...args], { encoding: 'utf8' });
packages/webui/tests/preview.test.mjs:19: const CLI = fileURLToPath(new URL('../src/cli.mjs', import.meta.url));
packages/webui/tests/decide-sso.test.mjs:186: const cli = readFileSync(new URL('../src/cli.mjs', import.meta.url), 'utf8');
packages/webui/tests/library-cli.test.mjs:24: const result = spawnSync(process.execPath, [new URL('../src/cli.mjs', import.meta.url).pathname,
packages/webui/tests/proof-cli.test.mjs:14: const CLI = new URL('../src/cli.mjs', import.meta.url).pathname;
packages/webui/tests/bus.test.mjs:331: const cli = fileURLToPath(new URL('../src/cli.mjs', import.meta.url));
packages/webui/tests/tester-cli.test.mjs:15: const CLI = new URL('../src/cli.mjs', import.meta.url).pathname;
packages/discord/tests/library-seat.test.mjs:17: const CLI = fileURLToPath(new URL("../src/cli.mjs", import.meta.url));
packages/discord/tests/recover.test.mjs:18: const cli = fileURLToPath(new URL("../src/cli.mjs", import.meta.url));
packages/discord/tests/processing.test.mjs:61: const r = spawnSync(process.execPath, [new URL('../src/cli.mjs', import.meta.url).pathname, command, d.raw.name, '--config', d.config, '--repo', repo], { encoding: 'utf8', timeout: 5000 });
packages/discord/tests/offline-check.test.mjs:8: const CLI=fileURLToPath(new URL('../src/cli.mjs',import.meta.url));
packages/control-board/tests/relaunch.test.mjs:52: const run = spawnSync(process.execPath, [new URL('../src/cli.mjs', import.meta.url).pathname, 'scan', '--config', config, '--repo', f.root, '--fleet', 'none', '--liveness', 'assume-alive', '--print'], { encoding: 'utf8', timeout: 10000
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);as
packages/model-proxy/tests/config-unit.test.mjs:15: test('proxy-config-invalid: loader refuses an unknown sidecar key and binary outside pinned install',()=>{for(const value of [{port:8317,binary:'/bad'},{port:18432,binary:'/bad'},{...{port:18432,binary:'/bad'},future:true}]){const f=fixture
packages/model-proxy/tests/config-unit.test.mjs:16: test('proxy unit and bus dependency render default and named instance only when configured',()=>{for(const name of ['dev','testbox']){const f=fixture(name),flags=name==='dev'?[]:['--instance',name];let r=run(f,'scripts/bus-service.sh',[...f
packages/model-proxy/tests/config-unit.test.mjs:20: test('proxy-config-invalid: instance selection also refuses invalid present sidecar',()=>{const f=fixture();writeFileSync(join(f.configDir,'proxy.json'),'{');const r=run(f,'scripts/mosaic-config.mjs',['instance']);assert.equal(r.status,2);a
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(joi
packages/bus/tests/revoke-window.test.mjs:13: import { Broker } from '../src/broker.mjs';
packages/bus/tests/viewer.test.mjs:14: } from '../src/broker.mjs';
packages/bus/tests/review.test.mjs:14: import { Broker } from '../src/broker.mjs';
packages/bus/tests/task-page.test.mjs:7: import { Broker, TASK_PAGE, TASK_PAGE_MAX } from '../src/broker.mjs';
packages/bus/tests/spec-socket.test.mjs:6: import { serve } from '../src/server.mjs';
packages/bus/tests/user-principal.test.mjs:16: } from '../src/broker.mjs';
packages/bus/tests/grant-migration.test.mjs:10: import { Broker } from '../src/broker.mjs';
packages/bus/tests/gitea-grant.test.mjs:12: import { Broker, GRANT_EVIDENCE, writeGrantEvent } from '../src/broker.mjs';
packages/bus/tests/model-proxy.test.mjs:7: import { Broker } from '../src/broker.mjs';
packages/bus/tests/model-proxy.test.mjs:15: test('socket awaits proxy.init and does not return a Promise object or retain human cap',async t=>{const {serve}=await import('../src/server.mjs');const {Client}=await import('../src/client.mjs');const f=fixture(t,async()=>{await new Promis
packages/bus/tests/thread-claims.test.mjs:11: import { Broker } from '../src/broker.mjs';
packages/bus/tests/credential-status.test.mjs:12: import { Broker } from '../src/broker.mjs';
packages/bus/tests/library-credential.test.mjs:16: import { Broker, GRANT_EVIDENCE, LIBRARY_FETCH_FIX, writeGrantEvent } from '../src/broker.mjs';
packages/bus/tests/library-credential.test.mjs:19: import { startBroker } from '../src/runtime.mjs';
packages/bus/tests/limits.test.mjs:7: import {Broker} from '../src/broker.mjs';
packages/bus/tests/limits.test.mjs:20: const {startBroker}=await import('../src/runtime.mjs');const {businessDoc,rolesCopy,systemFor,writeJson}=await import('../../business/tests/helpers.mjs');const {readFileSync}=await import('node:fs');
packages/bus/tests/limits.test.mjs:27: const {startBroker}=await import('../src/runtime.mjs');const {businessDoc,rolesCopy,systemFor,writeJson}=await import('../../business/tests/helpers.mjs');const {organizationWriter}=await import('../../business/src/organization.mjs');const
packages/bus/tests/publish.test.mjs:14: import { Broker } from '../src/broker.mjs';
packages/bus/tests/publish.test.mjs:221: const { ACTIONS, HARD_WAITS } = await import('../src/broker.mjs');
packages/bus/tests/publish.test.mjs:234: const { startBroker } = await import('../src/runtime.mjs');
packages/bus/tests/message-citation.test.mjs:6: import { Broker } from '../src/broker.mjs';
packages/bus/tests/approval-binding.test.mjs:7: import { Broker } from '../src/broker.mjs';
packages/bus/tests/access-revoke.test.mjs:14: import { Broker } from '../src/broker.mjs';
packages/bus/tests/access-revoke.test.mjs:15: import { ACCESS_SWEEP_MS, startBroker } from '../src/runtime.mjs';
packages/bus/tests/messages.test.mjs:3: import { Broker } from '../src/broker.mjs';
packages/bus/tests/csuite-messaging.test.mjs:12: import { Broker } from '../src/broker.mjs';
packages/bus/tests/rooms.test.mjs:8: import { Broker, THREAD_PAGE, THREAD_PAGE_MAX, TESTER_VERBS } from '../src/broker.mjs';
packages/bus/tests/messaging.test.mjs:8: import { Broker } from '../src/broker.mjs';
packages/bus/tests/citation-availability.test.mjs:10: import { serve } from '../src/server.mjs';
packages/bus/tests/citation-guard.test.mjs:7: import { serve } from '../src/server.mjs';
packages/bus/tests/tester-threads.test.mjs:11: import { Broker } from '../src/broker.mjs';
packages/bus/tests/state.test.mjs:7: import { Broker, TASK_PAGE } from '../src/broker.mjs';
packages/bus/tests/workflow-roles.test.mjs:11: import { Broker } from '../src/broker.mjs';
packages/bus/tests/citation-runtime.test.mjs:7: import { startBroker } from '../src/runtime.mjs';
packages/bus/tests/fire-revokes-grants.test.mjs:15: import { Broker, GRANT_EVIDENCE, registrySha256 } from '../src/broker.mjs';
packages/bus/tests/thread-migration.test.mjs:11: import { Broker } from '../src/broker.mjs';
packages/bus/tests/room-check.test.mjs:6: import { Broker } from '../src/broker.mjs';
packages/bus/tests/web.test.mjs:8: import { Broker, BusError, WEB_UNTRUSTED } from '../src/broker.mjs';
packages/bus/tests/web.test.mjs:9: import { serve } from '../src/server.mjs';
packages/bus/tests/web.test.mjs:283: const { webServices } = await import('../src/runtime.mjs');
packages/bus/tests/transport.test.mjs:8: import { Broker } from '../src/broker.mjs';
packages/bus/tests/transport.test.mjs:9: import { serve } from '../src/server.mjs';
packages/bus/tests/comment-cursors.test.mjs:7: import { Broker } from '../src/broker.mjs';
packages/bus/tests/gitea-grant-docker.test.mjs:16: import { Broker, GRANT_EVIDENCE } from '../src/broker.mjs';
packages/bus/tests/peer-transport.test.mjs:7: import { serve } from '../src/server.mjs';
packages/bus/tests/peer-transport.test.mjs:9: import { BusError } from '../src/broker.mjs';
packages/bus/tests/peer-transport.test.mjs:47: const {TASK_VERBS,BusError}=await import('../src/broker.mjs');
packages/bus/tests/land.test.mjs:18: import { Broker } from '../src/broker.mjs';
packages/bus/tests/thread-shares.test.mjs:14: import { Broker } from '../src/broker.mjs';
packages/bus/tests/plugins-verb.test.mjs:9: import { Broker } from '../src/broker.mjs';
packages/bus/tests/plugins-verb.test.mjs:34: const { startBroker } = await import('../src/runtime.mjs');
packages/bus/tests/plugins-verb.test.mjs:69: const { startBroker } = await import('../src/runtime.mjs');
packages/bus/tests/decision-close.test.mjs:9: import { Broker } from '../src/broker.mjs';
packages/bus/tests/grant-scope.test.mjs:14: import { Broker, GRANT_EVIDENCE, writeGrantEvent } from '../src/broker.mjs';
packages/bus/tests/project-await.test.mjs:7: import { Broker, BusError } from '../src/broker.mjs';
packages/bus/tests/launcher-released.test.mjs:8: import { Broker } from '../src/broker.mjs';
packages/bus/tests/session-clear.test.mjs:10: import { Broker } from '../src/broker.mjs';
packages/bus/tests/task-read.test.mjs:7: import { Broker } from '../src/broker.mjs';
packages/bus/tests/message-filter.test.mjs:12: import { Broker, REPLIES } from '../src/broker.mjs';
packages/bus/tests/processing-runtime.test.mjs:8: import { startBroker } from '../src/runtime.mjs';
packages/bus/tests/grant-enroll.test.mjs:10: import { Broker, BusError, writeGrantEvent, registrySha256 } from '../src/broker.mjs';
packages/bus/tests/limit-stop.test.mjs:12: import { Broker } from '../src/broker.mjs';
packages/bus/tests/replies.test.mjs:11: import { Broker, REPLIES } from '../src/broker.mjs';
packages/bus/tests/launch-notice.test.mjs:8: import { Broker } from '../src/broker.mjs';
packages/bus/tests/broker.test.mjs:10: const { Broker } = await import('../src/broker.mjs').catch((e) => {
packages/bus/tests/project-runtime.test.mjs:6: import { startBroker } from '../src/runtime.mjs';
packages/bus/tests/project-runtime.test.mjs:13: const source = readFileSync(new URL('../src/broker.mjs', import.meta.url), 'utf8');
packages/bus/tests/usage-broker.test.mjs:7: import { Broker } from '../src/broker.mjs';
packages/bus/tests/instances-verb.test.mjs:11: import { Broker, LIFECYCLE_HUMAN_VERBS, ORGANIZATION_KEYS, TESTER_ORGANIZATION_VERBS } from '../src/broker.mjs';
packages/bus/tests/instances-verb.test.mjs:62: const { startBroker } = await import('../src/runtime.mjs');
packages/bus/tests/routes-verb.test.mjs:13: import { Broker, ORGANIZATION_KEYS } from '../src/broker.mjs';
packages/bus/tests/routes-verb.test.mjs:93: const { harnessLevels } = await import('../src/runtime.mjs');
packages/bus/tests/routes-verb.test.mjs:103: const { startBroker } = await import('../src/runtime.mjs');
packages/bus/tests/lifecycle-revoke.test.mjs:11: import { startBroker } from '../src/runtime.mjs';
packages/bus/tests/library-lookups.test.mjs:8: import { Broker } from '../src/broker.mjs';
packages/bus/tests/library-lookups.test.mjs:9: import { createLibraryLookup, startBroker } from '../src/runtime.mjs';
packages/bus/tests/library-lookups.test.mjs:10: import { serve } from '../src/server.mjs';
packages/bus/tests/grant-executor.test.mjs:15: import { Broker, registrySha256, writeGrantEvent } from '../src/broker.mjs';
packages/bus/tests/single-use.test.mjs:7: import { Broker } from '../src/broker.mjs';
packages/bus/tests/decisions.test.mjs:9: import { Broker, RECENT } from '../src/broker.mjs';
packages/bus/tests/daily-quota-runtime.test.mjs:10: import { Broker } from '../src/broker.mjs';
packages/bus/tests/daily-quota-runtime.test.mjs:17: const { startBroker } = await import('../src/runtime.mjs');
packages/bus/tests/sessions.test.mjs:10: import { Broker } from '../src/broker.mjs';
packages/bus/tests/requirements.test.mjs:10: import { Broker } from '../src/broker.mjs';
packages/bus/tests/hard-waits.test.mjs:7: import { Broker, ACTIONS, HARD_WAITS } from '../src/broker.mjs';
packages/bus/tests/library.test.mjs:8: import { Broker } from '../src/broker.mjs';
packages/bus/tests/library.test.mjs:9: import { serve } from '../src/server.mjs';
packages/bus/tests/library.test.mjs:11: import { startBroker } from '../src/runtime.mjs';
packages/bus/tests/threads.test.mjs:9: import { Broker, THREAD_PAGE } from '../src/broker.mjs';
packages/bus/tests/tester.test.mjs:10: import { Broker, TASK_VERBS } from '../src/broker.mjs';
packages/bus/tests/tester.test.mjs:11: import { serve } from '../src/server.mjs';
packages/bus/tests/tester.test.mjs:13: import { startBroker } from '../src/runtime.mjs';
packages/bus/tests/limits-runtime.test.mjs:15: const { startBroker } = await import('../src/runtime.mjs');
packages/bus/tests/creative.test.mjs:8: import { Broker } from '../src/broker.mjs';
packages/bus/tests/creative.test.mjs:9: import { startBroker } from '../src/runtime.mjs';
packages/bus/tests/usage-runtime.test.mjs:6: import { startBroker } from '../src/runtime.mjs';
packages/bus/tests/limit-checks.test.mjs:323: for (const file of ['../src/runtime.mjs', '../src/broker.mjs', '../src/process.mjs']) {
packages/bus/tests/library-sync.test.mjs:11: import { Broker, LIBRARY_VERBS } from '../src/broker.mjs';
packages/bus/tests/library-sync.test.mjs:12: import { startBroker } from '../src/runtime.mjs';
packages/bus/tests/credential-verbs.test.mjs:10: import { startBroker } from '../src/runtime.mjs';
packages/bus/tests/credential-verbs.test.mjs:11: import { Broker, CREDENTIAL_VERBS } from '../src/broker.mjs';
packages/bus/tests/end-launch.test.mjs:9: import { Broker } from '../src/broker.mjs';
packages/bus/tests/agents.test.mjs:9: import { Broker } from '../src/broker.mjs';
packages/bus/tests/reload.test.mjs:9: import { Broker } from '../src/broker.mjs';
packages/bus/tests/reload.test.mjs:11: import { startBroker } from '../src/runtime.mjs';
packages/bus/tests/names.test.mjs:8: import { Broker } from '../src/broker.mjs';
packages/bus/tests/processing.test.mjs:6: import { Broker } from '../src/broker.mjs';
packages/bus/tests/processing.test.mjs:8: import { serve } from '../src/server.mjs';
packages/bus/tests/chief-of-staff.test.mjs:10: import { Broker, ACTIONS } from '../src/broker.mjs';
packages/bus/tests/chief-of-staff.test.mjs:12: import { startBroker } from '../src/runtime.mjs';
packages/bus/tests/tester-writes.test.mjs:10: import { Broker, TESTER_VERBS, TESTER_ORGANIZATION_VERBS } from '../src/broker.mjs';
packages/bus/tests/tester-writes.test.mjs:11: import { startBroker } from '../src/runtime.mjs';
packages/bus/tests/grant-host.test.mjs:10: import { Broker, registrySha256 } from '../src/broker.mjs';
packages/bus/tests/staff-request.test.mjs:8: import { Broker } from '../src/broker.mjs';
packages/bus/tests/host-run.test.mjs:16: import { Broker, ORGANIZATION_KEYS } from '../src/broker.mjs';
packages/bus/tests/host-run.test.mjs:19: import { startBroker } from '../src/runtime.mjs';
packages/bus/tests/decision-route.test.mjs:12: import { Broker } from '../src/broker.mjs';
packages/bus/tests/grant-scope-runtime.test.mjs:8: import { startBroker } from '../src/runtime.mjs';
packages/bus/tests/tasks.test.mjs:9: import { Broker, BusError, TASK_VERBS } from '../src/broker.mjs';
packages/bus/tests/tasks.test.mjs:10: import { serve } from '../src/server.mjs';
packages/bus/tests/tasks.test.mjs:11: import { startBroker } from '../src/runtime.mjs';
packages/bus/tests/lifecycle-verbs.test.mjs:11: import { Broker, LIFECYCLE_REQUEST_MS, TESTER_VERBS } from '../src/broker.mjs';
packages/bus/tests/lifecycle-verbs.test.mjs:368: const { startBroker } = await import('../src/runtime.mjs');
packages/bus/tests/project-files.test.mjs:6: import { serve } from '../src/server.mjs';
packages/bus/tests/project-files.test.mjs:8: import { PROJECT_FILE_VERBS } from '../src/broker.mjs';
packages/bus/tests/console-decide.test.mjs:14: import { Broker, CONSOLE_VERBS } from '../src/broker.mjs';
packages/bus/tests/console-decide.test.mjs:16: import { serve } from '../src/server.mjs';
packages/bus/tests/session-compactions.test.mjs:9: import { Broker } from '../src/broker.mjs';
packages/cli/tests/thread-cli.test.mjs:16: import { run } from "../src/cli.mjs";
packages/cli/tests/revoke-window.test.mjs:25: import { bootConfig, loadSystem } from "../src/config.mjs";
packages/cli/tests/usage.test.mjs:3: import { main } from '../src/cli.mjs';
packages/cli/tests/optional-tracker.test.mjs:6: import { bootConfig } from "../src/config.mjs";
packages/cli/tests/follow.test.mjs:3: import {main} from '../src/cli.mjs';
packages/cli/tests/other-tree.test.mjs:3: import { OTHER_TREE, run } from "../src/cli.mjs";
packages/cli/tests/model-proxy.test.mjs:7: import { main } from '../src/cli.mjs';
packages/cli/tests/stack-copy.test.mjs:38: const s=setup(t);assert.equal(s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies]).status,0);
packages/cli/tests/stack-copy.test.mjs:50: const s=setup(t),r=s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies]);assert.equal(r.status,0,r.stderr);
packages/cli/tests/stack-copy.test.mjs:56: const denied=s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies]);assert.notEqual(denied.status,0);assert.match(denied.stderr,/origin\/refactor/);
packages/cli/tests/stack-copy.test.mjs:59: const s=setup(t);assert.equal(s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies]).status,0);
packages/cli/tests/stack-copy.test.mjs:63: assert.equal(s.run('stack-copy.sh',['wrapper',s.id,'--copies',s.copies,'--bin',s.bin]).status,0);
packages/cli/tests/stack-copy.test.mjs:73: assert.equal(s.run('stack-copy.sh',['check','--copies',s.copies,'--bin',s.bin]).status,0);
packages/cli/tests/stack-copy.test.mjs:75: assert.equal(s.run('stack-copy.sh',['check','--copies',s.copies,'--bin',s.bin]).status,1,'effective ExecStart mismatch');
packages/cli/tests/stack-copy.test.mjs:76: s.fake(`echo 'LoadState=loaded'; echo 'WorkingDirectory=/other';\n`);assert.equal(s.run('stack-copy.sh',['check','--copies',s.copies,'--bin',s.bin]).status,1);
packages/cli/tests/stack-copy.test.mjs:80: assert.equal(s.run('stack-copy.sh',['check','--copies',s.copies,'--bin',s.bin]).status,1);
packages/cli/tests/stack-copy.test.mjs:81: assert.notEqual(s.run('stack-copy.sh',['wrapper',s.id,'--copies',s.copies,'--bin',s.bin]).status,0);assert.equal(readFileSync(wrapper,'utf8'),foreign);
packages/cli/tests/stack-copy.test.mjs:82: const backing=join(s.bin,'backing');writeFileSync(backing,`#!/usr/bin/env bash\n${marker}\n${lease}\nexec \"/old/scripts/mosaic\" \"$@\"\n`);unlinkSync(wrapper);symlinkSync(backing,wrapper);assert.notEqual(s.run('stack-copy.sh',['wrapper',
packages/cli/tests/stack-copy.test.mjs:85: const s=setup(t);assert.equal(s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies]).status,0);
packages/cli/tests/stack-copy.test.mjs:88: const w=s.run('stack-copy.sh',['wrapper',s.id,'--instance','alpha','--copies',s.copies,'--bin',s.bin]);assert.equal(w.status,0,w.stderr);
packages/cli/tests/stack-copy.test.mjs:90: const check=()=>s.run('stack-copy.sh',['check','--instance','alpha','--copies',s.copies,'--bin',s.bin,'--business','demo']);
packages/cli/tests/stack-copy.test.mjs:102: const s=setup(t);assert.equal(s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies]).status,0);
packages/cli/tests/stack-copy.test.mjs:104: const render=s.run('bus-service.sh',['render',...args]);assert.equal(render.status,0,render.stderr);assert.match(render.stdout,new RegExp(`WorkingDirectory=${s.copies}/${s.id}`));assert.match(render.stdout,/bus start %i --pm/);
packages/cli/tests/stack-copy.test.mjs:106: for(const state of ['active','activating','deactivating','reloading']){s.fake(`echo '[email protected] loaded ${state} running fixture';\n`);const r=s.run('bus-service.sh',['install',...args,'--dir',units,'--no-reload']);assert.notEq
packages/cli/tests/stack-copy.test.mjs:107: s.fake('exit 1\n');assert.notEqual(s.run('bus-service.sh',['install',...args,'--dir',units,'--no-reload']).status,0);assert.ok(!existsSync(units));
packages/cli/tests/stack-copy.test.mjs:110: const refused=s.run('bus-service.sh',['install',...args,'--dir',units,'--no-reload']);assert.notEqual(refused.status,0);assert.ok(refused.stderr.includes(drop));assert.ok(!existsSync(units));
packages/cli/tests/stack-copy.test.mjs:111: writeFileSync(drop,'[Service]\nEnvironment=SAFE=1\n');assert.equal(s.run('bus-service.sh',['install',...args,'--dir',units,'--no-reload']).status,0);
packages/cli/tests/stack-copy.test.mjs:118: const r=s.run('stack-copy.sh',['copy',id,'--copies',s.copies]);assert.notEqual(r.status,0);assert.match(r.stderr,/package.json/);assert.ok(!existsSync(called));assert.ok(!existsSync(join(s.copies,id)));
packages/cli/tests/stack-copy.test.mjs:121: const absent=s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies]);assert.notEqual(absent.status,0);assert.match(absent.stderr,/package.json.*missing/);assert.ok(!existsSync(called));unlinkSync(tar);
packages/cli/tests/stack-copy.test.mjs:122: const fail=s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies]);assert.notEqual(fail.status,0);assert.match(fail.stderr,/npm ci failed/);assert.ok(existsSync(called));assert.deepEqual(execFileSync('ls',['-A',s.copies],{encoding:'utf8'}
packages/cli/tests/stack-copy.test.mjs:135: const copied=s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies]);assert.equal(copied.status,0,copied.stderr);
packages/cli/tests/stack-copy.test.mjs:183: const s=setup(t);assert.equal(s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies]).status,0);
packages/cli/tests/stack-copy.test.mjs:187: const status=s.run('bus-service.sh',['status','demo']);assert.equal(status.status,0,status.stderr);assert.match(status.stdout,new RegExp(`copy: ${s.id}`));assert.match(status.stdout,/pm: on/);assert.ok(status.stdout.includes(drop));
packages/cli/tests/stack-copy.test.mjs:196: const r=s.run('bus-service.sh',['install','--dir',units,'--no-reload']);assert.notEqual(r.status,0);assert.ok(r.stderr.includes(drop));assert.ok(!existsSync(units));
packages/cli/tests/stack-copy.test.mjs:198: s.fake('exit 0\n');s.analyze('exit 0\n');const unknown=s.run('bus-service.sh',['install','--dir',units,'--no-reload']);assert.notEqual(unknown.status,0);assert.match(unknown.stderr,/unknown/);
packages/cli/tests/stack-copy.test.mjs:200: s.fake("echo '[email protected] loaded active running fixture'\n");assert.notEqual(s.run('bus-service.sh',['uninstall','--dir',units,'--no-reload']).status,0);assert.equal(readFileSync(file,'utf8'),'unchanged');
packages/cli/tests/stack-copy.test.mjs:207: const result=s.run('bus-service.sh',['install','--dir',units,'--no-reload']);assert.equal(result.status,0,result.stderr);
packages/cli/tests/stack-copy.test.mjs:213: const denied=s.run('bus-service.sh',['install','--dir',units,'--no-reload']);assert.notEqual(denied.status,0);assert.ok(denied.stderr.includes(file));assert.equal(readFileSync(join(units,'[email protected]'),'utf8'),before);
packages/cli/tests/stack-copy.test.mjs:220: const denied=s.run('bus-service.sh',['install','--dir',units,'--no-reload']);assert.notEqual(denied.status,0);assert.ok(denied.stderr.includes(file));assert.ok(!existsSync(units));
packages/cli/tests/stack-copy.test.mjs:221: s.analyze('exit 1\n');const failed=s.run('bus-service.sh',['install','--dir',units,'--no-reload']);assert.notEqual(failed.status,0);assert.match(failed.stderr,/unit-paths failed/);assert.ok(!existsSync(units));
packages/cli/tests/stack-copy.test.mjs:232: const denied=s.run('bus-service.sh',['install','--dir',units,'--no-reload']);
packages/cli/tests/stack-copy.test.mjs:236: const allowed=s.run('bus-service.sh',['install','--dir',units,'--no-reload']);assert.equal(allowed.status,0,allowed.stderr);
packages/cli/tests/stack-copy.test.mjs:241: const dir=s.run('bus-service.sh',['install','--dir',units,'--no-reload']);assert.equal(dir.status,1);assert.ok(dir.stderr.includes(dropdir));assert.equal(readFileSync(target,'utf8'),'previous unit bytes');
packages/cli/tests/stack-copy.test.mjs:243: const broken=s.run('bus-service.sh',['install','--dir',units,'--no-reload']);assert.equal(broken.status,1);assert.ok(broken.stderr.includes(file));assert.equal(readFileSync(target,'utf8'),'previous unit bytes');
packages/cli/tests/stack-copy.test.mjs:260: let r=s.run('stack-copy.sh',['copy',s.id,'--copies',a]);assert.equal(r.status,0,r.stderr);
packages/cli/tests/stack-copy.test.mjs:261: r=s.run('stack-copy.sh',['copy','--bundle',good,s.id,'--copies',b]);assert.equal(r.status,0,r.stderr);
packages/cli/tests/stack-copy.test.mjs:269: r=s.run('stack-copy.sh',['copy','--bundle',good,s.id,'--copies',b]);assert.equal(r.status,0,r.stderr);
packages/cli/tests/stack-copy.test.mjs:273: r=spawnSync(join(s.repo,'scripts','stack-copy.sh'),['copy','--bundle','good.bundle',s.id,'--copies',c],{cwd:s.root,env:s.env,encoding:'utf8'});
packages/cli/tests/stack-copy.test.mjs:293: r=s.run('stack-copy.sh',['copy','--bundle',bundle,id,'--copies',d]);
packages/cli/tests/stack-copy.test.mjs:299: r=s.run('stack-copy.sh',['copy','--bundle',link,s.id,'--copies',d]);assert.equal(r.status,1);assert.match(r.stderr,/symlink/);
packages/cli/tests/stack-copy.test.mjs:301: assert.equal(s.run('stack-copy.sh',args).status,4,args.join(' '));
packages/cli/tests/stack-copy.test.mjs:304: const s=setup(t);assert.equal(s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies]).status,0);
packages/cli/tests/stack-copy.test.mjs:306: let r=s.run('stack-copy.sh',['wrapper',s.id,'--copies',s.copies,'--bin',s.bin]);
packages/cli/tests/stack-copy.test.mjs:308: r=s.run('stack-copy.sh',['wrapper',s.id,'--copies',s.copies,'--bin',s.bin,'--create']);assert.equal(r.status,0,r.stderr);
packages/cli/tests/stack-copy.test.mjs:311: r=s.run('stack-copy.sh',['wrapper',s.id,'--copies',s.copies,'--bin',s.bin]);assert.equal(r.status,0,r.stderr);
packages/cli/tests/stack-copy.test.mjs:316: r=s.run('stack-copy.sh',['wrapper',s.id,'--copies',s.copies,'--bin',s.bin,'--create']);
packages/cli/tests/stack-copy.test.mjs:324: r=s.run('stack-copy.sh',['wrapper',s.id,'--instance','alpha','--copies',s.copies,'--bin',s.bin,'--create']);assert.equal(r.status,0,r.stderr);
packages/cli/tests/stack-copy.test.mjs:326: r=s.run('stack-copy.sh',['wrapper',s.id,'--instance','alpha','--copies',s.copies,'--bin',s.bin]);assert.equal(r.status,0,r.stderr);
packages/cli/tests/stack-copy.test.mjs:328: r=s.run('stack-copy.sh',['wrapper',s.id,'--instance','alpha','--copies',s.copies,'--bin',s.bin,'--create']);
packages/cli/tests/stack-copy.test.mjs:332: const s=setup(t);assert.equal(s.run('stack-copy.sh',['copy',s.id,'--copies',s.copies]).status,0);
packages/cli/tests/stack-copy.test.mjs:333: assert.equal(s.run('stack-copy.sh',['wrapper',s.id,'--copies',s.copies,'--bin',s.bin,'--create']).status,0);
packages/cli/tests/stack-copy.test.mjs:337: let r=s.run('stack-copy.sh',['check','--copies',s.copies,'--bin',s.bin,'--units','external']);assert.equal(r.status,0,r.stderr);assert.equal(r.stdout,want);
packages/cli/tests/stack-copy.test.mjs:343: r=s.run('stack-copy.sh',['check','--copies',s.copies,'--bin',s.bin,...args]);assert.equal(r.status,0,r.stderr);assert.equal(r.stdout,want);
packages/cli/tests/stack-copy.test.mjs:360: const r=spawnSync('/bin/sh',['-c',enter,'stack-copy-cwd',cwd,join(s.repo,'scripts','stack-copy.sh'),...args,'--copies',copies],{env:s.env,encoding:'utf8',timeout:30000});
packages/cli/tests/limits.test.mjs:6: import {main} from '../src/cli.mjs';
packages/cli/tests/access-boot.test.mjs:14: import { bootConfig, loadSystem } from "../src/config.mjs";
packages/cli/tests/stack-instances.test.mjs:20: const CONFIG = join(SCRIPTS, "mosaic-config.mjs");
packages/cli/tests/stack-instances.test.mjs:22: const BUS_SERVICE = join(SCRIPTS, "bus-service.sh");
packages/cli/tests/stack-instances.test.mjs:24: const STACK_COPY = join(SCRIPTS, "stack-copy.sh");
packages/cli/tests/stack-instances.test.mjs:41: // sage-1918). mosaic-config.mjs creates with 0644 and lets the umask narrow it.
packages/cli/tests/hosts.test.mjs:9: import { main } from "../src/cli.mjs";
packages/cli/tests/session-launch.test.mjs:29: import { bootConfig, loadSystem } from "../src/config.mjs";
packages/cli/tests/federation-acceptance.test.mjs:18: import { socketPath, REPO } from '../src/config.mjs';
packages/cli/tests/boot-status.test.mjs:15: const CLI = fileURLToPath(new URL("../src/cli.mjs", import.meta.url));
packages/cli/tests/trackers-boot.test.mjs:8: import { bootConfig, loadSystem } from "../src/config.mjs";
packages/cli/tests/tester-threads.test.mjs:18: import { run } from "../src/cli.mjs";
packages/cli/tests/mark-read.test.mjs:5: import {main} from '../src/cli.mjs';
packages/cli/tests/session-cli.test.mjs:4: import { run } from '../src/cli.mjs';
packages/cli/tests/stack-release.test.mjs:35: assert.match(bad.err,/^stack-release: refusing check-failed: stack-copy.sh check did not pass:\nbus: /);
packages/cli/tests/stack-release.test.mjs:468: // A repo of its own, so the stand-in check replaces stack-copy.sh for this test only. Sourced, the
packages/cli/tests/stack-release.test.mjs:471: const sc=join(own,'scripts','stack-copy.sh'),at=join(COPIES,B);cpSync(sc,join(own,'scripts','stack-copy.real.sh'));
packages/cli/tests/stack-release.test.mjs:1907: // mosaic-config.mjs, with stand-ins for the bus host (cli.mjs), the Console, the Store and
packages/cli/tests/stack-release.test.mjs:1991: for(const p of ['scripts/mosaic','scripts/mosaic-config.mjs','packages/cli/src/config.mjs','packages/cli/src/errors.mjs','packages/cli/src/access.mjs','packages/cli/src/transport.mjs','packages/bus/src/business.mjs','packages/plugins/src/k
packages/cli/tests/stack-release.test.mjs:2365: bus:shOut(x.env,'bus-service.sh','render','--instance',x.name,'--copy',B,'--copies',COPIES,'--claude',e.claude),
packages/cli/tests/stack-release.test.mjs:2465: const i=shOut(x.env,'bus-service.sh','install','--copy',C1,'--copies',COPIES,'--claude',e.claude);
packages/cli/tests/stack-release.test.mjs:2616: const undo=anyConfigLoader(join(COPIES,B,'scripts/mosaic-config.mjs'),`console.error(${JSON.stringify(why)});process.exitCode=2;\n`);
packages/cli/tests/stack-release.test.mjs:2634: const original=readFileSync(e.cfg,'utf8'),now=join(COPIES,C1,'scripts/mosaic-config.mjs');
packages/cli/tests/stack-release.test.mjs:2636: const undo=anyConfigLoader(join(COPIES,B,'scripts/mosaic-config.mjs'),PRE270(readFileSync(now,'utf8')));
packages/cli/tests/stack-release.test.mjs:2651: const undos=[B,C1].map(id=>anyConfigLoader(join(COPIES,id,'scripts/mosaic-config.mjs'),`import fs from 'node:fs';fs.appendFileSync(${JSON.stringify(witness)},JSON.stringify({copy:${JSON.stringify(id)},args:process.argv.slice(2),config:proc
packages/cli/tests/stack-release.test.mjs:2679: assert.equal(r.status,3,r.stderr);assert.equal(r.stderr,`config-console-key: ${KEY_MSG(e,'old','activate','it has no scripts/mosaic-config.mjs')}\n`);
packages/cli/tests/stack-release.test.mjs:2683: const e=await mk(t),file=join(e.root,'target-copies/old/scripts/mosaic-config.mjs'),witness=join(e.root,'validate-called');
packages/cli/tests/project-name-repo.test.mjs:7: import { run } from '../src/cli.mjs';
packages/cli/tests/cli.test.mjs:4: import { main, readNotifyConfig } from "../src/cli.mjs";
packages/cli/tests/session-clear.test.mjs:24: import { bootConfig, loadSystem } from "../src/config.mjs";
packages/cli/tests/idle-stop-race.test.mjs:23: import { bootConfig, loadSystem } from "../src/config.mjs";
packages/cli/tests/tester-reads.test.mjs:11: import { main } from "../src/cli.mjs";
packages/cli/tests/grants.test.mjs:10: import { main } from "../src/cli.mjs";
packages/cli/tests/credentials.test.mjs:9: import { main } from "../src/cli.mjs";
packages/cli/tests/compactions-launch.test.mjs:32: import { bootConfig, loadSystem } from "../src/config.mjs";
packages/cli/tests/host.test.mjs:11: import { bootConfig, loadSystem, REPO } from "../src/config.mjs";
packages/cli/tests/host.test.mjs:17: const CLI = fileURLToPath(new URL("../src/cli.mjs", import.meta.url));
packages/cli/tests/host.test.mjs:837: test("bus-service.sh renders the unit and installs it into a given directory", (t) => {
packages/cli/tests/host.test.mjs:839: const script = join(REPO, "scripts", "bus-service.sh");
packages/cli/tests/host.test.mjs:875: test("bus-service.sh pins the claude binary, refuses one the unit can't carry, and renders without one", (t) => {
packages/cli/tests/host.test.mjs:877: const script = join(REPO, "scripts", "bus-service.sh");
packages/cli/tests/host.test.mjs:944: test("bus-service.sh refuses a directory pin and keeps a symlink default unresolved", (t) => {
packages/cli/tests/host.test.mjs:946: const script = join(REPO, "scripts", "bus-service.sh");
packages/cli/tests/host.test.mjs:996: test("bus-service.sh refuses a default claude without the execute bit, or in a PATH dir with a space", (t) => {
packages/cli/tests/host.test.mjs:998: const script = join(REPO, "scripts", "bus-service.sh");
packages/cli/tests/stack-upgrade.test.mjs:64: sh('bus-service.sh','install','--copy',copy,'--copies',copies,...(pre.includes('--pm')?['--pm']:[]),'--claude',get('--claude'));
packages/cli/tests/stack-upgrade.test.mjs:113: sh('bus-service.sh','install','--copy',copy,'--copies',e.hc,'--claude',e.claude);
packages/cli/tests/stack-upgrade.test.mjs:115: sh('stack-copy.sh','wrapper',copy,'--copies',e.hc,'--bin',e.bin);
packages/cli/tests/stack-upgrade.test.mjs:179: assert.deepEqual(sudoKeys(e),['git-fetch','git-merge','stack-copy.sh','activate-plan','rehearse','activate','confirm','revert','confirm-rollback']);
packages/cli/tests/stack-upgrade.test.mjs:442: assert.deepEqual(sudoKeys(e),['git-fetch','git-merge','stack-copy.sh']);
packages/cli/tests/processing-cli.test.mjs:9: import { main } from '../src/cli.mjs';
packages/cli/tests/verbs.test.mjs:8: import { main } from "../src/cli.mjs";
packages/cli/tests/verbs.test.mjs:101: `${role} isn't running${either}. systemctl --user restart mosaic-bus@demo starts it if the unit runs with --pm (scripts/bus-service.sh status demo shows pm: on). The restart stops running sessions.\n`;
packages/cli/tests/verbs.test.mjs:132: assert.equal(await text("cto"), "cto isn't running, pm has been told\n" + `pm isn't running either. systemctl --user restart mosaic-bus@demo starts it if the unit runs with --pm (scripts/bus-service.sh status demo shows pm: on). The resta
packages/cli/tests/stack-release-external.test.mjs:4: // Root's part (stop, unit install, start) runs here as the stand-in systemctl and bus-service.sh and
packages/cli/tests/stack-release-external.test.mjs:23: sh(e,'bus-service.sh','install','--copy',copy,'--copies',COPIES,...(pm?['--pm']:[]),'--claude',e.claude);
packages/cli/tests/stack-release-external.test.mjs:451: for(const f of ['scripts/stack-release.sh','scripts/stack-copy.sh'])writeFileSync(join(baseRepo,f),execFileSync('git',['-C',ROOT,'show',`${BASE}:${f}`]),{mode:0o755});
packages/cli/tests/stack-release-external.test.mjs:460: execFileSync('bash',[join(dir,'scripts','stack-copy.sh'),'check','--copies',COPIES,'--bin',e.bin,'--business','acme'],{env:e.env,stdio:'ignore'});
packages/cli/tests/app.test.mjs:9: import { main, resolveDecision, SEAT_USAGE, USAGE } from "../src/cli.mjs";
packages/cli/tests/tester.test.mjs:5: import { main } from "../src/cli.mjs";
packages/cli/tests/project.test.mjs:6: import { main, run } from '../src/cli.mjs';
packages/cli/tests/channel-cli.test.mjs:4: import { run } from "../src/cli.mjs";
packages/cli/tests/config.test.mjs:4: import { bootConfig, loadSystem } from "../src/config.mjs";
packages/cli/tests/names.test.mjs:6: import { main, USAGE } from "../src/cli.mjs";
packages/cli/tests/grants-scope.test.mjs:5: import { main } from "../src/cli.mjs";
packages/cli/tests/instances.test.mjs:16: import { main, run } from "../src/cli.mjs";
packages/cli/tests/instances.test.mjs:17: import { bootConfig, loadSystem } from "../src/config.mjs";
packages/cli/tests/thread-revoke.test.mjs:24: import { bootConfig, loadSystem } from "../src/config.mjs";
packages/cli/tests/limits-tokens.test.mjs:5: import {main} from '../src/cli.mjs';
packages/cli/tests/access.test.mjs:14: import { socketPath } from "../src/config.mjs";
packages/cli/tests/thread-launch.test.mjs:26: import { bootConfig, loadSystem } from "../src/config.mjs";
packages/cli/tests/launcher.test.mjs:23: import { bootConfig, loadSystem } from "../src/config.mjs";
packages/cli/tests/lifecycle.test.mjs:28: import { run } from "../src/cli.mjs";
packages/cli/tests/lifecycle.test.mjs:29: import { bootConfig, loadSystem } from "../src/config.mjs";
packages/cli/tests/git-socket.test.mjs:25: import { bootConfig, loadSystem, rolesDir } from "../src/config.mjs";
@@ -0,0 +1,31 @@
New refusal codes (static inventory; tests await CLEAR):
proxy-already-initialized: packages/model-proxy/tests/settings.test.mjs
proxy-client-down: packages/model-proxy/tests/client.test.mjs
proxy-client-invalid: packages/model-proxy/tests/client.test.mjs
proxy-client-redirect: packages/model-proxy/tests/client.test.mjs
proxy-client-refused: packages/model-proxy/tests/client.test.mjs
proxy-client-response: packages/model-proxy/tests/client.test.mjs
proxy-config-invalid: packages/model-proxy/tests/settings.test.mjs, packages/model-proxy/tests/config-unit.test.mjs, packages/cli/tests/model-proxy.test.mjs, packages/bus/tests/model-proxy.test.mjs
proxy-fetch-archive: packages/model-proxy/tests/fetch.test.mjs
proxy-fetch-existing: packages/model-proxy/tests/fetch.test.mjs
proxy-fetch-failed: packages/model-proxy/tests/fetch.test.mjs
proxy-fetch-hash: packages/model-proxy/tests/fetch.test.mjs
proxy-fetch-redirect: packages/model-proxy/tests/fetch.test.mjs
proxy-fetch-url: packages/model-proxy/tests/fetch.test.mjs
proxy-init-busy: packages/model-proxy/tests/settings.test.mjs
proxy-init-failed: packages/model-proxy/tests/settings.test.mjs, packages/bus/tests/model-proxy.test.mjs
proxy-install-invalid: packages/model-proxy/tests/settings.test.mjs, packages/model-proxy/tests/runner.test.mjs, packages/model-proxy/tests/config-unit.test.mjs
proxy-install-missing: packages/model-proxy/tests/settings.test.mjs
proxy-key-invalid: packages/model-proxy/tests/settings.test.mjs, packages/cli/tests/model-proxy.test.mjs, packages/bus/tests/model-proxy.test.mjs
proxy-key-mismatch: packages/cli/tests/model-proxy.test.mjs
proxy-management-key-invalid: packages/model-proxy/tests/client.test.mjs
proxy-not-configured: packages/model-proxy/tests/config-unit.test.mjs
proxy-port-in-use: packages/model-proxy/tests/settings.test.mjs
proxy-start-failed: packages/model-proxy/tests/runner.test.mjs
proxy-terminal-required: packages/cli/tests/model-proxy.test.mjs
proxy-unit-copy: packages/model-proxy/tests/config-unit.test.mjs
proxy-unit-dropin: packages/model-proxy/tests/config-unit.test.mjs
proxy-unit-exists: packages/model-proxy/tests/config-unit.test.mjs
proxy-unit-path: packages/model-proxy/tests/config-unit.test.mjs
proxy-unit-usage: packages/model-proxy/tests/config-unit.test.mjs
proxy-usage: packages/cli/tests/model-proxy.test.mjs
@@ -0,0 +1 @@
/mnt/storage/scratch/tmp/muoz5Z
+1
View File
@@ -1688,3 +1688,4 @@ are never rewritten or removed; corrections are new entries.
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.
+18 -1
View File
@@ -418,6 +418,7 @@ export class Broker {
#requirements;
#organization;
#credentialVerbs;
#proxyInit;
#project;
#library;
// C2-06 (design 5.2, B7): cap -> the session's kept web.fetch pages, in a library business only.
@@ -476,7 +477,7 @@ export class Broker {
#clock = 0;
#processing;
// credentialStatus is the runtime's metadata-only Credentials.status(); the broker never gets use().
constructor({ store, businesses, secretCheck = () => {}, credentialStatus = () => [], organization = null, project = null, library = null, usage = null, limits = null, harnessOn = null, credentialVerbs = null, hostGrant = null, hostKeys = null, processing = null, testerOn = null, grantScope = null, roleFile = null, giteaGrant = null, giteaKeys = null, access = null, accessStamp = null, compactions = null }) {
constructor({ store, businesses, secretCheck = () => {}, credentialStatus = () => [], organization = null, project = null, library = null, usage = null, limits = null, harnessOn = null, credentialVerbs = null, proxyInit = null, hostGrant = null, hostKeys = null, processing = null, testerOn = null, grantScope = null, roleFile = null, giteaGrant = null, giteaKeys = null, access = null, accessStamp = null, compactions = null }) {
if (!object(businesses) || !Object.keys(businesses).length) fail('invalid-business');
// Row 194 b15 (sage-2501): the function-argument checks come before anything reads the store.
if (harnessOn !== null && typeof harnessOn !== 'function') throw new TypeError('harnessOn must be a function');
@@ -500,6 +501,7 @@ export class Broker {
this.#organization = organization;
// Row 199: the runtime's credential lifecycle (credential-verbs.mjs); it holds the store, not the broker.
this.#credentialVerbs = credentialVerbs;
this.#proxyInit = proxyInit;
this.#hostGrant = hostGrant;
this.#hostKeys = hostKeys;
this.#giteaGrant = giteaGrant;
@@ -4818,6 +4820,21 @@ export class Broker {
if (!!s.console !== CONSOLE_VERBS.includes(request.verb)) fail('human-required');
const args = request.args ?? {};
if (!object(args)) fail('invalid-request');
if (request.verb === 'proxy.init') {
this.#human(s);
if (!this.#proxyInit) fail('not-configured');
keys(args, ['key', 'port'], ['key']);
if (typeof args.key !== 'string' || !args.key.trim() || args.key.length > 4096 || /[\x00-\x1f\x7f]/.test(args.key)) fail('proxy-key-invalid');
if (Object.hasOwn(args, 'port') && (!Number.isInteger(args.port) || args.port < 1024 || args.port > 65535 || args.port === 8317)) fail('proxy-config-invalid');
// Async initialization is outside the store transaction. Refusals retain codes only.
return Promise.resolve().then(() => this.#proxyInit(args)).then(result => {
if (!object(result) || Object.keys(result).sort().join(',') !== 'binary,port'
|| !Number.isInteger(result.port) || result.port < 1024 || result.port > 65535 || result.port === 8317
|| typeof result.binary !== 'string' || !result.binary.startsWith('/')) fail('proxy-init-failed');
this.#store.transaction(() => this.#event(s, 'action.allowed', { call: 'proxy.init', port: result.port }, null));
return result;
}).catch(error => { throw this.#refused(s, new BusError(/^proxy-[a-z-]+$/.test(error.code ?? '') ? error.code : 'proxy-init-failed'), request); });
}
this.#secretCheck(args);
if (request.verb === 'processing.halt' || request.verb === 'processing.resume')
return this.#requestProcessing(s, request.verb, structuredClone(args));
+19
View File
@@ -13,6 +13,8 @@ import { compactionCount, storeDir } from '../../seat/src/compactions.mjs';
import { publishRemote, workingRepo, WorkingError } from '../../seat/src/working.mjs';
import { createLoader, hasModule } from '../../plugins/src/loader.mjs';
import { pluginSettings } from '../../business/src/plugin-settings.mjs';
import { loadManagementClient } from '../../model-proxy/src/client.mjs';
import { initializeProxy } from '../../model-proxy/src/settings.mjs';
import { instanceSettings } from '../../business/src/instance-settings.mjs';
import { organizationWriter, OrganizationError, setReplyLimits } from '../../business/src/organization.mjs';
import { credentialVerbs } from './credential-verbs.mjs';
@@ -142,6 +144,8 @@ export async function startBroker({
// owner of the business (Webby N1). The boot config carries them; no socket verb sets them. Empty, no
// Console owner binds and the terminal owner is unchanged.
consoleOwners = [],
// P1 validated sidecar from the trusted host; never a socket request setting.
proxy = null,
}) {
if (!Array.isArray(consoleOwners) || consoleOwners.length > 512 || consoleOwners.some((o) => o === null || typeof o !== 'object' || Array.isArray(o) ||
Object.keys(o).length !== 3 || ['provider', 'iss', 'sub'].some((k) => typeof o[k] !== 'string' || !o[k] || o[k].length > 512)))
@@ -154,8 +158,10 @@ export async function startBroker({
|| (Object.hasOwn(librarySync, 'intervalMs') && !(Number.isSafeInteger(librarySync.intervalMs) && librarySync.intervalMs > 0))
|| (Object.hasOwn(librarySync, 'configDir') && typeof librarySync.configDir !== 'string')))
throw new BusError('invalid-host-request');
let modelProxyClient = null;
let store, credentials, server, adapter, processing, syncTimer = null, syncPass = null, accessTimer = null;
try {
const proxyReceipt = entry => process.stderr.write(`model-proxy: ${JSON.stringify(entry)}\n`);
const references = {};
for (const [business, b] of Object.entries(businesses)) {
for (const [role, r] of Object.entries(b.roles))
@@ -170,6 +176,12 @@ export async function startBroker({
// Row 199 (#1697, design D1): the per-agent store is <configDir>/secrets/agents.
const storeRoot = organization ? join(organization.dir, 'secrets', 'agents') : null;
store = new Store(dataRoot);
// Store's writer lock admits one bus per data root before it holds the management client.
if (proxy) {
if (!organization) throw new BusError('not-configured');
modelProxyClient = await loadManagementClient({ configDir: organization.dir, proxy, receipt: proxyReceipt });
}
// Row 234 (#1740, r6 X4 third bullet): each host grant record gives its agent a store reference to
// host.<id>, so a restart finds the keys the grant executor wrote. The loader refuses host.<id> in a
// role's credentials, so these never meet a business-file reference.
@@ -408,6 +420,11 @@ export async function startBroker({
credentialStatus: () => credentials.status(),
// Row 199 (#1697, design section 6): issue, rotate, revoke and delete, live with no restart; each
// rewrites or records through the organization writer, so changes.jsonl holds the receipt.
proxyInit: organization ? async args => {
const settings = await initializeProxy({ configDir: organization.dir, dataRoot, ...args });
modelProxyClient = await loadManagementClient({ configDir: organization.dir, proxy: settings, receipt: proxyReceipt });
return settings;
} : null,
credentialVerbs: organization
? credentialVerbs({ credentials, writer: organizationWriter({ dir: organization.dir, rolesDir: organization.rolesDir }) })
: null,
@@ -665,6 +682,8 @@ export async function startBroker({
return {
broker,
credentials,
// P2 consumes this inside the bus only. No process IPC/verb returns it.
get modelProxy() { return modelProxyClient; },
path,
bindLaunch,
endLaunch: (r) => broker.endLaunch(r),
+1
View File
@@ -151,6 +151,7 @@ export async function serve({ broker, path, authenticateHuman = null, authentica
HOSTS_VERBS.includes(r.verb) ||
// Row 236 (#1742): the grants verbs may call Gitea several times, 15 s each at most.
GRANTS_VERBS.includes(r.verb) ||
r.verb === 'proxy.init' ||
(r.verb === 'decision.raise' && (Object.hasOwn(r.args ?? {}, 'spec_path') || Object.hasOwn(r.args ?? {}, 'spec_commit') ||
Object.hasOwn(r.args ?? {}, 'grant')))) {
const own = transient;
+16
View File
@@ -0,0 +1,16 @@
// P1's human proof boundary; all records and fixture input under short TMPDIR.
import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { Broker } from '../src/broker.mjs';
import { Store } from '../src/store.mjs';
const role={authority:{withinRole:['message.send'],crossRole:[]}};
function fixture(t,proxyInit){const store=new Store(mkdtempSync(join(tmpdir(),'p1-bus-')));t.after(()=>store.close());const b=new Broker({store,proxyInit,businesses:{demo:{id:'demo',human:'owner',arbiters:{technical:'coder',delivery:'coder'},vars:{'bus.tester':true},roles:{coder:role}}}});return {store,b,human:b.bindHuman({business:'demo',human:'owner',via:'cli',outsideAgent:true}),tester:b.bindTester({business:'demo'}),reader:b.bindReader({business:'demo'}),agent:b.bindLaunch({business:'demo',role:'coder',run:'p1-run',harness:'pi'})};}
test('proxy.init only accepts path-bound outside-agent human; refuses agent/tester/reader',async t=>{const calls=[];const f=fixture(t,async a=>{calls.push(a);return {port:a.port,binary:'/fixture'};});for(const cap of [f.tester,f.agent])assert.throws(()=>f.b.request(cap,{verb:'proxy.init',args:{key:'p1-management'}}),e=>e.code==='human-required');assert.throws(()=>f.b.request(f.reader,{verb:'proxy.init',args:{key:'p1-management'}}),e=>e.code==='read-only');assert.equal(calls.length,0);assert.deepEqual(await f.b.request(f.human,{verb:'proxy.init',args:{key:'p1-management',port:18432}}),{port:18432,binary:'/fixture'});assert.equal(JSON.stringify(f.store.all('SELECT body FROM events')).includes('p1-management'),false);});
for(const [code,args]of [['proxy-key-invalid',{key:'a\nb'}],['proxy-config-invalid',{key:'p1-management',port:8317}],['invalid-request',{key:'p1-management',binary:'/forged'}]])test(`proxy.init ${code}`,t=>{const f=fixture(t,()=>{throw Error('must not call');});assert.throws(()=>f.b.request(f.human,{verb:'proxy.init',args}),e=>e.code===code);});
test('proxy.init not-configured',t=>{const f=fixture(t,null);assert.throws(()=>f.b.request(f.human,{verb:'proxy.init',args:{key:'p1-management'}}),e=>e.code==='not-configured');});
test('proxy.init callback error redacts key and maps outcome to named refusal',async t=>{const f=fixture(t,()=>{throw Error('p1-management');});await assert.rejects(f.b.request(f.human,{verb:'proxy.init',args:{key:'p1-management'}}),e=>e.code==='proxy-init-failed'&&!e.message.includes('p1-management'));assert.equal(JSON.stringify(f.store.all('SELECT body FROM events')).includes('p1-management'),false);});
test('socket awaits proxy.init and does not return a Promise object or retain human cap',async t=>{const {serve}=await import('../src/server.mjs');const {Client}=await import('../src/client.mjs');const f=fixture(t,async()=>{await new Promise(r=>setTimeout(r,10));return {port:18432,binary:'/fixture'};});const root=mkdtempSync(join(tmpdir(),'p1-sock-'));const socket=join(root,'s');const server=await serve({broker:f.b,path:socket,peerReader:{read:async()=>({pid:process.pid,uid:process.getuid(),gid:process.getgid()}),close(){}},authenticateHuman:()=>f.b.bindHuman({business:'demo',human:'owner',via:'cli',outsideAgent:true})});t.after(()=>server.close());const client=new Client({path:socket,human:{business:'demo',pid:process.pid,startTime:'1',nonce:'fixture'}});assert.deepEqual(await client.call('proxy.init',{key:'p1-management'}),{port:18432,binary:'/fixture'});});
test('proxy-init-failed: extra callback fields cannot return a management key',async t=>{const f=fixture(t,async()=>({port:18432,binary:'/fixture',key:'p1-management'}));await assert.rejects(f.b.request(f.human,{verb:'proxy.init',args:{key:'p1-management'}}),e=>e.code==='proxy-init-failed');assert.equal(JSON.stringify(f.store.all('SELECT body FROM events')).includes('p1-management'),false);});
+3
View File
@@ -18,6 +18,7 @@ import { resolveThread, threadCommand, threadMembers, threadsCommand, THREAD_TAL
import { sessionCommand, SESSION_USAGE } from "./session.mjs";
import { limitsCommand, LIMITS_USAGE } from "./limits.mjs";
import { pluginsCommand, PLUGINS_USAGE } from "./plugins.mjs";
import { proxyCommand } from "./model-proxy.mjs";
import { credentialsCommand, CREDENTIALS_USAGE } from "./credentials.mjs";
import { hostsCommand, HOSTS_USAGE } from "./hosts.mjs";
import { grantsCommand, GRANTS_USAGE } from "./grants.mjs";
@@ -44,6 +45,7 @@ export const USAGE = `usage:
${LIMITS_USAGE}
${PLUGINS_USAGE}
${CREDENTIALS_USAGE}
mosaic proxy fetch | init [--port N] [--business ID]
${HOSTS_USAGE}
${GRANTS_USAGE}
${INSTANCES_USAGE}
@@ -654,6 +656,7 @@ export async function main(argv, io = { env: process.env, stdin: process.stdin,
if (verb === "usage") return usageCommand(rest, io, p => callFor(p, io, deps));
if (verb === "plugins") return pluginsCommand(rest, io, (p) => callFor(p, io, deps));
// Row 199 (#1697): issue and rotate read the token on stdin.
if (verb === "proxy") return proxyCommand(rest, io, { system: () => deps.system ?? loadSystem({ env: io.env }), callFor: p => callFor(p, io, deps), prompt: deps.promptProxyKey });
if (verb === "credentials") return credentialsCommand(rest, io, (p) => callFor(p, io, deps));
// Row 234 (#1740, r6 E4): the host records.
if (verb === "hosts") return hostsCommand(rest, io, (p) => callFor(p, io, deps));
+1
View File
@@ -130,6 +130,7 @@ export function bootConfig({ system, businessId, env = process.env, warn = () =>
for (const instance of Object.keys(b.roles)) resolved[instance] = resolveInstance({ system: vars, business: b, instance });
const config = {
dataRoot: system.dataRoot,
...(system.proxy ? { proxy: system.proxy } : {}),
businesses: { [b.id]: busBusiness(b, resolved) },
organization: {dir:configDir(env),rolesDir:rolesDir(env),system:vars},
launches: [],
+64
View File
@@ -0,0 +1,64 @@
import { CliError } from './errors.mjs';
import { refuseInsideAgent } from './transport.mjs';
import { fetchPinned } from '../../model-proxy/src/fetch.mjs';
const failure = (code, exitCode = 2) => Object.assign(new CliError(code, exitCode), { code });
// A raw terminal suppresses echo; input is never argv, env, readline history or stdout.
export function promptProxyKey(io, label) {
if (!io.stdin.isTTY || !io.stdout.isTTY || typeof io.stdin.setRawMode !== 'function') throw failure('proxy-terminal-required', 3);
io.stderr.write(label);
const wasRaw = io.stdin.isRaw;
return new Promise((resolve, reject) => {
let value = '';
const finish = (error) => {
io.stdin.off('data', data); io.stdin.off('end', end); io.stdin.off('error', failed);
io.stdin.setRawMode(wasRaw); io.stdin.pause(); io.stderr.write('\n');
error ? reject(error) : resolve(value);
};
const end = () => finish(failure('proxy-key-invalid'));
const failed = () => finish(failure('proxy-key-invalid'));
const data = chunk => {
for (const char of chunk.toString()) {
if (char === '\u0003' || char === '\u0004') return finish(failure('proxy-key-invalid'));
if (char === '\r' || char === '\n') return finish();
if (char === '\u007f' || char === '\b') value = value.slice(0, -1);
else value += char;
if (value.length > 4096) return finish(failure('proxy-key-invalid'));
}
};
io.stdin.setRawMode(true); io.stdin.on('data', data); io.stdin.once('end', end); io.stdin.once('error', failed); io.stdin.resume();
});
}
export async function proxyCommand(args, io, { system, callFor, prompt = promptProxyKey, fetch = fetchPinned }) {
const [verb, ...rest] = args;
if (args.includes('--tester')) throw failure('human-required', 3);
if (verb === 'init') {
try { refuseInsideAgent(io.env); } catch { throw failure('human-required', 3); }
}
if (!['fetch', 'init'].includes(verb)) throw failure('proxy-usage', 4);
const parsed = { flags: new Set(), values: {} }; let port;
for (let i = 0; i < rest.length; i++) {
const flag = rest[i];
if (verb !== 'init' || !['--business', '--port'].includes(flag) || parsed.values[flag] !== undefined || !rest[i + 1]) throw failure('proxy-usage', 4);
parsed.values[flag] = rest[++i];
}
if (parsed.values['--port'] !== undefined) {
if (!/^[0-9]+$/.test(parsed.values['--port'])) throw failure('proxy-config-invalid');
port = Number(parsed.values['--port']);
if (port < 1024 || port > 65535 || port === 8317) throw failure('proxy-config-invalid');
}
try {
const config = typeof system === 'function' ? system() : system;
if (verb === 'fetch') { const result = await fetch({ dataRoot: config.dataRoot }); io.stdout.write(JSON.stringify({ binary: result.binary, panel: result.panel }) + '\n'); return; }
if (!io.stdin.isTTY || !io.stdout.isTTY) throw failure('proxy-terminal-required', 3);
// Establish that the CLI is eligible before reading sensitive input. The bus verifies the proof.
const call = callFor(parsed);
const first = await prompt(io, 'Management key: '), second = await prompt(io, 'Repeat management key: ');
if (first !== second) throw failure('proxy-key-mismatch');
if (!first.trim() || first.length > 4096 || /[\x00-\x1f\x7f]/.test(first)) throw failure('proxy-key-invalid');
const result = await call('proxy.init', { key: first, ...(port === undefined ? {} : { port }) });
io.stdout.write(JSON.stringify(result) + '\n');
} catch (e) {
if (e instanceof CliError) throw e;
throw failure(/^proxy-[a-z-]+$/.test(e.code ?? '') ? e.code : verb === 'fetch' ? 'proxy-fetch-failed' : 'proxy-init-failed');
}
}
@@ -0,0 +1,22 @@
# P1 pinned model proxy. The runner checks PIN before spawning the executable.
[Unit]
Description=Mosaic model proxy
StartLimitIntervalSec=600
StartLimitBurst=5
[Service]
Type=simple
WorkingDirectory=@REPO@
Environment=PATH=@PATH@
@CONFIG_ENV@
ExecStart=@NODE@ @REPO@/packages/model-proxy/src/run.mjs
Restart=on-failure
RestartSec=15
RestartPreventExitStatus=2 3 4
UMask=0077
NoNewPrivileges=yes
KillMode=mixed
TimeoutStopSec=60
[Install]
WantedBy=default.target
+19
View File
@@ -0,0 +1,19 @@
// P1: no real proxy or config. The CLI's test transport captures one fixture key.
import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { main } from '../src/cli.mjs';
import { io } from './helpers.mjs';
const code=c=>e=>e.code===c;
const fakeSystem=()=>({dataRoot:join(mkdtempSync(join(tmpdir(),'p1-cli-')),'data')});
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'));}});
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);});
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'));});
test('proxy-terminal-required: stdin redirection refuses',async()=>{await assert.rejects(main(['proxy','init'],io(),{system:fakeSystem()}),code('proxy-terminal-required'));});
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'));});
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')));
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'));});
test('real no-echo prompt restores terminal state and never writes the key',async()=>{const {promptProxyKey}=await import('../src/model-proxy.mjs');const x=io();x.stdin.isTTY=true;x.stdout.isTTY=true;const modes=[];x.stdin.isRaw=false;x.stdin.setRawMode=value=>{modes.push(value);x.stdin.isRaw=value;};const answer=promptProxyKey(x,'Management key: ');x.stdin.write('p1-management\r');assert.equal(await answer,'p1-management');assert.deepEqual(modes,[true,false]);assert.equal(x.out.text,'');assert.equal(x.err.text.includes('p1-management'),false);});
test('real no-echo prompt ctrl-C refuses and restores echo',async()=>{const {promptProxyKey}=await import('../src/model-proxy.mjs');const x=io();x.stdin.isTTY=true;x.stdout.isTTY=true;x.stdin.isRaw=false;x.stdin.setRawMode=value=>{x.stdin.isRaw=value;};const answer=promptProxyKey(x,'Management key: ');x.stdin.write('\u0003');await assert.rejects(answer,code('proxy-key-invalid'));assert.equal(x.stdin.isRaw,false);});
+17
View File
@@ -0,0 +1,17 @@
{
"version": "8.0.42",
"archive": {
"asset": "CLIProxyAPI_8.0.42_linux_amd64.tar.gz",
"url": "https://github.com/router-for-me/CLIProxyAPI/releases/download/v8.0.42/CLIProxyAPI_8.0.42_linux_amd64.tar.gz",
"sha256": "1e10566fe0042ed5a9b80306cf296bf86a83de590cd967a8b177988b896c8d31"
},
"binary": {
"sha256": "74661f8cf949977870231359e8bf4344e07946766a673c08fa25a3e5f72fc95e"
},
"panel": {
"version": "1.26.7",
"asset": "management.html",
"url": "https://github.com/router-for-me/Cli-Proxy-API-Management-Center/releases/download/v1.26.7/management.html",
"sha256": "8c9c6b7d3fb311e6aad5d06e1993e53a2d28389e640448a1bc6304ed20b057b5"
}
}
+31
View File
@@ -0,0 +1,31 @@
# Model proxy (P1, #1827)
The bus initializes and manages one pinned CLIProxyAPI per instance. PIN records
release URLs and independently reviewable SHA256s of the archive, executable and
panel. `mosaic proxy fetch` verifies both downloads before installing under
`<dataRoot>/opt/cliproxyapi/<version>/`; there is no runtime panel update.
From a human terminal, with the business bus running:
mosaic proxy fetch
mosaic proxy init [--port N] [--business ID]
Init prompts twice without echo and passes the key to the bus through its
path-bound human proof. An agent, tester, redirected stdin or mismatched
confirmation refuses. The bus exclusively creates proxy.json beside config.json,
the private management-key file and initial proxy directory. It never rewrites
config.json. Init does not install/start a service. The validated system config
contains `proxy.port` and `proxy.binary` only when proxy.json exists.
The management client is imported by the bus; it only GETs/PUTs the v8 list on
127.0.0.1, never follows redirects, and emits receipts with SHA256 key IDs only.
Client-key values are held in memory, never returned by a bus verb. P2 adds
per-agent lifecycle integration; this row creates only the sentinel.
The unit starts through a hash-checking runner with the pinned panel directory
and `-local-model`, so catalogs require no network. Render/install is explicit;
no test or acceptance script installs or starts a host unit.
Acceptance is agents/magica/work/queue-329/acceptance.sh, outside the generic gate.
It uses a fresh scratch root and private network namespace. v1.26.7 panel fails
CSP sandbox storage restrictions: the Console forward remains blocked for P4.
+12
View File
@@ -0,0 +1,12 @@
{
"name": "@mosaic/model-proxy",
"private": true,
"type": "module",
"description": "Pinned per-instance CLIProxyAPI fetch, initialization and bus management client",
"engines": {
"node": ">=24"
},
"scripts": {
"test": "node --test tests/*.test.mjs"
}
}
+78
View File
@@ -0,0 +1,78 @@
// Bus-only in-memory management client. Values never enter receipts or errors.
import http from 'node:http';
import { ProxyError, refuse, sha256, regularBytes, validManagementKey } from './common.mjs';
import { lstatSync } from 'node:fs';
import { join } from 'node:path';
const KEY_PATH = '/v8/management/config/access/api-keys';
const validKeys = value => Array.isArray(value) && value.length <= 100000 && value.every(k => typeof k === 'string' && k.length > 0 && k.length <= 4096 && !/[\r\n\x00]/.test(k));
export class ManagementClient {
#base; #key; #receipt; #timeout;
constructor({ baseUrl, key, receipt = () => {}, timeout = 5000 }) {
let u; try { u = new URL(baseUrl); } catch { refuse('proxy-client-invalid'); }
if (u.protocol !== 'http:' || u.hostname !== '127.0.0.1' || !u.port || Number(u.port) < 1024 || u.port === '8317'
|| u.username || u.password || u.pathname !== '/' || u.search || u.hash
|| !validManagementKey(key)
|| typeof receipt !== 'function' || !Number.isSafeInteger(timeout) || timeout <= 0) refuse('proxy-client-invalid');
this.#base = u.origin; this.#key = key; this.#receipt = receipt; this.#timeout = timeout;
}
#request(method, keys, onStatus) {
// node:http addresses the fixed numeric loopback directly. An ambient fetch
// dispatcher or HTTP_PROXY cannot send the Authorization header elsewhere.
let deadline;
return new Promise((resolve, reject) => {
let received = false;
const req = http.request(this.#base + KEY_PATH, {
method, headers: { Authorization: `Bearer ${this.#key}`, ...(method === 'PUT' ? { 'Content-Type': 'application/json' } : {}) },
}, response => {
received = true; onStatus(response.statusCode);
const error = response.statusCode >= 300 && response.statusCode < 400 ? 'proxy-client-redirect'
: response.statusCode < 200 || response.statusCode >= 300 ? 'proxy-client-refused' : null;
if (error) { response.destroy(); reject(new ProxyError(error)); return; }
let size = 0; const chunks = [];
response.on('data', chunk => {
size += chunk.length;
if (size > 8 * 1024 * 1024) { response.destroy(); reject(new ProxyError('proxy-client-response')); }
else chunks.push(chunk);
});
response.on('aborted', () => reject(new ProxyError('proxy-client-response')));
response.on('error', () => reject(new ProxyError('proxy-client-response')));
response.on('end', () => {
try { resolve(JSON.parse(Buffer.concat(chunks))); }
catch { reject(new ProxyError('proxy-client-response')); }
});
});
req.on('error', () => reject(new ProxyError(received ? 'proxy-client-response' : 'proxy-client-down')));
deadline = setTimeout(() => { req.destroy(); reject(new ProxyError('proxy-client-down')); }, this.#timeout);
req.end(method === 'PUT' ? JSON.stringify(keys) : undefined);
}).finally(() => clearTimeout(deadline));
}
async #call(method, keys) {
let status = 0, ids = [];
try {
const body = await this.#request(method, keys, value => { status = value; });
if (method === 'GET') {
if (!validKeys(body)) refuse('proxy-client-response');
ids = body.map(sha256).map(h => h.slice(0, 12));
return body;
}
if (!body || body.status !== 'ok') refuse('proxy-client-response');
ids = keys.map(sha256).map(h => h.slice(0, 12));
return { ok: true };
} finally { this.#receipt({ call: `${method} ${KEY_PATH}`, status, keyIds: ids }); }
}
getKeys() { return this.#call('GET'); }
putKeys(keys) { if (!validKeys(keys)) refuse('proxy-client-invalid'); return this.#call('PUT', keys); }
}
// Trusted bus boot only: no CLI/Console/session exports this value or client.
export async function loadManagementClient({ configDir, proxy, receipt }) {
const file = join(configDir, 'secrets', 'proxy-management.key');
let key;
try {
const stat = lstatSync(file);
if ((stat.mode & 0o777) !== 0o600 || !stat.isFile() || stat.isSymbolicLink()) refuse('proxy-management-key-invalid');
key = regularBytes(file, 'proxy-management-key-invalid').toString('utf8');
if (!validManagementKey(key)) refuse('proxy-management-key-invalid');
} catch { refuse('proxy-management-key-invalid'); }
return new ManagementClient({ baseUrl: `http://127.0.0.1:${proxy.port}`, key, receipt });
}
+36
View File
@@ -0,0 +1,36 @@
import fs from 'node:fs';
import path from 'node:path';
import { validateHeaderValue } from 'node:http';
import { createHash } from 'node:crypto';
export class ProxyError extends Error {
constructor(code) { super(code); this.name = 'ProxyError'; this.code = code; }
}
export const refuse = code => { throw new ProxyError(code); };
export const sha256 = value => createHash('sha256').update(value).digest('hex');
export const PIN = Object.freeze(JSON.parse(fs.readFileSync(new URL('../PIN', import.meta.url), 'utf8')));
// Refuse symlink components before handling any state. Callers only create owned new leaves.
export function safePath(file, code) {
if (typeof file !== 'string' || !path.isAbsolute(file) || path.resolve(file) !== file) refuse(code);
let current = path.parse(file).root;
for (const part of file.slice(current.length).split('/').filter(Boolean)) {
current = path.join(current, part);
try { if (fs.lstatSync(current).isSymbolicLink()) refuse(code); }
catch (e) { if (e.code !== 'ENOENT') throw e; }
}
}
export function regularBytes(file, code) {
safePath(file, code);
let fd;
try {
fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
if (!fs.fstatSync(fd).isFile()) refuse(code);
return fs.readFileSync(fd);
} catch { refuse(code); }
finally { if (fd !== undefined) fs.closeSync(fd); }
}
export function validManagementKey(key) {
if (typeof key !== 'string' || !key.trim() || key.length > 4096 || /[\x00-\x1f\x7f]/.test(key)) return false;
try { validateHeaderValue('Authorization', `Bearer ${key}`); return true; }
catch { return false; }
}
+73
View File
@@ -0,0 +1,73 @@
import fs from 'node:fs';
import path from 'node:path';
import { execFileSync } from 'node:child_process';
import { PIN, refuse, safePath, sha256, regularBytes } from './common.mjs';
import { installPaths } from './settings.mjs';
const RELEASE_HOSTS = new Set(['github.com', 'release-assets.githubusercontent.com', 'objects.githubusercontent.com']);
const releaseUrl = u => u.protocol === 'https:' && !u.username && !u.password && !u.port && !u.hash
&& u.hostname === 'github.com' && /^\/router-for-me\/(CLIProxyAPI|Cli-Proxy-API-Management-Center)\/releases\/download\/[^/]+\/[^/]+$/.test(u.pathname);
// testOrigin is an in-process local fake-server seam, never read from argv/config/env.
export async function downloadRelease(url, { testOrigin } = {}) {
let current; try { current = new URL(url); } catch { refuse('proxy-fetch-url'); }
const local = testOrigin && /^http:\/\/127\.0\.0\.1:[0-9]+$/.test(testOrigin) ? testOrigin : null;
if (!(local ? current.origin === local : releaseUrl(current))) refuse('proxy-fetch-url');
for (let redirects = 0; redirects <= 5; redirects++) {
let response;
try { response = await fetch(current, { redirect: 'manual', signal: AbortSignal.timeout(120000) }); }
catch { refuse('proxy-fetch-failed'); }
if (response.status >= 300 && response.status < 400) {
const location = response.headers.get('location'); await response.body?.cancel();
let next; try { if (!location) throw Error(); next = new URL(location, current); } catch { refuse('proxy-fetch-redirect'); }
if (next.username || next.password || next.hash || (local ? next.origin !== local : next.protocol !== 'https:' || next.port || !RELEASE_HOSTS.has(next.hostname))) refuse('proxy-fetch-redirect');
current = next; continue;
}
if (!response.ok) { await response.body?.cancel(); refuse('proxy-fetch-failed'); }
const reader = response.body.getReader(), chunks = []; let size = 0;
try { for (;;) { const { done, value } = await reader.read(); if (done) break; size += value.length; if (size > 256 * 1024 * 1024) { await reader.cancel(); refuse('proxy-fetch-failed'); } chunks.push(Buffer.from(value)); } }
catch { refuse('proxy-fetch-failed'); }
return Buffer.concat(chunks);
}
refuse('proxy-fetch-redirect');
}
export async function fetchPinned({ dataRoot, pin = PIN, download = downloadRelease }) {
safePath(dataRoot, 'proxy-fetch-existing');
const paths = installPaths(dataRoot, pin);
safePath(paths.dir, 'proxy-fetch-existing');
// Fetch and verify both assets before any install filesystem mutation.
const archive = await download(pin.archive.url);
if (sha256(archive) !== pin.archive.sha256) refuse('proxy-fetch-hash');
const panel = await download(pin.panel.url);
if (sha256(panel) !== pin.panel.sha256) refuse('proxy-fetch-hash');
let binary;
try {
const listing = execFileSync('tar', ['-tzf', '-'], { input: archive, maxBuffer: 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] }).toString().split('\n').filter(Boolean);
if (listing.filter(x => x === 'cli-proxy-api').length !== 1) refuse('proxy-fetch-archive');
// Only the root executable is read to stdout. No archive path is extracted to disk.
binary = execFileSync('tar', ['-xOzf', '-', 'cli-proxy-api'], { input: archive, maxBuffer: 256 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] });
} catch { refuse('proxy-fetch-archive'); }
if (sha256(binary) !== pin.binary.sha256) refuse('proxy-fetch-hash');
try {
fs.lstatSync(paths.dir);
if (fs.readdirSync(paths.dir).sort().join(',') !== 'cli-proxy-api,static'
|| fs.readdirSync(path.join(paths.dir, 'static')).join(',') !== 'management.html'
|| sha256(regularBytes(paths.binary, 'proxy-fetch-existing')) !== pin.binary.sha256
|| sha256(regularBytes(paths.panel, 'proxy-fetch-existing')) !== pin.panel.sha256) refuse('proxy-fetch-existing');
return paths;
} catch (e) { if (e.code !== 'ENOENT') throw e; }
const parent = path.dirname(paths.dir);
fs.mkdirSync(parent, { recursive: true, mode: 0o700 });
const stage = fs.mkdtempSync(path.join(parent, '.fetch-'));
// Lock the version name with exclusive mkdir; a competing installer cannot overwrite it.
let ownsTarget = false;
try {
fs.writeFileSync(path.join(stage, 'cli-proxy-api'), binary, { flag: 'wx', mode: 0o755 });
fs.mkdirSync(path.join(stage, 'static'), { mode: 0o700 });
fs.writeFileSync(path.join(stage, 'static', 'management.html'), panel, { flag: 'wx', mode: 0o644 });
try { fs.mkdirSync(paths.dir, { mode: 0o700 }); ownsTarget = true; } catch { refuse('proxy-fetch-existing'); }
// Same-filesystem rename of each already-verified file; any failure removes only our target.
fs.renameSync(path.join(stage, 'cli-proxy-api'), paths.binary);
fs.renameSync(path.join(stage, 'static'), path.join(paths.dir, 'static'));
return paths;
} catch (e) { if (ownsTarget) fs.rmSync(paths.dir, { recursive: true }); if (e.code?.startsWith('proxy-')) throw e; refuse('proxy-fetch-failed'); }
finally { fs.rmSync(stage, { recursive: true, force: true }); }
}
+19
View File
@@ -0,0 +1,19 @@
// User-unit entry point: validate config and both pinned assets before executing anything.
import { spawnSync } from 'node:child_process';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { startVerifiedProxy } from './runner.mjs';
const repo = fileURLToPath(new URL('../../../', import.meta.url));
try {
const result = spawnSync(process.execPath, [path.join(repo, 'scripts/mosaic-config.mjs'), 'validate'], { encoding: 'utf8', env: process.env });
if (result.status !== 0) throw Error('proxy-config-invalid');
const config = JSON.parse(result.stdout);
const child = startVerifiedProxy(config, {
onError: code => { process.stderr.write(code + '\n'); process.exitCode = 3; },
onExit: code => { process.exitCode = code; },
});
for (const signal of ['SIGTERM', 'SIGINT']) process.on(signal, () => child.kill(signal));
} catch (e) {
process.stderr.write((/^proxy-[a-z-]+$/.test(e.code ?? e.message) ? (e.code ?? e.message) : 'proxy-start-failed') + '\n');
process.exitCode = 3;
}
+20
View File
@@ -0,0 +1,20 @@
import { spawn } from 'node:child_process';
import path from 'node:path';
import { verifyInstall } from './settings.mjs';
import { refuse } from './common.mjs';
// In-process seams let the no-binary gate verify failure handling and signal forwarding.
// The unit entry point supplies no seams and always uses PIN verification.
export function startVerifiedProxy(config, { verify = verifyInstall, spawnImpl = spawn, env = process.env, onError = () => {}, onExit = () => {} } = {}) {
if (!config.proxy) refuse('proxy-not-configured');
const install = verify(config.dataRoot);
let child;
try {
child = spawnImpl(install.binary, ['-config', path.join(config.dataRoot, 'proxy/config.yaml'), '-local-model'], {
cwd: path.join(config.dataRoot, 'proxy'), stdio: 'inherit',
env: { HOME: env.HOME, PATH: '/usr/bin:/bin', MANAGEMENT_STATIC_PATH: path.dirname(install.panel) },
});
} catch { refuse('proxy-start-failed'); }
child.once('error', () => onError('proxy-start-failed'));
child.once('exit', (code, signal) => onExit(signal ? 1 : (code ?? 1)));
return child;
}
+95
View File
@@ -0,0 +1,95 @@
import fs from 'node:fs';
import path from 'node:path';
import net from 'node:net';
import { randomBytes } from 'node:crypto';
import { PIN, refuse, safePath, regularBytes, sha256, validManagementKey } from './common.mjs';
export function installPaths(dataRoot, pin = PIN) {
const dir = path.join(dataRoot, 'opt', 'cliproxyapi', pin.version);
return { dir, binary: path.join(dir, 'cli-proxy-api'), panel: path.join(dir, 'static', 'management.html') };
}
export function validateProxy(value, dataRoot, pin = PIN) {
if (!value || typeof value !== 'object' || Array.isArray(value)
|| Object.keys(value).sort().join(',') !== 'binary,port'
|| !Number.isInteger(value.port) || value.port < 1024 || value.port > 65535 || value.port === 8317
|| value.binary !== installPaths(dataRoot, pin).binary) refuse('proxy-config-invalid');
return { port: value.port, binary: value.binary };
}
export function loadProxy(configDir, dataRoot) {
const file = path.join(configDir, 'proxy.json');
try { fs.lstatSync(file); } catch (e) { if (e.code === 'ENOENT') return undefined; refuse('proxy-config-invalid'); }
try { return validateProxy(JSON.parse(regularBytes(file, 'proxy-config-invalid')), dataRoot); }
catch { refuse('proxy-config-invalid'); }
}
export function verifyInstall(dataRoot, pin = PIN) {
const paths = installPaths(dataRoot, pin);
for (const [file, hash] of [[paths.binary, pin.binary.sha256], [paths.panel, pin.panel.sha256]]) {
try { fs.lstatSync(file); } catch (e) { if (e.code === 'ENOENT') refuse('proxy-install-missing'); refuse('proxy-install-invalid'); }
if (sha256(regularBytes(file, 'proxy-install-invalid')) !== hash) refuse('proxy-install-invalid');
}
return paths;
}
async function reservePort(wanted) {
if (wanted !== undefined && (!Number.isInteger(wanted) || wanted < 1024 || wanted > 65535 || wanted === 8317)) refuse('proxy-config-invalid');
const server = net.createServer();
try {
await new Promise((resolve, reject) => { server.once('error', reject); server.listen(wanted ?? 0, '127.0.0.1', resolve); });
const port = server.address().port;
if (port === 8317 || port < 1024) { await new Promise(r => server.close(r)); return reservePort(wanted); }
return { port, release: () => new Promise(r => server.close(r)) };
} catch { refuse('proxy-port-in-use'); }
}
export function initialConfig({ dataRoot, port, key, sentinel }) {
return {
'config-version': 8,
server: { host: '127.0.0.1', port },
management: { 'allow-remote': false, 'secret-key': key, 'disable-control-panel': false, 'disable-auto-update-panel': true },
access: { 'api-keys': [sentinel] },
oauth: { 'auth-dir': path.join(dataRoot, 'proxy', 'auth') },
observability: { logs: { 'logging-to-file': true } },
routing: { strategy: 'fill-first', 'session-affinity': true, 'session-affinity-rebind-on': 'quota', retry: { 'request-retry': 0 } },
};
}
// Called only by the bus's path-bound human verb, never by an agent or the tester.
export async function initializeProxy({ configDir, dataRoot, key, port, pin = PIN }) {
if (!validManagementKey(key)) refuse('proxy-key-invalid');
safePath(configDir, 'proxy-init-failed'); safePath(dataRoot, 'proxy-init-failed');
const paths = verifyInstall(dataRoot, pin);
const settingsFile = path.join(configDir, 'proxy.json'), proxyDir = path.join(dataRoot, 'proxy');
const secrets = path.join(configDir, 'secrets'), keyFile = path.join(secrets, 'proxy-management.key');
for (const file of [settingsFile, proxyDir, keyFile]) {
try { fs.lstatSync(file); refuse('proxy-already-initialized'); } catch (e) { if (e.code !== 'ENOENT') throw e; }
}
const lockFile = path.join(configDir, '.proxy-init.lock');
let lock;
try { lock = fs.openSync(lockFile, 'wx', 0o600); } catch { refuse('proxy-init-busy'); }
let reservation, ownsProxy = false, ownsKey = false, ownsSettings = false, ownsSecrets = false;
try {
reservation = await reservePort(port);
safePath(secrets, 'proxy-init-failed');
try { fs.mkdirSync(secrets, { mode: 0o700 }); ownsSecrets = true; }
catch (e) { if (e.code !== 'EEXIST') throw e; }
const stat = fs.lstatSync(secrets);
if (!stat.isDirectory() || stat.isSymbolicLink() || (stat.mode & 0o777) !== 0o700) refuse('proxy-init-failed');
fs.mkdirSync(proxyDir, { mode: 0o700 }); ownsProxy = true;
for (const name of ['auth', 'logs']) fs.mkdirSync(path.join(proxyDir, name), { mode: 0o700 });
const keyFd = fs.openSync(keyFile, 'wx', 0o600); ownsKey = true;
try { fs.writeFileSync(keyFd, key); } finally { fs.closeSync(keyFd); }
const tag = sha256(configDir).slice(0, 12);
const sentinel = `msk-${tag}-sentinel-${randomBytes(32).toString('base64url')}`;
fs.writeFileSync(path.join(proxyDir, 'config.yaml'), JSON.stringify(initialConfig({ dataRoot, port: reservation.port, key, sentinel }), null, 2) + '\n', { flag: 'wx', mode: 0o600 });
const settings = { port: reservation.port, binary: paths.binary };
const settingsFd = fs.openSync(settingsFile, 'wx', 0o600); ownsSettings = true;
try { fs.writeFileSync(settingsFd, JSON.stringify(settings, null, 2) + '\n'); } finally { fs.closeSync(settingsFd); }
return settings;
} catch (e) {
if (ownsSettings) fs.unlinkSync(settingsFile);
if (ownsKey) fs.unlinkSync(keyFile);
if (ownsProxy) fs.rmSync(proxyDir, { recursive: true });
if (ownsSecrets) fs.rmdirSync(secrets);
if (e.code?.startsWith('proxy-')) throw e;
refuse('proxy-init-failed');
} finally {
if (reservation) await reservation.release();
fs.closeSync(lock); fs.unlinkSync(lockFile);
}
}
@@ -0,0 +1,14 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import http from 'node:http';
import { ManagementClient } from '../src/client.mjs';
const code=c=>e=>e.code===c;
for(const url of ['https://127.0.0.1:18318','http://example.com:18318','http://localhost:18318','http://127.0.0.1:8317','http://user:[email protected]:18318','http://127.0.0.1:18318/path'])test(`proxy-client-invalid: ${url}`,()=>assert.throws(()=>new ManagementClient({baseUrl:url,key:'p1-management'}),code('proxy-client-invalid')));
test('client GET/PUT v8 only, Authorization header, receipt IDs without values or bodies',async()=>{let keys=['p1-client'];const calls=[],receipts=[];const server=http.createServer(async(q,s)=>{let body='';for await(const b of q)body+=b;calls.push({method:q.method,path:q.url,auth:q.headers.authorization,body});if(q.method==='PUT')keys=JSON.parse(body);s.setHeader('Content-Type','application/json');s.end(JSON.stringify(q.method==='GET'?keys:{status:'ok'}));});await new Promise(r=>server.listen(0,'127.0.0.1',r));try{const c=new ManagementClient({baseUrl:`http://127.0.0.1:${server.address().port}`,key:'p1-management',receipt:r=>receipts.push(r)});assert.deepEqual(await c.getKeys(),keys);await c.putKeys(['p1-new']);assert.deepEqual(await c.getKeys(),['p1-new']);assert.deepEqual(calls.map(x=>x.method),['GET','PUT','GET']);assert.ok(calls.every(x=>x.path==='/v8/management/config/access/api-keys'&&x.auth==='Bearer p1-management'));assert.equal(JSON.stringify(receipts).includes('p1-'),false);assert.equal(receipts.length,3);assert.match(receipts[0].keyIds[0],/^[0-9a-f]{12}$/);}finally{server.closeAllConnections();await new Promise(r=>server.close(r));}});
for(const [name,answer]of [['proxy-client-refused',{status:401,body:'p1-management'}],['proxy-client-response',{status:200,body:'{"bad":"p1-management"}'}],['proxy-client-redirect',{status:302,body:''}]])test(name,async()=>{const receipts=[];const s=http.createServer((q,r)=>{r.writeHead(answer.status,{Location:'http://example.com'});r.end(answer.body);});await new Promise(r=>s.listen(0,'127.0.0.1',r));try{const c=new ManagementClient({baseUrl:`http://127.0.0.1:${s.address().port}`,key:'p1-management',receipt:r=>receipts.push(r)});await assert.rejects(c.getKeys(),code(name));assert.equal(JSON.stringify(receipts).includes('p1-management'),false);}finally{s.closeAllConnections();await new Promise(r=>s.close(r));}});
test('proxy-client-down: transport errors redact details',async()=>{const s=http.createServer();await new Promise(r=>s.listen(0,'127.0.0.1',r));const p=s.address().port;await new Promise(r=>s.close(r));await assert.rejects(new ManagementClient({baseUrl:`http://127.0.0.1:${p}`,key:'p1-management'}).getKeys(),code('proxy-client-down'));});
test('proxy-client-invalid: PUT rejects object key records and empty strings locally',async()=>{const c=new ManagementClient({baseUrl:'http://127.0.0.1:18318',key:'p1-management'});for(const keys of [[{key:'p1-client',models:['some']}],[''],null])assert.throws(()=>c.putKeys(keys),code('proxy-client-invalid'));});
test('proxy-management-key-invalid: missing, symlink and public key file refuse without values',async()=>{const fs=await import('node:fs'),os=await import('node:os'),path=await import('node:path');const {loadManagementClient}=await import('../src/client.mjs');for(const variant of ['missing','public','symlink']){const root=fs.mkdtempSync(path.join(os.tmpdir(),'p1-key-'));fs.mkdirSync(path.join(root,'secrets'));const f=path.join(root,'secrets/proxy-management.key');if(variant==='public')fs.writeFileSync(f,'p1-management',{mode:0o644});if(variant==='symlink'){fs.writeFileSync(path.join(root,'other'),'p1-management',{mode:0o600});fs.symlinkSync(path.join(root,'other'),f);}await assert.rejects(loadManagementClient({configDir:root,proxy:{port:18432}}),e=>e.code==='proxy-management-key-invalid'&&!e.message.includes('p1-management'));}});
test('bus management-key loader supplies a private fixture key without exposing it on the client',async()=>{const fs=await import('node:fs'),os=await import('node:os'),path=await import('node:path');const {loadManagementClient}=await import('../src/client.mjs');const root=fs.mkdtempSync(path.join(os.tmpdir(),'p1-key-'));fs.mkdirSync(path.join(root,'secrets'),{mode:0o700});fs.writeFileSync(path.join(root,'secrets/proxy-management.key'),'p1-management',{mode:0o600});const c=await loadManagementClient({configDir:root,proxy:{port:18432}});assert.deepEqual(Object.keys(c),[]);assert.equal(JSON.stringify(c).includes('p1-management'),false);});
test('proxy-client-response: truncated response remains a redacted named refusal',async()=>{const s=http.createServer((q,r)=>{r.writeHead(200,{'Content-Length':'100000'});r.write('["p1-management"');setTimeout(()=>r.destroy(),10);});await new Promise(r=>s.listen(0,'127.0.0.1',r));try{const c=new ManagementClient({baseUrl:`http://127.0.0.1:${s.address().port}`,key:'p1-management'});await assert.rejects(c.getKeys(),e=>e.code==='proxy-client-response'&&!e.message.includes('p1-management'));}finally{s.closeAllConnections();await new Promise(r=>s.close(r));}});
test('management client ignores ambient fetch dispatchers',async()=>{const original=globalThis.fetch;globalThis.fetch=()=>{throw Error('ambient dispatcher must not see key');};const s=http.createServer((q,r)=>{r.setHeader('Content-Type','application/json');r.end('["p1-client"]');});await new Promise(r=>s.listen(0,'127.0.0.1',r));try{assert.deepEqual(await new ManagementClient({baseUrl:`http://127.0.0.1:${s.address().port}`,key:'p1-management'}).getKeys(),['p1-client']);}finally{globalThis.fetch=original;s.closeAllConnections();await new Promise(r=>s.close(r));}});
@@ -0,0 +1,23 @@
// The real config loader and unit renderers with made-up HOME/config/data only.
import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { spawnSync } from 'node:child_process';
import { installPaths } from '../src/settings.mjs';
const repo=fileURLToPath(new URL('../../../',import.meta.url));
function fixture(name='dev'){const root=mkdtempSync(join(tmpdir(),'p1-unit-')),configDir=join(root,'.config',`mosaic-${name}`),dataRoot=join(root,'data');mkdirSync(configDir,{recursive:true});mkdirSync(dataRoot);const file=join(configDir,'config.json');writeFileSync(file,JSON.stringify({configVersion:1,environment:'development',dataRoot,execution:{backend:'docker',provider:'fixture',model:'fixture'}}));if(name!=='dev')writeFileSync(join(dataRoot,'instance.json'),JSON.stringify({instanceVersion:1,name}));const env={PATH:process.env.PATH,HOME:root,MOSAIC_CONFIG:file};return {root,configDir,dataRoot,file,env};}
function run(f,script,args=[]){return spawnSync(script.endsWith('.mjs')?process.execPath:'bash',[join(repo,script),...args],{env:f.env,encoding:'utf8',timeout:10000});}
function proxy(f){const v={port:18432,binary:installPaths(f.dataRoot).binary};writeFileSync(join(f.configDir,'proxy.json'),JSON.stringify(v));return v;}
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);});
test('proxy-config-invalid: loader refuses an unknown sidecar key and binary outside pinned install',()=>{for(const value of [{port:8317,binary:'/bad'},{port:18432,binary:'/bad'},{...{port:18432,binary:'/bad'},future:true}]){const f=fixture();writeFileSync(join(f.configDir,'proxy.json'),JSON.stringify(value));const r=run(f,'scripts/mosaic-config.mjs',['validate']);assert.equal(r.status,2);assert.match(r.stderr,/proxy-config-invalid/);}});
test('proxy unit and bus dependency render default and named instance only when configured',()=>{for(const name of ['dev','testbox']){const f=fixture(name),flags=name==='dev'?[]:['--instance',name];let r=run(f,'scripts/bus-service.sh',[...flags,'render']);assert.equal(r.status,0,r.stderr);assert.doesNotMatch(r.stdout,/Wants=mosaic-proxy/);proxy(f);r=run(f,'scripts/proxy-service.sh',[...flags,'render']);assert.equal(r.status,0,r.stderr);assert.match(r.stdout,/packages\/model-proxy\/src\/run.mjs/);assert.match(r.stdout,new RegExp(`Environment=MOSAIC_CONFIG=${f.file}`));assert.doesNotMatch(r.stdout,/@[A-Z_]+@/);r=run(f,'scripts/bus-service.sh',[...flags,'render']);assert.equal(r.status,0,r.stderr);const unit=`mosaic-proxy${name==='dev'?'':'-'+name}.service`;assert.ok(r.stdout.includes(`Wants=${unit}\nAfter=${unit}`),r.stdout);}});
test('proxy-not-configured: runner and unit render refuse absent sidecar',()=>{const f=fixture();for(const [script,args]of [['scripts/proxy-service.sh',['render']],['packages/model-proxy/src/run.mjs',[]]]){const r=run(f,script,args);assert.equal(r.status,3);assert.match(r.stderr,/proxy-not-configured/);}});
test('proxy-install-invalid: unit runner refuses tampered executable before spawning',()=>{const f=fixture(),p=installPaths(f.dataRoot);mkdirSync(dirname(p.binary),{recursive:true});writeFileSync(p.binary,'#!/bin/sh\ntouch '+join(f.root,'executed')+'\n',{mode:0o755});proxy(f);const r=run(f,'packages/model-proxy/src/run.mjs');assert.equal(r.status,3);assert.match(r.stderr,/proxy-install-invalid/);assert.equal(existsSync(join(f.root,'executed')),false);});
for(const [label,args,prep]of [['proxy-unit-usage',['bad'],()=>{}],['proxy-unit-exists',['install','--no-reload'],(f,dir)=>{mkdirSync(dir);writeFileSync(join(dir,'mosaic-proxy.service'),'old');}],['proxy-unit-dropin',['install','--no-reload'],(f,dir)=>{mkdirSync(join(dir,'service.d'),{recursive:true});}],['proxy-unit-path',['install','--no-reload','--dir','/invalid space'],()=>{}]])test(label,()=>{const f=fixture();proxy(f);const dir=join(f.root,'units');prep(f,dir);const r=run(f,'scripts/proxy-service.sh',[...args,...(label==='proxy-unit-path'?[]:['--dir',dir])]);assert.notEqual(r.status,0);assert.match(r.stderr,new RegExp(label));});
test('proxy-config-invalid: instance selection also refuses invalid present sidecar',()=>{const f=fixture();writeFileSync(join(f.configDir,'proxy.json'),'{');const r=run(f,'scripts/mosaic-config.mjs',['instance']);assert.equal(r.status,2);assert.match(r.stderr,/proxy-config-invalid/);});
test('explicit unit install writes only a scratch user unit and refuses a repeat',()=>{const f=fixture();proxy(f);const dir=join(f.root,'units');let r=run(f,'scripts/proxy-service.sh',['install','--dir',dir,'--no-reload']);assert.equal(r.status,0,r.stderr);const file=join(dir,'mosaic-proxy.service'),before=readFileSync(file);r=run(f,'scripts/proxy-service.sh',['install','--dir',dir,'--no-reload']);assert.equal(r.status,3);assert.match(r.stderr,/proxy-unit-exists/);assert.deepEqual(readFileSync(file),before);});
test('proxy-unit-copy: renderer refuses an unknown or unpinned stack copy',()=>{const f=fixture();proxy(f);const r=run(f,'scripts/proxy-service.sh',['render','--copy','a'.repeat(40),'--copies',join(f.root,'missing-copies')]);assert.equal(r.status,3);assert.match(r.stderr,/proxy-unit-copy/);});
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);});
+20
View File
@@ -0,0 +1,20 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, writeFileSync, readFileSync, existsSync, mkdirSync, readdirSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { execFileSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import http from 'node:http';
import { fetchPinned, downloadRelease } from '../src/fetch.mjs';
import { installPaths } from '../src/settings.mjs';
const digest=x=>createHash('sha256').update(x).digest('hex'),code=c=>e=>e.code===c;
function fixture(){const root=mkdtempSync(join(tmpdir(),'p1-'));mkdirSync(join(root,'source'));writeFileSync(join(root,'source/cli-proxy-api'),'fake executable');execFileSync('tar',['-czf',join(root,'release.tar.gz'),'-C',join(root,'source'),'cli-proxy-api']);const archive=readFileSync(join(root,'release.tar.gz')),panel=Buffer.from('fake panel');const pin={version:'8.0.42',archive:{url:'https://github.com/router-for-me/CLIProxyAPI/releases/download/v8.0.42/fake.tar.gz',sha256:digest(archive)},binary:{sha256:digest('fake executable')},panel:{url:'https://github.com/router-for-me/Cli-Proxy-API-Management-Center/releases/download/v1.26.7/management.html',sha256:digest(panel)}};return {root,archive,panel,pin,dataRoot:join(root,'data')};}
test('verified fetch installs binary and panel together; identical repeat is idempotent',async()=>{const f=fixture();const calls=[];const download=async url=>{calls.push(url);return url===f.pin.archive.url?f.archive:f.panel;};const p=await fetchPinned({...f,download});assert.equal(readFileSync(p.binary,'utf8'),'fake executable');assert.equal(readFileSync(p.panel,'utf8'),'fake panel');await fetchPinned({...f,download});assert.equal(calls.length,4);});
test('proxy-fetch-hash: either bad asset installs nothing',async()=>{for(const bad of ['archive','panel']){const f=fixture();await assert.rejects(fetchPinned({...f,download:async url=>url===f.pin[bad].url?Buffer.from('corrupt'):url===f.pin.archive.url?f.archive:f.panel}),code('proxy-fetch-hash'));assert.equal(existsSync(installPaths(f.dataRoot,f.pin).dir),false);}});
test('proxy-fetch-existing: conflicting install remains byte-identical',async()=>{const f=fixture(),p=installPaths(f.dataRoot,f.pin);mkdirSync(p.dir,{recursive:true});writeFileSync(p.binary,'old');await assert.rejects(fetchPinned({...f,download:async u=>u===f.pin.archive.url?f.archive:f.panel}),code('proxy-fetch-existing'));assert.equal(readFileSync(p.binary,'utf8'),'old');assert.deepEqual(readdirSync(p.dir),['cli-proxy-api']);});
test('proxy-fetch-archive: missing executable in verified archive refuses without install',async()=>{const f=fixture();writeFileSync(join(f.root,'source/other'),'other');execFileSync('tar',['-czf',join(f.root,'bad.tar.gz'),'-C',join(f.root,'source'),'other']);f.archive=readFileSync(join(f.root,'bad.tar.gz'));f.pin.archive.sha256=digest(f.archive);await assert.rejects(fetchPinned({...f,download:async u=>u===f.pin.archive.url?f.archive:f.panel}),code('proxy-fetch-archive'));assert.equal(existsSync(installPaths(f.dataRoot,f.pin).dir),false);});
test('proxy-fetch-url: production downloader permits pinned GitHub release URLs only',async()=>{for(const url of ['http://127.0.0.1:18318/x','https://example.com/x','https://github.com/router-for-me/CLIProxyAPI/blob/main/x'])await assert.rejects(downloadRelease(url),code('proxy-fetch-url'));});
test('local fake server covers redirect off allowlist and HTTP failure without external fetch',async()=>{const s=http.createServer((q,r)=>{if(q.url==='/redirect'){r.writeHead(302,{Location:'https://example.com/forbidden'});r.end();}else{r.writeHead(503);r.end('do not log response');}});await new Promise(r=>s.listen(0,'127.0.0.1',r));const local=`http://127.0.0.1:${s.address().port}`;try{await assert.rejects(downloadRelease(`${local}/redirect`,{testOrigin:local}),code('proxy-fetch-redirect'));await assert.rejects(downloadRelease(`${local}/fail`,{testOrigin:local}),code('proxy-fetch-failed'));}finally{s.closeAllConnections();await new Promise(r=>s.close(r));}});
test('proxy-fetch-existing: extra content in an existing install refuses',async()=>{const f=fixture(),download=async u=>u===f.pin.archive.url?f.archive:f.panel,p=await fetchPinned({...f,download});writeFileSync(join(p.dir,'extra'),'old');await assert.rejects(fetchPinned({...f,download}),code('proxy-fetch-existing'));assert.equal(readFileSync(join(p.dir,'extra'),'utf8'),'old');});
test('local fake file server success and same-origin redirect return exact bytes',async()=>{const f=fixture();const s=http.createServer((q,r)=>{if(q.url==='/redirect'){r.writeHead(302,{Location:'/asset'});r.end();}else r.end(f.archive);});await new Promise(r=>s.listen(0,'127.0.0.1',r));const local=`http://127.0.0.1:${s.address().port}`;try{assert.deepEqual(await downloadRelease(`${local}/redirect`,{testOrigin:local}),f.archive);}finally{s.closeAllConnections();await new Promise(r=>s.close(r));}});
@@ -0,0 +1,11 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { EventEmitter } from 'node:events';
import { mkdtempSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { startVerifiedProxy } from '../src/runner.mjs';
function fixture(){const root=mkdtempSync(join(tmpdir(),'p1-runner-'));return {root,config:{proxy:{port:18432,binary:join(root,'pin/cli-proxy-api')},dataRoot:join(root,'data')},install:{binary:join(root,'pin/cli-proxy-api'),panel:join(root,'pin/static/management.html')}};}
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')}}]);});
test('proxy-start-failed: sync and async spawn failures report code only',()=>{const {config,install}=fixture();assert.throws(()=>startVerifiedProxy(config,{verify:()=>install,spawnImpl:()=>{throw Error('p1-management');}}),e=>e.code==='proxy-start-failed');const child=new EventEmitter(),codes=[];startVerifiedProxy(config,{verify:()=>install,spawnImpl:()=>child,onError:c=>codes.push(c)});child.emit('error',Error('p1-management'));assert.deepEqual(codes,['proxy-start-failed']);});
test('hash refusal prevents spawn and ordinary exit status is propagated',()=>{const {config,install}=fixture();let spawned=false;assert.throws(()=>startVerifiedProxy(config,{verify:()=>{throw Object.assign(Error('proxy-install-invalid'),{code:'proxy-install-invalid'});},spawnImpl:()=>{spawned=true;}}),e=>e.code==='proxy-install-invalid');assert.equal(spawned,false);const child=new EventEmitter(),exits=[];startVerifiedProxy(config,{verify:()=>install,spawnImpl:()=>child,onExit:c=>exits.push(c)});child.emit('exit',7,null);assert.deepEqual(exits,[7]);});
@@ -0,0 +1,24 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, writeFileSync, readFileSync, mkdirSync, lstatSync, symlinkSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { createHash } from 'node:crypto';
import { validateProxy, loadProxy, installPaths, initializeProxy, verifyInstall } from '../src/settings.mjs';
const digest=x=>createHash('sha256').update(x).digest('hex');
const pin={version:'8.0.42',binary:{sha256:digest('fake binary')},panel:{sha256:digest('fake panel')}};
function fixture(){const root=mkdtempSync(join(tmpdir(),'p1-'));const configDir=join(root,'config'),dataRoot=join(root,'data');mkdirSync(configDir);mkdirSync(dataRoot);writeFileSync(join(configDir,'config.json'),'unchanged');return {root,configDir,dataRoot};}
function installed(f){const p=installPaths(f.dataRoot,pin);mkdirSync(join(p.dir,'static'),{recursive:true});writeFileSync(p.binary,'fake binary',{mode:0o755});writeFileSync(p.panel,'fake panel');return p;}
const code=c=>e=>e.code===c;
test('absent proxy settings preserves no-proxy contract; present settings validate pinned path',()=>{const f=fixture();assert.equal(loadProxy(f.configDir,f.dataRoot),undefined);const v={port:18432,binary:installPaths(f.dataRoot).binary};assert.deepEqual(validateProxy(v,f.dataRoot),v);writeFileSync(join(f.configDir,'proxy.json'),JSON.stringify(v));assert.deepEqual(loadProxy(f.configDir,f.dataRoot),v);});
for(const [name,value]of Object.entries({unknown:{port:18432,binary:'/bad',extra:1},missing:{port:18432},low:{port:1023,binary:'/bad'},high:{port:65536,binary:'/bad'},host:{port:8317,binary:'/bad'},string:{port:'18432',binary:'/bad'},binary:{port:18432,binary:'/bad'},null:null}))test(`proxy-config-invalid: ${name}`,()=>{const f=fixture();const input=value&&name!=='binary'&&Object.hasOwn(value,'binary')?{...value,binary:installPaths(f.dataRoot).binary}:value;assert.throws(()=>validateProxy(input,f.dataRoot),code('proxy-config-invalid'));});
test('proxy-config-invalid: malformed or symlink settings refuse',()=>{const f=fixture(),p=join(f.configDir,'proxy.json');writeFileSync(p,'{');assert.throws(()=>loadProxy(f.configDir,f.dataRoot),code('proxy-config-invalid'));const g=fixture();symlinkSync(p,join(g.configDir,'proxy.json'));assert.throws(()=>loadProxy(g.configDir,g.dataRoot),code('proxy-config-invalid'));});
test('proxy-install-missing: init requires fetch before any state is written',async()=>{const f=fixture();await assert.rejects(initializeProxy({...f,pin,key:'p1-management',port:18432}),code('proxy-install-missing'));assert.equal(readFileSync(join(f.configDir,'config.json'),'utf8'),'unchanged');});
test('proxy-install-invalid: binary or panel mutation refuses',()=>{const f=fixture(),p=installed(f);writeFileSync(p.binary,'tampered');assert.throws(()=>verifyInstall(f.dataRoot,pin),code('proxy-install-invalid'));});
test('proxy-key-invalid: empty, multiline and too-long key refuse before creation',async()=>{for(const key of ['', 'a\nb','x'.repeat(4097),'bad-\u2603-key']){const f=fixture();installed(f);await assert.rejects(initializeProxy({...f,pin,key,port:18432}),code('proxy-key-invalid'));}});
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'));});
test('proxy-port-in-use: init refuses occupied loopback without files',async()=>{const {createServer}=await import('node:net');const server=createServer();await new Promise(r=>server.listen(0,'127.0.0.1',r));try{const f=fixture();installed(f);await assert.rejects(initializeProxy({...f,pin,key:'p1-management',port:server.address().port}),code('proxy-port-in-use'));}finally{server.close();}});
test('proxy-init-busy: existing lock refuses without touching it',async()=>{const f=fixture();installed(f);writeFileSync(join(f.configDir,'.proxy-init.lock'),'owner');await assert.rejects(initializeProxy({...f,pin,key:'p1-management'}),code('proxy-init-busy'));assert.equal(readFileSync(join(f.configDir,'.proxy-init.lock'),'utf8'),'owner');});
test('proxy-init-failed: symlink or non-private secrets directory refuses without changing it',async()=>{const f=fixture();installed(f);mkdirSync(join(f.configDir,'secrets'),{mode:0o755});await assert.rejects(initializeProxy({...f,pin,key:'p1-management'}),code('proxy-init-failed'));assert.equal(lstatSync(join(f.configDir,'secrets')).mode&0o777,0o755);});
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');}});
test('proxy-install-invalid: modified panel also refuses',()=>{const f=fixture(),p=installed(f);writeFileSync(p.panel,'different');assert.throws(()=>verifyInstall(f.dataRoot,pin),code('proxy-install-invalid'));});
+5 -1
View File
@@ -88,7 +88,11 @@ render() {
case "$REPO" in *@*|*'|'*) die "repository path contains a character the template cannot carry: $REPO" ;; esac
local claude
claude="$(claude_line)" || exit 1
sed -e "s|@REPO@|$root|g" -e "s|@PATH@|$path|g" -e "s|@PM_FLAGS@|$pm_flags|g" -e "s|^@CLAUDE_ENV@$|$claude|" "${INSTANCE_SED[@]}" "$TEMPLATE"
local -a proxy_sed=()
if [ "$MOSAIC_INSTANCE_PROXY_CONFIGURED" = 1 ]; then
proxy_sed=(-e "/^Description=/a Wants=$PROXY_UNIT\\nAfter=$PROXY_UNIT")
fi
sed "${proxy_sed[@]}" -e "s|@REPO@|$root|g" -e "s|@PATH@|$path|g" -e "s|@PM_FLAGS@|$pm_flags|g" -e "s|^@CLAUDE_ENV@$|$claude|" "${INSTANCE_SED[@]}" "$TEMPLATE"
}
UNIT_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
+2
View File
@@ -77,11 +77,13 @@ instance_select() {
else
out="$(node "$here/mosaic-config.mjs" instance)" || exit $?
fi
MOSAIC_INSTANCE_PROXY_CONFIGURED=0
eval "$out"
local s="$MOSAIC_UNIT_SUFFIX"
BUS_TEMPLATE="mosaic-bus$s@.service"
BUS_GLOB="mosaic-bus$s@*.service"
CONSOLE_UNIT="mosaic-webui$s.service"
PROXY_UNIT="mosaic-proxy$s.service"
DISCORD_TEMPLATE="mosaic-discord$s@.service"
WRAPPER_NAME="mosaic$s"
if [ -z "$s" ]; then
+1 -1
View File
@@ -41,6 +41,6 @@ if [ "${1:-}" = library ] && [ "${2:-}" = survey ]; then
exec node "$REPO/packages/library/src/survey.mjs" "$@"
fi
case "${1:-}" in
halt|resume|processing|usage|project|limits|inbox|decide|tasks|agents|trail|talk|stop|launches|bus|plugins|credentials|hosts|grants|name|names|follow|threads|thread|access) exec node "$REPO/packages/cli/src/cli.mjs" "$@" ;;
proxy|halt|resume|processing|usage|project|limits|inbox|decide|tasks|agents|trail|talk|stop|launches|bus|plugins|credentials|hosts|grants|name|names|follow|threads|thread|access) exec node "$REPO/packages/cli/src/cli.mjs" "$@" ;;
esac
exec node "$REPO/packages/seat/src/cli.mjs" "$@"
+21 -2
View File
@@ -342,8 +342,22 @@ async function validate(document, file) {
// FED R2: the same rule as `console`. The key appears in the output only when the file has it.
const federationKey = Object.hasOwn(document, "federation") ? validateFederation(document.federation) : undefined;
let proxy;
try {
let present = false;
try { fs.lstatSync(path.join(path.dirname(file), "proxy.json")); present = true; }
catch (e) { if (e.code !== "ENOENT") throw e; }
// Historical no-proxy copies have no model-proxy package. Do not load a new
// dependency unless the sidecar exists; a broken symlink is present and refuses.
if (present) {
const { loadProxy } = await import("../packages/model-proxy/src/settings.mjs");
proxy = loadProxy(path.dirname(file), dataRoot);
}
} catch { fail(2, "proxy-config-invalid"); }
return {
configVersion: document.configVersion,
...(proxy === undefined ? {} : { proxy }),
environment: document.environment,
dataRoot,
execution: {
@@ -475,18 +489,22 @@ async function bootstrapNamed(file, instance) {
async function selected(instance) {
const file = configPath(instance);
let dataRoot = "";
let proxy = false;
if (isNamed(instance)) {
dataRoot = (await load(file, instance)).dataRoot;
const resolved = await load(file, instance);
dataRoot = resolved.dataRoot;
proxy = Boolean(resolved.proxy);
checkClaim(dataRoot, instance);
} else {
let resolved = null;
try {
resolved = readRaw(file) === null ? null : await load(file);
} catch (error) {
if (!(error instanceof Failure)) throw error;
if (!(error instanceof Failure) || error.message === "proxy-config-invalid") throw error;
}
if (resolved !== null) {
dataRoot = resolved.dataRoot;
proxy = Boolean(resolved.proxy);
checkClaim(dataRoot, instance);
}
}
@@ -497,6 +515,7 @@ async function selected(instance) {
`MOSAIC_INSTANCE_CONFIG_DIR=${shellQuote(path.dirname(path.resolve(file)))}`,
`MOSAIC_INSTANCE_DATA_ROOT=${shellQuote(dataRoot)}`,
`MOSAIC_UNIT_SUFFIX=${shellQuote(isNamed(instance) ? `-${instance}` : "")}`,
...(proxy ? ["MOSAIC_INSTANCE_PROXY_CONFIGURED=1"] : []),
"",
].join("\n");
}
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# Explicit user-unit rendering/install. Never enables, starts or restarts a unit.
set -euo pipefail
REPO=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
. "$REPO/scripts/instance.sh"
instance_args "$@"
set -- "${INSTANCE_ARGS[@]}"
instance_select
OP=${1:-}; [ $# -eq 0 ] || shift
UNIT_DIR=${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user
RELOAD=1
COPY_ID=""
COPIES="${XDG_DATA_HOME:-$HOME/.local/share}/mosaic-stack/copies"
while [ $# -gt 0 ]; do
case "$1" in
--dir) [ $# -ge 2 ] || exit 4; UNIT_DIR=$2; shift 2 ;;
--no-reload) RELOAD=0; shift ;;
--copy) [ $# -ge 2 ] || exit 4; COPY_ID=$2; shift 2 ;;
--copies) [ $# -ge 2 ] || exit 4; COPIES=$2; shift 2 ;;
*) echo 'proxy-service: proxy-unit-usage' >&2; exit 4 ;;
esac
done
case "$OP" in render|install) ;; *) echo 'proxy-service: proxy-unit-usage' >&2; exit 4 ;; esac
[ "$MOSAIC_INSTANCE_PROXY_CONFIGURED" = 1 ] || { echo 'proxy-service: proxy-not-configured' >&2; exit 3; }
ROOT=$REPO
if [ -n "$COPY_ID" ]; then
[[ "$COPY_ID" =~ ^[0-9a-f]{40}$ ]] && [ -f "$COPIES/$COPY_ID/packages/model-proxy/PIN" ] || { echo 'proxy-service: proxy-unit-copy' >&2; exit 3; }
ROOT="$COPIES/$COPY_ID"
fi
case "$ROOT" in /*) ;; *) echo 'proxy-service: proxy-unit-path' >&2; exit 3 ;; esac
NODE=$(command -v node)
case "$NODE:$ROOT:$UNIT_DIR:$MOSAIC_INSTANCE_CONFIG" in *[!A-Za-z0-9/._+:-]*) echo 'proxy-service: proxy-unit-path' >&2; exit 3 ;; esac
render() {
# Both default and named instances bind their exact selected config, never a service-manager default.
sed -e "s|@REPO@|$ROOT|g" -e 's|@PATH@|/usr/local/bin:/usr/bin:/bin|g' \
-e "s|@NODE@|$NODE|g" -e "s|^@CONFIG_ENV@$|Environment=MOSAIC_CONFIG=$MOSAIC_INSTANCE_CONFIG|" \
-e "s|^Description=Mosaic model proxy$|Description=Mosaic model proxy [$MOSAIC_INSTANCE]|" \
"$REPO/packages/cli/systemd/mosaic-proxy.service.in"
}
if [ "$OP" = render ]; then render; exit 0; fi
# Refuse replacement and drop-ins: installation is explicit creation only.
[ ! -e "$UNIT_DIR/$PROXY_UNIT" ] && [ ! -L "$UNIT_DIR/$PROXY_UNIT" ] || { echo 'proxy-service: proxy-unit-exists' >&2; exit 3; }
for DROP in "$PROXY_UNIT.d" mosaic-proxy-.service.d mosaic-.service.d service.d; do
[ ! -e "$UNIT_DIR/$DROP" ] || { echo 'proxy-service: proxy-unit-dropin' >&2; exit 3; }
done
mkdir -p "$UNIT_DIR"
# noclobber refuses a concurrent install; no host service is started or enabled.
(set -o noclobber; render > "$UNIT_DIR/$PROXY_UNIT") || { echo 'proxy-service: proxy-unit-exists' >&2; exit 3; }
[ "$RELOAD" = 0 ] || systemctl --user daemon-reload
+2
View File
@@ -33,6 +33,8 @@ COPY_PATHS=(
packages/conversation/src packages/discord/src packages/discord/bin packages/discord/extension packages/harness/src
packages/seat/src packages/tasks/src packages/tasks/fields.json packages/tasks/requirements.json
packages/processing-control
# P1: the config sidecar, CLI proxy verbs and bus management client depend on these.
packages/model-proxy/src packages/model-proxy/PIN
packages/webui/src packages/webui/README.md packages/cli/README.md
packages/queue/src packages/library/src packages/library/bin
# Whole directory: every in-repo plug-in module ships with the copy (row 131).