# dsh-plugin-doctor — Verification Criteria Specification

| | |
|---|---|
| **Spec version** | `v1` (checkset `R0-R8+K1-K14+D0-D3,D9+CC1-CC5/3`) |
| **Author / Editor** | **PerryLink** — <https://github.com/PerryLink> |
| **Canonical source** | <https://github.com/PerryLink/dsh-plugin-doctor> |
| **Reference implementation** | `@perrylink/dsh-plugin-doctor` (see `package.json`) |
| **Status** | Authored specification. Single-maintainer; see [`GOVERNANCE.md`](GOVERNANCE.md). |
| **Normative text** | This file. When this document and the implementation disagree, that is a defect in one of them — not a licence to read the implementation as the spec. |

## 0. Purpose and scope

This document defines a set of **verification criteria** for plugins of DeepSeek
Harness (dsh). Each criterion has a stable ID, a stated requirement, a defined
verdict vocabulary, and the first-hand evidence it traces to.

What this specification is **not**:

- It is **not a certification**. It states nothing about whether a plugin is
  safe, useful, or well-engineered beyond the specific requirement of each check.
- It is **not a security audit**. It performs no provenance or supply-chain
  attestation.
- It does **not** prove business logic correct. `D3` proves only that the
  composition boots as far as a model request.

The criteria were authored by PerryLink, and versioned as a specification rather
than left as an emergent convention, so that a result can be cited as
`dsh-plugin-doctor SPEC v1 §K3` and keep its meaning over time.

## 1. Conformance and citation

- **Conforming implementation.** A tool conforms to this specification at
  version `v1` if, for each check in §4, it emits a result carrying the check's
  ID and one of the §2 verdicts, decided by the §4 requirement. Conformance does
  not require using the reference implementation, and does not require
  permission.
- **Citing a result.** A result should be cited with the spec version, the check
  ID, and the commit or version tested — e.g.
  *"dsh-plugin-doctor SPEC v1 §R1 passed on `<sha>`"*. A citation without a
  version is not falsifiable and should not be treated as evidence.
- **The canonical gate.** The set of checks a project gates its CI on is its own
  choice and must be stated by that project. The reference project's own gate is
  `R0, R1, R3, R5, R6, R7, R8, K1–K14` (21 checks), deliberately excluding `R2`
  and `R4`, which read **built** artifacts. A project that gates a different set
  has not thereby conformed to the same claim.

### 1.1 Interoperating with the ecosystem check contract

During 2026-08 the ecosystem's plugin checkers converged on one interface: a
three-value status vocabulary (`PASS`/`WARN`/`FAIL`), exit codes `0`/`1`/`2`, and
a flat `checks` array carrying `{name, status, detail}`. It is written up as
[RFC #1846](https://github.com/deepseek-ai/deepseek-harness/discussions/1846),
and the reference implementations named there follow it.

This specification is **not** that contract. It is richer in three ways that
matter, and a conforming implementation must not silently discard them:

| This specification | The three-value contract | Why the difference is kept |
|---|---|---|
| five statuses, including `skip` | three | `skip` means **not evaluated**. Collapsing it into `pass` is the false-green this specification exists to prevent. |
| exit codes `3`/`4`/`5`/`6` | `0`/`1`/`2` | `4` (unsupported host) and `6` (degraded) are *not* plugin defects. Reporting them as `1` blames the plugin for the environment. |
| per-check `id`, `groupId`, `category`, `coverage` | `name`/`status`/`detail` | §3.3 makes the ID the citable, frozen handle. |

An implementation MAY therefore expose a **contract view** in addition to its own
output, provided that:

1. the contract view never changes whether a project passes — the two views must
   agree on pass/fail for the same run;
2. a check that returned `skip` is never presented as `PASS` in the contract view
   (the reference implementation reports it as `WARN` and marks it
   `skipped: true`);
3. any distinction the contract cannot express is either carried in an extra
   field or flagged as approximated — never dropped in silence.

The reference implementation provides this as `--format check`, and
`tests/compat.mjs` asserts all three properties, including that the default view
is unchanged.

## 2. Verdict vocabulary (normative)

Five statuses exist. Their meanings are fixed and may not be interchanged.

| Status | Meaning | Counts as a failure? |
|---|---|---|
| `pass` | The requirement was evaluated and holds. | no |
| `warn` | The requirement was evaluated and looks violated, **but the check is heuristic** and may be wrong. Requires human review; never condemns a plugin by itself. | no |
| `fail` | The requirement was evaluated and is violated. | **yes** |
| `error` | The check itself could not complete (doctor-internal fault). | **yes** |
| `skip` | The requirement was **not evaluated** — it does not apply, or its input was absent. | no |

Two additional classifications are orthogonal to status:

- **`critical`** — currently only `R1`. A failing critical check means the plugin
  cannot activate at all. Distinct from a normal failure.
- **`category`** — `plugin-defect`, `policy`, `not-applicable`, `environment`,
  `infrastructure`, `unsupported-host`, or `unstable`. **`environment`,
  `infrastructure`, `unsupported-host` and `unstable` are never plugin defects.**
  **`policy`** marks a deliberate convention this specification does not share,
  as opposed to a defect in the package (§5.4 note 4 covers `R5` and `R8`). It was
  added because `warn` otherwise defaulted to `plugin-defect`, which contradicted
  those checks' own wording: a message reading "this is a choice, not a defect"
  filed under `plugin-defect` is a report that disagrees with itself.

**`skip` is not a pass** (§3.2). A group whose checks all returned `skip` was not
evaluated, and reporting it as green is the specific false-positive this
specification exists to prevent.

## 3. Group structure and evaluation semantics

### 3.1 Groups

| Group | ID | Checks | Requires |
|---|---|---|---|
| Static · package structure | `R` | R0–R8 | the committed tree |
| Static · Cordis contract scan | `K` | K1–K14 | the committed tree |
| Dynamic · sandbox smoke | `D` | D0–D3, D9 | network, `npm`, `pnpm` |
| Ecosystem · directory listings | `CC` | CC1–CC5 | a workspace holding the listed directories |

`R` and `K` are **offline-capable**. `D` is not. `CC` verifies third-party
directory listings and is meaningful only alongside those directories.

### 3.2 Degradation (normative)

A requested group in which **every** check returned `skip` has not been
evaluated. Such a run is **degraded** and must not report success:

- the run reports the degraded groups, and
- a non-zero, distinct exit code is required unless degradation is explicitly
  accepted by the caller.

*Reporting the degraded groups is a requirement on the **human-readable output**,
not only on the envelope or the exit code*: an interface with no exit code (the
bundled `plugin_doctor` tool and `/doctor` command) has nothing else to carry the
fact, and `verdict.ok` is deliberately true for a degraded run. Until 0.7.1
`render()` dropped the `degraded` list, so a degraded tool reply was
indistinguishable from a run with nothing to do.

A run in which zero checks executed at all, or in which a requested group name
matched nothing, must also not report success. *Rationale: a mangled group name
once turned the CI gates of 35 repositories green while checking nothing.*

### 3.3 Verdicts do not depend on colour or language

Check IDs are ASCII and **frozen**: `R0`–`R8`, `K1`–`K14`, `D0`–`D3`, `D9`,
`CC1`–`CC5`. A check's human-readable label may be translated; its ID may not
change meaning. Implementations must expose the ID independently of any
translated display string.

### 3.4 K coverage modes (normative disclosure)

The `K` group scans source files discovered by a defined strategy, and must
disclose which mode it used:

| Mode | When | Meaning |
|---|---|---|
| `src` | the repository ships `src/` (plus root-level scripts) | full intended coverage |
| `lib-fallback` | no `src/`; the directory holding `main`, else `lib/`, else `dist/` | real coverage **but not equivalent** — a bundle is harder to read heuristically and K3–K7 may under-report |
| `none` | neither found | `K` did not run → §3.2 degradation |

A `lib-fallback` result must never be presented as equivalent to `src`.

### 3.5 Standards mapping (informative)

The JSON envelope carries an additive `standards` field that projects a run onto
the DSH adoption tracks defined by the independent
[`PerryLink/dsh-standards`](https://github.com/PerryLink/dsh-standards) project
(`schema: "dsh-levels/1"`). It is derived by `lib/standards.mjs`, a pure function
of the run's results and group summary: no filesystem, no clock, no process.

The field is **additive and informative**. It does not change any verdict, exit
code, check ID, the `results` array, or the `--format check` view; a consumer that
ignores it sees exactly the run it saw before.

| Doctor group | ENTRY contribution | Decides `entry.status`? |
|---|---|---|
| R (R0–R8) | static package/entry/patch shape, dependency and peer eligibility | yes |
| K (K1–K14) | static Cordis contract heuristics | yes |
| D (D0–D3, D9) | sandbox pack/install/layer/boot chain (D9 is hygiene, not boot evidence) | yes |
| X (X1–X5) | host-surface and peer-corridor support | no — supplementary |
| CC (CC1–CC5) | directory listings, not package compatibility | no |

`entry.status` is one of `runtime`, `static`, `failed`, `not-run`:

- `failed` — any R/K/D check returned `fail` or `error`, or a critical check failed.
- `runtime` — R and K each ran at least one check, **and** D0–D3 all ran, with no
  `fail`/`error`. This is doctor's own sandbox chain, not a claim that canonical
  G0–G5 all passed.
- `static` — R and K ran clean but the D0–D3 chain is incomplete. Static is not a
  pass.
- `not-run` — no ENTRY level can be established (for example, every group was
  skipped). Not-run is not a pass.

A skipped check is never evidence: a D1 skip (missing `pnpm`, for example) skips
D2/D3 and yields `static`, never `runtime`. That conservative projection is the
point, and `tests/standards.mjs` freezes it.

FLOW is `manual` (`J1–J8`) and BOOST is `not-measured` (`LEV-1..LEV-8`): this
tool runs no user journeys and no benchmarks, so it must not claim either track.
The three tracks are different questions, not grades; certification grades A–D
are orthogonal and are never combined into a level. The mapping is
**non-official** — the levels come from the independent `PerryLink/dsh-standards`
project, not from DeepSeek.

The crosswalk, the group/check → ENTRY-gate map, and the FLOW/BOOST gaps are in
[`STANDARDS.md`](STANDARDS.md) and `data/standards.json`.

## 4. The criteria

Legend: **Verdicts** lists the statuses the check can return.
`(critical)` marks §2 criticality. Every criterion below is normative.

### 4.1 Group R — static · package structure

#### R0 — Base manifest fields
**Requirement.** `package.json` declares `name`, `version`, `license` (SPDX) and
`type`; the repository ships `README.md` and a `LICENSE` file.
**Verdicts.** `warn` (any field missing — listed) · `pass`.
**Note.** `warn`, not `fail`: these are publication-readiness fields, and npm
itself accepts packages lacking several of them.

#### R1 — Activation gate `dsh.bundle.patch` **(critical)**
**Requirement.** `package.json` declares `dsh.bundle.patch` as a non-empty
string.
**Verdicts.** `fail` · `pass`.
**Failure meaning.** The package installs as an ordinary dependency and the host
never adds it to `dsh.profile.bundles`. This is the **most silent failure mode**
in the ecosystem: the install succeeds, nothing errors, and the plugin simply
never activates. This is why `R1` is the only critical check.

#### R2 — Tarball integrity
**Requirement.** `npm pack --dry-run --json --ignore-scripts` succeeds, and the
resulting file list contains both the declared entry point and the declared patch
file, both non-empty; the tarball filename matches the package name and version.
**Verdicts.** `fail` · `pass` · `skip` (see the unbuilt-tree rule below).
**Requires.** A built tree. **Excluded from the canonical gate** (§1) — see
§5 for why.
**Unbuilt-tree rule.** When the entry resolves into a build-output directory
(`lib/`, `dist/`, `build/`, `out/`, `esm/`, `cjs/`), that file is absent, and
`files` is declared and *does* cover it, the check reports `skip` with category
`environment` rather than `fail`. An absent build output on a tree that was never
built is an environment fact, not a plugin defect. If `main` points into `src/`,
or the build output is missing from `files`, the check still **fails** — those
defects survive a build.

#### R3 — `cordis.patch.yml` structure (heuristic)
**Requirement.** The declared patch file exists and parses as a top-level YAML
array of loader patch entries. An **empty** array is valid: it declares a bundle
layer that mounts no rows of its own, which is the normal shape for a library
that only needs to travel through the bundle channel — the harness itself writes
`[]` as the template for a new profile's patch layer, and the patch schema is a
JSON array with `!!js` expressions. For a **non-empty** patch, after comment lines
are removed it must contain an `- insert:` structure and at least one `id:` line,
and every `name:` must equal the package name or be namespaced under it.
**Verdicts.** `skip` (no declared patch) · `fail` · `pass`.
**Why `id:` is required.** Row ids are how a patch line is located for whole-row
replacement upstream. That requirement applies only when there is a row to
locate — hence not to an empty layer.
**Why `name:` must be the package name.** The row is resolved through the
profile's `node_modules`; a name that is not the package name will not resolve.
**Limit.** Heuristic. `D2` is the authoritative check for layer insertion.

#### R4 — Entry contract
**Requirement.** The entry file named by `main`/`exports` exists and exports
both `apply` and `name`; a declared `inject` array contains only string literals.
**Verdicts.** `fail` (entry missing) · `warn` (exports or `inject` shape
suspect) · `pass` · `skip` (unbuilt tree — same rule as R2).
**Requires.** A built tree. **Excluded from the canonical gate** (§1, §5).

#### R5 — Dependency policy
**Requirement.**
1. No bare `cordis` or bare `schemastery` in `dependencies`,
   `peerDependencies` or `devDependencies` — the rescoped
   `@deepseek-ai/*` names are required.
2. If source imports `@deepseek-ai/cordis`, it must be declared.
3. `@deepseek-ai/cordis` must be declared as `peerDependencies` **and**
   `devDependencies`, not as `dependencies`.
4. `@deepseek-ai/dsh` must not be a direct `dependencies` entry (the host
   provides it).

**Verdicts.** `fail` · `pass` · `skip` (no source files **and** no cordis
declaration — explicitly **not** a pass).
**Failure meaning.** A bare `cordis` dependency resolves to the unscoped v3
line, not the host's rescoped v4 instance — the plugin gets a different registry
than the host and its services do not connect.

#### R6 — Node engine declaration
**Requirement.** `engines.node` must be compatible with the official
`^22.19.0 || >=24.0.0`.
**Verdicts.** `fail` (declares Node 23 only — the official line excludes 23) ·
`warn` (absent, or an intersecting range needing human confirmation) · `pass`
(exact agreement).
**Note.** Advisory on npm-line hosts, whose packuments do not enforce
`engines`.

#### R7 — Prebuild and `files` allowlist
**Requirement.** A `files` allowlist is declared and covers both the entry point
and the patch file. Additionally, **if the package needs a build**, the entry
must not point into source.
**When a build is needed.** The package declares a `build` script, or its entry
is a TypeScript file, or it ships TypeScript sources under `src/`. The harness
documents that a git install fetches *sources and never runs `build`*, so a
TypeScript package that is not prebuilt cannot load.
**A plain-JavaScript package pointing `main` at its own `src/` is valid** and
passes. The official publish documentation's own manifest example uses
`"index.js"`, and it presents shipping build output as one distribution option
rather than an obligation. Requiring `lib/` unconditionally rejected working,
zero-build packages.
**Verdicts.** `fail` · `pass`.
**Failure meaning.** A missing or incomplete `files` allowlist silently drops
the entry point or the patch file from the published tarball — the package
installs and never activates. A build-needing package whose entry points at
source publishes `.ts` that the host cannot import.

#### R8 — Stale peer ranges (dual-baseline lesson)
**Requirement.** No `@deepseek-ai/dsh*` or cordis peer range names a
superseded prerelease line (`0.1.0-rc.*`, `0.1.1-rc.*`, `0.1.2-alpha.*`,
`0.1.3-alpha.*`), and no such range is **open-top** (a `>=` comparator with no
upper bound).
**Verdicts.** `skip` (no dsh-related peer) · `fail` (a superseded line is named) ·
`warn` (a single-arm prerelease-tuple range, and/or an open-top range) · `pass`.
A `fail` reports the open-top notes as well when both apply — `>=0.1.0-rc.1` is
both, and reporting only the staleness would let a repository fix the cosmetic
problem and keep the one that admits a breaking host line.
**Failure meaning (superseded line).** The declared range cannot admit the host
line in use, while appearing compatible.
**Warning meaning (single-arm range).** A range such as
`>=0.1.7-alpha.1 <0.2.0` without `||` admits only the one prerelease it names, so
it excludes whatever line is currently `latest`. That is a real consequence — the
plugin will not install against the stable host — but it is a **choice about which
host to support**, not a defect, and the check cannot tell a deliberate target from
the historical trap offline. The historical trap is the reason the OR form exists:
`>=0.1.2-rc.1 <0.2.0` silently rejects `0.1.5-rc.1`. Naming a superseded line
still fails; naming a current one warns.
**Warning meaning (open-top range).** `>=0.1.0-rc.1` has no upper bound at all and
admits `0.5.0`, `1.0.0` and anything later, so a breaking host line is accepted by
the range and the plugin loads against an API it never tested — a silent failure.
`^0.1.0-rc.1` does **not** have this problem: caret is semver sugar for
`>=0.1.0-rc.1 <0.2.0`, so it is bounded even though the string contains no `<`.
Measured 2026-09-24 across 22 third-party DSH repositories: the caret form is the
prevailing convention, which is why this check separates the two rather than
treating every range without a literal `<` as open.

### 4.2 Group K — static · Cordis contract scan

All `K` checks return `skip` when no source files were discovered (§3.4), and
all are **heuristic** — `warn` never condemns a plugin by itself.

#### K1 — Service access vs `inject` declaration
**Requirement.** Every `ctx.<service>` access is either a Context intrinsic, or
declared in `inject`, or obtained through `ctx.get(name)`.
**Verdicts.** `skip` · `warn` · `pass`.
**Failure meaning.** A hard dependency accessed without `inject` throws
*"cannot get property … without inject"* at runtime.

#### K2 — Live-data serialization red line
**Requirement.** A Context must not be serialized, cloned or spread —
`JSON.stringify(ctx…)`, `structuredClone(ctx…)`, `{...ctx…}`.
**Verdicts.** `skip` · `fail` · `pass`.
**Failure meaning.** Context is a Proxy; serializing it loses `def`/use-site
tracking or trips proxy traps. Unlike its neighbours this check is a **hard
fail**, because the construct is unambiguous.

#### K3 — Timer and listener lifecycle
**Requirement.** A timer or global listener must be wrapped in `ctx.effect`
(equivalently `ctx.on`) or explicitly self-cleaned via the matching
`clearTimeout`/`clearInterval`/`unref`/`removeEventListener`.
**Verdicts.** `skip` · `warn` · `pass`.
**Failure meaning.** The resource is not reclaimed on unload → HMR leaks.

#### K4 — Schema containing a function
**Requirement.** A file that defines a `Schema` must not also introduce arrow
functions into it — function values cannot be validated or persisted.
**Verdicts.** `skip` · `warn` · `pass`.

#### K5 — `apply` return shape (v4 Effect contract)
**Requirement.** A legal effect return: a disposer function, `null`,
`undefined`, `Promise<disposer>`, or an (async) iterable. Anything else throws
`TypeError("Invalid effect")`. An `async apply` is legal **but** must return a
`Promise<disposer>`; until it settles the fiber stays `LOADING` and its services
are invisible to dependents.
**Verdicts.** `skip` · `warn` (async `apply` present — reminder of the
settle-before-visibility rule) · `pass`.

#### K6 — `inject` service names are known seams
**Requirement.** Each injected service name is a known host seam, is namespaced
(contains `.`), or is `provide`d by the plugin itself.
**Verdicts.** `skip` · `warn` · `pass`.
**Failure meaning.** An unknown name leaves the fiber permanently `PENDING` and
`apply` never runs.

#### K7 — v3 legacy APIs
**Requirement.** No use of APIs removed in Cordis v4: `ctx.using/scope/runtime/
lifecycle/collect/accept/decline/alias/off/fork`, `ctx.config` (→
`ctx.fiber.config`), `ctx.start()/stop()` (→
`fiber.await()/dispose()/update()`), and the v3 `inject: {required, optional}`
shape (→ `string[]` or `{name: interceptConfig}`, optional via `ctx.get`).
**Verdicts.** `skip` · `fail` (hard list) · `warn` (soft list: `reusable`/
`reactive` metadata, `using` metadata, v3 `Service` style, returning a
`{dispose}` object) · `pass`.

#### K8 — `Config` must be a Standard Schema validator
**Requirement.** `export const Config` must be a Schema, not a plain object
literal. A plain object exported as `Config` **does not work**.
**Verdicts.** `skip` · `fail` · `pass`.

#### K9 — Plugin `name` special case
**Requirement.** An object plugin must not set `name: 'apply'` — the framework
resets it to `undefined` (registry special case), destroying the diagnostic
name.
**Verdicts.** `skip` · `warn` · `pass`.

### 4.2b Group K continued — cross-plugin interference (K10–K14)

K1–K9 answer *"is this plugin correct on its own?"*. K10–K14 answer *"does this
plugin interfere with other plugins, or can another plugin silently eat it?"*.
All four are `warn`-level heuristics in the same `K` group (no new group, so
`--only K` and every downstream `plugin-doctor.yml` keep their meaning), and all
four are grounded in observed defect classes rather than style preference.

Reference data for K10/K11/K13 is extracted from upstream machine-readable
sources — the `@mode` column of `docs/event-producer-consumer.md`, the
`### \`name\`` sections of `docs/tool-catalog.md`, and the top-level ids of
`packages/bundle/base/cordis.patch.yml` — at baseline DSH `0.2.0-rc.2`. Regenerate
per `THIRD-PARTY-RK-SCAN.md` rather than hand-editing the tables.

#### K10 — waterfall listeners must delegate `next()`
**Requirement.** A listener registered with `ctx.on(<waterfall event>, …)` must
reference `next`. DSH's standing rule: a listener that only observes or annotates
**must** call `next()`; returning without it is an intentional veto.
**Verdicts.** `skip` · `warn` · `pass`.
**Failure meaning.** A logging listener that forgets `next()` silently swallows
**every** downstream listener on that chain — including the built-in behaviour.
`agent/pre-step` is the most contended chain in the ecosystem (15 official
consumers), so the blast radius is large. Heuristic: a listener that names `next`
only outside the scanned window is a false negative, never a false positive, so
`warn` is the right ceiling.

#### K11 — tool names shadowing built-in/reserved names
**Requirement.** A registered tool name must not collide with a host built-in
tool name or the reserved PTC name `run_code`, and the plugin must not use the
explicit `tools.data.delete(def.name)` + re-register shadowing idiom.
**Verdicts.** `skip` · `warn` · `pass`.
**Failure meaning.** Two registrations of one name in the same layer throw; the
explicit two-step idiom instead **rewrites** the built-in tool the model sees,
which is how a widely installed third-party plugin redefines the built-in
`get_goal` / `create_goal` / `update_goal`.

#### K12 — provided service key equals a host seam
**Requirement.** A key passed to `super(ctx, '<name>')` or `ctx.provide('<name>')`
must not equal a host seam name.
**Verdicts.** `skip` · `warn` · `pass`.
**Failure meaning.** Cordis permits exactly one provider per service key per
isolate scope, so a second provider cannot take effect; a host seam replaced this
way requires the original provider row to be explicitly disabled, never run
alongside.

#### K13 — patch overrides a built-in row's `config`
**Requirement.** A `cordis.patch.yml` must not carry a top-level (non-`insert`)
row whose `id` is a host-owned row.
**Verdicts.** `skip` · `warn` · `pass`.
**Failure meaning.** An id-targeted patch replaces the target row's **whole**
`config` object — it does not deep-merge. Two bundles overriding the same built-in
row therefore silently erase each other, and the winner is decided purely by
`dsh.profile.bundles` order. (An override row also asserts `name`; a mismatch
skips the whole patch.)

#### K14 — cross-repo injection-point collision
**Requirement.** No name this repository owns may also be owned by a sibling
plugin repository in the same family workspace. Owned names are: service keys from
`super(ctx, '<name>')` / `ctx.provide('<name>')`; tool names from
`defineTool({ name })`; command names from `ctx.commands.register({ name })`; and
`insert` row ids in `cordis.patch.yml`. Siblings are direct children of
`--workspace` whose name starts with `dsh-` **and** whose manifest carries a `dsh`
field or a `@deepseek-ai/dsh*` peer.
**Verdicts.** `skip` · `warn` · `pass`.
**Failure meaning.** A service key admits exactly one provider per isolate scope,
so the second provider cannot take effect; a second registration of one tool name
in a layer throws. Either way one half silently loses.
**Guard-aware exemption (normative).** A repository may keep a *runtime*
first-provider-wins stand-down guard on purpose. Because such a guard leaves the
contested name in the source, K14 cannot distinguish it from a genuine collision,
so the author declares intent instead of the tool guessing. A declaration is a
list of `{kind, name, peer?, guard?}` entries under
`dsh-plugin-doctor.crossPlugin.exempt` in `package.json`, or the same list in a
`dsh-plugin-doctor.yml` sidecar. `kind` is one of `service` / `tool` / `command` /
`patch insert id`; `peer` restricts the exemption to one counterpart package;
`guard` records the version in which the stand-down landed so the exemption cannot
silently outlive the code that justified it. An exempted collision is a `pass`.
**Degradation.** Returns `skip` when no source files were discovered (the
whole-group contract of §3.2), and when the workspace exposes fewer than two
plugin repositories. It never passes silently on an unreadable workspace, and the
shipped CI workflow passes no `--workspace`, so the check degrades to `skip`
there rather than failing a build.

### 4.3 Group D — dynamic · sandbox smoke

Runs against a **sandbox environment**, never the developer's real one, and
never executes the tested package's install/prepare scripts.

#### D0 — Package tarball
**Requirement.** `npm pack --ignore-scripts` succeeds and the tarball is written
into the sandbox, not into the tested repository.
**Verdicts.** `fail` · `pass`.

#### D1 — Install smoke and bundles assertion
**Requirement.** The host installs from the tarball and
`dsh plugin --profile headless add <tarball>` (with `--ignore-scripts`) places
the package name into `dsh.profile.bundles`.
**Verdicts.** `skip` (D0 failed) · `fail` · `pass`.
**Not plugin defects:** a missing `pnpm`/`npm` (`infrastructure`); a host
version that will not install (`unsupported-host`); a pnpm `ignored-builds`
block (`environment`, reported as `skip` — see §5).

#### D2 — Layer verification (`--dump-config`, no boot)
**Requirement.** `dsh --profile headless --dump-config` emits the layer marker
`# == <package name>` — i.e. the patch entered the composition layer.
**Verdicts.** `skip` · `fail` · `pass`.

#### D3 — Keyless headless smoke (`MISSING_CREDENTIAL` criterion)
**Requirement.** Running the profile with no credentials must terminate with
**exit 1 and `dsh: MISSING_CREDENTIAL`** on stderr/stdout. That specific pair
proves the composition booted, the plugin's `apply` succeeded, and the request
reached the model layer.
**Verdicts.** `skip` · `fail` · `pass` (also `pass` on exit 0 — a credentialed
environment completed the run).
**Explicitly fatal:** `NO_ADAPTER`, `ERR_MODULE_NOT_FOUND`, `SyntaxError`,
`TypeError`, `ReferenceError`, `Cannot find module`.
**Limit.** Proves the composition boots to a model request. It does **not**
prove tool schemas are valid or business logic is correct.

#### D9 — Sandbox quarantine
**Requirement.** The sandbox is **quarantined, not deleted**; the run prints the
quarantine path, and removal is a separate, explicitly invoked step.
**Verdicts.** `pass` · `fail`.

### 4.4 Group CC — ecosystem · directory listings

Each `CC` check targets a specific third-party channel and returns `skip` when
that channel's data is not present in the workspace — never a failure.

#### CC1 — Certification registry (spec v1)
**Requirement.** If the repository appears in the certification registry, its
entry carries `grade`, `snapshot`, and `evidence` for all five dimensions
(`manifest`, `buildHygiene`, `supplyChain`, `releaseIntegrity`, `installSmoke`),
no `veto`, and `installSmoke ≠ install-fail`.
**Verdicts.** `skip` (registry absent, slug unresolvable, or not listed) ·
`warn` · `pass`.

#### CC2 — `adp-list` entry
**Requirement.** If listed: `url`, `name`, `category` present; `category` in the
23-value enum; `url` consistent with the repository; a single-line
`description.en`; and any `tarball` hosted as an `https` `.tgz` on a GitHub
Release.
**Verdicts.** `skip` · `fail` · `pass`.

#### CC3 — Market catalogue entry (`dsh-catalog`)
**Requirement.** If listed: `npm`, `repo`, `displayName`, `categories`,
`summary` present; `repo` consistent; `categories` a non-empty array; `summary`
≤ 1000 characters, containing no install-command text, and not truncated
(must end on a punctuation mark).
**Verdicts.** `skip` · `fail` · `pass`.

#### CC4 — `dshWorkshop` manifest
**Requirement.** If `dshWorkshop` is declared, `lifecycle.activation` must be
one of the five enumerated values.
**Verdicts.** `skip` · `fail` · `pass`.

#### CC5 — Family triple gate — **not an ecosystem standard**
**Requirement.** *(PerryLink-family policy, scoped deliberately.)* Apache-2.0
licence, five-language READMEs, and the three seam roles, as checked by the
`dsh-plugin-kit` verification CLI.
**Verdicts.** `skip` — reported as **not-applicable** — when run outside a
PerryLink workspace, i.e. when the kit CLI is absent · `fail` · `pass`.
**Scope limit (normative).** These three gates are **PerryLink-family standards,
not ecosystem standards**. They must not be applied to an unrelated third-party
repository, and a non-family project failing them has not necessarily done
anything wrong. An earlier implementation applied them anyway and reported every
outside repository as failing; §4.4 exists so that this cannot recur.

## 5. Known limits (normative disclosure)

An implementation conforming to this specification must not overstate results.
Specifically:

1. **`K` is heuristic.** K1, K3 and K4 both miss complex wrappers and raise
   false alarms. Every `warn` needs a human look and never condemns a plugin.
2. **`R2`/`R4` read built artifacts** and cannot be evaluated on an unbuilt
   source tree. Most third-party repositories do not commit `lib/`. Folding
   them into a verdict without disclosing this produces false failures — this
   happened, and `THIRD-PARTY-RK-SCAN.md` records the correction.
3. **`D3` proves boot, not correctness.** See §4.3/D3.
4. **`R5` and `R8` encode policy, not correctness.** A legitimate project with
   different dependency or peer conventions can fail them without being
   defective. `R8` in particular reports **staleness, not breakage**: a peer
   pinned to a superseded release-candidate line is a package that works and has
   fallen behind this family's release train. The verdict is `fail` because the
   exit code is a frozen contract that downstream gates read, but a failure here
   is not a claim that the package is broken — and no consumer of a result should
   read it as one.
5. **`R6` is advisory on npm-line hosts**, which do not enforce `engines`.
6. **A pnpm `ignored-builds` block is an environment recipe problem**, not a
   plugin defect. It is reported as `skip` with `category: environment`.
7. **`CC5` is family policy** (§4.4).
8. **Absence of a finding is not absence of a defect.** A `pass` means the
   stated requirement was met on the tested commit, and nothing more.

## 6. Change control

See [`GOVERNANCE.md`](GOVERNANCE.md). In short:

- A **major** spec version changes or removes the meaning of a check ID.
- A **minor** spec version adds checks or strengthens evidence without changing
  what an existing pass meant.
- A **patch** changes wording, evidence URLs or clarifications only.
- A check ID whose meaning changes is a **new ID**. IDs are frozen for the same
  reason the `R0 `/`K1 ` output prefixes are: downstream CI greps them.

## 7. Provenance of the criteria

Every criterion traces to first-hand sources rather than to convention. The
full research record, with URLs, is in [`SURVEY.md`](SURVEY.md):

- **Harness side** — the publish documentation, the CLI plugin activation gate,
  the headless bundle's `MISSING_CREDENTIAL` criterion, the release history, and
  the official loader-smoke pattern.
- **Cordis side** — the cordiverse/cordis v4 source (registry / fiber / reflect
  / events), the DSH cordis primer and tutorial documentation, and a v3 type
  declaration diff producing the 3.x→4.x removal list in K7.
- **Ecosystem side** — the certification registry spec v1, the `adp-list`
  entry schema, the `dsh-catalog` validator, the `omdsh` build-submission
  rules, and the `dsh-plugin-kit` verification gates.
- **Upstream discussion** — the official plugin-scaffold RFC
  ([discussion #1629](https://github.com/deepseek-ai/deepseek-harness/discussions/1629)),
  which this specification's treatment of the scaffold and activation contract
  follows, and this project's own proposal to the harness maintainers
  ([discussion #1814](https://github.com/deepseek-ai/deepseek-harness/discussions/1814)).

**Status relative to upstream.** This is an independent specification authored by
PerryLink. It has been *proposed* to the harness maintainers for adoption; it has
not been adopted, and nothing here should be described as the official check.
See [`OUTREACH.md`](OUTREACH.md) for the current state of that proposal.
