iiwish
dsh-testkit
Lifecycle conformance testing for DeepSeek Harness plugins
- Stars
- 1
- Language
- TypeScript
- Created
- Aug 15, 2026
- Updated
- Aug 15, 2026
Introduction
DSH Testkit
Real-host lifecycle testing for DeepSeek Harness plugins.
DSH Testkit installs the exact DSH version and a plugin's packed artifact into a disposable environment, boots the real host, probes deterministic runtime capabilities, removes the plugin, reboots the same profile, and retains machine-readable evidence. It does not call a model or require a model API key.
pnpm add -D dsh-testkit
pnpm dsh-test . --dsh 0.1.0-rc.6 \
--expect-row tool-my-plugin \
--expect-tool my_tool
DSH Testkit is also an official DSH Profile Bundle. Install it into the profile where you want the tool available:
dsh plugin --profile web add dsh-testkit@0.2.0
dsh --profile web --dump-config
The bundle registers dsh_test. It tests the active workspace by default, accepts the same expected row/service/tool assertions, and returns the verdict plus paths to the complete report. The tool always uses Docker, ignores implicit dsh-testkit.yaml configuration, restricts local inputs to the active workspace, requires confirm: true, and asks through DSH's pre-execution approval pipeline for agent-originated calls. It never exposes unsafe-local execution or arbitrary CLI arguments.
Docker is the default runner. The plugin and its install scripts execute inside a fresh container, while the source mount is read-only and the run root uses disposable filesystems. Local execution is deliberately loud:
pnpm dsh-test . --dsh 0.1.0-rc.6 --runner local --unsafe-local
What It Proves
One scenario records these ordered stages:
resolve -> install-dsh -> package -> install-plugin -> assemble -> boot -> register -> exercise -> update? -> uninstall -> reboot -> recover? -> cleanup
- Local directories are copied and tested through
npm pack, catching publication mistakes that links hide. - DSH is installed at an exact npm version and invoked through its public profile and plugin commands.
- Expected config rows come from
--dump-config; expected services and tool schemas come from an in-process Cordis probe. - A baseline runtime-probe exercise always runs; declared tool exercises also use the real tool runtime without asking a model to choose or generate anything.
- Uninstall checks the profile manifest, bundle list, rebooted capabilities, and unexplained owned-root files.
- Every unavailable observer is disclosed. Requiring unavailable coverage yields
unsupported, not a false pass. --suite fullruns five isolated attempts and returnsflakywhen their semantic outcomes disagree.
The current DSH adapter supports @deepseek-ai/dsh@0.1.0-rc.6. Other exact versions fail with exit code 4 before a runner is created, preventing host-version drift from being reported as a plugin failure.
Scenario
Place dsh-testkit.yaml in the plugin project:
schemaVersion: 1
name: my-plugin-quick
subject:
source: .
dsh:
version: 0.1.0-rc.6
expect:
boot: success
rows: [tool-my-plugin]
services: [myService]
tools: [my_tool]
exercise:
- tool: my_tool
arguments:
value: smoke
observers:
filesystem: required
process: preferred
ports: preferred
network: off
canary: preferred
See Scenario Reference for updates, expected failures, recovery, timeouts, and observer policy.
Evidence And CI
Each run writes:
report.json: versioned canonical result and environment fingerprint.junit.xml: one test case per lifecycle stage.report.md: support-ready lifecycle and observer summary.logs/: sanitized stdout and stderr for each external command.evidence/: resolved scenario, effective config, runtime probes, filesystem, process, and port snapshots.
Full or explicitly repeated runs retain each complete report under attempts/{nn}/ and write an aggregate root report with semantic digests. Use --repeat 2 through --repeat 20 for targeted repeatability checks; full requires at least five attempts.
Lifecycle stages are stable case identifiers. Rerun a failed stage with its required prefix and the same scenario identity using --case:
pnpm dsh-test . --dsh 0.1.0-rc.6 --case boot
Later stages are recorded as skipped and cleanup still runs. The selected case is preserved in report.json and the reproduction command.
The package publishes schemas/scenario-v1.json and schemas/report-v1.json for CI consumers that validate artifacts outside Node.js.
Stable exit codes are 0 passed, 1 lifecycle failure, 2 invalid input, 3 infrastructure error, 4 unsupported required capability, and 5 flaky.
The composite Action publishes JUnit and uploads the complete run directory:
- uses: iiwish/dsh-testkit/.github/actions/dsh-test@v0
with:
plugin: .
dsh-version: 0.1.0-rc.6
Matrix jobs derive unique output directories, check names, and artifact names. artifact-name, check-name, output, and artifact-retention-days remain configurable; artifact-id, artifact-url, and artifact-digest are Action outputs. Pass complex extra options without shell quoting loss through args-json, for example args-json: '["--expect-row", "row with spaces"]'.
The composite Action targets GitHub.com because actions/upload-artifact@v4+ is unavailable on GitHub Enterprise Server. GHES and other CI systems can invoke the dsh-test CLI directly and retain the same JSON/JUnit evidence.
Private plugins stay on the CI runner. The Testkit has no SaaS dependency and does not upload source or credentials.
Scope And Safety
DSH Testkit tests DSH lifecycle behavior. It is not a plugin marketplace, cross-Harness standard, model-quality benchmark, static security scanner, or proof that executable code is safe. Docker reduces the default blast radius but is not a hardened malware sandbox. Do not run untrusted plugins with --unsafe-local.
The native dsh_test tool requires access to the Docker daemon and can execute package scripts with network access inside the runner. Confirmation is a deliberate trust decision, not a security certification.
Architecture and trust boundaries are documented in Architecture. Development workflow is in Contributing.
MIT License.