feat(compat): drop dsh 0.1.5-rc.1, floor the support matrix at 0.1.7-rc.2
The supported set is now the two V4-generation lines (0.1.7-rc.2+ and 0.2.0-rc.2+): the manifest's dshReleases, the peer floors (>=0.1.7-rc.2, cordis ^4.0.4 — both lines vendor 4.0.4), the devDependency pins, the baseline matrix, the release workflow's tag fetch, and the compatibility docs. With every supported line on session format V4, the V3-only paths leave: the fold reads the tool result's first-class message alone (source.callId pairing, message.isError — the admission validation refuses any row whose data.error outlives it); the settings.register host face module is gone (the Config-form entry schema serves the preferences); the client drops the settingsScope/settings.plugin.item card seat, the retired sessions.open jump fallback, the legacy icon spellings, and the settings-dialog leg of the settings jump; the history page's tool/result mapper reads the V4 message (its V3 wrapper reads were dropping result content on supported lines); the cache probe rides the 2-arg cachedSnapshot face. The baseline gate stays: a below-floor harness composes the fallback units and the upgrade prompt, and an undetectable version still fails open. Verified: pnpm run lint:fix && pnpm run test && pnpm run build green (1918 tests, 100% per-file coverage); the compat matrix passes against the real dsh sources at both baseline tags; live on dsh 0.2.0-rc.2 the plugin composes, the Plugins-page preference card renders and round-trips a write, the insights warm-up fills the heatmap, and the session jump and Context tab work end to end.
This commit is contained in:
1 parent
65d1c77ac9
commit
a988c9a8fb
79 files changed
+809
-1821
No files matched your search
+34
-38
@@ -2,27 +2,26 @@
|
||||
|
||||
dsh-context declares per-release compatibility with `@deepseek-ai/dsh` in its package manifest (`dsh.compatibility.dshReleases`). This page records what is actually verified for each declared release, and how.
|
||||
|
||||
Last verified: **2026-09-29** (plugin `dsh-context@0.60.0` source tree, dsh `0.2.0-rc.2`).
|
||||
Last verified: **2026-10-07** (plugin `dsh-context@0.65.0` source tree, dsh `0.2.0-rc.2`).
|
||||
|
||||
## Supported dsh releases
|
||||
|
||||
| dsh release | Session log | Declared | Automated seam matrix | Disposable-profile install / uninstall |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `0.1.5-rc.1` | V3 | compatible | ✅ baseline `v0.1.5-rc.1` | ✅ install OK → 1 composed row → uninstall OK → 0 rows (verified 2026-09-10) |
|
||||
| `0.1.7-rc.2` | V4 | compatible | ✅ baseline `v0.1.7-rc.2` | ✅ install OK → uninstall OK (verified manually 2026-09-28) |
|
||||
| `0.2.0-rc.2` | V4 | compatible | ✅ baseline `v0.2.0-rc.2` | ✅ install OK → 1 composed row → uninstall OK → 0 rows (`dsh-context@0.60.0`, verified manually 2026-09-29) |
|
||||
|
||||
The automated seam matrix runs for every row on every `pnpm test`. The disposable-profile column is a manual, per-release check: each release's CLI was installed from npm into a temporary `DSH_HOME` (the real `~/.dsh` is never touched) — `0.1.5-rc.1` on 2026-09-10 (a stale mirror can 404 the harness's own dependency closure before the plugin is even considered), then `0.1.7-rc.2` and `0.2.0-rc.2` on 2026-09-28 and 2026-09-29. The check pins the plugin build it installs: a bare-name install resolves through the profile's package manager, whose release-age policy can lag a just-published build (on `0.2.0-rc.2` the bare name resolved to `0.59.1`, so the row records the explicit `dsh-context@0.60.0`). The only pnpm complaints there are peers the harness supplies from its own runtime module tree — none version-incompatible, and the install is never refused.
|
||||
The automated seam matrix runs for every row on every `pnpm test`. The disposable-profile column is a manual, per-release check: each release's CLI was installed from npm into a temporary `DSH_HOME` (the real `~/.dsh` is never touched) — `0.1.7-rc.2` and `0.2.0-rc.2` on 2026-09-28 and 2026-09-29. The check pins the plugin build it installs: a bare-name install resolves through the profile's package manager, whose release-age policy can lag a just-published build (on `0.2.0-rc.2` the bare name resolved to `0.59.1`, so the row records the explicit `dsh-context@0.60.0`). The only pnpm complaints there are peers the harness supplies from its own runtime module tree — none version-incompatible, and the install is never refused.
|
||||
|
||||
The supported range is the three lines above: the `0.1.5` line from `rc.1`, the `0.1.7` line from `rc.2` (the first release of that line with a complete npm dependency closure; `rc.2` adds `startsSeries` to the first `request/header` of a resumed series and the `developer/message` tool-registry events; `startsSeries` is inert to the fold, while registry messages contribute to injected context), and the `0.2.0` line from `rc.2` — the continuation of the V4 Config-form generation, every seam the plugin rides spelled as at `0.1.7-rc.2` (cordis `4.0.4`, the same projection-registry contract, platform module table, client seats, settings transport, and durable-event vocabulary; still the V4 log generation, with no `v4-to-v5` migration in the session-format tree). Everything older — the `0.1.2`/`0.1.3` generations, the `0.1.1` line, and the alpha previews — was supported through `dsh-context@0.55.x` and is no longer in the support matrix; on such a harness the plugin's baseline gate composes the fallback units and shows the upgrade prompt instead of folding.
|
||||
The supported range is the two lines above: the `0.1.7` line from `rc.2` (the first release of that line with a complete npm dependency closure; `rc.2` adds `startsSeries` to the first `request/header` of a resumed series and the `developer/message` tool-registry events; `startsSeries` is inert to the fold, while registry messages contribute to injected context), and the `0.2.0` line from `rc.2` — the continuation of the V4 Config-form generation, every seam the plugin rides spelled as at `0.1.7-rc.2` (cordis `4.0.4`, the same projection-registry contract, platform module table, client seats, settings transport, and durable-event vocabulary; still the V4 log generation, with no `v4-to-v5` migration in the session-format tree). Everything older — the `0.1.5` line (supported through `dsh-context@0.65.x`), the `0.1.2`/`0.1.3` generations, the `0.1.1` line, and the alpha previews — is no longer in the support matrix; on such a harness the plugin's baseline gate composes the fallback units and shows the upgrade prompt instead of folding.
|
||||
|
||||
## Harness-side compatibility admission (0.1.7+)
|
||||
|
||||
From `0.1.7` the harness enforces a plugin's own `peerDependencies` at startup and install: a bundle whose `@deepseek-ai/dsh*` peer ranges do not satisfy the running version (semver, `includePrerelease: true`) is skipped at composition, refused at install, and only loads with an exact-version exemption (`dsh plugin allow-version`). This plugin's dsh peers are all `>=0.1.5-rc.1`, which every supported release — all three prerelease lines included — satisfies, so the plugin composes normally with no exemption. The `0.2.0` line keeps the same gate.
|
||||
Every supported line enforces a plugin's own `peerDependencies` at startup and install: a bundle whose `@deepseek-ai/dsh*` peer ranges do not satisfy the running version (semver, `includePrerelease: true`) is skipped at composition, refused at install, and only loads with an exact-version exemption (`dsh plugin allow-version`). This plugin's dsh peers are all `>=0.1.7-rc.2`, which both supported lines satisfy, so the plugin composes normally with no exemption. The gate predates them (`0.1.7` introduced it), so an older harness without it (the `0.1.5`/`0.1.6` lines) still loads the plugin — and the plugin's own baseline gate below is what composes the fallback units there.
|
||||
|
||||
## Baseline gate
|
||||
|
||||
The declared floor is enforced at runtime, not only documented. At startup the host resolves the running harness's version — first from the module tree the plugin is bound to, then from the `$DSH_HOME/profiles/node_modules` mirror — and compares it against `0.1.5-rc.1` (channel order: release > rc > beta > alpha):
|
||||
The declared floor is enforced at runtime, not only documented. At startup the host resolves the running harness's version — first from the module tree the plugin is bound to, then from the `$DSH_HOME/profiles/node_modules` mirror — and compares it against `0.1.7-rc.2` (channel order: release > rc > beta > alpha):
|
||||
|
||||
- **Below the floor** — fallback projection units that fold nothing and serve zeroed data: the Context tab keeps its (empty) cards while a modal names both versions and urges the update.
|
||||
- **At or above the floor** — the real projection units.
|
||||
@@ -30,33 +29,30 @@ The declared floor is enforced at runtime, not only documented. At startup the h
|
||||
|
||||
The mirror reflects whichever installation last booted a CLI profile, which need not be the running one (a packaged desktop client never refreshes it), so it never outranks the running module tree. Implementation: `src/host/version.ts`, `src/host/fallback.ts`, `src/shared/version.ts`.
|
||||
|
||||
## Session-log generations
|
||||
## Session-log generation
|
||||
|
||||
The supported range spans three baselines and two durable-log generations, and the plugin folds both generations from one shape-driven code path: the spellings meet only in `src/host/logShapes.ts` (`firstTokenTimeOfStream`, `replaceRangeOf`, `isTokenChunk`), and `applyTimeline` runs a single code path over all of them:
|
||||
Every supported line is the V4 durable-log generation, and the plugin folds it from one shape-driven code path (`src/host/logShapes.ts` holds the vocabulary readers — `firstTokenTimeOfStream`, `replaceRangeOf`, `isTokenChunk` — and `applyTimeline` runs a single code path over them):
|
||||
|
||||
| Seam | V3 (`0.1.5-rc.1+`) | V4 (`0.1.6/0.1.7+`, `0.2.0+`) |
|
||||
| --- | --- | --- |
|
||||
| System prompt | `system/message` surface node (a `request/header` carrying `header.system` is rejected outright) | same as V3 |
|
||||
| First token | embedded `assistant/message.data.stream` (also `assistant/attempt.data.stream`) — packed delta runs plus raw `chunk` records | same as V3 |
|
||||
| Replacement endpoints | `{ startSeq, endSeq }` | same as V3 |
|
||||
| Nested PTC dispatch | `tool/ptc-dispatch` | same as V3 |
|
||||
| Tool result | `tool-result` wrapper block, `role: 'user'` | `role: 'tool'`, lifted `message.toolCallId`/`message.isError`, direct content (no wrapper block) |
|
||||
| Seam | V4 (`0.1.7-rc.2+`, `0.2.0-rc.2+`) |
|
||||
| --- | --- |
|
||||
| System prompt | `system/message` surface node (a `request/header` carrying `header.system` is rejected outright) |
|
||||
| First token | embedded `assistant/message.data.stream` (also `assistant/attempt.data.stream`) — packed delta runs plus raw `chunk` records |
|
||||
| Replacement endpoints | `{ startSeq, endSeq }` |
|
||||
| Nested PTC dispatch | `tool/ptc-dispatch` |
|
||||
| Tool result | first-class `role: 'tool'` message with lifted `message.toolCallId`/`message.isError`, direct content |
|
||||
| Developer messages | `developer/message` tool-registry changes, nested under `data.message` |
|
||||
|
||||
**Legacy logs never reach the fold.** The running harness migrates every historical log to its own generation on EVERY read path the plugin rides — live open, persistence read handles, `sessionQuery.observeSession` (the detail/backfill cold reads) — in memory on read opens and as a published successor file on write opens; the fork-inherited prefix migrates with the rest. A log the migration chain cannot convert is refused entirely (`SessionFormatUnsupportedError`): the session is unavailable, never served raw. So on any supported harness the events the fold sees are V3/V4-shaped by construction, and the pre-V3 spellings (`header.system` envelopes, `assistant/chunk` floods, `{ start, end }` endpoints, `tool/code-dispatch`) exist nowhere in the code.
|
||||
**Legacy logs never reach the fold.** The running harness migrates every historical log to V4 on EVERY read path the plugin rides — live open, persistence read handles, `sessionQuery.observeSession` (the detail/backfill cold reads) — in memory on read opens and as a published successor file on write opens; the fork-inherited prefix migrates with the rest (the V3→V4 stage lifts the old `tool-result` wrapper into the first-class message and rewrites sources). A log the migration chain cannot convert is refused entirely (`SessionFormatUnsupportedError`): the session is unavailable, never served raw. So on any supported harness the events the fold sees are V4-shaped by construction, and the pre-V4 spellings (the `tool-result` wrapper block and its `isError`, the event-level `data.error` mark, and further back the `header.system` envelopes, `assistant/chunk` floods, `{ start, end }` endpoints, `tool/code-dispatch`) exist nowhere in the code.
|
||||
|
||||
The fold never branches on a detected harness version. The version probe is best-effort — it reports the module tree the embedding shell resolves plugin imports to, which a packaged client may redirect — so it decides which units register and nothing more.
|
||||
|
||||
### Known V4 deltas
|
||||
### Deltas of the current generation
|
||||
|
||||
- **Developer messages**: V4 tool-registry changes are nested under `data.message` and fold into injected context, including request composition, live surface, and removed-node archives. They never count as human inputs; empty-content events keep their surface position at zero tokens, matching the harness's message projection.
|
||||
- **Tool-result error mark**: V4 made the event-level `data.error` identity optional while lifting `isError` onto the message, so the fold reads the flag from BOTH spellings (`src/host/fold.ts`); a V4 result carrying only `message.isError` still flags its surface node and file-op row.
|
||||
- **Developer messages**: `developer/message` tool-registry changes are nested under `data.message` and fold into injected context, including request composition, live surface, and removed-node archives. They never count as human inputs; empty-content events keep their surface position at zero tokens, matching the harness's message projection (its `deriveEventMessage` projects an empty message to null — the fold's structural read keeps the node).
|
||||
- **Tool-result error mark**: the event-level `data.error` metadata is admitted only alongside the message mark (the generation's own validation refuses a row whose `data.error` outlives `message.isError`), so the fold reads `message.isError` alone; a hostile row carrying only the envelope field degrades to unflagged, never to a throw.
|
||||
- **Image offload accounting (deferred)**: when a route rejects with `IMAGE_OFFLOAD_REQUIRED`, the harness offloads images out of the context through a message projection. The plugin's fold reads raw durable events, so an offloaded message keeps its pre-offload image count and price until compaction removes it — an over-count on a rare recovery path, not a crash. Revisit alongside the harness's own token-meter treatment if it shows up in practice.
|
||||
- **User preferences (shipped)**: the V4 line (first shipped in `0.1.6-alpha.2`) retired the `settings.register` host face and the `settings.plugin.item` browser slot — plugin configuration moved to the Plugins page, derived from each loader entry's own Config schema. The plugin serves both generations from one entry schema:
|
||||
- The entry `Config` (`src/host/config.ts`) is schemastery. Cordis still validates the `config:` block through its Standard Schema face on every supported line; on V4+ the SAME schema is what the settings `describe` projects — the namespace is the entry id (`dsh-context`) and the eight preference fields (`.volatile()`-marked) are the served, live-editable form; the fold bounds stay ordinary entry config (their edit remounts the entry, a preference edit commits volatile-only and never remounts).
|
||||
- The browser half registers its card on the seat the running line declares, by service presence: `settingsScope` + `settings.plugin.item` on V3, `configForms` + the Plugins page's keyed `plugins.bundle.config` on V4+ (`ctx.configForms.whileServed` keeps the card alive only while the Host serves the namespace). Both seats render the same eight preference rows.
|
||||
- The `.volatile()` modifier exists only on the Config-form generations' schemastery; the schema builder feature-detects it, so the same bundle serves both generations.
|
||||
- Deliberate relaxations against the replaced zod schema: schemastery objects merge unknown keys through instead of failing the load, and only whole-value edits are rejected by range/step checks (bounds remain `min 1, step 1`).
|
||||
- Preference values persisted on the V3 line live in the settings document (`settings.yaml` section `dsh-context`); the V4 harness's own legacy import moves that section into the profile entry of the same name on first boot, so existing values carry into the new surface.
|
||||
- **User preferences**: plugin configuration lives on the Plugins page, derived from the loader entry's own Config schema (`src/host/config.ts`): cordis validates the `config:` block through the schema's Standard Schema face, and the SAME schema is what the settings `describe` projects — the namespace is the entry id (`dsh-context`) and the eight preference fields (`.volatile()`-marked) are the served, live-editable form; the fold bounds stay ordinary entry config (their edit remounts the entry, a preference edit commits volatile-only and never remounts). The browser half registers its card on the Plugins page's keyed `plugins.bundle.config` seat over the `configForms` transport (`ctx.configForms.whileServed` keeps the card alive only while the Host serves the namespace). The `.volatile()` modifier is feature-detected at schema build time (`volatileField`), so the same bundle still loads — preferences inert — on a below-floor harness whose schemastery predates it (the gate's fallback composition).
|
||||
- Preference values persisted on a retired line (the `settings.yaml` section `dsh-context`) are moved into the profile entry of the same name by the harness's own legacy import on first boot, so existing values carry into the current surface.
|
||||
|
||||
## Parsing resilience [IMPORTANT!]
|
||||
|
||||
@@ -76,27 +72,27 @@ The plugin's low-level logic mirrors the newest supported dsh implementation: to
|
||||
|
||||
## Web client seams
|
||||
|
||||
The browser half rides generation-specific seats, each reached through an optional seam so a deployment without the service simply goes without the capability. Capabilities only newer lines serve are reached through a deferred `ctx.inject` — never a hard module `inject` — so an older harness composes fully with the capability absent (no pending fiber, no throw): the service shape is re-proved before use, the registration is guarded, and the module is declared in `dsh.client.inject`. Every supported line ships the right Sidebar and the keyed `main` conversation panel; the plugin contributes to whichever face the running line serves:
|
||||
The browser half rides generation-specific seats, each reached through an optional seam so a deployment without the service simply goes without the capability. Capabilities only newer lines serve are reached through a deferred `ctx.inject` — never a hard module `inject` — so a composition without the service composes fully with the capability absent (no pending fiber, no throw): the service shape is re-proved before use, the registration is guarded, and the module is declared in `dsh.client.inject`. Every supported line ships the right Sidebar and the keyed `main` conversation panel; the plugin contributes to whichever face the running line serves:
|
||||
|
||||
| Seam | V3 (`0.1.5-rc.1`) | V4 (`0.1.7-rc.2`, `0.2.0-rc.2`) |
|
||||
| --- | --- | --- |
|
||||
| Conversation panel root | keyed `main` panel's `main.conversation` (the plugin's `conversation.view` seat hangs under it) | same as V3 |
|
||||
| Insight page (panel + entry) | the layout's keyed `main` slot under the `dsh-context` panel key, with the sidebar entry on the `sidebar.panellist` list under the same id — the shell owns the entry row and panel switching; both spellings probed per baseline (the `insightPage` probe) | same as V3 |
|
||||
| Right Sidebar tab + guide entry | `sidebarRightTabs` registry, `sidebar.right.pane.tab`(`.title`) seats, guide capsule with `order`/`title`/`description`/`icon` | same as V3 (`0.1.7-rc.2`+ adds an optional `commandId` to guide entries and `bindCommands` to tab actions — additive, unused by the plugin) |
|
||||
| Preferences card seat | `settings.plugin.item` over the `settingsScope` transport | `plugins.bundle.config` over the `configForms` transport |
|
||||
| Session jump | `uiWorkspace.openSession` — the sidebar row click's own verb; the retired `sessions.open(id)` spelling is still served as a fallback | `uiWorkspace.openSession(target: SessionTarget)` — same verb, parameter re-spelled by the 0.1.6 selection refactor; the sessions service carries no selection verb any more |
|
||||
| Seam | V4 (`0.1.7-rc.2`, `0.2.0-rc.2`) |
|
||||
| --- | --- |
|
||||
| Conversation panel root | keyed `main` panel's `main.conversation` (the plugin's `conversation.view` seat hangs under it) |
|
||||
| Insight page (panel + entry) | the layout's keyed `main` slot under the `dsh-context` panel key, with the sidebar entry on the `sidebar.panellist` list under the same id — the shell owns the entry row and panel switching; both spellings probed per baseline (the `insightPage` probe) |
|
||||
| Right Sidebar tab + guide entry | `sidebarRightTabs` registry, `sidebar.right.pane.tab`(`.title`) seats, guide capsule with `order`/`title`/`description`/`icon` (an optional `commandId` on guide entries and `bindCommands` on tab actions are additive and unused by the plugin) |
|
||||
| Preferences card seat | `plugins.bundle.config` over the `configForms` transport |
|
||||
| Session jump | `uiWorkspace.openSession(target: SessionTarget)` — the sidebar row click's own verb, its parameter re-spelled by the 0.1.6 selection refactor; the sessions service carries no selection verb any more |
|
||||
|
||||
The guide capsule carries `order`, `title`, `description`, and `icon`; `0.1.5-rc.1+` renders the description line (the `guideEntry` probe in `tests/baselines.ts` pins the fields of both supported generations).
|
||||
The guide capsule carries `order`, `title`, `description`, and `icon` (the `guideEntry` probe in `tests/baselines.ts` pins the fields of the supported generation).
|
||||
|
||||
The File Activity card's file-name affordance is an optional-column story: on a line with the right Sidebar it opens the file's preview tab through `ctx.sidebarRight.openResource` (the built-in Files sidebar's own path), building the `dsh-resource://file/…` address with the harness's browser-safe `fileAddressFor` (inlined by the client bundle, exactly as the Sidebar's own file types inline it). Where the preview face is absent (or refuses the address), the name keeps its original system-open behavior; the `sidebar.nav` probe pins the navigation face's spelling.
|
||||
The File Activity card's file-name affordance is an optional-column story: the plugin opens the file's preview tab through `ctx.sidebarRight.openResource` (the built-in Files sidebar's own path), building the `dsh-resource://file/…` address with the harness's browser-safe `fileAddressFor` (inlined by the client bundle, exactly as the Sidebar's own file types inline it). Where the preview face is absent (or refuses the address), the name keeps its original system-open behavior; the `sidebar.nav` probe pins the navigation face's spelling.
|
||||
|
||||
The session jump — a Context Insights session card or an Agent network node bringing its session forward — rides the harness's own selection verb (`openSessionVia` in `src/client/services.ts`): `uiWorkspace.openSession`, the verb the built-in sidebar's row click uses on every supported line, with the sessions service's retired `open(id)` spelling kept as the degradation path for a composition without the workspace module. Both faces are re-proved per call and a deployment serving neither (or a hostile face) swallows silently — the verb's own navigation returns the center column to the conversation (`selectPanel(null)`), so a dead jump simply stays on the page. Issue #90 was exactly the retired-spelling trap: the plugin called `sessions.open` alone, which the 0.1.6 selection refactor removed from the service (the click then did nothing on `0.1.7-rc.2`), so the jump now follows the view owner first and the per-baseline `sessionNav` probe pins both spellings presence-as-declared.
|
||||
The session jump — a Context Insights session card or an Agent network node bringing its session forward — rides the harness's own selection verb (`openSessionVia` in `src/client/services.ts`): `uiWorkspace.openSession`, the verb the built-in sidebar's row click uses on every supported line. The face is re-proved per call and a deployment serving none (or a hostile face) swallows silently — the verb's own navigation returns the center column to the conversation (`selectPanel(null)`), so a dead jump simply stays on the page. Issue #90 was exactly the retired-spelling trap: the plugin once called the sessions service's own `open(id)`, which the 0.1.6 selection refactor removed from the service (the click then did nothing on `0.1.7-rc.2`), so the jump follows the view owner and the per-baseline `sessionNav` probe pins the verb's signature and the retired spelling's absence.
|
||||
|
||||
The Context Insights page's DeepSeek balance capsule is an all-optional stack, so it arms nowhere it cannot: the host reaches the `settings` and `credentials` services only inside a deferred `ctx.inject` (and re-reads them per request, so a key added while the host runs is picked up on the next open), mounts `/api/dsh-context/balance` through the same Connection exact-route registry as the detail and backfill routes, and the outbound `/user/balance` read carries the key only inside the host process. The provider's settings row is read through whichever face the running line serves: the `get(ns)` registered-section read through V3 (the `llm-deepseek` namespace), the Config-form `describe()` projection on V4+ — there the row is the entry whose served value declares the top-level `apiKeyEnv` credential ref, preferred under the settings ids the generations have served it as (`llm-deepseek` on product profiles, `llm-deepseek-api-key` elsewhere). The capsule appears only on a live, well-formed answer; a deployment whose DeepSeek API-key provider is not composed (no settings row on either face), whose credentials service cannot resolve the key, whose connection service lacks the exact-route registry, or whose platform read fails (network, non-2xx, malformed payload, a third-party endpoint that does not serve the balance path) answers a typed `null` and the browser renders nothing — no pill, no spinner, no error state. The `balance` probe pins the settings face and the provider row's sources per baseline.
|
||||
The Context Insights page's DeepSeek balance capsule is an all-optional stack, so it arms nowhere it cannot: the host reaches the `settings` and `credentials` services only inside a deferred `ctx.inject` (and re-reads them per request, so a key added while the host runs is picked up on the next open), mounts `/api/dsh-context/balance` through the same Connection exact-route registry as the detail and backfill routes, and the outbound `/user/balance` read carries the key only inside the host process. The provider's settings row is read off the Config-form `describe()` projection — the row is the entry whose served value declares the top-level `apiKeyEnv` credential ref, preferred under the settings ids the generations have served it as (`llm-deepseek` on product profiles, `llm-deepseek-api-key` elsewhere). The capsule appears only on a live, well-formed answer; a deployment whose DeepSeek API-key provider is not composed (no settings row), whose credentials service cannot resolve the key, whose connection service lacks the exact-route registry, or whose platform read fails (network, non-2xx, malformed payload, a third-party endpoint that does not serve the balance path) answers a typed `null` and the browser renders nothing — no pill, no spinner, no error state. The `balance` probe pins the settings face and the provider row's sources per baseline.
|
||||
|
||||
## What each check means
|
||||
|
||||
- **Automated seam matrix** — part of this repository's `pnpm test` (the `compat` vitest project). For every baseline tag it stages the harness's REAL sources at that tag, boots the plugin's built host entry into that tag's actual `SessionProjectionRegistry` on the cordis release the line vendors, and probes the tag's client seams (slots, finalized-nodes seat, image loader, history face/envelope, markdown chrome, platform module table, that generation's durable-event vocabulary, the right Sidebar tab seam, its guide-entry contract and its resource-navigation face, the session-jump seam, the preferences card seat and settings transport). Definitions live in `tests/baselines.ts` — the one source of truth for the supported baselines and their per-version seam faces; the release workflow fetches the pinned baseline tags before testing. Preferences seats and transports are asserted BOTH ways: each slot exists on exactly one supported generation, so the plugin's deferred registrations pick their side and never pend. To add a future dsh release: add one `Baseline` entry (tag, vendored cordis, faces) to `tests/baselines.ts` and its tag to the release workflow's fetch step, then run `pnpm test` and re-fit whichever seam a failing probe names. The `compat` project boots the BUILT plugin (`pnpm run build` first) against the ACTUAL harness sources at each baseline tag and needs a dsh checkout (env `DSH_REPO`, default `~/dev/deepseek-harness`).
|
||||
- **Automated seam matrix** — part of this repository's `pnpm test` (the `compat` vitest project). For every baseline tag it stages the harness's REAL sources at that tag, boots the plugin's built host entry into that tag's actual `SessionProjectionRegistry` on the cordis release the line vendors, and probes the tag's client seams (slots, finalized-nodes seat, image loader, history face/envelope, markdown chrome, platform module table, that generation's durable-event vocabulary, the right Sidebar tab seam, its guide-entry contract and its resource-navigation face, the session-jump seam, the preferences card seat and settings transport). Definitions live in `tests/baselines.ts` — the one source of truth for the supported baselines and their per-version seam faces; the release workflow fetches the pinned baseline tags before testing. Retired seams are asserted ABSENT (`settings.register`, the settings `get(ns)` read, the sessions service's `open(id)`), so a resurrection names the seam instead of silently shadowing the current face. To add a future dsh release: add one `Baseline` entry (tag, vendored cordis, faces) to `tests/baselines.ts` and its tag to the release workflow's fetch step, then run `pnpm test` and re-fit whichever seam a failing probe names. The `compat` project boots the BUILT plugin (`pnpm run build` first) against the ACTUAL harness sources at each baseline tag and needs a dsh checkout (env `DSH_REPO`, default `~/dev/deepseek-harness`).
|
||||
- **Statistics against the harness's own folds** — the plugin's figures were verified differentially against the harness's OWN projection values (`sessionStats`, `contextBreakdown`, `contextPressure`, `tokenUsage`) over real session logs: system/tools/message tokens, per-request counts, turns/steps, TTFT, generation, tool time, and every billed cost bucket match exactly.
|
||||
- **Disposable-profile install / uninstall** — for each release, that exact `dsh` CLI version was installed from npm into a temporary `DSH_HOME` (the real `~/.dsh` is never touched), then:
|
||||
1. `dsh plugin --profile <disposable> add dsh-context` — install OK;
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 133 KiB |
Reference in new issue
Block a user