The tooltip and shared type still described the pre-#78 scheme (peak at list price, off at half), while models.dev lists DeepSeek's off-peak rates and the estimator doubles the peak bucket. Correct the wording in both locales, the cost type, and the READMEs; only DeepSeek's peak buckets double, every other provider prices at list. Express the peak clock in Beijing Time directly (Monday-Friday 09:00-12:00 and 14:00-18:00, weekends off-peak) and rename isPeakUtc to isBeijingPeak. The UTC-to-Beijing refactor is behavior-preserving, so no stateVersion bump is needed. Statutory holidays are deliberately not modeled; record that limitation in docs/compatibility.md. Refs #178
13 KiB
Compatibility
The rules dsh-context must hold on every supported @deepseek-ai/dsh release, and the evidence for each. Supported releases are declared in package.json (dsh.compatibility.dshReleases); baselines and their per-line seam faces are pinned in tests/baselines.ts.
Grammar. Entries are numbered rules (the bold lead is the invariant), plus Limitation (an accepted gap), Check (how a claim is proven), and Procedure (how to run a compatibility task). Evidence rides in trailing parens — paths in this repo, or dsh: paths in the harness tree read at the baseline tag — or as a table directly beneath the rule it maps. A rationale appears only where code cannot show it.
Law. Record only what no other artifact carries; delete what code, tests, or git history already say. Verify against the dsh sources before acting — source wins over this page. Less is ALWAYS better.
Last verified: 2026-10-20 (dsh-context 0.73.3, dsh 0.1.7-rc.2 / 0.2.0-rc.2 / 0.2.1-rc.2 tag trees).
1 · Supported set
What the plugin declares it works on.
- Support exactly
0.1.7-rc.2,0.2.0-rc.2and0.2.1-rc.2— all the V4 durable-log generation. The floor is0.1.7-rc.2because it is the first0.1.7-line release with a complete npm dependency closure; anything older gets §2's fallback composition with an upgrade prompt, never a fold.
2 · Admission
Whether the plugin loads at all.
Harness-side
- dsh ≥
0.1.7enforces a plugin's@deepseek-ai/dsh*peer ranges at startup and install (semver,includePrerelease: true; exemptiondsh plugin allow-version). This plugin's peers are all>=0.1.7-rc.2— no exemption on any supported line. (dsh:packages/boot/app-boot/src/plugin-compatibility.ts,apps/cli/src/plugin.ts)
Plugin-side
- Enforce the floor again at runtime — a pre-
0.1.7harness has no gate and would still load the plugin. Below the floor: zeroed fallback units plus an upgrade modal. Version undetectable: FAIL OPEN into the real units — a probe misfire must never blank a working deployment, and the home mirror (last-booted profile) never outranks the running module tree. (src/host/version.ts,src/host/fallback.ts,src/shared/version.ts) - Feature-detect
.volatile()at schema build time, so the same bundle loads — preferences inert — on a below-floor harness whose schemastery predates it. (src/host/config.ts)
3 · Fold
Durable events → projection state, host-side.
Shapes
- One shape-driven path; never branch on a detected harness version. (
src/host/fold.ts,src/host/logShapes.ts) - Trust the migration guarantee; carry no legacy readers. Every read path the plugin rides (live open, persistence handles,
sessionQuery.observeSession) migrates pre-V4 logs to V4; an unconvertible log is refused withSessionFormatUnsupportedError, never served raw. Pre-V4 spellings exist nowhere in this repo. (dsh:packages/session/session-format-v3-to-v4,packages/session/session-format-catalog,packages/session/session-persistence/src/errors.ts,packages/session-query/session-query/src/documents.ts) developer/messagefolds into injected context — request composition, live surface, archives; never human inputs; an empty-content event keeps its surface node at zero tokens where the harness'sderiveEventMessageprojects null. (src/host/fold.ts)- Read tool-result errors from
message.isErroralone — the generation admits event-leveldata.erroronly alongside it; a hostile envelope-only row degrades to unflagged, never a throw. (src/host/fold.ts)
Totality
- Folds are TOTAL — unknown event types return state unchanged; a malformed event drops whole (no partial state); no
undefined-valued property is ever materialized into persisted state. The projection registry drivesapplywith no error boundary: one throw stalls the unit's cells and push feed forever, and oneundefinedproperty fails EVERY projection-cache write for the session.
- Limitation — image offload (deferred): on
IMAGE_OFFLOAD_REQUIREDthe harness offloads images via a message projection; the fold reads raw durable events, so an offloaded message keeps its pre-offload image count and price until compaction removes it. Revisit only if it shows in practice.
4 · Metering parity
Figures the plugin prints that the harness also prints.
- Mirror, don't import. Re-fit when a supported line moves an implementation; verify differentially against the harness's own projections (§8). Right-hand paths are
dsh:tree paths.
| Plugin site | dsh source mirrored |
|---|---|
src/shared/estimate.ts |
packages/llm/token-meter/src/estimate.ts — estimateSystemMessage and its per-block text arm |
src/shared/fileTokens.ts |
packages/llm/llm/src/content.ts fileHandleText — a file block costs its request handle text, never its bytes (priced via packages/llm/token-meter/src/route-pricing.ts); price the pathless arm |
| TPS chip first-token anchor | packages/llm/llm/src/assistant-stream.ts assistantStreamFirstTokenTime |
src/client/headline.ts occupancyPercent |
packages/client/ui-conversation/src/client/context-occupancy.ts — internal to the kernel bundle, no package export, hence mirrored |
src/client/reserve.ts |
packages/compaction/compaction-basic/src/config.ts — only the shipped defaults (thresholdRatio 0.8, headroomTokens 65536): preset realms make a deployment's policy unreadable; when contextWindow − reservedOutput − headroomTokens is non-positive the backend refuses (TargetPressureConfigError), so the bar paints no masks |
- Limitation — declared divergence: the timing card's wait/generation boundary opens at the first OBSERVABLE instant (the first token, or an earlier
block-startmarker the token packing could not stamp), not at the meter's first-token anchor — so marker-tiled decode buckets and the split never double-count one window. - Limitation — daily-ledger attribution:
contextTimeline.costprices at the settlement instant while the daily ledger prices at the step-initiation instant, so a tariff-boundary step can split its peak/off buckets differently between the lifetime total and the KPI band; the ledger also retains 400 days (MAX_KEPT_DAYSinsrc/host/activity.ts). - Limitation — statutory holidays: no holiday calendar is maintained (by decision), so a holiday-weekday request inside the peak windows prices at peak where the platform bills off-peak; the tooltip states the weekday/weekend rule. (
src/host/foldMetering.tsisBeijingPeak)
5 · Client surface
What the browser half may rely on.
Seams
- Every capability rides an OPTIONAL seam — deferred
ctx.inject(never a hardinject), the face re-proved before use, the registration guarded, the module declared indsh.client.inject. No service → no capability: no pending fiber, no throw. Per-line faces are pinned intests/baselines.tsand probed by §8's matrix. - Mount at most once per page (issue #137) — the client-plugin runner force-assigns a fresh shadowing priority to every registration, so the slot registry's same-priority duplicate guard never fires across a double mount. Read the raw ledger; mount nothing when
context-jumpis already seated; the read fails open. (dsh:packages/client/ui-renderer/src/client/registry.tsSlotRegistry.entries; probeledger) - Session jump uses the sidebar's own verb,
uiWorkspace.openSession(SessionTarget)— never the sessions service'sopen(id), removed by the0.1.6selection refactor (issue #90). (dsh:packages/client/ui-workspace/src/client/navigation.ts; probesessionNav) - The member card's open prefers the right Sidebar's subagent chat, addressed by the row's own direct parent — a face that is absent or leaves the address unclaimed keeps rule 3's whole-view jump. (dsh:
packages/client/ui-subagent/src/client/sidebar-chat/index.tsx,packages/client/ui-sidebar-right/src/client/service.ts)
Boundary
- Re-prove everything at the boundary — collections re-proven, scalars zeroed, whole-value absence stays
null(the loading screen); structural narrowing over blind casts; skip elements that fail the shape instead of throwing. (src/client/timelineSource.ts) - Isolate per-item work over hostile objects; every async read resolves to data or a retryable failure note — never an eternal spinner. (
src/client/timelineSource.ts) - A pushed revision decrease resets the detail ledger — stale reads never merge into the new head. (
src/client/timelineSource.ts) - Fetch routes ride browser-relative spellings (issue #159) — the client never issues a root-absolute
/api/...URL: fetch resolves the relative form against the page's document base, so a deployment behind a path-prefixed reverse proxy (page at/app/dsh/) keeps its prefix, where a root-absolute spelling 404s at the gateway. (src/client/apiRoutes.ts)
- Limitation — complete prompt sections: a deployment registering a
completesystem-prompt section keeps ONLY that section (registry design);promptPrefix/promptSuffixare dropped there. (dsh:packages/core/system-prompt)
6 · Projection cache
How fold-state changes reach existing installations.
- Bump a unit's
stateVersioniff later events cannot backfill the change — running totals, per-step collections flushed atstep/end, materialized envelopes. Serve-time-derivable fields need no bump, and new wire fields are additive-optional: a pre-field row serves without them and the client degrades per §5's boundary rules. Current versions sit at each unit'sstateVersionsite; past bumps live in git history. - Stale rows refold from the durable log; the warm-up probes BOTH
contextActivityandcontextTimelineand cold-refolds missing or stale rows on first page open, so idle sessions rebuild exactly. (src/host/backfill.ts) - Every cold read passes the host-wide gate (issue #121) — FIFO, one at a time, only while the V8 heap sits below two-thirds of its ceiling; a skipped read takes the caller's designed no-data path, never a failure. (
src/host/coldRead.ts) - The warm-up folds sessions over 2,000 events in in-memory slices (issue #145) through
sessionProjections.restore/hydrate,sessions.prepare, andsessionProjectionCache.write, with one write-back at the log end and byte-identical rows to a single-shotcoldSnapshot; any face absent → the session stays on the single-shot path (the faces exist on every supported line, §8's matrix probes them). (src/host/backfill.ts)
- Procedure — force one session's refold:
rm ~/.dsh/storages/session_projcache/sessions/<session-id>.json— a derived cache, rebuilt from the durable log.
7 · Packaging
Dependency declarations that look dead and are not.
- Declare
@deepseek-ai/dsh-client-ui-primitives' bare imports as this plugin's own build/test requirements — itslib/index.jsimportsshiki,@shikijs/langs/*,katex, themdast-util-*/micromark-*chain,anser,clsx,diff, andsimple-iconswhile listing them as its own devDependencies. The client bundle never carries them (deps.onlyBundle+ thedsh-client-bundle-purityplugin,tsdown.config.ts). If a primitives release starts inlining them, drop the matching entries in the same change. - Declare
@deepseek-ai/dsh-client-store,@deepseek-ai/dsh-util-code-language, and the store's ownzustand+immer— same reason, reached only through the primitives dist. - Never add
tailwindcssas a direct dependency —@tailwindcss/nodepins it at the same version; a second resolving copy breaks the napi native binding (src/client/styles/tailwind.css).
8 · Verification
How every claim above is proven.
- Check — seam matrix (the
compatvitest project, everypnpm testin CI): stages the harness's REAL sources per baseline tag, boots the BUILT plugin into the tag's actual registries on its vendored cordis, probes the host and client seams, asserts retired seams ABSENT (settings.register, settingsget(ns), sessionsopen(id)), and asserts every event familysrc/host/fold.tsswitches on is declared by every baseline. Requires a dsh checkout (DSH_REPO, default~/dev/deepseek-harness) andpnpm run buildfirst; CI fails on a missing tag, a dev box skips with a printed reason. - Check — differential statistics: figures verified equal to the harness's own projections (
sessionStats,contextBreakdown,contextPressure,tokenUsage) over real session logs — tokens, request counts, turns/steps, TTFT, generation, tool time, billed cost buckets. - Check — install (manual, per release): install the exact CLI into a disposable
DSH_HOME(never the real~/.dsh);dsh plugin add dsh-context→--dump-configshows exactly 1 composed row →dsh plugin remove→ 0 rows. Pin an exact plugin version for a just-published build — the profile's package-manager release-age policy can lag. - Check — hostile fixtures: every guard branch carries them (wrong types, missing fields, throwing accessors) — an untested guard is an unverified promise.
- Procedure — add a baseline: one
Baselineentry intests/baselines.ts(tag, vendored cordis, faces) plus the tag in both workflows' fetch step; runpnpm testand re-fit whichever seam a failing probe names. - Limitation — scope: these checks prove source-level seam compatibility, statistical parity, and install/uninstall per release — not full web-app runtime acceptance on a real profile.