skill-family-harness-node 0.21.0 → 0.22.0
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 +20 -0
- package/CHANGELOG.zh-CN.md +20 -0
- package/README.md +51 -15
- package/README.zh-CN.md +50 -15
- package/package.json +2 -2
- package/release-notes/0.22.0.yaml +21 -0
- package/src/file-set-recovery.mjs +2391 -0
- package/src/index.mjs +6 -0
- package/src/state-store.mjs +348 -12
- package/src/version.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
<!-- release-skill:changelog:start version=0.22.0 locale=en baseline=sha256:704d9a47cc28bc1f7358e2817b266cbc83c27a5d3bded306299705a59f7bb05b -->
|
|
4
|
+
## [0.22.0] - 2026-09-18
|
|
5
|
+
|
|
6
|
+
Harness 0.22.0 adds the multi-path ordinary-file apply, recovery, and material-cleanup mechanism through three package-root exports, and gives the durable state store a bounded lock-inspection and lock-recovery extension.
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- Adds `applyFileSet`, `recoverFileSet`, and `pruneFileSetRecovery` as package-root exports of `skill-family-harness-node`, composing the existing strict single-file primitives, bound read, and durable state store.
|
|
11
|
+
- Adds `inspectStateStoreLock` and `recoverStateStoreLock` so a caller can observe lock state and repair an interrupted state-store operation without clearing the store's internal files.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Records whole-set preflight, inverse operations, strict synchronization, and per-path unknown facts while keeping caller-owned domain validation read-only.
|
|
16
|
+
|
|
17
|
+
### Upgrade Notes
|
|
18
|
+
|
|
19
|
+
Pin all three Foundation packages to exactly 0.22.0. Callers must stop old participants and establish an external exclusive maintenance window before recovery; domain verdicts, business plans, and cleanup authorization remain caller responsibilities. The mechanism does not add a second logging or locking algorithm, directory operations, or a platform guarantee beyond darwin/arm64 APFS.
|
|
20
|
+
<!-- release-skill:changelog:end version=0.22.0 locale=en -->
|
|
21
|
+
|
|
22
|
+
|
|
3
23
|
<!-- release-skill:changelog:start version=0.21.0 locale=en baseline=sha256:18c4e6ddfd3e1ac0b4ac4848620854e250b653b1f95fe47920625ac40629df84 -->
|
|
4
24
|
## [0.21.0] - 2026-09-11
|
|
5
25
|
|
package/CHANGELOG.zh-CN.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# 变更日志
|
|
2
2
|
|
|
3
|
+
<!-- release-skill:changelog:start version=0.22.0 locale=zh-CN baseline=sha256:c06c001717b465cbff9ff239961fc3c873a2c0d39e09594ed885b84323da5bb5 -->
|
|
4
|
+
## [0.22.0] - 2026-09-18
|
|
5
|
+
|
|
6
|
+
Harness 0.22.0 通过三个包根导出增加多路径普通文件的应用、恢复与材料清理机制,并给持久状态底座增加有界的锁观察与锁恢复扩展。
|
|
7
|
+
|
|
8
|
+
### 新增
|
|
9
|
+
|
|
10
|
+
- 增加 `applyFileSet`、`recoverFileSet`、`pruneFileSetRecovery` 三个 `skill-family-harness-node` 包根导出,组合既有的严格单文件原语、绑定读取和持久状态底座。
|
|
11
|
+
- 增加 `inspectStateStoreLock` 与 `recoverStateStoreLock`,调用方可以观察锁状态并修复被中断的状态底座操作,而不清理其内部文件。
|
|
12
|
+
|
|
13
|
+
### 变更
|
|
14
|
+
|
|
15
|
+
- 记录整组前检、逆操作、严格同步和逐路径未知事实,同时保持调用方持有的领域验证只读。
|
|
16
|
+
|
|
17
|
+
### 升级说明
|
|
18
|
+
|
|
19
|
+
三个 Foundation 包须一起精确锁定到 0.22.0。恢复前调用方必须停止旧参与者并建立外部排他维护区间;领域判定、业务计划和清理授权仍由调用方负责。本机制不新增第二套日志或锁算法、不扩大为目录操作,也不在 darwin/arm64 APFS 之外承诺平台资格。
|
|
20
|
+
<!-- release-skill:changelog:end version=0.22.0 locale=zh-CN -->
|
|
21
|
+
|
|
22
|
+
|
|
3
23
|
<!-- release-skill:changelog:start version=0.21.0 locale=zh-CN baseline=sha256:9e19e64d15bb9c04d91b66876cf2e7666e4af8ad909bdddfefe88852669ef147 -->
|
|
4
24
|
## [0.21.0] - 2026-09-11
|
|
5
25
|
|
package/README.md
CHANGED
|
@@ -4,22 +4,27 @@
|
|
|
4
4
|
|
|
5
5
|
# skill-family-harness-node
|
|
6
6
|
|
|
7
|
-
<!-- release-skill:release-version: 0.
|
|
7
|
+
<!-- release-skill:release-version: 0.22.0 -->
|
|
8
8
|
|
|
9
9
|
The **single default Node implementation** of the Contracts mechanism protocol. This is a thin runtime: it only implements the mechanism protocol, introduces no business semantics, and does not provide a second-language implementation.
|
|
10
10
|
|
|
11
11
|
<!-- release-skill:managed:start id=latest-release -->
|
|
12
|
-
**0.
|
|
12
|
+
**0.22.0** (2026-09-18)
|
|
13
13
|
|
|
14
|
-
Harness 0.
|
|
14
|
+
Harness 0.22.0 adds the multi-path ordinary-file apply, recovery, and material-cleanup mechanism through three package-root exports, and gives the durable state store a bounded lock-inspection and lock-recovery extension.
|
|
15
|
+
|
|
16
|
+
**Added**
|
|
17
|
+
|
|
18
|
+
- Adds `applyFileSet`, `recoverFileSet`, and `pruneFileSetRecovery` as package-root exports of `skill-family-harness-node`, composing the existing strict single-file primitives, bound read, and durable state store.
|
|
19
|
+
- Adds `inspectStateStoreLock` and `recoverStateStoreLock` so a caller can observe lock state and repair an interrupted state-store operation without clearing the store's internal files.
|
|
15
20
|
|
|
16
21
|
**Changed**
|
|
17
22
|
|
|
18
|
-
-
|
|
23
|
+
- Records whole-set preflight, inverse operations, strict synchronization, and per-path unknown facts while keeping caller-owned domain validation read-only.
|
|
19
24
|
|
|
20
25
|
**Upgrade Notes**
|
|
21
26
|
|
|
22
|
-
Pin all three Foundation packages to exactly 0.
|
|
27
|
+
Pin all three Foundation packages to exactly 0.22.0. Callers must stop old participants and establish an external exclusive maintenance window before recovery; domain verdicts, business plans, and cleanup authorization remain caller responsibilities. The mechanism does not add a second logging or locking algorithm, directory operations, or a platform guarantee beyond darwin/arm64 APFS.
|
|
23
28
|
<!-- release-skill:managed:end id=latest-release -->
|
|
24
29
|
|
|
25
30
|
## Problem It Solves
|
|
@@ -32,7 +37,7 @@ The Harness consumes `skill-family-contracts` (a workspace dependency), reusing
|
|
|
32
37
|
|
|
33
38
|
## Installation and Minimal Example
|
|
34
39
|
|
|
35
|
-
Version 0.
|
|
40
|
+
Version 0.22.0 is the local source candidate. Build all three tarballs into one temporary directory and install those exact files for a candidate check:
|
|
36
41
|
|
|
37
42
|
```sh
|
|
38
43
|
pack_dir="$(mktemp -d)"
|
|
@@ -40,13 +45,13 @@ pack_dir="$(mktemp -d)"
|
|
|
40
45
|
(cd packages/skill-family-harness-node && pnpm pack --pack-destination "$pack_dir")
|
|
41
46
|
(cd packages/skill-family-engineering-kit && pnpm pack --pack-destination "$pack_dir")
|
|
42
47
|
mkdir "$pack_dir/consumer" && (cd "$pack_dir/consumer" && npm init -y)
|
|
43
|
-
(cd "$pack_dir/consumer" && npm install "$pack_dir/skill-family-contracts-0.
|
|
48
|
+
(cd "$pack_dir/consumer" && npm install "$pack_dir/skill-family-contracts-0.22.0.tgz" "$pack_dir/skill-family-harness-node-0.22.0.tgz" "$pack_dir/skill-family-engineering-kit-0.22.0.tgz")
|
|
44
49
|
```
|
|
45
50
|
|
|
46
51
|
After publication, use the registry coordinate:
|
|
47
52
|
|
|
48
53
|
```sh
|
|
49
|
-
npm install skill-family-harness-node@0.
|
|
54
|
+
npm install skill-family-harness-node@0.22.0
|
|
50
55
|
npm info skill-family-harness-node --help
|
|
51
56
|
```
|
|
52
57
|
|
|
@@ -95,6 +100,7 @@ The capability remains **candidate**. Pin all three Foundation packages exactly
|
|
|
95
100
|
- Need to normalize resources into a recomputable closure or generate a digest: use resource closure.
|
|
96
101
|
- Need to generate a human report from a machine result: use report model/render/binding/check.
|
|
97
102
|
- Need to persist an event log with derived snapshots: use state-store (event meaning is owned by the caller).
|
|
103
|
+
- Need to apply one ordered group of ordinary files across scattered paths with restart recovery and explicit pruning: use the file-set apply/recovery entries.
|
|
98
104
|
|
|
99
105
|
## Boundaries
|
|
100
106
|
|
|
@@ -125,7 +131,8 @@ The capability remains **candidate**. Pin all three Foundation packages exactly
|
|
|
125
131
|
| `probeVersionVector` | A version-probe mechanism that disables spawn by default; when explicitly enabled, executes only absolute, symlink-free, audited vectors, using no PATH/shell. |
|
|
126
132
|
| `openStateStore` / `appendEvent` / `readEvents` / `verifyStateStore` / `closeStateStore` | Strict single-writer append-only event store; the event directory is the sole state authority, `chain-head.json` is only a cache. |
|
|
127
133
|
| `readSnapshot` / `writeSnapshot` / `rebuildSnapshot` | Atomic derived snapshots and full-event rebuild; a bad event cannot be masked by an old snapshot, and a bad snapshot can be ignored by rebuild. |
|
|
128
|
-
| `inspectStateStoreLock` / `recoverStateStoreLock` | Read-only lock diagnostics and explicit recovery; recovery
|
|
134
|
+
| `inspectStateStoreLock` / `recoverStateStoreLock` | Read-only lock diagnostics and explicit recovery; recovery has **two mutually exclusive takeover modes** — the legacy mode precisely matches the observed owner + fencing, while the maintenance mode requires a complete observation of that root plus both explicit confirmations; mixing fields of the two modes (including legacy fields explicitly present as `undefined`) is rejected. |
|
|
135
|
+
| `applyFileSet` / `recoverFileSet` / `pruneFileSetRecovery` | One ordered create/replace/delete group over scattered ordinary files under a bound root, restart recovery of an uncommitted operation through an explicit public entry point, and exact pruning of a terminal operation's materials. Cooperative-exclusion precondition, per-path intent facts, no instantaneous multi-file visibility. |
|
|
129
136
|
|
|
130
137
|
## Replacing an Existing Fixed Set
|
|
131
138
|
|
|
@@ -136,13 +143,38 @@ The operation is not idempotent: a second call with the same paths exchanges the
|
|
|
136
143
|
## State Store Lock and Recovery Boundaries
|
|
137
144
|
|
|
138
145
|
- The lock uses exclusive create; a second writer immediately receives `store-locked`; it does not queue, nor steals the lock by time, PID, or lease expiry.
|
|
139
|
-
- `inspectStateStoreLock` creates no file
|
|
140
|
-
- A crash-left lock can only be recovered by the caller, after confirming outside Foundation that the old writer has terminated
|
|
146
|
+
- `inspectStateStoreLock` creates no file. By default it returns `owner`, monotonic `fencing`, `ageMs`, and an in-recovery flag, where `ageMs` is for diagnostics only and never participates in correctness decisions; only an explicit `{ recoveryObservation: true }` returns the complete `state-store-recovery-observation` object instead (the maintenance mode needs it). A default diagnostic result is not a valid observation.
|
|
147
|
+
- A crash-left lock can only be recovered by the caller, after confirming outside Foundation that the old writer has terminated. `recoverStateStoreLock` has two **mutually exclusive** modes; pick exactly one. Both modes require `payloadSchemas` (the same eventType → version → JSON Schema registry as a normal open, with at least one entry — the new writer handle returned by recovery uses it to validate later event payloads); `newOwner` and `clock` are optional:
|
|
148
|
+
- Legacy mode: `recoverStateStoreLock(root, { expectedOwner, expectedFencing, confirmOwnerTerminated: true, payloadSchemas })`, where owner and fencing must precisely match the currently observed values; a mismatch, a missing confirmation, or a missing `payloadSchemas` fails closed.
|
|
149
|
+
- Maintenance mode: `recoverStateStoreLock(root, { observation, confirmAllParticipantsStopped: true, confirmExclusiveMaintenance: true, payloadSchemas })`. `observation` must be the complete observation obtained by calling `inspectStateStoreLock(root, { recoveryObservation: true })` on the same root; it covers a missing writer and partially written control files, not just owner/fencing.
|
|
150
|
+
Fields of the two modes must not be mixed: a legacy field that is explicitly present with the value `undefined` still counts as mixing and is rejected, so maintenance-mode options must omit legacy fields entirely.
|
|
151
|
+
- The maintenance mode's two confirmations are external trust preconditions, not a lock implemented by the boolean fields: `confirmAllParticipantsStopped` states that the old writer, old recoverers, and their child processes have stopped, and `confirmExclusiveMaintenance` states that the exclusive maintenance interval still holds. The interval starts **before** the observation is taken and ends when this call obtains a new writer handle or returns a failure; during it, other recoveries, normal opens, state-store writes, and related business writes are forbidden. PID, age, or owner/fencing comparisons cannot replace that precondition; once the handle is obtained the normal writer contract applies again.
|
|
152
|
+
|
|
153
|
+
```js
|
|
154
|
+
// Maintenance takeover: take the complete observation inside the external maintenance interval,
|
|
155
|
+
// then submit maintenance-mode fields only.
|
|
156
|
+
const observation = await inspectStateStoreLock(stateStoreRoot, { recoveryObservation: true });
|
|
157
|
+
const store = await recoverStateStoreLock(stateStoreRoot, {
|
|
158
|
+
observation,
|
|
159
|
+
confirmAllParticipantsStopped: true,
|
|
160
|
+
confirmExclusiveMaintenance: true,
|
|
161
|
+
payloadSchemas, // required: the returned writer handle validates later event payloads with it
|
|
162
|
+
});
|
|
163
|
+
```
|
|
141
164
|
- Recovery produces a larger fencing. The old handle re-checks owner, fencing, and acquisition id on every append; final event publication uses a same-directory temporary regular file, fsync, and exclusive link, never overwriting an existing sequence.
|
|
142
165
|
- Append, snapshot, close, and recovery are serialized by a short-lived `writer-mutation.lock`; recovery cannot cross an authoritative write that already holds the mutation guard.
|
|
143
|
-
- If the recovering process itself crashes while holding `writer-recovery.lock`, the system stays in a diagnosable deadlock state and
|
|
166
|
+
- If the recovering process itself crashes while holding `writer-recovery.lock`, the system stays in a diagnosable deadlock state and the normal paths never auto-delete that guard: only a freshly established exclusive maintenance interval, a fresh observation, and the maintenance mode reorganize the reclaimable event temporary alias and the three control residues `writer.lock`, `writer-mutation.lock`, and `writer-recovery.lock`. Control records or fencing counters in unknown formats are refused rather than guessed or zeroed, and unknown files are never deleted. The current API does not claim to solve the scenario where an untrusted caller falsely reports "old writer terminated".
|
|
144
167
|
- The state root, `events/`, `snapshots/`, events, and snapshots reject symlinks, hard links, FIFOs, devices, and other non-regular entries. Payload must be pure JSON, and `eventType + payloadSchemaVersion` must hit the Schema pair frozen by the caller at open/recover.
|
|
145
168
|
|
|
169
|
+
## Multi-Path File-Set Apply and Recovery Boundaries
|
|
170
|
+
|
|
171
|
+
- `applyFileSet(request, { validate })` applies one ordered create/replace/delete group over scattered ordinary files under a bound root. `validate` is a caller-supplied read-only function; recovery and pruning use the public entries `recoverFileSet(request)` and `pruneFileSetRecovery(request)` rather than a private path.
|
|
172
|
+
- Recovery materials live under the fixed in-root `.foundation-file-apply/` (`journal/` plus `operations/<id>/{before,after}/<index>`). They are retained by default; only an explicit prune removes the exact materials of a terminal operation and keeps the journal. Materials of unfinished or conflicting operations are never cleaned, and unknown adjacent staging files are never deleted by suffix — they are only reported as `possible-unknown`.
|
|
173
|
+
- Preflight (whole-group rejection, unsupported environment, capacity breach) returns before any business write, leaving zero business writes. No business path is touched before a durable `apply-intent` fact exists for that path.
|
|
174
|
+
- Domain validation that returns false, throws, times out, or returns an invalid result triggers automatic recovery, with validation state reported separately from recovery state. A valid commit event is never rolled back; if target re-verification or durability stays unconfirmed the outcome is `commit-unconfirmed` and the materials are retained.
|
|
175
|
+
- The lock is cooperative-exclusion only: recovery requires the caller to have established an exclusive maintenance interval outside Foundation and to submit the complete `maintenance` observation of that interval (obtained by calling `inspectStateStoreLock(journalRoot, { recoveryObservation: true })` on the fixed in-root `.foundation-file-apply/journal`, whose path must equal `observation.root`; a default diagnostic result cannot be used for a fault takeover); prune takes the normal writer when nothing is left over and requires that same `maintenance` observation only for a fault takeover. Instantaneous multi-file visibility is not promised, and uncooperative concurrent writers are not resisted.
|
|
176
|
+
- Catchable failures return a `file-set-result`; the outer error code stays `SFC2004` with closed `details.kind` and `details.phase` enums, and the result's `outcome`, `paths`, and `materials` fields report per-path facts. A failed lock release is reported through `errors` rather than raised.
|
|
177
|
+
|
|
146
178
|
## Stable Error Codes
|
|
147
179
|
|
|
148
180
|
All reuse the Contracts frozen registry; no unregistered codes are added. Mechanism failures are uniformly `SFC2004` (EXECUTION_FAILED), and `details.kind` takes a stable value from `HARNESS_ERROR_KINDS`, such as `path-traversal`, `symlink-escape`, `realpath-escape`, `atomic-write-failed`, `missing-resource`, `workspace-disposed`.
|
|
@@ -162,7 +194,7 @@ Comparison is based on the canonical root after `realpath`, avoiding misjudgment
|
|
|
162
194
|
|
|
163
195
|
## Testing
|
|
164
196
|
|
|
165
|
-
`node --test` covers: full Contracts fixture replay, security negative cases, atomic-failure paths, temporary workspaces, closure determinism, raw sink delayed-stream and failure paths, report fact binding and Markdown injection, host manifest/path/command trust, and state-store crashes, concurrency, corruption, fencing, explicit recovery, symlinks, hard links, and FIFO negative cases.
|
|
197
|
+
`node --test` covers: full Contracts fixture replay, security negative cases, atomic-failure paths, temporary workspaces, closure determinism, raw sink delayed-stream and failure paths, report fact binding and Markdown injection, host manifest/path/command trust, and state-store crashes, concurrency, corruption, fencing, explicit recovery, symlinks, hard links, and FIFO negative cases. The file-set apply/recovery family adds crash-restart recovery, validation-failure auto-recovery, prune, and conflict-retention negative cases.
|
|
166
198
|
|
|
167
199
|
## Troubleshooting
|
|
168
200
|
|
|
@@ -201,6 +233,7 @@ Mechanism failures uniformly throw `SFC2004` (EXECUTION_FAILED), with `details.k
|
|
|
201
233
|
- `foundation.harness.report`: report-model validation/render/binding/check.
|
|
202
234
|
- `foundation.harness.host-adapter`: adapter source closure/build/materialize, version probe, and read-only peer adapter verification.
|
|
203
235
|
- `foundation.harness.state-store`: append-only events, hash chain, snapshots, and lock recovery.
|
|
236
|
+
- `foundation.harness.file-set-recovery`: ordered multi-path create/replace/delete apply over scattered ordinary files, restart recovery of an uncommitted operation, and exact pruning of terminal materials.
|
|
204
237
|
- `foundation.harness.errors`: mechanism error types and stable error classes.
|
|
205
238
|
- `foundation.harness.quickstart-profile-candidate`: exact-version observation/task/result construction and binding verification.
|
|
206
239
|
|
|
@@ -208,11 +241,12 @@ Mechanism failures uniformly throw `SFC2004` (EXECUTION_FAILED), with `details.k
|
|
|
208
241
|
|
|
209
242
|
- The contained root directory (the boundary for path containment).
|
|
210
243
|
- The document, resource, or event payload to validate/write.
|
|
244
|
+
- For a multi-path file set: the ordered operation group bound to one root and environment plus the caller's read-only `validate` function for apply, and the complete `maintenance` observation for recover or a fault-takeover prune.
|
|
211
245
|
|
|
212
246
|
### Outputs and evidence
|
|
213
247
|
|
|
214
248
|
- Validation result, contained absolute path, atomically written file, closure digest, terminal result, report text, events/snapshots.
|
|
215
|
-
- Evidence: `packages/skill-family-harness-node/test/validation.test.mjs`, `atomic.test.mjs`, `containment.test.mjs`, `closure.test.mjs`, `report.test.mjs`, `state-store.test.mjs`.
|
|
249
|
+
- Evidence: `packages/skill-family-harness-node/test/validation.test.mjs`, `atomic.test.mjs`, `containment.test.mjs`, `closure.test.mjs`, `report.test.mjs`, `state-store.test.mjs`, `file-set-recovery.test.mjs`, `file-set-recovery-crash.test.mjs`.
|
|
216
250
|
|
|
217
251
|
### Side effects
|
|
218
252
|
|
|
@@ -223,10 +257,12 @@ Mechanism failures uniformly throw `SFC2004` (EXECUTION_FAILED), with `details.k
|
|
|
223
257
|
|
|
224
258
|
- Mechanism failures are uniformly `SFC2004`, with `details.kind` as a stable subcategory (e.g., `path-traversal`, `atomic-write-failed`).
|
|
225
259
|
- Residual state after failure: atomic write rolls back the temp file; a broken state-store hash chain throws, and old snapshots can be ignored by rebuild.
|
|
260
|
+
- File-set residual state after failure: the recovery materials stay under the fixed in-root `.foundation-file-apply`, a validation failure triggers automatic recovery with validation and recovery state reported separately, and an unconfirmed commit reports `commit-unconfirmed` with the materials retained.
|
|
226
261
|
|
|
227
262
|
### Architectural invariants
|
|
228
263
|
|
|
229
264
|
- Event meaning and reducer transitions remain consumer-owned; state-store only provides the base.
|
|
265
|
+
- The file-set protocol adds no second event log, lock, sequence, or digest chain: it reuses the existing publication, atomic-replace, bound-read, and digest mechanisms, requires exactly one cooperative writer per bound root at any time, and never cleans the state-store's internal files.
|
|
230
266
|
- Only text adapter source (utf8) is supported; binary projection is not supported.
|
|
231
267
|
- `verifyPeerAdapterDirectories` enumerates and reads two or more real peer roots, reuses bound-read/path containment/closure/manifest primitives, and fails closed on symlinks, escapes, byte drift, member drift, or incomplete mappings.
|
|
232
268
|
|
|
@@ -255,4 +291,4 @@ When the actual threat includes malicious concurrency, return a minimal upstream
|
|
|
255
291
|
|
|
256
292
|
The separate candidate `observeExecutableIdentity({ boundRoots, lookup, interpreterPolicy? })` provides a read-only point-in-time observation of only the caller-explicit roots and lookup paths, for an immediate re-observation before launch. When an `/usr/bin/env` shebang resolves an interpreter through explicit `pathEntries`, the observation preserves the interpreter candidate's complete symlink chain rather than collapsing it to the final file. It is not part of `host-adapter` and does not prove wrapper control flow, ambient `PATH`, fd-exec/kernel image, signature trust, cross-call caching, host support/lifecycle, or domain acceptance; the caller owns those semantics. The candidate entry alone does not qualify a host.
|
|
257
293
|
|
|
258
|
-
Version 0.
|
|
294
|
+
Version 0.22.0 is the local source candidate. Remote availability must be established by the corresponding release-skill post-release evidence. Consume the three locally verified tarballs for candidate checks; a version marker, unit test, or successful install is not complete contract integration, migration completion, or real-host qualification.
|
package/README.zh-CN.md
CHANGED
|
@@ -5,22 +5,27 @@
|
|
|
5
5
|
|
|
6
6
|
# skill-family-harness-node
|
|
7
7
|
|
|
8
|
-
<!-- release-skill:release-version: 0.
|
|
8
|
+
<!-- release-skill:release-version: 0.22.0 -->
|
|
9
9
|
|
|
10
10
|
Contracts 机制协议的**唯一默认 Node 实现**。这是一个薄运行时(thin runtime):只实现机制协议,不引入业务语义,不做第二语言实现。
|
|
11
11
|
|
|
12
12
|
<!-- release-skill:managed:start id=latest-release -->
|
|
13
|
-
**0.
|
|
13
|
+
**0.22.0** (2026-09-18)
|
|
14
14
|
|
|
15
|
-
Harness 0.
|
|
15
|
+
Harness 0.22.0 通过三个包根导出增加多路径普通文件的应用、恢复与材料清理机制,并给持久状态底座增加有界的锁观察与锁恢复扩展。
|
|
16
|
+
|
|
17
|
+
**新增**
|
|
18
|
+
|
|
19
|
+
- 增加 `applyFileSet`、`recoverFileSet`、`pruneFileSetRecovery` 三个 `skill-family-harness-node` 包根导出,组合既有的严格单文件原语、绑定读取和持久状态底座。
|
|
20
|
+
- 增加 `inspectStateStoreLock` 与 `recoverStateStoreLock`,调用方可以观察锁状态并修复被中断的状态底座操作,而不清理其内部文件。
|
|
16
21
|
|
|
17
22
|
**变更**
|
|
18
23
|
|
|
19
|
-
-
|
|
24
|
+
- 记录整组前检、逆操作、严格同步和逐路径未知事实,同时保持调用方持有的领域验证只读。
|
|
20
25
|
|
|
21
26
|
**升级说明**
|
|
22
27
|
|
|
23
|
-
三个 Foundation 包须一起精确锁定到 0.
|
|
28
|
+
三个 Foundation 包须一起精确锁定到 0.22.0。恢复前调用方必须停止旧参与者并建立外部排他维护区间;领域判定、业务计划和清理授权仍由调用方负责。本机制不新增第二套日志或锁算法、不扩大为目录操作,也不在 darwin/arm64 APFS 之外承诺平台资格。
|
|
24
29
|
<!-- release-skill:managed:end id=latest-release -->
|
|
25
30
|
|
|
26
31
|
## 解决的问题
|
|
@@ -33,7 +38,7 @@ Harness 消费 `skill-family-contracts`(工作区依赖),复用其方言
|
|
|
33
38
|
|
|
34
39
|
## 安装和最小示例
|
|
35
40
|
|
|
36
|
-
0.
|
|
41
|
+
0.22.0 是本地源码候选。候选验证先把三个包分别打入同一个临时目录,再安装这三个精确 tarball:
|
|
37
42
|
|
|
38
43
|
```sh
|
|
39
44
|
pack_dir="$(mktemp -d)"
|
|
@@ -41,13 +46,13 @@ pack_dir="$(mktemp -d)"
|
|
|
41
46
|
(cd packages/skill-family-harness-node && pnpm pack --pack-destination "$pack_dir")
|
|
42
47
|
(cd packages/skill-family-engineering-kit && pnpm pack --pack-destination "$pack_dir")
|
|
43
48
|
mkdir "$pack_dir/consumer" && (cd "$pack_dir/consumer" && npm init -y)
|
|
44
|
-
(cd "$pack_dir/consumer" && npm install "$pack_dir/skill-family-contracts-0.
|
|
49
|
+
(cd "$pack_dir/consumer" && npm install "$pack_dir/skill-family-contracts-0.22.0.tgz" "$pack_dir/skill-family-harness-node-0.22.0.tgz" "$pack_dir/skill-family-engineering-kit-0.22.0.tgz")
|
|
45
50
|
```
|
|
46
51
|
|
|
47
52
|
发布后再使用 registry 坐标:
|
|
48
53
|
|
|
49
54
|
```sh
|
|
50
|
-
npm install skill-family-harness-node@0.
|
|
55
|
+
npm install skill-family-harness-node@0.22.0
|
|
51
56
|
npm info skill-family-harness-node --help
|
|
52
57
|
```
|
|
53
58
|
|
|
@@ -96,6 +101,7 @@ v2 机制会重算每个 path-backed output 和 evidence Resource 的真实字
|
|
|
96
101
|
- 需要把资源归一成可复算闭包或生成摘要:用 resource closure。
|
|
97
102
|
- 需要从机器结果生成人类报告:用 report model/render/binding/check。
|
|
98
103
|
- 需要持久化事件日志与派生快照:用 state-store(事件含义由调用方拥有)。
|
|
104
|
+
- 需要在分散路径上应用一组有序普通文件,并支持重启恢复与显式清理:用文件集合 apply/recovery 入口。
|
|
99
105
|
|
|
100
106
|
## 边界
|
|
101
107
|
|
|
@@ -126,7 +132,8 @@ v2 机制会重算每个 path-backed output 和 evidence Resource 的真实字
|
|
|
126
132
|
| `probeVersionVector` | 默认禁用 spawn 的版本探测机制;显式启用时只执行绝对、无 symlink 的受审计向量,不使用 PATH/shell。 |
|
|
127
133
|
| `openStateStore` / `appendEvent` / `readEvents` / `verifyStateStore` / `closeStateStore` | 严格单写者的 append-only 事件存储;事件目录是唯一状态权威,`chain-head.json` 只是缓存。 |
|
|
128
134
|
| `readSnapshot` / `writeSnapshot` / `rebuildSnapshot` | 原子派生快照与完整事件重建;坏事件不能被旧快照掩盖,坏快照可被重建忽略。 |
|
|
129
|
-
| `inspectStateStoreLock` / `recoverStateStoreLock` |
|
|
135
|
+
| `inspectStateStoreLock` / `recoverStateStoreLock` | 只读锁诊断与显式恢复;恢复有**两种互斥接管模式**——旧模式精确匹配观测到的 owner + fencing,维护模式要求该 root 的完整观察加两项显式确认;混用两模式字段(含显式写成 `undefined` 的旧模式字段)被拒绝。 |
|
|
136
|
+
| `applyFileSet` / `recoverFileSet` / `pruneFileSetRecovery` | 在绑定根下对分散普通文件执行一组有序 create/replace/delete,通过显式公共入口重启恢复未提交操作,并精确清理已终结操作的材料。前提是合作式排他,只给出逐路径意图事实,不承诺瞬时多文件可见性。 |
|
|
130
137
|
|
|
131
138
|
## 替换既有固定集合
|
|
132
139
|
|
|
@@ -137,13 +144,37 @@ v2 机制会重算每个 path-backed output 和 evidence Resource 的真实字
|
|
|
137
144
|
## 状态存储的锁与恢复边界
|
|
138
145
|
|
|
139
146
|
- 锁使用 exclusive create,第二写者立即收到 `store-locked`;不排队,也不按时间、PID 或租约过期偷锁。
|
|
140
|
-
- `inspectStateStoreLock`
|
|
141
|
-
- 崩溃遗留锁只能由调用方在 Foundation
|
|
147
|
+
- `inspectStateStoreLock` 不创建任何文件。默认返回 `owner`、单调 `fencing`、`ageMs` 和恢复中标记,`ageMs` 仅供诊断、从不参与正确性判断;只有显式传入 `{ recoveryObservation: true }` 时才改为返回完整的 `state-store-recovery-observation` 观察对象(维护模式需要它),默认诊断结果不是有效观察。
|
|
148
|
+
- 崩溃遗留锁只能由调用方在 Foundation 之外确认旧写者已经终止后接管。`recoverStateStoreLock` 有两种**互斥**模式,只能取其一;两种模式都必须提供 `payloadSchemas`:与正常打开相同的 eventType→版本→JSON Schema 注册表,至少一个条目。恢复返回的新写者句柄用它校验后续事件负载。`newOwner` 与 `clock` 可选:
|
|
149
|
+
- 旧模式:`recoverStateStoreLock(root, { expectedOwner, expectedFencing, confirmOwnerTerminated: true, payloadSchemas })`,owner 与 fencing 必须精确匹配当前观测值;不匹配、缺少确认或缺少 `payloadSchemas` 均失败关闭。
|
|
150
|
+
- 维护模式:`recoverStateStoreLock(root, { observation, confirmAllParticipantsStopped: true, confirmExclusiveMaintenance: true, payloadSchemas })`。`observation` 必须是对同一 root 调用 `inspectStateStoreLock(root, { recoveryObservation: true })` 取得的完整观察;它覆盖 writer 缺失与部分写入的控制文件,不只核对 owner/fencing。
|
|
151
|
+
两种模式的字段不得混用:显式写出但值为 `undefined` 的旧模式字段同样计入混用并被拒绝,构造维护模式 options 时必须整体省略旧模式字段。
|
|
152
|
+
- 维护模式的两项确认是外部信任前提,不是布尔字段自动实现的锁:`confirmAllParticipantsStopped` 声明旧写者、旧恢复者及其子进程均已停止,`confirmExclusiveMaintenance` 声明排他维护区间仍然成立。维护区间从取得观察**之前**开始,到本次调用取得新写者句柄或失败返回结束;区间内禁止其他恢复、正常打开、状态存储写入与相关业务写入。PID、年龄或 owner/fencing 比较都不能替代该前提;取得句柄后由正常写者合同继续保护。
|
|
153
|
+
|
|
154
|
+
```js
|
|
155
|
+
// 维护接管:先在外部维护区间内取得完整观察,再只提交维护模式字段。
|
|
156
|
+
const observation = await inspectStateStoreLock(stateStoreRoot, { recoveryObservation: true });
|
|
157
|
+
const store = await recoverStateStoreLock(stateStoreRoot, {
|
|
158
|
+
observation,
|
|
159
|
+
confirmAllParticipantsStopped: true,
|
|
160
|
+
confirmExclusiveMaintenance: true,
|
|
161
|
+
payloadSchemas, // 必填:恢复返回的新写者句柄用它校验后续事件负载
|
|
162
|
+
});
|
|
163
|
+
```
|
|
142
164
|
- 恢复产生更大的 fencing。旧 handle 每次 append 都重新核对 owner、fencing 和 acquisition id;事件最终发布使用同目录临时普通文件、fsync 和 exclusive link,绝不覆盖既有 sequence。
|
|
143
165
|
- append、snapshot、close 与 recovery 由短期 `writer-mutation.lock` 串行化;恢复不能越过已经持有 mutation guard 的权威写入。
|
|
144
|
-
- 如果恢复进程自身在持有 `writer-recovery.lock`
|
|
166
|
+
- 如果恢复进程自身在持有 `writer-recovery.lock` 时崩溃,系统保持可诊断的锁死状态,普通路径不自动删除该 guard:只有重新建立排他维护区间、重新观察后经维护模式,才整理可处置的事件临时别名与 `writer.lock`、`writer-mutation.lock`、`writer-recovery.lock` 三种控制残留。未知格式的控制记录或 fencing 计数器拒绝接管,不猜测、不归零,未知文件不删除。当前 API 不声称解决不可信调用方谎报“旧写者已终止”的场景。
|
|
145
167
|
- state root、`events/`、`snapshots/`、事件和快照拒绝 symlink、硬链接、FIFO、设备与其它非普通条目。payload 必须是纯 JSON,且 `eventType + payloadSchemaVersion` 必须命中调用方在 open/recover 时冻结的 Schema 对。
|
|
146
168
|
|
|
169
|
+
## 多路径文件集合的应用与恢复边界
|
|
170
|
+
|
|
171
|
+
- `applyFileSet(request, { validate })` 在绑定根下对分散普通文件执行一组有序 create/replace/delete;`validate` 是调用方提供的只读函数。恢复与清理走公共入口 `recoverFileSet(request)` 与 `pruneFileSetRecovery(request)`,不依赖私有路径。
|
|
172
|
+
- 恢复材料位于根内固定 `.foundation-file-apply/`(`journal/` 与 `operations/<id>/{before,after}/<index>`),默认保留;只有显式 prune 才会清理某个已终结操作的精确材料并保留 journal。未终结与冲突操作的材料不清理,未知邻接暂存文件不按后缀删除,只报告 `possible-unknown`。
|
|
173
|
+
- 整组前检(整体拒绝、环境不支持、容量越限)在任何业务写入前返回,业务零写入;未为某路径写入持久 `apply-intent` 事实前不触碰该业务路径。
|
|
174
|
+
- 领域验证返回 false、抛错、超时或返回非法结果会触发自动恢复,验证状态与恢复状态分开报告。有效提交事件决定不再回滚;目标复核或持久化仍未确认时结果报告 `commit-unconfirmed` 并保留材料。
|
|
175
|
+
- 锁只是合作式排他:恢复要求调用方先在 Foundation 之外建立排他维护区间,并提交该区间的完整 `maintenance` 观察(对根内固定 `.foundation-file-apply/journal` 调用 `inspectStateStoreLock(journalRoot, { recoveryObservation: true })` 取得,`observation.root` 必须是该 journal 目录;默认诊断结果不能用于故障接管);清理在无残留时直接取得正常 writer,只在需要故障接管时才要求同一 `maintenance` 观察。不承诺瞬时多文件可见性,也不抵御不合作的并发写者。
|
|
176
|
+
- 可捕获失败返回 `file-set-result`;外码沿用 `SFC2004`,`details.kind` 与 `details.phase` 为闭枚举,结果中的 `outcome`、`paths`、`materials` 给出逐路径事实。锁释放失败通过 `errors` 报告而不是抛出。
|
|
177
|
+
|
|
147
178
|
## 稳定错误码
|
|
148
179
|
|
|
149
180
|
全部复用 Contracts 冻结登记表,不新增未登记码。机制失败统一为 `SFC2004`(EXECUTION_FAILED),`details.kind` 取 `HARNESS_ERROR_KINDS` 中的稳定值,例如 `path-traversal`、`symlink-escape`、`realpath-escape`、`atomic-write-failed`、`missing-resource`、`workspace-disposed`。
|
|
@@ -163,7 +194,7 @@ v2 机制会重算每个 path-backed output 和 evidence Resource 的真实字
|
|
|
163
194
|
|
|
164
195
|
## 测试
|
|
165
196
|
|
|
166
|
-
`node --test` 覆盖:Contracts fixture 全量回放、安全反例、原子性失败路径、临时工作区、闭包确定性、报告事实绑定与 Markdown 注入、宿主 manifest/路径/命令信任,以及状态存储的崩溃、并发、损坏、fencing、显式恢复、symlink、硬链接与 FIFO
|
|
197
|
+
`node --test` 覆盖:Contracts fixture 全量回放、安全反例、原子性失败路径、临时工作区、闭包确定性、报告事实绑定与 Markdown 注入、宿主 manifest/路径/命令信任,以及状态存储的崩溃、并发、损坏、fencing、显式恢复、symlink、硬链接与 FIFO 反例。文件集合 apply/recovery 另覆盖崩溃重启恢复、验证失败自动恢复、prune 与冲突保留反例。
|
|
167
198
|
|
|
168
199
|
## 故障诊断
|
|
169
200
|
|
|
@@ -202,6 +233,7 @@ v2 机制会重算每个 path-backed output 和 evidence Resource 的真实字
|
|
|
202
233
|
- `foundation.harness.report`:report-model 校验/渲染/绑定/检查。
|
|
203
234
|
- `foundation.harness.host-adapter`:adapter source closure/build/materialize 与版本探测。
|
|
204
235
|
- `foundation.harness.state-store`:append-only 事件、hash chain、快照与锁恢复。
|
|
236
|
+
- `foundation.harness.file-set-recovery`:在分散普通文件上执行有序多路径 create/replace/delete,重启恢复未提交操作,并精确清理已终结材料。
|
|
205
237
|
- `foundation.harness.errors`:机制错误类型与稳定错误类。
|
|
206
238
|
- `foundation.harness.quickstart-profile-candidate`:锁定精确版本后构造 observation/task/result 并复验绑定。
|
|
207
239
|
|
|
@@ -209,11 +241,12 @@ v2 机制会重算每个 path-backed output 和 evidence Resource 的真实字
|
|
|
209
241
|
|
|
210
242
|
- 受收容根目录(路径收容的边界)。
|
|
211
243
|
- 待校验/待写入的文档、资源或事件负载。
|
|
244
|
+
- 多路径文件集合:绑定同一根与 environment 的有序操作组,apply 另需调用方提供的只读 `validate` 函数;recover 或故障接管式 prune 另需完整的 `maintenance` 观察。
|
|
212
245
|
|
|
213
246
|
### Outputs and evidence
|
|
214
247
|
|
|
215
248
|
- 校验结果、受收容绝对路径、原子写文件、闭包摘要、终态结果、报告文本、事件/快照。
|
|
216
|
-
- 证据:`packages/skill-family-harness-node/test/validation.test.mjs`、`atomic.test.mjs`、`containment.test.mjs`、`closure.test.mjs`、`report.test.mjs`、`state-store.test.mjs`。
|
|
249
|
+
- 证据:`packages/skill-family-harness-node/test/validation.test.mjs`、`atomic.test.mjs`、`containment.test.mjs`、`closure.test.mjs`、`report.test.mjs`、`state-store.test.mjs`、`file-set-recovery.test.mjs`、`file-set-recovery-crash.test.mjs`。
|
|
217
250
|
|
|
218
251
|
### Side effects
|
|
219
252
|
|
|
@@ -224,10 +257,12 @@ v2 机制会重算每个 path-backed output 和 evidence Resource 的真实字
|
|
|
224
257
|
|
|
225
258
|
- 机制失败统一 `SFC2004`,`details.kind` 为稳定细分(如 `path-traversal`、`atomic-write-failed`)。
|
|
226
259
|
- 失败后残余状态:原子写回滚临时文件;状态存储链断裂抛错,旧快照可被重建忽略。
|
|
260
|
+
- 文件集合失败后的残余状态:恢复材料留在根内固定 `.foundation-file-apply` 下;验证失败触发自动恢复,验证状态与恢复状态分开报告;提交未确认时报告 `commit-unconfirmed` 并保留材料。
|
|
227
261
|
|
|
228
262
|
### Architectural invariants
|
|
229
263
|
|
|
230
264
|
- Event meaning and reducer transitions remain consumer-owned;state-store 只提供底座。
|
|
265
|
+
- 文件集合协议不新增第二套事件日志、锁、序列或摘要链:它复用既有发布、原子替换、绑定读取与摘要机制,同一绑定根在任一时刻只允许一个合作式写者,也从不清理状态存储的内部文件。
|
|
231
266
|
- 仅支持文本 adapter source(utf8),不支持二进制投影。
|
|
232
267
|
|
|
233
268
|
### Route elsewhere when
|
|
@@ -255,4 +290,4 @@ v2 机制会重算每个 path-backed output 和 evidence Resource 的真实字
|
|
|
255
290
|
|
|
256
291
|
另一个独立候选 `observeExecutableIdentity({ boundRoots, lookup, interpreterPolicy? })` 只对调用方显式提供的根和查找路径做逐次只读观察,供正式启动前紧邻重观察。`/usr/bin/env` shebang 通过显式 `pathEntries` 找到解释器时,结果保留解释器候选的完整 symlink chain,不折叠成最终文件。它不属于 `host-adapter`,也不证明 wrapper 控制流、ambient `PATH`、fd-exec/内核映像、签名信任、跨调用缓存、宿主支持/生命周期或领域接受;这些语义仍由调用方负责。候选入口存在不等于宿主已获资格。
|
|
257
292
|
|
|
258
|
-
0.
|
|
293
|
+
0.22.0 是本地源码候选,远端可用性须由对应的 release-skill 发布后证据证明。候选检查使用本地已验证的三包 tarball;版本标记、单元测试或安装成功都不等于契约接入完成、迁移完成或真实宿主资格。
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@pnpm/lockfile.fs": "1001.1.35",
|
|
12
12
|
"ipaddr.js": "2.5.0",
|
|
13
|
-
"skill-family-contracts": "0.
|
|
13
|
+
"skill-family-contracts": "0.22.0",
|
|
14
14
|
"yaml": "2.9.0"
|
|
15
15
|
},
|
|
16
16
|
"description": "Thin Node.js mechanism runtime for Skill Family engineering contracts.",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"url": "https://github.com/ifoohoo/skill-family-harness-node.git"
|
|
50
50
|
},
|
|
51
51
|
"type": "module",
|
|
52
|
-
"version": "0.
|
|
52
|
+
"version": "0.22.0",
|
|
53
53
|
"scripts": {
|
|
54
54
|
"check": "node --test",
|
|
55
55
|
"test": "node --test"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
version: 0.22.0
|
|
2
|
+
date: 2026-09-18
|
|
3
|
+
locales:
|
|
4
|
+
en:
|
|
5
|
+
summary: Harness 0.22.0 adds the multi-path ordinary-file apply, recovery, and material-cleanup mechanism through three package-root exports, and gives the durable state store a bounded lock-inspection and lock-recovery extension.
|
|
6
|
+
changes:
|
|
7
|
+
added:
|
|
8
|
+
- Adds `applyFileSet`, `recoverFileSet`, and `pruneFileSetRecovery` as package-root exports of `skill-family-harness-node`, composing the existing strict single-file primitives, bound read, and durable state store.
|
|
9
|
+
- Adds `inspectStateStoreLock` and `recoverStateStoreLock` so a caller can observe lock state and repair an interrupted state-store operation without clearing the store's internal files.
|
|
10
|
+
changed:
|
|
11
|
+
- Records whole-set preflight, inverse operations, strict synchronization, and per-path unknown facts while keeping caller-owned domain validation read-only.
|
|
12
|
+
upgradeNotes: Pin all three Foundation packages to exactly 0.22.0. Callers must stop old participants and establish an external exclusive maintenance window before recovery; domain verdicts, business plans, and cleanup authorization remain caller responsibilities. The mechanism does not add a second logging or locking algorithm, directory operations, or a platform guarantee beyond darwin/arm64 APFS.
|
|
13
|
+
zh-CN:
|
|
14
|
+
summary: Harness 0.22.0 通过三个包根导出增加多路径普通文件的应用、恢复与材料清理机制,并给持久状态底座增加有界的锁观察与锁恢复扩展。
|
|
15
|
+
changes:
|
|
16
|
+
added:
|
|
17
|
+
- 增加 `applyFileSet`、`recoverFileSet`、`pruneFileSetRecovery` 三个 `skill-family-harness-node` 包根导出,组合既有的严格单文件原语、绑定读取和持久状态底座。
|
|
18
|
+
- 增加 `inspectStateStoreLock` 与 `recoverStateStoreLock`,调用方可以观察锁状态并修复被中断的状态底座操作,而不清理其内部文件。
|
|
19
|
+
changed:
|
|
20
|
+
- 记录整组前检、逆操作、严格同步和逐路径未知事实,同时保持调用方持有的领域验证只读。
|
|
21
|
+
upgradeNotes: 三个 Foundation 包须一起精确锁定到 0.22.0。恢复前调用方必须停止旧参与者并建立外部排他维护区间;领域判定、业务计划和清理授权仍由调用方负责。本机制不新增第二套日志或锁算法、不扩大为目录操作,也不在 darwin/arm64 APFS 之外承诺平台资格。
|