From cbe3de18ba75f49af2b064fee4343d46956e236c Mon Sep 17 00:00:00 2001 From: july <2151119264@qq.com> Date: Wed, 19 Aug 2026 12:11:42 +0800 Subject: [PATCH] feat(session-analysis): add DeepSeek Harness evidence adapter --- docs/adapters/README.md | 43 +- docs/docs/hosts/adapter-matrix.md | 51 +- ...18-93-deepseek-harness-session-evidence.md | 259 ++ scripts/host-support/index.mjs | 7 + scripts/session-analysis/analyzer.mjs | 4 +- scripts/session-analysis/platforms/dsh.mjs | 2212 +++++++++++++++++ .../scripts-refactor-contract/root-help.txt | 2 +- .../session-help.txt | 4 +- .../scripts-refactor-contract.test.mjs | 4 +- test/plugins/host-support.test.mjs | 24 +- .../harness-report-render-cli.test.mjs | 14 +- test/sessions/dsh-fixtures.mjs | 684 +++++ .../session-analysis-dsh-discovery.test.mjs | 1641 ++++++++++++ .../session-analysis-dsh-fixtures.test.mjs | 334 +++ .../session-analysis-dsh-provider.test.mjs | 754 ++++++ .../session-analysis-providers.test.mjs | 6 + 16 files changed, 6022 insertions(+), 21 deletions(-) create mode 100644 docs/specs/2026-08-18-93-deepseek-harness-session-evidence.md create mode 100644 scripts/session-analysis/platforms/dsh.mjs create mode 100644 test/sessions/dsh-fixtures.mjs create mode 100644 test/sessions/session-analysis-dsh-discovery.test.mjs create mode 100644 test/sessions/session-analysis-dsh-fixtures.test.mjs create mode 100644 test/sessions/session-analysis-dsh-provider.test.mjs diff --git a/docs/adapters/README.md b/docs/adapters/README.md index 7301ca65..50a5c328 100644 --- a/docs/adapters/README.md +++ b/docs/adapters/README.md @@ -1,7 +1,8 @@ # Host Adapter Matrix This is the single entry point for Claude Code, Codex, Qoder, Cursor, Qwen, -GitHub Copilot, Pi, Kimi Code, WorkBuddy, and Grok host boundaries. Do not +GitHub Copilot, Pi, Kimi Code, WorkBuddy, and Grok host boundaries, plus the +DeepSeek Harness (DSH) developer-preview session-only slice. Do not create `docs/adapters/claude-code.md`, `docs/adapters/codex.md`, `docs/adapters/qoder.md`, `docs/adapters/cursor.md`, `docs/adapters/qwen.md`, `docs/adapters/copilot.md`, `docs/adapters/pi.md`, @@ -43,6 +44,7 @@ project `.kimi-code/skills/`), then runs `/skill:better-harness`. | Kimi Code | Analysis-capable source-local host | `.kimi-plugin/plugin.json` | `scripts/agent-customize/providers/kimi.mjs` | `scripts/session-analysis/platforms/kimi.mjs` | self-contained HTML + Markdown | `AGENTS.md` + `~/.kimi-code/skills` + project `.kimi-code/skills`/`.kimi/skills` + `~/.kimi-code/mcp.json` | `harness evidence-bundle --platform kimi` -> validated `html` render | | WorkBuddy | Analysis-capable source-local host | none (skills install into `~/.workbuddy/skills`) | `scripts/agent-customize/providers/workbuddy.mjs` | `scripts/session-analysis/platforms/workbuddy.mjs` | self-contained HTML + Markdown | `~/.workbuddy` `AGENTS.md` + identity files + `.agents` + `AGENTS.md` | `session-analysis --platform workbuddy sources` -> validated `html` render | | Grok | Analysis-capable source-local host | none (skills install into `~/.grok/skills`) | `scripts/agent-customize/providers/grok.mjs` | `scripts/session-analysis/platforms/grok.mjs` | self-contained HTML + Markdown | `~/.grok` + `.grok` + `.agents` + `AGENTS.md` | `session-analysis --platform grok sources` -> skill symlink -> validated `html` render | +| DeepSeek Harness (DSH) | Partial session-evidence adapter (developer preview) | none / unavailable | unavailable | `scripts/session-analysis/platforms/dsh.mjs`; `dsh-v1` for DSH `dsh-v0.1.0-rc.7` session format `0`, raw `.jsonl` and feature-detected `.jsonl.zstd` | unavailable; no report route | unavailable | read-only `node scripts/session-analysis.mjs sources --platform dsh --workspace [--dsh-home ]` or `node scripts/session-analysis.mjs facts --platform dsh --workspace [--dsh-home ]` | ## Read-only Plugin Lifecycle @@ -79,10 +81,12 @@ Plans never execute and always preserve native surface differences: | Pi CLI / CLI session | Persistent user/project install guidance and inventory; separate `pi -e` session-only activation whose update/remove operations are not applicable | | WorkBuddy | `PLUGIN_LIFECYCLE_UNSUPPORTED`; adapter evidence remains available | -Kimi Code and Grok are absent from this table on purpose: neither host has a +Kimi Code, Grok, and DSH are absent from this table on purpose: none has a validated native lifecycle contract yet, so lifecycle targets reject them with -`UNKNOWN_HOST` instead of borrowing another host's install route. Their adapter, -configured-asset, and session evidence remain available through the matrix above. +`UNKNOWN_HOST` instead of borrowing another host's install route. Kimi Code and +Grok retain their configured-asset and session evidence. DSH retains only its +partial session-evidence slice and has no lifecycle profile or native lifecycle +claim. The lifecycle commands do not read raw session transcripts, contact a registry, edit host settings, or register an `apply` path. @@ -188,6 +192,33 @@ edit host settings, or register an `apply` path. `signals.json`). The adapter honors `GROK_HOME`. Grok has no install shell in this repository; skills install manually into `~/.grok/skills` (symlink is enough for `/better-harness`). +- DeepSeek Harness has a developer-preview, JSONL-only session adapter at + `scripts/session-analysis/platforms/dsh.mjs`. Home resolution is strictly + `--dsh-home` over `DSH_HOME` over `~/.dsh`, and the only source root is + `/sessions`. Discovery is read-only and accepts only the fixed nested + `session.jsonl` or `session.jsonl.zstd` layout. Workspace qualification uses + only the format-0 header's absolute `cwd`; the lossy project directory is not + workspace evidence. Better Harness reports adapter metadata `dsh-v1` and + supports native DSH session format `0` pinned to `dsh-v0.1.0-rc.7`. The host + is registered only for the `sessionAnalysis` capability. + Known-but-unsupported events and unknown ignorable events are explicitly + accounted for. Unknown required events, malformed records, identity drift, + and unsupported versions fail closed; an open trailing turn remains + incomplete. Bounded source distinctions are retained without copying + arbitrary plugin data or inferring plugin ownership, causality, or faults. + Compressed artifacts are concatenated independently checksummed Zstandard + frames and are scanned and decompressed one complete frame at a time. The + public API available in supported Node.js 22.20 and 24 runtimes is + feature-detected; where it is absent, including Node.js 23.0 through 23.7, + compressed evidence is unavailable while independent raw JSONL evidence + remains readable. There is no fallback dependency or shell. + This slice does not provide native DSH installation or invocation, live PTY + or process state, configured assets or Skills, plugin lifecycle, a shell, + manifest or package integration, report/output routing, README Quickstart or + Installation placement, SQLite or custom persistence, automatic + optimization, plugin fault or causality attribution, or artifact repair or + writes. See [Story #93](https://github.com/QoderAI/better-harness/issues/93) + and the [dated support specification](../specs/2026-08-18-93-deepseek-harness-session-evidence.md). ## Output Modes @@ -201,6 +232,10 @@ Canonical templates live under `templates/reporting/`. `findings.json`, `report.md`, and `report.html`. - Markdown-only output has no visual companion. +DSH is intentionally absent from these output-mode host lists. Its +`sessionAnalysis` capability does not grant report routing, HTML, Canvas, or +Markdown output support. + ## Split Triggers Split a host into `docs/adapters/.md` only when at least one condition is diff --git a/docs/docs/hosts/adapter-matrix.md b/docs/docs/hosts/adapter-matrix.md index 9e4f155f..65b59ba8 100644 --- a/docs/docs/hosts/adapter-matrix.md +++ b/docs/docs/hosts/adapter-matrix.md @@ -13,10 +13,12 @@ host-neutral. ## Support levels -Better Harness currently declares ten capability-level host adapters. Six -have verified public Quickstart paths. Pi, Kimi Code, WorkBuddy, and Grok are visible as adapter +Better Harness currently declares ten more complete capability-level host +adapters plus one DSH session-only partial slice. Six have verified public +Quickstart paths. Pi, Kimi Code, WorkBuddy, and Grok are visible as adapter support because their installation and end-to-end evidence boundaries differ -from that six-host set. The [canonical adapter matrix](https://github.com/QoderAI/better-harness/blob/main/docs/adapters/README.md) +from that six-host set. DSH is visible only as a developer-preview session +evidence contract, not as a runnable report adapter. The [canonical adapter matrix](https://github.com/QoderAI/better-harness/blob/main/docs/adapters/README.md) remains the complete capability-level source of truth. ## Supported host adapters @@ -33,6 +35,7 @@ remains the complete capability-level source of truth. | Kimi Code | Adapter support | Analysis-capable source-local host | `.kimi-plugin/plugin.json` | Workspace-matching Kimi wire transcripts | Self-contained HTML + Markdown | | WorkBuddy | Adapter support | Analysis-capable source-local host | None; skills use WorkBuddy-owned paths | Workspace-matching WorkBuddy JSONL transcripts | Self-contained HTML + Markdown | | Grok | Adapter support | Analysis-capable source-local host | None; skills use Grok-owned paths | Workspace-matching Grok session dirs (`updates.jsonl`) | Self-contained HTML + Markdown | +| DeepSeek Harness (DSH) | Session analysis only | Partial, developer-preview contract | None | DSH JSONL backend session format `0`: raw `.jsonl` and feature-detected `.jsonl.zstd` | Unavailable | The `@qoder-ai/better-harness` npm package includes all seven plugin metadata roots. Pi reuses install metadata in the existing `package.json`, so it does @@ -58,7 +61,9 @@ contract is stale, persistent Pi operations without native evidence stay unavailable, transient Pi update/remove are not applicable, and WorkBuddy returns `PLUGIN_LIFECYCLE_UNSUPPORTED`. Kimi Code and Grok have no validated native lifecycle contract yet, so lifecycle targets reject them with -`UNKNOWN_HOST` while their adapter evidence stays available. The shadow host +`UNKNOWN_HOST` while their adapter evidence stays available. DSH likewise has +no lifecycle profile: lifecycle targets reject it with `UNKNOWN_HOST`, and only +its partial session evidence remains available. The shadow host profiles do not replace the canonical adapter matrix while ADR-0002 remains proposed. @@ -73,6 +78,9 @@ proposed. (see the [sample report](pathname:///demo/better-harness-report/)). - **Markdown-only** — no visual companion. +DSH is not an output-mode host. Session-analysis evidence does not grant HTML, +Canvas, Markdown, or any report route. + ## Adapter support boundaries ### Pi {#pi} @@ -110,6 +118,41 @@ npm-packaged host artifact; installation is a manual skill symlink into outside the verified Quickstart set until a complete interactive report-loop smoke is observed. +### DeepSeek Harness (DSH) {#deepseek-harness-dsh} + +DSH coverage is a developer-preview, JSONL-only session slice, with Better +Harness adapter metadata `dsh-v1` and native session format `0` pinned to DSH +`dsh-v0.1.0-rc.7`. Home resolution is strictly `--dsh-home` over `DSH_HOME` +over `~/.dsh`; the only source root is `/sessions`. The adapter reads the +fixed nested `session.jsonl` or `session.jsonl.zstd` layout without writing or +repairing artifacts, and it qualifies a workspace only from the header's +absolute `cwd`. DSH is registered only for the `sessionAnalysis` capability. + +Compressed artifacts are concatenated independently checksummed Zstandard +frames and are validated and decompressed one complete frame at a time. The +public API available in supported Node.js 22.20 and 24 runtimes is +feature-detected. When it is unavailable, including Node.js 23.0 through 23.7, +compressed evidence is reported unavailable while independent raw JSONL +evidence remains readable; no fallback dependency is installed. +Known-but-unsupported and unknown ignorable events are accounted +for, while unknown required events, malformed data, identity drift, and +unsupported versions fail closed. Open trailing turns remain incomplete, and +the adapter does not infer plugin ownership, causality, or faults. + +The implemented source-checkout smoke boundary is read-only: + +```bash +node scripts/session-analysis.mjs sources --platform dsh --workspace [--dsh-home ] +``` + +This is not evidence of native DSH installation or invocation. DSH has no live +PTY/process integration, configured-asset or Skill discovery, plugin lifecycle, +shell, manifest, package integration, report/output route, README Quickstart or +Installation path, SQLite or custom persistence support, automatic +optimization, plugin-fault attribution, or artifact mutation/recovery. See the +[canonical source matrix](https://github.com/QoderAI/better-harness/blob/main/docs/adapters/README.md) +and [Story #93](https://github.com/QoderAI/better-harness/issues/93). + ## Capability coverage Capabilities differ per host on purpose: no host claims a capability without a diff --git a/docs/specs/2026-08-18-93-deepseek-harness-session-evidence.md b/docs/specs/2026-08-18-93-deepseek-harness-session-evidence.md new file mode 100644 index 00000000..d7624329 --- /dev/null +++ b/docs/specs/2026-08-18-93-deepseek-harness-session-evidence.md @@ -0,0 +1,259 @@ +# DeepSeek Harness Session Evidence + +## Traceability + +- Spec ID: deepseek-harness-session-evidence +- Story: #93 +- Status: Implemented + +## Intent + +Add a narrowly scoped, read-only DeepSeek Harness (`dsh`) session adapter so +Better Harness can analyze durable DSH JSONL evidence after a run. The first +slice stops at session discovery, workspace qualification, validation, and +normalization into the existing session-analysis contract. It deliberately does +not present DSH as a first-class or natively integrated Better Harness host. + +This specification freezes the partial boundary approved by the maintainer in +[Issue #93](https://github.com/QoderAI/better-harness/issues/93), including the +feature-detection policy approved in the 2026-08-18T13:13:05Z comment. The +supported native contract is pinned to upstream commit +`99f6f02fecdb7dff40c3fbc9470f5907c29f74ca`, tag/contract +`dsh-v0.1.0-rc.7`, and `SESSION_FORMAT_VERSION = 0`. Later upstream behavior is +not implicitly supported. + +## Native Contract Evidence + +The implementation and its support claims must remain bound to these five +primary upstream sources at commit +`99f6f02fecdb7dff40c3fbc9470f5907c29f74ca`: + +1. [Developer-preview status, compatibility warning, and plugin-oriented positioning](https://github.com/deepseek-ai/deepseek-harness/blob/99f6f02fecdb7dff40c3fbc9470f5907c29f74ca/README.md) +2. [Base profile composition and the DSH-home sessions route](https://github.com/deepseek-ai/deepseek-harness/blob/99f6f02fecdb7dff40c3fbc9470f5907c29f74ca/packages/bundle/base/cordis.patch.yml) +3. [Session header, format version, event vocabulary, correlation fields, and turn outcomes](https://github.com/deepseek-ai/deepseek-harness/blob/99f6f02fecdb7dff40c3fbc9470f5907c29f74ca/packages/core/session/src/types.ts) +4. [JSONL layout, default Zstandard encoding, packed rows, identity checks, and discovery constraints](https://github.com/deepseek-ai/deepseek-harness/blob/99f6f02fecdb7dff40c3fbc9470f5907c29f74ca/packages/session/session-persistence-jsonl/README.md) +5. [SQLite's separate persistence and discovery contract](https://github.com/deepseek-ai/deepseek-harness/blob/99f6f02fecdb7dff40c3fbc9470f5907c29f74ca/packages/session/session-persistence-sqlite/README.md) + +Synthetic fixtures may encode only behavior supported by those pinned sources +and the approved Issue #93 boundary. A fixture passing is not evidence that a +newer DSH build remains compatible. Same-version structural drift must fail +closed rather than extending this contract by inference. + +## Support Boundary + +The delivered support claim is: + +```text +DSH persisted JSONL session + -> workspace qualification + -> supported event normalization + -> Better Harness session evidence +``` + +The Better Harness adapter metadata id is `dsh-v1`; the only supported native +DSH session format is `0`. The `dsh` host is registered only for the +`sessionAnalysis` capability. Raw `session.jsonl` and, when the running Node.js +runtime exposes the required public Zstandard API, concatenated checksummed +`session.jsonl.zstd` are the only physical encodings. SQLite and custom +persistence providers remain unavailable. + +Support is JSONL-only and partial. Every artifact is read-only. Unavailable, +incomplete, malformed, ambiguous, foreign-workspace, and unsupported evidence +must remain visible as such or be rejected according to the acceptance +scenarios below; it must never be guessed, repaired, rewritten, or promoted to +a broader host capability. + +## Acceptance Scenarios + +### AC-1: Scope resolution + +An explicit `--dsh-home` value takes precedence over inherited `DSH_HOME`, +which takes precedence over the default `~/.dsh`. The only session root is +`/sessions`; empty, malformed, or otherwise unresolved values do +not trigger guesses at alternate roots. + +### AC-2: Artifact discovery + +Discovery accepts only the fixed nested DSH JSONL layout containing +`session.jsonl.zstd` or raw `session.jsonl`. It deduplicates by canonical +artifact path and bound session identity. Conflicting encodings or identities, +flat legacy layouts, and ambiguous artifacts fail closed and contribute no +session evidence. + +### AC-3: Physical and logical validation + +Before evidence is accepted, the adapter validates the tagged header's `type`, +format `version`, session `id`, `createdAt`, absolute `cwd`, and required +`delegationDepth`; artifact identity; session-id/path binding; and logical JSONL +record shape. Format `0` event `seq` values start at `0` and remain contiguous. +Default `packChunks=true` storage rows are losslessly decoded according to the +pinned upstream shape and their logical events participate in sequence +validation; a packed storage row is never treated as a `SessionEvent` itself. +Malformed records, identity or sequence mismatch, unsupported versions, and +same-version structural drift are rejected. + +### AC-4: Zstandard runtime policy + +A compressed artifact is treated as a concatenation of independently +checksummed Zstandard frames. The adapter scans and validates frame boundaries, +decompresses each complete frame independently, and concatenates the decoded +payloads; it must not submit the entire file to a single decompression call. +The public Zstandard API available in supported Node.js 22.20 and 24 runtimes is +feature-detected at runtime. Where Node.js 23.0 through 23.7 exposes no required +public API, compressed evidence is explicitly unavailable while raw JSONL +evidence remains readable. This slice adds no dependency and changes no Node.js +engine range. + +### AC-5: Workspace qualification + +Only the header's absolute `cwd` qualifies a session to the requested +workspace. The intentionally lossy project-directory name is never used as +workspace evidence. Qualification follows existing Better Harness workspace +topology and path, case, canonicalization, and symlink semantics across Windows, +macOS, and Linux. A foreign workspace cannot enter the result. + +### AC-6: Normalization and bounded provenance + +Normalization allowlists only `user/message`, `assistant/message`, `tool/call`, +`tool/result`, and turn lifecycle/outcome evidence. It preserves observed user +source distinctions, native call/result ids, turn/step/sequence coordinates, +and bounded `parentSession`, `seedLength`, `origin`, `delegationDepth`, and +`agentPreset` provenance. It does not copy arbitrary raw or plugin data and +does not infer plugin ownership, plugin causality, or faulty-plugin attribution. + +### AC-7: Privacy and completeness + +The adapter reuses the existing `includeUserText`, `includeCommandText`, and +`includeContent` gates. Credential-shaped fixture values do not leak through +facts, diagnostics, provenance, or errors. Missing token or usage fields remain +unobserved rather than becoming zero. An unknown required event rejects the +artifact; an unknown ignorable event is explicitly accounted for. An open +trailing turn is marked incomplete. Every source read is read-only, and no path +repairs or rewrites an upstream artifact. + +### AC-8: Capability boundary + +The Better Harness adapter id is `dsh-v1` and supports only native DSH session +format `0`. The `dsh` host receives only `sessionAnalysis`; catalog projection +must not grant configured assets, plugin lifecycle, shell, output, packaging, +or another capability. The session-analysis CLI and loader support `dsh` +explicitly, and unknown hosts continue to fail closed rather than falling +through to another adapter. + +### AC-9: Deterministic evidence + +Synthetic fixtures and behavioral tests cover raw JSONL, concatenated +checksummed Zstandard frames, workspace acceptance and rejection, packed rows, +event correlation, every terminal outcome, unknown required and ignorable +events, bad format version/header/id/sequence, malformed data, an open trailing +turn, canonical path and session-identity deduplication, privacy gates, +Zstandard API absence, and Windows/macOS/Linux path behavior. No fixture contains +a real transcript, secret, credential, or machine-specific absolute path. + +### AC-10: Honest documentation + +The source host adapter matrix and published adapter matrix describe DSH as +JSONL-only partial session evidence and identify every unavailable slice. DSH is +not added to README Quickstart or Installation. Documentation does not claim a +shell, configured assets, Skills, plugin lifecycle, packaging, report output, +native invocation, SQLite, or custom-provider support. + +### AC-11: Validation and readiness + +Focused adapter, loader, registry, CLI, fixture, and documentation tests pass, +followed by `npm test`, `npm run pack:verify`, and `git diff --check`. Review +evidence maps Story #93 to this spec, every AC to behavioral tests or an +explicit review check, and each material risk to a fail-closed response. The +diff contains no real transcript, secret, credential, or machine absolute path. + +## Non-goals + +- Native DSH installation, invocation, live PTY integration, or process-state + observation. +- Configured-asset or Skill discovery. +- DSH plugin lifecycle integration. +- A shell, manifest, package integration, or packaging claim. +- README Quickstart or Installation placement. +- A report output route or a new report mode. +- SQLite or custom persistence-provider support. +- Global Node.js engine-range changes or new dependencies. +- Automatic harness optimization or self-modification. +- Faulty-plugin identification, plugin ownership, or causality inference. +- Upstream artifact mutation, repair, or recovery. +- Complete first-class DeepSeek Harness support. + +## Plan and Tasks + +1. **Contracts and fixtures (AC-3, AC-4, AC-6, AC-7, AC-9):** encode the + pinned format-0 header, logical events, packed-row shape, terminal outcomes, + bounded provenance, privacy values, raw JSONL, and independently checksummed + concatenated Zstandard frames as deterministic synthetic fixtures. Include + malformed and unsupported variants without copying a real transcript. +2. **Discovery and decoding (AC-1, AC-2, AC-3, AC-4, AC-5, AC-7, AC-9):** add + explicit DSH-home resolution, nested artifact discovery, canonical identity + deduplication, workspace qualification, raw decoding, feature-detected + frame-by-frame Zstandard decoding, packed-row expansion, and fail-closed + validation. Keep all reads side-effect free. +3. **Normalization and registration (AC-6, AC-7, AC-8, AC-9):** map only the + allowlisted event subset into existing session facts, preserve bounded + correlation/provenance, expose incompleteness and unavailable evidence, and + register `dsh-v1` only in session-analysis loader/CLI and the corresponding + capability slice. +4. **Documentation and support claims (AC-8, AC-10):** update only the two + adapter matrices and relevant session-analysis support references so their + JSONL-only partial claim, runtime Zstandard boundary, and unavailable slices + exactly match executable behavior. Do not widen README installation or + Quickstart surfaces. +5. **Full validation and readiness (AC-9, AC-11):** run focused behavioral + tests, the full suite, package verification, whitespace validation, and a + Review Readiness Check. Inspect the complete diff for capability overclaim, + private data, real transcripts, machine paths, generated-file drift, and a + coherent Story -> Spec -> AC -> test/risk evidence chain. + +Each module is reviewed before the next begins. A later task may tighten an +implementation detail only when it remains inside these ACs and the pinned +native sources; widening support requires a separately approved specification. + +## Test and Review Evidence + +| Acceptance criteria | Test or command | Expected review evidence | +| --- | --- | --- | +| AC-1 | Focused home-resolution tests | CLI override wins over environment and default; the only derived root ends in `sessions`; invalid values do not guess. | +| AC-2 | Focused discovery and dedupe tests | Only fixed nested raw/compressed artifacts qualify; canonical path/session collisions, flat legacy, conflicts, and ambiguity fail closed. | +| AC-3 | Header, identity, sequence, malformed, and packed-row fixture tests | Format-0 headers and logical records validate; packed logical events retain contiguous sequence; drift and malformed input are rejected. | +| AC-4 | Raw, concatenated checksummed-frame, truncated-frame, and API-absence tests | Each complete frame is decoded independently; truncation rejects; unavailable API disables only compressed evidence; dependency and engine diffs stay empty. | +| AC-5 | Workspace topology tests on Windows/macOS/Linux path forms | Absolute header `cwd` follows existing case/canonical/symlink semantics and foreign workspaces never contribute facts. | +| AC-6 | Event normalization, correlation, and provenance tests | Only allowlisted evidence appears; native ids and bounded coordinates/lineage survive; arbitrary/plugin fields and causal claims do not. | +| AC-7 | Privacy-gate, unknown-event, missing-usage, open-turn, and read-only tests | Content gates redact credential-shaped values; absence stays unobserved; required unknowns reject, ignorable unknowns are accounted, and open turns stay incomplete without writes. | +| AC-8 | Catalog capability mapping, loader, CLI help, and unknown-host tests | `dsh-v1` accepts only format 0; only `sessionAnalysis` maps to `dsh`; loader/CLI route explicitly and unknown hosts reject. | +| AC-9 | Focused cross-platform synthetic fixture suite | Every enumerated encoding, validation, lifecycle, identity, privacy, runtime, and path case has deterministic behavioral evidence with no real local data. | +| AC-10 | Adapter matrix assertions plus `npx vitest run test/skills-docs/doc-link-graph.test.mjs` | Both matrices expose the same partial JSONL claim and unavailable slices; links resolve; README Quickstart/Installation remains unchanged. | +| AC-11 | Focused tests; `npm test`; `npm run pack:verify`; `git diff --check`; Review Readiness Check | All commands report their actual result; package boundaries remain valid; diff is whitespace-clean and the Story/Spec/Test/Risk chain is complete. | + +Native smoke evidence, if available during implementation, is bounded and +redacted. It may confirm the pinned contract but cannot replace deterministic +fixtures or justify a broader support claim. Node.js runtimes without the public +Zstandard API must be exercised through deterministic feature-absence tests, +not described as a successful compressed-session smoke. + +## Risks and Rollback + +| Risk | Fail-closed control and review signal | +| --- | --- | +| Same-version structural drift | Validate the complete supported header and logical shapes against pinned fixtures; reject unrecognized required structure. | +| Packed rows mistaken for events | Decode only the upstream lossless packed-row shape before sequence validation; reject malformed packing and never normalize the storage row itself. | +| Multi-frame Zstandard truncation or silent tail loss | Scan and validate every boundary/checksum, require complete frames, decompress frame by frame, and reject a truncated or trailing-invalid artifact. | +| Zstandard API unavailable | Feature-detect the public API, mark compressed evidence unavailable, and continue to support independent raw evidence. | +| Path alias, case, or symlink mismatch | Reuse existing workspace topology and canonical path semantics with cross-platform positive and negative fixtures. | +| Foreign workspace admitted | Require the absolute header `cwd` to qualify; do not consult the lossy directory name or another heuristic. | +| Artifact or session identity collision | Bind header id to the nested artifact path, deduplicate canonical identity, and reject conflicts or ambiguity. | +| Unknown required event silently dropped | Distinguish required from explicitly ignorable events; reject the former and account for the latter. | +| Private content or credential leakage | Apply existing content gates at normalization and error/provenance boundaries, then assert value-level non-disclosure with synthetic credential shapes. | +| Capability or documentation overclaim | Test catalog-to-implementation mapping and review both matrices against the non-goals; no catalog projection may create an unsupported slice. | + +Rollback removes the session-only adapter, its `sessionAnalysis` registration, +its synthetic fixtures/tests, and its two matrix claims. No user-data migration +or cleanup is needed because the adapter never writes, repairs, converts, or +owns DSH artifacts. If a runtime or upstream compatibility risk is found before +that full rollback, the affected evidence path fails closed while raw evidence +and unrelated host adapters remain unchanged. diff --git a/scripts/host-support/index.mjs b/scripts/host-support/index.mjs index f372319b..e605e8d3 100644 --- a/scripts/host-support/index.mjs +++ b/scripts/host-support/index.mjs @@ -107,6 +107,13 @@ export const HOST_DESCRIPTORS = Object.freeze([ capabilities: COMMON_CAPABILITIES, sessionScopeTokens: [".grok", "grok"], }), + descriptor({ + id: "dsh", + displayName: "DeepSeek Harness", + aliases: ["DeepSeek Harness"], + capabilities: [HOST_CAPABILITIES.SESSION_ANALYSIS], + sessionScopeTokens: [".dsh", "dsh", "deepseek-harness"], + }), ]); const HOST_BY_ID = new Map(HOST_DESCRIPTORS.map((host) => [host.id, host])); diff --git a/scripts/session-analysis/analyzer.mjs b/scripts/session-analysis/analyzer.mjs index a6ffefa5..a4bbff43 100644 --- a/scripts/session-analysis/analyzer.mjs +++ b/scripts/session-analysis/analyzer.mjs @@ -56,6 +56,7 @@ Options: --kimi-home Kimi Code data root (default: ~/.kimi-code) --workbuddy-home WorkBuddy data root (default: ~/.workbuddy) --grok-home Grok CLI data root (default: ~/.grok or $GROK_HOME) + --dsh-home DeepSeek Harness data root (default: ~/.dsh or $DSH_HOME) --include-cache Include optional Qoder cache evidence --include-global-capabilities Include optional user-global Qoder evidence @@ -251,6 +252,7 @@ const PLATFORM_MODULES = Object.freeze({ kimi: { specifier: "./platforms/kimi.mjs", analyzer: "KimiSessionAnalyzer" }, workbuddy: { specifier: "./platforms/workbuddy.mjs", analyzer: "WorkbuddySessionAnalyzer" }, grok: { specifier: "./platforms/grok.mjs", analyzer: "GrokSessionAnalyzer" }, + dsh: { specifier: "./platforms/dsh.mjs", analyzer: "DshSessionAnalyzer" }, "harness-run": { specifier: "./platforms/harness-run.mjs", analyzer: "HarnessRunSessionAnalyzer" }, }); @@ -330,7 +332,7 @@ export async function main(argv = process.argv.slice(2), dependencies = {}) { ...eventOptions, ...claudeOptions, "", - "Options: --kimi-home overrides the Kimi Code data root (default: ~/.kimi-code); --workbuddy-home overrides the WorkBuddy data root (default: ~/.workbuddy); --grok-home overrides the Grok data root (default: ~/.grok or $GROK_HOME).", + "Options: --kimi-home overrides the Kimi Code data root (default: ~/.kimi-code); --workbuddy-home overrides the WorkBuddy data root (default: ~/.workbuddy); --grok-home overrides the Grok data root (default: ~/.grok or $GROK_HOME); --dsh-home overrides the DeepSeek Harness data root (default: ~/.dsh or $DSH_HOME).", "", "Use facts --debug only for local diagnosis; it exposes raw session ids and must not be passed to report agents.", ].join("\n") + "\n"); diff --git a/scripts/session-analysis/platforms/dsh.mjs b/scripts/session-analysis/platforms/dsh.mjs new file mode 100644 index 00000000..5b622467 --- /dev/null +++ b/scripts/session-analysis/platforms/dsh.mjs @@ -0,0 +1,2212 @@ +import { createHash } from "node:crypto"; +import { readdir, readFile, realpath, stat } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import * as zlib from "node:zlib"; + +import { SessionAnalyzer } from "../analyzer.mjs"; +import { parseArgs, parseBooleanFlag } from "../cli.mjs"; +import { + bindSessionWorkspaceCwds, + emitProviderResult, + markSessionReadCoverage, + runProviderAnalysis, + runProviderCommand, + workspaceMatchScopeFromOptions, +} from "../provider-runner.mjs"; +import { privacySafeUserInputText, sanitizePrivateReviewText } from "../privacy-safe-text.mjs"; +import { normalizeCliDate, withinTimeRange } from "../time.mjs"; +import { WORKSPACE_CWD_MATCH, classifyWorkspaceCwd } from "../workspace-match.mjs"; + +export const DSH_ADAPTER_VERSION = "dsh-v1"; +export const DSH_SESSION_FORMAT_VERSION = 0; + +const ZSTD_MAGIC = 0xFD2FB528; +const PACKED_TYPES = new Set(["text-chunks", "reasoning-chunks", "tool-call-chunks"]); +const NORMALIZATION_ALLOWLIST = new Set([ + "user/message", "assistant/message", "tool/call", "tool/result", + "turn/start", "turn/end", +]); +const VALIDATED_TYPES = new Set([...NORMALIZATION_ALLOWLIST, "assistant/chunk", "step/start", "step/end"]); +const VALIDATED_KNOWN_UNSUPPORTED_TYPES = new Set([ + "agent-preset/selected", "agent/inbox/spliced", "approval/asked", "approval/decided", + "approval/policy", "command/done", "command/run", "compaction/end", "compaction/prune", + "compaction/start", "compaction/summary", "feedback/record", "goal/change", "hook/invoked", + "hook/result", "llm/retry", "llm/retry-started", "permission/preset", "plan/mode", + "request/context", "request/header", "sandbox/mode", "session/end-seed", "session/title", + "session/title-llm-request", "subagent/descriptor", "todo/write", "tool-workflow/agent-end", + "tool-workflow/agent-start", "tool-workflow/run-end", "tool-workflow/run-start", + "tool/code-dispatch", "tool/code-dispatch-start", "schedule/change", "web/deepseek-search-llm-request", +]); +const CONTROL_TYPES = new Set(["step/start", "step/end"]); +const KNOWN_EVENT_TYPES = new Set([ + "agent-preset/selected", "agent/inbox/spliced", "approval/asked", "approval/decided", + "approval/policy", "assistant/chunk", "assistant/message", "command/done", "command/run", + "compaction/end", "compaction/prune", "compaction/start", "compaction/summary", + "feedback/record", "goal/change", "hook/invoked", "hook/result", "llm/retry", + "llm/retry-started", "permission/preset", "plan/mode", "request/context", "request/header", + "sandbox/mode", "schedule/change", "session/end-seed", "session/title", + "session/title-llm-request", "step/end", "step/start", "subagent/descriptor", "todo/write", + "tool-workflow/agent-end", "tool-workflow/agent-start", "tool-workflow/run-end", + "tool-workflow/run-start", "tool/call", "tool/code-dispatch", "tool/code-dispatch-start", + "tool/result", "turn/end", "turn/start", "user/message", "web/deepseek-search-llm-request", +]); +const HEADER_KEYS = new Set([ + "type", "version", "id", "cwd", "createdAt", "parentSession", "seedLength", "origin", + "delegationDepth", "agentPreset", +]); +const EVENT_KEYS = new Set(["type", "seq", "time", "data", "ignorable", "sourceEventSeqs", "surfaceOp"]); +const WINDOWS_ABSOLUTE = /^(?:[A-Za-z]:[\\/]|\\\\[^\\/]+[\\/][^\\/]+)/u; +const MAX_TIMER_DELAY_MS = 2_147_483_647; + +function fail(code, message = code) { + throw Object.assign(new Error(message), { code }); +} + +function plain(value) { + return value !== null && typeof value === "object" && !Array.isArray(value); +} + +function exactKeys(value, required, optional = []) { + if (!plain(value)) return false; + const allowed = new Set([...required, ...optional]); + return required.every((key) => Object.hasOwn(value, key)) + && Object.keys(value).every((key) => allowed.has(key)); +} + +function safeNonnegative(value) { + return Number.isSafeInteger(value) && value >= 0 && !Object.is(value, -0); +} + +function safePositive(value) { + return Number.isSafeInteger(value) && value >= 1; +} + +function validDshEpochMillis(value) { + return safeNonnegative(value) && Number.isFinite(new Date(value).getTime()); +} + +export function normalizeDshEpochMillis(value) { + if (!validDshEpochMillis(value)) fail("DSH_INVALID_EPOCH_MILLIS"); + return new Date(value).toISOString(); +} + +function absoluteFlavor(value) { + if (typeof value !== "string" || value.length === 0 || value.includes("\0")) return null; + if (WINDOWS_ABSOLUTE.test(value)) return "win32"; + if (path.posix.isAbsolute(value)) return "posix"; + return null; +} + +function expandExplicitHome(value) { + if (typeof value !== "string" || value.length === 0 || value.includes("\0")) { + fail("DSH_INVALID_HOME", "DSH home must be a non-empty absolute path"); + } + let expanded = value; + if (value === "~") expanded = os.homedir(); + else if (value.startsWith("~/") || value.startsWith("~\\")) expanded = path.join(os.homedir(), value.slice(2)); + if (!path.isAbsolute(expanded)) fail("DSH_INVALID_HOME", "DSH home must resolve to an absolute path"); + return path.resolve(expanded); +} + +function explicitHome(options) { + for (const key of ["home", "dshHome", "dsh-home"]) { + if (Object.hasOwn(options, key)) return options[key]; + } + return undefined; +} + +export function encodeDshSessionId(raw) { + if (typeof raw !== "string" || raw.length === 0) fail("DSH_INVALID_SESSION_ID"); + if (raw === ".") return "~002E"; + if (raw === "..") return "~002E~002E"; + let encoded = ""; + for (let index = 0; index < raw.length; index += 1) { + const code = raw.charCodeAt(index); + const char = String.fromCharCode(code); + encoded += char !== "~" && /^[A-Za-z0-9._-]$/u.test(char) + ? char + : `~${code.toString(16).toUpperCase().padStart(4, "0")}`; + } + return encoded; +} + +export function dshProjectKey(cwd) { + if (typeof cwd !== "string" || cwd.length === 0 || cwd.includes("\0")) fail("DSH_INVALID_CWD"); + let readable = ""; + let separatorRun = false; + for (let index = 0; index < cwd.length; index += 1) { + const code = cwd.charCodeAt(index); + const char = String.fromCharCode(code); + if (char === "/" || char === "\\" || char === ":") { + if (!separatorRun) readable += "-"; + separatorRun = true; + } else if (char !== "~" && /^[A-Za-z0-9._-]$/u.test(char)) { + readable += char; + separatorRun = false; + } else { + readable += `~${code.toString(16).toUpperCase().padStart(4, "0")}`; + separatorRun = false; + } + } + return `--${(readable.replace(/^-+/u, "") || "root").slice(0, 251)}--`; +} + +function readU24(buffer, offset) { + return buffer[offset] | (buffer[offset + 1] << 8) | (buffer[offset + 2] << 16); +} + +/** Split standard checksummed Zstd frames without guessing boundaries from magic bytes. */ +export function splitDshZstdFrames(input) { + const buffer = Buffer.from(input); + if (buffer.length === 0) fail("DSH_ZSTD_EMPTY"); + const frames = []; + let offset = 0; + while (offset < buffer.length) { + const start = offset; + if (offset + 5 > buffer.length) fail("DSH_ZSTD_TRUNCATED_HEADER"); + if (buffer.readUInt32LE(offset) !== ZSTD_MAGIC) fail("DSH_ZSTD_BAD_MAGIC"); + offset += 4; + const descriptor = buffer[offset++]; + if ((descriptor & 0x18) !== 0) fail("DSH_ZSTD_RESERVED_DESCRIPTOR"); + if ((descriptor & 0x04) === 0) fail("DSH_ZSTD_CHECKSUM_REQUIRED"); + const singleSegment = (descriptor & 0x20) !== 0; + if (!singleSegment) { + if (offset + 1 > buffer.length) fail("DSH_ZSTD_TRUNCATED_HEADER"); + offset += 1; + } + const dictionarySize = [0, 1, 2, 4][descriptor & 0x03]; + const fcsFlag = descriptor >>> 6; + const contentSizeLength = fcsFlag === 0 ? (singleSegment ? 1 : 0) : fcsFlag === 1 ? 2 : fcsFlag === 2 ? 4 : 8; + if (offset + dictionarySize + contentSizeLength > buffer.length) fail("DSH_ZSTD_TRUNCATED_HEADER"); + offset += dictionarySize + contentSizeLength; + let lastBlock = false; + while (!lastBlock) { + if (offset + 3 > buffer.length) fail("DSH_ZSTD_TRUNCATED_BLOCK"); + const blockHeader = readU24(buffer, offset); + offset += 3; + lastBlock = (blockHeader & 1) === 1; + const blockType = (blockHeader >>> 1) & 0x03; + const blockSize = blockHeader >>> 3; + if (blockType === 3) fail("DSH_ZSTD_RESERVED_BLOCK"); + const payloadSize = blockType === 1 ? 1 : blockSize; + if (offset + payloadSize > buffer.length) fail("DSH_ZSTD_TRUNCATED_BLOCK"); + offset += payloadSize; + } + if (offset + 4 > buffer.length) fail("DSH_ZSTD_TRUNCATED_CHECKSUM"); + offset += 4; + frames.push(Buffer.from(buffer.subarray(start, offset))); + } + return frames; +} + +export function decodeDshZstdArtifact(input, { decompressor = zlib.zstdDecompressSync } = {}) { + if (typeof decompressor !== "function") fail("DSH_ZSTD_UNAVAILABLE", "DSH compressed evidence is unavailable"); + const frames = splitDshZstdFrames(input); + const decoded = []; + for (const [index, frame] of frames.entries()) { + try { + const bytes = Buffer.from(decompressor(Buffer.from(frame))); + if (bytes.length === 0 || bytes.at(-1) !== 0x0A) fail("DSH_ZSTD_FRAME_NOT_JSONL_BATCH"); + if (index === 0 && bytes.subarray(0, -1).includes(0x0A)) fail("DSH_ZSTD_FIRST_FRAME_NOT_HEADER_ONLY"); + decoded.push(bytes); + } catch (error) { + if (["DSH_ZSTD_FRAME_NOT_JSONL_BATCH", "DSH_ZSTD_FIRST_FRAME_NOT_HEADER_ONLY"].includes(error?.code)) { + throw error; + } + fail("DSH_ZSTD_DECOMPRESSION_FAILED"); + } + } + return { frames, bytes: Buffer.concat(decoded) }; +} + +function packedRow(record) { + if (!PACKED_TYPES.has(record?.type)) return null; + if (!exactKeys(record, ["type", "seq0", "time0", "data"]) + || !safeNonnegative(record.seq0) || !Number.isSafeInteger(record.time0)) fail("DSH_MALFORMED_PACKED_ROW"); + const data = record.data; + const tool = record.type === "tool-call-chunks"; + const required = tool ? ["turn", "step", "index", "id", "dt", "args"] : ["turn", "step", "index", "dt", "texts"]; + const optional = tool ? ["name"] : []; + if (!exactKeys(data, required, optional) || typeof data.turn !== "number" || typeof data.step !== "number" + || typeof data.index !== "number" || (tool && typeof data.id !== "string") + || (tool && Object.hasOwn(data, "name") && typeof data.name !== "string")) { + fail("DSH_MALFORMED_PACKED_ROW"); + } + const members = tool ? data.args : data.texts; + if (!Array.isArray(members) || members.length === 0 || members.some((value) => typeof value !== "string") + || !Array.isArray(data.dt) || data.dt.length !== members.length - 1 + || data.dt.some((value) => !Number.isSafeInteger(value)) || !safeNonnegative(record.seq0 + members.length - 1)) { + fail("DSH_MALFORMED_PACKED_ROW"); + } + const events = []; + let time = record.time0; + for (let index = 0; index < members.length; index += 1) { + if (index > 0) time += data.dt[index - 1]; + if (!Number.isSafeInteger(time)) fail("DSH_MALFORMED_PACKED_ROW"); + const chunk = record.type === "text-chunks" + ? { type: "text-delta", index: data.index, text: members[index] } + : record.type === "reasoning-chunks" + ? { type: "reasoning-delta", index: data.index, text: members[index] } + : { type: "tool-call-delta", index: data.index, id: data.id, + ...(Object.hasOwn(data, "name") ? { name: data.name } : {}), argumentsDelta: members[index] }; + events.push({ type: "assistant/chunk", seq: record.seq0 + index, time, + data: { turn: data.turn, step: data.step, chunk } }); + } + return events; +} + +function validateHeader(header) { + if (!plain(header) || Object.keys(header).some((key) => !HEADER_KEYS.has(key))) fail("DSH_INVALID_HEADER"); + for (const key of ["type", "version", "id", "cwd", "createdAt", "delegationDepth"]) { + if (!Object.hasOwn(header, key)) fail("DSH_INVALID_HEADER"); + } + if (header.type !== "session") fail("DSH_INVALID_HEADER"); + if (header.version !== DSH_SESSION_FORMAT_VERSION) fail("DSH_UNSUPPORTED_SESSION_FORMAT"); + if (typeof header.id !== "string") fail("DSH_INVALID_HEADER"); + if (!absoluteFlavor(header.cwd) || !validDshEpochMillis(header.createdAt) || !safeNonnegative(header.delegationDepth)) { + fail("DSH_INVALID_HEADER"); + } + for (const key of ["parentSession", "origin", "agentPreset"]) { + if (Object.hasOwn(header, key) && typeof header[key] !== "string") fail("DSH_INVALID_HEADER"); + } + if (Object.hasOwn(header, "seedLength") && !safeNonnegative(header.seedLength)) fail("DSH_INVALID_HEADER"); + if (Object.hasOwn(header, "origin") && header.origin !== "subagent") fail("DSH_INVALID_HEADER"); + return header; +} + +function validateSurface(event) { + const eligible = new Set(["user/message", "assistant/message", "tool/result"]); + if ((Object.hasOwn(event, "sourceEventSeqs") || Object.hasOwn(event, "surfaceOp")) && !eligible.has(event.type)) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } + if (eligible.has(event.type) && !Object.hasOwn(event, "surfaceOp")) fail("DSH_EVENT_SHAPE_DRIFT"); + if (Object.hasOwn(event, "sourceEventSeqs") + && (!Array.isArray(event.sourceEventSeqs) + || new Set(event.sourceEventSeqs).size !== event.sourceEventSeqs.length + || event.sourceEventSeqs.some((seq) => !safeNonnegative(seq) || seq >= event.seq) + || (event.sourceEventSeqs.length === 0 && event.type !== "assistant/message"))) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } + if (Object.hasOwn(event, "surfaceOp")) { + const op = event.surfaceOp; + if (op !== "append" && !(exactKeys(op, ["op", "start", "end"]) && op.op === "replace" + && safeNonnegative(op.start) && safeNonnegative(op.end))) fail("DSH_EVENT_SHAPE_DRIFT"); + } +} + +function isDshJsonValueInner(value, seen) { + if (value === null || typeof value === "string" || typeof value === "boolean") return true; + if (typeof value === "number") return Number.isFinite(value); + if (typeof value !== "object" || seen.has(value)) return false; + seen.add(value); + let result; + if (Array.isArray(value)) result = value.every((item) => isDshJsonValueInner(item, seen)); + else { + const prototype = Object.getPrototypeOf(value); + result = (prototype === Object.prototype || prototype === null) + && Object.values(value).every((item) => isDshJsonValueInner(item, seen)); + } + seen.delete(value); + return result; +} + +export function isDshJsonValue(value) { + return isDshJsonValueInner(value, new Set()); +} + +function validateTokenUsage(usage) { + if (!exactKeys(usage, ["inputTokens", "outputTokens"], + ["cacheReadTokens", "cacheWriteTokens", "reasoningTokens"]) + || Object.values(usage).some((value) => typeof value !== "number")) fail("DSH_EVENT_SHAPE_DRIFT"); +} + +function validateLlmFailure(error) { + if (!exactKeys(error, ["message", "code"], ["status", "providerRetryAfterMs", "requestId"]) + || !nonemptyString(error.message) || !nonemptyString(error.code) + || (Object.hasOwn(error, "status") + && (!Number.isInteger(error.status) || error.status < 100 || error.status > 599)) + || (Object.hasOwn(error, "providerRetryAfterMs") + && (!Number.isFinite(error.providerRetryAfterMs) || error.providerRetryAfterMs <= 0)) + || (Object.hasOwn(error, "requestId") && !nonemptyString(error.requestId))) fail("DSH_EVENT_SHAPE_DRIFT"); +} + +function validateContentBlock(block) { + if (!plain(block)) fail("DSH_EVENT_SHAPE_DRIFT"); + switch (block.type) { + case "text": + case "reasoning": + if (!exactKeys(block, ["type", "text"]) || typeof block.text !== "string") fail("DSH_EVENT_SHAPE_DRIFT"); + break; + case "image": { + if (!exactKeys(block, ["type", "attachment"]) || !exactKeys(block.attachment, + ["attachmentId", "mediaType", "bytes", "width", "height"], ["name"])) fail("DSH_EVENT_SHAPE_DRIFT"); + const attachment = block.attachment; + if (typeof attachment.attachmentId !== "string" + || !["image/png", "image/jpeg", "image/webp", "image/gif"].includes(attachment.mediaType) + || [attachment.bytes, attachment.width, attachment.height].some((value) => typeof value !== "number") + || (Object.hasOwn(attachment, "name") && typeof attachment.name !== "string")) fail("DSH_EVENT_SHAPE_DRIFT"); + break; + } + case "tool-call": + if (!exactKeys(block, ["type", "id", "name", "arguments"]) + || [block.id, block.name, block.arguments].some((value) => typeof value !== "string")) fail("DSH_EVENT_SHAPE_DRIFT"); + break; + case "tool-result": + if (!exactKeys(block, ["type", "toolCallId", "content"], ["isError"]) + || typeof block.toolCallId !== "string" || !Array.isArray(block.content) + || (Object.hasOwn(block, "isError") && typeof block.isError !== "boolean")) fail("DSH_EVENT_SHAPE_DRIFT"); + for (const nested of block.content) validateContentBlock(nested); + break; + default: + fail("DSH_EVENT_SHAPE_DRIFT"); + } +} + +function validateMessage(message, role) { + if (!exactKeys(message, ["id", "role", "content", "source"]) + || message.role !== role || typeof message.id !== "string" + || !Array.isArray(message.content) || !plain(message.source)) fail("DSH_EVENT_SHAPE_DRIFT"); + for (const part of message.content) validateContentBlock(part); +} + +function validateUserSource(source) { + if (source.kind === "user") { + if (!exactKeys(source, ["kind"], ["rpcId", "clientTimeZone"]) + || (Object.hasOwn(source, "rpcId") && typeof source.rpcId !== "string") + || (Object.hasOwn(source, "clientTimeZone") && typeof source.clientTimeZone !== "string") + || (Object.hasOwn(source, "clientTimeZone") && !Object.hasOwn(source, "rpcId"))) fail("DSH_EVENT_SHAPE_DRIFT"); + return; + } + if (source.kind === "plugin") { + if (typeof source.plugin !== "string") fail("DSH_EVENT_SHAPE_DRIFT"); + if (source.plugin === "compact") { + if (!exactKeys(source, ["kind", "plugin", "compactionId"], ["sourceCommandId"]) + || !nonemptyString(source.compactionId) + || (Object.hasOwn(source, "sourceCommandId") && !nonemptyString(source.sourceCommandId))) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } + return; + } + if (!Object.hasOwn(source, "form")) { + if (!exactKeys(source, ["kind", "plugin"])) fail("DSH_EVENT_SHAPE_DRIFT"); + } else if (["instructions", "catalog", "relay", "recall"].includes(source.form)) { + if (!exactKeys(source, ["kind", "plugin", "form"])) fail("DSH_EVENT_SHAPE_DRIFT"); + } else if (source.form === "notice") { + if (!exactKeys(source, ["kind", "plugin", "form", "summary"]) || typeof source.summary !== "string") { + fail("DSH_EVENT_SHAPE_DRIFT"); + } + } else if (source.form === "snapshot") { + if (!exactKeys(source, ["kind", "plugin", "form", "sections"]) || !Array.isArray(source.sections) + || source.sections.some((section) => !exactKeys(section, ["name", "text"]) + || typeof section.name !== "string" || typeof section.text !== "string")) fail("DSH_EVENT_SHAPE_DRIFT"); + } else fail("DSH_EVENT_SHAPE_DRIFT"); + return; + } + if (source.kind === "goal") { + if (!exactKeys(source, ["kind", "goalId", "revision", "round"]) + || !nonemptyString(source.goalId) || !safePositive(source.revision) || !safePositive(source.round)) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } + return; + } + if (source.kind === "agent-instructions") { + if (!exactKeys(source, ["kind", "form", "changes"], ["baseline", "baselineIdentity"]) + || source.form !== "instructions" || !Array.isArray(source.changes) + || (Object.hasOwn(source, "baseline") && source.baseline !== true) + || (Object.hasOwn(source, "baselineIdentity") && typeof source.baselineIdentity !== "string")) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } + for (const change of source.changes) { + if (!exactKeys(change, ["action", "scope", "path"], ["digest"]) + || !["set", "replace", "remove"].includes(change.action) + || typeof change.scope !== "string" || typeof change.path !== "string" + || (Object.hasOwn(change, "digest") && typeof change.digest !== "string")) fail("DSH_EVENT_SHAPE_DRIFT"); + } + return; + } + if (source.kind === "session-reference") { + if (!exactKeys(source, ["kind", "form", "version", "references"]) + || source.form !== "recall" || source.version !== 1 || !Array.isArray(source.references)) fail("DSH_EVENT_SHAPE_DRIFT"); + for (const ref of source.references) { + if (!exactKeys(ref, ["sessionId", "label", "capturedThroughSeq", "compacted", "originalMessages", + "retainedMessages", "omittedMessages", "omittedBytes", "truncated", "inputIndex"]) + || typeof ref.sessionId !== "string" || typeof ref.label !== "string" + || !(ref.capturedThroughSeq === null || safeNonnegative(ref.capturedThroughSeq)) + || typeof ref.compacted !== "boolean" || typeof ref.truncated !== "boolean" + || [ref.originalMessages, ref.retainedMessages, ref.omittedMessages, ref.omittedBytes, ref.inputIndex] + .some((value) => !safeNonnegative(value))) fail("DSH_EVENT_SHAPE_DRIFT"); + } + return; + } + if (["coordinator", "subagent-report"].includes(source.kind)) { + if (!exactKeys(source, ["kind", "form", "senderSessionId"]) || source.form !== "relay" + || typeof source.senderSessionId !== "string") fail("DSH_EVENT_SHAPE_DRIFT"); + return; + } + if (source.kind === "subagent-settled") { + if (!exactKeys(source, ["kind", "form", "summary", "senderSessionId"]) || source.form !== "notice" + || typeof source.summary !== "string" || typeof source.senderSessionId !== "string") fail("DSH_EVENT_SHAPE_DRIFT"); + return; + } + if (source.kind === "skill-catalog") { + if (!exactKeys(source, ["kind", "form", "entries"], ["update"]) || source.form !== "catalog" + || (Object.hasOwn(source, "update") && source.update !== true) || !Array.isArray(source.entries) + || source.entries.some((entry) => !exactKeys(entry, ["name", "description"]) + || typeof entry.name !== "string" || typeof entry.description !== "string")) fail("DSH_EVENT_SHAPE_DRIFT"); + return; + } + if (source.kind === "skill-invocation") { + if (!exactKeys(source, ["kind", "name", "form"]) || source.form !== "instructions" + || typeof source.name !== "string") fail("DSH_EVENT_SHAPE_DRIFT"); + return; + } + fail("DSH_EVENT_SHAPE_DRIFT"); +} + +function validateAssistantSource(source) { + if (!exactKeys(source, ["kind", "provider", "model"], ["replayState"]) || source.kind !== "model" + || typeof source.provider !== "string" || typeof source.model !== "string" + || (Object.hasOwn(source, "replayState") && !isDshJsonValue(source.replayState))) fail("DSH_EVENT_SHAPE_DRIFT"); +} + +function validateToolResultMessage(message, eventData) { + validateMessage(message, "user"); + if (!exactKeys(message.source, ["kind", "callId"]) || message.source.kind !== "tool" + || typeof message.source.callId !== "string" || message.content.length !== 1) fail("DSH_EVENT_SHAPE_DRIFT"); + const result = message.content[0]; + if (!exactKeys(result, ["type", "toolCallId", "content"], ["isError"]) + || result.type !== "tool-result" || result.toolCallId !== message.source.callId + || !Array.isArray(result.content) + || (Object.hasOwn(result, "isError") && typeof result.isError !== "boolean")) fail("DSH_EVENT_SHAPE_DRIFT"); + if (Object.hasOwn(eventData, "error") && (!exactKeys(eventData.error, ["name", "code"]) + || typeof eventData.error.name !== "string" || typeof eventData.error.code !== "string")) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } +} + +function validateReplayEnvelope(value) { + if (!exactKeys(value, ["response"], ["blocks"]) || !isDshJsonValue(value.response) + || (Object.hasOwn(value, "blocks") && (!Array.isArray(value.blocks) || !isDshJsonValue(value.blocks)))) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } +} + +function validateStreamChunk(chunk) { + if (!plain(chunk)) fail("DSH_EVENT_SHAPE_DRIFT"); + switch (chunk.type) { + case "block-start": + if (!exactKeys(chunk, ["type", "index", "blockType"]) || typeof chunk.index !== "number" + || !["text", "reasoning", "image", "tool-call", "tool-result"].includes(chunk.blockType)) fail("DSH_EVENT_SHAPE_DRIFT"); + break; + case "text-delta": + case "reasoning-delta": + if (!exactKeys(chunk, ["type", "index", "text"]) || typeof chunk.index !== "number" + || typeof chunk.text !== "string") fail("DSH_EVENT_SHAPE_DRIFT"); + break; + case "tool-call-delta": + if (!exactKeys(chunk, ["type", "index", "id", "argumentsDelta"], ["name"]) + || typeof chunk.index !== "number" || typeof chunk.id !== "string" + || typeof chunk.argumentsDelta !== "string" + || (Object.hasOwn(chunk, "name") && typeof chunk.name !== "string")) fail("DSH_EVENT_SHAPE_DRIFT"); + break; + case "block-end": + if (!exactKeys(chunk, ["type", "index", "block"]) || typeof chunk.index !== "number") fail("DSH_EVENT_SHAPE_DRIFT"); + validateContentBlock(chunk.block); + break; + case "usage": + if (!exactKeys(chunk, ["type", "usage"])) fail("DSH_EVENT_SHAPE_DRIFT"); + validateTokenUsage(chunk.usage); + break; + case "finish": { + if (!exactKeys(chunk, ["type", "reason"], ["replayState"]) || !plain(chunk.reason)) fail("DSH_EVENT_SHAPE_DRIFT"); + if (["stop", "tool-calls", "max-tokens"].includes(chunk.reason.kind)) { + if (!exactKeys(chunk.reason, ["kind"])) fail("DSH_EVENT_SHAPE_DRIFT"); + } else if (["aborted", "error"].includes(chunk.reason.kind)) { + if (!exactKeys(chunk.reason, ["kind", "failure"])) fail("DSH_EVENT_SHAPE_DRIFT"); + validateLlmFailure(chunk.reason.failure); + } else fail("DSH_EVENT_SHAPE_DRIFT"); + if (Object.hasOwn(chunk, "replayState")) validateReplayEnvelope(chunk.replayState); + break; + } + default: + fail("DSH_EVENT_SHAPE_DRIFT"); + } +} + +function validateSupportedEvent(event) { + const data = event.data; + switch (event.type) { + case "turn/start": + if (!exactKeys(data, ["turn"]) || !safePositive(data.turn)) fail("DSH_EVENT_SHAPE_DRIFT"); + break; + case "step/start": + case "step/end": + if (!exactKeys(data, ["turn", "step"]) || !safePositive(data.turn) || !safePositive(data.step)) fail("DSH_EVENT_SHAPE_DRIFT"); + break; + case "turn/end": { + if (!exactKeys(data, ["turn", "reason"]) || !safePositive(data.turn) || !plain(data.reason)) fail("DSH_EVENT_SHAPE_DRIFT"); + const kind = data.reason.kind; + if (!["completed", "aborted", "blocked", "error", "max-tokens", "interrupted"].includes(kind)) fail("DSH_EVENT_SHAPE_DRIFT"); + if (kind === "aborted") { + if (!exactKeys(data.reason, ["kind", "reason"]) || !plain(data.reason.reason)) fail("DSH_EVENT_SHAPE_DRIFT"); + const abort = data.reason.reason; + if (abort.kind === "hook") { + if (!exactKeys(abort, ["kind", "reason"]) || typeof abort.reason !== "string") fail("DSH_EVENT_SHAPE_DRIFT"); + } else if (!["user", "parent", "disposed", "legacy"].includes(abort.kind) + || !exactKeys(abort, ["kind"])) fail("DSH_EVENT_SHAPE_DRIFT"); + } else if (kind === "error") { + if (!exactKeys(data.reason, ["kind", "error"])) fail("DSH_EVENT_SHAPE_DRIFT"); + validateLlmFailure(data.reason.error); + } else if (!exactKeys(data.reason, ["kind"])) fail("DSH_EVENT_SHAPE_DRIFT"); + break; + } + case "user/message": + validateMessage(data, "user"); + validateUserSource(data.source); + break; + case "assistant/message": + if (!exactKeys(data, ["turn", "step", "message"], ["usage"]) || !safePositive(data.turn) + || !safePositive(data.step)) fail("DSH_EVENT_SHAPE_DRIFT"); + validateMessage(data.message, "assistant"); + validateAssistantSource(data.message.source); + if (Object.hasOwn(data, "usage")) validateTokenUsage(data.usage); + break; + case "assistant/chunk": + if (!exactKeys(data, ["turn", "step", "chunk"]) || !safePositive(data.turn) + || !safePositive(data.step) || !plain(data.chunk)) fail("DSH_EVENT_SHAPE_DRIFT"); + validateStreamChunk(data.chunk); + break; + case "tool/call": + if (!exactKeys(data, ["turn", "step", "callId", "name", "arguments"]) + || !safePositive(data.turn) || !safePositive(data.step) + || [data.callId, data.name, data.arguments].some((value) => typeof value !== "string")) fail("DSH_EVENT_SHAPE_DRIFT"); + break; + case "tool/result": + if (!exactKeys(data, ["turn", "step", "message"], ["error", "meta"]) || !safePositive(data.turn) + || !safePositive(data.step)) fail("DSH_EVENT_SHAPE_DRIFT"); + validateToolResultMessage(data.message, data); + if (Object.hasOwn(data, "meta") && !isDshJsonValue(data.meta)) fail("DSH_EVENT_SHAPE_DRIFT"); + break; + default: + break; + } +} + +// Pinned core payloads that are required for replay but intentionally omitted from BH normalization. +function validateKnownUnsupportedEvent(event) { + const data = event.data; + switch (event.type) { + case "permission/preset": + if (!exactKeys(data, ["preset"]) || typeof data.preset !== "string") fail("DSH_EVENT_SHAPE_DRIFT"); + break; + case "sandbox/mode": + if (!exactKeys(data, ["mode"], ["source"]) + || !["read-only", "workspace-write", "danger-full-access"].includes(data.mode) + || (Object.hasOwn(data, "source") && data.source !== "delegation")) fail("DSH_EVENT_SHAPE_DRIFT"); + break; + case "approval/policy": + if (!exactKeys(data, ["policy"], ["source"]) || !["ask", "never"].includes(data.policy) + || (Object.hasOwn(data, "source") && data.source !== "delegation")) fail("DSH_EVENT_SHAPE_DRIFT"); + break; + case "agent/inbox/spliced": + validateInboxSplice(data); + break; + case "session/title": + validateSessionTitle(data); + break; + case "session/title-llm-request": + validateSessionTitleRequest(data); + break; + case "subagent/descriptor": + validateSubagentDescriptor(data); + break; + case "agent-preset/selected": + if (!exactKeys(data, ["agentPreset"]) || typeof data.agentPreset !== "string") fail("DSH_EVENT_SHAPE_DRIFT"); + break; + case "approval/asked": + if (!exactKeys(data, ["id", "toolName"], ["callId", "reason"]) + || typeof data.id !== "string" || !nonemptyString(data.toolName) + || (Object.hasOwn(data, "callId") && typeof data.callId !== "string") + || (Object.hasOwn(data, "reason") && typeof data.reason !== "string")) fail("DSH_EVENT_SHAPE_DRIFT"); + break; + case "approval/decided": + if (!exactKeys(data, ["id", "outcome"]) || typeof data.id !== "string" + || !["allowed-once", "rejected", "cancelled", "unavailable"].includes(data.outcome)) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } + break; + case "command/run": + validateCommandRun(data); + break; + case "command/done": + validateCommandDone(data); + break; + case "compaction/start": + case "compaction/end": + case "compaction/prune": + case "compaction/summary": + validateCompactionEvent(event.type, data); + break; + case "feedback/record": + if (!exactKeys(data, ["text"]) || typeof data.text !== "string") fail("DSH_EVENT_SHAPE_DRIFT"); + break; + case "goal/change": + validateGoalChange(data); + break; + case "hook/invoked": + case "hook/result": + validateHookEvent(event.type, data); + break; + case "llm/retry": + case "llm/retry-started": + validateRetryEvent(event.type, data); + break; + case "plan/mode": + if (!exactKeys(data, ["active"]) || typeof data.active !== "boolean") fail("DSH_EVENT_SHAPE_DRIFT"); + break; + case "tool-workflow/run-start": + case "tool-workflow/agent-start": + case "tool-workflow/agent-end": + case "tool-workflow/run-end": + validateWorkflowEvent(event.type, data); + break; + case "tool/code-dispatch-start": + case "tool/code-dispatch": + validateCodeDispatchEvent(event.type, data); + break; + case "schedule/change": + validateScheduleChange(data); + break; + case "web/deepseek-search-llm-request": + validateDeepSeekSearchRequest(data); + break; + case "todo/write": + if (!exactKeys(data, ["todos"]) || !Array.isArray(data.todos) + || data.todos.some((todo) => !exactKeys(todo, ["content", "status"]) + || !nonemptyString(todo.content) || todo.content.trim() !== todo.content + || !["pending", "in_progress", "completed"].includes(todo.status))) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } + if (new Set(data.todos.map((todo) => todo.content)).size !== data.todos.length) fail("DSH_EVENT_SHAPE_DRIFT"); + break; + case "request/context": + if (!exactKeys(data, ["provider", "model"], ["contextWindow"]) + || !nonemptyString(data.provider) || !nonemptyString(data.model) + || (Object.hasOwn(data, "contextWindow") + && !safePositive(data.contextWindow))) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } + break; + case "request/header": + validateRequestHeader(data); + break; + case "session/end-seed": + if (!exactKeys(data, [])) fail("DSH_EVENT_SHAPE_DRIFT"); + break; + default: + fail("DSH_KNOWN_EVENT_UNVALIDATED"); + } +} + +function validateLlmCallConfig(config) { + if (!exactKeys(config, ["provider", "model"], + ["reasoningEffort", "temperature", "maxTokens", "stop"]) + || !nonemptyString(config.provider) || !nonemptyString(config.model) + || (Object.hasOwn(config, "reasoningEffort") && !nonemptyString(config.reasoningEffort)) + || (Object.hasOwn(config, "temperature") && !Number.isFinite(config.temperature)) + || (Object.hasOwn(config, "maxTokens") && !Number.isFinite(config.maxTokens)) + || (Object.hasOwn(config, "stop") + && (!Array.isArray(config.stop) || config.stop.some((value) => typeof value !== "string")))) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } +} + +function validateAdapterDefaults(defaults) { + if (!exactKeys(defaults, [], ["reasoningEffort", "maxTokens"]) + || Object.values(defaults).some((value) => value !== true)) fail("DSH_EVENT_SHAPE_DRIFT"); +} + +function validateToolSchema(tool) { + if (!exactKeys(tool, ["name", "description", "parameters"]) + || typeof tool.name !== "string" || typeof tool.description !== "string" + || !plain(tool.parameters) || !isDshJsonValue(tool.parameters)) fail("DSH_EVENT_SHAPE_DRIFT"); +} + +function validateRequestHeader(data) { + if (!exactKeys(data, ["header", "reason"]) + || !["initial", "resume", "change"].includes(data.reason) + || !exactKeys(data.header, ["config"], ["adapterDefaults", "system", "tools"])) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } + validateLlmCallConfig(data.header.config); + if (Object.hasOwn(data.header, "adapterDefaults")) { + validateAdapterDefaults(data.header.adapterDefaults); + if (Object.hasOwn(data.header.adapterDefaults, "reasoningEffort") + && !Object.hasOwn(data.header.config, "reasoningEffort")) fail("DSH_EVENT_SHAPE_DRIFT"); + if (Object.hasOwn(data.header.adapterDefaults, "maxTokens") + && !Object.hasOwn(data.header.config, "maxTokens")) fail("DSH_EVENT_SHAPE_DRIFT"); + } + if (Object.hasOwn(data.header, "system") && typeof data.header.system !== "string") { + fail("DSH_EVENT_SHAPE_DRIFT"); + } + if (Object.hasOwn(data.header, "tools")) { + if (!Array.isArray(data.header.tools)) fail("DSH_EVENT_SHAPE_DRIFT"); + for (const tool of data.header.tools) validateToolSchema(tool); + } +} + +function nonemptyString(value) { + return typeof value === "string" && value.length > 0; +} + +function validateUserMessageValue(message) { + validateMessage(message, "user"); + validateUserSource(message.source); +} + +function validateInboxSplice(data) { + if (!exactKeys(data, ["target", "start", "inserted"], ["removedCount", "outcome"]) + || !["next-turn", "next-step"].includes(data.target) || !safeNonnegative(data.start) + || !Array.isArray(data.inserted) + || (Object.hasOwn(data, "removedCount") && !safeNonnegative(data.removedCount)) + || (Object.hasOwn(data, "outcome") && data.outcome !== "canceled")) fail("DSH_EVENT_SHAPE_DRIFT"); + for (const message of data.inserted) validateUserMessageValue(message); +} + +function validateSessionTitle(data) { + if (!exactKeys(data, ["title", "messageSeqs", "source"]) || !nonemptyString(data.title) + || !Array.isArray(data.messageSeqs) || data.messageSeqs.some((seq) => !safeNonnegative(seq)) + || !strictlyIncreasing(data.messageSeqs) || !plain(data.source)) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } + if (["fallback", "user"].includes(data.source.kind)) { + if (!exactKeys(data.source, ["kind"])) fail("DSH_EVENT_SHAPE_DRIFT"); + } else if (data.source.kind === "provider") { + if (!exactKeys(data.source, ["kind", "provider"], ["model"]) || !nonemptyString(data.source.provider)) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } + if (Object.hasOwn(data.source, "model") && (!exactKeys(data.source.model, ["provider", "model"]) + || !nonemptyString(data.source.model.provider) || !nonemptyString(data.source.model.model))) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } + } else fail("DSH_EVENT_SHAPE_DRIFT"); +} + +function validateAnyMessage(message) { + if (!plain(message)) fail("DSH_EVENT_SHAPE_DRIFT"); + if (message.role === "assistant") { + validateMessage(message, "assistant"); + validateAssistantSource(message.source); + return; + } + if (message.role !== "user") fail("DSH_EVENT_SHAPE_DRIFT"); + if (message.source?.kind === "tool") { + validateToolResultMessage(message, {}); + return; + } + validateUserMessageValue(message); +} + +function validateSessionTitleRequest(data) { + if (!exactKeys(data, ["titleProvider", "messageSeqs", "route", "system", "messages", "maxTokens"]) + || !nonemptyString(data.titleProvider) || !Array.isArray(data.messageSeqs) || data.messageSeqs.length === 0 + || data.messageSeqs.some((seq) => !safeNonnegative(seq)) + || !strictlyIncreasing(data.messageSeqs) + || !exactKeys(data.route, ["provider", "model"]) || !nonemptyString(data.route.provider) + || !nonemptyString(data.route.model) || typeof data.system !== "string" || !Array.isArray(data.messages) + || !safePositive(data.maxTokens)) fail("DSH_EVENT_SHAPE_DRIFT"); + for (const message of data.messages) validateAnyMessage(message); +} + +function validateSubagentDescriptor(data) { + if (!plain(data) || data.version !== 2 || !["one-shot", "continuable"].includes(data.mode) + || typeof data.provider !== "string") fail("DSH_EVENT_SHAPE_DRIFT"); + const optionalStrings = ["label", "agentProvider", "agentModel", "persona"]; + if (optionalStrings.some((key) => Object.hasOwn(data, key) && typeof data[key] !== "string")) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } + if (data.mode === "one-shot") { + if (!exactKeys(data, ["version", "mode", "provider"], ["label"])) fail("DSH_EVENT_SHAPE_DRIFT"); + return; + } + if (!exactKeys(data, ["version", "mode", "provider", "label"], + ["agentProvider", "agentModel", "persona", "toolFilter"])) fail("DSH_EVENT_SHAPE_DRIFT"); + if (Object.hasOwn(data, "toolFilter")) { + const filter = data.toolFilter; + if (!exactKeys(filter, [], ["allow", "deny"]) + || (!Object.hasOwn(filter, "allow") && !Object.hasOwn(filter, "deny"))) fail("DSH_EVENT_SHAPE_DRIFT"); + for (const key of ["allow", "deny"]) { + if (Object.hasOwn(filter, key) + && (!Array.isArray(filter[key]) || filter[key].some((value) => typeof value !== "string"))) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } + } + } +} + +function validateCommandRun(data) { + if (!exactKeys(data, ["commandId", "name", "source"], ["args"]) + || !nonemptyString(data.commandId) || !nonemptyString(data.name) + || !exactKeys(data.source, ["kind"]) || data.source.kind !== "user" + || (Object.hasOwn(data, "args") && typeof data.args !== "string")) fail("DSH_EVENT_SHAPE_DRIFT"); +} + +function validateCommandDone(data) { + if (!exactKeys(data, ["commandId", "kind"], ["text", "sourceEventSeq"]) + || typeof data.commandId !== "string" || !["success", "error"].includes(data.kind) + || (Object.hasOwn(data, "text") && typeof data.text !== "string") + || (Object.hasOwn(data, "sourceEventSeq") + && (data.kind !== "success" || !safeNonnegative(data.sourceEventSeq)))) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } +} + +function validateSeqList(value) { + return Array.isArray(value) && value.every(safeNonnegative) && new Set(value).size === value.length; +} + +function strictlyIncreasing(values) { + return values.every((value, index) => index === 0 || value > values[index - 1]); +} + +function validateShadow(data) { + return exactKeys(data.shadowedRange, ["start", "end"]) && safeNonnegative(data.shadowedRange.start) + && safeNonnegative(data.shadowedRange.end) && validateSeqList(data.shadowedSeqs) + && safeNonnegative(data.shadowedTokenCount); +} + +function validateCompactionEvent(type, data) { + if (type === "compaction/prune") { + if (!exactKeys(data, ["shadowedRange", "shadowedSeqs", "shadowedTokenCount"]) || !validateShadow(data)) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } + return; + } + if (["compaction/start", "compaction/end"].includes(type)) { + const optional = type === "compaction/end" ? ["sourceCommandId", "error"] : ["sourceCommandId"]; + if (!exactKeys(data, ["compactionId", "turn"], optional) || !nonemptyString(data.compactionId) + || !(data.turn === null || safePositive(data.turn)) + || (Object.hasOwn(data, "sourceCommandId") && !nonemptyString(data.sourceCommandId)) + || (Object.hasOwn(data, "error") && typeof data.error !== "string")) fail("DSH_EVENT_SHAPE_DRIFT"); + return; + } + if (!exactKeys(data, ["compactionId", "summary", "shadowedRange", "shadowedSeqs", + "shadowedTokenCount", "provider", "model"], + ["sourceCommandId", "maxTokens", "usage", "rawOutput", "llmStreamCall"]) || !nonemptyString(data.compactionId) + || !Array.isArray(data.summary) || !validateShadow(data) || typeof data.provider !== "string" + || typeof data.model !== "string" + || (Object.hasOwn(data, "sourceCommandId") && !nonemptyString(data.sourceCommandId))) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } + for (const block of data.summary) validateContentBlock(block); + if (Object.hasOwn(data, "rawOutput")) { + if (!Array.isArray(data.rawOutput)) fail("DSH_EVENT_SHAPE_DRIFT"); + for (const block of data.rawOutput) validateContentBlock(block); + } + if (Object.hasOwn(data, "llmStreamCall") && data.llmStreamCall !== true) fail("DSH_EVENT_SHAPE_DRIFT"); + if (data.llmStreamCall === true && !Object.hasOwn(data, "rawOutput")) fail("DSH_EVENT_SHAPE_DRIFT"); + if (Object.hasOwn(data, "maxTokens") && !Number.isFinite(data.maxTokens)) fail("DSH_EVENT_SHAPE_DRIFT"); + if (Object.hasOwn(data, "usage")) validateTokenUsage(data.usage); +} + +function validateGoalRef(value) { + return plain(value) && nonemptyString(value.id) && safePositive(value.revision); +} + +function validateGoalChange(data) { + if (!plain(data) || data.kind !== "goal/change" || data.version !== 1) fail("DSH_EVENT_SHAPE_DRIFT"); + if (data.operation === "clear") { + if (!exactKeys(data, ["kind", "version", "operation", "cleared", "clearedAt"]) + || !exactKeys(data.cleared, ["id", "revision"]) || !validateGoalRef(data.cleared) + || !safeNonnegative(data.clearedAt)) fail("DSH_EVENT_SHAPE_DRIFT"); + return; + } + if (!["create", "edit", "pause", "resume", "complete", "block"].includes(data.operation) + || !exactKeys(data, ["kind", "version", "operation", "goal", "roundsStarted", "createdAt", "updatedAt"]) + || !validateGoalRef(data.goal) + || !exactKeys(data.goal, ["id", "revision", "objective", "phase", "maxGoalRounds"], ["blockedReason"]) + || !nonemptyString(data.goal.id) || !nonemptyString(data.goal.objective) + || data.goal.objective.trim() !== data.goal.objective + || !["active", "paused", "blocked", "complete"].includes(data.goal.phase) + || !safePositive(data.goal.maxGoalRounds) || !safeNonnegative(data.roundsStarted) + || !safeNonnegative(data.createdAt) || !safeNonnegative(data.updatedAt) + || data.updatedAt < data.createdAt) fail("DSH_EVENT_SHAPE_DRIFT"); + const hasBlock = Object.hasOwn(data.goal, "blockedReason"); + if (hasBlock !== (data.goal.phase === "blocked")) fail("DSH_EVENT_SHAPE_DRIFT"); + if (hasBlock && (!exactKeys(data.goal.blockedReason, ["code", "message"]) + || !/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(data.goal.blockedReason.code) + || !nonemptyString(data.goal.blockedReason.message) + || data.goal.blockedReason.message.trim() !== data.goal.blockedReason.message)) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } +} + +function validateHookEvent(type, data) { + if (type === "hook/invoked") { + if (!exactKeys(data, ["turn", "point", "dialect", "handlerId"], ["matcher"]) + || !safePositive(data.turn) || !nonemptyString(data.point) + || !["claude-code", "codex"].includes(data.dialect) || !nonemptyString(data.handlerId) + || (Object.hasOwn(data, "matcher") && typeof data.matcher !== "string")) fail("DSH_EVENT_SHAPE_DRIFT"); + return; + } + if (!exactKeys(data, ["turn", "point", "handlerId", "decision", "durationMs"], + ["exitCode", "stderrSummary"]) || !safePositive(data.turn) || !nonemptyString(data.point) + || !nonemptyString(data.handlerId) || typeof data.decision !== "string" + || !Number.isFinite(data.durationMs) || data.durationMs < 0 + || (Object.hasOwn(data, "exitCode") && !Number.isFinite(data.exitCode)) + || (Object.hasOwn(data, "stderrSummary") && typeof data.stderrSummary !== "string")) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } +} + +function validateRetryEvent(type, data) { + if (type === "llm/retry-started") { + if (!exactKeys(data, ["retryId", "turn", "step", "retry"]) || !nonemptyString(data.retryId) + || !safePositive(data.turn) || !safePositive(data.step) || !safePositive(data.retry)) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } + return; + } + const required = ["retryId", "turn", "step", "provider", "mode", "policyKey", "retry", "delayMs", "failure"]; + const optional = data.mode === "normal" ? [] : []; + if (data.mode === "normal") required.push("maxRetries"); + if (!exactKeys(data, required, optional) || !nonemptyString(data.retryId) + || !safePositive(data.turn) || !safePositive(data.step) || !nonemptyString(data.provider) + || !["normal", "always"].includes(data.mode) || !nonemptyString(data.policyKey) + || !safePositive(data.retry) || !Number.isFinite(data.delayMs) || data.delayMs < 0 + || data.delayMs > MAX_TIMER_DELAY_MS + || (data.mode === "normal" && (!safePositive(data.maxRetries) || data.retry > data.maxRetries))) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } + validateLlmFailure(data.failure); +} + +function validateWorkflowEvent(type, data) { + if (type === "tool-workflow/run-start") { + if (!exactKeys(data, ["runId", "name"]) || !nonemptyString(data.runId) || !nonemptyString(data.name)) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } + return; + } + if (type === "tool-workflow/agent-start") { + if (!exactKeys(data, ["runId", "seq", "label", "childId"], ["phase"]) + || !nonemptyString(data.runId) || !safePositive(data.seq) || typeof data.label !== "string" + || !nonemptyString(data.childId) || (Object.hasOwn(data, "phase") && typeof data.phase !== "string")) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } + return; + } + if (type === "tool-workflow/agent-end") { + if (!exactKeys(data, ["runId", "seq", "outcome"]) || !nonemptyString(data.runId) + || !safePositive(data.seq) || !["completed", "failed", "cancelled"].includes(data.outcome)) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } + return; + } + if (!exactKeys(data, ["runId", "stopReason"]) || !nonemptyString(data.runId) + || !["completed", "cancelled", "error"].includes(data.stopReason)) fail("DSH_EVENT_SHAPE_DRIFT"); +} + +function validateCodeDispatchEvent(type, data) { + const required = ["rootCallId", "parentCallId", "subCallId", "name", "arguments"]; + if (type === "tool/code-dispatch") required.push("isError", "content"); + if (!exactKeys(data, required) || [data.rootCallId, data.parentCallId, data.subCallId, data.name] + .some((value) => !nonemptyString(value)) || !isDshJsonValue(data.arguments)) fail("DSH_EVENT_SHAPE_DRIFT"); + if (type === "tool/code-dispatch") { + if (typeof data.isError !== "boolean" || !Array.isArray(data.content)) fail("DSH_EVENT_SHAPE_DRIFT"); + for (const block of data.content) validateContentBlock(block); + } +} + +function validateScheduleId(value) { + return nonemptyString(value) && value.trim() === value; +} + +function validateScheduleInstant(value) { + return typeof value === "string" && /^(?!0000)\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d\.\d{3}Z$/u.test(value) + && Number.isFinite(Date.parse(value)) && new Date(Date.parse(value)).toISOString() === value; +} + +function validateScheduleRecord(record) { + const common = plain(record) && validateScheduleId(record.id) && typeof record.prompt === "string" + && record.prompt.length > 0 && record.prompt.trim() === record.prompt && validateScheduleInstant(record.scheduledAt); + if (!common) fail("DSH_EVENT_SHAPE_DRIFT"); + if (record.kind === "after") { + if (!exactKeys(record, ["id", "kind", "prompt", "afterSeconds", "scheduledAt"]) + || !safePositive(record.afterSeconds)) fail("DSH_EVENT_SHAPE_DRIFT"); + return; + } + if (record.kind === "at") { + if (!exactKeys(record, ["id", "kind", "prompt", "scheduledAt"])) fail("DSH_EVENT_SHAPE_DRIFT"); + return; + } + if (record.kind === "every") { + if (!exactKeys(record, ["id", "kind", "prompt", "everySeconds", "scheduledAt"]) + || !safePositive(record.everySeconds) || record.everySeconds < 300 + || !Number.isSafeInteger(record.everySeconds * 1_000)) fail("DSH_EVENT_SHAPE_DRIFT"); + return; + } + fail("DSH_EVENT_SHAPE_DRIFT"); +} + +function validateScheduleChange(data) { + if (!plain(data) || data.version !== 1) fail("DSH_EVENT_SHAPE_DRIFT"); + if (data.operation === "create") { + if (!exactKeys(data, ["version", "operation", "schedule"])) fail("DSH_EVENT_SHAPE_DRIFT"); + validateScheduleRecord(data.schedule); + return; + } + if (data.operation === "delete") { + if (!exactKeys(data, ["version", "operation", "id"]) || !validateScheduleId(data.id)) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } + return; + } + if (data.operation === "dispatch") { + if (!exactKeys(data, ["version", "operation", "id"], ["acceptedAt"]) + || !validateScheduleId(data.id) + || (Object.hasOwn(data, "acceptedAt") && !validateScheduleInstant(data.acceptedAt))) { + fail("DSH_EVENT_SHAPE_DRIFT"); + } + return; + } + fail("DSH_EVENT_SHAPE_DRIFT"); +} + +function validateDeepSeekSearchRequest(data) { + if (!exactKeys(data, ["endpoint", "apiVersion", "body"]) || !nonemptyString(data.endpoint) + || !nonemptyString(data.apiVersion) || !exactKeys(data.body, ["model", "max_tokens", "messages", "tools"]) + || !nonemptyString(data.body.model) || !safePositive(data.body.max_tokens) + || !Array.isArray(data.body.messages) || data.body.messages.length !== 1 + || !exactKeys(data.body.messages[0], ["role", "content"]) || data.body.messages[0].role !== "user" + || !Array.isArray(data.body.messages[0].content) || data.body.messages[0].content.length !== 1 + || !exactKeys(data.body.messages[0].content[0], ["type", "text"]) + || data.body.messages[0].content[0].type !== "text" + || typeof data.body.messages[0].content[0].text !== "string" + || !Array.isArray(data.body.tools) || data.body.tools.length !== 1 + || !exactKeys(data.body.tools[0], ["type", "name", "max_uses"]) + || data.body.tools[0].type !== "web_search_20250305" || data.body.tools[0].name !== "web_search" + || !safePositive(data.body.tools[0].max_uses)) fail("DSH_EVENT_SHAPE_DRIFT"); +} + +function validateEventEnvelope(event, expectedSeq) { + if (!plain(event) || Object.keys(event).some((key) => !EVENT_KEYS.has(key)) + || typeof event.type !== "string" || event.type.length === 0 || event.seq !== expectedSeq + || !safeNonnegative(event.seq) || !validDshEpochMillis(event.time) || !plain(event.data) + || (Object.hasOwn(event, "ignorable") && event.ignorable !== true)) fail("DSH_INVALID_EVENT"); + validateSurface(event); + if (!KNOWN_EVENT_TYPES.has(event.type)) { + if (event.ignorable !== true) fail("DSH_UNKNOWN_REQUIRED_EVENT"); + return; + } + if (VALIDATED_TYPES.has(event.type)) { + validateSupportedEvent(event); + return; + } + if (VALIDATED_KNOWN_UNSUPPORTED_TYPES.has(event.type)) { + validateKnownUnsupportedEvent(event); + return; + } + // A known event remains part of the pinned replay contract even if a producer marks it ignorable. + fail("DSH_KNOWN_EVENT_UNVALIDATED"); +} + +function deepJsonEqual(left, right) { + if (left === right) return true; + if (Array.isArray(left) || Array.isArray(right)) { + return Array.isArray(left) && Array.isArray(right) && left.length === right.length + && left.every((value, index) => deepJsonEqual(value, right[index])); + } + if (!plain(left) || !plain(right)) return false; + const keys = Object.keys(left); + return keys.length === Object.keys(right).length + && keys.every((key) => Object.hasOwn(right, key) && deepJsonEqual(left[key], right[key])); +} + +function validateToolResultRewrite(event, shadowedSeqs, events) { + if (event.type !== "tool/result") return; + if (shadowedSeqs.length !== 1) fail("DSH_SURFACE_TOOL_REWRITE_INVALID"); + const original = events[shadowedSeqs[0]]; + if (original?.type !== "tool/result") fail("DSH_SURFACE_TOOL_REWRITE_INVALID"); + const originalRest = structuredClone(original.data); + const replacementRest = structuredClone(event.data); + originalRest.message.content[0].content = null; + replacementRest.message.content[0].content = null; + if (!deepJsonEqual(originalRest, replacementRest)) fail("DSH_SURFACE_TOOL_REWRITE_INVALID"); +} + +function applySurfaceEvent(event, nodes, events) { + if (!new Set(["user/message", "assistant/message", "tool/result"]).has(event.type)) return; + if (event.surfaceOp === "append") { + nodes.push(event.seq); + return; + } + const startIndex = nodes.indexOf(event.surfaceOp.start); + const endIndex = nodes.indexOf(event.surfaceOp.end); + if (startIndex === -1 || endIndex === -1 || startIndex > endIndex) fail("DSH_SURFACE_RANGE_INVALID"); + const shadowedSeqs = nodes.slice(startIndex, endIndex + 1); + const sources = new Set(event.sourceEventSeqs ?? []); + if (shadowedSeqs.some((seq) => !sources.has(seq))) fail("DSH_SURFACE_PROVENANCE_INCOMPLETE"); + validateToolResultRewrite(event, shadowedSeqs, events); + nodes.splice(startIndex, endIndex - startIndex + 1, event.seq); +} + +function applyInboxSplice(data, queues) { + const queue = queues[data.target]; + const removedCount = data.removedCount ?? 0; + if (data.start > queue.length || data.start + removedCount > queue.length + || (data.outcome === "canceled" && removedCount === 0)) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + const candidate = queue.toSpliced(data.start, removedCount, ...structuredClone(data.inserted)); + const ids = [...(data.target === "next-turn" ? candidate : queues["next-turn"]), + ...(data.target === "next-step" ? candidate : queues["next-step"])].map((message) => message.id); + if (new Set(ids).size !== ids.length) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + queues[data.target] = candidate; +} + +function applyScheduleChange(data, state) { + if (data.operation === "create") { + if (state.seen.has(data.schedule.id)) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + state.seen.add(data.schedule.id); + state.active.set(data.schedule.id, structuredClone(data.schedule)); + return; + } + const current = state.active.get(data.id); + if (current === undefined) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + if (data.operation === "delete") { + state.active.delete(data.id); + return; + } + const hasAcceptedAt = Object.hasOwn(data, "acceptedAt"); + if ((current.kind === "every") !== hasAcceptedAt) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + if (current.kind !== "every") { + state.active.delete(data.id); + return; + } + const accepted = Date.parse(data.acceptedAt); + const target = Date.parse(current.scheduledAt); + if (accepted < target) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + const interval = current.everySeconds * 1_000; + const occurrence = target + Math.floor((accepted - target) / interval) * interval; + const next = occurrence + interval; + const nextDate = new Date(next); + if (!Number.isSafeInteger(next) || !Number.isFinite(nextDate.getTime()) || nextDate.getUTCFullYear() > 9999) { + state.active.delete(data.id); + } else state.active.set(data.id, { ...current, scheduledAt: nextDate.toISOString() }); +} + +function validateTitleReferences(data, eventSeq, directHumanSeqs) { + if (data.messageSeqs.some((seq) => seq >= eventSeq || !directHumanSeqs.includes(seq))) { + fail("DSH_EVENT_RELATIONSHIP_INVALID"); + } + if ((data.source.kind === "user") !== (data.messageSeqs.length === 0)) { + fail("DSH_EVENT_RELATIONSHIP_INVALID"); + } + if (data.source.kind === "fallback" + && (data.messageSeqs.length !== 1 || data.messageSeqs[0] !== directHumanSeqs[0])) { + fail("DSH_EVENT_RELATIONSHIP_INVALID"); + } +} + +function validateTitleRequestReferences(data, eventSeq, directHumanSeqs) { + if (data.messageSeqs.some((seq) => seq >= eventSeq || !directHumanSeqs.includes(seq))) { + fail("DSH_EVENT_RELATIONSHIP_INVALID"); + } +} + +function renderGoalRoundContent(goal, round) { + return [{ + type: "text", + text: "\n" + + `Objective: ${JSON.stringify(goal.objective)}\n` + + `Round: ${round}/${goal.maxGoalRounds}\n\n` + + "Continue working toward the objective in this same session. Treat the current workspace, " + + "tool results, and durable session state as authoritative; inspect them instead of assuming " + + "earlier narration is still current. Make concrete progress and verify the result. Before " + + "claiming completion, gather evidence that the whole objective is achieved, read the current " + + "goal, and mark it complete. If work remains, leave the goal active for the next round. Follow " + + "the configured goal-tool policy before reporting a blocker.\n" + + "", + }]; +} + +function sameGoalDefinition(left, right) { + return left.objective === right.objective && left.maxGoalRounds === right.maxGoalRounds; +} + +function applyGoalFold(state, event) { + if (event.type === "user/message" && event.data.source.kind === "goal") { + const source = event.data.source; + const goal = state.goal; + if (goal === null || goal.phase !== "active" || source.goalId !== goal.id + || source.revision !== goal.revision || source.round !== state.roundsStarted + 1 + || source.round > goal.maxGoalRounds + || !deepJsonEqual(event.data.content, renderGoalRoundContent(goal, source.round))) { + fail("DSH_EVENT_RELATIONSHIP_INVALID"); + } + state.roundsStarted = source.round; + return; + } + if (event.type !== "goal/change") return; + const change = event.data; + if (change.operation === "clear") { + if (state.goal === null || change.cleared.id !== state.goal.id + || change.cleared.revision !== state.goal.revision + 1 + || change.clearedAt < state.updatedAt) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + state.goal = null; + state.roundsStarted = 0; + state.createdAt = null; + state.updatedAt = null; + return; + } + const next = change.goal; + if (change.operation === "create") { + if (next.revision !== 1 || next.phase !== "active" || change.roundsStarted !== 0 + || (state.goal !== null && state.goal.phase !== "complete") || state.seenIds.has(next.id)) { + fail("DSH_EVENT_RELATIONSHIP_INVALID"); + } + state.seenIds.add(next.id); + } else { + const current = state.goal; + if (current === null || next.id !== current.id || next.revision !== current.revision + 1 + || change.createdAt !== state.createdAt || change.updatedAt < state.updatedAt + || change.roundsStarted !== state.roundsStarted) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + if (change.operation === "edit") { + if (next.phase !== current.phase || !deepJsonEqual(next.blockedReason, current.blockedReason)) { + fail("DSH_EVENT_RELATIONSHIP_INVALID"); + } + } else { + if (!sameGoalDefinition(current, next)) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + if (change.operation === "pause" && !(current.phase === "active" && next.phase === "paused")) { + fail("DSH_EVENT_RELATIONSHIP_INVALID"); + } + if (change.operation === "resume" && (!new Set(["active", "paused", "blocked"]).has(current.phase) + || next.phase !== "active" || state.roundsStarted >= next.maxGoalRounds)) { + fail("DSH_EVENT_RELATIONSHIP_INVALID"); + } + if (change.operation === "complete" && (current.phase === "complete" || next.phase !== "complete")) { + fail("DSH_EVENT_RELATIONSHIP_INVALID"); + } + if (change.operation === "block" && !(current.phase === "active" && next.phase === "blocked")) { + fail("DSH_EVENT_RELATIONSHIP_INVALID"); + } + } + } + state.goal = structuredClone(next); + state.roundsStarted = change.roundsStarted; + state.createdAt = change.createdAt; + state.updatedAt = change.updatedAt; +} + +function compactionReplacementMatches(event, shadow) { + if (shadow.kind === "prune" && event.type !== "tool/result") return false; + if (shadow.kind === "summary" && (event.type !== "user/message" + || event.data.source.kind !== "plugin" || event.data.source.plugin !== "compact" + || event.data.source.compactionId !== shadow.compactionId + || event.data.source.sourceCommandId !== shadow.sourceCommandId)) return false; + return ["user/message", "assistant/message", "tool/result"].includes(event.type) + && plain(event.surfaceOp) && event.surfaceOp.op === "replace" + && event.surfaceOp.start === shadow.range.start && event.surfaceOp.end === shadow.range.end + && deepJsonEqual(event.sourceEventSeqs, shadow.sourceEventSeqs); +} + +function applyCompactionFold(state, event, openTurn, durableSuffixStart) { + if (state.pendingShadow !== null) { + if (!compactionReplacementMatches(event, state.pendingShadow)) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + state.pendingShadow = null; + } + if ((event.type === "turn/start" || event.type === "turn/end") && state.open !== null + && !state.open.inherited) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + if (event.type === "session/end-seed") { + state.open = null; + return; + } + if (event.type === "user/message" && event.data.source.kind === "plugin" + && event.data.source.plugin === "compact") { + const source = event.data.source; + if (state.open === null || source.compactionId !== state.open.id + || source.sourceCommandId !== state.open.sourceCommandId) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + } + if (event.type === "compaction/prune") { + if (event.data.shadowedSeqs.length === 0 + || event.data.shadowedSeqs[0] !== event.data.shadowedRange.start + || event.data.shadowedSeqs.at(-1) !== event.data.shadowedRange.end) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + state.pendingShadow = { + kind: "prune", + range: event.data.shadowedRange, + sourceEventSeqs: event.data.shadowedSeqs, + }; + return; + } + if (event.type === "compaction/start") { + if (state.open !== null || (event.data.turn === null ? openTurn !== null : event.data.turn !== openTurn)) { + fail("DSH_EVENT_RELATIONSHIP_INVALID"); + } + state.open = { id: event.data.compactionId, sourceCommandId: event.data.sourceCommandId, + startSeq: event.seq, turn: event.data.turn, summarized: false, inherited: event.seq < durableSuffixStart }; + return; + } + if (event.type !== "compaction/summary" && event.type !== "compaction/end") return; + const open = state.open; + if (open === null || event.data.compactionId !== open.id + || event.data.sourceCommandId !== open.sourceCommandId + || (open.turn === null ? openTurn !== null : open.turn !== openTurn)) { + fail("DSH_EVENT_RELATIONSHIP_INVALID"); + } + if (event.type === "compaction/summary") { + if (open.summarized || event.data.shadowedSeqs.length === 0 + || event.data.shadowedSeqs[0] !== event.data.shadowedRange.start + || event.data.shadowedSeqs.at(-1) !== event.data.shadowedRange.end) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + open.summarized = true; + state.pendingShadow = { + kind: "summary", + compactionId: open.id, + sourceCommandId: open.sourceCommandId, + range: event.data.shadowedRange, + sourceEventSeqs: [open.startSeq, event.seq, ...event.data.shadowedSeqs], + }; + return; + } + if (event.data.turn !== open.turn || (!Object.hasOwn(event.data, "error") && !open.summarized)) { + fail("DSH_EVENT_RELATIONSHIP_INVALID"); + } + state.open = null; +} + +function validateSequence(events, header) { + let nextTurn = 1; + let nextStep = 1; + let openTurn = null; + let openStep = null; + const pendingCalls = new Set(); + const surfaceNodes = []; + const directHumanSeqs = []; + const queues = { "next-turn": [], "next-step": [] }; + const schedules = { active: new Map(), seen: new Set() }; + const approvals = new Set(); + const commandIds = new Set(); + const hooks = new Map(); + const workflows = new Map(); + const dispatchRoots = new Map(); + const retryChains = new Map(); + const retryOwners = new Map(); + const retryScheduled = new Map(); + const retryStarted = new Set(); + const goal = { goal: null, roundsStarted: 0, createdAt: null, updatedAt: null, seenIds: new Set() }; + const compaction = { open: null, pendingShadow: null }; + let latestRequestHeader = null; + let ownDescriptorSeen = false; + let liveBoundarySeen = false; + let lifecycleRequestSeen = false; + let lifecycleFirstTurn = null; + const durableSuffixStart = header.seedLength ?? 0; + for (const [index, event] of events.entries()) { + if (plain(event) && ["todo/write", "request/header", "request/context"].includes(event.type) + && openTurn === null) fail("DSH_EVENT_ORDER_INVALID"); + validateEventEnvelope(event, index); + applyCompactionFold(compaction, event, openTurn, durableSuffixStart); + applySurfaceEvent(event, surfaceNodes, events); + const data = event.data; + if (event.type === "agent/inbox/spliced" && index >= durableSuffixStart) applyInboxSplice(data, queues); + if (event.type === "schedule/change" && index >= durableSuffixStart) applyScheduleChange(data, schedules); + applyGoalFold(goal, event); + if (event.type === "user/message" && data.source?.kind === "user" + && textFromContent(data.content).trim().length > 0) directHumanSeqs.push(event.seq); + if (event.type === "approval/asked") { + if (openTurn === null || approvals.has(data.id)) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + approvals.add(data.id); + } else if (event.type === "approval/decided") { + if (openTurn === null || !approvals.delete(data.id)) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + } + if (event.type === "command/run") { + if (commandIds.has(data.commandId)) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + commandIds.add(data.commandId); + } else if (event.type === "command/done") { + if (!commandIds.has(data.commandId)) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + if (Object.hasOwn(data, "sourceEventSeq")) { + const source = events[data.sourceEventSeq]; + if (data.sourceEventSeq >= event.seq || source?.seq !== data.sourceEventSeq + || ["command/run", "command/done"].includes(source.type)) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + } + } + if (event.type === "hook/invoked" || event.type === "hook/result") { + if (openTurn !== data.turn) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + const key = `${data.turn}\0${data.point}\0${data.handlerId}`; + const pending = hooks.get(key) ?? 0; + if (event.type === "hook/invoked") hooks.set(key, pending + 1); + else if (pending === 0) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + else if (pending === 1) hooks.delete(key); + else hooks.set(key, pending - 1); + } + if (["tool-workflow/run-start", "tool-workflow/agent-start", "tool-workflow/agent-end", + "tool-workflow/run-end"].includes(event.type)) { + const run = workflows.get(data.runId); + if (event.type === "tool-workflow/run-start") { + if (run !== undefined) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + workflows.set(data.runId, { ended: false, members: new Map() }); + } else { + if (run === undefined || run.ended) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + if (event.type === "tool-workflow/agent-start") { + if (run.members.has(data.seq)) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + run.members.set(data.seq, false); + } else if (event.type === "tool-workflow/agent-end") { + if (run.members.get(data.seq) !== false) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + run.members.set(data.seq, true); + } else { + if ([...run.members.values()].some((ended) => !ended)) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + run.ended = true; + run.members.clear(); + } + } + } + if (event.type === "tool/code-dispatch-start" || event.type === "tool/code-dispatch") { + if (openTurn === null) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + const knownRoot = dispatchRoots.get(data.subCallId); + if (knownRoot !== undefined && knownRoot !== data.rootCallId) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + if (data.parentCallId !== data.rootCallId + && dispatchRoots.get(data.parentCallId) !== data.rootCallId) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + dispatchRoots.set(data.subCallId, data.rootCallId); + } + if (event.type === "request/header") { + if (!lifecycleRequestSeen) { + const expectedReason = latestRequestHeader === null ? "initial" : "resume"; + if (data.reason !== expectedReason) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + } else { + if (data.reason !== "change" || deepJsonEqual(data.header, latestRequestHeader)) { + fail("DSH_EVENT_RELATIONSHIP_INVALID"); + } + } + latestRequestHeader = structuredClone(data.header); + lifecycleRequestSeen = true; + } + if (event.type === "request/context") { + if (latestRequestHeader === null || data.provider !== latestRequestHeader.config.provider + || data.model !== latestRequestHeader.config.model) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + } + if (event.type === "session/title") validateTitleReferences(data, event.seq, directHumanSeqs); + if (event.type === "session/title-llm-request") { + validateTitleRequestReferences(data, event.seq, directHumanSeqs); + } + if (event.type === "subagent/descriptor") { + if (index >= durableSuffixStart) { + if (ownDescriptorSeen || lifecycleRequestSeen) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + if (data.mode === "one-shot") { + if (openTurn === null || openTurn !== lifecycleFirstTurn || openStep !== null) { + fail("DSH_EVENT_RELATIONSHIP_INVALID"); + } + } else if (openTurn !== null + || !(liveBoundarySeen || (header.seedLength === undefined && index === 0))) { + fail("DSH_EVENT_RELATIONSHIP_INVALID"); + } + ownDescriptorSeen = true; + } + } + if (event.type === "session/end-seed") { + lifecycleRequestSeen = false; + lifecycleFirstTurn = openTurn; + if (index >= durableSuffixStart) liveBoundarySeen = true; + } + if (event.type === "llm/retry") { + if (openTurn !== data.turn || openStep !== data.step + || latestRequestHeader?.config?.provider !== data.provider) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + const chainKey = `${data.turn}\0${data.step}\0${data.provider}\0${data.policyKey}`; + const prior = retryChains.get(chainKey); + if (data.retry !== (prior?.retry ?? 0) + 1 || (prior && prior.retryId !== data.retryId)) { + fail("DSH_EVENT_RELATIONSHIP_INVALID"); + } + const owner = retryOwners.get(data.retryId); + if (owner !== undefined && owner !== chainKey) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + retryOwners.set(data.retryId, chainKey); + retryChains.set(chainKey, { retry: data.retry, retryId: data.retryId }); + retryScheduled.set(`${data.retryId}\0${data.retry}`, { turn: data.turn, step: data.step }); + } else if (event.type === "llm/retry-started") { + const key = `${data.retryId}\0${data.retry}`; + const scheduled = retryScheduled.get(key); + if (scheduled === undefined || retryStarted.has(key) || scheduled.turn !== data.turn + || scheduled.step !== data.step) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + retryStarted.add(key); + } + if (event.type === "turn/start") { + if (openTurn !== null || data.turn !== nextTurn) fail("DSH_EVENT_ORDER_INVALID"); + openTurn = data.turn; + if (lifecycleFirstTurn === null) lifecycleFirstTurn = data.turn; + nextStep = 1; + } else if (event.type === "step/start") { + if (openTurn !== data.turn || openStep !== null || data.step !== nextStep) fail("DSH_EVENT_ORDER_INVALID"); + openStep = data.step; + } else if (["assistant/message", "assistant/chunk", "tool/call"].includes(event.type)) { + if (openTurn !== data.turn || openStep !== data.step) fail("DSH_EVENT_ORDER_INVALID"); + if (event.type === "tool/call") pendingCalls.add(data.callId); + } else if (event.type === "tool/result") { + const callId = data.message?.source?.callId; + if (event.surfaceOp === "append") { + if (typeof callId !== "string" || openTurn !== data.turn || openStep !== data.step) fail("DSH_EVENT_ORDER_INVALID"); + const syntheticNotStarted = data.message.content[0].isError === true && data.error?.code === "TOOL_NOT_STARTED"; + if (!pendingCalls.delete(callId) && !syntheticNotStarted) fail("DSH_EVENT_ORDER_INVALID"); + } else if (openTurn === null) fail("DSH_EVENT_ORDER_INVALID"); + } else if (event.type === "step/end") { + if (openTurn !== data.turn || openStep !== data.step) fail("DSH_EVENT_ORDER_INVALID"); + pendingCalls.clear(); + openStep = null; + nextStep += 1; + } else if (event.type === "turn/end") { + if (openTurn !== data.turn || openStep !== null || pendingCalls.size > 0) fail("DSH_EVENT_ORDER_INVALID"); + openTurn = null; + nextTurn += 1; + } + } + if (compaction.pendingShadow !== null) fail("DSH_EVENT_RELATIONSHIP_INVALID"); + const pendingInboxMessageCount = queues["next-turn"].length + queues["next-step"].length; + if (openTurn === null && (openStep !== null || pendingCalls.size > 0)) fail("DSH_EVENT_ORDER_INVALID"); + if (openTurn === null && pendingInboxMessageCount === 0) { + return { incomplete: false, incompleteReason: null, pendingToolCallCount: 0, pendingInboxMessageCount: 0 }; + } + return { + incomplete: true, + incompleteReason: pendingCalls.size > 0 ? "pending-tool-result" + : openStep !== null ? "open-step" : openTurn !== null ? "open-turn" : "pending-inbox", + pendingToolCallCount: pendingCalls.size, + pendingInboxMessageCount, + }; +} + +export function decodeDshJsonl(input) { + let text; + try { + text = new TextDecoder("utf-8", { fatal: true }).decode(input); + } catch { + fail("DSH_INVALID_UTF8"); + } + if (text.length === 0) fail("DSH_EMPTY_ARTIFACT"); + if (!text.endsWith("\n")) fail("DSH_INCOMPLETE_JSONL_LINE"); + const lines = text.slice(0, -1).split("\n"); + if (lines.some((line) => line.length === 0)) fail("DSH_BLANK_JSONL_LINE"); + let records; + try { + records = lines.map((line) => JSON.parse(line)); + } catch { + fail("DSH_MALFORMED_JSONL"); + } + if (records.some((record) => !plain(record))) fail("DSH_MALFORMED_JSONL"); + const header = validateHeader(records[0]); + const events = []; + for (const record of records.slice(1)) { + const expanded = packedRow(record); + if (expanded) events.push(...expanded); + else if (typeof record.type === "string" && record.type.endsWith("-chunks")) fail("DSH_UNSUPPORTED_PACKED_ROW"); + else events.push(record); + } + const sequence = validateSequence(events, header); + const knownUnsupportedTypes = [...new Set(events.filter((event) => KNOWN_EVENT_TYPES.has(event.type) + && !NORMALIZATION_ALLOWLIST.has(event.type) && !CONTROL_TYPES.has(event.type)).map((event) => event.type))].sort(); + const unknownIgnorableTypes = [...new Set(events.filter((event) => !KNOWN_EVENT_TYPES.has(event.type) + && event.ignorable === true).map((event) => unknownEventTypeToken(event.type)))].sort().slice(0, 16); + return { + header, + events, + incomplete: sequence.incomplete, + diagnostics: { + knownUnsupportedCount: events.filter((event) => KNOWN_EVENT_TYPES.has(event.type) + && !NORMALIZATION_ALLOWLIST.has(event.type) && !CONTROL_TYPES.has(event.type)).length, + knownUnsupportedTypes, + unknownIgnorableCount: events.filter((event) => !KNOWN_EVENT_TYPES.has(event.type) + && event.ignorable === true).length, + unknownIgnorableTypes, + ...(sequence.incomplete ? { incompleteReason: sequence.incompleteReason } : {}), + ...(sequence.pendingToolCallCount > 0 ? { pendingToolCallCount: sequence.pendingToolCallCount } : {}), + ...(sequence.pendingInboxMessageCount > 0 ? { pendingInboxMessageCount: sequence.pendingInboxMessageCount } : {}), + }, + }; +} + +export function decodeDshArtifact(input, { compressed = false, decompressor = zlib.zstdDecompressSync } = {}) { + const decoded = compressed ? decodeDshZstdArtifact(input, { decompressor }).bytes : Buffer.from(input); + return decodeDshJsonl(decoded); +} + +function pathMatchesWorkspace(cwd, workspace) { + const leftFlavor = absoluteFlavor(cwd); + const rightFlavor = absoluteFlavor(workspace); + if (!leftFlavor || leftFlavor !== rightFlavor) return false; + const api = leftFlavor === "win32" ? path.win32 : path.posix; + const left = api.normalize(cwd); + const right = api.normalize(workspace); + const comparableLeft = leftFlavor === "win32" ? left.toLowerCase() : left; + const comparableRight = leftFlavor === "win32" ? right.toLowerCase() : right; + const relative = api.relative(comparableRight, comparableLeft); + return relative === "" || (relative !== ".." && !relative.startsWith(`..${api.sep}`) && !api.isAbsolute(relative)); +} + +function diagnostic(code, details = {}) { + return { code, ...details }; +} + +function unknownEventTypeToken(type) { + return `unknown:${createHash("sha256").update(type).digest("hex").slice(0, 12)}`; +} + +function privacySafeProvenance(value) { + if (value === "") return ""; + return sanitizePrivateReviewText(value, { limit: 160 }) ?? ""; +} + +function dshProvenance(header) { + return { + delegationDepth: header.delegationDepth, + ...(Object.hasOwn(header, "parentSession") ? { parentSession: privacySafeProvenance(header.parentSession) } : {}), + ...(Object.hasOwn(header, "seedLength") ? { seedLength: header.seedLength } : {}), + ...(Object.hasOwn(header, "origin") ? { origin: header.origin } : {}), + ...(Object.hasOwn(header, "agentPreset") ? { agentPreset: privacySafeProvenance(header.agentPreset) } : {}), + }; +} + +function validatedNormalizationSourceRef(sourceRef) { + if (!plain(sourceRef) + || sourceRef.kind !== "dsh-session-jsonl" + || typeof sourceRef.sessionId !== "string" || sourceRef.sessionId.length === 0 + || typeof sourceRef.path !== "string" || sourceRef.path.length === 0 || sourceRef.path.includes("\0") + || !absoluteFlavor(sourceRef.cwd) + || !plain(sourceRef.dshProvenance) + || !safeNonnegative(sourceRef.dshProvenance.delegationDepth)) { + fail("DSH_NORMALIZATION_UNAVAILABLE", "DSH normalization requires validated session evidence"); + } + const provenance = sourceRef.dshProvenance; + if ((Object.hasOwn(provenance, "parentSession") && typeof provenance.parentSession !== "string") + || (Object.hasOwn(provenance, "seedLength") && !safeNonnegative(provenance.seedLength)) + || (Object.hasOwn(provenance, "origin") && provenance.origin !== "subagent") + || (Object.hasOwn(provenance, "agentPreset") && typeof provenance.agentPreset !== "string")) { + fail("DSH_NORMALIZATION_UNAVAILABLE", "DSH normalization requires validated session evidence"); + } + return { + kind: "dsh-session-jsonl", + role: "session-transcript", + path: sourceRef.path, + sessionId: sourceRef.sessionId, + cwd: sourceRef.cwd, + dshProvenance: { + delegationDepth: provenance.delegationDepth, + ...(Object.hasOwn(provenance, "parentSession") + ? { parentSession: privacySafeProvenance(provenance.parentSession) } : {}), + ...(Object.hasOwn(provenance, "seedLength") ? { seedLength: provenance.seedLength } : {}), + ...(Object.hasOwn(provenance, "origin") ? { origin: provenance.origin } : {}), + ...(Object.hasOwn(provenance, "agentPreset") + ? { agentPreset: privacySafeProvenance(provenance.agentPreset) } : {}), + }, + }; +} + +export function dedupeDshArtifactCandidates(candidates) { + const unique = new Map(); + for (const candidate of candidates) { + const identity = process.platform === "win32" ? candidate.canonicalPath.toLowerCase() : candidate.canonicalPath; + if (!unique.has(identity)) unique.set(identity, candidate); + } + return [...unique.values()]; +} + +async function directoryEntries(directory) { + try { + return await readdir(directory, { withFileTypes: true }); + } catch (error) { + if (error?.code === "ENOENT") return []; + throw error; + } +} + +async function isDirectoryEntry(entry, fullPath) { + if (entry.isDirectory()) return true; + if (!entry.isSymbolicLink()) return false; + try { return (await stat(fullPath)).isDirectory(); } catch { return false; } +} + +function sourceRef(candidate) { + return { kind: "dsh-session-jsonl", role: "session-transcript", path: candidate.path, + encoding: candidate.compressed ? "zstd" : "jsonl" }; +} + +export async function readDshSessionArtifact(candidate, options = {}) { + const before = await stat(candidate.path); + const bytes = await readFile(candidate.path); + const decoded = decodeDshArtifact(bytes, { compressed: candidate.compressed, decompressor: options.decompressor }); + const after = await stat(candidate.path); + const unchanged = before.size === after.size && before.mtimeMs === after.mtimeMs; + if (!unchanged) fail("DSH_ARTIFACT_CHANGED_DURING_READ"); + return { ...decoded, bytesRead: bytes.length, unchanged }; +} + +function textFromContent(content) { + return (Array.isArray(content) ? content : []) + .filter((block) => block?.type === "text" && typeof block.text === "string") + .map((block) => block.text) + .join("\n") + .trim(); +} + +function normalizedEvidenceRef(sourceRef, event, type) { + return { + kind: sourceRef.kind, + path: sourceRef.path, + seq: event.seq, + type, + }; +} + +function normalizedBase(event, sourceRef) { + const provenance = sourceRef.dshProvenance; + return { + sessionId: sourceRef.sessionId, + timestamp: normalizeDshEpochMillis(event.time), + sourceKind: sourceRef.kind, + planningScope: "workspace", + cwd: sourceRef.cwd, + nativeType: event.type, + nativeSeq: event.seq, + ...(Object.hasOwn(event.data, "turn") ? { turn: event.data.turn } : {}), + ...(Object.hasOwn(event.data, "step") ? { step: event.data.step } : {}), + isSubagent: provenance?.origin === "subagent", + dshProvenance: provenance, + }; +} + +function includeGate(options, camel, dashed) { + const value = options[camel] ?? options[dashed]; + return value === undefined ? false : parseBooleanFlag(value); +} + +function boundedPrivateText(value, limit) { + return sanitizePrivateReviewText(value, { limit }); +} + +function safeLabel(value, fallback = null) { + const result = boundedPrivateText(value, 160); + return result || fallback; +} + +function parsedToolArguments(value) { + if (typeof value !== "string" || value.length > 64_000) return null; + try { + const parsed = JSON.parse(value); + return plain(parsed) ? parsed : null; + } catch { + return null; + } +} + +function selectedPath(value) { + if (typeof value !== "string" || value.length === 0 || value.length > 4_096 || value.includes("\0")) return null; + const flavor = absoluteFlavor(value); + const basename = flavor === "win32" ? path.win32.basename(value) + : flavor === "posix" ? path.posix.basename(value) : null; + const basenameLabel = basename ? sanitizePrivateReviewText(basename, { limit: 224 }) : null; + const sanitized = flavor + ? `${basenameLabel && basenameLabel !== "" ? `/${basenameLabel}` : ""}` + : sanitizePrivateReviewText(value, { limit: 239 }); + if (!sanitized || ["", ""].includes(sanitized)) return null; + return [...sanitized].slice(0, 240).join(""); +} + +function selectedPaths(parsed) { + const values = [parsed.paths, parsed.targetPaths, parsed.affectedPaths] + .filter(Array.isArray) + .flat(); + const seen = new Set(); + const selected = []; + for (const value of values) { + const projected = selectedPath(value); + if (!projected || seen.has(projected)) continue; + seen.add(projected); + selected.push(projected); + if (selected.length === 8) break; + } + return selected; +} + +function toolArgumentFacts(toolName, argumentsText, options) { + const parsed = parsedToolArguments(argumentsText); + if (!parsed) return {}; + const facts = {}; + if (/(?:read|edit|write|file|notebook|view|patch|create)/iu.test(toolName)) { + const filePath = selectedPath(parsed.file_path ?? parsed.filePath ?? parsed.path); + if (filePath) facts.filePath = filePath; + const targetPaths = selectedPaths(parsed); + if (targetPaths.length > 0) facts.targetPaths = targetPaths; + } + if (includeGate(options, "includeCommandText", "include-command-text") + && /(?:bash|shell|exec|terminal|run|powershell|command)/iu.test(toolName)) { + const command = parsed.command ?? parsed.cmd; + const commandText = typeof command === "string" ? boundedPrivateText(command, 4_096) : null; + if (commandText) facts.commandText = commandText; + } + return facts; +} + +function normalizedUsage(usage) { + return { + inputTokens: usage.inputTokens, + outputTokens: usage.outputTokens, + ...(Object.hasOwn(usage, "cacheReadTokens") ? { cacheReadInputTokens: usage.cacheReadTokens } : {}), + ...(Object.hasOwn(usage, "cacheWriteTokens") ? { cacheCreationInputTokens: usage.cacheWriteTokens } : {}), + ...(Object.hasOwn(usage, "reasoningTokens") ? { reasoningTokens: usage.reasoningTokens } : {}), + }; +} + +function normalizeTurnEnd(event, sourceRef) { + const reason = event.data.reason; + const common = { + ...normalizedBase(event, sourceRef), + type: "turn.end", + category: "lifecycle", + lifecyclePhase: "result", + outcome: reason.kind, + evidenceRef: normalizedEvidenceRef(sourceRef, event, "turn.end"), + summary: `turn ended: ${reason.kind}`, + }; + switch (reason.kind) { + case "completed": return { ...common, success: true, hasError: false }; + case "aborted": return { ...common, success: false, cancelled: true, hasError: false, + cancelCause: reason.reason.kind }; + case "blocked": return { ...common, success: false, blocked: true, hasError: false }; + case "error": return { ...common, success: false, hasError: true, + errorCode: safeLabel(reason.error.code, "UNKNOWN") }; + case "max-tokens": return { ...common, success: false, maxTokensReached: true, hasError: false }; + case "interrupted": return { ...common, success: null, incomplete: true, hasError: false }; + default: fail("DSH_EVENT_SHAPE_DRIFT"); + } +} + +function normalizeDshEvents(event, sourceRef, options = {}) { + const base = normalizedBase(event, sourceRef); + if (event.type === "turn/start") { + return [{ ...base, type: "turn.start", category: "lifecycle", lifecyclePhase: "request", + evidenceRef: normalizedEvidenceRef(sourceRef, event, "turn.start"), summary: "turn started" }]; + } + if (event.type === "turn/end") return [normalizeTurnEnd(event, sourceRef)]; + if (event.type === "user/message") { + const direct = event.data.source.kind === "user"; + const rawText = textFromContent(event.data.content); + const safeText = direct + ? privacySafeUserInputText(rawText, { limit: 8_000 }) + : boundedPrivateText(rawText, 8_000); + const normalized = { + ...base, + type: direct ? "user" : "context", + category: direct ? "user" : "context", + userPrompt: direct && rawText.length > 0, + ...(direct ? { userSourceKind: "human" } : { contextSourceKind: event.data.source.kind }), + ...(event.data.source.kind === "plugin" + ? { contextForm: event.data.source.form ?? "opaque" } + : {}), + contentLength: rawText.length, + evidenceRef: normalizedEvidenceRef(sourceRef, event, direct ? "user" : "message"), + summary: direct ? "direct user message" : "injected user-role context", + }; + if (direct && includeGate(options, "includeUserText", "include-user-text") && safeText) { + normalized.userText = safeText; + } + if (includeGate(options, "includeContent", "include-content") && safeText) normalized.content = safeText; + return [normalized]; + } + if (event.type === "assistant/message") { + const rawText = textFromContent(event.data.message.content); + const safeText = boundedPrivateText(rawText, 8_000); + const model = safeLabel(event.data.message.source.model); + const provider = safeLabel(event.data.message.source.provider); + const assistant = { + ...base, + type: "assistant", + category: "assistant", + model, + modelProvider: provider, + contentLength: rawText.length, + userVisibleAssistantMessage: rawText.length > 0, + evidenceRef: normalizedEvidenceRef(sourceRef, event, "assistant"), + summary: "assembled assistant message", + }; + if (includeGate(options, "includeContent", "include-content") && safeText) assistant.content = safeText; + const events = [assistant]; + if (event.data.usage) { + events.push({ + ...base, + type: "model.response.completed", + category: "model", + model, + modelProvider: provider, + modelUsage: normalizedUsage(event.data.usage), + usageFieldsObserved: true, + evidenceRef: normalizedEvidenceRef(sourceRef, event, "model.response.completed"), + summary: "DeepSeek Harness model response completed", + }); + } + return events; + } + if (event.type === "tool/call") { + const toolName = safeLabel(event.data.name, "unknown-tool"); + return [{ + ...base, + type: "tool.call", + category: "tool", + lifecyclePhase: "request", + toolInvocationId: event.data.callId, + toolName, + functionCallName: toolName, + ...toolArgumentFacts(toolName, event.data.arguments, options), + evidenceRef: normalizedEvidenceRef(sourceRef, event, "tool.call"), + summary: `${toolName} request`, + }]; + } + if (event.type === "tool/result") { + const block = event.data.message.content[0]; + const rawText = textFromContent(block.content); + const safeText = boundedPrivateText(rawText, 8_000); + const success = block.isError !== true; + const normalized = { + ...base, + type: "tool.result", + category: "tool", + lifecyclePhase: "result", + toolInvocationId: event.data.message.source.callId, + success, + hasError: !success, + ...(event.data.error ? { internalError: true, internalErrorName: safeLabel(event.data.error.name), + internalErrorCode: safeLabel(event.data.error.code) } : { internalError: false }), + evidenceRef: normalizedEvidenceRef(sourceRef, event, "tool.result"), + summary: success ? "tool result" : "tool result failed", + }; + if (includeGate(options, "includeContent", "include-content") && safeText) normalized.content = safeText; + return [normalized]; + } + return []; +} + +function finalDshSurfaceSeqs(events) { + const nodes = []; + for (const event of events) { + if (!["user/message", "assistant/message", "tool/result"].includes(event.type)) continue; + if (event.surfaceOp === "append") { + nodes.push(event.seq); + continue; + } + const start = nodes.indexOf(event.surfaceOp.start); + const end = nodes.indexOf(event.surfaceOp.end); + nodes.splice(start, end - start + 1, event.seq); + } + return new Set(nodes); +} + +function normalizedEventOrdinal(event) { + return event.type === "model.response.completed" ? 1 : 0; +} + +export class DshSessionAnalyzer extends SessionAnalyzer { + constructor({ decompressor = zlib.zstdDecompressSync } = {}) { + super(); + this.decompressor = decompressor; + this.analysisWarnings = []; + } + + async resolveScope(options = {}) { + const direct = explicitHome(options); + const envValue = options.env?.DSH_HOME ?? process.env.DSH_HOME; + const home = expandExplicitHome(direct !== undefined ? direct : envValue !== undefined ? envValue : path.join(os.homedir(), ".dsh")); + const workspace = options.workspace ?? process.cwd(); + if (!absoluteFlavor(workspace)) fail("DSH_INVALID_WORKSPACE", "workspace must be an absolute path"); + const since = normalizeCliDate(options.since); + const until = normalizeCliDate(options.until, true); + return { + platform: "dsh", workspace, dshHome: home, sessionRoot: path.join(home, "sessions"), + sessionId: options.sessionId ?? options["session-id"] ?? null, + since: since.label, sinceTime: since.time, until: until.label, untilTime: until.time, + _workspaceMatchScope: workspaceMatchScopeFromOptions(options), + }; + } + + async discoverSourceRoots(scope) { + let exists = false; + try { exists = (await stat(scope.sessionRoot)).isDirectory(); } catch (error) { if (error?.code !== "ENOENT") throw error; } + return [{ id: "dsh-sessions", kind: "dsh-session-jsonl", role: "session-transcript", + path: scope.sessionRoot, exists, enabled: true, optional: false, workspaceScoped: false, coverage: "partial" }]; + } + + async discoverSessions(scope, roots) { + this.analysisWarnings = []; + const root = roots[0]; + if (!root?.exists) { + if (root) root.warnings = []; + return []; + } + const candidates = []; + for (const projectEntry of await directoryEntries(root.path)) { + const projectPath = path.join(root.path, projectEntry.name); + if (!(await isDirectoryEntry(projectEntry, projectPath))) { + if (projectEntry.name.startsWith("session.jsonl")) this.analysisWarnings.push(diagnostic("dsh-flat-artifact-rejected")); + continue; + } + for (const sessionEntry of await directoryEntries(projectPath)) { + const sessionPath = path.join(projectPath, sessionEntry.name); + if (!(await isDirectoryEntry(sessionEntry, sessionPath))) { + if (sessionEntry.name.startsWith("session.jsonl")) this.analysisWarnings.push(diagnostic("dsh-flat-artifact-rejected")); + continue; + } + const entries = await directoryEntries(sessionPath); + const artifacts = entries.filter((entry) => entry.isFile() + && ["session.jsonl", "session.jsonl.zstd"].includes(entry.name)); + const nestedArtifacts = []; + for (const entry of entries) { + const childPath = path.join(sessionPath, entry.name); + if (await isDirectoryEntry(entry, childPath)) { + nestedArtifacts.push(...(await directoryEntries(childPath)).filter((child) => child.name.startsWith("session.jsonl"))); + } + } + if (nestedArtifacts.length > 0) this.analysisWarnings.push(diagnostic("dsh-wrong-depth-rejected")); + if (artifacts.length !== 1) { + if (artifacts.length > 1) this.analysisWarnings.push(diagnostic("dsh-ambiguous-artifact-rejected")); + continue; + } + const artifact = artifacts[0]; + const artifactPath = path.join(sessionPath, artifact.name); + let canonicalPath; + try { canonicalPath = await realpath(artifactPath); } catch { canonicalPath = path.resolve(artifactPath); } + candidates.push({ path: artifactPath, canonicalPath, projectSegment: projectEntry.name, + sessionSegment: sessionEntry.name, compressed: artifact.name.endsWith(".zstd") }); + } + } + + const decoded = []; + for (const candidate of dedupeDshArtifactCandidates(candidates)) { + try { + const artifact = await readDshSessionArtifact(candidate, { decompressor: this.decompressor }); + if (candidate.sessionSegment !== encodeDshSessionId(artifact.header.id)) fail("DSH_SESSION_ID_PATH_MISMATCH"); + if (candidate.projectSegment !== dshProjectKey(artifact.header.cwd)) fail("DSH_PROJECT_KEY_MISMATCH"); + decoded.push({ candidate, artifact }); + } catch (error) { + const code = error?.code === "DSH_ZSTD_UNAVAILABLE" ? "dsh-zstd-unavailable" : "dsh-artifact-rejected"; + this.analysisWarnings.push(diagnostic(code, { reason: error?.code ?? "DSH_READ_FAILED" })); + } + } + + const byId = new Map(); + for (const item of decoded) { + const list = byId.get(item.artifact.header.id) ?? []; + list.push(item); + byId.set(item.artifact.header.id, list); + } + const sessions = []; + for (const [sessionId, items] of byId) { + if (items.length !== 1) { + this.analysisWarnings.push(diagnostic("dsh-session-identity-conflict", { artifactCount: items.length })); + continue; + } + const { candidate, artifact } = items[0]; + const header = artifact.header; + const workspaceMatch = scope._workspaceMatchScope + ? classifyWorkspaceCwd(header.cwd, scope._workspaceMatchScope) + : pathMatchesWorkspace(header.cwd, scope.workspace) ? WORKSPACE_CWD_MATCH.DIRECT : WORKSPACE_CWD_MATCH.UNMATCHED; + if (workspaceMatch !== WORKSPACE_CWD_MATCH.DIRECT) { + this.analysisWarnings.push(diagnostic("dsh-foreign-workspace-rejected")); + continue; + } + const times = artifact.events.map((event) => event.time); + const first = times.length > 0 ? Math.min(header.createdAt, ...times) : header.createdAt; + const last = times.length > 0 ? Math.max(header.createdAt, ...times) : header.createdAt; + const session = { + platform: "dsh", sessionId, cwd: header.cwd, workspaceMatch, + firstSeen: normalizeDshEpochMillis(first), lastSeen: normalizeDshEpochMillis(last), + sourceKinds: ["dsh-session-jsonl"], sourceRefs: [sourceRef(candidate)], + incomplete: artifact.incomplete, + diagnostics: artifact.diagnostics, + dshProvenance: dshProvenance(header), + }; + if (artifact.incomplete) this.analysisWarnings.push(diagnostic("dsh-incomplete-session", { + reason: artifact.diagnostics.incompleteReason, + })); + sessions.push(bindSessionWorkspaceCwds(session, [header.cwd])); + } + root.warnings = [...this.analysisWarnings]; + return sessions.sort((left, right) => Date.parse(right.lastSeen) - Date.parse(left.lastSeen) + || left.sessionId.localeCompare(right.sessionId)); + } + + async analyze(options = {}) { + return runProviderAnalysis(this, options, { platform: "dsh", adapterVersion: DSH_ADAPTER_VERSION }); + } + + currentSessionId() { return null; } + + normalizeEvent(raw, sourceRef, options = {}) { + return this.normalizeEvents(raw, sourceRef, options)[0] ?? null; + } + + normalizeEvents(raw, sourceRef, options = {}) { + if (!plain(raw) || !KNOWN_EVENT_TYPES.has(raw.type)) { + fail("DSH_NORMALIZATION_UNAVAILABLE", "DSH normalization requires validated session evidence"); + } + validateEventEnvelope(raw, raw.seq); + if (!NORMALIZATION_ALLOWLIST.has(raw.type)) return []; + validateSupportedEvent(raw); + return normalizeDshEvents(raw, validatedNormalizationSourceRef(sourceRef), options); + } + + async readSession(session, scope, options = {}) { + if (!session || !scope) fail("DSH_NORMALIZATION_UNAVAILABLE", "DSH normalization requires a discovered session"); + const normalized = []; + for (const ref of session.sourceRefs ?? []) { + if (ref.kind !== "dsh-session-jsonl" || !["jsonl", "zstd"].includes(ref.encoding)) continue; + const candidate = { + path: ref.path, + compressed: ref.encoding === "zstd", + }; + const artifact = await readDshSessionArtifact(candidate, { decompressor: this.decompressor }); + const sessionSegment = path.basename(path.dirname(ref.path)); + const projectSegment = path.basename(path.dirname(path.dirname(ref.path))); + if (artifact.header.id !== session.sessionId + || artifact.header.cwd !== session.cwd + || sessionSegment !== encodeDshSessionId(artifact.header.id) + || projectSegment !== dshProjectKey(artifact.header.cwd) + || JSON.stringify(dshProvenance(artifact.header)) !== JSON.stringify(session.dshProvenance)) { + fail("DSH_ARTIFACT_IDENTITY_DRIFT"); + } + const finalSurfaceSeqs = finalDshSurfaceSeqs(artifact.events); + const normalizedSourceRef = { + ...ref, + sessionId: session.sessionId, + cwd: artifact.header.cwd, + dshProvenance: dshProvenance(artifact.header), + }; + for (const event of artifact.events) { + if (!withinTimeRange(normalizeDshEpochMillis(event.time), scope)) continue; + if (!NORMALIZATION_ALLOWLIST.has(event.type)) continue; + if (["user/message", "assistant/message", "tool/result"].includes(event.type) + && !finalSurfaceSeqs.has(event.seq)) continue; + normalized.push(...this.normalizeEvents(event, normalizedSourceRef, options)); + } + } + const seen = new Set(); + const events = normalized.filter((event) => { + const key = `${event.sessionId}:${event.nativeSeq}:${event.type}`; + if (seen.has(key)) return false; + seen.add(key); + return true; + }).sort((left, right) => + left.nativeSeq - right.nativeSeq + || normalizedEventOrdinal(left) - normalizedEventOrdinal(right) + || left.type.localeCompare(right.type)); + return markSessionReadCoverage(events, { truncated: false }); + } +} + +export async function main(argv = process.argv.slice(2)) { + const { command = "sessions", options } = parseArgs(argv); + const analyzer = new DshSessionAnalyzer(); + const result = await runProviderCommand(analyzer, command, options); + await emitProviderResult({ provider: "DeepSeek Harness", command, options, result }); + return result; +} + +const isCli = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url); +if (isCli) { + main().catch((error) => { + process.stderr.write(`dsh session-analysis failed: ${error.stack ?? error.message}\n`); + process.exitCode = 1; + }); +} diff --git a/test/fixtures/scripts-refactor-contract/root-help.txt b/test/fixtures/scripts-refactor-contract/root-help.txt index dfc09e7a..3b8030d7 100644 --- a/test/fixtures/scripts-refactor-contract/root-help.txt +++ b/test/fixtures/scripts-refactor-contract/root-help.txt @@ -23,7 +23,7 @@ Commands: Project Evidence session-analysis Collect and normalize Qoder, Codex, Claude, Cursor, Qwen, Copilot, - Pi, Kimi, WorkBuddy, and Grok session evidence + Pi, Kimi, WorkBuddy, Grok, and DeepSeek Harness session evidence commit-session-link Correlate local git commits with discovered coding-agent sessions and render a commit-view HTML report dependency-governance Detect dependency governance files, automation, audit signals, and diff --git a/test/fixtures/scripts-refactor-contract/session-help.txt b/test/fixtures/scripts-refactor-contract/session-help.txt index 15a6b32f..09d65915 100644 --- a/test/fixtures/scripts-refactor-contract/session-help.txt +++ b/test/fixtures/scripts-refactor-contract/session-help.txt @@ -1,7 +1,7 @@ -Usage: session-analysis --platform --workspace [options] +Usage: session-analysis --platform --workspace [options] Commands: sources, sessions, facets, insights, facts, file-reads, show, events, claude-facets -Options: --kimi-home overrides the Kimi Code data root (default: ~/.kimi-code); --workbuddy-home overrides the WorkBuddy data root (default: ~/.workbuddy); --grok-home overrides the Grok data root (default: ~/.grok or $GROK_HOME). +Options: --kimi-home overrides the Kimi Code data root (default: ~/.kimi-code); --workbuddy-home overrides the WorkBuddy data root (default: ~/.workbuddy); --grok-home overrides the Grok data root (default: ~/.grok or $GROK_HOME); --dsh-home overrides the DeepSeek Harness data root (default: ~/.dsh or $DSH_HOME). Use facts --debug only for local diagnosis; it exposes raw session ids and must not be passed to report agents. diff --git a/test/governance/scripts-refactor-contract.test.mjs b/test/governance/scripts-refactor-contract.test.mjs index e7feb708..740afca4 100644 --- a/test/governance/scripts-refactor-contract.test.mjs +++ b/test/governance/scripts-refactor-contract.test.mjs @@ -107,12 +107,12 @@ test("scripts refactor contract freezes machine-readable CLI output", () => { { label: "command inventory", args: ["commands", "--json"], - sha256: "d56fbee6c35e587d70477b2228f46778b76c49365cc70eca1aeffb1c162da36d", + sha256: "8eafc652fb50a78c0fd69e3ed8e054f3d391ccde15d598d711cd2e27ebaf00e7", }, { label: "OpenCLI schema", args: ["schema"], - sha256: "4570210d8ffbfaebad7534aacb6e6ff76ae936fe9f01f9baab440573beef0420", + sha256: "da7fabced64aa9ddc9591ceef96dc2c5c60889473bc45fb915dc5eebfefcc01f", }, { label: "Harness command description", diff --git a/test/plugins/host-support.test.mjs b/test/plugins/host-support.test.mjs index d7b063f9..362f6261 100644 --- a/test/plugins/host-support.test.mjs +++ b/test/plugins/host-support.test.mjs @@ -35,12 +35,28 @@ test("capability projections are immutable, ordered, and independently addressed for (const capability of Object.values(HOST_CAPABILITIES)) { const hostIds = hostIdsFor(capability); assert.ok(Object.isFrozen(hostIds)); - if (capability === HOST_CAPABILITIES.CHECKUP) { - assert.deepEqual(hostIds, HOST_IDS.filter((hostId) => !new Set(["kimi", "grok"]).has(hostId))); - } else { - assert.deepEqual(hostIds, HOST_IDS); + assert.deepEqual(hostIds, HOST_DESCRIPTORS + .filter((host) => host.capabilities.includes(capability)) + .map((host) => host.id)); + for (const host of HOST_DESCRIPTORS) { + assert.equal(hostIds.includes(host.id), host.capabilities.includes(capability), + `${host.id}:${capability}`); } } + const dsh = getHostDescriptor("dsh"); + assert.ok(Object.isFrozen(dsh.capabilities)); + assert.deepEqual(dsh.capabilities, [HOST_CAPABILITIES.SESSION_ANALYSIS]); + assert.equal(hostIdsFor(HOST_CAPABILITIES.SESSION_ANALYSIS).includes("dsh"), true); + for (const capability of [ + HOST_CAPABILITIES.AGENT_CUSTOMIZE, + HOST_CAPABILITIES.ASSET_PRACTICES, + HOST_CAPABILITIES.HARNESS_REPORT, + HOST_CAPABILITIES.REPORT_RENDERING, + HOST_CAPABILITIES.EVIDENCE_BUNDLE, + HOST_CAPABILITIES.CHECKUP, + ]) { + assert.equal(hostIdsFor(capability).includes("dsh"), false, capability); + } assert.throws(() => hostIdsFor("unknown"), /Unknown host capability/u); }); diff --git a/test/reporting/harness-report-render-cli.test.mjs b/test/reporting/harness-report-render-cli.test.mjs index ae333725..3edd0581 100644 --- a/test/reporting/harness-report-render-cli.test.mjs +++ b/test/reporting/harness-report-render-cli.test.mjs @@ -8,7 +8,7 @@ import { test } from "vitest"; import { evaluateHtmlReport, renderHtml } from "../../scripts/harness-analysis/renderers/html.mjs"; import { RENDER_REPORT_PLATFORMS, renderReport } from "../../scripts/harness-analysis/render-report.mjs"; -import { SUPPORTED_SESSION_PLATFORMS } from "../../scripts/session-analysis/analyzer.mjs"; +import { HOST_CAPABILITIES, hostIdsFor } from "../../scripts/host-support/index.mjs"; import { renderCanvasTsx } from "../../scripts/harness-analysis/renderers/qoder-canvas.mjs"; import { buildTaskLoopSourceCandidate } from "../../scripts/harness-analysis/task-loop-source.mjs"; import { applyEpisodeReviews } from "../../scripts/harness-analysis/episode-evidence-review.mjs"; @@ -869,6 +869,13 @@ test("render routes html output by host id and fails closed on unknown platforms assert.equal(rejected.status, 1); assert.match(rejected.stderr, /unsupported render platform: grock/u); + const sessionOnly = runNode( + [renderPath, "--findings", findingsPath, "--mode", "html", "--platform", "dsh", "--target", root, "--json"], + { cwd: root }, + ); + assert.equal(sessionOnly.status, 1); + assert.match(sessionOnly.stderr, /unsupported render platform: dsh/u); + // Help must stay usable even with an invalid platform so agents can self-correct. const help = runNode([renderPath, "--help", "--platform", "grock"], { cwd: root }); assert.equal(help.status, 0, help.stderr); @@ -876,8 +883,9 @@ test("render routes html output by host id and fails closed on unknown platforms }); }); -test("render platform allowlist matches the session platform registry", () => { - assert.deepEqual([...RENDER_REPORT_PLATFORMS].sort(), [...SUPPORTED_SESSION_PLATFORMS].sort()); +test("render platform allowlist follows report-rendering capability rather than session support", () => { + assert.deepEqual([...RENDER_REPORT_PLATFORMS], hostIdsFor(HOST_CAPABILITIES.REPORT_RENDERING)); + assert.equal(RENDER_REPORT_PLATFORMS.includes("dsh"), false); }); test("HTML relative action metadata carries the finding's current repair revision", () => { diff --git a/test/sessions/dsh-fixtures.mjs b/test/sessions/dsh-fixtures.mjs new file mode 100644 index 00000000..91bef447 --- /dev/null +++ b/test/sessions/dsh-fixtures.mjs @@ -0,0 +1,684 @@ +import { mkdir, writeFile } from "node:fs/promises"; +import path from "node:path"; +import * as zlib from "node:zlib"; + +// Pinned contract at deepseek-harness commit +// 99f6f02fecdb7dff40c3fbc9470f5907c29f74ca: +// packages/core/session/src/{types,invariant,chunk-rows,known-event-types}.ts, +// packages/llm/llm/src/{message,types}.ts, and +// packages/session/session-persistence-jsonl/src/{format,zstd}.ts. + +export const DSH_FORMAT_VERSION = 0; +export const DSH_FIXTURE_SECRET = "sk-dsh_fixture_secret_NEVER_EXPOSE"; +export const DSH_ZSTD_UNAVAILABLE = "DSH_ZSTD_UNAVAILABLE"; +export const DSH_FIXTURE_MALFORMED_PACKED_ROW = "DSH_FIXTURE_MALFORMED_PACKED_ROW"; +export const DSH_FIXTURE_UNSUPPORTED_PACKED_ROW = "DSH_FIXTURE_UNSUPPORTED_PACKED_ROW"; + +const DEFAULT_SESSION_ID = "dsh-fixture-session-0001"; +const DEFAULT_WORKSPACE = "/synthetic/workspace/project"; +const BASE_TIME = Date.parse("2026-08-18T00:00:00.000Z"); +const PACKED_TAGS = new Set(["text-chunks", "reasoning-chunks", "tool-call-chunks"]); + +function fresh(value) { + return structuredClone(value); +} + +function surfaceEvent(type, seq, time, data, extra = {}) { + return { type, seq, time, data: fresh(data), ...fresh(extra) }; +} + +function userMessage(id, text, source) { + return { + id, + role: "user", + content: [{ type: "text", text }], + source: fresh(source), + }; +} + +function assistantMessage(id, text) { + return { + id, + role: "assistant", + content: [{ type: "text", text }], + source: { kind: "model", provider: "fixture-provider", model: "fixture-model" }, + }; +} + +function toolResultMessage(id, callId, text, isError) { + return { + id, + role: "user", + content: [{ + type: "tool-result", + toolCallId: callId, + content: [{ type: "text", text }], + isError, + }], + source: { kind: "tool", callId }, + }; +} + +export function makeDshHeader(options = {}) { + const { + workspace = DEFAULT_WORKSPACE, + sessionId = DEFAULT_SESSION_ID, + createdAt = BASE_TIME, + delegationDepth = 1, + } = options; + const parentSession = Object.hasOwn(options, "parentSession") + ? options.parentSession + : "dsh-fixture-parent-0001"; + const seedLength = Object.hasOwn(options, "seedLength") ? options.seedLength : 2; + const origin = Object.hasOwn(options, "origin") ? options.origin : "subagent"; + const agentPreset = Object.hasOwn(options, "agentPreset") ? options.agentPreset : "fixture-reviewer"; + return fresh({ + type: "session", + version: DSH_FORMAT_VERSION, + id: sessionId, + cwd: workspace, + createdAt, + ...(parentSession !== undefined ? { parentSession } : {}), + ...(seedLength !== undefined ? { seedLength } : {}), + ...(origin !== undefined ? { origin } : {}), + delegationDepth, + ...(agentPreset !== undefined ? { agentPreset } : {}), + }); +} + +export function makeDshEvent(type, data, { + seq = 0, + time = BASE_TIME + 1_000, + ignorable, + sourceEventSeqs, + surfaceOp, +} = {}) { + return fresh({ + type, + seq, + time, + data, + ...(ignorable === true ? { ignorable: true } : {}), + ...(sourceEventSeqs !== undefined ? { sourceEventSeqs } : {}), + ...(surfaceOp !== undefined ? { surfaceOp } : {}), + }); +} + +export function makeSupportedDshSessionRows(options = {}) { + const header = makeDshHeader(options); + const eventTime = header.createdAt + 1_000; + const successCallId = "fixture-call-success"; + const errorCallId = "fixture-call-error"; + const events = [ + makeDshEvent("turn/start", { turn: 1 }, { seq: 0, time: eventTime }), + makeDshEvent("step/start", { turn: 1, step: 1 }, { seq: 1, time: eventTime + 10 }), + surfaceEvent("user/message", 2, eventTime + 20, + userMessage("fixture-user-direct", "Inspect the synthetic fixture.", { kind: "user" }), + { surfaceOp: "append" }), + surfaceEvent("user/message", 3, eventTime + 30, + userMessage("fixture-user-injected", DSH_FIXTURE_SECRET, { + kind: "plugin", + plugin: "fixture-context", + form: "notice", + summary: "Synthetic context injection.", + }), + { surfaceOp: "append" }), + surfaceEvent("assistant/message", 4, eventTime + 40, { + turn: 1, + step: 1, + message: assistantMessage("fixture-assistant", "I will call both synthetic tools."), + usage: { + inputTokens: 12, + outputTokens: 8, + cacheReadTokens: 2, + reasoningTokens: 1, + }, + }, { sourceEventSeqs: [], surfaceOp: "append" }), + makeDshEvent("tool/call", { + turn: 1, + step: 1, + callId: successCallId, + name: "fixture_read", + arguments: "{\"path\":\"synthetic.txt\"}", + }, { seq: 5, time: eventTime + 50 }), + surfaceEvent("tool/result", 6, eventTime + 60, { + turn: 1, + step: 1, + message: toolResultMessage("fixture-tool-success", successCallId, "synthetic result", false), + }, { surfaceOp: "append" }), + makeDshEvent("tool/call", { + turn: 1, + step: 1, + callId: errorCallId, + name: "fixture_fail", + arguments: "{}", + }, { seq: 7, time: eventTime + 70 }), + surfaceEvent("tool/result", 8, eventTime + 80, { + turn: 1, + step: 1, + message: toolResultMessage("fixture-tool-error", errorCallId, "synthetic failure", true), + error: { name: "FixtureToolError", code: "FIXTURE_TOOL_FAILURE" }, + }, { surfaceOp: "append" }), + makeDshEvent("step/end", { turn: 1, step: 1 }, { seq: 9, time: eventTime + 90 }), + makeDshEvent("turn/end", { turn: 1, reason: { kind: "completed" } }, { + seq: 10, + time: eventTime + 100, + }), + ]; + return fresh([header, ...events]); +} + +// Mirrors examples/headless-agent/tests/snapshots/headless-profile/session.expected.jsonl +// at the pinned DSH commit while keeping all values synthetic and machine-independent. +export function makeNativeSnapshotDshSessionRows(options = {}) { + const header = makeDshHeader({ + ...options, + parentSession: Object.hasOwn(options, "parentSession") ? options.parentSession : undefined, + seedLength: Object.hasOwn(options, "seedLength") ? options.seedLength : undefined, + origin: Object.hasOwn(options, "origin") ? options.origin : undefined, + delegationDepth: options.delegationDepth ?? 0, + agentPreset: Object.hasOwn(options, "agentPreset") ? options.agentPreset : undefined, + }); + const time0 = header.createdAt + 1_000; + const userText = options.userText ?? "Prove the synthetic headless profile path."; + const privateText = options.privateText ?? "Synthetic private request metadata."; + const direct = userMessage("fixture-native-human", userText, { kind: "user" }); + const injected = userMessage("fixture-native-context", "Synthetic runtime context.", { + kind: "plugin", + plugin: "@deepseek-ai/dsh-system-prompt", + form: "snapshot", + sections: [{ name: "sandbox:policy", text: "Synthetic policy context." }], + }); + const config = { provider: "fixture-provider", model: "fixture-model", reasoningEffort: "high" }; + const tools = [{ + name: "fixture_read", + description: `Synthetic tool. ${privateText}`, + parameters: { type: "object", properties: { path: { type: "string" } } }, + }]; + const callId = "fixture-native-call"; + const events = []; + const push = (type, data, extra = {}) => { + events.push(makeDshEvent(type, data, { seq: events.length, time: time0 + events.length, ...extra })); + }; + push("permission/preset", { preset: "danger-full-access" }); + push("sandbox/mode", { mode: "danger-full-access" }); + push("approval/policy", { policy: "never" }); + push("agent/inbox/spliced", { target: "next-turn", start: 0, inserted: [direct] }); + push("turn/start", { turn: 1 }); + push("agent/inbox/spliced", { target: "next-turn", start: 0, removedCount: 1, inserted: [] }); + push("step/start", { turn: 1, step: 1 }); + push("user/message", direct, { surfaceOp: "append" }); + push("user/message", injected, { surfaceOp: "append" }); + push("session/title", { + title: "Prove the synthetic headless profile", messageSeqs: [7], source: { kind: "fallback" }, + }); + push("request/header", { + header: { + config, + adapterDefaults: { reasoningEffort: true }, + system: privateText, + tools, + }, + reason: "initial", + }); + push("request/context", { provider: config.provider, model: config.model, contextWindow: 32_768 }); + push("session/title-llm-request", { + titleProvider: "session-title-first-prompt-llm", + messageSeqs: [7], + route: { provider: config.provider, model: config.model }, + system: `Synthetic title system. ${privateText}`, + messages: [userMessage("fixture-title-request", `Synthetic title prompt. ${privateText}`, { + kind: "plugin", plugin: "dsh-session-title-llm", + })], + maxTokens: 64, + }); + push("assistant/chunk", { turn: 1, step: 1, chunk: { type: "block-start", index: 0, blockType: "tool-call" } }); + push("assistant/chunk", { + turn: 1, step: 1, chunk: { + type: "tool-call-delta", index: 0, id: callId, name: "fixture_read", + argumentsDelta: "{\"path\":\"synthetic.txt\"}", + }, + }); + push("assistant/chunk", { + turn: 1, step: 1, chunk: { + type: "block-end", index: 0, + block: { type: "tool-call", id: callId, name: "fixture_read", arguments: "{\"path\":\"synthetic.txt\"}" }, + }, + }); + push("assistant/chunk", { + turn: 1, step: 1, chunk: { type: "usage", usage: { inputTokens: 11, outputTokens: 3, cacheReadTokens: 2 } }, + }); + push("assistant/chunk", { turn: 1, step: 1, chunk: { type: "finish", reason: { kind: "tool-calls" } } }); + push("assistant/message", { + turn: 1, + step: 1, + message: { + id: "fixture-native-assistant-tool", + role: "assistant", + content: [{ type: "tool-call", id: callId, name: "fixture_read", arguments: "{\"path\":\"synthetic.txt\"}" }], + source: { kind: "model", provider: config.provider, model: config.model }, + }, + usage: { inputTokens: 11, outputTokens: 3, cacheReadTokens: 2 }, + }, { sourceEventSeqs: [13, 14, 15, 16, 17], surfaceOp: "append" }); + push("tool/call", { + turn: 1, step: 1, callId, name: "fixture_read", arguments: "{\"path\":\"synthetic.txt\"}", + }); + push("tool/result", { + turn: 1, + step: 1, + message: toolResultMessage("fixture-native-result", callId, "synthetic native result", false), + }, { sourceEventSeqs: [19], surfaceOp: "append" }); + push("step/end", { turn: 1, step: 1 }); + push("step/start", { turn: 1, step: 2 }); + push("request/header", { + header: { config: { provider: config.provider, model: config.model, reasoningEffort: "off" }, system: privateText, tools }, + reason: "change", + }); + push("assistant/chunk", { turn: 1, step: 2, chunk: { type: "block-start", index: 0, blockType: "text" } }); + push("assistant/chunk", { turn: 1, step: 2, chunk: { type: "text-delta", index: 0, text: "Synthetic complete." } }); + push("assistant/chunk", { + turn: 1, step: 2, chunk: { type: "block-end", index: 0, block: { type: "text", text: "Synthetic complete." } }, + }); + push("assistant/chunk", { turn: 1, step: 2, chunk: { type: "usage", usage: { inputTokens: 7, outputTokens: 5 } } }); + push("assistant/chunk", { turn: 1, step: 2, chunk: { type: "finish", reason: { kind: "stop" } } }); + push("assistant/message", { + turn: 1, + step: 2, + message: assistantMessage("fixture-native-assistant-final", "Synthetic complete."), + usage: { inputTokens: 7, outputTokens: 5 }, + }, { sourceEventSeqs: [24, 25, 26, 27, 28], surfaceOp: "append" }); + push("step/end", { turn: 1, step: 2 }); + push("turn/end", { turn: 1, reason: { kind: "completed" } }); + return fresh([header, ...events]); +} + +function terminalReason(kind) { + switch (kind) { + case "completed": + return { kind: "completed" }; + case "aborted": + return { kind: "aborted", reason: { kind: "hook", reason: "synthetic cancellation" } }; + case "blocked": + return { kind: "blocked" }; + case "error": + return { + kind: "error", + error: { message: "synthetic provider failure", code: "FIXTURE_PROVIDER_FAILURE", status: 503 }, + }; + case "max-tokens": + return { kind: "max-tokens" }; + case "interrupted": + return { kind: "interrupted" }; + default: + throw new TypeError(`unsupported synthetic terminal reason: ${kind}`); + } +} + +export function makeTerminalDshSessionRows(kind, options = {}) { + const sessionId = options.sessionId ?? `dsh-fixture-terminal-${kind}`; + const header = makeDshHeader({ ...options, sessionId, parentSession: undefined, seedLength: undefined, + origin: undefined, delegationDepth: 0, agentPreset: undefined }); + const time = header.createdAt + 1_000; + const events = [makeDshEvent("turn/start", { turn: 1 }, { seq: 0, time })]; + if (kind !== "blocked") { + events.push(surfaceEvent("user/message", 1, time + 10, + userMessage(`fixture-terminal-user-${kind}`, `Exercise ${kind}.`, { kind: "user" }), + { surfaceOp: "append" })); + } + if (["completed", "error", "max-tokens"].includes(kind)) { + const stepSeq = events.length; + events.push(makeDshEvent("step/start", { turn: 1, step: 1 }, { seq: stepSeq, time: time + 20 })); + events.push(makeDshEvent("step/end", { turn: 1, step: 1 }, { seq: stepSeq + 1, time: time + 30 })); + } + events.push(makeDshEvent("turn/end", { turn: 1, reason: terminalReason(kind) }, { + seq: events.length, + time: time + 40, + })); + return fresh([header, ...events]); +} + +export function makeKnownUnsupportedDshEvent({ seq = 0, time = BASE_TIME + 1_000 } = {}) { + return makeDshEvent("todo/write", { + todos: [{ content: "Review the synthetic fixture", status: "in_progress" }], + }, { seq, time }); +} + +export function makeKnownUnsupportedDshSessionRows(options = {}) { + const header = makeDshHeader({ ...options, sessionId: options.sessionId ?? "dsh-fixture-known-unsupported" }); + const time = header.createdAt + 1_000; + return fresh([ + header, + makeDshEvent("turn/start", { turn: 1 }, { seq: 0, time }), + makeKnownUnsupportedDshEvent({ seq: 1, time: time + 10 }), + makeDshEvent("turn/end", { turn: 1, reason: { kind: "completed" } }, { seq: 2, time: time + 20 }), + ]); +} + +export function makeUnknownRequiredDshEvent({ seq = 0, time = BASE_TIME + 1_000 } = {}) { + return makeDshEvent("fixture-future/required", { value: "synthetic" }, { seq, time }); +} + +export function makeUnknownIgnorableDshEvent({ seq = 0, time = BASE_TIME + 1_000 } = {}) { + return makeDshEvent("fixture-future/ignorable", { value: "synthetic" }, { seq, time, ignorable: true }); +} + +export function makePackedDshStorageRows() { + return fresh([ + { + type: "text-chunks", + seq0: 0, + time0: BASE_TIME + 1_000, + data: { turn: 1, step: 1, index: 0, dt: [2, 3], texts: ["one", " two", "."] }, + }, + { + type: "reasoning-chunks", + seq0: 3, + time0: BASE_TIME + 1_010, + data: { turn: 1, step: 1, index: 1, dt: [1, -1], texts: ["think", "ing", "."] }, + }, + { + type: "tool-call-chunks", + seq0: 6, + time0: BASE_TIME + 1_020, + data: { + turn: 1, + step: 1, + index: 2, + id: "fixture-packed-call", + name: "fixture_tool", + dt: [4, 5], + args: ["{\"value\":", "1", "}"], + }, + }, + ]); +} + +export function makeMalformedPackedDshStorageRows() { + const [text, reasoning, tool] = makePackedDshStorageRows(); + return fresh([ + { ...text, unexpected: true }, + { ...reasoning, data: { ...reasoning.data, dt: [] } }, + { ...tool, data: { ...tool.data, id: 42 } }, + { type: "future-chunks", seq0: 9, time0: BASE_TIME + 2_000, + data: { turn: 1, step: 1, index: 3, dt: [], texts: ["future"] } }, + ]); +} + +function packedError(code, message) { + const error = new Error(message); + error.code = code; + return error; +} + +function hasExactKeys(value, keys) { + return value !== null && typeof value === "object" + && Object.keys(value).length === keys.length + && keys.every((key) => Object.hasOwn(value, key)); +} + +export function decodePackedDshStorageRecordForFixture(input) { + const row = fresh(input); + if (!PACKED_TAGS.has(row?.type)) { + throw packedError(DSH_FIXTURE_UNSUPPORTED_PACKED_ROW, "unsupported packed DSH storage row"); + } + if (!hasExactKeys(row, ["type", "seq0", "time0", "data"])) { + throw packedError(DSH_FIXTURE_MALFORMED_PACKED_ROW, "packed envelope has unexpected fields"); + } + if (!Number.isSafeInteger(row.seq0) || row.seq0 < 0 || !Number.isSafeInteger(row.time0)) { + throw packedError(DSH_FIXTURE_MALFORMED_PACKED_ROW, "packed sequence/time anchor is invalid"); + } + const data = row.data; + const isTool = row.type === "tool-call-chunks"; + const toolKeys = Object.hasOwn(data ?? {}, "name") + ? ["turn", "step", "index", "id", "name", "dt", "args"] + : ["turn", "step", "index", "id", "dt", "args"]; + const expectedKeys = isTool ? toolKeys : ["turn", "step", "index", "dt", "texts"]; + if (!hasExactKeys(data, expectedKeys) + || typeof data.turn !== "number" + || typeof data.step !== "number" + || typeof data.index !== "number") { + throw packedError(DSH_FIXTURE_MALFORMED_PACKED_ROW, "packed data shape is invalid"); + } + if (isTool && (typeof data.id !== "string" + || (Object.hasOwn(data, "name") && typeof data.name !== "string"))) { + throw packedError(DSH_FIXTURE_MALFORMED_PACKED_ROW, "packed tool identity is invalid"); + } + const members = isTool ? data.args : data.texts; + if (!Array.isArray(members) || members.length === 0 || members.some((item) => typeof item !== "string") + || !Array.isArray(data.dt) || data.dt.some((gap) => !Number.isSafeInteger(gap)) + || data.dt.length !== members.length - 1 + || !Number.isSafeInteger(row.seq0 + members.length - 1)) { + throw packedError(DSH_FIXTURE_MALFORMED_PACKED_ROW, "packed member arity is invalid"); + } + const events = []; + let time = row.time0; + for (let index = 0; index < members.length; index += 1) { + if (index > 0) time += data.dt[index - 1]; + if (!Number.isSafeInteger(time)) { + throw packedError(DSH_FIXTURE_MALFORMED_PACKED_ROW, "packed member time is invalid"); + } + let chunk; + if (row.type === "text-chunks") { + chunk = { type: "text-delta", index: data.index, text: members[index] }; + } else if (row.type === "reasoning-chunks") { + chunk = { type: "reasoning-delta", index: data.index, text: members[index] }; + } else { + chunk = { + type: "tool-call-delta", + index: data.index, + id: data.id, + ...(Object.hasOwn(data, "name") ? { name: data.name } : {}), + argumentsDelta: members[index], + }; + } + events.push({ + type: "assistant/chunk", + seq: row.seq0 + index, + time, + data: { turn: data.turn, step: data.step, chunk }, + }); + } + return fresh(events); +} + +export function encodeDshRawJsonl(rows) { + return Buffer.from(`${rows.map((row) => JSON.stringify(row)).join("\n")}\n`, "utf8"); +} + +function zstdUnavailable() { + const error = new Error("public Node.js Zstandard compression API is unavailable"); + error.code = DSH_ZSTD_UNAVAILABLE; + return error; +} + +export function makeDshZstdArtifact(rowBatches, { + compressor = zlib.zstdCompressSync, + checksumFlag = zlib.constants?.ZSTD_c_checksumFlag, +} = {}) { + if (typeof compressor !== "function" || !Number.isSafeInteger(checksumFlag)) { + throw zstdUnavailable(); + } + const frames = rowBatches.map((rows) => Buffer.from(compressor(encodeDshRawJsonl(rows), { + params: { [checksumFlag]: 1 }, + }))); + return { + frames: frames.map((frame) => Buffer.from(frame)), + artifact: Buffer.concat(frames), + }; +} + +export function decodeDshZstdFramesForFixture(frames, { + decompressor = zlib.zstdDecompressSync, +} = {}) { + if (typeof decompressor !== "function") throw zstdUnavailable(); + if (!Array.isArray(frames) || frames.length === 0 || frames.some((frame) => !Buffer.isBuffer(frame))) { + throw new TypeError("frames must be a non-empty Buffer array"); + } + return frames.map((frame) => Buffer.from(decompressor(Buffer.from(frame)))); +} + +export function encodeDshSessionIdSegment(raw) { + if (typeof raw !== "string" || raw.length === 0) throw new TypeError("sessionId must be non-empty"); + if (raw === ".") return "~002E"; + if (raw === "..") return "~002E~002E"; + let encoded = ""; + for (let index = 0; index < raw.length; index += 1) { + const code = raw.charCodeAt(index); + const char = String.fromCharCode(code); + encoded += char !== "~" && /^[A-Za-z0-9._-]$/u.test(char) + ? char + : `~${code.toString(16).toUpperCase().padStart(4, "0")}`; + } + return encoded; +} + +export function dshProjectKey(cwd) { + if (typeof cwd !== "string" || cwd.length === 0) throw new TypeError("cwd must be non-empty"); + let readable = ""; + let separatorRun = false; + for (let index = 0; index < cwd.length; index += 1) { + const code = cwd.charCodeAt(index); + const char = String.fromCharCode(code); + if (char === "/" || char === "\\" || char === ":") { + if (!separatorRun) readable += "-"; + separatorRun = true; + } else if (char !== "~" && /^[A-Za-z0-9._-]$/u.test(char)) { + readable += char; + separatorRun = false; + } else { + readable += `~${code.toString(16).toUpperCase().padStart(4, "0")}`; + separatorRun = false; + } + } + const slug = readable.replace(/^-+/u, "") || "root"; + return `--${slug.slice(0, 251)}--`; +} + +export async function writeNestedDshArtifact({ + dshHome, + rows, + projectSegment, + sessionId = rows?.[0]?.id ?? DEFAULT_SESSION_ID, + compression = "raw", + compressor = zlib.zstdCompressSync, +} = {}) { + if (typeof dshHome !== "string" || !path.isAbsolute(dshHome)) { + throw new TypeError("dshHome must be an absolute caller-owned temporary directory"); + } + const resolvedProjectSegment = projectSegment ?? dshProjectKey(rows?.[0]?.cwd); + if (typeof resolvedProjectSegment !== "string" || resolvedProjectSegment.length === 0 + || resolvedProjectSegment === "." || resolvedProjectSegment === ".." + || path.basename(resolvedProjectSegment) !== resolvedProjectSegment) { + throw new TypeError("projectSegment must be one safe path segment"); + } + if (!Array.isArray(rows) || rows.length < 1) throw new TypeError("rows must include a header"); + const directory = path.join( + dshHome, + "sessions", + resolvedProjectSegment, + encodeDshSessionIdSegment(sessionId), + ); + await mkdir(directory, { recursive: true }); + const compressed = compression === "zstd"; + if (!compressed && compression !== "raw") throw new TypeError(`unsupported fixture compression: ${compression}`); + const filePath = path.join(directory, compressed ? "session.jsonl.zstd" : "session.jsonl"); + const bytes = compressed + ? makeDshZstdArtifact([[rows[0]], rows.slice(1)], { compressor }).artifact + : encodeDshRawJsonl(rows); + await writeFile(filePath, bytes); + return { + dshHome, + directory, + filePath, + sessionId, + projectSegment: resolvedProjectSegment, + compression, + bytes: Buffer.from(bytes), + }; +} + +export function makeForeignWorkspaceDshFixture({ + requestedWorkspace = "/synthetic/workspace/target", + foreignWorkspace = "/synthetic/workspace/foreign", + sessionId = "dsh-fixture-foreign-workspace", +} = {}) { + return fresh({ + requestedWorkspace, + rows: makeSupportedDshSessionRows({ workspace: foreignWorkspace, sessionId }), + }); +} + +export function makeCrossPlatformDshWorkspaceFixtures() { + const cases = [ + { platform: "posix", workspace: "/synthetic/Workspace/My Project" }, + { platform: "win32", workspace: "C:\\synthetic\\Workspace\\My Project" }, + ]; + return fresh(cases.map(({ platform, workspace }) => ({ + platform, + workspace, + rows: makeSupportedDshSessionRows({ workspace, sessionId: `dsh-fixture-${platform}-path` }), + }))); +} + +export function makeCanonicalDedupeDshFixture({ + dshHome, + workspace = DEFAULT_WORKSPACE, + sessionId = "dsh-fixture-dedupe", +} = {}) { + if (typeof dshHome !== "string" || !path.isAbsolute(dshHome)) { + throw new TypeError("dshHome must be an absolute caller-owned temporary directory"); + } + const rows = makeSupportedDshSessionRows({ workspace, sessionId }); + const encodedSessionId = encodeDshSessionIdSegment(sessionId); + const directory = path.join(dshHome, "sessions", dshProjectKey(workspace), encodedSessionId); + const canonicalPath = path.join(directory, "session.jsonl"); + const aliasPath = `${directory}${path.sep}..${path.sep}${encodedSessionId}${path.sep}session.jsonl`; + return fresh({ + sessionId, + canonicalPath, + candidates: [ + { path: canonicalPath, rows }, + { path: aliasPath, rows: fresh(rows) }, + ], + }); +} + +export function makeBadVersionDshRows(options = {}) { + const rows = makeSupportedDshSessionRows(options); + rows[0].version = DSH_FORMAT_VERSION + 1; + return fresh(rows); +} + +export function makeBadHeaderDshRows(options = {}) { + const rows = makeSupportedDshSessionRows(options); + delete rows[0].delegationDepth; + return fresh(rows); +} + +export function makeBadIdentityDshFixture(options = {}) { + const artifactSessionId = options.artifactSessionId ?? "dsh-fixture-expected-id"; + const rows = makeSupportedDshSessionRows({ ...options, sessionId: "dsh-fixture-other-id" }); + return fresh({ artifactSessionId, rows }); +} + +export function makeBadSequenceDshRows(options = {}) { + const rows = makeSupportedDshSessionRows(options); + rows[4].seq += 1; + return fresh(rows); +} + +export function makeMalformedDshJsonlBytes(options = {}) { + const header = makeDshHeader(options); + return Buffer.from(`${JSON.stringify(header)}\n{\"type\":\n`, "utf8"); +} + +export function makeOpenTurnDshRows(options = {}) { + const header = makeDshHeader(options); + return fresh([ + header, + makeDshEvent("turn/start", { turn: 1 }, { seq: 0, time: header.createdAt + 1_000 }), + surfaceEvent("user/message", 1, header.createdAt + 1_010, + userMessage("fixture-open-user", "Leave this synthetic turn open.", { kind: "user" }), + { surfaceOp: "append" }), + ]); +} diff --git a/test/sessions/session-analysis-dsh-discovery.test.mjs b/test/sessions/session-analysis-dsh-discovery.test.mjs new file mode 100644 index 00000000..cbb296ae --- /dev/null +++ b/test/sessions/session-analysis-dsh-discovery.test.mjs @@ -0,0 +1,1641 @@ +import assert from "node:assert/strict"; +import { mkdir, mkdtemp, readFile, stat, writeFile } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import * as zlib from "node:zlib"; +import { test } from "vitest"; + +import { + DSH_ADAPTER_VERSION, + DSH_SESSION_FORMAT_VERSION, + DshSessionAnalyzer, + decodeDshArtifact, + decodeDshJsonl, + decodeDshZstdArtifact, + dedupeDshArtifactCandidates, + dshProjectKey, + encodeDshSessionId, + isDshJsonValue, + normalizeDshEpochMillis, + readDshSessionArtifact, + splitDshZstdFrames, +} from "../../scripts/session-analysis/platforms/dsh.mjs"; +import { + DSH_FIXTURE_SECRET, + decodePackedDshStorageRecordForFixture, + dshProjectKey as fixtureProjectKey, + encodeDshRawJsonl, + encodeDshSessionIdSegment, + makeBadHeaderDshRows, + makeBadIdentityDshFixture, + makeBadSequenceDshRows, + makeBadVersionDshRows, + makeCrossPlatformDshWorkspaceFixtures, + makeDshHeader, + makeDshEvent, + makeDshZstdArtifact, + makeKnownUnsupportedDshSessionRows, + makeMalformedDshJsonlBytes, + makeMalformedPackedDshStorageRows, + makeNativeSnapshotDshSessionRows, + makeOpenTurnDshRows, + makePackedDshStorageRows, + makeSupportedDshSessionRows, + makeTerminalDshSessionRows, + makeUnknownIgnorableDshEvent, + makeUnknownRequiredDshEvent, + writeNestedDshArtifact, +} from "./dsh-fixtures.mjs"; + +async function tempRoot(prefix = "dsh-discovery-") { + return mkdtemp(path.join(os.tmpdir(), prefix)); +} + +async function inventory(home, workspace, dependencies = {}) { + const analyzer = new DshSessionAnalyzer(dependencies); + const scope = await analyzer.resolveScope({ dshHome: home, workspace }); + const roots = await analyzer.discoverSourceRoots(scope); + const sessions = await analyzer.discoverSessions(scope, roots); + return { analyzer, scope, roots, sessions, warnings: analyzer.analysisWarnings }; +} + +function stableError(code) { + return (error) => error?.code === code && !String(error?.message).includes(DSH_FIXTURE_SECRET); +} + +function appendEvent(rows, event) { + const result = structuredClone(rows); + result.splice(result.length - 1, 0, event); + result.at(-1).seq += 1; + return result; +} + +function rowsWithChunks(chunks) { + const header = makeDshHeader({ parentSession: undefined, seedLength: undefined, origin: undefined, + delegationDepth: 0, agentPreset: undefined }); + const time = header.createdAt + 1_000; + const events = [ + makeDshEvent("turn/start", { turn: 1 }, { seq: 0, time }), + makeDshEvent("step/start", { turn: 1, step: 1 }, { seq: 1, time: time + 1 }), + ...chunks.map((chunk, index) => makeDshEvent("assistant/chunk", { turn: 1, step: 1, chunk }, + { seq: index + 2, time: time + index + 2 })), + ]; + events.push(makeDshEvent("step/end", { turn: 1, step: 1 }, { seq: events.length, time: time + events.length })); + events.push(makeDshEvent("turn/end", { turn: 1, reason: { kind: "completed" } }, + { seq: events.length, time: time + events.length })); + return [header, ...events]; +} + +function insertBeforeTurnEnd(rows, events) { + const output = structuredClone(rows); + output.splice(output.length - 1, 0, ...events); + output.slice(1).forEach((event, index) => { event.seq = index; }); + return output; +} + +function requestHeaderEvent({ + reason = "initial", + config = { provider: "fixture-provider", model: "fixture-model" }, + adapterDefaults, + system, + tools, +} = {}) { + return makeDshEvent("request/header", { + header: { + config, + ...(adapterDefaults !== undefined ? { adapterDefaults } : {}), + ...(system !== undefined ? { system } : {}), + ...(tools !== undefined ? { tools } : {}), + }, + reason, + }); +} + +function insertRequestHeader(rows, event = requestHeaderEvent()) { + const output = structuredClone(rows); + const assistantIndex = output.findIndex((row) => row.type === "assistant/message"); + output.splice(assistantIndex, 0, event); + output.slice(1).forEach((row, index) => { row.seq = index; }); + return output; +} + +const PINNED_KNOWN_EVENT_TYPES = [ + "agent-preset/selected", "agent/inbox/spliced", "approval/asked", "approval/decided", + "approval/policy", "assistant/chunk", "assistant/message", "command/done", "command/run", + "compaction/end", "compaction/prune", "compaction/start", "compaction/summary", + "feedback/record", "goal/change", "hook/invoked", "hook/result", "llm/retry", + "llm/retry-started", "permission/preset", "plan/mode", "request/context", "request/header", + "sandbox/mode", "schedule/change", "session/end-seed", "session/title", + "session/title-llm-request", "step/end", "step/start", "subagent/descriptor", "todo/write", + "tool-workflow/agent-end", "tool-workflow/agent-start", "tool-workflow/run-end", + "tool-workflow/run-start", "tool/call", "tool/code-dispatch", "tool/code-dispatch-start", + "tool/result", "turn/end", "turn/start", "user/message", "web/deepseek-search-llm-request", +]; + +test("DSH scope resolution has strict explicit, environment, and default precedence", async () => { + const analyzer = new DshSessionAnalyzer(); + const workspace = path.resolve("synthetic-workspace"); + const explicit = path.resolve("explicit-dsh-home"); + const envHome = path.resolve("env-dsh-home"); + const scope = await analyzer.resolveScope({ home: explicit, dshHome: path.resolve("ignored"), + env: { DSH_HOME: envHome }, workspace }); + assert.equal(scope.dshHome, explicit); + assert.equal(scope.sessionRoot, path.join(explicit, "sessions")); + assert.equal((await analyzer.discoverSourceRoots(scope)).length, 1); + assert.equal((await analyzer.resolveScope({ env: { DSH_HOME: envHome }, workspace })).dshHome, envHome); + const prior = process.env.DSH_HOME; + delete process.env.DSH_HOME; + try { + assert.equal((await analyzer.resolveScope({ env: {}, workspace })).dshHome, path.join(os.homedir(), ".dsh")); + } finally { + if (prior === undefined) delete process.env.DSH_HOME; + else process.env.DSH_HOME = prior; + } + for (const value of ["", "relative", "bad\0home", 42]) { + await assert.rejects(() => analyzer.resolveScope({ dshHome: value, workspace }), stableError("DSH_INVALID_HOME")); + } +}); + +test("production identity encoders independently match the pinned fixture oracle", () => { + for (const value of ["session", ".", "..", "with spaces/~and-unicode-\u03bb", "nul\0unit"]) { + assert.equal(encodeDshSessionId(value), encodeDshSessionIdSegment(value)); + } + assert.notEqual(encodeDshSessionId("nul\0unit"), encodeDshSessionId("nul~0000unit")); + assert.equal(path.basename(encodeDshSessionId("nul\0unit")), encodeDshSessionId("nul\0unit")); + assert.throws(() => encodeDshSessionId(""), stableError("DSH_INVALID_SESSION_ID")); + for (const cwd of ["/synthetic/workspace/project", "C:\\Synthetic\\Workspace\\Project"]) { + assert.equal(dshProjectKey(cwd), fixtureProjectKey(cwd)); + } + assert.equal(DSH_ADAPTER_VERSION, "dsh-v1"); + assert.equal(DSH_SESSION_FORMAT_VERSION, 0); +}); + +test("canonical path aliases are deterministically counted once before artifact reading", () => { + const canonical = path.resolve("synthetic", "artifact", "session.jsonl"); + const aliases = [ + { path: canonical, canonicalPath: canonical }, + { path: path.join(path.dirname(canonical), "..", "artifact", "session.jsonl"), canonicalPath: canonical }, + ]; + assert.deepEqual(dedupeDshArtifactCandidates(aliases), [aliases[0]]); +}); + +test("valid nested raw evidence binds header identity, workspace, time, source, and stays read-only", async () => { + const root = await tempRoot(); + const home = path.join(root, "home"); + const workspace = path.join(root, "workspace"); + const rows = makeSupportedDshSessionRows({ workspace, sessionId: "raw-valid" }); + const written = await writeNestedDshArtifact({ dshHome: home, rows }); + const beforeBytes = await readFile(written.filePath); + const beforeStat = await stat(written.filePath); + const result = await inventory(home, workspace); + assert.equal(result.sessions.length, 1); + assert.equal(result.sessions[0].sessionId, "raw-valid"); + assert.equal(result.sessions[0].cwd, workspace); + assert.equal(result.sessions[0].sourceRefs[0].path, written.filePath); + assert.equal(result.sessions[0].sourceRefs[0].encoding, "jsonl"); + assert.equal(result.sessions[0].incomplete, false); + assert.equal(result.sessions[0].dshProvenance.parentSession, rows[0].parentSession); + assert.equal(result.sessions[0].dshProvenance.agentPreset, rows[0].agentPreset); + assert.equal(result.sessions[0].firstSeen, new Date(rows[0].createdAt).toISOString()); + assert.equal(result.sessions[0].lastSeen, new Date(rows.at(-1).time).toISOString()); + assert.deepEqual(await readFile(written.filePath), beforeBytes); + const afterStat = await stat(written.filePath); + assert.equal(afterStat.size, beforeStat.size); + assert.equal(afterStat.mtimeMs, beforeStat.mtimeMs); + const direct = await readDshSessionArtifact({ path: written.filePath, compressed: false }); + assert.equal(direct.unchanged, true); +}); + +test("concatenated Zstd is boundary-scanned and decompressed one complete frame at a time", () => { + if (typeof zlib.zstdCompressSync !== "function" || typeof zlib.zstdDecompressSync !== "function" + || !Number.isSafeInteger(zlib.constants?.ZSTD_c_checksumFlag)) return; + const rows = makeSupportedDshSessionRows(); + const batches = [[rows[0]], rows.slice(1, 6), rows.slice(6)]; + const fixture = makeDshZstdArtifact(batches); + const frames = splitDshZstdFrames(fixture.artifact); + assert.equal(frames.length, 3); + assert.deepEqual(frames, fixture.frames); + const calls = []; + const decoded = decodeDshZstdArtifact(fixture.artifact, { decompressor(frame) { + calls.push(Buffer.from(frame)); + assert.equal(frame.equals(fixture.artifact), false); + return zlib.zstdDecompressSync(frame); + } }); + assert.deepEqual(calls, fixture.frames); + assert.deepEqual(decoded.bytes, Buffer.concat(batches.map(encodeDshRawJsonl))); + assert.deepEqual(decodeDshArtifact(fixture.artifact, { compressed: true }).events, + decodeDshJsonl(encodeDshRawJsonl(rows)).events); + const badFirst = makeDshZstdArtifact([[rows[0], rows[1]], rows.slice(2)]); + assert.throws(() => decodeDshZstdArtifact(badFirst.artifact), stableError("DSH_ZSTD_FIRST_FRAME_NOT_HEADER_ONLY")); + const crossBoundary = makeDshZstdArtifact([[rows[0]], rows.slice(1)]); + const brokenFrame = Buffer.from(zlib.zstdCompressSync(Buffer.from(JSON.stringify(rows[1]), "utf8"), { + params: { [zlib.constants.ZSTD_c_checksumFlag]: 1 }, + })); + assert.throws(() => decodeDshZstdArtifact(Buffer.concat([crossBoundary.frames[0], brokenFrame])), + stableError("DSH_ZSTD_FRAME_NOT_JSONL_BATCH")); +}); + +test("valid compressed nested evidence participates in discovery when the public API exists", async () => { + if (typeof zlib.zstdCompressSync !== "function" || typeof zlib.zstdDecompressSync !== "function" + || !Number.isSafeInteger(zlib.constants?.ZSTD_c_checksumFlag)) return; + const root = await tempRoot(); + const home = path.join(root, "home"); + const workspace = path.join(root, "workspace"); + const rows = makeSupportedDshSessionRows({ workspace, sessionId: "compressed-valid" }); + const written = await writeNestedDshArtifact({ dshHome: home, rows, compression: "zstd" }); + const result = await inventory(home, workspace); + assert.equal(result.sessions.length, 1); + assert.equal(result.sessions[0].sourceRefs[0].encoding, "zstd"); + assert.deepEqual(await readFile(written.filePath), written.bytes); +}); + +test("missing public decompressor marks compressed unavailable while independent raw remains readable", async () => { + const root = await tempRoot(); + const home = path.join(root, "home"); + const workspace = path.join(root, "workspace"); + await writeNestedDshArtifact({ dshHome: home, + rows: makeSupportedDshSessionRows({ workspace, sessionId: "raw-survives" }) }); + const compressedRows = makeSupportedDshSessionRows({ workspace, sessionId: "compressed-unavailable" }); + const compressedDir = path.join(home, "sessions", fixtureProjectKey(workspace), + encodeDshSessionIdSegment(compressedRows[0].id)); + await mkdir(compressedDir, { recursive: true }); + await writeFile(path.join(compressedDir, "session.jsonl.zstd"), Buffer.from("not inspected without API")); + const result = await inventory(home, workspace, { decompressor: null }); + assert.deepEqual(result.sessions.map((session) => session.sessionId), ["raw-survives"]); + assert.equal(result.warnings.some((warning) => warning.code === "dsh-zstd-unavailable"), true); + assert.equal(JSON.stringify({ sessions: result.sessions, warnings: result.warnings }).includes("not inspected"), false); +}); + +test("Zstd frame scanner rejects malformed boundaries with stable privacy-safe codes", () => { + const header = Buffer.from([0x28, 0xB5, 0x2F, 0xFD, 0x24, 0x00]); + const cases = [ + [Buffer.from([0x50, 0x2A, 0x4D, 0x18]), "DSH_ZSTD_TRUNCATED_HEADER"], + [Buffer.from([0x00, 0x00, 0x00, 0x00, 0x24]), "DSH_ZSTD_BAD_MAGIC"], + [Buffer.from([0x28, 0xB5, 0x2F, 0xFD, 0x2C, 0x00]), "DSH_ZSTD_RESERVED_DESCRIPTOR"], + [Buffer.from([0x28, 0xB5, 0x2F, 0xFD, 0x20, 0x00]), "DSH_ZSTD_CHECKSUM_REQUIRED"], + [header, "DSH_ZSTD_TRUNCATED_BLOCK"], + [Buffer.concat([header, Buffer.from([0x07, 0x00, 0x00])]), "DSH_ZSTD_RESERVED_BLOCK"], + [Buffer.concat([header, Buffer.from([0x09, 0x00, 0x00])]), "DSH_ZSTD_TRUNCATED_BLOCK"], + [Buffer.concat([header, Buffer.from([0x01, 0x00, 0x00])]), "DSH_ZSTD_TRUNCATED_CHECKSUM"], + ]; + for (const [bytes, code] of cases) assert.throws(() => splitDshZstdFrames(bytes), stableError(code)); + const syntacticallyComplete = Buffer.concat([header, Buffer.from([0x01, 0, 0]), Buffer.alloc(4)]); + assert.throws(() => decodeDshZstdArtifact(syntacticallyComplete, { decompressor() { throw new Error(DSH_FIXTURE_SECRET); } }), + stableError("DSH_ZSTD_DECOMPRESSION_FAILED")); + assert.throws(() => splitDshZstdFrames(Buffer.concat([syntacticallyComplete, Buffer.from([1])])), + stableError("DSH_ZSTD_TRUNCATED_HEADER")); +}); + +test("all three pinned packed rows expand losslessly and malformed packed shapes fail closed", () => { + const header = makeDshHeader({ parentSession: undefined, seedLength: undefined, origin: undefined, + delegationDepth: 0, agentPreset: undefined }); + const storage = makePackedDshStorageRows().map((row) => ({ ...row, seq0: row.seq0 + 2 })); + const logical = storage.flatMap(decodePackedDshStorageRecordForFixture); + const decoded = decodeDshJsonl(encodeDshRawJsonl([ + header, + makeDshEvent("turn/start", { turn: 1 }, { seq: 0, time: header.createdAt + 900 }), + makeDshEvent("step/start", { turn: 1, step: 1 }, { seq: 1, time: header.createdAt + 950 }), + ...storage, + makeDshEvent("step/end", { turn: 1, step: 1 }, { seq: 11, time: logical.at(-1).time + 1 }), + makeDshEvent("turn/end", { turn: 1, reason: { kind: "completed" } }, { seq: 12, time: logical.at(-1).time + 2 }), + ])); + const oracle = [decoded.events[0], decoded.events[1], ...logical, decoded.events.at(-2), decoded.events.at(-1)]; + assert.deepEqual(decoded.events, oracle); + assert.equal(decoded.diagnostics.knownUnsupportedCount, 9); + assert.deepEqual(decoded.diagnostics.knownUnsupportedTypes, ["assistant/chunk"]); + assert.deepEqual(decoded.events.map((event) => event.seq), decoded.events.map((_, index) => index)); + for (const row of makeMalformedPackedDshStorageRows()) { + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl([header, row])), + (error) => ["DSH_MALFORMED_PACKED_ROW", "DSH_UNSUPPORTED_PACKED_ROW"].includes(error?.code)); + } +}); + +test("pinned types.ts accepts all seven raw StreamChunk variants and accounts them as unsupported evidence", () => { + const chunks = [ + { type: "block-start", index: 0, blockType: "text" }, + { type: "text-delta", index: 0, text: "visible" }, + { type: "reasoning-delta", index: 1, text: "reasoning" }, + { type: "tool-call-delta", index: 2, id: "raw-call", name: "fixture", argumentsDelta: "{}" }, + { type: "block-end", index: 2, block: { type: "tool-call", id: "raw-call", name: "fixture", arguments: "{}" } }, + { type: "usage", usage: { inputTokens: 3, outputTokens: 2, cacheWriteTokens: 1 } }, + { type: "finish", reason: { kind: "stop" }, replayState: { response: { id: "synthetic" }, blocks: [null] } }, + ]; + const decoded = decodeDshJsonl(encodeDshRawJsonl(rowsWithChunks(chunks))); + assert.deepEqual(decoded.events.filter((event) => event.type === "assistant/chunk") + .map((event) => event.data.chunk.type), chunks.map((chunk) => chunk.type)); + assert.equal(decoded.diagnostics.knownUnsupportedCount, chunks.length); + assert.deepEqual(decoded.diagnostics.knownUnsupportedTypes, ["assistant/chunk"]); +}); + +test("pinned types.ts rejects extra, missing, and wrong-typed fields on every StreamChunk variant", () => { + const cases = [ + [{ type: "block-start", index: 0, blockType: "text" }, "blockType", "index"], + [{ type: "text-delta", index: 0, text: "x" }, "text", "index"], + [{ type: "reasoning-delta", index: 0, text: "x" }, "text", "index"], + [{ type: "tool-call-delta", index: 0, id: "c", argumentsDelta: "{}" }, "id", "index"], + [{ type: "block-end", index: 0, block: { type: "text", text: "x" } }, "block", "index"], + [{ type: "usage", usage: { inputTokens: 1, outputTokens: 1 } }, "usage", "usage"], + [{ type: "finish", reason: { kind: "stop" } }, "reason", "reason"], + ]; + for (const [chunk, required, typed] of cases) { + const extra = { ...structuredClone(chunk), extra: true }; + const missing = structuredClone(chunk); + delete missing[required]; + const wrong = structuredClone(chunk); + wrong[typed] = null; + for (const candidate of [extra, missing, wrong]) { + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(rowsWithChunks([candidate]))), + stableError("DSH_EVENT_SHAPE_DRIFT")); + } + } +}); + +test("pinned message.ts accepts every ContextFormed variant, model replayState, and all five content blocks", () => { + const pluginSources = [ + { kind: "plugin", plugin: "fixture" }, + { kind: "plugin", plugin: "fixture", form: "instructions" }, + { kind: "plugin", plugin: "fixture", form: "catalog" }, + { kind: "plugin", plugin: "fixture", form: "snapshot", sections: [{ name: "state", text: "synthetic" }] }, + { kind: "plugin", plugin: "fixture", form: "notice", summary: "synthetic notice" }, + { kind: "plugin", plugin: "fixture", form: "relay" }, + { kind: "plugin", plugin: "fixture", form: "recall" }, + ]; + const header = makeDshHeader(); + const userEvents = pluginSources.map((source, seq) => makeDshEvent("user/message", { + id: `context-${seq}`, role: "user", content: [{ type: "text", text: "synthetic" }], source, + }, { seq, time: header.createdAt + seq + 1, surfaceOp: "append" })); + assert.equal(decodeDshJsonl(encodeDshRawJsonl([header, ...userEvents])).events.length, pluginSources.length); + + const blocks = [ + { type: "text", text: "visible" }, + { type: "reasoning", text: "thought" }, + { type: "image", attachment: { attachmentId: "image-1", mediaType: "image/png", bytes: 10, width: 2, height: 3, + name: "fixture.png" } }, + { type: "tool-call", id: "call-1", name: "fixture", arguments: "{}" }, + { type: "tool-result", toolCallId: "call-1", content: [{ type: "text", text: "done" }], isError: false }, + ]; + const rows = makeSupportedDshSessionRows(); + rows[5].data.message.content = blocks; + rows[5].data.message.source.replayState = { response: { providerId: "synthetic" }, blocks: [null, null] }; + const decoded = decodeDshJsonl(encodeDshRawJsonl(rows)); + assert.deepEqual(decoded.events[4].data.message.content.map((block) => block.type), + ["text", "reasoning", "image", "tool-call", "tool-result"]); +}); + +test("pinned message/content unions reject extra, missing, and wrong-typed fields for every variant", () => { + const header = makeDshHeader(); + const sourceVariants = [ + { kind: "plugin", plugin: "fixture" }, + { kind: "plugin", plugin: "fixture", form: "instructions" }, + { kind: "plugin", plugin: "fixture", form: "catalog" }, + { kind: "plugin", plugin: "fixture", form: "snapshot", sections: [] }, + { kind: "plugin", plugin: "fixture", form: "notice", summary: "notice" }, + { kind: "plugin", plugin: "fixture", form: "relay" }, + { kind: "plugin", plugin: "fixture", form: "recall" }, + ]; + const decodeSource = (source) => decodeDshJsonl(encodeDshRawJsonl([header, + makeDshEvent("user/message", { id: "source", role: "user", content: [], source }, + { seq: 0, time: header.createdAt + 1, surfaceOp: "append" })])); + for (const source of sourceVariants) { + const extra = { ...structuredClone(source), extra: true }; + const missing = structuredClone(source); + delete missing.plugin; + const wrong = { ...structuredClone(source), form: 42 }; + for (const candidate of [extra, missing, wrong]) { + assert.throws(() => decodeSource(candidate), stableError("DSH_EVENT_SHAPE_DRIFT")); + } + } + const invalidCoreSources = [ + { kind: "user", extra: true }, + { kind: "model", provider: "fixture", model: "model", extra: true }, + { kind: "model", provider: "fixture", replayState: {} }, + { kind: "tool", callId: "call", extra: true }, + ]; + assert.throws(() => decodeSource(invalidCoreSources[0]), stableError("DSH_EVENT_SHAPE_DRIFT")); + for (const source of invalidCoreSources.slice(1, 3)) { + const rows = makeSupportedDshSessionRows(); + rows[5].data.message.source = source; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(rows)), stableError("DSH_EVENT_SHAPE_DRIFT")); + } + const invalidTool = makeSupportedDshSessionRows(); + invalidTool[7].data.message.source = invalidCoreSources[3]; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(invalidTool)), stableError("DSH_EVENT_SHAPE_DRIFT")); + + const blocks = [ + [{ type: "text", text: "x" }, "text", "text"], + [{ type: "reasoning", text: "x" }, "text", "text"], + [{ type: "image", attachment: { attachmentId: "i", mediaType: "image/webp", bytes: 1, width: 1, height: 1 } }, + "attachment", "attachment"], + [{ type: "tool-call", id: "c", name: "t", arguments: "{}" }, "id", "arguments"], + [{ type: "tool-result", toolCallId: "c", content: [] }, "toolCallId", "content"], + ]; + const decodeBlock = (block) => decodeDshJsonl(encodeDshRawJsonl([header, + makeDshEvent("user/message", { id: "block", role: "user", content: [block], source: { kind: "user" } }, + { seq: 0, time: header.createdAt + 1, surfaceOp: "append" })])); + for (const [block, required, typed] of blocks) { + const extra = { ...structuredClone(block), extra: true }; + const missing = structuredClone(block); + delete missing[required]; + const wrong = structuredClone(block); + wrong[typed] = null; + for (const candidate of [extra, missing, wrong]) { + assert.throws(() => decodeBlock(candidate), stableError("DSH_EVENT_SHAPE_DRIFT")); + } + } +}); + +test("pinned tool-result meta and internal error remain independent from model-facing isError", () => { + const withMeta = makeSupportedDshSessionRows(); + withMeta[7].data.meta = { presentation: ["synthetic", 1, true, null] }; + assert.deepEqual(decodeDshJsonl(encodeDshRawJsonl(withMeta)).events[6].data.meta, + { presentation: ["synthetic", 1, true, null] }); + + const toolDeclaredOnly = makeSupportedDshSessionRows(); + delete toolDeclaredOnly[9].data.error; + assert.equal(decodeDshJsonl(encodeDshRawJsonl(toolDeclaredOnly)).events[8].data.message.content[0].isError, true); + + const internalOnly = makeSupportedDshSessionRows(); + internalOnly[7].data.error = { name: "InternalFailure", code: "INTERNAL_ONLY" }; + assert.equal(decodeDshJsonl(encodeDshRawJsonl(internalOnly)).events[6].data.message.content[0].isError, false); + + const malformed = makeSupportedDshSessionRows(); + malformed[7].data.error = { name: "MissingCode" }; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(malformed)), stableError("DSH_EVENT_SHAPE_DRIFT")); + assert.equal(isDshJsonValue({ meta: undefined }), false); + assert.equal(isDshJsonValue({ meta: Number.NaN }), false); + assert.equal(isDshJsonValue(new Date(0)), false); + const cyclic = {}; + cyclic.self = cyclic; + assert.equal(isDshJsonValue(cyclic), false); +}); + +test("logical invariant resets step numbering per turn and mirrors tool repair semantics", () => { + const header = makeDshHeader({ parentSession: undefined, seedLength: undefined, origin: undefined, + delegationDepth: 0, agentPreset: undefined }); + const time = header.createdAt + 1_000; + const twoTurns = [ + header, + makeDshEvent("turn/start", { turn: 1 }, { seq: 0, time }), + makeDshEvent("step/start", { turn: 1, step: 1 }, { seq: 1, time: time + 1 }), + makeDshEvent("tool/call", { turn: 1, step: 1, callId: "unfinished", name: "fixture", arguments: "{}" }, + { seq: 2, time: time + 2 }), + makeDshEvent("step/end", { turn: 1, step: 1 }, { seq: 3, time: time + 3 }), + makeDshEvent("turn/end", { turn: 1, reason: { kind: "completed" } }, { seq: 4, time: time + 4 }), + makeDshEvent("turn/start", { turn: 2 }, { seq: 5, time: time + 5 }), + makeDshEvent("step/start", { turn: 2, step: 1 }, { seq: 6, time: time + 6 }), + makeDshEvent("tool/result", { + turn: 2, step: 1, + message: { id: "repair-result", role: "user", content: [{ type: "tool-result", + toolCallId: "not-started", content: [], isError: true }], source: { kind: "tool", callId: "not-started" } }, + error: { name: "ToolNotStartedError", code: "TOOL_NOT_STARTED" }, + }, { seq: 7, time: time + 7, surfaceOp: "append" }), + makeDshEvent("step/end", { turn: 2, step: 1 }, { seq: 8, time: time + 8 }), + makeDshEvent("turn/end", { turn: 2, reason: { kind: "completed" } }, { seq: 9, time: time + 9 }), + ]; + assert.equal(decodeDshJsonl(encodeDshRawJsonl(twoTurns)).events.length, 10); +}); + +test("replace tool results require only the open turn and do not re-correlate a completed call", () => { + const rows = makeSupportedDshSessionRows(); + const replacement = structuredClone(rows[7]); + replacement.seq = 10; + replacement.time = rows.at(-1).time - 1; + replacement.data.message.content[0].content = [{ type: "text", text: "replacement content" }]; + replacement.sourceEventSeqs = [6]; + replacement.surfaceOp = { op: "replace", start: 6, end: 6 }; + rows.splice(rows.length - 1, 0, replacement); + rows.at(-1).seq = 11; + assert.equal(decodeDshJsonl(encodeDshRawJsonl(rows)).events.at(-2).surfaceOp.op, "replace"); +}); + +test("pinned surface.ts folds real nodes, complete provenance sets, and non-adjacent raw seqs", () => { + const rows = makeSupportedDshSessionRows(); + const replacement = structuredClone(rows[3]); + replacement.time = rows.at(-1).time - 1; + replacement.data.id = "surface-replacement"; + replacement.sourceEventSeqs = [6, 4]; + replacement.surfaceOp = { op: "replace", start: 4, end: 6 }; + const decoded = decodeDshJsonl(encodeDshRawJsonl(insertBeforeTurnEnd(rows, [replacement]))); + assert.deepEqual(decoded.events.at(-2).sourceEventSeqs, [6, 4]); +}); + +test("pinned surface.ts rejects missing, reversed, removed, or non-surface replacement endpoints", () => { + const base = makeSupportedDshSessionRows(); + const userReplacement = (op, sources) => { + const event = structuredClone(base[3]); + event.time = base.at(-1).time - 2; + event.data.id = "surface-replacement"; + event.surfaceOp = op; + event.sourceEventSeqs = sources; + return event; + }; + for (const event of [ + userReplacement({ op: "replace", start: 999, end: 999 }, [2]), + userReplacement({ op: "replace", start: 4, end: 2 }, [2, 3, 4]), + userReplacement({ op: "replace", start: 5, end: 5 }, [5]), + ]) { + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(insertBeforeTurnEnd(base, [event]))), + stableError("DSH_SURFACE_RANGE_INVALID")); + } + const first = userReplacement({ op: "replace", start: 2, end: 2 }, [2]); + const second = userReplacement({ op: "replace", start: 2, end: 2 }, [2]); + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(insertBeforeTurnEnd(base, [first, second]))), + stableError("DSH_SURFACE_RANGE_INVALID")); +}); + +test("pinned surface.ts requires complete, unique, earlier provenance with empty only for assistant", () => { + const base = makeSupportedDshSessionRows(); + const replacement = structuredClone(base[3]); + replacement.time = base.at(-1).time - 1; + replacement.data.id = "surface-replacement"; + replacement.surfaceOp = { op: "replace", start: 2, end: 4 }; + replacement.sourceEventSeqs = [2, 4]; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(insertBeforeTurnEnd(base, [replacement]))), + stableError("DSH_SURFACE_PROVENANCE_INCOMPLETE")); + + for (const index of [3, 7]) { + const empty = structuredClone(base); + empty[index].sourceEventSeqs = []; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(empty)), stableError("DSH_EVENT_SHAPE_DRIFT")); + } + assert.deepEqual(decodeDshJsonl(encodeDshRawJsonl(base)).events[4].sourceEventSeqs, []); +}); + +test("pinned surface.ts tool-result replacement changes only one current result content", () => { + const base = makeSupportedDshSessionRows(); + const toolReplacement = (target = 6) => { + const event = structuredClone(base[7]); + event.time = base.at(-1).time - 1; + event.data.message.content[0].content = [{ type: "text", text: "rewritten" }]; + event.surfaceOp = { op: "replace", start: target, end: target }; + event.sourceEventSeqs = [target]; + return event; + }; + assert.doesNotThrow(() => decodeDshJsonl(encodeDshRawJsonl(insertBeforeTurnEnd(base, [toolReplacement()])))); + + const nonToolTarget = toolReplacement(2); + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(insertBeforeTurnEnd(base, [nonToolTarget]))), + stableError("DSH_SURFACE_TOOL_REWRITE_INVALID")); + const identityMutation = toolReplacement(); + identityMutation.data.message.id = "mutated-id"; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(insertBeforeTurnEnd(base, [identityMutation]))), + stableError("DSH_SURFACE_TOOL_REWRITE_INVALID")); + const metaMutation = toolReplacement(); + metaMutation.data.meta = { changed: true }; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(insertBeforeTurnEnd(base, [metaMutation]))), + stableError("DSH_SURFACE_TOOL_REWRITE_INVALID")); +}); + +test("known turn-scoped events are rejected outside an open turn", () => { + const header = makeDshHeader(); + for (const type of ["todo/write", "request/header", "request/context"]) { + const event = makeDshEvent(type, { synthetic: true }, { seq: 0, time: header.createdAt + 1 }); + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl([header, event])), stableError("DSH_EVENT_ORDER_INVALID")); + } +}); + +test("all six terminal outcomes decode and malformed outcome drift fails closed", () => { + for (const kind of ["completed", "aborted", "blocked", "error", "max-tokens", "interrupted"]) { + const decoded = decodeDshJsonl(encodeDshRawJsonl(makeTerminalDshSessionRows(kind))); + assert.equal(decoded.events.at(-1).data.reason.kind, kind); + } + const malformed = [ + { kind: "aborted", reason: { kind: "hook" } }, + { kind: "aborted", reason: { kind: "user", extra: true } }, + { kind: "error", error: { message: "missing code" } }, + { kind: "error", error: { message: "x", code: "X", extra: true } }, + { kind: "completed", extra: true }, + ]; + for (const reason of malformed) { + const rows = makeTerminalDshSessionRows("completed"); + rows.at(-1).data.reason = reason; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(rows)), stableError("DSH_EVENT_SHAPE_DRIFT")); + } +}); + +test("format guards reject negative zero and tool-result error shape drift", () => { + for (const field of ["createdAt", "delegationDepth"]) { + const rows = makeSupportedDshSessionRows(); + const bytes = encodeDshRawJsonl(rows); + const text = bytes.toString("utf8").replace(new RegExp(`"${field}":\\d+`, "u"), `"${field}":-0`); + assert.throws(() => decodeDshJsonl(Buffer.from(text, "utf8")), stableError("DSH_INVALID_HEADER")); + } + const rows = makeSupportedDshSessionRows(); + rows[9].data.error.extra = true; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(rows)), stableError("DSH_EVENT_SHAPE_DRIFT")); +}); + +test("pinned format accepts empty agent preset and injectively encodes NUL session ids", () => { + const rows = makeOpenTurnDshRows({ sessionId: "nul\0session", agentPreset: "" }); + const decoded = decodeDshJsonl(encodeDshRawJsonl(rows)); + assert.equal(decoded.header.id, "nul\0session"); + assert.equal(decoded.header.agentPreset, ""); +}); + +test("header lineage accepts seed parent independently of depth but pins origin discriminator", () => { + const parentAtDepthZero = makeDshHeader({ delegationDepth: 0, origin: "subagent" }); + const rows = makeOpenTurnDshRows({ delegationDepth: 0, origin: "subagent" }); + rows[0] = parentAtDepthZero; + assert.equal(decodeDshJsonl(encodeDshRawJsonl(rows)).header.parentSession, parentAtDepthZero.parentSession); + const invalidOrigin = structuredClone(rows); + invalidOrigin[0].origin = "invented-origin"; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(invalidOrigin)), stableError("DSH_INVALID_HEADER")); +}); + +test("surface records require exact correlation, causal source references, and surface operations", () => { + const rows = makeSupportedDshSessionRows(); + for (const index of [3, 4, 7]) { + const malformed = structuredClone(rows); + delete malformed[index].surfaceOp; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(malformed)), stableError("DSH_EVENT_SHAPE_DRIFT")); + } + const futureSource = structuredClone(rows); + futureSource[5].sourceEventSeqs = [futureSource[5].seq]; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(futureSource)), stableError("DSH_EVENT_SHAPE_DRIFT")); + const duplicateSource = structuredClone(rows); + duplicateSource[5].sourceEventSeqs = [0, 0]; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(duplicateSource)), stableError("DSH_EVENT_SHAPE_DRIFT")); + const mismatchedResult = structuredClone(rows); + mismatchedResult[7].data.message.content[0].toolCallId = "different-call"; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(mismatchedResult)), stableError("DSH_EVENT_SHAPE_DRIFT")); + const independentToolError = structuredClone(rows); + delete independentToolError[9].data.error; + assert.equal(decodeDshJsonl(encodeDshRawJsonl(independentToolError)).events[8].data.message.content[0].isError, true); +}); + +test("header, format, sequence, identity, project key, raw syntax, and unknown-required defects are rejected", async () => { + const directCases = [ + [makeBadVersionDshRows(), "DSH_UNSUPPORTED_SESSION_FORMAT"], + [makeBadHeaderDshRows(), "DSH_INVALID_HEADER"], + [makeBadSequenceDshRows(), "DSH_INVALID_EVENT"], + ]; + for (const [rows, code] of directCases) assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(rows)), stableError(code)); + assert.throws(() => decodeDshJsonl(makeMalformedDshJsonlBytes()), stableError("DSH_MALFORMED_JSONL")); + const valid = encodeDshRawJsonl(makeSupportedDshSessionRows()); + assert.throws(() => decodeDshJsonl(valid.subarray(0, -1)), stableError("DSH_INCOMPLETE_JSONL_LINE")); + assert.throws(() => decodeDshJsonl(Buffer.concat([valid.subarray(0, valid.length - 1), Buffer.from("\n\n")])), + stableError("DSH_BLANK_JSONL_LINE")); + const unknownRows = appendEvent(makeSupportedDshSessionRows(), makeUnknownRequiredDshEvent({ seq: 10 })); + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(unknownRows)), stableError("DSH_UNKNOWN_REQUIRED_EVENT")); + + const root = await tempRoot(); + const home = path.join(root, "home"); + const workspace = path.join(root, "workspace"); + const identity = makeBadIdentityDshFixture({ artifactSessionId: "path-id", workspace }); + await writeNestedDshArtifact({ dshHome: home, rows: identity.rows, sessionId: identity.artifactSessionId }); + const wrongProjectRows = makeSupportedDshSessionRows({ workspace, sessionId: "bad-project" }); + await writeNestedDshArtifact({ dshHome: home, rows: wrongProjectRows, projectSegment: "--wrong--" }); + const result = await inventory(home, workspace); + assert.equal(result.sessions.length, 0); + assert.equal(result.warnings.filter((warning) => warning.code === "dsh-artifact-rejected").length, 2); + assert.equal(result.warnings.some((warning) => warning.reason === "DSH_SESSION_ID_PATH_MISMATCH"), true); + assert.equal(result.warnings.some((warning) => warning.reason === "DSH_PROJECT_KEY_MISMATCH"), true); +}); + +test("flat, wrong-depth, and dual-encoding layouts fail closed without hiding valid siblings", async () => { + const root = await tempRoot(); + const home = path.join(root, "home"); + const workspace = path.join(root, "workspace"); + const sessionsRoot = path.join(home, "sessions"); + await mkdir(sessionsRoot, { recursive: true }); + await writeFile(path.join(sessionsRoot, "session.jsonl"), encodeDshRawJsonl(makeSupportedDshSessionRows({ workspace }))); + const rows = makeSupportedDshSessionRows({ workspace, sessionId: "dual" }); + const dual = await writeNestedDshArtifact({ dshHome: home, rows }); + await writeFile(`${dual.filePath}.zstd`, Buffer.from("ambiguous")); + const deep = path.join(sessionsRoot, fixtureProjectKey(workspace), encodeDshSessionIdSegment("too-deep"), "extra"); + await mkdir(deep, { recursive: true }); + await writeFile(path.join(deep, "session.jsonl"), encodeDshRawJsonl(rows)); + await writeNestedDshArtifact({ dshHome: home, rows: makeSupportedDshSessionRows({ workspace, sessionId: "valid-sibling" }) }); + const result = await inventory(home, workspace); + assert.deepEqual(result.sessions.map((session) => session.sessionId), ["valid-sibling"]); + assert.equal(result.warnings.some((warning) => warning.code === "dsh-flat-artifact-rejected"), true); + assert.equal(result.warnings.some((warning) => warning.code === "dsh-wrong-depth-rejected"), true); + assert.equal(result.warnings.some((warning) => warning.code === "dsh-ambiguous-artifact-rejected"), true); +}); + +test("known unsupported and unknown ignorable events are accounted, while open turns are admitted incomplete", async () => { + const known = decodeDshJsonl(encodeDshRawJsonl(makeKnownUnsupportedDshSessionRows())); + assert.deepEqual(known.diagnostics.knownUnsupportedTypes, ["todo/write"]); + assert.equal(known.diagnostics.knownUnsupportedCount, 1); + const base = makeSupportedDshSessionRows(); + const ignorable = appendEvent(base, makeUnknownIgnorableDshEvent({ seq: 10 })); + const decodedIgnorable = decodeDshJsonl(encodeDshRawJsonl(ignorable)); + assert.equal(decodedIgnorable.diagnostics.unknownIgnorableTypes.length, 1); + assert.match(decodedIgnorable.diagnostics.unknownIgnorableTypes[0], /^unknown:[0-9a-f]{12}$/u); + assert.equal(decodedIgnorable.events.some((event) => event.type === "fixture-future/ignorable"), true); + + const root = await tempRoot(); + const home = path.join(root, "home"); + const workspace = path.join(root, "workspace"); + const written = await writeNestedDshArtifact({ dshHome: home, + rows: makeOpenTurnDshRows({ workspace, sessionId: "open-turn" }) }); + const before = await readFile(written.filePath); + const result = await inventory(home, workspace); + assert.equal(result.sessions[0].incomplete, true); + assert.equal(result.sessions[0].diagnostics.incompleteReason, "open-turn"); + assert.equal(result.warnings.some((warning) => warning.code === "dsh-incomplete-session"), true); + assert.deepEqual(await readFile(written.filePath), before); +}); + +test("pinned types.ts validates todo, request context, and end-seed payloads before accounting", () => { + const base = makeSupportedDshSessionRows(); + const validEvents = [ + makeDshEvent("todo/write", { + todos: [ + { content: "Inspect contract", status: "pending" }, + { content: "Implement validator", status: "in_progress" }, + { content: "Review evidence", status: "completed" }, + ], + }), + requestHeaderEvent({ config: { provider: "synthetic", model: "fixture" } }), + makeDshEvent("request/context", { provider: "synthetic", model: "fixture", contextWindow: 128_000 }), + makeDshEvent("session/end-seed", {}), + ]; + const decoded = decodeDshJsonl(encodeDshRawJsonl(insertBeforeTurnEnd(base, validEvents))); + assert.deepEqual(decoded.diagnostics.knownUnsupportedTypes, + ["request/context", "request/header", "session/end-seed", "todo/write"]); + + const malformedTodos = [ + {}, + { todos: [], extra: true }, + { todos: "not-an-array" }, + { todos: [{}] }, + { todos: [{ content: 7, status: "pending" }] }, + { todos: [{ content: "x", status: "started" }] }, + { todos: [{ content: "x", status: "pending", extra: true }] }, + ]; + for (const data of malformedTodos) { + const rows = insertBeforeTurnEnd(base, [makeDshEvent("todo/write", data)]); + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(rows)), stableError("DSH_EVENT_SHAPE_DRIFT")); + } +}); + +test("the complete pinned known-event catalog rejects malformed payloads instead of accepting names alone", () => { + const base = makeSupportedDshSessionRows(); + for (const type of PINNED_KNOWN_EVENT_TYPES) { + const rows = insertBeforeTurnEnd(base, [makeDshEvent(type, { malformed: true })]); + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(rows)), `${type} accepted malformed required data`); + } +}); + +test("pinned request headers validate initial, resume, and changed snapshots", () => { + const base = makeSupportedDshSessionRows(); + const initial = insertRequestHeader(base, requestHeaderEvent()); + assert.equal(decodeDshJsonl(encodeDshRawJsonl(initial)).diagnostics.knownUnsupportedTypes + .includes("request/header"), true); + const secondResume = insertBeforeTurnEnd(base, [requestHeaderEvent(), requestHeaderEvent({ reason: "resume" })]); + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(secondResume)), + stableError("DSH_EVENT_RELATIONSHIP_INVALID")); + const changed = insertBeforeTurnEnd(base, [ + requestHeaderEvent(), + requestHeaderEvent({ reason: "change", config: { provider: "fixture-provider", model: "fixture-model-2" } }), + ]); + assert.doesNotThrow(() => decodeDshJsonl(encodeDshRawJsonl(changed))); + const emptyOptional = requestHeaderEvent({ + config: { provider: "", model: "" }, adapterDefaults: {}, system: "", tools: [], + }); + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(insertRequestHeader(base, emptyOptional))), + stableError("DSH_EVENT_SHAPE_DRIFT")); + + const full = requestHeaderEvent({ + reason: "initial", + config: { + provider: "fixture-provider", + model: "fixture-model", + reasoningEffort: "high", + temperature: 0.25, + maxTokens: 4_096, + stop: ["STOP", "DONE"], + }, + adapterDefaults: { reasoningEffort: true, maxTokens: true }, + system: `System metadata ${DSH_FIXTURE_SECRET}`, + tools: [{ + name: "read_file", + description: `Tool metadata ${DSH_FIXTURE_SECRET}`, + parameters: { + type: "object", + properties: { path: { type: "string" } }, + required: ["path"], + additionalProperties: false, + }, + }], + }); + const decoded = decodeDshJsonl(encodeDshRawJsonl(insertRequestHeader(base, full))); + assert.equal(decoded.events.find((event) => event.type === "request/header").data.header.system, + `System metadata ${DSH_FIXTURE_SECRET}`); + assert.equal(JSON.stringify(decoded.diagnostics).includes(DSH_FIXTURE_SECRET), false); +}); + +test("pinned headless snapshot-like base flow decodes all required writer records account-only", () => { + const decoded = decodeDshJsonl(encodeDshRawJsonl(makeNativeSnapshotDshSessionRows())); + assert.equal(decoded.incomplete, false); + assert.deepEqual(decoded.events.map((event) => event.seq), Array.from({ length: 32 }, (_value, index) => index)); + for (const type of ["permission/preset", "sandbox/mode", "approval/policy", "agent/inbox/spliced", + "session/title", "request/header", "request/context", "session/title-llm-request"]) { + assert.equal(decoded.diagnostics.knownUnsupportedTypes.includes(type), true, type); + } +}); + +test("pinned fork snapshot admits a turn-2 descriptor before the resumed lifecycle request", () => { + const base = makeNativeSnapshotDshSessionRows(); + // The persisted end-seed marker is at seq 32, while this resume's actual live boundary is seq 33. + base[0].seedLength = base.length - 1; + base[0].parentSession = "fixture-parent"; + base[0].origin = "subagent"; + base[0].delegationDepth = 1; + const events = base.slice(1); + const push = (type, data, extra = {}) => events.push(makeDshEvent(type, data, { + seq: events.length, time: base[0].createdAt + 5_000 + events.length, ...extra, + })); + const message = { + id: "fixture-fork-user", role: "user", content: [{ type: "text", text: "Resume the child." }], + source: { kind: "user" }, + }; + push("session/end-seed", {}); + push("subagent/descriptor", { + version: 2, mode: "continuable", provider: "fork", label: "Synthetic child", + }); + push("approval/policy", { policy: "never", source: "delegation" }); + push("agent/inbox/spliced", { target: "next-turn", start: 0, inserted: [message] }); + push("turn/start", { turn: 2 }); + push("agent/inbox/spliced", { target: "next-turn", start: 0, removedCount: 1, inserted: [] }); + push("step/start", { turn: 2, step: 1 }); + push("user/message", message, { surfaceOp: "append" }); + push("request/header", { + header: { config: { provider: "fixture-provider", model: "fixture-model", reasoningEffort: "off" } }, + reason: "resume", + }); + push("request/context", { provider: "fixture-provider", model: "fixture-model" }); + push("step/end", { turn: 2, step: 1 }); + push("turn/end", { turn: 2, reason: { kind: "completed" } }); + const decoded = decodeDshJsonl(encodeDshRawJsonl([base[0], ...events])); + assert.equal(decoded.incomplete, false); + assert.equal(decoded.diagnostics.knownUnsupportedTypes.includes("subagent/descriptor"), true); +}); + +test("pinned inbox replay starts at header seedLength and reports only live pending input", () => { + const header = makeDshHeader({ seedLength: 1 }); + const message = { + id: "fixture-seed-pending", role: "user", content: [{ type: "text", text: "Seed input." }], + source: { kind: "user" }, + }; + const seedOnly = [ + header, + makeDshEvent("agent/inbox/spliced", { target: "next-turn", start: 0, inserted: [message] }, { seq: 0 }), + makeDshEvent("session/end-seed", {}, { seq: 1 }), + ]; + const decodedSeed = decodeDshJsonl(encodeDshRawJsonl(seedOnly)); + assert.equal(decodedSeed.incomplete, false); + assert.equal(decodedSeed.diagnostics.pendingInboxMessageCount, undefined); + + const livePending = structuredClone(seedOnly); + livePending.push(makeDshEvent("agent/inbox/spliced", { + target: "next-step", start: 0, inserted: [{ ...message, id: "fixture-live-pending" }], + }, { seq: 2 })); + const decodedLive = decodeDshJsonl(encodeDshRawJsonl(livePending)); + assert.equal(decodedLive.incomplete, true); + assert.equal(decodedLive.diagnostics.incompleteReason, "pending-inbox"); + assert.equal(decodedLive.diagnostics.pendingInboxMessageCount, 1); + + const badBounds = structuredClone(seedOnly); + badBounds.push(makeDshEvent("agent/inbox/spliced", { + target: "next-turn", start: 1, inserted: [], + }, { seq: 2 })); + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(badBounds)), + stableError("DSH_EVENT_RELATIONSHIP_INVALID")); +}); + +test("pinned request and title relationships reject drift while allowing an auxiliary title route", () => { + const base = makeSupportedDshSessionRows(); + const initial = requestHeaderEvent(); + const alternateTitle = makeDshEvent("session/title-llm-request", { + titleProvider: "fixture-title-provider", + messageSeqs: [2], + route: { provider: "aux-provider", model: "aux-model" }, + system: "private title system", + messages: [{ + id: "fixture-title-message", role: "user", content: [{ type: "text", text: "private title prompt" }], + source: { kind: "plugin", plugin: "fixture-title" }, + }], + maxTokens: 32, + }); + assert.doesNotThrow(() => decodeDshJsonl(encodeDshRawJsonl(insertBeforeTurnEnd(base, [initial, alternateTitle])))); + + const noHeaderContext = insertBeforeTurnEnd(base, [ + makeDshEvent("request/context", { provider: "fixture-provider", model: "fixture-model" }), + ]); + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(noHeaderContext)), + stableError("DSH_EVENT_RELATIONSHIP_INVALID")); + const mismatch = insertBeforeTurnEnd(base, [initial, + makeDshEvent("request/context", { provider: "other", model: "fixture-model" })]); + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(mismatch)), + stableError("DSH_EVENT_RELATIONSHIP_INVALID")); + for (const headers of [ + [requestHeaderEvent({ reason: "resume" })], + [initial, requestHeaderEvent()], + [initial, requestHeaderEvent({ reason: "change" })], + ]) { + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(insertBeforeTurnEnd(base, headers))), + stableError("DSH_EVENT_RELATIONSHIP_INVALID")); + } + + const extraHuman = { + id: "fixture-second-human", role: "user", content: [{ type: "text", text: "Second human prompt." }], + source: { kind: "user" }, + }; + const reversedTitle = insertBeforeTurnEnd(base, [ + makeDshEvent("user/message", extraHuman, { surfaceOp: "append" }), + makeDshEvent("session/title", { + title: "Synthetic provider title", messageSeqs: [10, 2], source: { kind: "provider", provider: "fixture" }, + }), + ]); + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(reversedTitle)), + stableError("DSH_EVENT_SHAPE_DRIFT")); + const invalidFallback = insertBeforeTurnEnd(base, [makeDshEvent("session/title", { + title: "Invalid fallback", messageSeqs: [3], source: { kind: "fallback" }, + })]); + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(invalidFallback)), + stableError("DSH_EVENT_RELATIONSHIP_INVALID")); +}); + +test("pinned descriptor and inbox relations reject duplicate, late, and cross-queue identity drift", () => { + const header = makeDshHeader({ seedLength: undefined }); + const descriptor = { + version: 2, mode: "continuable", provider: "fixture", label: "Synthetic child", + agentProvider: "fixture-provider", agentModel: "fixture-model", persona: "Synthetic persona", + toolFilter: { allow: ["read_file"], deny: ["shell_exec"] }, + }; + const fresh = [header, + makeDshEvent("subagent/descriptor", descriptor, { seq: 0 }), + makeDshEvent("turn/start", { turn: 1 }, { seq: 1 }), + makeDshEvent("turn/end", { turn: 1, reason: { kind: "completed" } }, { seq: 2 })]; + assert.doesNotThrow(() => decodeDshJsonl(encodeDshRawJsonl(fresh))); + const duplicate = [ + header, + makeDshEvent("subagent/descriptor", descriptor, { seq: 0 }), + makeDshEvent("subagent/descriptor", descriptor, { seq: 1 }), + ]; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(duplicate)), + stableError("DSH_EVENT_RELATIONSHIP_INVALID")); + const late = [ + header, + makeDshEvent("turn/start", { turn: 1 }, { seq: 0 }), + makeDshEvent("step/start", { turn: 1, step: 1 }, { seq: 1 }), + requestHeaderEvent(), + makeDshEvent("subagent/descriptor", descriptor, { seq: 3 }), + makeDshEvent("step/end", { turn: 1, step: 1 }, { seq: 4 }), + makeDshEvent("turn/end", { turn: 1, reason: { kind: "completed" } }, { seq: 5 }), + ]; + late.slice(1).forEach((event, index) => { event.seq = index; }); + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(late)), + stableError("DSH_EVENT_RELATIONSHIP_INVALID")); + + const message = { + id: "duplicate-pending", role: "user", content: [{ type: "text", text: "Synthetic pending." }], + source: { kind: "user" }, + }; + const duplicateInbox = [ + header, + makeDshEvent("agent/inbox/spliced", { target: "next-turn", start: 0, inserted: [message] }, { seq: 0 }), + makeDshEvent("agent/inbox/spliced", { target: "next-step", start: 0, inserted: [message] }, { seq: 1 }), + ]; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(duplicateInbox)), + stableError("DSH_EVENT_RELATIONSHIP_INVALID")); + const invalidCanceled = [header, + makeDshEvent("agent/inbox/spliced", { + target: "next-turn", start: 0, inserted: [], outcome: "canceled", + }, { seq: 0 })]; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(invalidCanceled)), + stableError("DSH_EVENT_RELATIONSHIP_INVALID")); + + const seededDescriptor = [ + makeDshHeader({ seedLength: 3 }), + makeDshEvent("turn/start", { turn: 1 }, { seq: 0 }), + makeDshEvent("subagent/descriptor", { version: 2, mode: "one-shot", provider: "fixture" }, { seq: 1 }), + makeDshEvent("turn/end", { turn: 1, reason: { kind: "completed" } }, { seq: 2 }), + makeDshEvent("session/end-seed", {}, { seq: 3 }), + makeDshEvent("subagent/descriptor", descriptor, { seq: 4 }), + makeDshEvent("turn/start", { turn: 2 }, { seq: 5 }), + makeDshEvent("turn/end", { turn: 2, reason: { kind: "completed" } }, { seq: 6 }), + ]; + assert.doesNotThrow(() => decodeDshJsonl(encodeDshRawJsonl(seededDescriptor))); + const duplicateOwn = structuredClone(seededDescriptor); + duplicateOwn.splice(6, 0, makeDshEvent("subagent/descriptor", descriptor, { seq: 6 })); + duplicateOwn.slice(1).forEach((event, index) => { event.seq = index; }); + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(duplicateOwn)), + stableError("DSH_EVENT_RELATIONSHIP_INVALID")); +}); + +test("pinned end-seed lifecycle requires initial or resume first and change only afterward", () => { + const header = makeDshHeader({ seedLength: 6 }); + const events = [ + makeDshEvent("turn/start", { turn: 1 }), + makeDshEvent("step/start", { turn: 1, step: 1 }), + requestHeaderEvent(), + makeDshEvent("step/end", { turn: 1, step: 1 }), + makeDshEvent("turn/end", { turn: 1, reason: { kind: "completed" } }), + makeDshEvent("session/end-seed", {}), + makeDshEvent("session/end-seed", {}), + makeDshEvent("turn/start", { turn: 2 }), + makeDshEvent("step/start", { turn: 2, step: 1 }), + requestHeaderEvent({ reason: "resume" }), + makeDshEvent("step/end", { turn: 2, step: 1 }), + makeDshEvent("turn/end", { turn: 2, reason: { kind: "completed" } }), + ]; + events.forEach((event, index) => { event.seq = index; event.time += index; }); + assert.doesNotThrow(() => decodeDshJsonl(encodeDshRawJsonl([header, ...events]))); + + const firstLiveChange = structuredClone(events); + firstLiveChange[9].data.reason = "change"; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl([header, ...firstLiveChange])), + stableError("DSH_EVENT_RELATIONSHIP_INVALID")); + const secondResume = structuredClone(events); + secondResume.splice(10, 0, requestHeaderEvent({ reason: "resume" })); + secondResume.forEach((event, index) => { event.seq = index; }); + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl([header, ...secondResume])), + stableError("DSH_EVENT_RELATIONSHIP_INVALID")); +}); + +test("pinned common writer records use exact account-only payload validators", () => { + const time = Date.parse("2026-08-18T00:00:00.000Z"); + const cases = [ + ["agent-preset/selected", { agentPreset: "minimal" }], + ["feedback/record", { text: "synthetic feedback" }], + ["goal/change", { + kind: "goal/change", version: 1, operation: "create", + goal: { id: "goal-1", revision: 1, objective: "Synthetic goal", phase: "active", maxGoalRounds: 3 }, + roundsStarted: 0, createdAt: time, updatedAt: time, + }], + ["plan/mode", { active: true }], + ["todo/write", { todos: [{ content: "Synthetic task", status: "pending" }] }], + ["schedule/change", { + version: 1, operation: "create", schedule: { + id: "schedule-1", kind: "after", prompt: "Synthetic reminder", afterSeconds: 60, + scheduledAt: "2026-08-19T00:00:00.000Z", + }, + }], + ["web/deepseek-search-llm-request", { + endpoint: "https://api.deepseek.com/anthropic/v1/messages", + apiVersion: "2023-06-01", + body: { + model: "deepseek-v4-flash", max_tokens: 4_096, + messages: [{ role: "user", content: [{ type: "text", text: "Synthetic search" }] }], + tools: [{ type: "web_search_20250305", name: "web_search", max_uses: 5 }], + }, + }], + ["session/end-seed", {}], + ]; + const base = makeSupportedDshSessionRows(); + for (const [type, data] of cases) { + const rows = insertBeforeTurnEnd(base, [makeDshEvent(type, data)]); + const decoded = decodeDshJsonl(encodeDshRawJsonl(rows)); + assert.equal(decoded.diagnostics.knownUnsupportedTypes.includes(type), true, type); + const malformed = structuredClone(data); + malformed.__extra = true; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl( + insertBeforeTurnEnd(base, [makeDshEvent(type, malformed)]))), `${type} accepted an extra payload key`); + } +}); + +test("pinned MessageSourceMap extensions accept exact private context arms and reject union drift", () => { + const base = makeSupportedDshSessionRows(); + const sources = [ + { kind: "plugin", plugin: "fixture-context" }, + { kind: "plugin", plugin: "fixture-context", form: "snapshot", sections: [{ name: "scope", text: "private" }] }, + { kind: "agent-instructions", form: "instructions", baseline: true, baselineIdentity: "private", + changes: [{ action: "set", scope: "workspace", path: "AGENTS.md", digest: "private" }] }, + { kind: "session-reference", form: "recall", version: 1, references: [{ + sessionId: "private-session", label: "private-label", capturedThroughSeq: null, compacted: false, + originalMessages: 2, retainedMessages: 1, omittedMessages: 1, omittedBytes: 12, truncated: true, inputIndex: 0, + }] }, + { kind: "coordinator", form: "relay", senderSessionId: "private-parent" }, + { kind: "subagent-report", form: "relay", senderSessionId: "private-child" }, + { kind: "subagent-settled", form: "notice", summary: "private summary", senderSessionId: "private-child" }, + { kind: "skill-catalog", form: "catalog", update: true, + entries: [{ name: "private-skill", description: "private description" }] }, + { kind: "skill-invocation", form: "instructions", name: "private-skill" }, + ]; + for (const source of sources) { + const rows = structuredClone(base); + rows[4].data.source = source; + assert.doesNotThrow(() => decodeDshJsonl(encodeDshRawJsonl(rows)), source.kind); + rows[4].data.source.extra = true; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(rows)), stableError("DSH_EVENT_SHAPE_DRIFT")); + } + const apiHuman = structuredClone(base); + apiHuman[3].data.source = { kind: "user", rpcId: "private-rpc", clientTimeZone: "Asia/Shanghai" }; + assert.doesNotThrow(() => decodeDshJsonl(encodeDshRawJsonl(apiHuman))); + const badApiHuman = structuredClone(apiHuman); + delete badApiHuman[3].data.source.rpcId; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(badApiHuman)), stableError("DSH_EVENT_SHAPE_DRIFT")); + const compactWithoutIdentity = structuredClone(base); + compactWithoutIdentity[4].data.source = { kind: "plugin", plugin: "compact" }; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(compactWithoutIdentity)), + stableError("DSH_EVENT_SHAPE_DRIFT")); +}); + +test("pinned approval command hook workflow dispatch and retry folds admit one correlated stream", () => { + const header = makeDshHeader({ seedLength: undefined }); + const events = []; + const push = (type, data, extra = {}) => events.push(makeDshEvent(type, data, { + seq: events.length, time: header.createdAt + events.length + 1, ...extra, + })); + push("turn/start", { turn: 1 }); + push("step/start", { turn: 1, step: 1 }); + push("request/header", { + header: { config: { provider: "fixture-provider", model: "fixture-model" } }, reason: "initial", + }); + push("approval/asked", { id: "approval-1", toolName: "bash", callId: "call-1" }); + push("approval/decided", { id: "approval-1", outcome: "allowed-once" }); + push("command/run", { commandId: "command-1", name: "review", source: { kind: "user" } }); + push("command/done", { commandId: "command-1", kind: "success", sourceEventSeq: 2 }); + push("hook/invoked", { turn: 1, point: "PreToolUse", dialect: "codex", handlerId: "hook-1" }); + push("hook/result", { turn: 1, point: "PreToolUse", handlerId: "hook-1", decision: "pass", durationMs: 0.5 }); + push("tool-workflow/run-start", { runId: "workflow-1", name: "review" }); + push("tool-workflow/agent-start", { runId: "workflow-1", seq: 1, label: "worker", childId: "child-1" }); + push("tool-workflow/agent-end", { runId: "workflow-1", seq: 1, outcome: "completed" }); + push("tool-workflow/run-end", { runId: "workflow-1", stopReason: "completed" }); + push("tool/code-dispatch-start", { + rootCallId: "root", parentCallId: "root", subCallId: "child", name: "read", arguments: {}, + }); + push("tool/code-dispatch", { + rootCallId: "root", parentCallId: "child", subCallId: "grandchild", name: "read", arguments: {}, + isError: false, content: [{ type: "text", text: "ok" }], + }); + push("llm/retry", { + retryId: "retry-1", turn: 1, step: 1, provider: "fixture-provider", mode: "normal", + policyKey: "default", retry: 1, maxRetries: 2, delayMs: 12.5, + failure: { message: "temporary", code: "TEMPORARY", status: 503, providerRetryAfterMs: 2.5, + requestId: "request-1" }, + }); + push("llm/retry-started", { retryId: "retry-1", turn: 1, step: 1, retry: 1 }); + push("llm/retry", { + retryId: "retry-1", turn: 1, step: 1, provider: "fixture-provider", mode: "normal", + policyKey: "default", retry: 2, maxRetries: 2, delayMs: 0, + failure: { message: "temporary", code: "TEMPORARY" }, + }); + push("llm/retry-started", { retryId: "retry-1", turn: 1, step: 1, retry: 2 }); + push("step/end", { turn: 1, step: 1 }); + push("turn/end", { turn: 1, reason: { kind: "completed" } }); + const valid = [header, ...events]; + assert.doesNotThrow(() => decodeDshJsonl(encodeDshRawJsonl(valid))); + const corruptions = [ + (rows) => { rows.find((event) => event.type === "approval/decided").data.id = "missing"; }, + (rows) => { rows.find((event) => event.type === "command/done").data.kind = "error"; }, + (rows) => { rows.find((event) => event.type === "hook/result").data.handlerId = "missing"; }, + (rows) => { + const endIndex = rows.findIndex((event) => event.type === "tool-workflow/run-end"); + const [end] = rows.splice(endIndex, 1); + rows.splice(rows.findIndex((event) => event.type === "tool-workflow/agent-end"), 0, end); + }, + (rows) => { rows.find((event) => event.type === "tool/code-dispatch").data.rootCallId = "other"; }, + (rows) => { rows.find((event) => event.type === "llm/retry").data.delayMs = 2_147_483_648; }, + (rows) => { rows.filter((event) => event.type === "llm/retry")[1].data.retry = 1; }, + (rows) => { rows.filter((event) => event.type === "llm/retry-started")[1].data.retry = 1; }, + ]; + for (const corrupt of corruptions) { + const malformed = structuredClone(events); + corrupt(malformed); + malformed.forEach((event, index) => { event.seq = index; }); + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl([header, ...malformed]))); + } +}); + +test("pinned goal fold validates transitions and the exact rendered continuation prompt", () => { + const header = makeDshHeader({ seedLength: undefined }); + const events = []; + const push = (type, data, extra = {}) => events.push(makeDshEvent(type, data, { + seq: events.length, time: header.createdAt + events.length + 1, ...extra, + })); + const snapshot = (revision, phase, extra = {}) => ({ + id: "goal-1", revision, objective: "Finish the synthetic objective", phase, maxGoalRounds: 3, ...extra, + }); + const change = (operation, goal, updatedAt) => ({ + kind: "goal/change", version: 1, operation, goal, roundsStarted: operation === "create" ? 0 : 1, + createdAt: 10, updatedAt, + }); + push("goal/change", change("create", snapshot(1, "active"), 10)); + push("turn/start", { turn: 1 }); + push("user/message", { + id: "goal-round", role: "user", source: { kind: "goal", goalId: "goal-1", revision: 1, round: 1 }, + content: [{ type: "text", text: "\nObjective: \"Finish the synthetic objective\"\nRound: 1/3\n\nContinue working toward the objective in this same session. Treat the current workspace, tool results, and durable session state as authoritative; inspect them instead of assuming earlier narration is still current. Make concrete progress and verify the result. Before claiming completion, gather evidence that the whole objective is achieved, read the current goal, and mark it complete. If work remains, leave the goal active for the next round. Follow the configured goal-tool policy before reporting a blocker.\n" }], + }, { surfaceOp: "append" }); + push("turn/end", { turn: 1, reason: { kind: "completed" } }); + push("goal/change", change("edit", { ...snapshot(2, "active"), objective: "Finish the revised objective" }, 11)); + push("goal/change", change("pause", { ...snapshot(3, "paused"), objective: "Finish the revised objective" }, 12)); + push("goal/change", change("resume", { ...snapshot(4, "active"), objective: "Finish the revised objective" }, 13)); + push("goal/change", change("block", { ...snapshot(5, "blocked"), objective: "Finish the revised objective", + blockedReason: { code: "needs-input", message: "Need synthetic input" } }, 14)); + push("goal/change", change("resume", { ...snapshot(6, "active"), objective: "Finish the revised objective" }, 15)); + push("goal/change", change("complete", { ...snapshot(7, "complete"), objective: "Finish the revised objective" }, 16)); + push("goal/change", { + kind: "goal/change", version: 1, operation: "clear", cleared: { id: "goal-1", revision: 8 }, clearedAt: 17, + }); + const valid = [header, ...events]; + assert.doesNotThrow(() => decodeDshJsonl(encodeDshRawJsonl(valid))); + const counterfeit = structuredClone(valid); + counterfeit[3].data.content[0].text = "counterfeit"; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(counterfeit)), + stableError("DSH_EVENT_RELATIONSHIP_INVALID")); + const badRevision = structuredClone(valid); + badRevision[5].data.goal.revision = 4; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(badRevision)), + stableError("DSH_EVENT_RELATIONSHIP_INVALID")); + const badBlockCode = structuredClone(valid); + badBlockCode[8].data.goal.blockedReason.code = "1invalid"; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(badBlockCode)), stableError("DSH_EVENT_SHAPE_DRIFT")); + const badClear = structuredClone(valid); + badClear.at(-1).data.cleared.id = ""; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(badClear)), stableError("DSH_EVENT_SHAPE_DRIFT")); + + const todoBase = makeSupportedDshSessionRows(); + for (const todos of [ + [{ content: " spaced", status: "pending" }], + [{ content: "duplicate", status: "pending" }, { content: "duplicate", status: "completed" }], + [{ content: "", status: "pending" }], + ]) { + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl( + insertBeforeTurnEnd(todoBase, [makeDshEvent("todo/write", { todos })]))), + stableError("DSH_EVENT_SHAPE_DRIFT")); + } +}); + +test("pinned compaction fold accepts surface-order shadows and requires the adjacent checkpoint rewrite", () => { + const header = makeDshHeader({ seedLength: undefined }); + const events = []; + const push = (type, data, extra = {}) => events.push(makeDshEvent(type, data, { + seq: events.length, time: header.createdAt + events.length + 1, ...extra, + })); + const message = (id, text, source = { kind: "user" }) => ({ + id, role: "user", content: [{ type: "text", text }], source, + }); + push("turn/start", { turn: 1 }); + push("user/message", message("first", "first"), { surfaceOp: "append" }); + push("user/message", message("second", "second", { kind: "plugin", plugin: "fixture" }), { + surfaceOp: "append", + }); + push("user/message", message("first-rewrite", "first rewrite", { kind: "plugin", plugin: "fixture" }), { + sourceEventSeqs: [1], surfaceOp: { op: "replace", start: 1, end: 1 }, + }); + push("compaction/start", { compactionId: "compact-1", turn: 1, sourceCommandId: "command-1" }); + push("compaction/summary", { + compactionId: "compact-1", sourceCommandId: "command-1", + summary: [{ type: "text", text: "summary" }], shadowedRange: { start: 3, end: 2 }, + shadowedSeqs: [3, 2], shadowedTokenCount: 2, provider: "fixture", model: "fixture", + }); + push("user/message", message("checkpoint", "checkpoint", { + kind: "plugin", plugin: "compact", compactionId: "compact-1", sourceCommandId: "command-1", + }), { sourceEventSeqs: [4, 5, 3, 2], surfaceOp: { op: "replace", start: 3, end: 2 } }); + push("compaction/end", { compactionId: "compact-1", turn: 1, sourceCommandId: "command-1" }); + push("turn/end", { turn: 1, reason: { kind: "completed" } }); + const valid = [header, ...events]; + assert.doesNotThrow(() => decodeDshJsonl(encodeDshRawJsonl(valid))); + + const wrongCheckpointKind = structuredClone(valid); + wrongCheckpointKind[7].data.source = { kind: "plugin", plugin: "fixture" }; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(wrongCheckpointKind)), + stableError("DSH_EVENT_RELATIONSHIP_INVALID")); + const missingAdjacent = structuredClone(valid); + missingAdjacent.splice(7, 0, makeDshEvent("plan/mode", { active: true }, { seq: 6 })); + missingAdjacent.slice(1).forEach((event, index) => { event.seq = index; }); + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(missingAdjacent)), + stableError("DSH_EVENT_RELATIONSHIP_INVALID")); + const badEndpoint = structuredClone(valid); + badEndpoint[6].data.shadowedRange.end = 3; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(badEndpoint)), + stableError("DSH_EVENT_RELATIONSHIP_INVALID")); + for (const sources of [[5, 3, 2], [5, 4, 3, 2], [4, 5, 3]]) { + const badProvenance = structuredClone(valid); + badProvenance[7].sourceEventSeqs = sources; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(badProvenance)), + stableError("DSH_EVENT_RELATIONSHIP_INVALID")); + } + const duplicate = structuredClone(valid); + duplicate[6].data.shadowedSeqs = [3, 3]; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(duplicate)), stableError("DSH_EVENT_SHAPE_DRIFT")); + const emptySourceCommand = structuredClone(valid); + emptySourceCommand[6].data.sourceCommandId = ""; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(emptySourceCommand)), + stableError("DSH_EVENT_SHAPE_DRIFT")); + + const pruneHeader = makeDshHeader({ seedLength: undefined }); + const callId = "prune-call"; + const result = { + turn: 1, step: 1, + message: { id: "prune-result", role: "user", source: { kind: "tool", callId }, content: [{ + type: "tool-result", toolCallId: callId, content: [{ type: "text", text: "large result" }], + }] }, + }; + const pruneEvents = [ + makeDshEvent("turn/start", { turn: 1 }), + makeDshEvent("step/start", { turn: 1, step: 1 }), + makeDshEvent("tool/call", { turn: 1, step: 1, callId, name: "read", arguments: "{}" }), + makeDshEvent("tool/result", result, { surfaceOp: "append" }), + makeDshEvent("step/end", { turn: 1, step: 1 }), + makeDshEvent("compaction/prune", { + shadowedRange: { start: 3, end: 3 }, shadowedSeqs: [3], shadowedTokenCount: 10, + }), + makeDshEvent("tool/result", { + ...structuredClone(result), + message: { ...structuredClone(result.message), content: [{ + type: "tool-result", toolCallId: callId, content: [{ type: "text", text: "pruned" }], + }] }, + }, { sourceEventSeqs: [3], surfaceOp: { op: "replace", start: 3, end: 3 } }), + makeDshEvent("turn/end", { turn: 1, reason: { kind: "completed" } }), + ]; + pruneEvents.forEach((event, index) => { event.seq = index; event.time = pruneHeader.createdAt + index + 1; }); + assert.doesNotThrow(() => decodeDshJsonl(encodeDshRawJsonl([pruneHeader, ...pruneEvents]))); + const wrongPruneType = structuredClone(pruneEvents); + wrongPruneType[6] = makeDshEvent("user/message", message("wrong-prune", "wrong", { + kind: "plugin", plugin: "fixture", + }), { seq: 6, time: pruneHeader.createdAt + 7, sourceEventSeqs: [3], + surfaceOp: { op: "replace", start: 3, end: 3 } }); + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl([pruneHeader, ...wrongPruneType])), + stableError("DSH_EVENT_RELATIONSHIP_INVALID")); +}); + +test("pinned schedule v1 enforces exact records and session-local transitions", () => { + const base = makeSupportedDshSessionRows(); + const createAfter = makeDshEvent("schedule/change", { + version: 1, operation: "create", schedule: { + id: "schedule-after", kind: "after", prompt: "Synthetic reminder", afterSeconds: 60, + scheduledAt: "2026-08-19T00:00:00.000Z", + }, + }); + const dispatchAfter = makeDshEvent("schedule/change", { version: 1, operation: "dispatch", id: "schedule-after" }); + const createEvery = makeDshEvent("schedule/change", { + version: 1, operation: "create", schedule: { + id: "schedule-every", kind: "every", prompt: "Synthetic recurring reminder", everySeconds: 300, + scheduledAt: "2026-08-19T00:00:00.000Z", + }, + }); + const dispatchEvery = makeDshEvent("schedule/change", { + version: 1, operation: "dispatch", id: "schedule-every", acceptedAt: "2026-08-19T00:05:00.000Z", + }); + const deleteEvery = makeDshEvent("schedule/change", { version: 1, operation: "delete", id: "schedule-every" }); + const valid = insertBeforeTurnEnd(base, [createAfter, dispatchAfter, createEvery, dispatchEvery, deleteEvery]); + assert.doesNotThrow(() => decodeDshJsonl(encodeDshRawJsonl(valid))); + for (const events of [ + [dispatchAfter], + [createAfter, createAfter], + [createAfter, { ...dispatchAfter, data: { ...dispatchAfter.data, acceptedAt: "2026-08-19T00:00:00.000Z" } }], + [createEvery, { ...dispatchEvery, data: { ...dispatchEvery.data, acceptedAt: undefined } }], + [createEvery, { ...dispatchEvery, data: { ...dispatchEvery.data, acceptedAt: "2026-08-18T23:59:59.000Z" } }], + ]) { + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(insertBeforeTurnEnd(base, events)))); + } + const malformed = [ + { version: 2, operation: "delete", id: "schedule-1" }, + { version: 1, operation: "delete" }, + { version: 1, operation: "create", schedule: { id: "schedule-1", kind: "at", prompt: " x ", scheduledAt: "bad" } }, + { version: 1, operation: "create", schedule: { + id: "schedule-1", kind: "every", prompt: "x", everySeconds: 299, scheduledAt: "2026-08-19T00:00:00.000Z", + } }, + ]; + for (const data of malformed) { + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl( + insertBeforeTurnEnd(base, [makeDshEvent("schedule/change", data)]))), + stableError("DSH_EVENT_SHAPE_DRIFT")); + } +}); + +test("pinned DeepSeek search request rejects tuple and field drift without exposing request content", () => { + const base = makeSupportedDshSessionRows(); + const valid = { + endpoint: `https://example.invalid/${DSH_FIXTURE_SECRET}`, + apiVersion: "2023-06-01", + body: { + model: "deepseek-v4-flash", max_tokens: 64, + messages: [{ role: "user", content: [{ type: "text", text: DSH_FIXTURE_SECRET }] }], + tools: [{ type: "web_search_20250305", name: "web_search", max_uses: 1 }], + }, + }; + const decoded = decodeDshJsonl(encodeDshRawJsonl( + insertBeforeTurnEnd(base, [makeDshEvent("web/deepseek-search-llm-request", valid)]))); + assert.equal(decoded.diagnostics.knownUnsupportedTypes.includes("web/deepseek-search-llm-request"), true); + assert.equal(JSON.stringify(decoded.diagnostics).includes(DSH_FIXTURE_SECRET), false); + const malformed = []; + for (const mutate of [ + (data) => { delete data.endpoint; }, + (data) => { data.extra = true; }, + (data) => { data.body.max_tokens = 0; }, + (data) => { data.body.messages = []; }, + (data) => { data.body.messages[0].role = "assistant"; }, + (data) => { data.body.messages[0].content.push({ type: "text", text: "extra" }); }, + (data) => { data.body.tools[0].type = "other"; }, + ]) { + const data = structuredClone(valid); mutate(data); malformed.push(data); + } + for (const data of malformed) { + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl( + insertBeforeTurnEnd(base, [makeDshEvent("web/deepseek-search-llm-request", data)]))), + stableError("DSH_EVENT_SHAPE_DRIFT")); + } +}); + +test("pinned request headers reject outer, config, defaults, reason, tool, and schema drift", () => { + const base = makeSupportedDshSessionRows(); + const valid = requestHeaderEvent(); + const malformed = []; + const add = (mutate) => { + const event = structuredClone(valid); + mutate(event); + malformed.push(event); + }; + add((event) => { event.data.reason = "retry"; }); + add((event) => { delete event.data.header; }); + add((event) => { event.data.extra = true; }); + add((event) => { delete event.data.header.config.provider; }); + add((event) => { delete event.data.header.config.model; }); + add((event) => { event.data.header.extra = true; }); + add((event) => { event.data.header.config.extra = true; }); + add((event) => { event.data.header.config.provider = 7; }); + add((event) => { event.data.header.config.reasoningEffort = 1; }); + add((event) => { event.data.header.config.temperature = "cold"; }); + add((event) => { event.data.header.config.maxTokens = null; }); + add((event) => { event.data.header.config.stop = ["STOP", 2]; }); + add((event) => { event.data.header.adapterDefaults = { maxTokens: false }; }); + add((event) => { event.data.header.adapterDefaults = { maxTokens: true, extra: true }; }); + add((event) => { event.data.header.system = 7; }); + add((event) => { event.data.header.tools = {}; }); + add((event) => { event.data.header.tools = [{ name: "x", description: "x" }]; }); + add((event) => { event.data.header.tools = [{ name: "x", description: "x", parameters: [], extra: true }]; }); + for (const event of malformed) { + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(insertRequestHeader(base, event))), + stableError("DSH_EVENT_SHAPE_DRIFT")); + } + + const runtimeOnly = requestHeaderEvent({ + tools: [{ name: "x", description: "x", parameters: { invalid: undefined } }], + }); + const analyzer = new DshSessionAnalyzer(); + assert.throws(() => analyzer.normalizeEvents(runtimeOnly, { + kind: "dsh-session-jsonl", role: "session-transcript", path: "synthetic.jsonl", + sessionId: "synthetic", cwd: "/synthetic", dshProvenance: { delegationDepth: 0 }, + }), stableError("DSH_EVENT_SHAPE_DRIFT")); +}); + +test("unknown ignorable diagnostics use bounded stable tokens and required errors do not echo type", () => { + const secretType = `future/${DSH_FIXTURE_SECRET}`; + const longType = `future/${"x".repeat(4_000)}`; + const rows = insertBeforeTurnEnd(makeSupportedDshSessionRows(), [ + makeDshEvent(secretType, {}, { ignorable: true }), + makeDshEvent(secretType, {}, { ignorable: true }), + makeDshEvent(longType, {}, { ignorable: true }), + ]); + const first = decodeDshJsonl(encodeDshRawJsonl(rows)); + const second = decodeDshJsonl(encodeDshRawJsonl(rows)); + assert.equal(first.diagnostics.unknownIgnorableCount, 3); + assert.equal(first.diagnostics.unknownIgnorableTypes.length, 2); + assert.deepEqual(first.diagnostics.unknownIgnorableTypes, second.diagnostics.unknownIgnorableTypes); + assert.equal(first.diagnostics.unknownIgnorableTypes.every((type) => /^unknown:[0-9a-f]{12}$/u.test(type)), true); + const diagnosticsText = JSON.stringify(first.diagnostics); + assert.equal(diagnosticsText.includes(DSH_FIXTURE_SECRET), false); + assert.equal(diagnosticsText.includes("x".repeat(100)), false); + + const required = insertBeforeTurnEnd(makeSupportedDshSessionRows(), [makeDshEvent(secretType, {})]); + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(required)), + (error) => error?.code === "DSH_UNKNOWN_REQUIRED_EVENT" + && !String(error?.message).includes(DSH_FIXTURE_SECRET)); +}); + +test("crash prefixes with an open step or pending tool result remain incomplete", () => { + const header = makeDshHeader({ sessionId: "open-step-prefix" }); + const start = header.createdAt + 1_000; + const openStepRows = [ + header, + makeDshEvent("turn/start", { turn: 1 }, { seq: 0, time: start }), + makeDshEvent("step/start", { turn: 1, step: 1 }, { seq: 1, time: start + 1 }), + ]; + const openStep = decodeDshJsonl(encodeDshRawJsonl(openStepRows)); + assert.equal(openStep.incomplete, true); + assert.equal(openStep.diagnostics.incompleteReason, "open-step"); + + const pendingRows = structuredClone(openStepRows); + pendingRows[0].id = "pending-tool-prefix"; + pendingRows.push(makeDshEvent("tool/call", { + turn: 1, step: 1, callId: "pending-call", name: "read_file", arguments: "{}", + }, { seq: 2, time: start + 2 })); + const pending = decodeDshJsonl(encodeDshRawJsonl(pendingRows)); + assert.equal(pending.incomplete, true); + assert.equal(pending.diagnostics.incompleteReason, "pending-tool-result"); + assert.equal(pending.diagnostics.pendingToolCallCount, 1); + + const stepWithoutTurn = [header, + makeDshEvent("step/start", { turn: 1, step: 1 }, { seq: 0, time: start })]; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(stepWithoutTurn)), stableError("DSH_EVENT_ORDER_INVALID")); + const callWithoutStep = [header, + makeDshEvent("turn/start", { turn: 1 }, { seq: 0, time: start }), + makeDshEvent("tool/call", { + turn: 1, step: 1, callId: "orphan-call", name: "read_file", arguments: "{}", + }, { seq: 1, time: start + 1 })]; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(callWithoutStep)), stableError("DSH_EVENT_ORDER_INVALID")); +}); + +test("DSH timestamps are validated and normalized strictly as Unix epoch milliseconds", () => { + assert.equal(normalizeDshEpochMillis(0), "1970-01-01T00:00:00.000Z"); + assert.equal(normalizeDshEpochMillis(1_000), "1970-01-01T00:00:01.000Z"); + assert.equal(normalizeDshEpochMillis(Date.parse("2026-08-18T00:00:00.000Z")), + "2026-08-18T00:00:00.000Z"); + assert.throws(() => normalizeDshEpochMillis(Number.MAX_SAFE_INTEGER), stableError("DSH_INVALID_EPOCH_MILLIS")); + + const badHeader = makeOpenTurnDshRows(); + badHeader[0].createdAt = Number.MAX_SAFE_INTEGER; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(badHeader)), stableError("DSH_INVALID_HEADER")); + const badEvent = makeOpenTurnDshRows(); + badEvent[1].time = Number.MAX_SAFE_INTEGER; + assert.throws(() => decodeDshJsonl(encodeDshRawJsonl(badEvent)), stableError("DSH_INVALID_EVENT")); +}); + +test("DSH discovery sorts validated millisecond observations without nullable timestamps", async () => { + const root = await tempRoot(); + const home = path.join(root, "home"); + const workspace = path.join(root, "workspace"); + await writeNestedDshArtifact({ dshHome: home, + rows: makeOpenTurnDshRows({ workspace, sessionId: "earlier", createdAt: 0 }) }); + await writeNestedDshArtifact({ dshHome: home, + rows: makeOpenTurnDshRows({ workspace, sessionId: "later", createdAt: 1_000 }) }); + const result = await inventory(home, workspace); + assert.deepEqual(result.sessions.map((session) => session.sessionId), ["later", "earlier"]); + assert.deepEqual(result.sessions.map((session) => session.firstSeen), + ["1970-01-01T00:00:01.000Z", "1970-01-01T00:00:00.000Z"]); +}); + +test("workspace qualification rejects foreign cwd and handles POSIX and Windows case semantics", async () => { + const root = await tempRoot(); + const home = path.join(root, "home"); + const workspace = path.join(root, "target"); + await writeNestedDshArtifact({ dshHome: home, + rows: makeSupportedDshSessionRows({ workspace: path.join(root, "foreign"), sessionId: "foreign" }) }); + const foreign = await inventory(home, workspace); + assert.equal(foreign.sessions.length, 0); + assert.equal(foreign.warnings.some((warning) => warning.code === "dsh-foreign-workspace-rejected"), true); + + for (const entry of makeCrossPlatformDshWorkspaceFixtures()) { + const caseRoot = await tempRoot(); + const caseHome = path.join(caseRoot, "home"); + await writeNestedDshArtifact({ dshHome: caseHome, rows: entry.rows }); + const requested = entry.platform === "win32" ? entry.workspace.toLowerCase() : entry.workspace; + const result = await inventory(caseHome, requested); + assert.equal(result.sessions.length, 1, entry.platform); + } +}); + +test("validated topology semantics are reused for direct workspace qualification", async () => { + const root = await tempRoot(); + const home = path.join(root, "home"); + const workspace = path.join(root, "repo", "member"); + await writeNestedDshArtifact({ dshHome: home, + rows: makeSupportedDshSessionRows({ workspace, sessionId: "topology-direct" }) }); + await writeNestedDshArtifact({ dshHome: home, + rows: makeSupportedDshSessionRows({ workspace: path.join(root, "repo"), sessionId: "topology-root-candidate" }) }); + const analyzer = new DshSessionAnalyzer(); + const scope = await analyzer.resolveScope({ dshHome: home, workspace, topology: { + kind: "better-harness.workspace-topology", schemaVersion: 1, + requestedWorkspace: workspace, gitRoot: path.join(root, "repo"), + target: { kind: "workspace-member", route: "member", memberRoute: "member", memberMatch: "exact" }, + } }); + const sessions = await analyzer.discoverSessions(scope, await analyzer.discoverSourceRoots(scope)); + assert.equal(sessions.length, 1); + assert.equal(sessions[0].sessionId, "topology-direct"); + assert.equal(sessions[0].workspaceMatch, "direct-cwd"); + assert.equal(analyzer.analysisWarnings.some((warning) => warning.code === "dsh-foreign-workspace-rejected"), true); +}); + +test("same session id in distinct valid artifacts rejects the complete identity", async () => { + const root = await tempRoot(); + const home = path.join(root, "home"); + const workspace = path.join(root, "workspace"); + await writeNestedDshArtifact({ dshHome: home, + rows: makeSupportedDshSessionRows({ workspace, sessionId: "collision" }) }); + await writeNestedDshArtifact({ dshHome: home, + rows: makeSupportedDshSessionRows({ workspace: path.join(workspace, "child"), sessionId: "collision" }) }); + const result = await inventory(home, workspace); + assert.equal(result.sessions.length, 0); + assert.equal(result.warnings.some((warning) => warning.code === "dsh-session-identity-conflict"), true); +}); + +test("public discovery and diagnostics never expose fixture credentials", async () => { + const root = await tempRoot(); + const home = path.join(root, "home"); + const workspace = path.join(root, "workspace"); + const rows = makeSupportedDshSessionRows({ workspace, sessionId: "privacy", + parentSession: DSH_FIXTURE_SECRET, agentPreset: DSH_FIXTURE_SECRET }); + rows[5].data.message.source.replayState = { opaque: DSH_FIXTURE_SECRET }; + rows[7].data.meta = { opaque: DSH_FIXTURE_SECRET }; + await writeNestedDshArtifact({ dshHome: home, rows }); + const result = await inventory(home, workspace); + assert.equal(JSON.stringify({ sessions: result.sessions, warnings: result.warnings }).includes(DSH_FIXTURE_SECRET), false); + assert.equal(result.sessions[0].dshProvenance.parentSession, ""); + assert.equal(result.sessions[0].dshProvenance.agentPreset, ""); + try { decodeDshJsonl(Buffer.from(`${DSH_FIXTURE_SECRET}\n`, "utf8")); } catch (error) { + assert.equal(String(error.message).includes(DSH_FIXTURE_SECRET), false); + } +}); + +test("session reading and normalization remain explicitly unavailable until the next module", async () => { + const analyzer = new DshSessionAnalyzer(); + await assert.rejects(() => analyzer.readSession(), stableError("DSH_NORMALIZATION_UNAVAILABLE")); + assert.throws(() => analyzer.normalizeEvent({}), stableError("DSH_NORMALIZATION_UNAVAILABLE")); + assert.throws(() => analyzer.normalizeEvents([]), stableError("DSH_NORMALIZATION_UNAVAILABLE")); +}); diff --git a/test/sessions/session-analysis-dsh-fixtures.test.mjs b/test/sessions/session-analysis-dsh-fixtures.test.mjs new file mode 100644 index 00000000..03dc90a6 --- /dev/null +++ b/test/sessions/session-analysis-dsh-fixtures.test.mjs @@ -0,0 +1,334 @@ +import assert from "node:assert/strict"; +import { mkdtemp, readdir, readFile } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import * as zlib from "node:zlib"; +import { test } from "vitest"; + +import { + decodeDshZstdFramesForFixture, + decodePackedDshStorageRecordForFixture, + dshProjectKey, + DSH_FIXTURE_MALFORMED_PACKED_ROW, + DSH_FIXTURE_SECRET, + DSH_FIXTURE_UNSUPPORTED_PACKED_ROW, + DSH_FORMAT_VERSION, + DSH_ZSTD_UNAVAILABLE, + encodeDshRawJsonl, + encodeDshSessionIdSegment, + makeCanonicalDedupeDshFixture, + makeCrossPlatformDshWorkspaceFixtures, + makeBadHeaderDshRows, + makeBadIdentityDshFixture, + makeBadSequenceDshRows, + makeBadVersionDshRows, + makeDshHeader, + makeDshZstdArtifact, + makeForeignWorkspaceDshFixture, + makeKnownUnsupportedDshEvent, + makeKnownUnsupportedDshSessionRows, + makeMalformedDshJsonlBytes, + makeMalformedPackedDshStorageRows, + makeNativeSnapshotDshSessionRows, + makeOpenTurnDshRows, + makePackedDshStorageRows, + makeSupportedDshSessionRows, + makeTerminalDshSessionRows, + makeUnknownIgnorableDshEvent, + makeUnknownRequiredDshEvent, + writeNestedDshArtifact, +} from "./dsh-fixtures.mjs"; + +const ZSTD_MAGIC = 0xFD2FB528; +const TERMINAL_REASONS = ["completed", "aborted", "blocked", "error", "max-tokens", "interrupted"]; + +async function fixtureRoot(prefix) { + return mkdtemp(path.join(os.tmpdir(), prefix)); +} + +function jsonlRows(bytes) { + const text = bytes.toString("utf8"); + assert.ok(text.endsWith("\n")); + return text.slice(0, -1).split("\n").map((line) => JSON.parse(line)); +} + +test("pinned DSH v0 builders preserve exact header, event, message, and fresh-copy contracts", () => { + const rows = makeSupportedDshSessionRows(); + const [header, ...events] = rows; + assert.deepEqual(Object.keys(header), [ + "type", "version", "id", "cwd", "createdAt", "parentSession", "seedLength", "origin", + "delegationDepth", "agentPreset", + ]); + assert.equal(header.type, "session"); + assert.equal(header.version, DSH_FORMAT_VERSION); + assert.ok(path.posix.isAbsolute(header.cwd)); + assert.equal(header.delegationDepth, 1); + assert.deepEqual(events.map((event) => event.seq), events.map((_, index) => index)); + + const users = events.filter((event) => event.type === "user/message"); + assert.equal(users[0].data.source.kind, "user"); + assert.deepEqual(users[1].data.source, { + kind: "plugin", + plugin: "fixture-context", + form: "notice", + summary: "Synthetic context injection.", + }); + const assistant = events.find((event) => event.type === "assistant/message"); + assert.deepEqual(assistant.data.usage, { + inputTokens: 12, + outputTokens: 8, + cacheReadTokens: 2, + reasoningTokens: 1, + }); + assert.equal(assistant.data.turn, 1); + assert.equal(assistant.data.step, 1); + assert.deepEqual(assistant.data.message.source, { + kind: "model", + provider: "fixture-provider", + model: "fixture-model", + }); + const calls = events.filter((event) => event.type === "tool/call"); + const results = events.filter((event) => event.type === "tool/result"); + assert.deepEqual(results.map((event) => event.data.message.source.callId), calls.map((event) => event.data.callId)); + assert.equal(results[0].data.message.content[0].isError, false); + assert.equal(results[1].data.message.content[0].isError, true); + assert.deepEqual(header, makeDshHeader()); + + const other = makeSupportedDshSessionRows(); + rows[0].parentSession = "mutated"; + rows[3].data.content[0].text = "mutated"; + assert.notEqual(other[0].parentSession, "mutated"); + assert.notEqual(other[3].data.content[0].text, "mutated"); +}); + +test("pinned headless snapshot builder preserves native base ordering and request records", () => { + const rows = makeNativeSnapshotDshSessionRows(); + const events = rows.slice(1); + assert.deepEqual(events.map((event) => event.seq), events.map((_event, index) => index)); + assert.deepEqual(events.slice(0, 7).map((event) => event.type), [ + "permission/preset", "sandbox/mode", "approval/policy", "agent/inbox/spliced", + "turn/start", "agent/inbox/spliced", "step/start", + ]); + assert.deepEqual(events.filter((event) => event.type === "request/header").map((event) => event.data.reason), + ["initial", "change"]); + assert.deepEqual(events.filter((event) => event.type === "agent/inbox/spliced") + .map((event) => event.data.removedCount ?? 0), [0, 1]); + const another = makeNativeSnapshotDshSessionRows(); + rows[4].data.inserted[0].content[0].text = "mutated"; + assert.notEqual(another[4].data.inserted[0].content[0].text, "mutated"); +}); + +test("terminal fixtures each contain one semantically isolated exact turn outcome", () => { + for (const kind of TERMINAL_REASONS) { + const rows = makeTerminalDshSessionRows(kind); + const events = rows.slice(1); + const terminal = events.filter((event) => event.type === "turn/end"); + assert.equal(terminal.length, 1, kind); + assert.equal(terminal[0].data.reason.kind, kind); + assert.deepEqual(events.map((event) => event.seq), events.map((_, index) => index)); + assert.equal(Object.hasOwn(rows[0], "parentSession"), false); + assert.equal(Object.hasOwn(rows[0], "seedLength"), false); + assert.equal(Object.hasOwn(rows[0], "origin"), false); + assert.equal(Object.hasOwn(rows[0], "agentPreset"), false); + if (kind === "aborted") { + assert.deepEqual(terminal[0].data.reason.reason, { + kind: "hook", + reason: "synthetic cancellation", + }); + } + if (kind === "error") { + assert.deepEqual(terminal[0].data.reason.error, { + message: "synthetic provider failure", + code: "FIXTURE_PROVIDER_FAILURE", + status: 503, + }); + } + } +}); + +test("known unsupported and truly unknown event fixtures remain distinct", () => { + const knownRows = makeKnownUnsupportedDshSessionRows(); + const knownUnsupported = knownRows[2]; + const unknownRequired = makeUnknownRequiredDshEvent({ seq: 1 }); + const unknownIgnorable = makeUnknownIgnorableDshEvent({ seq: 2 }); + assert.equal(knownUnsupported.type, "todo/write"); + assert.deepEqual(knownUnsupported.data.todos, [{ + content: "Review the synthetic fixture", + status: "in_progress", + }]); + assert.equal(Object.hasOwn(knownUnsupported, "ignorable"), false); + assert.deepEqual(knownRows.slice(1).map((event) => event.seq), [0, 1, 2]); + assert.deepEqual(knownRows.slice(1).map((event) => event.type), ["turn/start", "todo/write", "turn/end"]); + assert.equal(unknownRequired.type, "fixture-future/required"); + assert.equal(Object.hasOwn(unknownRequired, "ignorable"), false); + assert.equal(unknownIgnorable.type, "fixture-future/ignorable"); + assert.equal(unknownIgnorable.ignorable, true); +}); + +test("pinned chunk-row oracle expands exact text, reasoning, and tool-call storage records", () => { + const rows = makePackedDshStorageRows(); + assert.deepEqual(Object.keys(rows[0]), ["type", "seq0", "time0", "data"]); + assert.deepEqual(Object.keys(rows[0].data), ["turn", "step", "index", "dt", "texts"]); + assert.deepEqual(Object.keys(rows[2].data), ["turn", "step", "index", "id", "name", "dt", "args"]); + const events = rows.flatMap((row) => decodePackedDshStorageRecordForFixture(row)); + assert.deepEqual(events.map((event) => event.seq), events.map((_, index) => index)); + assert.deepEqual(events.map((event) => event.type), Array(9).fill("assistant/chunk")); + assert.deepEqual(events.slice(0, 3).map((event) => event.data.chunk), [ + { type: "text-delta", index: 0, text: "one" }, + { type: "text-delta", index: 0, text: " two" }, + { type: "text-delta", index: 0, text: "." }, + ]); + assert.deepEqual(events.slice(3, 6).map((event) => event.data.chunk.type), [ + "reasoning-delta", "reasoning-delta", "reasoning-delta", + ]); + assert.deepEqual(events.slice(6).map((event) => event.data.chunk), [ + { type: "tool-call-delta", index: 2, id: "fixture-packed-call", name: "fixture_tool", + argumentsDelta: "{\"value\":" }, + { type: "tool-call-delta", index: 2, id: "fixture-packed-call", name: "fixture_tool", + argumentsDelta: "1" }, + { type: "tool-call-delta", index: 2, id: "fixture-packed-call", name: "fixture_tool", + argumentsDelta: "}" }, + ]); + assert.deepEqual(events.map((event) => [event.data.turn, event.data.step]), Array(9).fill([1, 1])); +}); + +test("fixture-side chunk-row oracle rejects malformed and unsupported packed shapes", () => { + const malformed = makeMalformedPackedDshStorageRows(); + for (const row of malformed.slice(0, -1)) { + assert.throws( + () => decodePackedDshStorageRecordForFixture(row), + (error) => error?.code === DSH_FIXTURE_MALFORMED_PACKED_ROW, + ); + } + assert.throws( + () => decodePackedDshStorageRecordForFixture(malformed.at(-1)), + (error) => error?.code === DSH_FIXTURE_UNSUPPORTED_PACKED_ROW, + ); +}); + +test("raw JSONL encoding is deterministic, newline terminated, and lossless", () => { + const rows = makeSupportedDshSessionRows(); + const first = encodeDshRawJsonl(rows); + const second = encodeDshRawJsonl(rows); + assert.notStrictEqual(first, second); + assert.deepEqual(first, second); + assert.equal(first.at(-1), 0x0A); + assert.deepEqual(jsonlRows(first), rows); +}); + +test("bad and incomplete builders each introduce their named contract defect", () => { + assert.notEqual(makeBadVersionDshRows()[0].version, DSH_FORMAT_VERSION); + assert.equal(Object.hasOwn(makeBadHeaderDshRows()[0], "delegationDepth"), false); + const identity = makeBadIdentityDshFixture(); + assert.notEqual(identity.rows[0].id, identity.artifactSessionId); + const badSequenceEvents = makeBadSequenceDshRows().slice(1); + assert.notDeepEqual(badSequenceEvents.map((event) => event.seq), + badSequenceEvents.map((_, index) => index)); + assert.throws(() => jsonlRows(makeMalformedDshJsonlBytes()), SyntaxError); + const openEvents = makeOpenTurnDshRows().slice(1); + assert.equal(openEvents.some((event) => event.type === "turn/start"), true); + assert.equal(openEvents.some((event) => event.type === "turn/end"), false); +}); + +test("Zstandard fixture encodes independent checksummed frames or reports stable unavailability", () => { + const rows = makeSupportedDshSessionRows(); + const batches = [[rows[0]], rows.slice(1, 6), rows.slice(6)]; + if (typeof zlib.zstdCompressSync === "function" && typeof zlib.zstdDecompressSync === "function" + && Number.isSafeInteger(zlib.constants?.ZSTD_c_checksumFlag)) { + const result = makeDshZstdArtifact(batches); + assert.equal(result.frames.length, 3); + for (const [index, frame] of result.frames.entries()) { + assert.equal(frame.readUInt32LE(0), ZSTD_MAGIC); + assert.equal((frame[4] & 0x04) === 0x04, true); + assert.deepEqual( + decodeDshZstdFramesForFixture([frame])[0], + encodeDshRawJsonl(batches[index]), + ); + } + assert.deepEqual(result.artifact, Buffer.concat(result.frames)); + } else { + assert.throws( + () => makeDshZstdArtifact(batches), + (error) => error?.code === DSH_ZSTD_UNAVAILABLE, + ); + } + assert.throws( + () => makeDshZstdArtifact(batches, { compressor: null }), + (error) => error?.code === DSH_ZSTD_UNAVAILABLE, + ); + assert.throws( + () => decodeDshZstdFramesForFixture([Buffer.from([0x28, 0xB5, 0x2F, 0xFD])], { + decompressor: null, + }), + (error) => error?.code === DSH_ZSTD_UNAVAILABLE, + ); + assert.deepEqual(jsonlRows(encodeDshRawJsonl(rows)), rows); +}); + +test("nested artifact writer emits only the fixed caller-owned raw and compressed layouts", async () => { + const root = await fixtureRoot("dsh-fixture-writer-"); + const rawHome = path.join(root, "raw-home"); + const zstdHome = path.join(root, "zstd-home"); + const rows = makeSupportedDshSessionRows(); + const raw = await writeNestedDshArtifact({ dshHome: rawHome, rows, compression: "raw" }); + const expectedProjectSegment = dshProjectKey(rows[0].cwd); + assert.equal(expectedProjectSegment, "--synthetic-workspace-project--"); + assert.equal(raw.filePath, path.join(rawHome, "sessions", expectedProjectSegment, + encodeDshSessionIdSegment(rows[0].id), "session.jsonl")); + assert.deepEqual(await readFile(raw.filePath), encodeDshRawJsonl(rows)); + + if (typeof zlib.zstdCompressSync === "function" + && Number.isSafeInteger(zlib.constants?.ZSTD_c_checksumFlag)) { + const compressed = await writeNestedDshArtifact({ dshHome: zstdHome, rows, compression: "zstd" }); + assert.equal(compressed.filePath, path.join(zstdHome, "sessions", expectedProjectSegment, + encodeDshSessionIdSegment(rows[0].id), "session.jsonl.zstd")); + assert.equal((await readFile(compressed.filePath)).equals(compressed.bytes), true); + } + assert.deepEqual((await readdir(root)).sort(), ["raw-home", ...(typeof zlib.zstdCompressSync === "function" + ? ["zstd-home"] : [])].sort()); + assert.equal(raw.filePath.startsWith(root), true); +}); + +test("workspace fixtures cover foreign rejection inputs and platform-specific absolute cwd forms", () => { + const foreign = makeForeignWorkspaceDshFixture(); + assert.notEqual(foreign.rows[0].cwd, foreign.requestedWorkspace); + assert.equal(path.posix.isAbsolute(foreign.rows[0].cwd), true); + + const cases = makeCrossPlatformDshWorkspaceFixtures(); + assert.deepEqual(cases.map((entry) => entry.platform), ["posix", "win32"]); + assert.equal(path.posix.isAbsolute(cases[0].workspace), true); + assert.equal(path.win32.isAbsolute(cases[1].workspace), true); + for (const entry of cases) { + assert.equal(entry.rows[0].cwd, entry.workspace); + assert.equal(entry.rows[0].delegationDepth, 1); + assert.equal(entry.rows.slice(1).every((event, index) => event.seq === index), true); + } +}); + +test("canonical dedupe fixture binds path aliases to one artifact and session identity", async () => { + const root = await fixtureRoot("dsh-fixture-dedupe-"); + const fixture = makeCanonicalDedupeDshFixture({ dshHome: path.join(root, "dsh-home") }); + assert.notEqual(fixture.candidates[0].path, fixture.candidates[1].path); + assert.equal(path.resolve(fixture.candidates[0].path), path.resolve(fixture.candidates[1].path)); + assert.equal(fixture.candidates.every((candidate) => candidate.rows[0].id === fixture.sessionId), true); + assert.deepEqual(fixture.candidates[0].rows, fixture.candidates[1].rows); + fixture.candidates[0].rows[0].id = "mutated"; + assert.equal(fixture.candidates[1].rows[0].id, fixture.sessionId); +}); + +test("serialized synthetic fixtures contain only the declared fake credential sentinel", () => { + const rows = [ + ...makeSupportedDshSessionRows(), + ...TERMINAL_REASONS.flatMap((kind) => makeTerminalDshSessionRows(kind)), + makeKnownUnsupportedDshEvent(), + makeUnknownRequiredDshEvent(), + makeUnknownIgnorableDshEvent(), + ...makePackedDshStorageRows(), + ]; + const serialized = encodeDshRawJsonl(rows).toString("utf8"); + assert.equal(serialized.includes(DSH_FIXTURE_SECRET), true); + assert.equal(serialized.includes(process.cwd()), false); + assert.equal(serialized.includes(os.homedir()), false); + assert.doesNotMatch(serialized, /ghp_[A-Za-z0-9]{20,}|AKIA[A-Z0-9]{16}/u); + assert.doesNotMatch(serialized.replaceAll(DSH_FIXTURE_SECRET, ""), /sk-[A-Za-z0-9_-]{16,}/u); +}); diff --git a/test/sessions/session-analysis-dsh-provider.test.mjs b/test/sessions/session-analysis-dsh-provider.test.mjs new file mode 100644 index 00000000..02a34585 --- /dev/null +++ b/test/sessions/session-analysis-dsh-provider.test.mjs @@ -0,0 +1,754 @@ +import assert from "node:assert/strict"; +import { mkdir, mkdtemp, writeFile } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import * as zlib from "node:zlib"; +import { test } from "vitest"; + +import { + DSH_ADAPTER_VERSION, + DshSessionAnalyzer, +} from "../../scripts/session-analysis/platforms/dsh.mjs"; +import { runProviderCommand } from "../../scripts/session-analysis/provider-runner.mjs"; +import { + DSH_FIXTURE_SECRET, + dshProjectKey, + encodeDshSessionIdSegment, + makeDshHeader, + makeDshEvent, + makeOpenTurnDshRows, + makeSupportedDshSessionRows, + makeTerminalDshSessionRows, + makeUnknownIgnorableDshEvent, + makeKnownUnsupportedDshEvent, + makeNativeSnapshotDshSessionRows, + writeNestedDshArtifact, +} from "./dsh-fixtures.mjs"; + +const UNTIL = "2026-08-20T00:00:00.000Z"; + +async function fixtureContext(prefix = "better-harness-dsh-provider-") { + const root = await mkdtemp(path.join(os.tmpdir(), prefix)); + const workspace = path.join(root, "workspace"); + const dshHome = path.join(root, "dsh-home"); + await mkdir(workspace, { recursive: true }); + return { root, workspace, dshHome }; +} + +async function discover(analyzer, context, options = {}) { + const input = { workspace: context.workspace, dshHome: context.dshHome, until: UNTIL, ...options }; + const scope = await analyzer.resolveScope(input); + const roots = await analyzer.discoverSourceRoots(scope); + const sessions = await analyzer.discoverSessions(scope, roots); + return { input, scope, roots, sessions }; +} + +async function writeRows(context, rows, options = {}) { + return writeNestedDshArtifact({ dshHome: context.dshHome, rows, ...options }); +} + +function insertAccountedEvents(rows) { + const stepEndIndex = rows.findIndex((row) => row.type === "step/end"); + const stepEnd = rows[stepEndIndex]; + const turnEnd = rows[stepEndIndex + 1]; + const secretUnknownType = `future/${DSH_FIXTURE_SECRET}`; + rows.splice( + stepEndIndex, + 0, + makeKnownUnsupportedDshEvent({ seq: stepEnd.seq, time: stepEnd.time - 2 }), + makeUnknownIgnorableDshEvent({ seq: stepEnd.seq + 1, time: stepEnd.time - 1 }), + makeDshEvent(secretUnknownType, {}, { seq: stepEnd.seq + 2, time: stepEnd.time - 1, ignorable: true }), + makeDshEvent(secretUnknownType, {}, { seq: stepEnd.seq + 3, time: stepEnd.time - 1, ignorable: true }), + makeDshEvent(`future/${"x".repeat(4_000)}`, {}, { + seq: stepEnd.seq + 4, time: stepEnd.time - 1, ignorable: true, + }), + ); + stepEnd.seq += 5; + turnEnd.seq += 5; + return rows; +} + +function insertProviderRequestHeader(rows) { + const output = structuredClone(rows); + const assistantIndex = output.findIndex((row) => row.type === "assistant/message"); + output.splice(assistantIndex, 0, makeDshEvent("request/header", { + header: { + config: { + provider: "fixture-provider", model: "fixture-model", reasoningEffort: "high", + temperature: 0.2, maxTokens: 4_096, stop: ["STOP"], + }, + adapterDefaults: { reasoningEffort: true, maxTokens: true }, + system: `System metadata ${DSH_FIXTURE_SECRET}`, + tools: [{ + name: "read_file", + description: `Tool metadata ${DSH_FIXTURE_SECRET}`, + parameters: { type: "object", properties: { path: { type: "string" } } }, + }], + }, + reason: "initial", + })); + output.slice(1).forEach((row, index) => { row.seq = index; }); + return output; +} + +function privacyRows(context, sessionId = "dsh-provider-privacy") { + const rows = insertAccountedEvents(makeSupportedDshSessionRows({ + workspace: context.workspace, + sessionId, + parentSession: DSH_FIXTURE_SECRET, + agentPreset: DSH_FIXTURE_SECRET, + })); + rows[3].data.content[0].text = `Run synthetic validation with ${DSH_FIXTURE_SECRET}`; + rows[4].data.content[0].text = `Injected continuation ${DSH_FIXTURE_SECRET}`; + rows[5].data.message.content.push({ + type: "tool-call", + id: "assistant-only-call", + name: "assistant_only", + arguments: "{}", + }); + rows[5].data.message.source.replayState = { opaque: DSH_FIXTURE_SECRET }; + rows[6].data.name = "shell_exec"; + rows[6].data.arguments = JSON.stringify({ command: `echo ${DSH_FIXTURE_SECRET}` }); + rows[7].data.meta = { private: DSH_FIXTURE_SECRET }; + rows[7].data.message.content[0].content[0].text = `result ${DSH_FIXTURE_SECRET}`; + rows[8].data.name = "read_file"; + rows[8].data.arguments = JSON.stringify({ + file_path: `C:/work/${DSH_FIXTURE_SECRET}.txt`, + paths: [ + `C:/work/${DSH_FIXTURE_SECRET}.txt`, + "synthetic.txt", + "synthetic.txt", + `${"x".repeat(260)}.txt`, + 42, + ...Array.from({ length: 12 }, (_value, index) => `safe-${index}.txt`), + ], + }); + return insertProviderRequestHeader(rows); +} + +test("DSH provider reads the pinned headless snapshot-like base flow without widening normalization", async () => { + const context = await fixtureContext("better-harness-dsh-native-snapshot-"); + const rows = makeNativeSnapshotDshSessionRows({ + workspace: context.workspace, + sessionId: "dsh-native-snapshot", + privateText: `Private writer metadata ${DSH_FIXTURE_SECRET}`, + }); + const turnEndIndex = rows.findIndex((row) => row.type === "turn/end"); + rows.splice(turnEndIndex, 0, + makeDshEvent("schedule/change", { + version: 1, operation: "create", schedule: { + id: "schedule-private", kind: "after", prompt: DSH_FIXTURE_SECRET, afterSeconds: 60, + scheduledAt: "2026-08-19T00:00:00.000Z", + }, + }), + makeDshEvent("web/deepseek-search-llm-request", { + endpoint: `https://example.invalid/${DSH_FIXTURE_SECRET}`, + apiVersion: "2023-06-01", + body: { + model: "deepseek-v4-flash", max_tokens: 64, + messages: [{ role: "user", content: [{ type: "text", text: DSH_FIXTURE_SECRET }] }], + tools: [{ type: "web_search_20250305", name: "web_search", max_uses: 1 }], + }, + })); + rows.slice(1).forEach((event, index) => { event.seq = index; }); + await writeRows(context, rows); + const analyzer = new DshSessionAnalyzer(); + const { input, scope, sessions } = await discover(analyzer, context); + assert.equal(sessions.length, 1); + assert.equal(sessions[0].incomplete, false); + const gates = [ + {}, + { includeUserText: true }, + { includeCommandText: true }, + { includeContent: true }, + { includeUserText: true, includeCommandText: true, includeContent: true }, + ]; + let events; + for (const gate of gates) { + const projected = await analyzer.readSession(sessions[0], scope, gate); + assert.equal(JSON.stringify(projected).includes(DSH_FIXTURE_SECRET), false); + events ??= projected; + } + assert.deepEqual([...new Set(events.map((event) => event.nativeType))].sort(), [ + "assistant/message", "tool/call", "tool/result", "turn/end", "turn/start", "user/message", + ]); + assert.equal(events.some((event) => event.nativeType === "request/header"), false); + assert.equal(events.some((event) => event.nativeType === "schedule/change"), false); + assert.equal(events.some((event) => event.nativeType === "web/deepseek-search-llm-request"), false); + for (const gate of gates) { + const facts = await analyzer.analyze({ ...input, command: "facts", limit: 1, ...gate }); + const publicJson = JSON.stringify(facts); + assert.equal(publicJson.includes(DSH_FIXTURE_SECRET), false); + assert.equal(publicJson.includes(rows[0].id), false); + assert.equal(publicJson.includes(context.dshHome), false); + } +}); + +test("DSH provider accounts private subagent descriptors without exposing composition", async () => { + const context = await fixtureContext("better-harness-dsh-subagent-private-"); + const header = makeDshHeader({ + workspace: context.workspace, sessionId: "dsh-subagent-private", parentSession: "fixture-parent", + seedLength: undefined, origin: "subagent", delegationDepth: 1, + }); + const rows = [ + header, + makeDshEvent("session/end-seed", {}, { seq: 0, time: header.createdAt + 1 }), + makeDshEvent("subagent/descriptor", { + version: 2, mode: "continuable", provider: "fixture-provider", label: DSH_FIXTURE_SECRET, + agentProvider: "fixture-agent", agentModel: "fixture-model", persona: DSH_FIXTURE_SECRET, + toolFilter: { allow: [DSH_FIXTURE_SECRET], deny: ["shell_exec"] }, + }, { seq: 1, time: header.createdAt + 2 }), + makeDshEvent("turn/start", { turn: 1 }, { seq: 2, time: header.createdAt + 3 }), + makeDshEvent("turn/end", { turn: 1, reason: { kind: "completed" } }, { seq: 3, time: header.createdAt + 4 }), + ]; + await writeRows(context, rows); + const analyzer = new DshSessionAnalyzer(); + const { input, scope, sessions } = await discover(analyzer, context); + assert.equal(sessions.length, 1); + assert.equal(sessions[0].diagnostics.knownUnsupportedTypes.includes("subagent/descriptor"), true); + for (const gate of [{}, { includeUserText: true }, { includeCommandText: true }, { includeContent: true }, + { includeUserText: true, includeCommandText: true, includeContent: true }]) { + const events = await analyzer.readSession(sessions[0], scope, gate); + const facts = await analyzer.analyze({ ...input, command: "facts", limit: 1, ...gate }); + assert.equal(JSON.stringify({ sessions, events, facts }).includes(DSH_FIXTURE_SECRET), false); + } +}); + +test("DSH provider projects only the six approved native event types and publishes dsh-v1 evidence", async () => { + const context = await fixtureContext(); + const rows = privacyRows(context); + await writeRows(context, rows); + const analyzer = new DshSessionAnalyzer(); + const { input, scope, roots, sessions } = await discover(analyzer, context); + + assert.equal(sessions.length, 1); + assert.equal(sessions[0].diagnostics.unknownIgnorableCount, 4); + assert.equal(sessions[0].diagnostics.unknownIgnorableTypes.length, 3); + assert.equal(sessions[0].diagnostics.knownUnsupportedTypes.includes("request/header"), true); + assert.equal(JSON.stringify(sessions[0]).includes(DSH_FIXTURE_SECRET), false); + assert.equal(JSON.stringify(roots).includes(DSH_FIXTURE_SECRET), false); + assert.equal(JSON.stringify(sessions[0]).includes("x".repeat(100)), false); + const events = await analyzer.readSession(sessions[0], scope, { + includeUserText: true, + includeCommandText: true, + includeContent: true, + }); + assert.deepEqual( + [...new Set(events.map((event) => event.nativeType))].sort(), + ["assistant/message", "tool/call", "tool/result", "turn/end", "turn/start", "user/message"], + ); + assert.equal(events.some((event) => ["step/start", "step/end", "assistant/chunk", "todo/write", + "fixture-future/ignorable"].includes(event.nativeType)), false); + assert.equal(events.filter((event) => event.type === "tool.call").length, 2); + assert.equal(events.some((event) => event.toolInvocationId === "assistant-only-call"), false); + + const sources = await analyzer.analyze({ ...input, command: "sources" }); + const listed = await analyzer.analyze({ ...input, command: "sessions" }); + const eventResult = await runProviderCommand(analyzer, "events", { ...input, "session-id": rows[0].id }); + const insights = await analyzer.analyze({ ...input, command: "insights", selection: "all-eligible" }); + const facts = await analyzer.analyze({ ...input, command: "facts", limit: 1 }); + + assert.equal(sources.scope.platform, "dsh"); + assert.equal(sources.sources[0].coverage, "partial"); + assert.equal(listed.sessions.length, 1); + assert.equal(eventResult.sessions[0].events.length, events.length); + assert.deepEqual(insights.insights.manifest.adapter, { id: "dsh", version: DSH_ADAPTER_VERSION }); + assert.equal(facts.kind, "session-core-facts"); + assert.equal(facts.scope.platform, "dsh"); + const factsText = JSON.stringify(facts); + assert.doesNotMatch(factsText, new RegExp(rows[0].id, "u")); + assert.equal(factsText.includes(context.dshHome), false); + assert.equal(factsText.includes(DSH_FIXTURE_SECRET), false); +}); + +test("DSH privacy gates preserve bounded user source distinctions without raw plugin payloads", async () => { + const context = await fixtureContext(); + const rows = privacyRows(context); + await writeRows(context, rows); + const analyzer = new DshSessionAnalyzer(); + const { scope, sessions } = await discover(analyzer, context); + + const closed = await analyzer.readSession(sessions[0], scope); + assert.equal(closed.some((event) => Object.hasOwn(event, "userText")), false); + assert.equal(closed.some((event) => Object.hasOwn(event, "commandText")), false); + assert.equal(closed.some((event) => Object.hasOwn(event, "content")), false); + + const open = await analyzer.readSession(sessions[0], scope, { + includeUserText: true, + includeCommandText: true, + includeContent: true, + }); + const direct = open.find((event) => event.type === "user"); + const continuation = open.find((event) => event.type === "context"); + const call = open.find((event) => event.type === "tool.call" && event.toolName === "shell_exec"); + assert.equal(direct.userSourceKind, "human"); + assert.equal(direct.userPrompt, true); + assert.equal(continuation.contextSourceKind, "plugin"); + assert.equal(continuation.contextForm, "notice"); + assert.equal(Object.hasOwn(continuation, "plugin"), false); + assert.equal(Object.hasOwn(continuation, "source"), false); + assert.match(direct.userText, //u); + assert.match(call.commandText, //u); + const serialized = JSON.stringify(open); + assert.equal(serialized.includes(DSH_FIXTURE_SECRET), false); + assert.equal(serialized.includes("opaque"), false); + assert.equal(serialized.includes("fixture-context"), false); + assert.equal(serialized.includes("private"), false); + assert.match(serialized, //u); + + const userTextOnly = await analyzer.readSession(sessions[0], scope, { includeUserText: true }); + const contextOnly = userTextOnly.find((event) => event.type === "context"); + assert.equal(Object.hasOwn(contextOnly, "userText"), false); + assert.equal(Object.hasOwn(contextOnly, "content"), false); + assert.equal(JSON.stringify(contextOnly).includes("Injected continuation"), false); + const contentOnly = await analyzer.readSession(sessions[0], scope, { includeContent: true }); + assert.match(contentOnly.find((event) => event.type === "context").content, /Injected continuation/u); + assert.equal(JSON.stringify(contentOnly).includes(DSH_FIXTURE_SECRET), false); +}); + +test("DSH assistant usage remains observed only when the native assembled message supplies it", async () => { + const context = await fixtureContext(); + const withUsage = makeSupportedDshSessionRows({ workspace: context.workspace, sessionId: "dsh-usage-present" }); + const withoutUsage = makeSupportedDshSessionRows({ workspace: context.workspace, sessionId: "dsh-usage-absent" }); + delete withoutUsage.find((row) => row.type === "assistant/message").data.usage; + await writeRows(context, withUsage); + await writeRows(context, withoutUsage); + const analyzer = new DshSessionAnalyzer(); + const { scope, sessions } = await discover(analyzer, context); + + const byId = new Map(sessions.map((session) => [session.sessionId, session])); + const observed = await analyzer.readSession(byId.get("dsh-usage-present"), scope); + const unobserved = await analyzer.readSession(byId.get("dsh-usage-absent"), scope); + const usage = observed.find((event) => event.type === "model.response.completed"); + assert.deepEqual(usage.modelUsage, { + inputTokens: 12, + outputTokens: 8, + cacheReadInputTokens: 2, + reasoningTokens: 1, + }); + assert.equal(usage.usageFieldsObserved, true); + assert.equal(unobserved.some((event) => event.type === "model.response.completed"), false); + assert.equal(unobserved.some((event) => Object.hasOwn(event, "modelUsage")), false); + assert.equal(unobserved.some((event) => Object.hasOwn(event, "usageFieldsObserved")), false); +}); + +test("DSH tool requests and results correlate while content, arguments, meta, and internal errors stay bounded", async () => { + const context = await fixtureContext(); + const rows = privacyRows(context, "dsh-tool-correlation"); + await writeRows(context, rows); + const analyzer = new DshSessionAnalyzer(); + const { scope, sessions } = await discover(analyzer, context); + const events = await analyzer.readSession(sessions[0], scope, { + includeCommandText: true, + includeContent: true, + }); + const calls = events.filter((event) => event.type === "tool.call"); + const results = events.filter((event) => event.type === "tool.result"); + assert.deepEqual(calls.map((event) => event.toolInvocationId), results.map((event) => event.toolInvocationId)); + assert.deepEqual(results.map((event) => event.success), [true, false]); + assert.equal(results[0].internalError, false); + assert.equal(results[1].internalError, true); + assert.equal(results[1].internalErrorCode, "FIXTURE_TOOL_FAILURE"); + assert.equal(Object.hasOwn(calls[0], "arguments"), false); + assert.equal(Object.hasOwn(results[0], "meta"), false); + assert.equal(calls[1].filePath.includes(DSH_FIXTURE_SECRET), false); + assert.equal(calls[1].targetPaths.includes("synthetic.txt"), true); + assert.equal(calls[1].targetPaths.length, 8); + assert.equal(new Set(calls[1].targetPaths).size, calls[1].targetPaths.length); + assert.equal(calls[1].targetPaths.every((value) => [...value].length <= 240), true); + assert.equal(JSON.stringify(events).includes(DSH_FIXTURE_SECRET), false); +}); + +test("DSH tool path facts are redacted, bounded, deduplicated, and gate independent", async () => { + const analyzer = new DshSessionAnalyzer(); + const sourceRef = { + kind: "dsh-session-jsonl", + role: "session-transcript", + path: path.join(os.tmpdir(), "synthetic-session.jsonl"), + sessionId: "dsh-path-privacy", + cwd: path.resolve(os.tmpdir(), "synthetic-workspace"), + dshProvenance: { delegationDepth: 0 }, + }; + const longPath = `${"long".repeat(80)}.txt`; + const rawSecretPath = `C:/work/${DSH_FIXTURE_SECRET}.txt`; + const event = makeDshEvent("tool/call", { + turn: 1, + step: 1, + callId: "fixture-path-call", + name: "read_file", + arguments: JSON.stringify({ + file_path: rawSecretPath, + paths: [rawSecretPath, "safe-relative.txt", "safe-relative.txt", longPath, null, 17, + ...Array.from({ length: 12 }, (_value, index) => `bounded-${index}.txt`)], + targetPaths: ["safe-target.txt", rawSecretPath], + affectedPaths: ["safe-affected.txt"], + }), + }, { seq: 0 }); + + for (const options of [ + {}, + { includeUserText: true }, + { includeCommandText: true }, + { includeContent: true }, + { includeUserText: true, includeCommandText: true, includeContent: true }, + ]) { + const normalized = analyzer.normalizeEvents(event, sourceRef, options); + const serialized = JSON.stringify(normalized); + assert.equal(serialized.includes(DSH_FIXTURE_SECRET), false); + assert.equal(serialized.includes(rawSecretPath), false); + assert.ok(normalized[0].filePath); + assert.equal([...normalized[0].filePath].length <= 240, true); + assert.equal(normalized[0].targetPaths.length, 8); + assert.deepEqual(normalized[0].targetPaths.slice(0, 2), [normalized[0].filePath, "safe-relative.txt"]); + assert.equal(new Set(normalized[0].targetPaths).size, normalized[0].targetPaths.length); + assert.equal(normalized[0].targetPaths.every((value) => [...value].length <= 240), true); + } + + const context = await fixtureContext("better-harness-dsh-path-privacy-"); + const rows = privacyRows(context, "dsh-provider-path-privacy"); + await writeRows(context, rows); + const input = { workspace: context.workspace, dshHome: context.dshHome, until: UNTIL }; + const eventsResult = await runProviderCommand(analyzer, "events", { + ...input, + "session-id": rows[0].id, + includeUserText: true, + includeCommandText: true, + includeContent: true, + }); + const publicEvents = eventsResult.sessions.flatMap((session) => session.events ?? []); + const facts = await analyzer.analyze({ ...input, command: "facts", limit: 1 }); + assert.equal(JSON.stringify(publicEvents).includes(DSH_FIXTURE_SECRET), false); + assert.equal(publicEvents.some((item) => item.type === "tool.call" + && item.toolInvocationId === "fixture-call-error" + && item.targetPaths.includes("synthetic.txt")), true); + const factsText = JSON.stringify(facts); + assert.equal(factsText.includes(DSH_FIXTURE_SECRET), false); + assert.equal(factsText.includes(rows[0].id), false); + assert.equal(factsText.includes(context.dshHome), false); +}); + +test("DSH source unions keep only human input user-facing and never expose private source metadata", async () => { + const analyzer = new DshSessionAnalyzer(); + const sourceRef = { + kind: "dsh-session-jsonl", role: "session-transcript", + path: path.join(os.tmpdir(), "source-union-session.jsonl"), sessionId: "dsh-source-union", + cwd: path.resolve(os.tmpdir(), "source-union-workspace"), dshProvenance: { delegationDepth: 0 }, + }; + const secret = DSH_FIXTURE_SECRET; + const sources = [ + { kind: "plugin", plugin: secret, form: "notice", summary: secret }, + { kind: "plugin", plugin: "compact", compactionId: secret, sourceCommandId: secret }, + { kind: "goal", goalId: secret, revision: 1, round: 1 }, + { kind: "agent-instructions", form: "instructions", baselineIdentity: secret, + changes: [{ action: "set", scope: secret, path: secret, digest: secret }] }, + { kind: "session-reference", form: "recall", version: 1, references: [{ + sessionId: secret, label: secret, capturedThroughSeq: null, compacted: false, + originalMessages: 1, retainedMessages: 1, omittedMessages: 0, omittedBytes: 0, + truncated: false, inputIndex: 0, + }] }, + { kind: "coordinator", form: "relay", senderSessionId: secret }, + { kind: "subagent-report", form: "relay", senderSessionId: secret }, + { kind: "subagent-settled", form: "notice", summary: secret, senderSessionId: secret }, + { kind: "skill-catalog", form: "catalog", entries: [{ name: secret, description: secret }] }, + { kind: "skill-invocation", form: "instructions", name: secret }, + ]; + for (const source of sources) { + const event = makeDshEvent("user/message", { + id: "private-context", role: "user", content: [{ type: "text", text: `private ${secret}` }], source, + }, { seq: 0, surfaceOp: "append" }); + const userOnly = analyzer.normalizeEvents(event, sourceRef, { includeUserText: true })[0]; + assert.equal(userOnly.type, "context"); + assert.equal(Object.hasOwn(userOnly, "userText"), false); + assert.equal(Object.hasOwn(userOnly, "content"), false); + const withContent = analyzer.normalizeEvents(event, sourceRef, { includeContent: true })[0]; + assert.equal(withContent.contextSourceKind, source.kind); + assert.equal(Object.hasOwn(withContent, "content"), true); + assert.equal(JSON.stringify({ userOnly, withContent }).includes(secret), false); + } + const direct = makeDshEvent("user/message", { + id: "api-human", role: "user", content: [{ type: "text", text: "safe prompt" }], + source: { kind: "user", rpcId: secret, clientTimeZone: secret }, + }, { seq: 0, surfaceOp: "append" }); + const projected = analyzer.normalizeEvents(direct, sourceRef, { includeUserText: true })[0]; + assert.equal(projected.type, "user"); + assert.equal(projected.userText, "safe prompt"); + assert.equal(JSON.stringify(projected).includes(secret), false); + + const context = await fixtureContext("better-harness-dsh-source-union-"); + const rows = makeSupportedDshSessionRows({ workspace: context.workspace, sessionId: "source-union-e2e" }); + rows[4].data.source = sources[4]; + rows[4].data.content[0].text = `private ${secret}`; + await writeRows(context, rows); + const { input, scope, sessions } = await discover(analyzer, context); + assert.equal(sessions.length, 1); + for (const gate of [{}, { includeUserText: true }, { includeContent: true }, + { includeUserText: true, includeCommandText: true, includeContent: true }]) { + const events = await analyzer.readSession(sessions[0], scope, gate); + const facts = await analyzer.analyze({ ...input, command: "facts", limit: 1, ...gate }); + assert.equal(JSON.stringify({ sessions, events, facts }).includes(secret), false); + } +}); + +test("DSH maps all six terminal outcomes and leaves open turns explicitly incomplete", async () => { + const context = await fixtureContext(); + const kinds = ["completed", "aborted", "blocked", "error", "max-tokens", "interrupted"]; + for (const kind of kinds) { + await writeRows(context, makeTerminalDshSessionRows(kind, { workspace: context.workspace })); + } + await writeRows(context, makeOpenTurnDshRows({ + workspace: context.workspace, + sessionId: "dsh-provider-open-turn", + })); + const analyzer = new DshSessionAnalyzer(); + const { scope, sessions } = await discover(analyzer, context); + const byId = new Map(sessions.map((session) => [session.sessionId, session])); + + const ends = new Map(); + for (const kind of kinds) { + const events = await analyzer.readSession(byId.get(`dsh-fixture-terminal-${kind}`), scope); + ends.set(kind, events.find((event) => event.type === "turn.end")); + } + assert.equal(ends.get("completed").success, true); + assert.equal(ends.get("aborted").cancelled, true); + assert.equal(ends.get("aborted").cancelCause, "hook"); + assert.equal(ends.get("blocked").blocked, true); + assert.equal(ends.get("error").hasError, true); + assert.equal(ends.get("error").errorCode, "FIXTURE_PROVIDER_FAILURE"); + assert.equal(ends.get("max-tokens").maxTokensReached, true); + assert.equal(ends.get("interrupted").incomplete, true); + assert.equal(ends.get("interrupted").success, null); + + const openSession = byId.get("dsh-provider-open-turn"); + const openEvents = await analyzer.readSession(openSession, scope); + assert.equal(openSession.incomplete, true); + assert.equal(openEvents.some((event) => event.type === "turn.end"), false); + assert.equal(openEvents.some((event) => event.success === true), false); +}); + +test("DSH provider admits open-step and pending-call crash prefixes without synthetic outcomes", async () => { + const context = await fixtureContext(); + const time = Date.parse("2026-08-18T00:00:00.000Z"); + const openStepRows = [ + makeDshHeader({ workspace: context.workspace, sessionId: "dsh-open-step", createdAt: time }), + makeDshEvent("turn/start", { turn: 1 }, { seq: 0, time: time + 1 }), + makeDshEvent("step/start", { turn: 1, step: 1 }, { seq: 1, time: time + 2 }), + ]; + const pendingRows = structuredClone(openStepRows); + pendingRows[0].id = "dsh-pending-call"; + pendingRows.push(makeDshEvent("tool/call", { + turn: 1, step: 1, callId: "pending-call", name: "read_file", arguments: "{}", + }, { seq: 2, time: time + 3 })); + await writeRows(context, openStepRows); + await writeRows(context, pendingRows); + + const analyzer = new DshSessionAnalyzer(); + const { input, scope, sessions } = await discover(analyzer, context); + const byId = new Map(sessions.map((session) => [session.sessionId, session])); + assert.equal(byId.get("dsh-open-step").diagnostics.incompleteReason, "open-step"); + assert.equal(byId.get("dsh-pending-call").diagnostics.incompleteReason, "pending-tool-result"); + assert.equal(byId.get("dsh-pending-call").diagnostics.pendingToolCallCount, 1); + + const openEvents = await analyzer.readSession(byId.get("dsh-open-step"), scope); + const pendingEvents = await analyzer.readSession(byId.get("dsh-pending-call"), scope); + assert.deepEqual(openEvents.map((event) => event.type), ["turn.start"]); + assert.deepEqual(pendingEvents.map((event) => event.type), ["turn.start", "tool.call"]); + assert.equal(pendingEvents.some((event) => event.type === "tool.result" || event.type === "turn.end"), false); + assert.equal(pendingEvents.some((event) => event.success === true), false); + const facts = await analyzer.analyze({ ...input, command: "facts", limit: 10 }); + const factsText = JSON.stringify(facts); + assert.equal(factsText.includes("tool result"), false); + assert.equal(factsText.includes("turn ended: completed"), false); +}); + +test("DSH provider preserves epoch-ms observations while native seq controls event order", async () => { + const context = await fixtureContext(); + const rows = makeSupportedDshSessionRows({ + workspace: context.workspace, + sessionId: "dsh-epoch-ms-order", + createdAt: 0, + }); + rows.slice(1).forEach((event) => { event.time = 1_000 + event.seq; }); + rows.find((event) => event.type === "tool/result").time = 500; + await writeRows(context, rows); + + const analyzer = new DshSessionAnalyzer(); + const { sessions } = await discover(analyzer, context); + const session = sessions[0]; + assert.equal(session.firstSeen, "1970-01-01T00:00:00.000Z"); + assert.equal(session.lastSeen, "1970-01-01T00:00:01.010Z"); + + const rangedScope = await analyzer.resolveScope({ + workspace: context.workspace, + dshHome: context.dshHome, + since: "1970-01-01T00:00:00.400Z", + until: "1970-01-01T00:00:01.010Z", + }); + const events = await analyzer.readSession(session, rangedScope); + assert.deepEqual(events.map((event) => event.nativeSeq), [...events.map((event) => event.nativeSeq)].sort((a, b) => a - b)); + const callIndex = events.findIndex((event) => event.type === "tool.call" && event.nativeSeq === 5); + const resultIndex = events.findIndex((event) => event.type === "tool.result" && event.nativeSeq === 6); + assert.equal(callIndex < resultIndex, true); + assert.equal(events[resultIndex].timestamp, "1970-01-01T00:00:00.500Z"); + const assistantIndex = events.findIndex((event) => event.type === "assistant" && event.nativeSeq === 4); + assert.deepEqual(events.slice(assistantIndex, assistantIndex + 2).map((event) => event.type), + ["assistant", "model.response.completed"]); + + const narrowScope = await analyzer.resolveScope({ + workspace: context.workspace, + dshHome: context.dshHome, + since: "1970-01-01T00:00:01.002Z", + until: "1970-01-01T00:00:01.004Z", + }); + const narrow = await analyzer.readSession(session, narrowScope); + assert.equal(narrow.some((event) => event.nativeSeq === 0), false); + assert.equal(narrow.some((event) => event.nativeSeq === 2), true); + assert.equal(narrow.some((event) => event.nativeSeq === 4), true); +}); + +function surfaceReplacementRows(workspace) { + const rows = makeSupportedDshSessionRows({ workspace, sessionId: "dsh-provider-surface" }); + const stepEndIndex = rows.findIndex((row) => row.type === "step/end"); + const stepEnd = rows[stepEndIndex]; + const turnEnd = rows[stepEndIndex + 1]; + const user = structuredClone(rows.find((row) => row.type === "user/message")); + user.seq = stepEnd.seq; + user.time = stepEnd.time - 3; + user.data.id = "fixture-user-replacement"; + user.data.content[0].text = "Replacement user surface."; + user.sourceEventSeqs = [2]; + user.surfaceOp = { op: "replace", start: 2, end: 2 }; + const assistant = structuredClone(rows.find((row) => row.type === "assistant/message")); + assistant.seq = stepEnd.seq + 1; + assistant.time = stepEnd.time - 2; + assistant.data.message.id = "fixture-assistant-replacement"; + assistant.data.message.content[0].text = "Replacement assistant surface."; + assistant.sourceEventSeqs = [4]; + assistant.surfaceOp = { op: "replace", start: 4, end: 4 }; + const result = structuredClone(rows.find((row) => row.type === "tool/result")); + result.seq = stepEnd.seq + 2; + result.time = stepEnd.time - 1; + result.data.message.content[0].content[0].text = "replacement tool content"; + result.sourceEventSeqs = [6]; + result.surfaceOp = { op: "replace", start: 6, end: 6 }; + rows.splice(stepEndIndex, 0, user, assistant, result); + stepEnd.seq += 3; + turnEnd.seq += 3; + return rows; +} + +function reusedCallAcrossTurnsRows(workspace) { + const rows = makeSupportedDshSessionRows({ workspace, sessionId: "dsh-provider-reused-call" }); + const firstCall = rows.find((row) => row.type === "tool/call"); + const firstResult = rows.find((row) => row.type === "tool/result"); + const last = rows.at(-1); + const seq0 = last.seq + 1; + const time0 = last.time + 10; + const call = structuredClone(firstCall); + call.seq = seq0 + 2; + call.time = time0 + 20; + call.data.turn = 2; + call.data.step = 1; + const result = structuredClone(firstResult); + result.seq = seq0 + 3; + result.time = time0 + 30; + result.data.turn = 2; + result.data.step = 1; + result.data.message.id = "fixture-tool-reused-call"; + result.surfaceOp = "append"; + rows.push( + makeDshEvent("turn/start", { turn: 2 }, { seq: seq0, time: time0 }), + makeDshEvent("step/start", { turn: 2, step: 1 }, { seq: seq0 + 1, time: time0 + 10 }), + call, + result, + makeDshEvent("step/end", { turn: 2, step: 1 }, { seq: seq0 + 4, time: time0 + 40 }), + makeDshEvent("turn/end", { turn: 2, reason: { kind: "completed" } }, { + seq: seq0 + 5, + time: time0 + 50, + }), + ); + return rows; +} + +test("DSH normalization projects final canonical surface nodes and preserves callId reuse across turns", async () => { + const context = await fixtureContext(); + await writeRows(context, surfaceReplacementRows(context.workspace)); + await writeRows(context, reusedCallAcrossTurnsRows(context.workspace)); + const analyzer = new DshSessionAnalyzer(); + const { scope, roots, sessions } = await discover(analyzer, context); + const byId = new Map(sessions.map((session) => [session.sessionId, session])); + assert.ok(byId.has("dsh-provider-surface"), JSON.stringify(roots[0].warnings)); + assert.ok(byId.has("dsh-provider-reused-call"), JSON.stringify(roots[0].warnings)); + + const surface = await analyzer.readSession(byId.get("dsh-provider-surface"), scope, { + includeUserText: true, + includeContent: true, + }); + assert.equal(surface.some((event) => event.nativeSeq === 2), false); + assert.equal(surface.some((event) => event.nativeSeq === 4), false); + assert.equal(surface.some((event) => event.nativeSeq === 6), false); + assert.match(surface.find((event) => event.type === "user")?.userText, /Replacement user/u); + assert.match(surface.find((event) => event.type === "assistant")?.content, /Replacement assistant/u); + assert.match(surface.find((event) => event.type === "tool.result" + && event.toolInvocationId === "fixture-call-success")?.content, /replacement tool/u); + + const reused = await analyzer.readSession(byId.get("dsh-provider-reused-call"), scope); + assert.equal(reused.filter((event) => event.type === "tool.call" + && event.toolInvocationId === "fixture-call-success").length, 2); + assert.equal(reused.filter((event) => event.type === "tool.result" + && event.toolInvocationId === "fixture-call-success").length, 2); +}); + +test("DSH provider reads concatenated Zstd evidence and reports unavailable compression without hiding raw sessions", async () => { + const context = await fixtureContext(); + const rawRows = makeSupportedDshSessionRows({ workspace: context.workspace, sessionId: "dsh-provider-raw" }); + await writeRows(context, rawRows); + + if (typeof zlib.zstdCompressSync === "function" && Number.isSafeInteger(zlib.constants?.ZSTD_c_checksumFlag)) { + const compressedRows = makeSupportedDshSessionRows({ + workspace: context.workspace, + sessionId: "dsh-provider-zstd", + }); + await writeRows(context, compressedRows, { compression: "zstd" }); + const analyzer = new DshSessionAnalyzer(); + const { scope, sessions } = await discover(analyzer, context); + const compressed = sessions.find((session) => session.sessionId === "dsh-provider-zstd"); + const events = await analyzer.readSession(compressed, scope); + assert.equal(events.some((event) => event.type === "assistant"), true); + } + + const compressedId = "dsh-provider-unavailable"; + const directory = path.join( + context.dshHome, + "sessions", + dshProjectKey(context.workspace), + encodeDshSessionIdSegment(compressedId), + ); + await mkdir(directory, { recursive: true }); + await writeFile(path.join(directory, "session.jsonl.zstd"), Buffer.from([0x28, 0xb5, 0x2f, 0xfd])); + const unavailableAnalyzer = new DshSessionAnalyzer({ decompressor: null }); + const { roots, sessions } = await discover(unavailableAnalyzer, context); + assert.equal(sessions.some((session) => session.sessionId === "dsh-provider-raw"), true); + assert.equal(roots[0].warnings.some((warning) => warning.code === "dsh-zstd-unavailable"), true); + assert.equal(JSON.stringify(roots[0].warnings).includes(DSH_FIXTURE_SECRET), false); +}); + +test("DSH public normalization entry validates event shape and re-sanitizes provenance", () => { + const analyzer = new DshSessionAnalyzer(); + const event = makeDshEvent("turn/start", { turn: 1 }, { seq: 0 }); + const ref = { + kind: "dsh-session-jsonl", + role: "session-transcript", + path: path.join(os.tmpdir(), "synthetic-session.jsonl"), + sessionId: "dsh-public-normalization", + cwd: path.resolve(os.tmpdir(), "synthetic-workspace"), + dshProvenance: { delegationDepth: 1, origin: "subagent", parentSession: DSH_FIXTURE_SECRET }, + }; + const normalized = analyzer.normalizeEvent(event, ref); + assert.equal(normalized.type, "turn.start"); + assert.equal(JSON.stringify(normalized).includes(DSH_FIXTURE_SECRET), false); + assert.throws( + () => analyzer.normalizeEvent({ ...event, data: { turn: 0 } }, ref), + (error) => error?.code === "DSH_EVENT_SHAPE_DRIFT", + ); + assert.equal(analyzer.normalizeEvent(makeKnownUnsupportedDshEvent({ seq: 0 }), ref), null); +}); diff --git a/test/sessions/session-analysis-providers.test.mjs b/test/sessions/session-analysis-providers.test.mjs index b8c4e35f..4b90ed5e 100644 --- a/test/sessions/session-analysis-providers.test.mjs +++ b/test/sessions/session-analysis-providers.test.mjs @@ -44,6 +44,7 @@ import { parseWorkspaceDescriptor, } from "../../scripts/session-analysis/platforms/copilot.mjs"; import { KimiSessionAnalyzer } from "../../scripts/session-analysis/platforms/kimi.mjs"; +import { DshSessionAnalyzer } from "../../scripts/session-analysis/platforms/dsh.mjs"; import { measureLongSessionRows } from "../../scripts/session-analysis/long-sessions.mjs"; async function fixtureRoot(prefix) { @@ -66,6 +67,7 @@ test("root dispatcher creates Claude and Cursor provider analyzers", async () => assert.ok(await createAnalyzer("pi") instanceof PiSessionAnalyzer); assert.ok(await createAnalyzer("workbuddy") instanceof WorkbuddySessionAnalyzer); assert.ok(await createAnalyzer("grok") instanceof GrokSessionAnalyzer); + assert.ok(await createAnalyzer("dsh") instanceof DshSessionAnalyzer); assert.ok(await createCapabilityAnalyzer("claude") instanceof ClaudeSessionAnalyzer); assert.ok(await createCapabilityAnalyzer("cursor") instanceof CursorSessionAnalyzer); assert.ok(await createCapabilityAnalyzer("qwen") instanceof QwenSessionAnalyzer); @@ -73,6 +75,8 @@ test("root dispatcher creates Claude and Cursor provider analyzers", async () => assert.ok(await createCapabilityAnalyzer("pi") instanceof PiSessionAnalyzer); assert.ok(await createCapabilityAnalyzer("workbuddy") instanceof WorkbuddySessionAnalyzer); assert.ok(await createCapabilityAnalyzer("grok") instanceof GrokSessionAnalyzer); + assert.ok(await createCapabilityAnalyzer("dsh") instanceof DshSessionAnalyzer); + await assert.rejects(() => createAnalyzer("unknown-host"), /Unsupported session provider: unknown-host/u); }); test("public session-analysis main preserves the bare help alias", async () => { @@ -87,6 +91,8 @@ test("public session-analysis main preserves the bare help alias", async () => { assert.equal(result, 0); assert.equal(output, SESSION_ANALYSIS_HELP); + assert.match(output, /\|dsh>/u); + assert.match(output, /--dsh-home \s+DeepSeek Harness data root \(default: ~\/\.dsh or \$DSH_HOME\)/u); }); test("Claude, Cursor, and Qwen workspace slugs cover Unix and Windows layouts", () => {