docs: row 330 P4 brief and design D7/J4 for mos-1608
Mos approved the P4 brief with upstream API keys added: a human-only write-only verb, z.ai-only allowlist, apikey-invalid and provider-not-allowed refusals, a canary test and an echo mutant. Account ids hash the proxy's own credential identifier, never a token or key value. Account removal stays out of P4.
This commit is contained in:
1 parent
696ea3a3af
commit
966ee31ea8
2 files changed
+48
-25
No files matched your search
@@ -1,6 +1,7 @@
|
||||
# Brief: row 330 (P4), the Console proxy page
|
||||
|
||||
Written 2026-10-24 by Sage for Mos's mos-1606, which carries Jason's Q46 and Q47 answers (DECISIONS Q250).
|
||||
Mos approved it in mos-1608, adding upstream API keys (item 4) and the account id rule (item 2).
|
||||
Read the design (`docs/plans/2026-10-24_stack-model-proxy.md`, amended for Q250) and the rows doc
|
||||
(`docs/plans/2026-10-24_model-proxy-rows.md`) first. The rows doc's "Rules for every row" apply here
|
||||
unchanged. Line numbers are at origin/refactor 0ce22dde6. Check them before you edit.
|
||||
@@ -15,16 +16,17 @@ landing. The P4 brief lives here instead.
|
||||
|
||||
## Brief: P4 the Console proxy page
|
||||
|
||||
**Why.** Q250: Jason enters accounts in the Console's own proxy page, not in the upstream panel. The page
|
||||
calls bus routes, and the bus calls the proxy's management API (D12). The management key stays in the bus
|
||||
(D6). No new hostname is added. A panel subdomain is a fallback only if our page can't keep up with the
|
||||
**Why.** Q250: Jason enters accounts in the Console's own proxy page, not in the upstream panel. That
|
||||
covers both his OAuth accounts and his API keys (Q248 item 1, Q249, mos-1608). The page calls bus routes,
|
||||
and the bus calls the proxy's management API (D12). The management key stays in the bus (D6). No new
|
||||
hostname is added. A panel subdomain is a fallback only if our page can't keep up with the
|
||||
management API across pinned proxy upgrades, and that call is Jason's (Q46).
|
||||
|
||||
**What ships.**
|
||||
|
||||
1. **Bus verbs.** New broker verbs for the page, for example `proxy.accounts`, `proxy.login.start`,
|
||||
`proxy.login.submit`, `proxy.login.status`, `proxy.login.cancel`, `proxy.priority.set` and
|
||||
`proxy.keys`. Settle the names in BUILD.md.
|
||||
`proxy.login.submit`, `proxy.login.status`, `proxy.login.cancel`, `proxy.apikey.add`,
|
||||
`proxy.priority.set` and `proxy.keys`. Settle the names in BUILD.md.
|
||||
- Every verb is human-only. A tester, agent or granted-user caller refuses `human-required`, the way
|
||||
the grants admin verbs do (`packages/bus/src/broker.mjs`:3721-3731).
|
||||
- With no proxy in the validated config (P1 item 3), every verb refuses `not-configured`.
|
||||
@@ -36,8 +38,9 @@ management API across pinned proxy upgrades, and that call is Jason's (Q46).
|
||||
endpoint (`/credentials` in the 8.0.27 doc,
|
||||
`agents/researcher/work/i1-proxy-test/docs/management-api-v8.0.27.md`:101).
|
||||
- The bus copies only allowlisted fields: provider, a display name, status and priority, plus an
|
||||
account id (the first 12 hex of a sha256, as for key ids). Every other field is dropped, so no token
|
||||
can pass through even if the proxy returns one.
|
||||
account id. Every other field is dropped, so no token can pass through even if the proxy returns one.
|
||||
- The account id is the first 12 hex of the sha256 of the proxy's own credential identifier, never of a
|
||||
token or key value (mos-1608).
|
||||
- Record in BUILD.md which endpoint and fields the pinned version has.
|
||||
3. **OAuth sign-in** through `oauth/auth-url`, `oauth/callback` and `oauth/status`
|
||||
(management-api-v8.0.27.md:108-138).
|
||||
@@ -56,18 +59,31 @@ management API across pinned proxy upgrades, and that call is Jason's (Q46).
|
||||
`wait` doesn't, because the doc says acceptance isn't completion (:136-138). The bus stops polling
|
||||
on `ok`, `error`, or after a timeout setting (default 300 s).
|
||||
- **Cancel** calls `DELETE oauth/session`.
|
||||
4. **Priority.** `proxy.priority.set` sets one account's priority, an integer in a range the builder
|
||||
4. **Upstream API keys** (mos-1608). A human-only verb (for example `proxy.apikey.add`; name it in
|
||||
BUILD.md) adds one upstream API key.
|
||||
- The provider allowlist starts with z.ai only. Adding a provider is a reviewed change. BUILD.md records
|
||||
which management endpoint the pinned 8.0.42 uses for it.
|
||||
- The Console route has the same checks as the other writes (item 8). The page's input is
|
||||
`type=password` with `autocomplete=off`.
|
||||
- The value is write-only. Neither the Console nor the bus logs it, stores it, caches it, or puts it in
|
||||
a receipt or an error. No part of it is echoed, not even the last four characters. The bus passes it
|
||||
straight to the management API in the request body and then drops it.
|
||||
- After the add, the key shows in `proxy.accounts` through the same allowlisted fields, and priority
|
||||
(item 5) works on it.
|
||||
- An empty value, one over a size limit, or one outside a charset refuses `apikey-invalid`. A provider
|
||||
outside the allowlist refuses `provider-not-allowed`.
|
||||
5. **Priority.** `proxy.priority.set` sets one account's priority, an integer in a range the builder
|
||||
records. Use the pinned version's credential-field endpoint (`/credentials/fields` PATCH in the 8.0.27
|
||||
doc). Confirm it on the stand-in and record what it changes. A bad account id or value refuses
|
||||
`priority-invalid`.
|
||||
5. **Client keys, list only.** `proxy.keys` reads `GET /config/access/api-keys`, maps each value to its key
|
||||
6. **Client keys, list only.** `proxy.keys` reads `GET /config/access/api-keys`, maps each value to its key
|
||||
id in memory and drops the value (D15).
|
||||
- It joins P2's record to show agent, issue time and state for each key id.
|
||||
- The sentinel is labelled. A key without this instance's prefix shows as "not stack-managed".
|
||||
- The page has no key write. Issue and revoke stay with hire and fire (P2).
|
||||
6. **Usage.** P5's Console view (rows doc P5 item 3) sits on this page. Whichever of P4 and P5 lands
|
||||
7. **Usage.** P5's Console view (rows doc P5 item 3) sits on this page. Whichever of P4 and P5 lands
|
||||
second puts it there. P4 adds no usage source.
|
||||
7. **The page and its Console routes.** An owner-only `/proxy` page in `packages/webui`, built like the
|
||||
8. **The page and its Console routes.** An owner-only `/proxy` page in `packages/webui`, built like the
|
||||
existing pages, with routes under `/api/proxy/`.
|
||||
- Each route makes one call over the owner's human transport, the way the Agents section does
|
||||
(`AGENT_ROUTES` at `packages/webui/src/serve.mjs`:451, `agentCaller` at
|
||||
@@ -79,14 +95,14 @@ management API across pinned proxy upgrades, and that call is Jason's (Q46).
|
||||
- The Console never holds the management key, never reads `proxy` from the config, and never opens a
|
||||
connection to the proxy port.
|
||||
- Logs record method, route, status and user only. They never record the authorization URL, a pasted
|
||||
callback URL, a state or a body.
|
||||
8. **Upgrade check.** An acceptance script, beside P1's and outside the gate's `scripts/test-*.sh` set,
|
||||
callback URL, a state, an API key or a body.
|
||||
9. **Upgrade check.** An acceptance script, beside P1's and outside the gate's `scripts/test-*.sh` set,
|
||||
runs the bus verbs against the pinned binary under `unshare -rn`. It uses a made-up config, key and
|
||||
data root in a fresh temp root, the same way P1's run does.
|
||||
- With no network, it checks what can be checked offline. The account list starts empty. `auth-url`
|
||||
returns a URL and a state for `claude` and for `codex`. A callback with a made-up code never reports
|
||||
`ok`. A priority set on a made-up credential reads back, if the stand-in can hold one. The key list
|
||||
returns ids only.
|
||||
`ok`. A made-up z.ai API key added through the verb shows in the account list, and a priority set on
|
||||
it reads back. The key list returns ids only.
|
||||
- It prints the endpoints and fields the page relies on, so a version change shows up as a diff.
|
||||
- `packages/model-proxy/README.md` records the rule: a PIN change lands only with this script's output
|
||||
for the new version in that row's BUILD.md, and Sage's landing gate checks for it.
|
||||
@@ -96,8 +112,14 @@ management API across pinned proxy upgrades, and that call is Jason's (Q46).
|
||||
**Tests.** Cover each of these:
|
||||
- every verb against a fake management server;
|
||||
- each refusal code by its own test (mos-1590): `not-configured`, `human-required` for the tester, an
|
||||
agent and a granted user, a provider outside the allowlist, `login-state-unknown`,
|
||||
`callback-url-invalid`, `priority-invalid` and `proxy-down`;
|
||||
agent and a granted user, `provider-not-allowed` for sign-in and for an API key, `login-state-unknown`,
|
||||
`callback-url-invalid`, `apikey-invalid` (empty, over the size limit and outside the charset, each its
|
||||
own case), `priority-invalid` and `proxy-down`;
|
||||
- an API-key canary: a unique made-up value goes through the Console route against the fake management
|
||||
server. Afterwards every Console and bus log, receipt, response, store file and error output from that
|
||||
run has zero matches for it, and the fake received it exactly once;
|
||||
- an added API key listed by `proxy.accounts` with allowlisted fields only, and priority set on it;
|
||||
- account ids derived from the credential identifier, with a fake whose token values never appear in an id;
|
||||
- a fake that puts token-like values in every credential field, where no value reaches a response, log or
|
||||
receipt;
|
||||
- a fake key list with full values, where only ids come back, the sentinel is labelled and a foreign key
|
||||
@@ -110,9 +132,8 @@ management API across pinned proxy upgrades, and that call is Jason's (Q46).
|
||||
- no `packages/webui` module imports the model-proxy client or reads the `proxy` config.
|
||||
|
||||
**Out of scope.**
|
||||
- Upstream API keys such as z.ai. Mos's list for the page (mos-1606) doesn't name them, and entering one
|
||||
means a secret crosses the Console. Sage has asked Mos. Until Mos rules, the page has no API-key entry.
|
||||
- Removing or disabling an account, and every routing setting other than account priority.
|
||||
- Removing or disabling an account. Mos is filing a Vikunja task for a later row (mos-1608).
|
||||
- Every routing setting other than account priority.
|
||||
- Issuing or revoking client keys (P2), and any usage source (P5).
|
||||
- The upstream panel, any forward to the proxy port, and the subdomain fallback.
|
||||
- Traefik, DNS, Authentik settings, and the .54 Console's provider (Q218).
|
||||
@@ -126,4 +147,4 @@ bus and webui suites run whole, webui with `MOSAIC_REQUIRE_EDITOR_BUILD=1`. Ever
|
||||
whole. The named mutants below are killed. Every package suite but queue plus every `scripts/test-*.sh` is
|
||||
green on Sage's landing gate. BUILD.md has the upgrade-check output on the pinned version. Mutants: the
|
||||
accounts verb returns an unfiltered field; the callback verb skips the state check; a `/api/proxy/` route
|
||||
accepts a granted user.
|
||||
accepts a granted user; the API-key verb echoes the value in an error or a log.
|
||||
@@ -106,8 +106,9 @@ the key to its own management calls (Q250). An agent never runs the prompt (Q173
|
||||
|
||||
D7. **Upstream credentials.** Accounts are entered in the Console's proxy page (Q250). It covers OAuth
|
||||
sign-ins for Claude and Codex through the management API's `oauth/auth-url`, `oauth/callback` and
|
||||
`oauth/status`, and account priority. Whether the page also takes upstream API keys (for example z.ai) is
|
||||
open with Mos. They live in the
|
||||
`oauth/status`, upstream API keys (z.ai first, write-only and never echoed, mos-1608), and account
|
||||
priority. The page shows each account by an id, the first 12 hex of the sha256 of the proxy's own
|
||||
credential identifier, never of a token or key value (mos-1608). The credentials live in the
|
||||
proxy's `auth/` directory and config and nowhere else. There is no per-seat credential file. The stack never
|
||||
reads, copies or logs an upstream credential.
|
||||
|
||||
@@ -116,7 +117,7 @@ proxy's own panel. Jason's Q46 and Q47 answers replace it with the Console's own
|
||||
calls bus routes, the bus calls the management API (D12), and the management key stays in the bus (D6). No
|
||||
new hostname is added. A panel subdomain is a fallback only if our page can't keep up with the management
|
||||
API across pinned proxy upgrades, and that call is Jason's. Each pinned upgrade runs the page's acceptance
|
||||
script against the new version (P4 brief item 8).
|
||||
script against the new version (P4 brief item 9).
|
||||
|
||||
## 3. Priority routing, and how the business routing table maps onto it
|
||||
|
||||
@@ -323,7 +324,8 @@ accounts (J7). The 8317 host proxy and its clients are not part of any step.
|
||||
- **J4.** Open the Console's proxy page and sign in to Authentik as jason.woltje. Sign in to each subscription
|
||||
account you want this instance to use (Claude, Codex). If the sign-in tab doesn't return on its own, copy
|
||||
the full address from that tab and paste it into the page. Set each account's priority. The highest one is
|
||||
used first, and the next one only when the first is limited. API keys such as z.ai wait on Mos's ruling.
|
||||
used first, and the next one only when the first is limited. Paste each API key you want it to have (for
|
||||
example z.ai) into the page's API-key field. The page never shows it again.
|
||||
- **J5.** Watch the Console while the two test sessions run (cutover step 6), and confirm the proxy page's usage
|
||||
section shows their requests on the account you expect.
|
||||
- **J6.** After 7 days on the proxy, delete `claude-session.env` for that instance.
|
||||
|
||||
Reference in new issue
Block a user