pi-session-mcp 0.8.1 → 0.8.3
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/CHANGELOG.md +107 -0
- package/CONTRIBUTING.md +5 -3
- package/README.md +5 -3
- package/dist/session-tools.js +1 -1
- package/dist/setup-skills.d.ts +8 -2
- package/dist/setup-skills.js +56 -18
- package/dist/setup.js +21 -4
- package/docs/acceptance.md +549 -22
- package/docs/adr/0003-post-mvp-maintenance.md +1 -1
- package/docs/architecture.md +1 -1
- package/docs/client-setup-design.md +60 -13
- package/docs/client-setup.md +19 -0
- package/docs/installation.md +7 -3
- package/docs/releasing.md +68 -14
- package/docs/reviews/2026-10-13-post-0.8.1-audit.md +393 -0
- package/docs/roadmap.md +140 -0
- package/docs/skills.md +39 -11
- package/docs/sprints/2026-10-13-s1-correctness.md +355 -0
- package/docs/sprints/2026-10-13-s2-evidence.md +431 -0
- package/docs/sprints/2026-10-13-s3-live-usage.md +267 -0
- package/docs/sprints/2026-10-13-s4-decision-inputs.md +241 -0
- package/docs/sprints/2026-10-15-s4-decision.md +185 -0
- package/docs/threat-model.md +1 -1
- package/docs/tool-contracts.md +20 -2
- package/package.json +8 -2
- package/skills/pi-session-orchestration/SKILL.md +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,113 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.8.3 — 2026-10-15
|
|
6
|
+
|
|
7
|
+
- Fixed: a fresh `npm install -g pi-session-mcp@0.8.2` produced a server that
|
|
8
|
+
exited at startup. `@earendil-works/pi-coding-agent` 1.1.0 imports
|
|
9
|
+
`MCP_TYPESCRIPT_PREAMBLE` from `@earendil-works/pi-codemode` but declares it
|
|
10
|
+
as `^1.1.0`, and `pi-codemode` 1.2.0 removed that export. Consumers do not
|
|
11
|
+
receive our lockfile, so npm resolved the new version, while CI and the
|
|
12
|
+
acceptance runs used `npm ci` and never saw it. The SDK's internal
|
|
13
|
+
`@earendil-works` packages, including those declared by other family members
|
|
14
|
+
(`pi-telemetry` comes from `pi-ai`), are now direct, exactly pinned
|
|
15
|
+
dependencies. Issue #78.
|
|
16
|
+
- Changed: Pi SDK 1.2.0 (was 1.1.0) with `@earendil-works/pi-ai`, `pi-mcp`,
|
|
17
|
+
`pi-tui`, `pi-codemode`, `pi-agent-core` and `pi-telemetry` pinned to 1.2.0.
|
|
18
|
+
The SDK no longer depends on `@earendil-works/chord`, so `esbuild` leaves the install.
|
|
19
|
+
Upstream changes that matter to operators: print mode (`pi -p`) now exits 1
|
|
20
|
+
with a warning when the final response was cut off at the output token limit;
|
|
21
|
+
Mistral streams are no longer aborted mid-generation after the request
|
|
22
|
+
timeout; "The upstream provider returned an error" is retried as a transient
|
|
23
|
+
provider error; and an OpenRouter key now lists only the models it may use.
|
|
24
|
+
For extensions, `before_agent_start` now also fires for runs started by
|
|
25
|
+
`pi.sendMessage(..., { triggerTurn: true })`, and its event became a union on
|
|
26
|
+
`messageType`; the allowlisted `pi-cursor-sdk` 0.5.3 registers that event and
|
|
27
|
+
the provider-free checks do not test it against SDK 1.2.0.
|
|
28
|
+
The tools declared to the model, the stop-reason mapping and the reserved MCP
|
|
29
|
+
tool names are unchanged. The Pi SDK canary now tests a candidate SDK together
|
|
30
|
+
with the family versions that candidate declares, resolved transitively.
|
|
31
|
+
- Added: an install smoke (`scripts/install-smoke.mjs`) that starts an installed
|
|
32
|
+
server, checks its handshake, name, version and the eight tools, and requires
|
|
33
|
+
it to exit by itself with status 0 on stdin EOF. CI runs it on every change against the packed package
|
|
34
|
+
installed without a lockfile; a nightly workflow
|
|
35
|
+
(`.github/workflows/install-smoke.yml`) runs it against the published
|
|
36
|
+
`latest` and reports a failure in an issue labeled `install-smoke`.
|
|
37
|
+
- Added: documentation of the packed-tarball Level 2 check, the only
|
|
38
|
+
acceptance-level pre-release check that resolves dependencies like a consumer
|
|
39
|
+
install. The CI install smoke also resolves fresh, but it is an early warning,
|
|
40
|
+
not acceptance. See
|
|
41
|
+
[releasing](docs/releasing.md#prepare-a-release) and
|
|
42
|
+
[acceptance](docs/acceptance.md#tooling).
|
|
43
|
+
|
|
44
|
+
## 0.8.2 — 2026-10-13
|
|
45
|
+
|
|
46
|
+
- Fixed: recovery of an interrupted skill update no longer deletes operator
|
|
47
|
+
content in the transaction's `.trash` directory. It deletes that directory
|
|
48
|
+
only when its contents are the complete, unchanged replaced copy (its
|
|
49
|
+
version 2 fingerprint equals the replaced fingerprint in the pending record).
|
|
50
|
+
A partial, modified, added-to, foreign or otherwise unprovable trash, and any
|
|
51
|
+
trash when the record names no usable replaced fingerprint, is retained and
|
|
52
|
+
reported as `skill_recovery_retained`; the pending record, the skill
|
|
53
|
+
directory and the other intermediates stay unchanged. This deliberately
|
|
54
|
+
replaces the automatic deletion of partial trash trees. See [Pi
|
|
55
|
+
skills](docs/skills.md#interrupted-updates). Issue #59.
|
|
56
|
+
- Fixed: `--verify` now reports `unsupported` when the client is unsupported or
|
|
57
|
+
not discoverable (for example its CLI is not on `PATH`) and `inspect_failed`
|
|
58
|
+
when the inspection fails, instead of `registration_not_equivalent`, which
|
|
59
|
+
stays for an absent, divergent or differently fingerprinted registration. A
|
|
60
|
+
failed Doctor ends the target's verify before inspection and the handshake.
|
|
61
|
+
None of these cases mutates a registration or starts the handshake. See
|
|
62
|
+
[client setup](docs/client-setup.md#verify-diagnostics). Issue #56.
|
|
63
|
+
- Fixed: `--dry-run` of a pending version 2 skill transaction now reports
|
|
64
|
+
`skill_recovery_retained` (divergent, exit 1) whenever `--apply` would retain
|
|
65
|
+
an intermediate, using the same read-only classification as the recovery, and
|
|
66
|
+
otherwise `skill_recovery_planned`. Before, it announced
|
|
67
|
+
`skill_recovery_planned` without inspecting the intermediates. A dry-run
|
|
68
|
+
never writes. See [Pi skills](docs/skills.md#interrupted-updates). Issue #60.
|
|
69
|
+
- Changed: the `pi-session-orchestration` skill classifies every documented
|
|
70
|
+
`pi_session_start` error code as operator needed, transient or unknown, by
|
|
71
|
+
the stable code only. A code that is not listed is unknown, and the
|
|
72
|
+
`startup_failed` diagnostics mean that no MCP server is available. The MCP
|
|
73
|
+
tool contract and the error codes are unchanged. Issue #62.
|
|
74
|
+
- Acceptance harness (maintainer script `scripts/multi-session-acceptance.mjs`,
|
|
75
|
+
not part of the npm package and not the package runtime): a run binds the
|
|
76
|
+
contents of the loaded Pi extensions, Pi's npm installation and the
|
|
77
|
+
candidate's skills, including content behind links, and passes the agent
|
|
78
|
+
directory to its server (#66); it binds every Codex skill source, launches
|
|
79
|
+
Codex with the recorded `CODEX_HOME` and refuses a run when any source holds
|
|
80
|
+
a differing controller skill, found by directory or declared name (#67); an
|
|
81
|
+
unreadable or too large bound resource refuses the run and fails the verdict
|
|
82
|
+
with the new code `resourcesUnbound` (#66, #67); and the handshake test skill
|
|
83
|
+
is offered only to the task that needs it, through a separate profile (#68).
|
|
84
|
+
See [acceptance](docs/acceptance.md#multi-session-skill-acceptance).
|
|
85
|
+
- Acceptance harness: a `trio` scenario runs three concurrent sessions from one
|
|
86
|
+
controller, two writers and a read-only reviewer of a frozen fixture, with a
|
|
87
|
+
neighbor abort (#71). `abort` and `trio` now judge every `pi_session_abort`
|
|
88
|
+
call, so an abort that fails or does not target the `wait` session fails the
|
|
89
|
+
run (new code `offTargetAbort`); `abort` is stricter than before (#71).
|
|
90
|
+
Harness tests that built several runs inside one test no longer time out on
|
|
91
|
+
slow macOS runners (#70).
|
|
92
|
+
- Documentation: the Sprint 3 status file
|
|
93
|
+
([`docs/sprints/2026-10-13-s3-live-usage.md`](docs/sprints/2026-10-13-s3-live-usage.md))
|
|
94
|
+
with the first ten-task Level 3 cycle on Pi SDK `1.x`, the R06, R07 and R08
|
|
95
|
+
live records and a macOS runbook; the Sprint 3 section of
|
|
96
|
+
[acceptance](docs/acceptance.md#sprint-3-live-acceptance-2026-10-13).
|
|
97
|
+
- CI: the test step runs with `NODE_OPTIONS=--unhandled-rejections=strict` on
|
|
98
|
+
the existing matrix, the same command that README, [CONTRIBUTING](CONTRIBUTING.md),
|
|
99
|
+
[Releasing](docs/releasing.md) and acceptance now name. Issue #63.
|
|
100
|
+
- Documentation: delivery-status notes on the historical macOS and Pi
|
|
101
|
+
extension plans (#64); acceptance wording that separates targeted
|
|
102
|
+
multi-session evidence from a Level 3 cycle, and one definition of the
|
|
103
|
+
package version, the MCP tool contract and the setup report `schemaVersion`
|
|
104
|
+
in the [tool contracts](docs/tool-contracts.md#versions) (#65); the Sprint 2
|
|
105
|
+
status file ([`docs/sprints/2026-10-13-s2-evidence.md`](docs/sprints/2026-10-13-s2-evidence.md))
|
|
106
|
+
with the Sprint 1 closure and the roadmap status.
|
|
107
|
+
- Documentation: the Sprint 1 status file
|
|
108
|
+
([`docs/sprints/2026-10-13-s1-correctness.md`](docs/sprints/2026-10-13-s1-correctness.md)),
|
|
109
|
+
the roadmap status, and the v0.8.1 post-release evidence carried from the
|
|
110
|
+
release notes into [acceptance](docs/acceptance.md).
|
|
111
|
+
|
|
5
112
|
## 0.8.1 — 2026-10-12
|
|
6
113
|
|
|
7
114
|
- `updatedAt` of a running turn now follows public SDK activity. It is the time
|
package/CONTRIBUTING.md
CHANGED
|
@@ -14,12 +14,14 @@ Linux or macOS and Node.js 22.19.0 or newer:
|
|
|
14
14
|
npm ci
|
|
15
15
|
npm run typecheck
|
|
16
16
|
npm run build:cli
|
|
17
|
-
npm test
|
|
17
|
+
NODE_OPTIONS=--unhandled-rejections=strict npm test
|
|
18
18
|
npm run build
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
The strict option makes an unhandled promise rejection fail the test run, and
|
|
22
|
+
CI runs the tests with the same setting. Tests use a fake Pi adapter or a
|
|
23
|
+
scripted model. They need no provider credentials and no network access beyond
|
|
24
|
+
the npm registry.
|
|
23
25
|
|
|
24
26
|
## Rules
|
|
25
27
|
|
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ It is not the right tool when:
|
|
|
30
30
|
|
|
31
31
|
## Status
|
|
32
32
|
|
|
33
|
-
The package version is **0.8.
|
|
33
|
+
The package version is **0.8.3**.
|
|
34
34
|
|
|
35
35
|
- **Pre-1.0.** The public tool contract is stable within a minor line: a change to tools, fields, states or error codes needs a new minor release.
|
|
36
36
|
- **Linux and macOS**, Node.js 22.19.0 or newer. macOS is tested in CI on macOS 15 and 26 on Apple silicon; Intel Macs run the same code but are untested. Setup acceptance on a real Mac is recorded for Claude Code only; Codex is still pending. See [Acceptance](docs/acceptance.md#status). On other platforms `setup` refuses with `platform_unsupported`.
|
|
@@ -173,6 +173,8 @@ Contract and design:
|
|
|
173
173
|
Maintain:
|
|
174
174
|
|
|
175
175
|
- [Acceptance](docs/acceptance.md)
|
|
176
|
+
- [Maintenance roadmap](docs/roadmap.md)
|
|
177
|
+
- [Post-0.8.1 audit and backlog snapshot](docs/reviews/2026-10-13-post-0.8.1-audit.md)
|
|
176
178
|
- [Releasing](docs/releasing.md)
|
|
177
179
|
- [Contributing](CONTRIBUTING.md)
|
|
178
180
|
- [Security policy](SECURITY.md)
|
|
@@ -183,11 +185,11 @@ Maintain:
|
|
|
183
185
|
npm ci
|
|
184
186
|
npm run typecheck
|
|
185
187
|
npm run build:cli
|
|
186
|
-
npm test
|
|
188
|
+
NODE_OPTIONS=--unhandled-rejections=strict npm test
|
|
187
189
|
npm run build
|
|
188
190
|
```
|
|
189
191
|
|
|
190
|
-
These are the checks that CI runs. Tests use a fake Pi adapter or a scripted model and need no provider credentials. Every direct dependency is pinned exactly, and the lockfile is committed. The project rules for contributors and coding agents are in [AGENTS.md](AGENTS.md).
|
|
192
|
+
These are the checks that CI runs, with the same strict handling of unhandled promise rejections. Tests use a fake Pi adapter or a scripted model and need no provider credentials. Every direct dependency is pinned exactly, and the lockfile is committed. The project rules for contributors and coding agents are in [AGENTS.md](AGENTS.md).
|
|
191
193
|
|
|
192
194
|
The Pi SDK moves quickly, so a nightly canary checks the unchanged sources against the newest published SDK without touching the pin. [Releasing](docs/releasing.md) describes how to update the pin, record a client contract, patch audit advisories and prepare a release.
|
|
193
195
|
|
package/dist/session-tools.js
CHANGED
|
@@ -9,7 +9,7 @@ export const PROFILE_TOOLS = {
|
|
|
9
9
|
*/
|
|
10
10
|
const EXTENSION_TOOL_NAME = /^[A-Za-z0-9_][A-Za-z0-9_-]{0,63}$/;
|
|
11
11
|
/**
|
|
12
|
-
* These mirror `isMcpToolName` of Pi SDK 1.
|
|
12
|
+
* These mirror `isMcpToolName` of Pi SDK 1.2.0 (core/mcp-servers.js), which is not a root
|
|
13
13
|
* export. When the allowlist names no `mcp__` entry, the SDK's `_isAllowedTool` keeps such
|
|
14
14
|
* tools outside the allowlist for codemode and tool_search, so an extension tool with one
|
|
15
15
|
* of these names could be active without being granted. This server's own MCP bridge
|
package/dist/setup-skills.d.ts
CHANGED
|
@@ -60,13 +60,19 @@ export declare class FileSetupSkillInstaller implements SetupSkillInstaller {
|
|
|
60
60
|
* under the backup name.
|
|
61
61
|
*/
|
|
62
62
|
private discardBackup;
|
|
63
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* Deletes the transaction's trash entry by entry, so a crash leaves only a smaller partial tree.
|
|
65
|
+
* Callers must have proven the trash to be the complete replaced copy; a later recovery retains
|
|
66
|
+
* a partial tree instead of deleting it.
|
|
67
|
+
*/
|
|
64
68
|
private discardTrash;
|
|
65
69
|
/**
|
|
66
70
|
* Settles an interrupted update of the pending record's transaction, inside the ownership
|
|
67
71
|
* transaction. Only directories with this transaction's exact names are examined, and only
|
|
68
72
|
* verified ones are deleted: the backup must equal the replaced copy, the staging directory
|
|
69
|
-
* the target or an interrupted write of it
|
|
73
|
+
* the target or an interrupted write of it, the trash the complete replaced copy. Anything
|
|
74
|
+
* else stays and is reported, including a partial or changed trash.
|
|
75
|
+
* A proven trash is still deleted when a later intermediate (backup or staging) is retained.
|
|
70
76
|
*
|
|
71
77
|
* apply, slot absent: a complete staging directory is renamed into the slot (forward);
|
|
72
78
|
* otherwise a verified backup is moved back. remove and rollback do not restore, they delete
|
package/dist/setup-skills.js
CHANGED
|
@@ -162,8 +162,11 @@ export class FileSetupSkillInstaller {
|
|
|
162
162
|
return finding("skill_recovery_retained", "divergent");
|
|
163
163
|
recovered = outcome === "recovered";
|
|
164
164
|
}
|
|
165
|
-
// A dry-run never recovers; it announces the settlement apply would perform for a version 2 transaction.
|
|
165
|
+
// A dry-run never recovers; it announces the settlement apply would perform for a version 2 transaction, or its retention.
|
|
166
166
|
const recoveryPlanned = operation === "dry-run" && owned?.phase === "pending" && SKILL_TREE_FINGERPRINT_PATTERN.test(owned.fingerprint) && !hasLegacy(owned);
|
|
167
|
+
// The classification apply's recovery acts on, read-only: an intermediate apply would retain is not announced as planned.
|
|
168
|
+
if (recoveryPlanned && (await classifyIntermediates(path, name, owned, desired, linkTarget)).state === "retained")
|
|
169
|
+
return finding("skill_recovery_retained", "divergent");
|
|
167
170
|
const current = await inspectSlot(path, linkTarget);
|
|
168
171
|
if (current.state === "linked") {
|
|
169
172
|
if (operation === "apply" && owned) {
|
|
@@ -307,7 +310,11 @@ export class FileSetupSkillInstaller {
|
|
|
307
310
|
await this.discardTrash(names, parent, ownership);
|
|
308
311
|
await this.step("trash-deleted");
|
|
309
312
|
}
|
|
310
|
-
/**
|
|
313
|
+
/**
|
|
314
|
+
* Deletes the transaction's trash entry by entry, so a crash leaves only a smaller partial tree.
|
|
315
|
+
* Callers must have proven the trash to be the complete replaced copy; a later recovery retains
|
|
316
|
+
* a partial tree instead of deleting it.
|
|
317
|
+
*/
|
|
311
318
|
async discardTrash(names, parent, ownership) {
|
|
312
319
|
await removeEntries(names.trash, parent, ownership, () => this.step("trash-entry-removed"));
|
|
313
320
|
}
|
|
@@ -315,7 +322,9 @@ export class FileSetupSkillInstaller {
|
|
|
315
322
|
* Settles an interrupted update of the pending record's transaction, inside the ownership
|
|
316
323
|
* transaction. Only directories with this transaction's exact names are examined, and only
|
|
317
324
|
* verified ones are deleted: the backup must equal the replaced copy, the staging directory
|
|
318
|
-
* the target or an interrupted write of it
|
|
325
|
+
* the target or an interrupted write of it, the trash the complete replaced copy. Anything
|
|
326
|
+
* else stays and is reported, including a partial or changed trash.
|
|
327
|
+
* A proven trash is still deleted when a later intermediate (backup or staging) is retained.
|
|
319
328
|
*
|
|
320
329
|
* apply, slot absent: a complete staging directory is renamed into the slot (forward);
|
|
321
330
|
* otherwise a verified backup is moved back. remove and rollback do not restore, they delete
|
|
@@ -326,26 +335,20 @@ export class FileSetupSkillInstaller {
|
|
|
326
335
|
const names = intermediates(path, name, record.transactionId);
|
|
327
336
|
const target = record.fingerprint;
|
|
328
337
|
const previous = record.replaces !== undefined && SKILL_TREE_FINGERPRINT_PATTERN.test(record.replaces) ? record.replaces : undefined;
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
if (slot.state === "divergent" || (slot.state === "tree" && slot.fingerprint !== target && slot.fingerprint !== previous))
|
|
338
|
+
let found = await classifyIntermediates(path, name, record, library, linkTarget);
|
|
339
|
+
if (found.state === "untouched")
|
|
332
340
|
return "untouched";
|
|
333
|
-
// A trash directory with the transaction's name was verified before it was renamed, so on
|
|
334
|
-
// recovery it may be partial. It is deleted after a structural recheck: a real directory,
|
|
335
|
-
// not a link, only regular files and directories, no hidden entries, within the tree limits.
|
|
336
341
|
let changed = false;
|
|
337
|
-
if (
|
|
338
|
-
if (await collectPlain(names.trash) === undefined)
|
|
339
|
-
return "retained";
|
|
342
|
+
if (found.deleteTrash) {
|
|
340
343
|
await this.discardTrash(names, parent, ownership);
|
|
341
344
|
changed = true;
|
|
345
|
+
// Backup and staging are read again once the trash is gone; the slot stays the one first read.
|
|
346
|
+
found = await classifyIntermediates(path, name, record, library, linkTarget, found.slot);
|
|
342
347
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
let
|
|
347
|
-
if (staging === "foreign")
|
|
348
|
-
return "retained";
|
|
348
|
+
if (found.state !== "settle")
|
|
349
|
+
return found.state;
|
|
350
|
+
const { slot } = found;
|
|
351
|
+
let { backup, staging } = found;
|
|
349
352
|
if (operation === "apply" && slot.state === "absent") {
|
|
350
353
|
if (staging === "complete" || backup.state === "tree") {
|
|
351
354
|
const source = staging === "complete" ? names.staging : names.backup;
|
|
@@ -380,6 +383,41 @@ export class FileSetupSkillInstaller {
|
|
|
380
383
|
return changed ? "recovered" : "settled";
|
|
381
384
|
}
|
|
382
385
|
}
|
|
386
|
+
/**
|
|
387
|
+
* Reads a pending version 2 transaction's slot, trash, backup and staging directory, by their
|
|
388
|
+
* exact names, and never writes. The slot is only inspected when `known` is not given. Shared by
|
|
389
|
+
* recovery and the dry-run, so both apply the same rules.
|
|
390
|
+
*
|
|
391
|
+
* Only an empty slot, a link or one of setup's two copies can have intermediates settled. A trash
|
|
392
|
+
* is deletable only while it is the complete replaced copy; a partial or changed trash, a backup
|
|
393
|
+
* that is not the replaced copy and a foreign staging directory are retained.
|
|
394
|
+
*/
|
|
395
|
+
async function classifyIntermediates(path, name, record, library, linkTarget, known) {
|
|
396
|
+
const names = intermediates(path, name, record.transactionId);
|
|
397
|
+
const target = record.fingerprint;
|
|
398
|
+
const previous = record.replaces !== undefined && SKILL_TREE_FINGERPRINT_PATTERN.test(record.replaces) ? record.replaces : undefined;
|
|
399
|
+
const slot = known ?? await inspectSlot(path, linkTarget);
|
|
400
|
+
if (slot.state === "divergent" || (slot.state === "tree" && slot.fingerprint !== target && slot.fingerprint !== previous))
|
|
401
|
+
return { state: "untouched" };
|
|
402
|
+
// A trash directory with the transaction's name holds the verified replaced copy, but only
|
|
403
|
+
// while it is complete: it is deleted only when it still equals the record's replaced
|
|
404
|
+
// fingerprint. A partial trash (an interrupted deletion) or any changed content cannot be
|
|
405
|
+
// proven to be setup's own, so it is retained and reported for the operator to settle.
|
|
406
|
+
let deleteTrash = false;
|
|
407
|
+
if (await present(names.trash)) {
|
|
408
|
+
const trash = await inspectSlot(names.trash);
|
|
409
|
+
if (trash.state !== "tree" || previous === undefined || trash.fingerprint !== previous)
|
|
410
|
+
return { state: "retained", slot, deleteTrash };
|
|
411
|
+
deleteTrash = true;
|
|
412
|
+
}
|
|
413
|
+
const backup = await inspectSlot(names.backup);
|
|
414
|
+
if (backup.state === "divergent" || (backup.state === "tree" && backup.fingerprint !== previous))
|
|
415
|
+
return { state: "retained", slot, deleteTrash };
|
|
416
|
+
const staging = await classifyStaging(names.staging, target, library);
|
|
417
|
+
if (staging === "foreign")
|
|
418
|
+
return { state: "retained", slot, deleteTrash };
|
|
419
|
+
return { state: "settle", slot, deleteTrash, backup, staging };
|
|
420
|
+
}
|
|
383
421
|
function healthy(ownership) { ownership.assertHealthy?.(); }
|
|
384
422
|
/** The owned form of a record, without the replaced fingerprint of a finished update. */
|
|
385
423
|
function ownedRecord(record, fingerprint) {
|
package/dist/setup.js
CHANGED
|
@@ -211,11 +211,28 @@ export class SetupOrchestrator {
|
|
|
211
211
|
findings.push({ target, status: "failed", code: "release_binding_mismatch" });
|
|
212
212
|
continue;
|
|
213
213
|
}
|
|
214
|
-
|
|
214
|
+
if (this.dependencies.doctor) {
|
|
215
|
+
const doctor = await this.safe(() => this.dependencies.doctor.check());
|
|
216
|
+
if (!doctor?.ok) {
|
|
217
|
+
findings.push({ target, status: "failed", code: doctor?.code ?? "doctor_failed" });
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
215
221
|
const inspected = await this.safe(() => adapter.inspect(target, this.dependencies.intendedFingerprint));
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
222
|
+
if (!inspected) {
|
|
223
|
+
findings.push({ target, status: "failed", code: "inspect_failed" });
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (inspected.state === "unsupported") {
|
|
227
|
+
findings.push({ target, status: "unsupported", code: "unsupported" });
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (inspected.state !== "equivalent" || inspected.fingerprint !== this.dependencies.intendedFingerprint) {
|
|
231
|
+
findings.push({ target, status: "failed", code: "registration_not_equivalent" });
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
const verified = await this.safe(() => adapter.verify(target));
|
|
235
|
+
findings.push({ target, status: verified?.ok ? "ok" : "failed", code: verified?.code ?? "verify_failed" });
|
|
219
236
|
}
|
|
220
237
|
return result("verify", findings);
|
|
221
238
|
}
|