dsh-deeppilot 0.9.4 → 0.9.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.
@@ -5,6 +5,78 @@
5
5
 
6
6
  ## Current decision
7
7
 
8
+ - **Audited release:** `dsh-v0.2.1-alpha.3` (official release page:
9
+ <https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.2.1-alpha.3>),
10
+ published to npm 2026-10-14T13:32:15Z on the `alpha` dist-tag. It is **not**
11
+ `latest`; `latest`/`next` still point at `0.2.0-rc.2`. Baseline commit
12
+ `2fbf4359b82fb94c03dcce0b831660d87f12ea5c`, compared against alpha.2's
13
+ `d743267388641bc76f17c45ce8b4c231aed1d32c` (**852 commits, 3311 changed files,
14
+ 120,978 insertions, 16,632 deletions**).
15
+ - **Comparison baseline:** `dsh-v0.2.1-alpha.2` (the previous audited tag).
16
+ - **Plugin runtime verdict: four confirmed breaks with one root cause — the
17
+ alpha.3 Peer model — and the resident interaction client does not start.**
18
+ **Fixed in this audit; see "The fix" below.**
19
+ This release makes every in-process carrier name a `PeerScope`
20
+ (`principal` / `signal` / `interactions`) explicitly; nothing defaults to the
21
+ operator any more. The four landing points, each with its own evidence:
22
+
23
+ | # | Upstream change | Plugin seam | Evidence |
24
+ | --- | --- | --- | --- |
25
+ | 1 | `TypertGatewayWireStream.open`'s 4th parameter `peer` is required; `assertPeerLive()` reads `peer.signal.aborted` before anything else | `src/dsh-remote-interactions.ts:180` passes `undefined, undefined` | Reproduced against the published bundles: alpha.2 returns normally, alpha.3 throws `TypeError: Cannot read properties of undefined (reading 'signal')` |
26
+ | 2 | `$events` additionally requires `peer.interactions === 'answer'` (`requireInteractions`) | same call | Reproduced: a peer with `interactions: 'none'` gets `gateway/interactions-forbidden`; a full owner/answer peer passes on **both** versions |
27
+ | 3 | `ConnectionFetchHandler.fetch(request, peer)` now takes a required Peer | `src/dsh-remote-interactions.ts:204` calls `fetchHandler.fetch(request)` | `lib/types/rpc.d.ts` declares `peer: PeerScope` required, "never defaulted" |
28
+ | 4 | `./client` gained `node`/`browser` conditions; Node now resolves `lib/client.node.js`, which carries **no** `window.__ModuleLoader__.load` banner | `loadClientFaces()` (`src/dsh-remote-interactions.ts:117`) builds its module table from that banner | `grep -c __ModuleLoader__` is 0 in `client.node.js`, 1 in `client.browser.js` and 1 in alpha.2's `client.js` |
29
+
30
+ A fifth, latent one: `installConnection` derives
31
+ `interactions ?? (location === undefined ? 'none' : 'answer')`, and the
32
+ plugin's Node context has no `location`, so `apply(ctx)` alone always selects
33
+ `'none'`. Passing an explicit Peer is not enough by itself — the install call
34
+ must name `interactions: 'answer'` too.
35
+ - **Failure mode is degradation, not a crash — but only partly contained.**
36
+ `src/dsh-api-proxy.ts:316` wraps `startDshRemoteInteractions` in `try/catch`
37
+ and logs `[deeppilot] DSH Remote interaction client unavailable`. Breaks 3 and
38
+ 4 surface while the resident client is being assembled, so they land in that
39
+ catch. Whether break 1/2 (the Gateway opens its Remote Events generation
40
+ asynchronously) also lands there is **not confirmed** — an uncaught rejection
41
+ there would be fatal under Node's default `--unhandled-rejections=throw`.
42
+ The phone loses approval/question delivery either way.
43
+ - **Fix direction (verified compatible with both hosted versions):** build one
44
+ owner/answer Peer (`new Peer(ownerCtx, { principal: { kind: 'owner' },
45
+ interactions: 'answer' })`, falling back to `connection.operator` on alpha.2)
46
+ and name it in every carrier call — alpha.2 ignores a second `fetch` argument
47
+ and defaults an absent `open` peer to the operator, so one call form serves
48
+ both. `loadClientFaces` additionally has to accept the Node bundle's named
49
+ exports when the banner did not fire; both shapes export `apply`/`inject`
50
+ (`installConnection`/`transportError` are extra on Node).
51
+ - **Install verdict: the peer range now names this release explicitly.** It moved
52
+ from the open-ended `>=0.2.0-rc.2 <0.3.0-0` to `0.2.0-rc.2 || 0.2.1-alpha.3`
53
+ across all ten `@deepseek-ai/dsh*` peers, and the two vendor peers widened to
54
+ `~4.0.4 || ~4.0.5-alpha.1` and `~3.18.4 || ~3.18.5-alpha.1`. Because DSH
55
+ evaluates that range on every start, the next DSH release deactivates this
56
+ plugin until the list is widened deliberately — see `docs/决策记录.md`
57
+ (章节一, 条目2), which records this as a reversal of the 2026-09-29 policy.
58
+ The published CLI composed the plugin's patch cleanly before the change (see
59
+ the 2026-10-14 entry). The plugin's own `lib/client.js` shape was not
60
+ re-verified against the alpha.3 client-modules scanner.
61
+ - **Protocol verdict: no phone-protocol change.** Protocol v2, pairing state,
62
+ and device records are untouched by this release; no phone needs to re-pair.
63
+ - **Two host-side migrations this machine must apply** (independent of the
64
+ plugin): `~/.dsh/profiles/web/cordis.patch.yml` carries
65
+ `llm-deepseek-account.config.models: []`, and alpha.3 throws on any residual
66
+ `models` key — **including an empty array** (`resolveAccountOptions`). Also
67
+ `dsh --profile <name> --from-default-profile <template>` now *replaces* an
68
+ existing profile directory (it deletes the old `node_modules` and
69
+ `cordis.patch.yml`); the audit procedure recorded below used that flag.
70
+ - **Capability gaps, not regressions:** the `contextBreakdown` projection was
71
+ reshaped (`nodes[]` → `systems[]` + `surfaceTokens` + `claim`, stateVersion
72
+ 5→6) and the plugin forwards it by key, so the phone may render empty values;
73
+ the model catalog shrank (account routes return only the version-gated
74
+ `offered` subset, pi-ai routes without a resolvable key return `[]`); and
75
+ `CLIENT_UPGRADE_REQUIRED` / `MODEL_UNSUPPORTED` are new codes with no mapping
76
+ in `src/wire-errors.ts` yet.
77
+
78
+ ### Previous decision — `dsh-v0.2.1-alpha.2`
79
+
8
80
  - **Audited release:** `dsh-v0.2.1-alpha.2` (official release page:
9
81
  <https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.2.1-alpha.2>),
10
82
  published to npm 2026-10-09T08:18:00Z on the `alpha` dist-tag. It is **not**
@@ -43,9 +115,21 @@
43
115
 
44
116
  ### Plugin release verdicts
45
117
 
46
- - **Release verdict (`0.9.4`, prepared 2026-10-10, NOT published):** carries the
47
- `projections` adapter fix and the Automation tasks documentation correction
48
- described in the 2026-10-09 entry below. `package.json`, both root
118
+ - **Release candidate (`0.9.5`, 2026-10-15):** carries the alpha.3 Peer/client
119
+ compatibility repair, the exact two-host peer policy, cross-language auth
120
+ vectors and recovery guidance, and the manual OIDC/provenance workflow.
121
+ The package and both lockfile version entries are `0.9.5`; the apply baseline
122
+ records that version. Clean install, 562 passing unit tests (zero failures,
123
+ one existing TODO), typecheck, build, the real pinned-host schema loader,
124
+ Go tests, all six helper checksums, and package inspection pass locally.
125
+ The altered-challenge test now mutates the challenge from its own socket,
126
+ eliminating the timestamp collision, and passes 20 consecutive focused runs.
127
+ Registry/provenance and clean published-profile checks remain release gates;
128
+ no physical-iPhone validation is claimed. Notes: `docs/RELEASE_NOTES-0.9.5.md`.
129
+
130
+ - **Release verdict (`0.9.4`, published 2026-10-10, tag `v0.9.4` @ `e2f77af`):**
131
+ carries the `projections` adapter fix and the Automation tasks documentation
132
+ correction described in the 2026-10-09 entry below. `package.json`, both root
49
133
  `package-lock.json` version entries, and `tests/apply-baseline.snapshot.json`
50
134
  are on `0.9.4`, and `lib/` is regenerated. The full checklist passed locally:
51
135
  `npm ci`, 538 unit tests, typecheck, build, the config-schema check against a
@@ -53,9 +137,24 @@
53
137
  `npm pack --dry-run` (65 files, 50,916,470 bytes packed). The fix was also
54
138
  verified on a live `0.2.1-alpha.2` host against the published `0.9.3` as an
55
139
  A/B control (25 pass / 0 fail either way, with the `sessions.projections
56
- failed: internal` diagnostic present only on `0.9.3`). Nothing was
57
- published, tagged, or pushed; npm `latest` still points at `0.9.3`, so users on
58
- `0.2.1-alpha.2` keep hitting the projections defect until this ships.
140
+ failed: internal` diagnostic present only on `0.9.3`).
141
+ - **Registry verification after publishing.** `dist-tags.latest` moved off
142
+ `0.9.3`; `dist.shasum` `85b3c71a802e45e42f5fb1b23444ed2e396d39dc` is
143
+ byte-identical to the local `npm pack` output at the tag; 65 files,
144
+ `dist.unpackedSize` 130,789,604. **`gitHead` is `e2f77af`, the tag commit** —
145
+ the 0.9.3 ordering deviation did not repeat, because the release notes and
146
+ every other documentation change were committed before the pack. The publish
147
+ itself needed the maintainer's one-time password, as in 0.9.3; everything up
148
+ to and after it was done here.
149
+ - **Clean-profile verification.** A fresh isolated home, built from the shipped
150
+ Web template and installed with the published `0.2.1-alpha.2` CLI
151
+ (`dsh plugin --profile verify add dsh-deeppilot@0.9.4`), composed a real
152
+ profile: `--dump-config` printed the plugin's three rows
153
+ (`deeppilot-directory-picker-browse`, `…-browse-client`, `deeppilot`) with no
154
+ missing-module or load error, the installed package's six helper checksums
155
+ verified, every helper binary embedded `go1.26.9`, and the installed
156
+ `lib/index.js` carried the projections fix. Only then was the GitHub Release
157
+ taken out of draft and marked Latest.
59
158
  - **The release also had to fix the helper's toolchain and its refresh
60
159
  workflow.** CI's `govulncheck` job reported 11 reachable Go standard-library
61
160
  vulnerabilities for the pinned `go 1.26.6`; this is time-triggered, not
@@ -207,6 +306,224 @@
207
306
  bundle" below. The plugin needs no code change; the phone's copy now points
208
307
  at the bundle.
209
308
 
309
+ ## 2026-10-14 — audit of `dsh-v0.2.1-alpha.3`
310
+
311
+ ### Scope and method
312
+
313
+ Compared `dsh-v0.2.1-alpha.2...dsh-v0.2.1-alpha.3` (**852 commits, 3311 changed
314
+ files, 120,978 insertions, 16,632 deletions**) by fetching the new tag into the
315
+ existing blobless clone (`.audit-dsh-alpha2`, whose HEAD was already the alpha.2
316
+ audit's target SHA `d7432673`), then `git diff --name-status` and per-path
317
+ content diffs into `.audit-alpha3/diffs/`. Four parallel read-only passes
318
+ covered `packages/api` + `apps/cli` + `packages/typert` + `packages/settings` +
319
+ `vendor`, `packages/client/{connection,locale,ui-settings,ui-slots}` +
320
+ `packages/boot` + `packages/bundle`, `packages/{credentials,subagent,session,
321
+ schedule}`, and `packages/{core,host,skill,llm}`. All **14** upgrade guides
322
+ added by this release (under `docs/upgrade-guide/v0.2.0-rc.2/`,
323
+ `v0.2.1-alpha.1/`, `v0.2.1-alpha.2/`) were read in Chinese in full.
324
+
325
+ The alpha.2 audit's lesson — judge the seams reached through `ctx.get`, not just
326
+ the peer list — is what caught this one: the peer list is *not* where the breaks
327
+ are. `@deepseek-ai/dsh-api-remote-access` is new, `packages/schedule/schedule`
328
+ has **zero** changed source files, and the peer packages' own exported surfaces
329
+ moved far less than the carrier contracts around them.
330
+
331
+ ### The four confirmed breaks
332
+
333
+ Every one is the same root cause: alpha.3 makes the Peer explicit. `PeerScope`
334
+ (`@deepseek-ai/dsh-typert-protocol`) gained required `principal`, `signal`, and
335
+ `interactions`; `client/connection` added a `Peer` class and deleted
336
+ `OperatorPeer`; the Gateway deleted `operatorPeer()` and its
337
+ `request.peer ?? this.operatorPeer()` fallback.
338
+
339
+ Reproduction used the **published** bundles, the same technique as the alpha.2
340
+ `projections` break, with the plugin's exact argument list
341
+ (`open('$events', payload, undefined, peer, signal)`):
342
+
343
+ ```sh
344
+ # .audit-alpha3/repro-peer-contract.mjs, against
345
+ # alpha.2: ~/.npm/_npx/b5c6312874079fe8/node_modules/@deepseek-ai/dsh-api-gateway/lib/index.js
346
+ # alpha.3: .audit-alpha3/cli-install/node_modules/@deepseek-ai/dsh-api-gateway/lib/index.js
347
+ # A. peer = undefined (what the plugin sends today)
348
+ alpha.2: 未抛错
349
+ alpha.3: 抛错 TypeError: Cannot read properties of undefined (reading 'signal')
350
+ # B. peer = { principal: owner, signal, interactions: 'none' }
351
+ alpha.2: 未抛错
352
+ alpha.3: 抛错 TypertGatewayError: $events: forwarded Remote events require interactions=answer
353
+ # C. peer = { principal: owner, signal, interactions: 'answer' }
354
+ alpha.2: 未抛错
355
+ alpha.3: 未抛错
356
+ ```
357
+
358
+ Break 4 was settled by reading the shipped artifacts rather than by inference:
359
+ `@deepseek-ai/dsh-client-connection`'s `./client` now maps `node` →
360
+ `lib/client.node.js`, and that file contains **zero** `__ModuleLoader__`
361
+ occurrences while `client.browser.js` and alpha.2's `client.js` each contain
362
+ one. `loadClientFaces` builds its module table *only* from that banner, so on
363
+ Node the table stays empty. Both shapes export `apply` and `inject`, which is
364
+ what makes a banner-optional fallback possible.
365
+
366
+ ### The fix
367
+
368
+ One call form serves both audited hosts, because alpha.2 ignores a second
369
+ `fetch` argument and defaults an absent `open` peer to the operator Peer it
370
+ holds:
371
+
372
+ - `src/dsh-remote-interactions.ts` mints one Peer through
373
+ `connection.createPeer({ principal: { kind: 'owner' }, interactions: 'answer' })`
374
+ and falls back to `connection.operator` on a Host without that entry point.
375
+ The same Peer is named on `fetch` and on `wireStream.open`.
376
+ - The Connection install prefers the explicit
377
+ `installConnection(ctx, { transport, interactions: 'answer' })`, because
378
+ `apply(ctx)` cannot name the participation and therefore always selects
379
+ `'none'` for this caller; `apply` remains the fallback for a host that predates
380
+ the installer.
381
+ - `loadClientFaces` accepts a Node bundle's named exports when the browser
382
+ banner did not register them. A banner registration still wins, because the
383
+ bundle's own internal `require()` resolves against it.
384
+
385
+ Six regression tests in `tests/dsh-wire-stream-contract.test.ts` pin the
386
+ contract: the operator fallback, the alpha.3 `createPeer` request shape and the
387
+ Peer reaching `wireStream.open`, both installer branches, and both module-table
388
+ branches. The file fails against the previous call form — verified by restoring
389
+ the source and re-running (1 failure) — and passes after the fix.
390
+
391
+ ### Peer range narrowed to the two audited hosts
392
+
393
+ By explicit user direction the DSH peer range stopped being open-ended:
394
+ `>=0.2.0-rc.2 <0.3.0-0` became `0.2.0-rc.2 || 0.2.1-alpha.3` across all ten
395
+ `@deepseek-ai/dsh*` peers, with `@deepseek-ai/cordis` widened to
396
+ `~4.0.4 || ~4.0.5-alpha.1` and `@deepseek-ai/schemastery` to
397
+ `~3.18.4 || ~3.18.5-alpha.1`. The two vendor peers are outside DSH's own peer
398
+ check — it reads only `@deepseek-ai/dsh` and `@deepseek-ai/dsh-*` — so their
399
+ ranges matter only to npm's install-time resolution, which runs without
400
+ `includePrerelease`; naming both vendored prereleases keeps a clean install on
401
+ either host. `devDependencies` stay pinned to `0.2.0-rc.2`.
402
+
403
+ The consequence is deliberate: `evaluatePluginCompatibility` runs before a
404
+ profile is composed on every start, so the next DSH release will refuse to
405
+ activate this plugin until the list is widened by an audit. That reversal of the
406
+ 2026-09-29 policy is recorded in `docs/决策记录.md` (章节一, 条目2).
407
+ `tests/compatibility-metadata.test.ts` now asserts both admissions, fifteen
408
+ rejections, and the two vendor ranges.
409
+
410
+ ### Host-side changes with no plugin impact
411
+
412
+ `packages/schedule/schedule/**` has no changed source; the schedule error-code
413
+ table is identical, so `src/wire-errors.ts`'s twelve mappings (including
414
+ `subagent_session → E_PROTOCOL`) stay correct. No session-log format migration
415
+ exists — `session-format*`, `v0-to-v1`…`v3-to-v4`, `session-persistence`,
416
+ `session-projection*`, `session-log-deepseek` changed only their version and
417
+ locale files — so phone history rendering is unaffected. The subagent descriptor
418
+ is still version 3. `host/webserver`'s `port` widening, the macOS directory
419
+ picker, the PTC call registry, and the new `job/registered` / `job/settled`
420
+ events are all unreachable or safely skipped by this plugin (`projectEvent`'s
421
+ `default: return null`). Settings surfaces are unchanged at the contract level:
422
+ `ui-settings`'s `settings.section` slot has the same SHA as alpha.2, and
423
+ `configForms.get()` kept its name. The Web composition still mounts
424
+ `schedule`/`ui-schedule`, and `directory-picker` is still disabled in place, so
425
+ the plugin's three `cordis.patch.yml` rows still land.
426
+
427
+ ### Validation evidence
428
+
429
+ - **Config schema on the published CLI:** isolated `DSH_HOME`, `dsh
430
+ --profile deeppilot-audit --from-default-profile sdk-minimal
431
+ --dump-config-schema --patch cordis.patch.yml` against
432
+ `@deepseek-ai/dsh@0.2.1-alpha.3` → exit 0, 179,851 bytes, the plugin entry at
433
+ `/37` reports `status: 'schema'` with `configRef: #/$defs/config24`, all seven
434
+ volatile assertions pass, no `deeppilot`-attributed diagnostic, and the only
435
+ warning is the `sdk-minimal` template's own missing `directory-picker` row.
436
+ - **Web composition:** the same CLI composed a real Web profile with the plugin
437
+ patch → **1225 rows versus 1214 without it**, the plugin's three rows present
438
+ at `deeppilot-directory-picker-browse` / `…-browse-client` / `deeppilot`, and
439
+ zero `unrecognized Loader tree carrier` entries on either side.
440
+ - **Plugin unit tests:** `npx tsx --test tests/*.test.ts` → **562 tests, 561
441
+ pass, 0 fail, 1 todo**, including the six new contract tests. `npm run
442
+ typecheck`, `npm run build`, and `npm run check:config-schema` (against the
443
+ pinned `0.2.0-rc.2` CLI) all pass with regenerated `lib/`.
444
+ - **Live A/B on a real `0.2.1-alpha.3` host — the evidence that matters.** An
445
+ isolated `DSH_HOME` profile built from the shipped Web template and linked to
446
+ this working tree booted under the published alpha.3 CLI (`--port 3180`; the
447
+ default 3080 was already held by this machine's own Host). The **unfixed**
448
+ `0.9.4` build printed
449
+ `[deeppilot] DSH Remote interaction client unavailable:
450
+ @deepseek-ai/dsh-typert-registry/client Client bundle returned no exports`
451
+ and the fixed build printed no such line. The checklist itself scored
452
+ **23 pass / 1 fail / 4 skipped in both runs** — see the blind spot below.
453
+ - **The regression checklist does not cover this seam.** `scripts/smoke-live.mts`
454
+ scored identically before and after the fix, because the resident Client it
455
+ exercises is only reachable through the approval/question waterfall, and step
456
+ 10 is skipped without `--interactions`. The observable difference is the Host
457
+ log line and the phone's approval/question delivery, not a checklist step. A
458
+ future revision should assert on that diagnostic directly.
459
+ - **Backward compatibility, same working tree:** the same profile booted under
460
+ the published `0.2.1-alpha.2` CLI (`--port 3181`) scored **25 pass / 0 fail /
461
+ 0 warn / 3 skipped** with no interaction-client diagnostic — the same result
462
+ the alpha.2 audit recorded, so the fix does not regress the host users run
463
+ today.
464
+ - **One checklist difference between the two host versions is real.**
465
+ `模型目录可读` passes on alpha.2 and fails on alpha.3 in the same isolated
466
+ home (`0 provider / 0 model, routable=true`): this release stops listing a
467
+ pi-ai route before its credential resolves and returns only the version-gated
468
+ subset for the account route. The home has no model credential, so the phone's
469
+ model list is empty there. That is the documented behavior change, not a
470
+ plugin defect.
471
+ - **Artifact inspection:** the `node`/`browser` conditional exports and the
472
+ banner counts above were read from the installed alpha.3 packages, not from
473
+ the diff.
474
+
475
+ ### Host-side migrations this machine must apply
476
+
477
+ Neither is a plugin defect, and neither is fixed by the plugin change:
478
+
479
+ 1. **`llm-deepseek-account`'s retired `models` key.**
480
+ `~/.dsh/profiles/web/cordis.patch.yml:77-79` sets `models: []`.
481
+ `resolveAccountOptions` throws for *any* own `models` key
482
+ (`llm-deepseek-account: models is no longer configurable…`), and the
483
+ upstream test pins `[{ models: [] }, /models is no longer configurable/]`.
484
+ The empty array is not exempt. The `desktop` profile's row carries no
485
+ `config` and is unaffected. Whether the throw takes the whole Host down or
486
+ only retires that route was **not** measured — the guide says the account
487
+ models stop appearing either way.
488
+ 2. **`--from-default-profile` now replaces.** It stages a fresh copy over the
489
+ target and deletes the previous directory, including `node_modules` and the
490
+ user's own `cordis.patch.yml`. The re-audit procedure below and the alpha.2
491
+ entry's schema command both use that flag; from now on it must be pointed at
492
+ a scratch name.
493
+
494
+ Checked and **not** required: Azure provider rename (no `azure` /
495
+ `azure-openai-responses` key exists under `~/.dsh/profiles`), credential-file
496
+ extra keys (the file's top level is already `version`/`refs`/`records`), and
497
+ the stored-credential precedence flip (no `refs` name is exported in the shell,
498
+ and the stored value won precedence over `.env` on both sides of the change).
499
+
500
+ ### Still unverified
501
+
502
+ - Whether breaks 1 and 2 surface inside `dsh-api-proxy.ts`'s catch or escape it
503
+ as an unhandled rejection — the Gateway opens its Remote Events generation
504
+ asynchronously, after `startDshRemoteInteractions` has already returned.
505
+ - Whether an old profile that predates `@deepseek-ai/dsh-api-remote-access`
506
+ still gets the row (the shipped Web template does; `dsh-base` carries it).
507
+ - How the reshaped `contextBreakdown` renders on iOS, and what the phone shows
508
+ for the unmapped `CLIENT_UPGRADE_REQUIRED` / `MODEL_UNSUPPORTED` codes.
509
+ - Whether the plugin's own `lib/client.js` (banner shape, `dsh.client.inject`,
510
+ `platform: web`) is still accepted by alpha.3's client-modules scanner; that
511
+ scanner lives in `packages/client/modules` and `packages/client/web`, which
512
+ were not read in full.
513
+
514
+ ### Re-audit procedure used
515
+
516
+ Fetch the new tag into the existing blobless clone at `.audit-dsh-alpha2`
517
+ (`git fetch --filter=blob:none --no-tags origin
518
+ '+refs/tags/<tag>:refs/tags/<tag>'`, ~3 s), slice
519
+ `git diff --name-status` per package, and write each slice's content diff to a
520
+ file before parallel reading — blobless clones fetch blobs on demand, so one
521
+ `git diff` per region is far cheaper than many small reads. Reproduce a
522
+ suspected carrier break against the **published** `lib/` bundles with a stub
523
+ `this`, as above, rather than trusting either the diff or the release notes.
524
+ `npm --cache <workspace dir>` is still required; `~/.npm/_cacache` holds
525
+ root-owned files and fails `npm view` with `EPERM`.
526
+
210
527
  ## 2026-10-09 — audit of `dsh-v0.2.1-alpha.2`
211
528
 
212
529
  ### Scope and method
@@ -316,12 +633,12 @@ the mirror's `signal` is required, so the compiler keeps it that way.
316
633
  own, so a dedicated long-lived controller is not warranted; following the
317
634
  existing convention in the same file is enough. `lib/index.js` was regenerated.
318
635
 
319
- The fix ships in `0.9.4`, which is prepared but not published (see "Plugin release
320
- verdicts"); until it is published, a host on `0.2.1-alpha.2` running the
321
- published `0.9.3` still loses the on-open projection baseline. That was observed
322
- directly: on a live `0.2.1-alpha.2` host running published `0.9.3` the diagnostic
323
- appeared, and on the same host version running this build it did not (see "Live
324
- run on a real `0.2.1-alpha.2` host" under Validation evidence).
636
+ The fix ships in `0.9.4`, published 2026-10-10 (see "Plugin release verdicts").
637
+ A host on `0.2.1-alpha.2` left on the published `0.9.3` still loses the on-open
638
+ projection baseline. That was observed directly: on a live `0.2.1-alpha.2` host
639
+ running published `0.9.3` the diagnostic appeared, and on the same host version
640
+ running this build it did not (see "Live run on a real `0.2.1-alpha.2` host"
641
+ under Validation evidence).
325
642
 
326
643
  `tests/dsh-api-proxy.test.ts` adds "projection baseline reads pass the
327
644
  AbortSignal the Host now requires": a controller stub that calls
@@ -0,0 +1,83 @@
1
+ # npm 可信发布说明
2
+
3
+ ## 一、发布方式
4
+
5
+ 1. `.github/workflows/publish.yml` 使用 GitHub 托管的运行器,通过
6
+ OpenID Connect(OIDC,开放身份连接)向 npm 获取临时发布凭据;无需配置
7
+ `NPM_TOKEN` 或 `NODE_AUTH_TOKEN`。来源证明(Provenance)将关联公开仓库、
8
+ 工作流和本次运行的源码提交。
9
+ 2. 工作流仅由 `workflow_dispatch`(手动触发)启动;推送提交、标签或创建
10
+ GitHub Release(发布页)不会自动上传 npm 包。手动运行须使用对应版本标签,
11
+ 不能在 `main` 上运行后另行检出标签,否则来源证明的提交会与源码不一致。
12
+ 3. 发布环境使用 Node.js 22 与固定的 npm `11.21.0`;npm 官方要求可信发布至少使用
13
+ npm `11.5.1` 和 Node.js `22.14.0`。稳定版使用 `latest`(稳定分发标签),
14
+ 预发布版使用 `alpha`(预发布分发标签),与原有发布策略一致。
15
+
16
+ ## 二、npm 网站绑定
17
+
18
+ 1. 将工作流合入默认分支 `main` 后,打开
19
+ [dsh-deeppilot 包设置](https://www.npmjs.com/package/dsh-deeppilot/access),
20
+ 在 Trusted Publisher(可信发布者)区域选择 GitHub Actions(GitHub 工作流)。
21
+ 2. 按下表配置,字段大小写必须一致:
22
+
23
+ | npm 字段 | 含义 | 填写值 |
24
+ | --- | --- | --- |
25
+ | Organization or user | GitHub 组织或用户 | `Mars-Sea` |
26
+ | Repository | 仓库名称 | `dsh-deeppilot` |
27
+ | Workflow filename | 工作流文件名 | `publish.yml` |
28
+ | Environment name | 部署环境名称 | 留空,本工作流未使用部署环境 |
29
+ | Allow npm publish | 允许直接发布 | 勾选 |
30
+ | Allow npm dist-tag | 允许独立修改分发标签 | 无需勾选,工作流通过发布命令设置标签 |
31
+
32
+ 3. 工作流文件名只填 `publish.yml`,不填 `.github/workflows/` 前缀。
33
+ 新建绑定默认只允许暂存发布(`npm stage publish`),须明确允许 `npm publish`。
34
+ npm 不在保存配置时验证绑定;首次发布成功才证明认证可用。
35
+ 4. 新绑定必须在 **2 天内完成首次成功发布**。因此准备好新版本后再绑定;若过期,
36
+ 删除过期绑定并重新创建。先验证可信发布成功,再按需调整原有令牌发布权限。
37
+
38
+ ## 三、每次发布
39
+
40
+ 1. 按项目 `AGENTS.md` 的发布清单完成验证,同时更新 `package.json`、
41
+ `package-lock.json` 顶层与根包条目的版本;提交并推送,等待持续集成(CI)通过。
42
+ 版本提交必须已经进入 `main`,且该提交包含 `publish.yml`。
43
+ 2. 经用户授权发布该版本后,从精确的发布提交创建并推送附注标签 `v<版本>`,
44
+ 准备包含英文与简体中文说明的 GitHub 发布草稿。
45
+ 3. 在对应标签上手动运行工作流。下例的 `0.9.5` 仅为示例,替换为已授权发布的版本:
46
+
47
+ ```sh
48
+ gh workflow run publish.yml --repo Mars-Sea/dsh-deeppilot --ref v0.9.5 \
49
+ -f version=0.9.5 -f dist_tag=latest
50
+ ```
51
+
52
+ 预发布示例:
53
+
54
+ ```sh
55
+ gh workflow run publish.yml --repo Mars-Sea/dsh-deeppilot --ref v0.9.5-alpha.1 \
56
+ -f version=0.9.5-alpha.1 -f dist_tag=alpha
57
+ ```
58
+
59
+ 4. 工作流验证输入、标签、两处锁文件版本、包仓库与运行提交一致;拒绝轻量标签、
60
+ 尚未合入 `main` 的提交、分发标签错配和构建元数据。随后执行依赖安全检查、
61
+ 插件测试、类型检查、构建产物一致性检查、DSH 配置校验、Go 测试和辅助程序校验和。
62
+ 5. 工作流检查包内容并生成压缩包,再发布该压缩包,显式要求来源证明。
63
+ 压缩包发布跳过生命周期脚本(`--ignore-scripts`);测试、类型检查和构建已在前序
64
+ 步骤执行,打包仍执行 `prepack`(打包前构建)。发布后核对版本、分发标签、
65
+ 压缩包完整性和来源证明元数据。
66
+ 6. 确认 npm 包可安装,在全新 DSH 配置中完成安装验证后,再公开 GitHub 发布草稿。
67
+ 工作流不自动公开发布草稿;npm 上传和发布后的核对分别记录在步骤中。
68
+
69
+ ## 四、失败处理与证据边界
70
+
71
+ 1. 认证失败先核对 npm 绑定中的仓库、大小写、`publish.yml` 和直接发布权限。
72
+ `npm whoami`(当前账号查询)不能验证 OIDC 发布权限。
73
+ 2. 若 npm 上传成功而后续核对失败,先检查注册表;已存在的版本不能通过重跑覆盖。
74
+ 排查来源证明或分发标签后再决定下一步,禁止把核对失败直接当作上传失败。
75
+ 3. 本地语法检查、测试和打包不能证明 npm 绑定已生效;真实 OIDC 认证与来源证明
76
+ 需要首次获授权的新版本发布才能验证。已有旧版本的认证状态不因新增工作流改变。
77
+
78
+ ## 五、官方资料
79
+
80
+ 1. [npm 可信发布配置与限制](https://docs.npmjs.com/trusted-publishers/)
81
+ 2. [npm 来源证明要求](https://docs.npmjs.com/generating-provenance-statements/)
82
+ 3. [GitHub 手动运行工作流](https://docs.github.com/en/actions/how-tos/manage-workflow-runs/manually-run-a-workflow)
83
+ 4. [GitHub 工作流调度接口:运行引用支持分支或标签](https://docs.github.com/en/rest/actions/workflows#create-a-workflow-dispatch-event)
@@ -14,11 +14,16 @@ Status: implemented on the development branch. `PROTOCOL.md` is normative. Proto
14
14
  - minimal unauthenticated health response; no Bearer or URL credentials;
15
15
  - privacy-preserving audit identifiers, a global connection switch, and per-device deletion; scopes remain enforced at the protocol layer.
16
16
 
17
+ - shared canonical/signature vectors (`tests/fixtures/auth-vectors.json`) read by both the TypeScript tests and the iOS tests, with signatures from Node and from CryptoKit;
18
+ - abuse tests for malformed requests, connection churn, replay, expired and altered challenges, and many-source pressure (`tests/auth-hardening.test.ts`);
19
+ - documented local recovery when `devices-v2.json` is lost ([DEVICE_REGISTRY_RECOVERY.md](./DEVICE_REGISTRY_RECOVERY.md)).
20
+
21
+ ## Decided
22
+
23
+ - High-impact approval responses do **not** require a fresh local biometric/user-presence confirmation (decided 2026-10-12). Revisit if the threat model changes, for example a phone shared between people.
24
+
17
25
  ## Remaining release validation
18
26
 
19
- - share fixed canonical/signature vectors between TypeScript and Swift tests;
20
27
  - exercise pairing, reconnect/resume, and per-device deletion through both real LAN and Funnel paths;
21
- - add abuse tests for malformed requests, connection churn, replay, and many-source pressure;
22
- - document local recovery when `devices-v2.json` is lost;
23
- - decide whether high-impact approval responses should require fresh local biometric/user-presence confirmation;
28
+ - an authenticated socket still accepts a second `c2s.auth.prove` and answers it with another welcome and resume replay; the gate should answer `E_PROTOCOL` instead (tracked as a `todo` test);
24
29
  - keep dependency scanning and CodeQL green, then sign and notarize every distributed helper before claiming production readiness.
@@ -14,11 +14,16 @@
14
14
  - 最小化匿名探活响应,不接受 Bearer 或 URL 凭据;
15
15
  - 隐私化审计标识、全局连接开关和逐设备删除入口;scope 继续由协议层强制执行。
16
16
 
17
+ - TypeScript 与 Swift 测试共用的固定规范化编码/签名向量(`tests/fixtures/auth-vectors.json`),签名分别来自 Node 与 CryptoKit;
18
+ - 畸形请求、连接抖动、重放、挑战过期/被篡改和多来源压力的滥用测试(`tests/auth-hardening.test.ts`);
19
+ - `devices-v2.json` 丢失后的本机恢复流程([DEVICE_REGISTRY_RECOVERY.zh-CN.md](./DEVICE_REGISTRY_RECOVERY.zh-CN.md))。
20
+
21
+ ## 已决定
22
+
23
+ - 高影响审批响应**不**要求新的本地生物识别/用户在场确认(2026-10-12 决定)。如威胁模型变化(例如手机多人共用)再重新评估。
24
+
17
25
  ## 发布前剩余验证
18
26
 
19
- - 在 TypeScript 与 Swift 测试间共享固定的规范化编码/签名向量;
20
27
  - 通过真实 LAN 与 Funnel 验证配对、重连/恢复和逐设备删除;
21
- - 补充畸形请求、连接抖动、重放和多来源压力测试;
22
- - 记录 `devices-v2.json` 丢失后的本机恢复流程;
23
- - 决定高影响审批是否要求新的本地生物识别/用户在场确认;
28
+ - 已认证的 socket 仍会接受第二次 `c2s.auth.prove`,并再回一次 welcome 与重放;门应改为回 `E_PROTOCOL`(以 `todo` 测试跟踪);
24
29
  - 保持依赖扫描与 CodeQL 通过,并在宣称生产可用前签名、公证所有分发 helper。
package/lib/client.js CHANGED
@@ -545,7 +545,7 @@ window.__ModuleLoader__.load({
545
545
  "push.testPush": "发送测试通知",
546
546
  "push.relayTesting": "测试中…",
547
547
  "push.pushSending": "发送中…",
548
- "push.relayDefault": "验证 Mac 到推送中继的连通性与自动注册是否正常。",
548
+ "push.relayDefault": "验证这台电脑到推送中继的连通性与自动注册是否正常。",
549
549
  "push.pushDefault": "向所有已注册设备强制发送一条真实推送(不受在线状态与分类开关影响)。",
550
550
  "push.relayOk": "通过",
551
551
  "push.relayBad": "存在问题",
@@ -656,7 +656,7 @@ window.__ModuleLoader__.load({
656
656
  en: {
657
657
  "nav": "DeepPilot",
658
658
  "meta.title": "DeepPilot",
659
- "meta.intro": "Connect your iPhone to DeepSeek Harness (DSH) on this Mac (protocol v2).",
659
+ "meta.intro": "Connect your iPhone to DeepSeek Harness (DSH) on this computer (protocol v2).",
660
660
  "meta.refresh": "Refresh",
661
661
  "master.title": "DeepPilot connection",
662
662
  "master.loading": "Loading settings…",
@@ -763,7 +763,7 @@ window.__ModuleLoader__.load({
763
763
  "push.testPush": "Send test notification",
764
764
  "push.relayTesting": "Testing…",
765
765
  "push.pushSending": "Sending…",
766
- "push.relayDefault": "Verify Mac → push relay connectivity and zero-touch enrollment.",
766
+ "push.relayDefault": "Verify this computer → push relay connectivity and zero-touch enrollment.",
767
767
  "push.pushDefault": "Force one real push to every registered device (ignores online state and category switches).",
768
768
  "push.relayOk": "OK",
769
769
  "push.relayBad": "Issues found",
@@ -814,7 +814,7 @@ window.__ModuleLoader__.load({
814
814
  "help.step2": "When the status shows \"Awaiting Tailscale authorization\", click \"Open authorization page\".",
815
815
  "help.step3": "Sign in with an account that can manage this Tailnet and follow the on-page prompts to enable Funnel. Owner / Admin / Network admin is usually required.",
816
816
  "help.step4": "Come back to this page and click \"Refresh\". When a green dot appears next to the remote title, scan the QR code below to add your phone.",
817
- "help.funnelHint": "The plugin bundles the Tailscale networking stack, so neither this Mac nor the phone needs the Tailscale app — but a Tailnet administrator must approve first-time use.",
817
+ "help.funnelHint": "The plugin bundles the Tailscale networking stack, so neither this computer nor the phone needs the Tailscale app — but a Tailnet administrator must approve first-time use.",
818
818
  "help.httpsTitle": "If authorization does not complete: enable HTTPS",
819
819
  "help.httpsStep1": "Open the Tailscale admin console → Network → DNS.",
820
820
  "help.httpsStep2": "Confirm MagicDNS is enabled.",
@@ -826,7 +826,7 @@ window.__ModuleLoader__.load({
826
826
  "help.faqTitle": "Frequently asked questions",
827
827
  "help.faq1": "\"HTTPS must be enabled\" — finish the HTTPS Certificates steps above.",
828
828
  "help.faq2": "\"Funnel not available\" — make sure the nodeAttrs snippet is saved and this node is in the rule's target.",
829
- "help.faq3": "Public URL exists but the phone times out: wait a few minutes, confirm the Mac is awake and DSH is running, then re-scan the latest QR code.",
829
+ "help.faq3": "Public URL exists but the phone times out: wait a few minutes, confirm the computer is awake and DSH is running, then re-scan the latest QR code.",
830
830
  "help.faq4": "No public URL: check that Tailscale authorization completed, then click \"Refresh\" or restart DSH.",
831
831
  "help.securityTitle": "Security notes",
832
832
  "help.security1": "A Funnel URL is public, but every connection requires a signature from a paired device P-256 private key. Do not share a pairing QR while it is valid.",