dsh-plugin-guide 0.4.3 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/entry.md CHANGED
@@ -16,6 +16,33 @@ market, index, or user hits.
16
16
  The exact rule IDs and evidence rules are in
17
17
  [`31-DSH-ENTRY`](https://github.com/PerryLink/dsh-standards/blob/main/31-DSH-ENTRY%E5%85%A5%E9%97%A8%E8%A7%84%E8%8C%83.md)
18
18
  and [`40-checker`](https://github.com/PerryLink/dsh-standards/tree/main/40-checker).
19
+ Every ID is also listed in [rules.md](rules.md); the copy-paste shapes are in
20
+ [recipes.md](recipes.md).
21
+
22
+ <a id="package-shape"></a>
23
+ ## Package shape (DSC-01, DSC-02, DSC-03)
24
+
25
+ `DSC-01` is one report line but six subitems; the detail lines say which one
26
+ failed. `DSC-03` runs only in the listing profile — in the package profile it is
27
+ `NA` with `install spec not supplied (package profile)`, which is allowed.
28
+
29
+ | Detail | What the report says | Fix |
30
+ |---|---|---|
31
+ | `C-1.1` | `name must be a valid npm package name` / `version must be an exact SemVer version` | use a lowercase npm name and an exact `1.2.3` (or `1.2.3-beta.1`); a tag or range is not a version |
32
+ | `C-1.2` | `no resolvable runtime entry` / `entry escapes the package` | point `exports["."]` or `main` at a file that exists inside the package; no `../` target |
33
+ | `C-1.2` | `exports["."] points to a missing file` vs `entry not present in checkout; source/build hint exists` (WARN) | shipped packages need the built file; if the checker sees `src/index.ts` or a `build`/`prepare` script it downgrades to WARN, do not rely on that |
34
+ | `C-1.2` | `patch-only bundle; no runtime entry by design` (`NA`) | fine: a package that only ships `dsh.bundle.patch` has no runtime entry and is not charged for it |
35
+ | `C-1.3` | `no apply/default-class shape found by static scan` (WARN) | export `apply` (function plugin) or a default class (service plugin), not both; L1 is authoritative |
36
+ | `C-1.4` | `patch path escapes the package` / `patch file is missing` | keep the patch inside the package and reference it relatively, e.g. `"./cordis.patch.yml"` |
37
+ | `C-1.5` | `license is missing` (WARN) | add `"license": "MIT"` or another SPDX id |
38
+ | `C-1.6` | `private:true; not publishable` (`NA`) | expected for a local profile package, not for a marketplace entry |
39
+ | `G4` | `dsh.client exists but exports["./client"] is missing; a package-root row cannot resolve it` | add `exports["./client"]` and the package-root row — see [recipes.md](recipes.md#client-entry-and-root-row) |
40
+ | `C-2.1` | `neither engines.dsh nor a @deepseek-ai/dsh* peer range is declared` | declare `"engines": { "dsh": ">=0.2.2-0 <0.3.0" }` |
41
+ | `C-2.2` | `engines.dsh <range> does not satisfy runtime <v>` | widen the range to the host you target, or run the checker with `--runtime-version <v>` to judge against the right one |
42
+ | `C-2.3` | `engines.dsh must not be a workspace: range` | publish a real SemVer range; `workspace:` must not reach a tarball |
43
+ | `C-2.4` | `peer <name> range <range> conflicts with engines.dsh` (WARN) | the host decides on `engines.dsh` alone; align the peer anyway |
44
+ | `C-3.1`/`C-3.2`/`C-3.5` | `install spec` / `source is pinned` / market entry | listing only: exact version or verifiable tag, `#<40-hex>` for git, and a `format:1` index entry with pinned versions |
45
+ | `C-3.3`/`C-3.4` | `non-registry dependency` / `install-time build script present` (WARN) | pin git/url/file dependencies and declare what install scripts build |
19
46
 
20
47
  ## The two claims
21
48
 
@@ -28,6 +55,79 @@ and [`40-checker`](https://github.com/PerryLink/dsh-standards/tree/main/40-check
28
55
  entry; it needs G0–G3. A client-only package is build-only unless a browser
29
56
  probe exists.
30
57
 
58
+ <a id="runtime-surface"></a>
59
+ ## Runtime surface (DSC-04 to DSC-09, DSC-14)
60
+
61
+ These are the L1–L3 checks: load, provide, effects, unload, restart, and
62
+ declared-vs-measured. They apply to `kind=plugin`; a `bundle` is verified by
63
+ `--scope entry` G0–G3 instead (in `--scope ds-c` it is `NA`), and a client,
64
+ preset, skill, theme, or MCP config is `NA` and must not claim runtime. `DSC-14`
65
+ is the ENTRY run gate: for a plugin it mirrors L1, for a bundle it is G0–G3 and
66
+ needs `--host`, and under `--entry-claim static` it is `NA`
67
+ (`entry static claim: no runtime evidence`).
68
+
69
+ | Detail | What the report says | Fix |
70
+ |---|---|---|
71
+ | `C-4.1`/`C-4.2` | `entry import failed: …` / `load failed: …` / `fiber state <n> is not ACTIVE (2)` | the entry must import and `apply()` without throwing; move timers, watchers, I/O, and subprocesses into `apply()` |
72
+ | `C-4.3` | `plugin load timed out` / `killed after <n>ms; plugin blocked the event loop` | do not block on module-scope work or a top-level `await`; the L1 child is hard-killed |
73
+ | — | `blocked by missing host dependency` (ERROR, `not-run`) | environment, not code: the plugin imports a bare package the fixture does not have. Install it or point `--runtime` at a complete host |
74
+ | `C-5.1`/`C-5.2` | `no provide declaration` (`NA`) / `declared provide <x> was not registered` / `owned by another fiber` | register the service from this plugin's own fiber: `ctx.provide(name, impl)` or a `Service` |
75
+ | `C-6.1` | `direct write to a Cordis private registration surface` | never assign into `ctx.events._hooks`, `ctx.reflect.store`, `ctx.registry._internal`, or `fiber._disposables` |
76
+ | `C-6.2` | `no registered effects observed` / `all effects are anonymous` (WARN) | give every effect a label: `ctx.effect(fn, 'my-plugin.timer')` |
77
+ | `C-7.1`–`C-7.3` | `getEffects().length=<n>` / `service store returned to baseline` / `registry.size <a> -> <b>` | every effect returns a disposer; `dispose()` must reach zero effects and restore the store and registry |
78
+ | `C-7.5` | `dispose failed: …` | await quiescence; do not fire-and-forget cleanup |
79
+ | `C-8.1`–`C-8.3` | `state after restart=<n>` / `effect count <a> -> <b>` / `revision … -> …` | `restart()` must re-enter `ACTIVE`, keep the effect count, advance revision/epoch, and work from a fresh `Context` |
80
+ | `C-9.1`–`C-9.3` | `declared provide <x> is missing from measured services` / `measured service <x> is not declared` / `inject declares <x> but no static ctx.<x> read was found` | declarations must equal reality: provide ⊆ measured, measured ⊆ declared, and each `inject` is read |
81
+
82
+ The runner stubs the services you declare in `inject` before it calls
83
+ `ctx.plugin()`, so a plugin that only needs declared services can reach `ACTIVE`.
84
+ Anything it cannot stub is reported as `ERROR`/`not-run`; that is not your
85
+ defect and must not be written as a pass.
86
+
87
+ ```js
88
+ export const name = 'hello'
89
+ export const inject = ['tools'] // only what you read
90
+ export const provide = ['hello'] // only what you register
91
+
92
+ export function apply(ctx) {
93
+ ctx.provide('hello', { greet: () => 'hello' }) // C-5
94
+ ctx.effect(() => { // C-6, C-7
95
+ const timer = setInterval(poll, 60_000)
96
+ return () => clearInterval(timer) // dispose → 0 effects
97
+ }, 'hello.timer')
98
+ }
99
+ ```
100
+
101
+ A bundle is verified by G0–G3, not by L1: G0 parses the patch, G1 composes the
102
+ rows, G2 runs the host compatibility gate, G3 boots the target rows. A `partial`
103
+ G3 is `ERROR`/`not-run`; only `G0-G3 verified; rows=<n>/<n>` with
104
+ `DSC-14 PASS` is a runtime claim. G4 is a composition property: with
105
+ `dsh.client`, the composed layer must contain a row whose `name` is the package
106
+ name and `exports["./client"]` must resolve.
107
+
108
+ <a id="client-files"></a>
109
+ ## Client files (G4)
110
+
111
+ A client package is dual-face: the server half is loaded as a row, the browser
112
+ half is a prebuilt file at `exports["./client"]`.
113
+
114
+ - `dsh.client` must be a non-empty object; the host's Web consumer selects it
115
+ with `platform: "web"`.
116
+ - `exports["./client"]` must be present. The host accepts a string, or an object
117
+ with a string `default`; the checker only fails when it is missing.
118
+ - The composed config needs a **package-root row**: a patch `insert` whose
119
+ `name` equals `manifest.name`. Without an active row the client scanner never
120
+ sees the package, even though the export exists.
121
+ - The client file must be a built artifact and must be included in `files`, or
122
+ the tarball ships a manifest that points at nothing.
123
+ - G4 is static. It does not prove the browser bundle renders; that is a
124
+ build/browser probe and belongs in `limitations`, not in a runtime claim.
125
+
126
+ The copy-paste client manifest and patch row are in
127
+ [recipes.md](recipes.md#client-entry-and-root-row). A missing export or row is
128
+ reported as `G4: FAIL` under `DSC-01` (or as `DSC-14` with `--host`), never as a
129
+ pass.
130
+
31
131
  ## Run it
32
132
 
33
133
  ```sh
@@ -64,6 +164,7 @@ pass.
64
164
 
65
165
  ## Declaration fields
66
166
 
167
+ <a id="declaration-fields"></a>
67
168
  Two optional fields become checks `DSC-11` and `DSC-13` once they exist:
68
169
 
69
170
  ```json
@@ -75,6 +176,11 @@ Two optional fields become checks `DSC-11` and `DSC-13` once they exist:
75
176
  }
76
177
  ```
77
178
 
179
+ Both are **standard extensions**: this host does not read them yet. They are
180
+ declarations plus UI disclosure, not a sandbox and not enforcement. Declaring
181
+ `[]` and importing `node:fs` directly still passes `DSC-11`; the check only
182
+ compares declarations with static triggers.
183
+
78
184
  Allowed `capabilities`: `fs:read`, `fs:write`, `shell`, `subprocess`,
79
185
  `network:egress`, `network:ingress`, `credentials:read`, `credentials:list`,
80
186
  `credentials:provider`, `llm:adapter`, `sessions:read`, `system-prompt:write`,
@@ -83,6 +189,95 @@ Allowed `capabilities`: `fs:read`, `fs:write`, `shell`, `subprocess`,
83
189
  Allowed `labels`: `prompt-modification`, `red-team`,
84
190
  `third-party-model-provider`, `remote-access`, `mirror`.
85
191
 
192
+ <a id="capability-triggers"></a>
193
+ ## Capability triggers (DSC-11)
194
+
195
+ `C-11.2` is the check authors hit most: if `dsh.capabilities` exists, every
196
+ static trigger below must have its capability in the array, or the check FAILs
197
+ with `static trigger <cap> is not declared in dsh.capabilities`.
198
+
199
+ | Static trigger (source shape) | Declare |
200
+ |---|---|
201
+ | `ctx.webServer` / `ctx.webhookRuntime` | `network:ingress` |
202
+ | `installBundle(` / `removeBundle(` | `plugin:install` |
203
+ | `registerAdapter(` / `ctx.llm.register` | `llm:adapter` |
204
+ | `ctx.systemPrompt.section(` / `.context(` / `.variable(` | `system-prompt:write` |
205
+ | `ctx.tools.register(` | `tools:register` |
206
+ | `@Remote(` / `@RemoteScope(` | `remote:expose` |
207
+ | `.listRecords(` | `credentials:list` |
208
+ | `ctx.sessions` / `ctx.sessionQuery` | `sessions:read` |
209
+ | `ctx.shell` / `ctx.bash` | `shell` |
210
+ | `ctx.subprocess` | `subprocess` |
211
+ | `ctx.fs` | `fs:read` (a declared `fs:write` satisfies it) |
212
+ | `ctx.web` / a bare `fetch(` | `network:egress` |
213
+
214
+ Reading the other subitems:
215
+
216
+ - `C-11.1`: an unknown capability id is a FAIL; an empty array is a WARN. A
217
+ missing field is a transitional WARN, and `C-11.2` is then **not evaluated** —
218
+ so an undeclared array hides the very failure you want to see.
219
+ - `C-11.3`: a bundle patch that enables `@deepseek-ai/dsh-plugin-manager/tools`
220
+ without `disabled: true` is a FAIL.
221
+ - `C-11.4`: a string literal that is exactly `danger-full-access` is a WARN; a
222
+ mention in a comment or a `console.log` description is not.
223
+ - `C-11.5`: a declared capability with no matching trigger is a WARN (over-
224
+ declaration). Keep declarations to what the scan can see; a dynamic call may
225
+ not match.
226
+
227
+ The scan covers `.js`, `.mjs`, `.cjs`, `.ts`, `.tsx`, and `.jsx` files in the
228
+ package, and skips `tests`, `docs`, `scripts`, `examples`, `fixtures`, `bench`,
229
+ `tools`, and dot-directories, plus the other build and asset directories the
230
+ walker ignores. A `fetch(` in `tests/` does not require `network:egress`; the
231
+ same call in `lib/client.js` does.
232
+
233
+ <a id="credentials-and-seams"></a>
234
+ ## Credentials and seams (DSC-12)
235
+
236
+ `DSC-12` is a declaration-consistency check, not a security audit. It looks for
237
+ direct access to the credential backing file and for global seam registration.
238
+
239
+ | Detail | What the report says | Fix |
240
+ |---|---|---|
241
+ | `C-12.1` | `credential backing-file access near a file-read API without credentials:provider` (a path in text or UI is PASS) | read through `ctx.credentials`; if you really are a provider, declare `credentials:provider` |
242
+ | `C-12.2` | `listRecords() used without credentials:list` | declare `credentials:list`, or stop enumerating records |
243
+ | `C-12.3` | `global seam provider registration detected` (WARN) | do not provide `shell`/`credentials`/`sandboxPolicy`/`sandbox`/`deepSeekAccount` unless that is the plugin's purpose |
244
+ | `C-12.4` | `danger-full-access/--add-dir literal` (WARN) | do not hard-code a sandbox bypass in a command or policy value |
245
+ | `C-12.6` | `dsh.commitments` missing (`NA`) or `missing commitment: <id>` (WARN) | optional: a boolean for each of `no-session-upload`, `no-third-party-proxy`, `no-credential-enumeration`, `no-silent-prompt-rewrite`, `no-adapter-bypass`, `no-webhook-auto-session`, `no-security-seam-replacement`, `third-party-endpoint-disclosure`, `no-danger-full-access` |
246
+ | `C-12.7` | `dsh.disclosure` missing (`NA`) or a shape WARN | optional: `endpoints` (https URLs), `retention` (string), `systemPrompt` (string) |
247
+
248
+ ```js
249
+ // prefer: go through the seam
250
+ export function apply(ctx) {
251
+ ctx.credentials.resolve('acme/api-key')
252
+ }
253
+
254
+ // avoid: the backing file is an implementation detail
255
+ // readFileSync(join(process.env.DSH_HOME, '.credentials.yaml'))
256
+ ```
257
+
258
+ <a id="labels-and-mirror"></a>
259
+ ## Labels and mirror (DSC-13)
260
+
261
+ A label is a declaration, not an endorsement. `C-13.2` maps a **declared
262
+ capability** to a required label:
263
+
264
+ | Declared capability | Required label |
265
+ |---|---|
266
+ | `system-prompt:write` | `prompt-modification` |
267
+ | `llm:adapter` **and** `network:egress` | `third-party-model-provider` |
268
+ | `network:ingress` | `remote-access` |
269
+ | `dsh.upstream` declared | `mirror` |
270
+
271
+ - `C-13.1`: an unknown label is a FAIL; a missing `dsh.labels` is a transitional
272
+ WARN.
273
+ - `C-13.3`: `dsh.upstream` must be an `https://` URL and must not point at this
274
+ package's own `repository`; a mirror also needs the `mirror` label.
275
+ - `C-13.4`: `official`, `verified`, `certified`, `secure`, `audited` in
276
+ `dsh.labels` is a FAIL. The prose ban is separate: the checker does not scan
277
+ README or `description`, so keeping those words out is on you.
278
+ - `C-13.5`: the `red-team` label should come with `dsh.usageScope`; without it
279
+ the check only WARNs.
280
+
86
281
  Do not label a plugin `official`, `verified`, `certified`, `secure`, or
87
282
  `audited` — those words are treated as an endorsement claim and fail `DSC-13`.
88
283
 
package/docs/flow.md CHANGED
@@ -19,6 +19,134 @@ the first run, configuration, upgrade, and removal are understandable.
19
19
  The canonical text is
20
20
  [`32-DSH-FLOW`](https://github.com/PerryLink/dsh-standards/blob/main/32-DSH-FLOW%E9%A1%BA%E7%95%85%E8%A7%84%E8%8C%83.md).
21
21
 
22
+ <a id="evidence-file"></a>
23
+ ## The evidence file: `dsh-flow-evidence/2`
24
+
25
+ A journey row without a command, exit code, log hash, and timestamp is a claim,
26
+ not evidence. The shape below is the evidence-file contract this guide
27
+ recommends: one subject, one host, one entry per journey, and a chain pointer so
28
+ a rewrite is detectable.
29
+
30
+ ```json
31
+ {
32
+ "schema": "dsh-flow-evidence/2",
33
+ "evidenceId": "01JCM8Z6R4W1V2K3N4P5Q6R7S8",
34
+ "generatedAt": "2026-11-06T12:00:00Z",
35
+ "producer": { "kind": "local", "runner": "flow-collect 0.1.0", "runUrl": null, "actor": "author" },
36
+ "subject": {
37
+ "package": "@acme/dsh-hello",
38
+ "version": "0.1.0",
39
+ "source": "npm",
40
+ "integrity": "sha512-…",
41
+ "tarballSha256": "sha256:…",
42
+ "commit": "ce7073d51254c26cd63b22a60835b8f948deb447",
43
+ "treeSha256": "sha256:…"
44
+ },
45
+ "host": { "dsh": "0.2.2-alpha.2", "dshCommit": null, "cordis": "4.0.6-alpha.1", "node": "v24.11.0", "platform": "linux-x64" },
46
+ "journeys": {
47
+ "J1": {
48
+ "status": "pass",
49
+ "kind": "observed",
50
+ "checks": [
51
+ {
52
+ "id": "J1.install",
53
+ "status": "pass",
54
+ "command": ["dsh", "plugin", "add", "."],
55
+ "exitCode": 0,
56
+ "startedAt": "2026-11-06T11:00:01Z",
57
+ "endedAt": "2026-11-06T11:00:09Z",
58
+ "stdout": { "path": "logs/J1.install.out", "sha256": "sha256:…" },
59
+ "assertions": [{ "claim": "row visible in dump", "observed": true }]
60
+ }
61
+ ],
62
+ "limitations": []
63
+ },
64
+ "J8": { "status": "na", "reason": "no dsh.client" }
65
+ },
66
+ "totals": { "pass": 7, "na": 1, "fail": 0 },
67
+ "conclusion": "flow-pass",
68
+ "claims": { "claimLevel": "self-attested", "notClaimed": ["certification", "security audit"] },
69
+ "chain": { "seq": 12, "prev": "sha256:…", "entrySha256": "sha256:…" },
70
+ "signature": null
71
+ }
72
+ ```
73
+
74
+ | Field | Required | What belongs there |
75
+ |---|---|---|
76
+ | `schema`, `evidenceId`, `generatedAt` | yes | the version, a unique id, an ISO timestamp |
77
+ | `producer` | yes | `kind` = `sandbox` / `ci` / `local`, the `runner`, a `runUrl` for CI, the `actor` |
78
+ | `subject` | yes | `name@version` plus `integrity`, `tarballSha256`, `commit`, `treeSha256` — binds evidence to one artifact, so a copied file for another version is `stale` |
79
+ | `host` | yes | `dsh`, `dshCommit`, `cordis`, `node`, `platform` — a pass on another host does not transfer |
80
+ | `journeys.Jn` | yes, per run journey | `status` (`pass` / `fail` / `not-run` / `na`), `kind` (`observed` / `static` / `manual`), `checks[]`, `limitations[]`, and `reason` for a skip |
81
+ | `journeys.Jn.checks[]` | yes, per check | `id` (`J1.install`), `command[]`, `exitCode`, `startedAt`, `endedAt`, `stdout.path` + `stdout.sha256`, `assertions[]` |
82
+ | `totals`, `conclusion` | yes | counts and `flow-pass` / `flow-fail` / `flow-blocked` / `flow-warn` |
83
+ | `claims` | yes | `claimLevel` (`self-attested`, `sandbox-attested`, …) and what is explicitly **not** claimed |
84
+ | `chain` | when chained | `seq`, `prev`, `entrySha256` from the append-only evidence chain |
85
+ | `signature` | optional | `null` unless you actually sign |
86
+
87
+ Honesty rules for the file itself:
88
+
89
+ - `kind: "static"` never counts as a FLOW pass. J5 and a client-only J8 are
90
+ static preflight; label them that way.
91
+ - `na` is a skip with a reason (`no dsh.client`), not a pass.
92
+ - A missing `command[]`, `exitCode`, or log hash means the row is not evidence.
93
+ Put the limitation in `limitations[]` instead of dropping the row.
94
+
95
+ <a id="what-the-checker-reads"></a>
96
+ ### What reads this file today
97
+
98
+ `dsh-levels --flow-evidence` does **not** read the nested v2 shape yet. Its
99
+ reader wants a flat map and only reads `status`:
100
+
101
+ ```json
102
+ {
103
+ "J1": { "status": "pass", "evidence": "add → dump → remove; logs/J1.*.out" },
104
+ "J2": { "status": "pass", "evidence": "same row id disable/enable; two dumps" }
105
+ }
106
+ ```
107
+
108
+ - Accepted values are `pass`, `fail`, `not-run`, `manual`. Anything else,
109
+ including `na` and `warn`, is silently downgraded to `not-run`.
110
+ - FLOW becomes `runtime` only at **8/8 pass**. One `na` gives `7/8` and
111
+ `not-run` — which is the honest answer for a package with no client, but it
112
+ is not a pass.
113
+ - The flat map has no binding, no hash, and no host. A hand-written 8-pass file
114
+ passes. That is why the v2 file above exists: keep the flat map as the
115
+ projection, and keep the v2 file plus logs as the thing you can defend.
116
+
117
+ Project the v2 file to the flat map before running the audit:
118
+
119
+ ```sh
120
+ node -e '
121
+ const fs = require("node:fs");
122
+ const evidence = require("./flow-evidence.json");
123
+ const flat = {};
124
+ for (const [id, journey] of Object.entries(evidence.journeys)) {
125
+ flat[id] = { status: journey.status, evidence: journey.reason ?? journey.checks?.[0]?.id ?? null };
126
+ }
127
+ fs.writeFileSync("flow-flat.json", JSON.stringify(flat, null, 2));
128
+ '
129
+ ```
130
+
131
+ <a id="minimum-evidence-per-journey"></a>
132
+ ## Minimum evidence per journey
133
+
134
+ | Journey | Run and record | The row is a pass only when |
135
+ |---|---|---|
136
+ | **J1** install / remove | `dsh plugin add .` → config dump → `dsh plugin remove` → dump | the row appears and then disappears, with no hand-edited profile; record both dumps and their hashes |
137
+ | **J2** enable / disable | disable and enable the same row id, dumping after each | the change is visible in the composed config under the same stable id |
138
+ | **J3** configuration | boot with an empty config; boot with an invalid field; change a `volatile()` field | empty config boots; the invalid value fails loudly with a field path; the volatile change restarts 0 times |
139
+ | **J4** first success | follow the README quickstart on a clean profile; also start with no credential | a five-minute first success, and the error names the cause, the next command, and the doc anchor; a no-key start stops at a clear credential boundary |
140
+ | **J5** permissions | compare `capabilities`/`labels`/commitments/disclosure with the README | every declaration matches the README and no endorsement claim appears; static only, so `kind:"static"` and **not** counted as pass |
141
+ | **J6** upgrade / rollback / uninstall | install `--previous`, upgrade, roll back, uninstall | the installed version changes and comes back, data survives, and no files or rows are left behind |
142
+ | **J7** observability | boot normally, with a missing service, and with a port conflict | the startup line prints `name@version`, and each of the three cases is diagnosable from the log |
143
+ | **J8** client / server | headless run, web run, client-disabled run | each mode works or is explicitly rejected; with no `dsh.client` the row is `na` with that reason; browser rendering goes in `limitations[]` |
144
+
145
+ When a journey fails, quote the five things a reader needs: `Jn.<subcheck>`,
146
+ the original `command[]`, `exitCode`, the last 20 lines of stderr, and the
147
+ absolute log path. Then attach the clause anchor the journey maps to (for
148
+ example `J2.toggle → 32-DSH-FLOW #R2`).
149
+
22
150
  ## Status, not a score
23
151
 
24
152
  | Status | Meaning |
@@ -27,26 +155,38 @@ The canonical text is
27
155
  | `failed` | at least one journey has an attributable FAIL |
28
156
  | `not-run` | no evidence, or fewer than eight journeys have pass evidence |
29
157
 
30
- Each journey is recorded as `pass` / `fail` / `not-run` / `manual`. There is no
31
- `flow-warn` status — a partial run is `not-run`, not a soft pass.
158
+ Each journey is recorded as `pass` / `fail` / `not-run` / `manual`; the v2 file
159
+ adds `na` for a justified skip, and the flat reader downgrades `na` (and `warn`)
160
+ to `not-run`. There is no `flow-warn` **FLOW state** — a partial run is
161
+ `not-run`, not a soft pass. The journey harness may print `flow-warn` or
162
+ `flow-blocked` in its own `dsh-flow-runtime/1` conclusion; neither is `runtime`.
32
163
 
33
164
  `not-run` is never a pass. Do not publish a pass rate or a grade from FLOW.
34
165
 
35
166
  ## Current limit
36
167
 
37
- The runtime harness is still a draft. J1/J2/J4/J6 have runtime evidence on
38
- fixtures; J3/J7 have fixture-level runtime; J5/J8 have static preflight; browser
39
- rendering is not yet covered. The guide must state which journey was actually
40
- run.
168
+ The harness is a draft, and this document is deliberately ahead of it:
169
+
170
+ - The v2 shape above is the contract to record against. The audit's
171
+ `--flow-evidence` reader still consumes the flat map, so project v2 → flat
172
+ before you run it (see above). A v2 file fed to the audit directly reads as
173
+ `0/8` and `not-run`.
174
+ - There is no shipped command that collects all eight journeys for you. J1 has
175
+ a runtime fixture (`journey-runtime.mjs`); the other journeys are recorded by
176
+ hand or by a fixture-level run. Browser rendering has no probe.
177
+ - `na`, `manual`, and `static` rows cannot become `runtime`. If one journey is
178
+ `na`, FLOW stays `not-run` — write that, not a pass.
41
179
 
42
180
  The source harness lives in
43
181
  [`40-checker/scripts/journey.mjs`](https://github.com/PerryLink/dsh-standards/blob/main/40-checker/scripts/journey.mjs)
44
182
  and
45
- [`journey-runtime.mjs`](https://github.com/PerryLink/dsh-standards/blob/main/40-checker/scripts/journey-runtime.mjs).
183
+ [`journey-runtime.mjs`](https://github.com/PerryLink/dsh-standards/blob/main/40-checker/scripts/journey-runtime.mjs);
184
+ the audit is
185
+ [`40-checker/scripts/level-audit.mjs`](https://github.com/PerryLink/dsh-standards/blob/main/40-checker/scripts/level-audit.mjs).
46
186
  The three-layer audit reads FLOW evidence through `--flow-evidence`:
47
187
 
48
188
  ```sh
49
- npx -p dsh-standards dsh-levels ./my-plugin --flow-evidence flow.json --format json
189
+ npx -p dsh-standards dsh-levels ./my-plugin --flow-evidence flow-flat.json --format json
50
190
  ```
51
191
 
52
192
  ## Author checklist
@@ -56,4 +196,4 @@ npx -p dsh-standards dsh-levels ./my-plugin --flow-evidence flow.json --format j
56
196
  - Errors say what happened, what to run next, and where the doc is.
57
197
  - Permissions say what the plugin can/cannot do and how to turn it off.
58
198
  - Upgrade and uninstall are tested against real data, not only a clean install.
59
- - No “certified”, “official”, or “secure” wording.
199
+ - No “certified”, “verified”, “audited”, “official”, “secure”, or “ranked” wording.
package/docs/levels.md CHANGED
@@ -11,6 +11,20 @@
11
11
 
12
12
  > **Current baseline (2026-11-04):** DSH `0.2.2-alpha.3` (npm `alpha`), Node `>=24.9.0` (since 0.2.2-alpha.2), Cordis `4.0.6-alpha.2`. `engines.dsh`, when declared, decides alone since 0.2.1-alpha.4; `ctx.menus` is gone in 0.2.2-alpha.3; Typert codecs use `create()` since 0.1.6-alpha.2; `@deepseek-ai/dsh-client-runtime` was removed in 0.1.2-alpha.1 and has no 0.2.x.
13
13
 
14
+ ## Where to go next
15
+
16
+ | You have | Read |
17
+ |---|---|
18
+ | a report line with a `DSC-nn` or `C-n.m` ID | [rules.md](rules.md) — every check, whether it blocks, and the one-line fix |
19
+ | a FAIL to fix right now | [recipes.md](recipes.md) — copy-paste `package.json`, `cordis.patch.yml`, and client entry |
20
+ | an ENTRY question | [entry.md](entry.md) — package shape, runtime surface, declarations |
21
+ | a FLOW journey to record | [flow.md](flow.md) — the `dsh-flow-evidence/2` file and J1–J8 evidence |
22
+ | a BOOST practice to evidence | [boost.md](boost.md) — the thirteen-field evidence card |
23
+
24
+ The two reference docs exist because a report ID alone does not tell you what to
25
+ change: [rules.md](rules.md) maps IDs to fixes, and [recipes.md](recipes.md)
26
+ holds the shapes those fixes need.
27
+
14
28
  ## ENTRY
15
29
 
16
30
  ENTRY is the compatibility floor. It checks the package shape, entry points,
@@ -24,6 +38,8 @@ made — an actual start.
24
38
 
25
39
  Start here: [ENTRY guide](entry.md) and the canonical
26
40
  [31-DSH-ENTRY](https://github.com/PerryLink/dsh-standards/blob/main/31-DSH-ENTRY%E5%85%A5%E9%97%A8%E8%A7%84%E8%8C%83.md).
41
+ Then fix by ID: [rules.md `DSC-01`–`DSC-14`](rules.md#table) and
42
+ [recipes.md](recipes.md).
27
43
 
28
44
  ## FLOW
29
45
 
@@ -33,11 +49,13 @@ modes.
33
49
 
34
50
  - The current FLOW harness is a draft. Some J1–J8 checks have runtime evidence;
35
51
  others are static preflight or `not-run`.
36
- - `flow-warn`, `flow-fail`, and `flow-not-run` are real outcomes. Do not turn a
37
- missing run into a pass.
52
+ - `runtime`, `failed`, and `not-run` are the FLOW statuses. A partial run is
53
+ `not-run`, never a soft pass.
38
54
 
39
55
  Start here: [FLOW guide](flow.md) and the canonical
40
56
  [32-DSH-FLOW](https://github.com/PerryLink/dsh-standards/blob/main/32-DSH-FLOW%E9%A1%BA%E7%95%85%E8%A7%84%E8%8C%83.md).
57
+ The evidence file shape and the J1–J8 minimum are in
58
+ [flow.md § the evidence file](flow.md#evidence-file).
41
59
 
42
60
  ## BOOST
43
61
 
@@ -52,6 +70,8 @@ parallel/jobs, and composition.
52
70
 
53
71
  Start here: [BOOST guide](boost.md) and the canonical
54
72
  [33-DSH-BOOST](https://github.com/PerryLink/dsh-standards/blob/main/33-DSH-BOOST%E5%8A%A0%E9%80%9F%E5%AE%9E%E8%B7%B5.md).
73
+ The card fields are in
74
+ [boost.md § evidence, not a badge](boost.md#thirteen-fields).
55
75
 
56
76
  ## Run the checks
57
77
 
@@ -75,4 +95,4 @@ The legacy build skill/CLI is [`dsh-primer`](https://github.com/PerryLink/dsh-pr
75
95
  - `not-run` ≠ `pass`
76
96
  - `boost-evidenced` ≠ plugin grade
77
97
  - `A–D` grades (where they exist) are orthogonal to ENTRY/FLOW/BOOST
78
- - No official, certified, secure, recommended, or ranked claim
98
+ - No official, certified, verified, audited, secure, recommended, or ranked claim