@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.
Files changed (192) hide show
  1. package/CHANGELOG.md +151 -0
  2. package/README.md +239 -16
  3. package/SPEC-dsh-qa-surface.md +600 -96
  4. package/capability-policy.json +5 -0
  5. package/compatibility.json +18 -4
  6. package/cordis.patch.yml +11 -0
  7. package/docs/ARCHITECTURE.md +50 -8
  8. package/docs/COMPATIBILITY.md +5 -4
  9. package/docs/CONFIGURATION.md +184 -3
  10. package/docs/SOURCES-MIGRATION.md +28 -0
  11. package/docs/SOURCES-PROVENANCE-AUDIT.md +88 -0
  12. package/docs/SPEC-dsh-qa-surface-accounts.md +219 -0
  13. package/docs/SPEC-dsh-qa-surface-sources-provenance.md +1692 -0
  14. package/lib/accounts/index.js +2 -0
  15. package/lib/accounts/index.js.map +1 -0
  16. package/lib/accounts/store.js +478 -0
  17. package/lib/accounts/store.js.map +1 -0
  18. package/lib/attestation.js +10 -0
  19. package/lib/attestation.js.map +1 -0
  20. package/lib/cli.js +143 -0
  21. package/lib/cli.js.map +1 -0
  22. package/lib/client/QaAccountsController.js +329 -0
  23. package/lib/client/QaAccountsController.js.map +1 -0
  24. package/lib/client/QaConfigController.js +111 -4
  25. package/lib/client/QaConfigController.js.map +1 -1
  26. package/lib/client/QaSessionController.js +621 -163
  27. package/lib/client/QaSessionController.js.map +1 -1
  28. package/lib/client/QaSurface.js +303 -24
  29. package/lib/client/QaSurface.js.map +1 -1
  30. package/lib/client/QaTranscriptAdapter.js +324 -49
  31. package/lib/client/QaTranscriptAdapter.js.map +1 -1
  32. package/lib/client/attestation.js +66 -0
  33. package/lib/client/attestation.js.map +1 -0
  34. package/lib/client/chat-index.js +86 -0
  35. package/lib/client/chat-index.js.map +1 -0
  36. package/lib/client/components/Markdown.js +124 -13
  37. package/lib/client/components/Markdown.js.map +1 -1
  38. package/lib/client/components/QaAgentsDrawer.js +40 -0
  39. package/lib/client/components/QaAgentsDrawer.js.map +1 -0
  40. package/lib/client/components/QaAuthGate.js +40 -0
  41. package/lib/client/components/QaAuthGate.js.map +1 -0
  42. package/lib/client/components/QaChangelog.js +112 -0
  43. package/lib/client/components/QaChangelog.js.map +1 -0
  44. package/lib/client/components/QaComposer.js +132 -15
  45. package/lib/client/components/QaComposer.js.map +1 -1
  46. package/lib/client/components/QaMessage.js +214 -10
  47. package/lib/client/components/QaMessage.js.map +1 -1
  48. package/lib/client/components/QaSidebar.js +167 -0
  49. package/lib/client/components/QaSidebar.js.map +1 -0
  50. package/lib/client/components/QaSourcesDrawer.js +127 -0
  51. package/lib/client/components/QaSourcesDrawer.js.map +1 -0
  52. package/lib/client/components/QaTurnRail.js +281 -0
  53. package/lib/client/components/QaTurnRail.js.map +1 -0
  54. package/lib/client/components/QaWelcomeNotice.js +125 -0
  55. package/lib/client/components/QaWelcomeNotice.js.map +1 -0
  56. package/lib/client/components/QaWidthHandle.js +142 -0
  57. package/lib/client/components/QaWidthHandle.js.map +1 -0
  58. package/lib/client/components/QaWorkGroup.js +135 -0
  59. package/lib/client/components/QaWorkGroup.js.map +1 -0
  60. package/lib/client/components/VariantSwitcher.js +29 -0
  61. package/lib/client/components/VariantSwitcher.js.map +1 -0
  62. package/lib/client/components/format.js +48 -0
  63. package/lib/client/components/format.js.map +1 -0
  64. package/lib/client/components/source-refs.js +78 -0
  65. package/lib/client/components/source-refs.js.map +1 -0
  66. package/lib/client/create-session.js +51 -0
  67. package/lib/client/create-session.js.map +1 -0
  68. package/lib/client/index.js +166 -35
  69. package/lib/client/index.js.map +1 -1
  70. package/lib/client/project-session-state.js +78 -0
  71. package/lib/client/project-session-state.js.map +1 -0
  72. package/lib/client/session-admission.js +38 -0
  73. package/lib/client/session-admission.js.map +1 -0
  74. package/lib/client/session-assets.js +52 -0
  75. package/lib/client/session-assets.js.map +1 -0
  76. package/lib/client/session-sources.js +61 -0
  77. package/lib/client/session-sources.js.map +1 -0
  78. package/lib/client/settlement.js +24 -0
  79. package/lib/client/settlement.js.map +1 -0
  80. package/lib/client/styles.js +450 -43
  81. package/lib/client/styles.js.map +1 -1
  82. package/lib/client/tool-presentation.js +102 -0
  83. package/lib/client/tool-presentation.js.map +1 -0
  84. package/lib/client/turn-sources.js +98 -0
  85. package/lib/client/turn-sources.js.map +1 -0
  86. package/lib/client/types.js +15 -0
  87. package/lib/client/types.js.map +1 -0
  88. package/lib/client/use-session-ui-state.js +68 -0
  89. package/lib/client/use-session-ui-state.js.map +1 -0
  90. package/lib/client/use-transcript-view.js +44 -0
  91. package/lib/client/use-transcript-view.js.map +1 -0
  92. package/lib/client/wait-for.js +49 -0
  93. package/lib/client/wait-for.js.map +1 -0
  94. package/lib/client.js +12551 -918
  95. package/lib/client.js.map +1 -1
  96. package/lib/config.js +226 -58
  97. package/lib/config.js.map +1 -1
  98. package/lib/entry-redirect.js +44 -0
  99. package/lib/entry-redirect.js.map +1 -0
  100. package/lib/host-route.js +25 -1
  101. package/lib/host-route.js.map +1 -1
  102. package/lib/index.js +354 -72
  103. package/lib/index.js.map +1 -1
  104. package/lib/launch-token.js +42 -0
  105. package/lib/launch-token.js.map +1 -0
  106. package/lib/lockdown-policy.js +20 -0
  107. package/lib/lockdown-policy.js.map +1 -0
  108. package/lib/provenance/collector.js +40 -0
  109. package/lib/provenance/collector.js.map +1 -0
  110. package/lib/provenance/connectors.js +147 -0
  111. package/lib/provenance/connectors.js.map +1 -0
  112. package/lib/provenance/dedupe.js +89 -0
  113. package/lib/provenance/dedupe.js.map +1 -0
  114. package/lib/provenance/extractors.js +340 -0
  115. package/lib/provenance/extractors.js.map +1 -0
  116. package/lib/provenance/file-preview.js +39 -0
  117. package/lib/provenance/file-preview.js.map +1 -0
  118. package/lib/provenance/host-store.js +387 -0
  119. package/lib/provenance/host-store.js.map +1 -0
  120. package/lib/provenance/index.js +9 -0
  121. package/lib/provenance/index.js.map +1 -0
  122. package/lib/provenance/normalize.js +128 -0
  123. package/lib/provenance/normalize.js.map +1 -0
  124. package/lib/provenance/reported.js +53 -0
  125. package/lib/provenance/reported.js.map +1 -0
  126. package/lib/provenance/types.js +2 -0
  127. package/lib/provenance/types.js.map +1 -0
  128. package/lib/resolve-config.js +283 -10
  129. package/lib/resolve-config.js.map +1 -1
  130. package/lib/secure-session.js +212 -0
  131. package/lib/secure-session.js.map +1 -0
  132. package/lib/typert.host.d.ts +3 -0
  133. package/lib/typert.host.js +784 -0
  134. package/lib/typert.remote-client.d.ts +40 -0
  135. package/lib/typert.remote-client.js +597 -0
  136. package/lib/types/accounts/index.d.ts +2 -0
  137. package/lib/types/accounts/store.d.ts +121 -0
  138. package/lib/types/attestation.d.ts +17 -0
  139. package/lib/types/cli.d.ts +9 -0
  140. package/lib/types/client/QaAccountsController.d.ts +95 -0
  141. package/lib/types/client/QaConfigController.d.ts +35 -3
  142. package/lib/types/client/QaSessionController.d.ts +146 -20
  143. package/lib/types/client/QaSurface.d.ts +11 -5
  144. package/lib/types/client/QaTranscriptAdapter.d.ts +13 -3
  145. package/lib/types/client/attestation.d.ts +25 -0
  146. package/lib/types/client/chat-index.d.ts +28 -0
  147. package/lib/types/client/components/Markdown.d.ts +17 -3
  148. package/lib/types/client/components/QaAgentsDrawer.d.ts +25 -0
  149. package/lib/types/client/components/QaAuthGate.d.ts +15 -0
  150. package/lib/types/client/components/QaChangelog.d.ts +21 -0
  151. package/lib/types/client/components/QaComposer.d.ts +12 -2
  152. package/lib/types/client/components/QaMessage.d.ts +22 -2
  153. package/lib/types/client/components/QaSidebar.d.ts +69 -0
  154. package/lib/types/client/components/QaSourcesDrawer.d.ts +20 -0
  155. package/lib/types/client/components/QaTurnRail.d.ts +59 -0
  156. package/lib/types/client/components/QaWelcomeNotice.d.ts +27 -0
  157. package/lib/types/client/components/QaWidthHandle.d.ts +36 -0
  158. package/lib/types/client/components/QaWorkGroup.d.ts +19 -0
  159. package/lib/types/client/components/VariantSwitcher.d.ts +19 -0
  160. package/lib/types/client/components/format.d.ts +9 -0
  161. package/lib/types/client/components/source-refs.d.ts +16 -0
  162. package/lib/types/client/create-session.d.ts +16 -0
  163. package/lib/types/client/index.d.ts +2 -5
  164. package/lib/types/client/project-session-state.d.ts +30 -0
  165. package/lib/types/client/session-admission.d.ts +23 -0
  166. package/lib/types/client/session-assets.d.ts +20 -0
  167. package/lib/types/client/session-sources.d.ts +28 -0
  168. package/lib/types/client/settlement.d.ts +6 -0
  169. package/lib/types/client/tool-presentation.d.ts +6 -0
  170. package/lib/types/client/turn-sources.d.ts +14 -0
  171. package/lib/types/client/types.d.ts +93 -0
  172. package/lib/types/client/use-session-ui-state.d.ts +35 -0
  173. package/lib/types/client/use-transcript-view.d.ts +20 -0
  174. package/lib/types/client/wait-for.d.ts +11 -0
  175. package/lib/types/entry-redirect.d.ts +20 -0
  176. package/lib/types/host-route.d.ts +14 -1
  177. package/lib/types/index.d.ts +67 -7
  178. package/lib/types/launch-token.d.ts +21 -0
  179. package/lib/types/lockdown-policy.d.ts +18 -0
  180. package/lib/types/provenance/collector.d.ts +26 -0
  181. package/lib/types/provenance/connectors.d.ts +11 -0
  182. package/lib/types/provenance/dedupe.d.ts +6 -0
  183. package/lib/types/provenance/extractors.d.ts +14 -0
  184. package/lib/types/provenance/file-preview.d.ts +10 -0
  185. package/lib/types/provenance/host-store.d.ts +54 -0
  186. package/lib/types/provenance/index.d.ts +10 -0
  187. package/lib/types/provenance/normalize.d.ts +13 -0
  188. package/lib/types/provenance/reported.d.ts +5 -0
  189. package/lib/types/provenance/types.d.ts +78 -0
  190. package/lib/types/secure-session.d.ts +31 -0
  191. package/lib/types/types.d.ts +304 -1
  192. package/package.json +70 -27
@@ -0,0 +1,5 @@
1
+ {
2
+ "version": 1,
3
+ "description": "Reviewed capabilities in the shipped default QA tool allow-list.",
4
+ "tools": []
5
+ }
@@ -1,14 +1,28 @@
1
1
  {
2
2
  "deepseekHarness": {
3
3
  "channel": "next",
4
- "range": ">=0.1.1-rc.2 <0.2.0",
5
- "testedReleases": ["0.1.1-rc.2"],
4
+ "range": ">=0.1.5-rc.2 <0.2.0",
5
+ "testedReleases": ["0.1.5-rc.2"],
6
6
  "requiredClientFeatures": [
7
7
  "shell.overlay",
8
- "connection.api.sessions",
8
+ "remote.session.selectModel",
9
+ "remote.agentPresets.select",
10
+ "uiConversation.binding",
9
11
  "sessions.binding",
10
12
  "settingsScope",
11
- "webServer.register"
13
+ "settings.onboarding list-slot shadowing",
14
+ "webServer.register",
15
+ "typert.remote",
16
+ "agentPresets.composedPreset",
17
+ "permissionPresets.set",
18
+ "tools.restrict",
19
+ "tools.guard",
20
+ "tools.register",
21
+ "session.event-extension",
22
+ "agent.turn-stopping",
23
+ "agent.currentInitiator",
24
+ "systemPrompt.section",
25
+ "workspaceRegistry.get"
12
26
  ]
13
27
  },
14
28
  "node": "^22.19.0 || >=24.0.0"
package/cordis.patch.yml CHANGED
@@ -7,3 +7,14 @@
7
7
  route:
8
8
  path: /qa
9
9
  matchChildren: true
10
+ ui:
11
+ showReset: false
12
+ lockdown:
13
+ enabled: true
14
+ sandboxMode: read-only
15
+ approvalPolicy: never
16
+ permissionPreset: qa-read-only
17
+ allowSessionReset: false
18
+ toolPolicy:
19
+ mode: allow-list
20
+ allow: []
@@ -2,22 +2,64 @@
2
2
 
3
3
  The Host half validates the Loader configuration, registers the `qa-surface`
4
4
  settings namespace and owns one narrow GET/HEAD route for the configured QA
5
- path. Published DSH `0.1.1-rc.2` returns 404 for unknown frontend paths, so the
5
+ path. Published DSH releases return 404 for unknown frontend paths, so the
6
6
  route redirects navigation through the canonical `/` index with an encoded
7
- same-origin path marker. It does not serve HTML, create another server, replace
8
- the frontend fallback, or touch API/plugin routes. The browser removes the
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
9
13
  marker and restores the requested path with `history.replaceState` before
10
- route matching. The Host creates no sessions.
14
+ route matching. The Host creates no sessions itself.
11
15
 
12
16
  The browser half registers `QaSurface` in the root-scoped `shell.overlay` list.
13
17
  `QaRouteController` keeps that entry dormant outside the configured path and
14
18
  reversibly observes `pushState`, `replaceState` and `popstate`.
15
19
 
16
- `QaSessionController` is the only DSH session-coupled module. It uses
20
+ `QaSessionController` is the only browser-side DSH session-coupled module. It uses
17
21
  `connection.api.sessions.create/selectModel` for creation policy and the public
18
- `SessionFace` for prompt, cancel and snapshots. `QaTranscriptAdapter` projects
19
- those snapshots to plugin-local messages, excluding reasoning, tool arguments,
20
- tool results, system context and raw failures.
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.
21
63
 
22
64
  The overlay locks document scrolling and contains keyboard focus while active.
23
65
  It does not locate AppFrame through generated CSS classes and does not dispose
@@ -1,12 +1,13 @@
1
1
  # Compatibility
2
2
 
3
- | Plugin version | Tested DSH release | Required public contracts |
4
- | -------------- | ------------------ | ----------------------------------------------------------------------------------------------------- |
5
- | 0.1.x | 0.1.1-rc.2 | `shell.overlay`, `settingsScope`, `connection.api.sessions`, `sessions.binding`, `webServer.register` |
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
6
 
7
7
  DSH is developer-preview software. Compatibility-sensitive calls are isolated
8
8
  in `QaSessionController`, `QaTranscriptAdapter`, `QaConfigController` and
9
- `QaRouteController`. The Host navigation adapter also works around this tested
9
+ `QaRouteController`; Host lockdown coupling is isolated in
10
+ `QaSurface.secureSession`. The Host navigation adapter also works around this tested
10
11
  release's exact-index static serving: `/qa` is redirected through `/`, then the
11
12
  client restores the original pathname. Missing settings fall back to default
12
13
  browser config; missing required client services prevent the browser plugin
@@ -7,12 +7,193 @@ rules are:
7
7
  `/`, `/api` or `/plugins`;
8
8
  - `fixed` requires `fixedSessionId`;
9
9
  - `provider` and `model` are either both absent or both present;
10
- - `maxContentWidth` is an integer from 480 through 1600;
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;
11
13
  - duplicate/blank suggested questions are removed;
12
14
  - approval and question policies are fixed to safe blocking behavior;
13
- - reasoning display cannot be enabled in the 0.1.x MVP.
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.
14
51
 
15
52
  Browser persistence stores only the DSH session id under
16
- `<storageKey>:v1:<route>:session`. Transcript content, credentials and tool
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
17
56
  results remain in the Host-owned DSH Session and are never copied to browser
18
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.
@@ -0,0 +1,28 @@
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.
@@ -0,0 +1,88 @@
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.