@yadsh/dsh-qa-surface 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +151 -0
- package/README.md +239 -16
- package/SPEC-dsh-qa-surface.md +600 -96
- package/capability-policy.json +5 -0
- package/compatibility.json +18 -4
- package/cordis.patch.yml +11 -0
- package/docs/ARCHITECTURE.md +50 -8
- package/docs/COMPATIBILITY.md +5 -4
- package/docs/CONFIGURATION.md +184 -3
- package/docs/SOURCES-MIGRATION.md +28 -0
- package/docs/SOURCES-PROVENANCE-AUDIT.md +88 -0
- package/docs/SPEC-dsh-qa-surface-accounts.md +219 -0
- package/docs/SPEC-dsh-qa-surface-sources-provenance.md +1692 -0
- package/lib/accounts/index.js +2 -0
- package/lib/accounts/index.js.map +1 -0
- package/lib/accounts/store.js +478 -0
- package/lib/accounts/store.js.map +1 -0
- package/lib/attestation.js +10 -0
- package/lib/attestation.js.map +1 -0
- package/lib/cli.js +143 -0
- package/lib/cli.js.map +1 -0
- package/lib/client/QaAccountsController.js +329 -0
- package/lib/client/QaAccountsController.js.map +1 -0
- package/lib/client/QaConfigController.js +111 -4
- package/lib/client/QaConfigController.js.map +1 -1
- package/lib/client/QaSessionController.js +621 -163
- package/lib/client/QaSessionController.js.map +1 -1
- package/lib/client/QaSurface.js +303 -24
- package/lib/client/QaSurface.js.map +1 -1
- package/lib/client/QaTranscriptAdapter.js +324 -49
- package/lib/client/QaTranscriptAdapter.js.map +1 -1
- package/lib/client/attestation.js +66 -0
- package/lib/client/attestation.js.map +1 -0
- package/lib/client/chat-index.js +86 -0
- package/lib/client/chat-index.js.map +1 -0
- package/lib/client/components/Markdown.js +124 -13
- package/lib/client/components/Markdown.js.map +1 -1
- package/lib/client/components/QaAgentsDrawer.js +40 -0
- package/lib/client/components/QaAgentsDrawer.js.map +1 -0
- package/lib/client/components/QaAuthGate.js +40 -0
- package/lib/client/components/QaAuthGate.js.map +1 -0
- package/lib/client/components/QaChangelog.js +112 -0
- package/lib/client/components/QaChangelog.js.map +1 -0
- package/lib/client/components/QaComposer.js +132 -15
- package/lib/client/components/QaComposer.js.map +1 -1
- package/lib/client/components/QaMessage.js +214 -10
- package/lib/client/components/QaMessage.js.map +1 -1
- package/lib/client/components/QaSidebar.js +167 -0
- package/lib/client/components/QaSidebar.js.map +1 -0
- package/lib/client/components/QaSourcesDrawer.js +127 -0
- package/lib/client/components/QaSourcesDrawer.js.map +1 -0
- package/lib/client/components/QaTurnRail.js +281 -0
- package/lib/client/components/QaTurnRail.js.map +1 -0
- package/lib/client/components/QaWelcomeNotice.js +125 -0
- package/lib/client/components/QaWelcomeNotice.js.map +1 -0
- package/lib/client/components/QaWidthHandle.js +142 -0
- package/lib/client/components/QaWidthHandle.js.map +1 -0
- package/lib/client/components/QaWorkGroup.js +135 -0
- package/lib/client/components/QaWorkGroup.js.map +1 -0
- package/lib/client/components/VariantSwitcher.js +29 -0
- package/lib/client/components/VariantSwitcher.js.map +1 -0
- package/lib/client/components/format.js +48 -0
- package/lib/client/components/format.js.map +1 -0
- package/lib/client/components/source-refs.js +78 -0
- package/lib/client/components/source-refs.js.map +1 -0
- package/lib/client/create-session.js +51 -0
- package/lib/client/create-session.js.map +1 -0
- package/lib/client/index.js +166 -35
- package/lib/client/index.js.map +1 -1
- package/lib/client/project-session-state.js +78 -0
- package/lib/client/project-session-state.js.map +1 -0
- package/lib/client/session-admission.js +38 -0
- package/lib/client/session-admission.js.map +1 -0
- package/lib/client/session-assets.js +52 -0
- package/lib/client/session-assets.js.map +1 -0
- package/lib/client/session-sources.js +61 -0
- package/lib/client/session-sources.js.map +1 -0
- package/lib/client/settlement.js +24 -0
- package/lib/client/settlement.js.map +1 -0
- package/lib/client/styles.js +450 -43
- package/lib/client/styles.js.map +1 -1
- package/lib/client/tool-presentation.js +102 -0
- package/lib/client/tool-presentation.js.map +1 -0
- package/lib/client/turn-sources.js +98 -0
- package/lib/client/turn-sources.js.map +1 -0
- package/lib/client/types.js +15 -0
- package/lib/client/types.js.map +1 -0
- package/lib/client/use-session-ui-state.js +68 -0
- package/lib/client/use-session-ui-state.js.map +1 -0
- package/lib/client/use-transcript-view.js +44 -0
- package/lib/client/use-transcript-view.js.map +1 -0
- package/lib/client/wait-for.js +49 -0
- package/lib/client/wait-for.js.map +1 -0
- package/lib/client.js +12551 -918
- package/lib/client.js.map +1 -1
- package/lib/config.js +226 -58
- package/lib/config.js.map +1 -1
- package/lib/entry-redirect.js +44 -0
- package/lib/entry-redirect.js.map +1 -0
- package/lib/host-route.js +25 -1
- package/lib/host-route.js.map +1 -1
- package/lib/index.js +354 -72
- package/lib/index.js.map +1 -1
- package/lib/launch-token.js +42 -0
- package/lib/launch-token.js.map +1 -0
- package/lib/lockdown-policy.js +20 -0
- package/lib/lockdown-policy.js.map +1 -0
- package/lib/provenance/collector.js +40 -0
- package/lib/provenance/collector.js.map +1 -0
- package/lib/provenance/connectors.js +147 -0
- package/lib/provenance/connectors.js.map +1 -0
- package/lib/provenance/dedupe.js +89 -0
- package/lib/provenance/dedupe.js.map +1 -0
- package/lib/provenance/extractors.js +340 -0
- package/lib/provenance/extractors.js.map +1 -0
- package/lib/provenance/file-preview.js +39 -0
- package/lib/provenance/file-preview.js.map +1 -0
- package/lib/provenance/host-store.js +387 -0
- package/lib/provenance/host-store.js.map +1 -0
- package/lib/provenance/index.js +9 -0
- package/lib/provenance/index.js.map +1 -0
- package/lib/provenance/normalize.js +128 -0
- package/lib/provenance/normalize.js.map +1 -0
- package/lib/provenance/reported.js +53 -0
- package/lib/provenance/reported.js.map +1 -0
- package/lib/provenance/types.js +2 -0
- package/lib/provenance/types.js.map +1 -0
- package/lib/resolve-config.js +283 -10
- package/lib/resolve-config.js.map +1 -1
- package/lib/secure-session.js +212 -0
- package/lib/secure-session.js.map +1 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +784 -0
- package/lib/typert.remote-client.d.ts +40 -0
- package/lib/typert.remote-client.js +597 -0
- package/lib/types/accounts/index.d.ts +2 -0
- package/lib/types/accounts/store.d.ts +121 -0
- package/lib/types/attestation.d.ts +17 -0
- package/lib/types/cli.d.ts +9 -0
- package/lib/types/client/QaAccountsController.d.ts +95 -0
- package/lib/types/client/QaConfigController.d.ts +35 -3
- package/lib/types/client/QaSessionController.d.ts +146 -20
- package/lib/types/client/QaSurface.d.ts +11 -5
- package/lib/types/client/QaTranscriptAdapter.d.ts +13 -3
- package/lib/types/client/attestation.d.ts +25 -0
- package/lib/types/client/chat-index.d.ts +28 -0
- package/lib/types/client/components/Markdown.d.ts +17 -3
- package/lib/types/client/components/QaAgentsDrawer.d.ts +25 -0
- package/lib/types/client/components/QaAuthGate.d.ts +15 -0
- package/lib/types/client/components/QaChangelog.d.ts +21 -0
- package/lib/types/client/components/QaComposer.d.ts +12 -2
- package/lib/types/client/components/QaMessage.d.ts +22 -2
- package/lib/types/client/components/QaSidebar.d.ts +69 -0
- package/lib/types/client/components/QaSourcesDrawer.d.ts +20 -0
- package/lib/types/client/components/QaTurnRail.d.ts +59 -0
- package/lib/types/client/components/QaWelcomeNotice.d.ts +27 -0
- package/lib/types/client/components/QaWidthHandle.d.ts +36 -0
- package/lib/types/client/components/QaWorkGroup.d.ts +19 -0
- package/lib/types/client/components/VariantSwitcher.d.ts +19 -0
- package/lib/types/client/components/format.d.ts +9 -0
- package/lib/types/client/components/source-refs.d.ts +16 -0
- package/lib/types/client/create-session.d.ts +16 -0
- package/lib/types/client/index.d.ts +2 -5
- package/lib/types/client/project-session-state.d.ts +30 -0
- package/lib/types/client/session-admission.d.ts +23 -0
- package/lib/types/client/session-assets.d.ts +20 -0
- package/lib/types/client/session-sources.d.ts +28 -0
- package/lib/types/client/settlement.d.ts +6 -0
- package/lib/types/client/tool-presentation.d.ts +6 -0
- package/lib/types/client/turn-sources.d.ts +14 -0
- package/lib/types/client/types.d.ts +93 -0
- package/lib/types/client/use-session-ui-state.d.ts +35 -0
- package/lib/types/client/use-transcript-view.d.ts +20 -0
- package/lib/types/client/wait-for.d.ts +11 -0
- package/lib/types/entry-redirect.d.ts +20 -0
- package/lib/types/host-route.d.ts +14 -1
- package/lib/types/index.d.ts +67 -7
- package/lib/types/launch-token.d.ts +21 -0
- package/lib/types/lockdown-policy.d.ts +18 -0
- package/lib/types/provenance/collector.d.ts +26 -0
- package/lib/types/provenance/connectors.d.ts +11 -0
- package/lib/types/provenance/dedupe.d.ts +6 -0
- package/lib/types/provenance/extractors.d.ts +14 -0
- package/lib/types/provenance/file-preview.d.ts +10 -0
- package/lib/types/provenance/host-store.d.ts +54 -0
- package/lib/types/provenance/index.d.ts +10 -0
- package/lib/types/provenance/normalize.d.ts +13 -0
- package/lib/types/provenance/reported.d.ts +5 -0
- package/lib/types/provenance/types.d.ts +78 -0
- package/lib/types/secure-session.d.ts +31 -0
- package/lib/types/types.d.ts +304 -1
- package/package.json +70 -27
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# SPEC — dsh-qa-surface accounts and entry routing
|
|
2
|
+
|
|
3
|
+
Status: implemented (this document is the design record).
|
|
4
|
+
Scope: `plugins/dsh-qa-surface` plus the external `qa-deploy` kit
|
|
5
|
+
(proxy + launcher). The DSH harness itself is not modified.
|
|
6
|
+
|
|
7
|
+
## Goals
|
|
8
|
+
|
|
9
|
+
1. A LAN visitor who opens the server root lands on the QA surface, not the
|
|
10
|
+
full harness UI; the loopback operator keeps the harness root as-is.
|
|
11
|
+
2. A LAN visitor without the host browser-auth cookie is transparently sent
|
|
12
|
+
through the one-time `?token=` exchange and returned to `/qa` without ever
|
|
13
|
+
handling the launch token manually.
|
|
14
|
+
3. QA users identify themselves with an email + password account. Existing
|
|
15
|
+
per-browser chats migrate to the account on first login. Session ownership
|
|
16
|
+
is enforced server-side on the plugin's admission boundary.
|
|
17
|
+
|
|
18
|
+
## Non-goals / honest boundary
|
|
19
|
+
|
|
20
|
+
- The host launch token remains a master key: `/api` RPC requires the
|
|
21
|
+
host-auth cookie (`rpc-host.ts` rejects 401 without it), so every QA user
|
|
22
|
+
must hold it, and with it the full harness UI is technically reachable.
|
|
23
|
+
Accounts gate the QA surface and the plugin's own remotes server-side; they
|
|
24
|
+
do not isolate a determined user from the harness. This is documented in the
|
|
25
|
+
deployment kit and the disclaimer stays visible in the UI.
|
|
26
|
+
- No email verification (no SMTP): the email is an identifier, not a contact
|
|
27
|
+
channel.
|
|
28
|
+
- No admin UI for user management in this phase; the first registered account
|
|
29
|
+
becomes `admin`, later roles change via the accounts file.
|
|
30
|
+
|
|
31
|
+
## Wire design: explicit account token, not cookies
|
|
32
|
+
|
|
33
|
+
Typert remotes receive only `(endpoint, payload, signal)` — the HTTP request
|
|
34
|
+
never reaches a remote method (`packages/client/connection/src/rpc-host.ts`,
|
|
35
|
+
`rpcFetchHandler`), so a cookie-based gate inside remotes is impossible
|
|
36
|
+
without harness changes. Named plugin routes do see `req`/`res`, but keeping
|
|
37
|
+
auth on a parallel HTTP surface would leave the existing typert remotes
|
|
38
|
+
unguarded.
|
|
39
|
+
|
|
40
|
+
Therefore the account credential is an explicit first argument on every
|
|
41
|
+
gated remote, and login/register return it in the value:
|
|
42
|
+
|
|
43
|
+
- `accountsLogin(email, password)` → `{ token, user }`
|
|
44
|
+
- `accountsRegister(email, password, displayName?)` → `{ token, user }`
|
|
45
|
+
- `accountsWhoami(token)` → `{ authenticated: true, user } | { authenticated: false }`
|
|
46
|
+
- `accountsClaimSessions(token, sessionIds)` → `{ claimed, conflicts }`
|
|
47
|
+
- `accountsOwnedSessions(token)` → `{ ids }`
|
|
48
|
+
- `accountsListOwnership(token)` → `{ entries: [{ sessionId, userId,
|
|
49
|
+
displayName, claimedAt }] }` — admin-only (`admin-required` refusal for
|
|
50
|
+
ordinary accounts, `auth-required` for anonymous)
|
|
51
|
+
- `secureSession(token, sessionId)` — now requires a valid token when
|
|
52
|
+
accounts are enabled
|
|
53
|
+
- `sources(token, sessionId)`, `readSourceFile(token, sessionId, path)` — same
|
|
54
|
+
|
|
55
|
+
The token is an HMAC-SHA256-signed `v1.<payload>.<sig>` string
|
|
56
|
+
(payload `{ uid, exp }`) keyed by a secret generated once and persisted in
|
|
57
|
+
the accounts file. The browser keeps it in `localStorage` under the
|
|
58
|
+
deployment's per-route key. Login/register failures are coarse (same message
|
|
59
|
+
for unknown email and wrong password); registration rejects duplicate emails;
|
|
60
|
+
a per-store in-memory counter rate-limits auth attempts.
|
|
61
|
+
|
|
62
|
+
## Server: accounts store and admission
|
|
63
|
+
|
|
64
|
+
- File: `$DSH_HOME/qa-accounts.json` (env fallback: process cwd). Shape:
|
|
65
|
+
`{ version, secret, users: [{ id, email, displayName, role, passwordHash,
|
|
66
|
+
createdAt, lastLoginAt }], ownership: { [sessionId]: { userId, claimedAt } } }`.
|
|
67
|
+
Written atomically (temp file + rename), all mutations synchronous.
|
|
68
|
+
- Passwords: scrypt with per-user salt, timing-safe comparison.
|
|
69
|
+
- Roles: `user` | `admin`. The first registered user becomes `admin`.
|
|
70
|
+
Admins bypass the ownership refusal (the surface is read-only anyway);
|
|
71
|
+
non-admins get a coarse `session-owned-elsewhere` attestation refusal for
|
|
72
|
+
sessions owned by someone else.
|
|
73
|
+
- Claim rule (first come, first served): attesting an _unowned_ session claims
|
|
74
|
+
it for the requesting user (this is how pre-accounts sessions migrate when
|
|
75
|
+
an old browser re-opens its `activeId` without a bulk claim). Bulk claims
|
|
76
|
+
from the client's local index follow the same rule; conflicting ids are
|
|
77
|
+
reported back and dropped from that browser's index.
|
|
78
|
+
- `QaPolicyAdmission.secureSession` gains an optional accounts gate: when
|
|
79
|
+
`accounts.enabled`, an invalid/expired token refuses with
|
|
80
|
+
`auth-required` before any policy work.
|
|
81
|
+
|
|
82
|
+
## Entry redirect (host-injected script)
|
|
83
|
+
|
|
84
|
+
The plugin listens to the `webserver/index-inject` event and pushes one
|
|
85
|
+
inline `script` row into the served root `index.html` (config
|
|
86
|
+
`entry.redirectNonLoopback`, default true):
|
|
87
|
+
|
|
88
|
+
- Skip when the navigation marker (`__dsh_qa_route`) is present — the `/qa`
|
|
89
|
+
route hands off through `/?__dsh_qa_route=…` and a naive redirect would
|
|
90
|
+
loop.
|
|
91
|
+
- Skip when `?ui=admin` is set (also persists the choice under the
|
|
92
|
+
deployment's storage key); `?ui=qa` clears the choice.
|
|
93
|
+
- Skip when the persisted choice is `admin`.
|
|
94
|
+
- Otherwise redirect non-loopback hostnames (`localhost`, `127.0.0.1`,
|
|
95
|
+
`::1`, `*.localhost`) to `/qa`. The check is client-side on
|
|
96
|
+
`location.hostname` **on purpose**: behind the qa-deploy proxy every
|
|
97
|
+
request appears loopback to the server, and the operator convention is
|
|
98
|
+
"operator uses localhost, QA users use the LAN address".
|
|
99
|
+
|
|
100
|
+
The script is defensive (try/catch, no dependency on bundle load) and never
|
|
101
|
+
redirects when the plugin is disabled or the flag is off.
|
|
102
|
+
|
|
103
|
+
## Client: accounts controller and auth gate
|
|
104
|
+
|
|
105
|
+
- `QaAccountsController` owns the state machine
|
|
106
|
+
`checking → anonymous → authed`, the token persistence, login/register
|
|
107
|
+
calls, the one-shot migration claim (local chat index → `claimSessions`)
|
|
108
|
+
after each successful login/registration, and the owned-session id list
|
|
109
|
+
that backs the sidebar.
|
|
110
|
+
- `QaSurface` renders a full-frame `QaAuthGate` (login/register tabs, email +
|
|
111
|
+
password, coarse errors, busy state) instead of the chat while accounts are
|
|
112
|
+
enabled and the user is anonymous; the session controller is not created
|
|
113
|
+
until `authed`, so nothing attests or sends before login.
|
|
114
|
+
- `QaSessionController` threads the token into `secureSession`/`sources`/
|
|
115
|
+
`readSourceFile` calls and merges the owned-session list into the chat
|
|
116
|
+
index; newly created chats are claimed at creation time. When accounts are
|
|
117
|
+
disabled the controller behaves exactly as before.
|
|
118
|
+
- An attestation refusal with reason `auth-required` re-opens the gate
|
|
119
|
+
(token expired/rotated) instead of surfacing the generic configuration
|
|
120
|
+
error.
|
|
121
|
+
- A small account chip (email, role, logout) lives in the sidebar footer;
|
|
122
|
+
logout clears the token and returns to the gate.
|
|
123
|
+
|
|
124
|
+
## Admin ownership views
|
|
125
|
+
|
|
126
|
+
When `accounts.showOtherUsersChats: true`, the admin sidebar switches from the
|
|
127
|
+
flat list to per-owner sections. The option defaults to false, so admins see
|
|
128
|
+
only their own chats unless the deployment explicitly opts in.
|
|
129
|
+
`accountsListOwnership` returns every ownership entry with the owner's
|
|
130
|
+
display name resolved at read time (disabled accounts still name their
|
|
131
|
+
chats), the controller merges those session ids into the visible list, and
|
|
132
|
+
`QaSidebar` renders one section per owner ordered by its freshest chat, with
|
|
133
|
+
unclaimed chats trailing under "Без владельца". Search filters rows first,
|
|
134
|
+
so empty sections disappear while searching. Ordinary accounts and
|
|
135
|
+
deployments with accounts disabled keep the exact flat list as before.
|
|
136
|
+
|
|
137
|
+
The same data drives author labels: user messages carry an `author` byline
|
|
138
|
+
with the chat owner's display name, but only for an admin reading a foreign
|
|
139
|
+
chat — the owner themself sees no label, and authorship is chat-level (the
|
|
140
|
+
ownership map), not per-message. Admins replying inside someone else's chat
|
|
141
|
+
are therefore labeled with the chat owner, not with their own name; the
|
|
142
|
+
per-message identity channel (the client-minted `requestId` on
|
|
143
|
+
`session/prompt`) is a possible future refinement.
|
|
144
|
+
|
|
145
|
+
## Config surface
|
|
146
|
+
|
|
147
|
+
```yaml
|
|
148
|
+
accounts:
|
|
149
|
+
enabled: false # opt-in; the qa-deploy profile patch turns it on
|
|
150
|
+
allowRegistration: true # self-service signup in the gate
|
|
151
|
+
sessionTtlDays: 30 # account token lifetime
|
|
152
|
+
showOtherUsersChats: false # opt-in cross-user admin view
|
|
153
|
+
entry:
|
|
154
|
+
redirectNonLoopback: true # the root → /qa script above
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
No file paths or secrets are configurable through the schema: the store path
|
|
158
|
+
is derived from `DSH_HOME` internally, and `describe()` (the LAN config
|
|
159
|
+
channel) keeps projecting only client-safe values.
|
|
160
|
+
|
|
161
|
+
## qa-deploy kit (external repo)
|
|
162
|
+
|
|
163
|
+
- `run-qa.sh` extracts the launch token from the boot banner
|
|
164
|
+
(`dsh web: …?token=…`) and starts `proxy.mjs` in front of the harness
|
|
165
|
+
(default proxy port 8088, `DSH_QA_PROXY=0` to disable). LAN visitors are
|
|
166
|
+
pointed at the proxy address; the operator keeps the direct loopback URL.
|
|
167
|
+
- `proxy.mjs` gains the transparent exchange: a GET/HEAD page navigation to
|
|
168
|
+
`/` or `/qa…` without a `dsh-auth-` cookie and without `?token=` gets a 302
|
|
169
|
+
to `/?token=<TOKEN>`; the harness exchanges it (server-side 303) and the
|
|
170
|
+
injected script finishes the trip to `/qa`. Everything else passes through
|
|
171
|
+
unchanged (Host/Origin rewrite, polyfill, WS splice).
|
|
172
|
+
- The profile patch (`profile/cordis.patch.yml`) enables
|
|
173
|
+
`accounts.enabled`, keeping self-registration on.
|
|
174
|
+
|
|
175
|
+
## Testing
|
|
176
|
+
|
|
177
|
+
- Store: scrypt round-trip, duplicate-email rejection, first-user-admin,
|
|
178
|
+
atomic persistence, token sign/verify/expire, ownership claim/conflict.
|
|
179
|
+
- Admission: token-less refusal when enabled, ownership refusal, admin
|
|
180
|
+
bypass, unowned auto-claim, disabled passthrough.
|
|
181
|
+
- Entry redirect: script text guards (marker, `ui=admin`/`?ui=qa`,
|
|
182
|
+
loopback hostnames, disable flag), injection through the index-inject
|
|
183
|
+
event.
|
|
184
|
+
- Client: controller state machine, migration claim, token threading,
|
|
185
|
+
auth-required recovery; auth gate rendering in the component suite.
|
|
186
|
+
- Admin views: ownership listing (display-name resolution, admin/anonymous
|
|
187
|
+
refusals), the visible-id merge, author-label rules, owner section
|
|
188
|
+
ordering and rendering.
|
|
189
|
+
- Packed verification keeps asserting the standard card shell and now the
|
|
190
|
+
auth gate markup.
|
|
191
|
+
|
|
192
|
+
## Follow-ups adopted from dsh-auth-gate (same release)
|
|
193
|
+
|
|
194
|
+
A review of the independent `dsh-auth-gate` plugin yielded four additions,
|
|
195
|
+
folded into this spec rather than shipped as a separate design:
|
|
196
|
+
|
|
197
|
+
1. **Proxy-side config-plane deny list** (qa-deploy kit). The deploy proxy
|
|
198
|
+
rewrites Host/Origin to loopback, which makes the `/api` trust fence treat
|
|
199
|
+
every proxied request as loopback — including the privileged methods
|
|
200
|
+
(`settings.*`, `credentials.*`, `host.*`, `llm.*`). The proxy now answers
|
|
201
|
+
403 for those method prefixes when the caller is not loopback. The QA
|
|
202
|
+
surface never calls them (its configuration rides `qaSurface/describe`);
|
|
203
|
+
the operator on the serving machine keeps the full plane.
|
|
204
|
+
2. **Plugin-side launch-token bridge.** The `/qa` route handler defers the
|
|
205
|
+
marker hand-off for cookie-less browsers: it 302s to a relative
|
|
206
|
+
`/?token=…` first (token resolved once per process from
|
|
207
|
+
`connection.authenticatedUrl`, the dsh-auth-gate pattern), so the
|
|
208
|
+
transparent entry works without the proxy. `entry.cookieBootstrap`
|
|
209
|
+
(default true) turns it off. Presence of any `dsh-auth-` cookie skips the
|
|
210
|
+
bridge; a token-less bridge or a disabled flag falls back to the marker
|
|
211
|
+
hand-off.
|
|
212
|
+
3. **`qa-accounts` management CLI** (`bin` entry). `list`, `add
|
|
213
|
+
--password-stdin`, `set-role`, `disable`, `enable`, `revoke` — so account
|
|
214
|
+
administration never requires hand-editing the JSON file.
|
|
215
|
+
4. **Account state and token revocation.** Accounts gain a `disabled` flag
|
|
216
|
+
(refused at login with `account-disabled`, invisible to `whoami`) and a
|
|
217
|
+
`tokenVersion` burned into every token: `disable` and `revoke` bump it,
|
|
218
|
+
invalidating all live tokens server-side — logout is no longer purely a
|
|
219
|
+
client-side act.
|