@yadsh/dsh-qa-surface 0.3.0 → 0.4.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/README.md +153 -12
- package/lib/account-remotes.js +100 -0
- package/lib/account-remotes.js.map +1 -0
- package/lib/accounts/credentials.js +27 -0
- package/lib/accounts/credentials.js.map +1 -0
- package/lib/accounts/errors.js +9 -0
- package/lib/accounts/errors.js.map +1 -0
- package/lib/accounts/file.js +101 -0
- package/lib/accounts/file.js.map +1 -0
- package/lib/accounts/store.js +144 -161
- package/lib/accounts/store.js.map +1 -1
- package/lib/accounts/token.js +61 -0
- package/lib/accounts/token.js.map +1 -0
- package/lib/attachment-rules.js +111 -0
- package/lib/attachment-rules.js.map +1 -0
- package/lib/cli.js +122 -9
- package/lib/cli.js.map +1 -1
- package/lib/client/QaAccountsController.js +32 -2
- package/lib/client/QaAccountsController.js.map +1 -1
- package/lib/client/QaSessionController.js +47 -65
- package/lib/client/QaSessionController.js.map +1 -1
- package/lib/client/QaSurface.js +121 -107
- package/lib/client/QaSurface.js.map +1 -1
- package/lib/client/QaTranscriptAdapter.js +71 -6
- package/lib/client/QaTranscriptAdapter.js.map +1 -1
- package/lib/client/attachments.js +150 -0
- package/lib/client/attachments.js.map +1 -0
- package/lib/client/attestation.js +1 -1
- package/lib/client/attestation.js.map +1 -1
- package/lib/client/base64.js +16 -0
- package/lib/client/base64.js.map +1 -0
- package/lib/client/chat-files.js +48 -0
- package/lib/client/chat-files.js.map +1 -0
- package/lib/client/components/Markdown.js +1 -1
- package/lib/client/components/Markdown.js.map +1 -1
- package/lib/client/components/QaChangelog.js +38 -19
- package/lib/client/components/QaChangelog.js.map +1 -1
- package/lib/client/components/QaComposer.js +56 -74
- package/lib/client/components/QaComposer.js.map +1 -1
- package/lib/client/components/QaFileAttachment.js +19 -0
- package/lib/client/components/QaFileAttachment.js.map +1 -0
- package/lib/client/components/QaFilesPanel.js +45 -0
- package/lib/client/components/QaFilesPanel.js.map +1 -0
- package/lib/client/components/QaHeader.js +34 -0
- package/lib/client/components/QaHeader.js.map +1 -0
- package/lib/client/components/QaMessage.js +17 -4
- package/lib/client/components/QaMessage.js.map +1 -1
- package/lib/client/components/QaModal.js +31 -0
- package/lib/client/components/QaModal.js.map +1 -0
- package/lib/client/components/QaProfile.js +64 -0
- package/lib/client/components/QaProfile.js.map +1 -0
- package/lib/client/components/QaRightRail.js +15 -0
- package/lib/client/components/QaRightRail.js.map +1 -0
- package/lib/client/components/QaSidebar.js +6 -1
- package/lib/client/components/QaSidebar.js.map +1 -1
- package/lib/client/components/{QaSourcesDrawer.js → QaSourcesPanel.js} +3 -3
- package/lib/client/components/QaSourcesPanel.js.map +1 -0
- package/lib/client/components/QaTurnRail.js +15 -0
- package/lib/client/components/QaTurnRail.js.map +1 -1
- package/lib/client/components/QaWidthHandle.js +25 -20
- package/lib/client/components/QaWidthHandle.js.map +1 -1
- package/lib/client/components/QaWorkGroup.js +19 -20
- package/lib/client/components/QaWorkGroup.js.map +1 -1
- package/lib/client/components/surface-utils.js +28 -0
- package/lib/client/components/surface-utils.js.map +1 -0
- package/lib/client/components/thinking-phrases.js +24 -0
- package/lib/client/components/thinking-phrases.js.map +1 -0
- package/lib/client/create-session.js +7 -32
- package/lib/client/create-session.js.map +1 -1
- package/lib/client/failure-copy.js +63 -0
- package/lib/client/failure-copy.js.map +1 -0
- package/lib/client/index.js +38 -7
- package/lib/client/index.js.map +1 -1
- package/lib/client/prompt-content.js +54 -0
- package/lib/client/prompt-content.js.map +1 -0
- package/lib/client/settings/card.js +143 -0
- package/lib/client/settings/card.js.map +1 -0
- package/lib/client/settings/fields.js +145 -0
- package/lib/client/settings/fields.js.map +1 -0
- package/lib/client/settings/format.js +255 -0
- package/lib/client/settings/format.js.map +1 -0
- package/lib/client/settings/sections/access.js +25 -0
- package/lib/client/settings/sections/access.js.map +1 -0
- package/lib/client/settings/sections/accounts.js +47 -0
- package/lib/client/settings/sections/accounts.js.map +1 -0
- package/lib/client/settings/sections/attachments.js +46 -0
- package/lib/client/settings/sections/attachments.js.map +1 -0
- package/lib/client/settings/sections/branding.js +28 -0
- package/lib/client/settings/sections/branding.js.map +1 -0
- package/lib/client/settings/sections/common.js +20 -0
- package/lib/client/settings/sections/common.js.map +1 -0
- package/lib/client/settings/sections/embedding.js +19 -0
- package/lib/client/settings/sections/embedding.js.map +1 -0
- package/lib/client/settings/sections/interface.js +60 -0
- package/lib/client/settings/sections/interface.js.map +1 -0
- package/lib/client/settings/sections/lockdown.js +82 -0
- package/lib/client/settings/sections/lockdown.js.map +1 -0
- package/lib/client/settings/sections/session.js +53 -0
- package/lib/client/settings/sections/session.js.map +1 -0
- package/lib/client/settings/sections/sources.js +72 -0
- package/lib/client/settings/sections/sources.js.map +1 -0
- package/lib/client/settings/sections/status.js +37 -0
- package/lib/client/settings/sections/status.js.map +1 -0
- package/lib/client/settings/sections.js +23 -0
- package/lib/client/settings/sections.js.map +1 -0
- package/lib/client/settings/styles.js +57 -0
- package/lib/client/settings/styles.js.map +1 -0
- package/lib/client/stream-publisher.js +47 -0
- package/lib/client/stream-publisher.js.map +1 -0
- package/lib/client/styles.js +85 -19
- package/lib/client/styles.js.map +1 -1
- package/lib/client/types.js.map +1 -1
- package/lib/client/use-right-rail.js +96 -0
- package/lib/client/use-right-rail.js.map +1 -0
- package/lib/client/use-session-ui-state.js +26 -19
- package/lib/client/use-session-ui-state.js.map +1 -1
- package/lib/client.js +4788 -869
- package/lib/client.js.map +1 -1
- package/lib/config-resolvers/accounts.js +60 -0
- package/lib/config-resolvers/accounts.js.map +1 -0
- package/lib/config-resolvers/attachments.js +28 -0
- package/lib/config-resolvers/attachments.js.map +1 -0
- package/lib/config-resolvers/basics.js +59 -0
- package/lib/config-resolvers/basics.js.map +1 -0
- package/lib/config-resolvers/defaults.js +132 -0
- package/lib/config-resolvers/defaults.js.map +1 -0
- package/lib/config-resolvers/lockdown.js +79 -0
- package/lib/config-resolvers/lockdown.js.map +1 -0
- package/lib/config-resolvers/session.js +42 -0
- package/lib/config-resolvers/session.js.map +1 -0
- package/lib/config-resolvers/shared.js +42 -0
- package/lib/config-resolvers/shared.js.map +1 -0
- package/lib/config-resolvers/sources.js +81 -0
- package/lib/config-resolvers/sources.js.map +1 -0
- package/lib/config-resolvers/ui.js +20 -0
- package/lib/config-resolvers/ui.js.map +1 -0
- package/lib/config.js +65 -4
- package/lib/config.js.map +1 -1
- package/lib/entry-redirect.js +2 -1
- package/lib/entry-redirect.js.map +1 -1
- package/lib/index.js +123 -74
- package/lib/index.js.map +1 -1
- package/lib/profile.js +214 -0
- package/lib/profile.js.map +1 -0
- package/lib/prompt-notes.js +231 -0
- package/lib/prompt-notes.js.map +1 -0
- package/lib/resolve-config.js +35 -410
- package/lib/resolve-config.js.map +1 -1
- package/lib/secure-session.js +82 -16
- package/lib/secure-session.js.map +1 -1
- package/lib/shared/session-key.js +18 -0
- package/lib/shared/session-key.js.map +1 -0
- package/lib/shared/settings.js +10 -0
- package/lib/shared/settings.js.map +1 -0
- package/lib/thinking-phrases.js +68 -0
- package/lib/thinking-phrases.js.map +1 -0
- package/lib/typert.host.js +162 -17
- package/lib/typert.remote-client.d.ts +5 -1
- package/lib/typert.remote-client.js +132 -13
- package/lib/types/account-remotes.d.ts +42 -0
- package/lib/types/accounts/credentials.d.ts +14 -0
- package/lib/types/accounts/errors.d.ts +11 -0
- package/lib/types/accounts/file.d.ts +82 -0
- package/lib/types/accounts/store.d.ts +66 -43
- package/lib/types/accounts/token.d.ts +31 -0
- package/lib/types/attachment-rules.d.ts +44 -0
- package/lib/types/cli.d.ts +2 -2
- package/lib/types/client/QaAccountsController.d.ts +8 -1
- package/lib/types/client/QaSessionController.d.ts +16 -16
- package/lib/types/client/QaSurface.d.ts +7 -1
- package/lib/types/client/attachments.d.ts +54 -0
- package/lib/types/client/base64.d.ts +10 -0
- package/lib/types/client/chat-files.d.ts +30 -0
- package/lib/types/client/components/QaChangelog.d.ts +1 -1
- package/lib/types/client/components/QaComposer.d.ts +8 -4
- package/lib/types/client/components/QaFileAttachment.d.ts +14 -0
- package/lib/types/client/components/QaFilesPanel.d.ts +15 -0
- package/lib/types/client/components/QaHeader.d.ts +41 -0
- package/lib/types/client/components/QaMessage.d.ts +3 -1
- package/lib/types/client/components/QaModal.d.ts +26 -0
- package/lib/types/client/components/QaProfile.d.ts +25 -0
- package/lib/types/client/components/QaRightRail.d.ts +23 -0
- package/lib/types/client/components/QaSidebar.d.ts +14 -0
- package/lib/types/client/components/{QaSourcesDrawer.d.ts → QaSourcesPanel.d.ts} +7 -4
- package/lib/types/client/components/QaTurnRail.d.ts +6 -0
- package/lib/types/client/components/QaWidthHandle.d.ts +14 -7
- package/lib/types/client/components/QaWorkGroup.d.ts +5 -3
- package/lib/types/client/components/surface-utils.d.ts +11 -0
- package/lib/types/client/components/thinking-phrases.d.ts +9 -0
- package/lib/types/client/create-session.d.ts +6 -9
- package/lib/types/client/failure-copy.d.ts +19 -0
- package/lib/types/client/prompt-content.d.ts +30 -0
- package/lib/types/client/settings/card.d.ts +23 -0
- package/lib/types/client/settings/fields.d.ts +139 -0
- package/lib/types/client/settings/format.d.ts +108 -0
- package/lib/types/client/settings/sections/access.d.ts +9 -0
- package/lib/types/client/settings/sections/accounts.d.ts +9 -0
- package/lib/types/client/settings/sections/attachments.d.ts +15 -0
- package/lib/types/client/settings/sections/branding.d.ts +8 -0
- package/lib/types/client/settings/sections/common.d.ts +28 -0
- package/lib/types/client/settings/sections/embedding.d.ts +8 -0
- package/lib/types/client/settings/sections/interface.d.ts +9 -0
- package/lib/types/client/settings/sections/lockdown.d.ts +9 -0
- package/lib/types/client/settings/sections/session.d.ts +9 -0
- package/lib/types/client/settings/sections/sources.d.ts +9 -0
- package/lib/types/client/settings/sections/status.d.ts +22 -0
- package/lib/types/client/settings/sections.d.ts +25 -0
- package/lib/types/client/settings/styles.d.ts +8 -0
- package/lib/types/client/stream-publisher.d.ts +28 -0
- package/lib/types/client/types.d.ts +29 -1
- package/lib/types/client/use-right-rail.d.ts +56 -0
- package/lib/types/client/use-session-ui-state.d.ts +13 -7
- package/lib/types/config-resolvers/accounts.d.ts +15 -0
- package/lib/types/config-resolvers/attachments.d.ts +6 -0
- package/lib/types/config-resolvers/basics.d.ts +8 -0
- package/lib/types/config-resolvers/defaults.d.ts +4 -0
- package/lib/types/config-resolvers/lockdown.d.ts +10 -0
- package/lib/types/config-resolvers/session.d.ts +6 -0
- package/lib/types/config-resolvers/shared.d.ts +16 -0
- package/lib/types/config-resolvers/sources.d.ts +6 -0
- package/lib/types/config-resolvers/ui.d.ts +6 -0
- package/lib/types/index.d.ts +20 -13
- package/lib/types/profile.d.ts +76 -0
- package/lib/types/prompt-notes.d.ts +110 -0
- package/lib/types/resolve-config.d.ts +8 -3
- package/lib/types/secure-session.d.ts +26 -1
- package/lib/types/shared/session-key.d.ts +23 -0
- package/lib/types/shared/settings.d.ts +10 -0
- package/lib/types/thinking-phrases.d.ts +17 -0
- package/lib/types/types.d.ts +123 -5
- package/lib/types/user-workspace.d.ts +38 -0
- package/lib/user-workspace.js +231 -0
- package/lib/user-workspace.js.map +1 -0
- package/package.json +14 -7
- package/CHANGELOG.md +0 -176
- package/SPEC-dsh-qa-surface.md +0 -2425
- package/docs/ARCHITECTURE.md +0 -66
- package/docs/COMPATIBILITY.md +0 -14
- package/docs/CONFIGURATION.md +0 -199
- package/docs/EMBEDDING.md +0 -13
- package/docs/SOURCES-MIGRATION.md +0 -28
- package/docs/SOURCES-PROVENANCE-AUDIT.md +0 -88
- package/docs/SPEC-dsh-qa-surface-accounts.md +0 -219
- package/docs/SPEC-dsh-qa-surface-sources-provenance.md +0 -1692
- package/lib/client/components/QaSourcesDrawer.js.map +0 -1
package/docs/ARCHITECTURE.md
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# Architecture
|
|
2
|
-
|
|
3
|
-
The Host half validates the Loader configuration, registers the `qa-surface`
|
|
4
|
-
settings namespace and owns one narrow GET/HEAD route for the configured QA
|
|
5
|
-
path. Published DSH releases return 404 for unknown frontend paths, so the
|
|
6
|
-
route redirects navigation through the canonical `/` index with an encoded
|
|
7
|
-
same-origin path marker. It also exposes one typed
|
|
8
|
-
`secureSession(sessionId)` operation whose policy is read only from Host
|
|
9
|
-
settings. The entry delegates that operation to `QaPolicyAdmission`
|
|
10
|
-
(`secure-session.ts`), the Host-side admission boundary. It does not serve
|
|
11
|
-
HTML, create another server, replace the frontend fallback, or touch
|
|
12
|
-
API/plugin routes. The browser removes the
|
|
13
|
-
marker and restores the requested path with `history.replaceState` before
|
|
14
|
-
route matching. The Host creates no sessions itself.
|
|
15
|
-
|
|
16
|
-
The browser half registers `QaSurface` in the root-scoped `shell.overlay` list.
|
|
17
|
-
`QaRouteController` keeps that entry dormant outside the configured path and
|
|
18
|
-
reversibly observes `pushState`, `replaceState` and `popstate`.
|
|
19
|
-
|
|
20
|
-
`QaSessionController` is the only browser-side DSH session-coupled module. It uses
|
|
21
|
-
`connection.api.sessions.create/selectModel` for creation policy and the public
|
|
22
|
-
`SessionFace` for prompt, cancel and snapshots; the shared client contracts and
|
|
23
|
-
the idle state live in `client/types.ts`. Its supporting modules are pure
|
|
24
|
-
helpers: `client/wait-for.ts` waits on observable stores, `client/chat-index.ts`
|
|
25
|
-
(`QaChatIndex`) keeps the browser-local chat index and persisted active
|
|
26
|
-
session in storage, and `client/attestation.ts` parses Host reason markers and
|
|
27
|
-
validates proofs against the deployed lockdown config. `QaTranscriptAdapter`
|
|
28
|
-
projects session snapshots to plugin-local messages. "New chat" enters a
|
|
29
|
-
draft that materializes an attested session only when the first prompt is
|
|
30
|
-
sent, and the chat list orders by the host's `updatedAt`. The log is
|
|
31
|
-
append-only and there is no truncation seam, so regeneration prompts a
|
|
32
|
-
hidden instruction and projects the follow-up turns as variants of one
|
|
33
|
-
question; the same projection flattens tool activity into the sources
|
|
34
|
-
drawer (web targets, searches, files read). Subagent delegation, when the
|
|
35
|
-
deployment opts it in, renders launches and settlement notices inline, and
|
|
36
|
-
the agents drawer opens a child session as a read-only live transcript: the
|
|
37
|
-
binding skips attestation and every send path stays closed. By default it excludes
|
|
38
|
-
reasoning, tool arguments and tool results. When explicitly enabled, it
|
|
39
|
-
correlates assistant blocks, paired tool results, running calls and turn
|
|
40
|
-
timings into one plugin-local work group per turn; `QaWorkGroup` owns
|
|
41
|
-
disclosure state and collapses the group when the turn completes. System
|
|
42
|
-
context and raw failures remain excluded.
|
|
43
|
-
|
|
44
|
-
In locked mode the Host operation verifies immutable session composition,
|
|
45
|
-
refuses to adopt a non-QA session with user history, validates the named
|
|
46
|
-
permission preset, installs `ToolRestriction.allow`, installs an agent-scoped
|
|
47
|
-
monotonic execution guard, selects the permission preset, and returns only a
|
|
48
|
-
sanitized proof. The controller remains non-writable until every proof field
|
|
49
|
-
matches its Host-delivered config and repeats the check before prompt
|
|
50
|
-
admission. No private RPC handler, slash command, direct provider API, or DSH
|
|
51
|
-
core patch is used.
|
|
52
|
-
|
|
53
|
-
This narrows a QA agent's effective session/tool policy, but a route overlay on
|
|
54
|
-
a shared privileged Host is not an authorization boundary for other DSH
|
|
55
|
-
clients. Strong isolation requires a dedicated process/profile, restricted
|
|
56
|
-
identity and network boundary.
|
|
57
|
-
|
|
58
|
-
All interaction colors come from the company interaction palette
|
|
59
|
-
("Цвета взаимодействия"), defined once as the `QA_BRAND_PALETTE` object at the
|
|
60
|
-
top of `styles.ts` and emitted as `--dsh-qa-*` custom properties on the
|
|
61
|
-
overlay root; surfaces and typography keep using the themed `--dsw-alias-*`
|
|
62
|
-
tokens so light and dark hosts stay coherent.
|
|
63
|
-
|
|
64
|
-
The overlay locks document scrolling and contains keyboard focus while active.
|
|
65
|
-
It does not locate AppFrame through generated CSS classes and does not dispose
|
|
66
|
-
global DSH runtime services when the route changes.
|
package/docs/COMPATIBILITY.md
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# Compatibility
|
|
2
|
-
|
|
3
|
-
| Plugin version | Tested DSH release | Required public contracts |
|
|
4
|
-
| -------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
5
|
-
| 0.1.x | 0.1.5-rc.2 | `shell.overlay`, Typert remote, `agentPresets.composedPreset`, `permissionPresets`, agent-scoped `tools.restrict/guard`, `workspaceRegistry`, session/model APIs, `webServer.register` |
|
|
6
|
-
|
|
7
|
-
DSH is developer-preview software. Compatibility-sensitive calls are isolated
|
|
8
|
-
in `QaSessionController`, `QaTranscriptAdapter`, `QaConfigController` and
|
|
9
|
-
`QaRouteController`; Host lockdown coupling is isolated in
|
|
10
|
-
`QaSurface.secureSession`. The Host navigation adapter also works around this tested
|
|
11
|
-
release's exact-index static serving: `/qa` is redirected through `/`, then the
|
|
12
|
-
client restores the original pathname. Missing settings fall back to default
|
|
13
|
-
browser config; missing required client services prevent the browser plugin
|
|
14
|
-
from being injected rather than patching DSH internals.
|
package/docs/CONFIGURATION.md
DELETED
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
# Configuration reference
|
|
2
|
-
|
|
3
|
-
Defaults and constraints are documented in the root README. Important runtime
|
|
4
|
-
rules are:
|
|
5
|
-
|
|
6
|
-
- `route.path` starts with `/`, has trailing slashes removed, and cannot claim
|
|
7
|
-
`/`, `/api` or `/plugins`;
|
|
8
|
-
- `fixed` requires `fixedSessionId`;
|
|
9
|
-
- `provider` and `model` are either both absent or both present;
|
|
10
|
-
- `maxContentWidth` is an integer from 480 through 1600 and caps the
|
|
11
|
-
user-resizable transcript/composer width; each QA route persists its chosen
|
|
12
|
-
width in browser storage;
|
|
13
|
-
- duplicate/blank suggested questions are removed;
|
|
14
|
-
- approval and question policies are fixed to safe blocking behavior;
|
|
15
|
-
- reasoning and tool details are opt-in through `ui.showReasoning` and
|
|
16
|
-
`ui.showToolActivity`; enable them only where those contents are appropriate
|
|
17
|
-
for the QA audience;
|
|
18
|
-
- accounts default to disabled; when enabled, `sessionTtlDays` is an integer
|
|
19
|
-
from 1 through 365, and other users' chats stay hidden unless an admin view
|
|
20
|
-
explicitly enables `accounts.showOtherUsersChats`;
|
|
21
|
-
- lockdown defaults to enabled and requires a non-empty permission preset;
|
|
22
|
-
- the preset must resolve on the Host to exactly `read-only` + `never`;
|
|
23
|
-
- permission, slash-command, settings, rename, delete and arbitrary-open
|
|
24
|
-
capability flags cannot be enabled;
|
|
25
|
-
- `showReset` requires the independent `allowSessionReset` opt-in;
|
|
26
|
-
- the tool policy is an allow-list; unknown configured tool names fail closed.
|
|
27
|
-
A name only resolves while its tool is actually mounted: MCP tools
|
|
28
|
-
(`mcp__<server>__*`) exist only while their server is reachable, so keep
|
|
29
|
-
them out of the allow list on hosts that cannot reach the server. Tools
|
|
30
|
-
mounted by an agent preset live in that preset's ancestor scope; the Host
|
|
31
|
-
validates and restricts the complete agent-scoped view, not just globals.
|
|
32
|
-
|
|
33
|
-
## Pinning chats to a directory
|
|
34
|
-
|
|
35
|
-
By default a QA chat is created against the Host working directory. Two
|
|
36
|
-
mutually exclusive pins anchor every new chat (and its subagents) to the
|
|
37
|
-
deployment's own directory:
|
|
38
|
-
|
|
39
|
-
- `session.cwd: "D:/qa-docs"` - an absolute directory used directly as the
|
|
40
|
-
session cwd; no registration needed. Attestation compares the session's
|
|
41
|
-
recorded cwd case- and separator-insensitively on Windows.
|
|
42
|
-
- `session.workspaceId: "<uuid>"` - a registered DSH workspace (created in
|
|
43
|
-
the web UI or via the workspace API; the id is a generated uuid, not the
|
|
44
|
-
path). Sessions attach to the workspace, so they group under it in the
|
|
45
|
-
host UI. When the id does not resolve, attestation refuses with
|
|
46
|
-
`reason: workspace-unavailable` instead of an opaque mismatch.
|
|
47
|
-
|
|
48
|
-
With `lockdown.enforceFixedWorkspace: true` (the default) a chat created
|
|
49
|
-
anywhere else refuses to attest, so the QA audience can never pull the
|
|
50
|
-
assistant out of the pinned directory.
|
|
51
|
-
|
|
52
|
-
Browser persistence stores only the DSH session id under
|
|
53
|
-
`<storageKey>:v1:<route>:session`, plus — when `ui.showSessionList` is
|
|
54
|
-
enabled — a per-browser chat index under `<storageKey>:v1:<route>:chats`
|
|
55
|
-
(session ids only, capped at 50). Transcript content, credentials and tool
|
|
56
|
-
results remain in the Host-owned DSH Session and are never copied to browser
|
|
57
|
-
storage.
|
|
58
|
-
|
|
59
|
-
When either work-detail flag is enabled, the QA transcript groups reasoning,
|
|
60
|
-
intermediate assistant progress, and tool rows by DSH turn. Running work is
|
|
61
|
-
expanded. Completed work is collapsed behind a duration summary and can be
|
|
62
|
-
reopened; each tool row can separately reveal its formatted input and output.
|
|
63
|
-
These controls change presentation only and never widen the Host allow-list.
|
|
64
|
-
|
|
65
|
-
The browser cannot override lockdown settings. Before binding and immediately
|
|
66
|
-
before every prompt, it requests a Host attestation for the selected session.
|
|
67
|
-
Any unproved agent preset, workspace, model, permission bundle or tool policy
|
|
68
|
-
disables Send with the generic message `Настройки помощника недоступны.`
|
|
69
|
-
Detailed mismatch facts are written only to Host logs; the
|
|
70
|
-
browser console additionally prints one line with a stable coarse reason code
|
|
71
|
-
(`reason: unknown-tools`, `workspace-unavailable`, `composition-mismatch`, `permission-preset`,
|
|
72
|
-
`adoption-refused`, `proof-mismatch` or `attestation-failed`) plus an operator
|
|
73
|
-
hint, so a refused surface can be diagnosed without Host log access.
|
|
74
|
-
|
|
75
|
-
The built-in branding, controls, status messages and accessibility labels are
|
|
76
|
-
Russian. The default quick questions are rendered directly above the composer
|
|
77
|
-
only while the current chat is empty. Set `suggestedQuestions: []` to hide
|
|
78
|
-
them, or provide a deployment-specific list to replace them.
|
|
79
|
-
|
|
80
|
-
When adding tools, update the deployment's reviewed capability inventory as
|
|
81
|
-
part of the same change. The package's
|
|
82
|
-
[default inventory](../capability-policy.json) is intentionally empty, matching
|
|
83
|
-
the default `toolPolicy.allow`.
|
|
84
|
-
|
|
85
|
-
## Structured sources
|
|
86
|
-
|
|
87
|
-
The `sources` block controls provenance independently from Activity rendering.
|
|
88
|
-
The defaults collect parent and subagent results, persist a `qa/sources` event,
|
|
89
|
-
hide discovery-only candidates, group visible evidence by kind, and promote at
|
|
90
|
-
most five substantive `web_search` results when no fetch occurs. URL tracking
|
|
91
|
-
parameters and overlapping file ranges are normalized during deduplication.
|
|
92
|
-
|
|
93
|
-
`sources.filePreview` is a narrow read capability, not a filesystem browser.
|
|
94
|
-
The Host serves only a path already present in the attested session's canonical
|
|
95
|
-
evidence bundle, resolves symlinks with `realpath`, rejects root escape, and
|
|
96
|
-
applies `maxBytes` plus `maxMarkdownRenderBytes`. Markdown uses the client's
|
|
97
|
-
HTML-free renderer; relative links and executable raw HTML are not activated.
|
|
98
|
-
|
|
99
|
-
Set `sources.display.showOriginBadges: true` while auditing inheritance. Local
|
|
100
|
-
subagent sources carry run/session origins automatically. An opaque provider
|
|
101
|
-
can call the internal `qa_report_sources` tool; if it does not, and
|
|
102
|
-
`markIncompleteOpaqueRuns` remains enabled, the bundle is explicitly marked
|
|
103
|
-
incomplete.
|
|
104
|
-
|
|
105
|
-
See [Structured sources migration](SOURCES-MIGRATION.md) before removing an
|
|
106
|
-
older prompt-authored bibliography convention.
|
|
107
|
-
|
|
108
|
-
## Accounts and the QA gate
|
|
109
|
-
|
|
110
|
-
`accounts.enabled: true` mounts the login/registration gate in front of the
|
|
111
|
-
QA surface and turns on server-side session ownership:
|
|
112
|
-
|
|
113
|
-
- The store lives at `$DSH_HOME/qa-accounts.json` (created on first use). It
|
|
114
|
-
holds scrypt password hashes, a persisted HMAC secret for the account
|
|
115
|
-
tokens, and the session ownership map. The path is not configurable and no
|
|
116
|
-
secret ever reaches `describe()`.
|
|
117
|
-
- `accounts.allowRegistration` (default true) controls self-service signup
|
|
118
|
-
in the gate; the first account ever registered becomes `admin`.
|
|
119
|
-
- `accounts.sessionTtlDays` (default 30) is the account token lifetime. The
|
|
120
|
-
token is an HMAC-signed value the browser keeps in `localStorage`; logout
|
|
121
|
-
or expiry returns the browser to the gate, and the Host re-derives the
|
|
122
|
-
identity on every attestation.
|
|
123
|
-
- `accounts.showOtherUsersChats` (default false) lets admins load and display
|
|
124
|
-
chats owned by other QA accounts. It has no effect for ordinary users.
|
|
125
|
-
- Session ownership is first come, first served: attesting or bulk-claiming
|
|
126
|
-
an unowned session binds it to the caller's account (this is how existing
|
|
127
|
-
per-browser chats migrate on the first login). Sessions owned by another
|
|
128
|
-
user are refused with the coarse `session-owned-elsewhere` reason and
|
|
129
|
-
hidden from the sidebar; admins are not refused.
|
|
130
|
-
- Manage accounts with the bundled CLI (no hand-editing of the JSON):
|
|
131
|
-
|
|
132
|
-
```sh
|
|
133
|
-
qa-accounts list
|
|
134
|
-
qa-accounts add user@example.com --password-stdin --role user
|
|
135
|
-
qa-accounts set-role user@example.com admin
|
|
136
|
-
qa-accounts disable user@example.com # blocks logins, revokes live tokens
|
|
137
|
-
qa-accounts revoke user@example.com # invalidates every issued token
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
`disable` bumps the account's token version, so all outstanding tokens die
|
|
141
|
-
server-side; `enable` requires a fresh sign-in. After a secret rotation
|
|
142
|
-
suspicion, `revoke` is the single-step response.
|
|
143
|
-
|
|
144
|
-
- Honest boundary: accounts identify QA users and gate the QA surface and
|
|
145
|
-
its remotes. They do not fence the harness: every QA user also holds the
|
|
146
|
-
host launch-token cookie, with which the full root UI stays technically
|
|
147
|
-
reachable. Keep the network scoping advice from the deployment kit.
|
|
148
|
-
|
|
149
|
-
## Entry redirect
|
|
150
|
-
|
|
151
|
-
`entry.redirectNonLoopback: true` (default) injects one script into the
|
|
152
|
-
served root `index.html`: browsers whose URL host is not loopback
|
|
153
|
-
(`localhost`, `127.0.0.1`, `::1`, `*.localhost`) continue into the QA route,
|
|
154
|
-
so LAN visitors never see the full harness root. The check is client-side on
|
|
155
|
-
`location.hostname` on purpose - behind a local reverse proxy every request
|
|
156
|
-
looks loopback to the server. The navigation hand-off `/?__dsh_qa_route=…`
|
|
157
|
-
is never redirected (that would loop), `/?ui=admin` bypasses the redirect
|
|
158
|
-
and is remembered for the browser, and `/?ui=qa` clears the bypass.
|
|
159
|
-
|
|
160
|
-
`entry.cookieBootstrap: true` (default) additionally lets the `/qa` route
|
|
161
|
-
itself bootstrap the host cookie: a browser without any `dsh-auth-` cookie is
|
|
162
|
-
sent to the one-time `/?token=…` exchange (relative redirect, token resolved
|
|
163
|
-
from the host connection service) before the marker hand-off. This makes the
|
|
164
|
-
transparent entry work without the deploy proxy; with the proxy in front,
|
|
165
|
-
either side may perform the exchange and the other becomes a no-op.
|
|
166
|
-
|
|
167
|
-
## Configuration channel over the LAN
|
|
168
|
-
|
|
169
|
-
The browser normally reads the effective configuration from the Host-owned
|
|
170
|
-
`qa-surface` settings namespace. DSH pins settings RPCs to loopback, so a
|
|
171
|
-
browser served over the LAN always sees that namespace as unavailable. In
|
|
172
|
-
that case the client calls the plugin's `qaSurface/describe` Host Remote and
|
|
173
|
-
uses the returned effective configuration; a rejected call falls back to the
|
|
174
|
-
client defaults with the same `unavailable` status as before. Only one
|
|
175
|
-
describe request runs per page load, and a settled answer survives scope
|
|
176
|
-
updates — the namespace (when readable) stays the authority and keeps
|
|
177
|
-
delivering live changes.
|
|
178
|
-
|
|
179
|
-
Host-side enforcement never depended on the browser's read path: `secureSession`
|
|
180
|
-
attestation re-derives everything from the Host-owned configuration on every
|
|
181
|
-
bind and every prompt.
|
|
182
|
-
|
|
183
|
-
## Skill catalog scope
|
|
184
|
-
|
|
185
|
-
The deployment's agent preset controls which skills the QA assistant sees.
|
|
186
|
-
The shipped `qa-research` preset mounts the skill filesystem with
|
|
187
|
-
`includeDefaultRoots: false`: project-root skills (for example the harness
|
|
188
|
-
checkout's own `.dsh/skills`) and user-home skills stay out of the catalog,
|
|
189
|
-
and skills enter only through plugin providers or an explicit
|
|
190
|
-
`customSkillDirs` list in the preset. Keep the QA catalog to exactly the
|
|
191
|
-
skills the audience is meant to use.
|
|
192
|
-
|
|
193
|
-
## Deleting chats
|
|
194
|
-
|
|
195
|
-
The sidebar delete control removes a chat from this browser's index only;
|
|
196
|
-
the Host session stays on disk. Deleting the chat that is currently open
|
|
197
|
-
continues in a fresh attested session when `lockdown.allowSessionReset` is
|
|
198
|
-
enabled. There is no Host session-deletion API in DSH 0.1.x for the plugin
|
|
199
|
-
to call.
|
package/docs/EMBEDDING.md
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# Embedding and reverse proxies
|
|
2
|
-
|
|
3
|
-
Serve `/qa` through the normal DSH origin so it shares the established browser
|
|
4
|
-
connection, authentication and trust policy. The plugin's narrow navigation
|
|
5
|
-
route redirects GET/HEAD requests through DSH's canonical `/` document; the
|
|
6
|
-
client immediately restores `/qa` before mounting. Reverse proxies must pass
|
|
7
|
-
the configured QA path to DSH instead of replacing it with a separately hosted
|
|
8
|
-
page.
|
|
9
|
-
|
|
10
|
-
Iframe embedding is disabled or allowed by deployment headers, not by this
|
|
11
|
-
plugin. Configure an explicit CSP `frame-ancestors` allowlist at a trusted
|
|
12
|
-
reverse proxy and keep the DSH API same-origin. Do not enable wildcard CORS or
|
|
13
|
-
expose settings/session APIs without the deployment's authentication layer.
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# Structured sources migration
|
|
2
|
-
|
|
3
|
-
`dsh-qa-surface` now treats `QaTurnSources` as the only canonical provenance
|
|
4
|
-
model. Assistant prose is never parsed for a `Sources` or `Источники` section,
|
|
5
|
-
and the default `sources.legacy.parseAssistantSourcesBlock` remains `false`.
|
|
6
|
-
|
|
7
|
-
## Deployment steps
|
|
8
|
-
|
|
9
|
-
1. Keep the QA agent's existing read-only retrieval tools in
|
|
10
|
-
`lockdown.toolPolicy.allow`.
|
|
11
|
-
2. Leave `sources.collect.persistTurnEvent: true` so completed turns append a
|
|
12
|
-
replayable `qa/sources` snapshot.
|
|
13
|
-
3. Remove prompt instructions that ask the assistant to manually write a
|
|
14
|
-
bibliography. The plugin contributes its own scoped guidance after QA
|
|
15
|
-
attestation.
|
|
16
|
-
4. For an opaque subagent provider, expose the inherited
|
|
17
|
-
`qa_report_sources` tool and ask the provider to call it before completion.
|
|
18
|
-
Local DSH subagents require no reporting prompt.
|
|
19
|
-
5. Start with `sources.display.showDiscovered: false`; enable it only for
|
|
20
|
-
provenance debugging.
|
|
21
|
-
|
|
22
|
-
Historical sessions without `qa/sources` remain readable: the Host reconstructs
|
|
23
|
-
bundles from durable structured tool metadata when available. Historical
|
|
24
|
-
assistant-authored source prose remains ordinary answer text and is not trusted
|
|
25
|
-
as provenance.
|
|
26
|
-
|
|
27
|
-
The `qa/sources` event is a required plugin-owned session event. Keep
|
|
28
|
-
`dsh-qa-surface` loaded in any Host that restores sessions containing it.
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
# Sources / provenance implementation audit
|
|
2
|
-
|
|
3
|
-
Date: 2026-09-11
|
|
4
|
-
Pinned DSH development API: `0.1.5-rc.2`
|
|
5
|
-
|
|
6
|
-
This note records the Phase 0 findings for
|
|
7
|
-
`SPEC-dsh-qa-surface-sources-provenance.md`. It is intentionally tied to the
|
|
8
|
-
versions in this repository rather than to DSH `master`.
|
|
9
|
-
|
|
10
|
-
## Existing QA data path
|
|
11
|
-
|
|
12
|
-
- `QaSessionController` binds the client `UiConversation` snapshot.
|
|
13
|
-
- `QaTranscriptAdapter` reads the `chat.legacy` projection.
|
|
14
|
-
- The former Sources drawer inferred one row from selected tool names and raw
|
|
15
|
-
arguments. It counted a search query as a source and did not retain turn,
|
|
16
|
-
line-range, evidence, or origin data.
|
|
17
|
-
- `ToolResultNode.meta` is available in the client projection. DSH documents it
|
|
18
|
-
as the tool-private, JSON-safe result presentation payload persisted on the
|
|
19
|
-
corresponding `tool/result` event.
|
|
20
|
-
|
|
21
|
-
## Structured metadata available in the pinned version
|
|
22
|
-
|
|
23
|
-
`@deepseek-ai/dsh-tools` exports the provider-neutral result shapes needed by
|
|
24
|
-
the first implementation slice:
|
|
25
|
-
|
|
26
|
-
- `ReadResultView`: `path`, numbered `lines`, `offset`, `totalLines`, `lang`;
|
|
27
|
-
- `SearchResultView`: grouped grep matches or path discovery;
|
|
28
|
-
- `WebSearchResultView`: structured `sources[]` and truncation state;
|
|
29
|
-
- `WebFetchResultView`: final redirect URL, status, and truncation state;
|
|
30
|
-
- generic call `FileLocation` values.
|
|
31
|
-
|
|
32
|
-
The QA extractor layer therefore consumes `ToolResultNode.meta` first and uses
|
|
33
|
-
tool arguments only as a compatibility fallback for direct read/fetch calls.
|
|
34
|
-
It does not parse assistant prose.
|
|
35
|
-
|
|
36
|
-
## Replay and persistence
|
|
37
|
-
|
|
38
|
-
The completed implementation rebuilds collectors from persisted
|
|
39
|
-
`tool/call`/`tool/result.meta` events and writes one materialized `qa/sources`
|
|
40
|
-
snapshot at `agent/turn-stopping`. The type is declaration-merged into
|
|
41
|
-
`SessionEventMap`.
|
|
42
|
-
|
|
43
|
-
The pinned DSH exports its live `KNOWN_SESSION_EVENT_TYPES` set. As already
|
|
44
|
-
demonstrated by the repository's `dsh-session-scope` plugin, registering the
|
|
45
|
-
plugin event there is the supported compatibility mechanism when
|
|
46
|
-
`Session.append` cannot mark an extension event ignorable. Registration is
|
|
47
|
-
effect-scoped and removed on plugin disposal. A Host that owns sessions with
|
|
48
|
-
these events must load `dsh-qa-surface`, just as it must load any plugin that
|
|
49
|
-
owns a required durable event vocabulary.
|
|
50
|
-
|
|
51
|
-
## Subagent lifecycle
|
|
52
|
-
|
|
53
|
-
The pinned `@deepseek-ai/dsh-subagent` does expose process-local
|
|
54
|
-
`subagent/start` and `subagent/end` events. Both carry `runId`, `provider`, child
|
|
55
|
-
session `id`, and `local`; the terminal payload additionally carries
|
|
56
|
-
`stopReason` and optional `lastAssistantMessage`. Scoped dispatch is keyed by
|
|
57
|
-
the delegating parent.
|
|
58
|
-
|
|
59
|
-
The Host now maintains that lineage registry, uses the child header or causal
|
|
60
|
-
`AgentRegistry.currentInitiator()` attribution, bubbles nested local-child
|
|
61
|
-
sources to the original root turn, and retains run/session origin fields.
|
|
62
|
-
Opaque providers can call `qa_report_sources`; an unreported opaque run sets
|
|
63
|
-
`complete=false` with an `incompleteOrigins` entry.
|
|
64
|
-
|
|
65
|
-
## File preview reuse
|
|
66
|
-
|
|
67
|
-
The new `readSourceFile` Remote is limited to evidence paths in canonical
|
|
68
|
-
bundles, re-attests the session, resolves both root and target with `realpath`,
|
|
69
|
-
rejects traversal/symlink escape, caps bytes, and exposes no write/list API.
|
|
70
|
-
|
|
71
|
-
## Implemented scope
|
|
72
|
-
|
|
73
|
-
- canonical source and turn-bundle types;
|
|
74
|
-
- URL/path/range normalization;
|
|
75
|
-
- extractor registry with read, grep discovery, web search, and web fetch;
|
|
76
|
-
- evidence promotion, deduplication, ranking, and a default display threshold;
|
|
77
|
-
- bounded structured web-search promotion (five results by default);
|
|
78
|
-
- Host per-turn collectors and materialized/replayable `qa/sources` events;
|
|
79
|
-
- local and nested subagent inheritance plus opaque-provider reporting;
|
|
80
|
-
- structured Jira, Confluence, knowledge and reported-source adapters;
|
|
81
|
-
- one canonical snapshot used by grouped drawer and answer footer;
|
|
82
|
-
- source details, origin/partial badges and completeness state;
|
|
83
|
-
- safe local-file preview, rendered/raw Markdown, range chips and exact raw
|
|
84
|
-
line highlighting;
|
|
85
|
-
- configuration and tests for the feature surface.
|
|
86
|
-
|
|
87
|
-
Optional inline citation rendering (SPEC Phase 5) remains intentionally out of
|
|
88
|
-
the MVP, as specified.
|
|
@@ -1,219 +0,0 @@
|
|
|
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.
|