skill-family-harness-node 0.16.0 → 0.18.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 +35 -0
- package/CHANGELOG.zh-CN.md +35 -0
- package/README.md +21 -10
- package/README.zh-CN.md +21 -10
- package/package.json +2 -2
- package/release-notes/0.17.0.yaml +15 -0
- package/release-notes/0.18.0.yaml +21 -0
- package/src/fixed-set-publication-loader.mjs +3 -1
- package/src/fixed-set-publication.mjs +282 -4
- package/src/index.mjs +1 -1
- package/src/native/assemble-stable-prebuild.mjs +1 -1
- package/src/native/bound_read.c +108 -12
- package/src/native/loader.mjs +13 -8
- package/src/native/prebuild-manifest.json +8 -4
- package/src/native/prebuild-release-receipt.json +10 -10
- package/src/native/prebuild-sbom.json +5 -5
- package/src/native/prebuilds/darwin-arm64/bound_read.darwin-arm64.node +0 -0
- package/src/native/prebuilds/darwin-x64/bound_read.darwin-x64.node +0 -0
- package/src/native/prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node +0 -0
- package/src/native/prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node +0 -0
- package/src/version.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
<!-- release-skill:changelog:start version=0.18.0 locale=en baseline=sha256:ca3679574ea53013c830563ae818326062794aec1f486e6953e89fa8937be644 -->
|
|
4
|
+
## [0.18.0] - 2026-09-05
|
|
5
|
+
|
|
6
|
+
Harness 0.18.0 adds the stable replaceFixedSetAtomic operation for replacing one existing fixed-set directory with one complete staged sibling.
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- Adds replaceFixedSetAtomic to the package root and fixed-set-publication subpath. Source and target must be real sibling directories under the same canonical parent.
|
|
11
|
+
- Uses one Darwin RENAME_SWAP or Linux RENAME_EXCHANGE commit. On success, the complete new set occupies the target and the displaced old target remains at sourceRoot.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Reports pre-commit, post-commit, publication, verification, commit, and durability state through the existing SFC2004 mechanism-error surface when replacement cannot return a verified success.
|
|
16
|
+
|
|
17
|
+
### Upgrade Notes
|
|
18
|
+
|
|
19
|
+
Pin all three Foundation packages to exactly 0.18.0. replaceFixedSetAtomic is not idempotent: calling it again with the same paths exchanges the directories back. Never retry blindly after success or a post-commit or indeterminate error. The caller owns cleanup of the displaced target after a verified success.
|
|
20
|
+
<!-- release-skill:changelog:end version=0.18.0 locale=en -->
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
<!-- release-skill:changelog:start version=0.17.0 locale=en baseline=sha256:5c685956ff1920dccdd91198687dbc1414bfef474a0d297bbcb7704014c549bd -->
|
|
24
|
+
## [0.17.0] - 2026-09-01
|
|
25
|
+
|
|
26
|
+
Harness 0.17.0 is a lockstep version update with no new mechanism, native source change, or public API.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- Moves the package version to 0.17.0 together with Contracts and Engineering Kit while preserving the existing Harness capability and native prebuild surfaces.
|
|
31
|
+
|
|
32
|
+
### Upgrade Notes
|
|
33
|
+
|
|
34
|
+
Pin all three Foundation packages to exactly 0.17.0. Engineering-baseline validation and structural comparison belong to Contracts and Engineering Kit; no Harness migration is required.
|
|
35
|
+
<!-- release-skill:changelog:end version=0.17.0 locale=en -->
|
|
36
|
+
|
|
37
|
+
|
|
3
38
|
<!-- release-skill:changelog:start version=0.16.0 locale=en baseline=sha256:27fd0f5623d5c93b3076af93231101956233f179dd0cab3d1c74dae891668ea8 -->
|
|
4
39
|
## [0.16.0] - 2026-08-31
|
|
5
40
|
|
package/CHANGELOG.zh-CN.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# 变更日志
|
|
2
2
|
|
|
3
|
+
<!-- release-skill:changelog:start version=0.18.0 locale=zh-CN baseline=sha256:ba40c3c901c8f717b18d131344fe94a470c149ddb1d0f96e5611d38bf1c70d3d -->
|
|
4
|
+
## [0.18.0] - 2026-09-05
|
|
5
|
+
|
|
6
|
+
Harness 0.18.0 新增稳定的 replaceFixedSetAtomic,用一个完整暂存目录替换一个既有固定集合目录。
|
|
7
|
+
|
|
8
|
+
### 新增
|
|
9
|
+
|
|
10
|
+
- 包根与 fixed-set-publication 子路径新增 replaceFixedSetAtomic。源目录和目标目录必须是同一规范父目录下的真实兄弟目录。
|
|
11
|
+
- 提交只执行一次 Darwin RENAME_SWAP 或 Linux RENAME_EXCHANGE。成功后,新集合完整位于目标路径,被置换的旧目标留在 sourceRoot。
|
|
12
|
+
|
|
13
|
+
### 变更
|
|
14
|
+
|
|
15
|
+
- 替换无法返回已验证成功时,沿用 SFC2004 机制错误表面,报告提交前后、发布、验证、提交和持久化状态。
|
|
16
|
+
|
|
17
|
+
### 升级说明
|
|
18
|
+
|
|
19
|
+
三个 Foundation 包须一起精确锁定到 0.18.0。replaceFixedSetAtomic 不是幂等操作:相同路径再次调用会把两个目录交换回去。成功后,或收到提交后及不确定错误时,均不得盲目重试。调用方只在确认成功后负责清理被置换的旧目标。
|
|
20
|
+
<!-- release-skill:changelog:end version=0.18.0 locale=zh-CN -->
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
<!-- release-skill:changelog:start version=0.17.0 locale=zh-CN baseline=sha256:fc1c5f48463b2d62363952f566011600a8a84b11456519507c8dc03bf58756d5 -->
|
|
24
|
+
## [0.17.0] - 2026-09-01
|
|
25
|
+
|
|
26
|
+
Harness 0.17.0 随 Foundation 锁步升版,不新增机制,不修改原生源码或公共 API。
|
|
27
|
+
|
|
28
|
+
### 变更
|
|
29
|
+
|
|
30
|
+
- 包版本与 Contracts、Engineering Kit 一同升至 0.17.0,既有 Harness 能力和原生预构建表面保持不变。
|
|
31
|
+
|
|
32
|
+
### 升级说明
|
|
33
|
+
|
|
34
|
+
三个 Foundation 包须一起精确锁定到 0.17.0。工程基线校验与结构比较分别归 Contracts 和 Engineering Kit 负责,无需迁移 Harness API。
|
|
35
|
+
<!-- release-skill:changelog:end version=0.17.0 locale=zh-CN -->
|
|
36
|
+
|
|
37
|
+
|
|
3
38
|
<!-- release-skill:changelog:start version=0.16.0 locale=zh-CN baseline=sha256:31d28348db231171b1ea5754c514d7fe3cc336c1063026af8763d92553d225de -->
|
|
4
39
|
## [0.16.0] - 2026-08-31
|
|
5
40
|
|
package/README.md
CHANGED
|
@@ -4,23 +4,27 @@
|
|
|
4
4
|
|
|
5
5
|
# skill-family-harness-node
|
|
6
6
|
|
|
7
|
-
<!-- release-skill:release-version: 0.
|
|
7
|
+
<!-- release-skill:release-version: 0.18.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.18.0** (2026-09-05)
|
|
13
13
|
|
|
14
|
-
Harness 0.
|
|
14
|
+
Harness 0.18.0 adds the stable replaceFixedSetAtomic operation for replacing one existing fixed-set directory with one complete staged sibling.
|
|
15
|
+
|
|
16
|
+
**Added**
|
|
17
|
+
|
|
18
|
+
- Adds replaceFixedSetAtomic to the package root and fixed-set-publication subpath. Source and target must be real sibling directories under the same canonical parent.
|
|
19
|
+
- Uses one Darwin RENAME_SWAP or Linux RENAME_EXCHANGE commit. On success, the complete new set occupies the target and the displaced old target remains at sourceRoot.
|
|
15
20
|
|
|
16
21
|
**Changed**
|
|
17
22
|
|
|
18
|
-
-
|
|
19
|
-
- Explicitly rejects rawStreamSink, transform sinks, generic sanitizers, second runners, and new stdout callbacks for a single-consumer redaction request.
|
|
23
|
+
- Reports pre-commit, post-commit, publication, verification, commit, and durability state through the existing SFC2004 mechanism-error surface when replacement cannot return a verified success.
|
|
20
24
|
|
|
21
25
|
**Upgrade Notes**
|
|
22
26
|
|
|
23
|
-
|
|
27
|
+
Pin all three Foundation packages to exactly 0.18.0. replaceFixedSetAtomic is not idempotent: calling it again with the same paths exchanges the directories back. Never retry blindly after success or a post-commit or indeterminate error. The caller owns cleanup of the displaced target after a verified success.
|
|
24
28
|
<!-- release-skill:managed:end id=latest-release -->
|
|
25
29
|
|
|
26
30
|
## Problem It Solves
|
|
@@ -33,7 +37,7 @@ The Harness consumes `skill-family-contracts` (a workspace dependency), reusing
|
|
|
33
37
|
|
|
34
38
|
## Installation and Minimal Example
|
|
35
39
|
|
|
36
|
-
Version 0.
|
|
40
|
+
Version 0.18.0 is a local candidate. Build all three tarballs into one temporary directory and install those exact files for a candidate check:
|
|
37
41
|
|
|
38
42
|
```sh
|
|
39
43
|
pack_dir="$(mktemp -d)"
|
|
@@ -41,13 +45,13 @@ pack_dir="$(mktemp -d)"
|
|
|
41
45
|
(cd packages/skill-family-harness-node && pnpm pack --pack-destination "$pack_dir")
|
|
42
46
|
(cd packages/skill-family-engineering-kit && pnpm pack --pack-destination "$pack_dir")
|
|
43
47
|
mkdir "$pack_dir/consumer" && (cd "$pack_dir/consumer" && npm init -y)
|
|
44
|
-
(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.18.0.tgz" "$pack_dir/skill-family-harness-node-0.18.0.tgz" "$pack_dir/skill-family-engineering-kit-0.18.0.tgz")
|
|
45
49
|
```
|
|
46
50
|
|
|
47
51
|
After publication, use the registry coordinate:
|
|
48
52
|
|
|
49
53
|
```sh
|
|
50
|
-
npm install skill-family-harness-node@0.
|
|
54
|
+
npm install skill-family-harness-node@0.18.0
|
|
51
55
|
npm info skill-family-harness-node --help
|
|
52
56
|
```
|
|
53
57
|
|
|
@@ -119,6 +123,7 @@ The capability remains **candidate**. Pin all three Foundation packages exactly
|
|
|
119
123
|
| `superviseProcess` / `validateTimeoutPolicy` | The single bounded subprocess supervisor. Its 0.11.0 `rawSink` option writes raw stdout/stderr bytes only to a fresh canonical private root, then waits for child/stream close, queued writes, fsync, and handle close. The caller must exclusively control the sink namespace for the entire call; handle protection does not prove stable pathname/root identity. |
|
|
120
124
|
| `observeFilesystemTree` | Observe a complete bound tree. Default/reject preserves the existing UTF-16 member order; record uses code-point order and records symlink target bytes without following them. |
|
|
121
125
|
| `observeExecutableIdentity` | Observe a caller-bound executable, its symlink chain, launch bytes, and any script interpreter chain for immediate pre-spawn re-observation. |
|
|
126
|
+
| `createFixedSetPublicationManifest` / `publishFixedSet` / `replaceFixedSetAtomic` | Publish a complete fixed set without replacement, or atomically exchange a complete staged sibling with one existing target. |
|
|
122
127
|
| `parseRequest` / `processRequest` | Parse `operation-request`, output terminal `operation-result`. |
|
|
123
128
|
| `validateReportModel` / `renderReportMarkdown` / `buildBinding` / `checkReport` | Consume a Contracts-validated report model, deterministically render neutral Markdown, and verify source/result/report binding; does not interpret business output. |
|
|
124
129
|
| `normalizeAdapterSource` / `buildAdapterClosure` / `verifyAdapterBuildManifest` / `materializeAdapterBuild` | Generic text-source closure, full-manifest digest re-verification, and atomic landing of the target set; specific Profile/driver is not in the Harness. |
|
|
@@ -127,6 +132,12 @@ The capability remains **candidate**. Pin all three Foundation packages exactly
|
|
|
127
132
|
| `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
133
|
| `inspectStateStoreLock` / `recoverStateStoreLock` | Read-only lock diagnostics and explicit recovery; recovery must precisely match the observed owner + fencing. |
|
|
129
134
|
|
|
135
|
+
## Replacing an Existing Fixed Set
|
|
136
|
+
|
|
137
|
+
`replaceFixedSetAtomic({ sourceRoot, targetParent, targetSegment })` requires `sourceRoot` and the existing target to be real sibling directories under the same canonical parent. It verifies both complete trees, then commits one Darwin `RENAME_SWAP` or Linux `RENAME_EXCHANGE`. A verified success leaves the new set at the target and the displaced old target at `sourceRoot`; Foundation does not delete either directory.
|
|
138
|
+
|
|
139
|
+
The operation is not idempotent: a second call with the same paths exchanges the directories back. Do not retry blindly after a verified success or an error whose state is post-commit or indeterminate. Mechanism failures use `SFC2004` with `details.kind: "atomic-replace-failed"` and include the phase, publication, commit, verification, and durability state needed for caller-controlled recovery.
|
|
140
|
+
|
|
130
141
|
## State Store Lock and Recovery Boundaries
|
|
131
142
|
|
|
132
143
|
- 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.
|
|
@@ -249,4 +260,4 @@ When the actual threat includes malicious concurrency, return a minimal upstream
|
|
|
249
260
|
|
|
250
261
|
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.
|
|
251
262
|
|
|
252
|
-
Version 0.
|
|
263
|
+
Version 0.18.0 is a local source candidate and is not published. Consume the three locally verified tarballs; 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,23 +5,27 @@
|
|
|
5
5
|
|
|
6
6
|
# skill-family-harness-node
|
|
7
7
|
|
|
8
|
-
<!-- release-skill:release-version: 0.
|
|
8
|
+
<!-- release-skill:release-version: 0.18.0 -->
|
|
9
9
|
|
|
10
10
|
Contracts 机制协议的**唯一默认 Node 实现**。这是一个薄运行时(thin runtime):只实现机制协议,不引入业务语义,不做第二语言实现。
|
|
11
11
|
|
|
12
12
|
<!-- release-skill:managed:start id=latest-release -->
|
|
13
|
-
**0.
|
|
13
|
+
**0.18.0** (2026-09-05)
|
|
14
14
|
|
|
15
|
-
Harness 0.
|
|
15
|
+
Harness 0.18.0 新增稳定的 replaceFixedSetAtomic,用一个完整暂存目录替换一个既有固定集合目录。
|
|
16
|
+
|
|
17
|
+
**新增**
|
|
18
|
+
|
|
19
|
+
- 包根与 fixed-set-publication 子路径新增 replaceFixedSetAtomic。源目录和目标目录必须是同一规范父目录下的真实兄弟目录。
|
|
20
|
+
- 提交只执行一次 Darwin RENAME_SWAP 或 Linux RENAME_EXCHANGE。成功后,新集合完整位于目标路径,被置换的旧目标留在 sourceRoot。
|
|
16
21
|
|
|
17
22
|
**变更**
|
|
18
23
|
|
|
19
|
-
-
|
|
20
|
-
- 明确拒绝为单一消费者脱敏需求新增 rawStreamSink、transform sink、通用 sanitizer、第二个 runner 或新的 stdout 回调。
|
|
24
|
+
- 替换无法返回已验证成功时,沿用 SFC2004 机制错误表面,报告提交前后、发布、验证、提交和持久化状态。
|
|
21
25
|
|
|
22
26
|
**升级说明**
|
|
23
27
|
|
|
24
|
-
|
|
28
|
+
三个 Foundation 包须一起精确锁定到 0.18.0。replaceFixedSetAtomic 不是幂等操作:相同路径再次调用会把两个目录交换回去。成功后,或收到提交后及不确定错误时,均不得盲目重试。调用方只在确认成功后负责清理被置换的旧目标。
|
|
25
29
|
<!-- release-skill:managed:end id=latest-release -->
|
|
26
30
|
|
|
27
31
|
## 解决的问题
|
|
@@ -34,7 +38,7 @@ Harness 消费 `skill-family-contracts`(工作区依赖),复用其方言
|
|
|
34
38
|
|
|
35
39
|
## 安装和最小示例
|
|
36
40
|
|
|
37
|
-
0.
|
|
41
|
+
0.18.0 是本地候选版本。候选验证先把三个包分别打入同一个临时目录,再安装这三个精确 tarball:
|
|
38
42
|
|
|
39
43
|
```sh
|
|
40
44
|
pack_dir="$(mktemp -d)"
|
|
@@ -42,13 +46,13 @@ pack_dir="$(mktemp -d)"
|
|
|
42
46
|
(cd packages/skill-family-harness-node && pnpm pack --pack-destination "$pack_dir")
|
|
43
47
|
(cd packages/skill-family-engineering-kit && pnpm pack --pack-destination "$pack_dir")
|
|
44
48
|
mkdir "$pack_dir/consumer" && (cd "$pack_dir/consumer" && npm init -y)
|
|
45
|
-
(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.18.0.tgz" "$pack_dir/skill-family-harness-node-0.18.0.tgz" "$pack_dir/skill-family-engineering-kit-0.18.0.tgz")
|
|
46
50
|
```
|
|
47
51
|
|
|
48
52
|
发布后再使用 registry 坐标:
|
|
49
53
|
|
|
50
54
|
```sh
|
|
51
|
-
npm install skill-family-harness-node@0.
|
|
55
|
+
npm install skill-family-harness-node@0.18.0
|
|
52
56
|
npm info skill-family-harness-node --help
|
|
53
57
|
```
|
|
54
58
|
|
|
@@ -120,6 +124,7 @@ v2 机制会重算每个 path-backed output 和 evidence Resource 的真实字
|
|
|
120
124
|
| `superviseProcess` / `validateTimeoutPolicy` | 唯一的受约束子进程监督器。0.11.0 的 `rawSink` 只向 fresh canonical 私有根写原始 stdout/stderr 字节,并等待子进程、流、排队写入、fsync 与句柄全部关闭。调用方必须在整个调用期间独占 sink 命名空间;句柄保护不证明 pathname 或根目录身份始终不变。 |
|
|
121
125
|
| `observeFilesystemTree` | 观察完整绑定树。默认/reject 保留既有 UTF-16 成员顺序;record 使用码点顺序,记录 symlink target bytes 且不跟随目标。 |
|
|
122
126
|
| `observeExecutableIdentity` | 观察调用方绑定的可执行入口、符号链接链、启动字节和脚本解释器链,供每次 spawn 前紧邻重观察。 |
|
|
127
|
+
| `createFixedSetPublicationManifest` / `publishFixedSet` / `replaceFixedSetAtomic` | 发布不替换的完整固定集合,或把完整暂存兄弟目录与一个既有目标原子交换。 |
|
|
123
128
|
| `parseRequest` / `processRequest` | 解析 `operation-request`,输出终态 `operation-result`。 |
|
|
124
129
|
| `validateReportModel` / `renderReportMarkdown` / `buildBinding` / `checkReport` | 消费经 Contracts 验证的 report model,确定性渲染中性 Markdown 并校验来源/结果/报告绑定;不解释业务输出。 |
|
|
125
130
|
| `normalizeAdapterSource` / `buildAdapterClosure` / `verifyAdapterBuildManifest` / `materializeAdapterBuild` | 通用文本 source closure、manifest 全摘要复验和目标集合原子落盘;具体 Profile/driver 不在 Harness。 |
|
|
@@ -128,6 +133,12 @@ v2 机制会重算每个 path-backed output 和 evidence Resource 的真实字
|
|
|
128
133
|
| `readSnapshot` / `writeSnapshot` / `rebuildSnapshot` | 原子派生快照与完整事件重建;坏事件不能被旧快照掩盖,坏快照可被重建忽略。 |
|
|
129
134
|
| `inspectStateStoreLock` / `recoverStateStoreLock` | 只读锁诊断与显式恢复;恢复必须对观测到的 owner + fencing 做精确匹配。 |
|
|
130
135
|
|
|
136
|
+
## 替换既有固定集合
|
|
137
|
+
|
|
138
|
+
`replaceFixedSetAtomic({ sourceRoot, targetParent, targetSegment })` 要求 `sourceRoot` 与既有目标是同一规范父目录下的真实兄弟目录。它先复验两棵完整目录树,再执行一次 Darwin `RENAME_SWAP` 或 Linux `RENAME_EXCHANGE`。确认成功后,新集合位于目标路径,被置换的旧目标留在 `sourceRoot`;Foundation 不删除任一目录。
|
|
139
|
+
|
|
140
|
+
该操作不是幂等操作:相同路径再次调用会把两个目录交换回去。确认成功后,或错误状态为提交后及不确定时,不得盲目重试。机制失败使用 `SFC2004` 与 `details.kind: "atomic-replace-failed"`,并给出提交阶段、发布、提交、验证和持久化状态,供调用方自行恢复。
|
|
141
|
+
|
|
131
142
|
## 状态存储的锁与恢复边界
|
|
132
143
|
|
|
133
144
|
- 锁使用 exclusive create,第二写者立即收到 `store-locked`;不排队,也不按时间、PID 或租约过期偷锁。
|
|
@@ -249,4 +260,4 @@ v2 机制会重算每个 path-backed output 和 evidence Resource 的真实字
|
|
|
249
260
|
|
|
250
261
|
另一个独立候选 `observeExecutableIdentity({ boundRoots, lookup, interpreterPolicy? })` 只对调用方显式提供的根和查找路径做逐次只读观察,供正式启动前紧邻重观察。`/usr/bin/env` shebang 通过显式 `pathEntries` 找到解释器时,结果保留解释器候选的完整 symlink chain,不折叠成最终文件。它不属于 `host-adapter`,也不证明 wrapper 控制流、ambient `PATH`、fd-exec/内核映像、签名信任、跨调用缓存、宿主支持/生命周期或领域接受;这些语义仍由调用方负责。候选入口存在不等于宿主已获资格。
|
|
251
262
|
|
|
252
|
-
0.
|
|
263
|
+
0.18.0 为本地源码候选,尚未发布。消费本地已验证的三包 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.18.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.18.0",
|
|
53
53
|
"scripts": {
|
|
54
54
|
"check": "node --test",
|
|
55
55
|
"test": "node --test"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
version: 0.17.0
|
|
2
|
+
date: 2026-09-01
|
|
3
|
+
locales:
|
|
4
|
+
en:
|
|
5
|
+
summary: Harness 0.17.0 is a lockstep version update with no new mechanism, native source change, or public API.
|
|
6
|
+
changes:
|
|
7
|
+
changed:
|
|
8
|
+
- Moves the package version to 0.17.0 together with Contracts and Engineering Kit while preserving the existing Harness capability and native prebuild surfaces.
|
|
9
|
+
upgradeNotes: Pin all three Foundation packages to exactly 0.17.0. Engineering-baseline validation and structural comparison belong to Contracts and Engineering Kit; no Harness migration is required.
|
|
10
|
+
zh-CN:
|
|
11
|
+
summary: Harness 0.17.0 随 Foundation 锁步升版,不新增机制,不修改原生源码或公共 API。
|
|
12
|
+
changes:
|
|
13
|
+
changed:
|
|
14
|
+
- 包版本与 Contracts、Engineering Kit 一同升至 0.17.0,既有 Harness 能力和原生预构建表面保持不变。
|
|
15
|
+
upgradeNotes: 三个 Foundation 包须一起精确锁定到 0.17.0。工程基线校验与结构比较分别归 Contracts 和 Engineering Kit 负责,无需迁移 Harness API。
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
version: 0.18.0
|
|
2
|
+
date: 2026-09-05
|
|
3
|
+
locales:
|
|
4
|
+
en:
|
|
5
|
+
summary: Harness 0.18.0 adds the stable replaceFixedSetAtomic operation for replacing one existing fixed-set directory with one complete staged sibling.
|
|
6
|
+
changes:
|
|
7
|
+
added:
|
|
8
|
+
- Adds replaceFixedSetAtomic to the package root and fixed-set-publication subpath. Source and target must be real sibling directories under the same canonical parent.
|
|
9
|
+
- Uses one Darwin RENAME_SWAP or Linux RENAME_EXCHANGE commit. On success, the complete new set occupies the target and the displaced old target remains at sourceRoot.
|
|
10
|
+
changed:
|
|
11
|
+
- Reports pre-commit, post-commit, publication, verification, commit, and durability state through the existing SFC2004 mechanism-error surface when replacement cannot return a verified success.
|
|
12
|
+
upgradeNotes: "Pin all three Foundation packages to exactly 0.18.0. replaceFixedSetAtomic is not idempotent: calling it again with the same paths exchanges the directories back. Never retry blindly after success or a post-commit or indeterminate error. The caller owns cleanup of the displaced target after a verified success."
|
|
13
|
+
zh-CN:
|
|
14
|
+
summary: Harness 0.18.0 新增稳定的 replaceFixedSetAtomic,用一个完整暂存目录替换一个既有固定集合目录。
|
|
15
|
+
changes:
|
|
16
|
+
added:
|
|
17
|
+
- 包根与 fixed-set-publication 子路径新增 replaceFixedSetAtomic。源目录和目标目录必须是同一规范父目录下的真实兄弟目录。
|
|
18
|
+
- 提交只执行一次 Darwin RENAME_SWAP 或 Linux RENAME_EXCHANGE。成功后,新集合完整位于目标路径,被置换的旧目标留在 sourceRoot。
|
|
19
|
+
changed:
|
|
20
|
+
- 替换无法返回已验证成功时,沿用 SFC2004 机制错误表面,报告提交前后、发布、验证、提交和持久化状态。
|
|
21
|
+
upgradeNotes: 三个 Foundation 包须一起精确锁定到 0.18.0。replaceFixedSetAtomic 不是幂等操作:相同路径再次调用会把两个目录交换回去。成功后,或收到提交后及不确定错误时,均不得盲目重试。调用方只在确认成功后负责清理被置换的旧目标。
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { loadNativeBoundReadAddon } from "./native/loader.mjs";
|
|
1
|
+
import { loadNativeBoundReadAddon, stableNativePlatformKey } from "./native/loader.mjs";
|
|
2
2
|
|
|
3
3
|
export async function loadNativeAddon() {
|
|
4
4
|
return loadNativeBoundReadAddon();
|
|
5
5
|
}
|
|
6
|
+
|
|
7
|
+
export { stableNativePlatformKey };
|
|
@@ -3,7 +3,8 @@ import { constants as FS_CONSTANTS } from "node:fs";
|
|
|
3
3
|
import { lstat, open, readdir, realpath } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { canonicalJson, digestDocument } from "skill-family-contracts";
|
|
6
|
-
import {
|
|
6
|
+
import { HARNESS_ERROR_KINDS, mechanismError } from "./errors.mjs";
|
|
7
|
+
import { loadNativeAddon, stableNativePlatformKey } from "./fixed-set-publication-loader.mjs";
|
|
7
8
|
|
|
8
9
|
const MANIFEST_KIND = "skill-family.fixed-set-publication-manifest";
|
|
9
10
|
const RECEIPT_KIND = "skill-family.fixed-set-publication-receipt";
|
|
@@ -47,10 +48,15 @@ function directoryIdentity(stats) {
|
|
|
47
48
|
};
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
function assertNormalizedAbsolutePath(input, label) {
|
|
51
52
|
if (typeof input !== "string" || !path.isAbsolute(input) || input.includes("\0") || path.normalize(input) !== input) {
|
|
52
53
|
throw new TypeError(`${label} must be a normalized absolute path`);
|
|
53
54
|
}
|
|
55
|
+
return input;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function canonicalDirectory(input, label) {
|
|
59
|
+
assertNormalizedAbsolutePath(input, label);
|
|
54
60
|
const before = await lstat(input, { bigint: true });
|
|
55
61
|
if (before.isSymbolicLink() || !before.isDirectory()) throw new TypeError(`${label} must be a real directory`);
|
|
56
62
|
const canonical = await realpath(input);
|
|
@@ -69,7 +75,7 @@ function assertSegment(value, label) {
|
|
|
69
75
|
return value;
|
|
70
76
|
}
|
|
71
77
|
|
|
72
|
-
async function scanDirectory(root) {
|
|
78
|
+
async function scanDirectory(root, { requireMember = true } = {}) {
|
|
73
79
|
if (FS_CONSTANTS.O_NOFOLLOW === undefined) throw new Error("O_NOFOLLOW is required");
|
|
74
80
|
const directories = [];
|
|
75
81
|
const members = [];
|
|
@@ -109,7 +115,7 @@ async function scanDirectory(root) {
|
|
|
109
115
|
}
|
|
110
116
|
}
|
|
111
117
|
await walk("");
|
|
112
|
-
if (members.length === 0) throw new TypeError("fixed-set source must contain at least one regular file");
|
|
118
|
+
if (requireMember && members.length === 0) throw new TypeError("fixed-set source must contain at least one regular file");
|
|
113
119
|
return { directories, members };
|
|
114
120
|
}
|
|
115
121
|
|
|
@@ -125,6 +131,137 @@ async function describeSource(sourceRoot) {
|
|
|
125
131
|
return { root, source, members: closure.members };
|
|
126
132
|
}
|
|
127
133
|
|
|
134
|
+
async function describeTree(rootPath, label, { requireMember = true } = {}) {
|
|
135
|
+
const root = await canonicalDirectory(rootPath, label);
|
|
136
|
+
const closure = await scanDirectory(root.path, { requireMember });
|
|
137
|
+
return {
|
|
138
|
+
root,
|
|
139
|
+
snapshot: {
|
|
140
|
+
root: root.identity,
|
|
141
|
+
directories: closure.directories,
|
|
142
|
+
members: closure.members,
|
|
143
|
+
closureDigest: digestDocument({
|
|
144
|
+
root: root.identity,
|
|
145
|
+
directories: closure.directories,
|
|
146
|
+
members: closure.members,
|
|
147
|
+
}),
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function sameIdentity(left, right) {
|
|
153
|
+
return left?.type === "directory" && right?.type === "directory" &&
|
|
154
|
+
left.device === right.device && left.inode === right.inode && left.mode === right.mode;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function sameSnapshot(left, right) {
|
|
158
|
+
return left?.closureDigest === right?.closureDigest && canonicalJson(left) === canonicalJson(right);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function replacementTuple({
|
|
162
|
+
phase,
|
|
163
|
+
publicationState,
|
|
164
|
+
commitState,
|
|
165
|
+
verification,
|
|
166
|
+
durability,
|
|
167
|
+
}) {
|
|
168
|
+
return { phase, publicationState, commitState, verification, durability };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const PRE_COMMIT_NOT_RUN = Object.freeze(replacementTuple({
|
|
172
|
+
phase: "pre-commit",
|
|
173
|
+
publicationState: "not-published",
|
|
174
|
+
commitState: "not-committed",
|
|
175
|
+
verification: "not-run",
|
|
176
|
+
durability: "not-requested",
|
|
177
|
+
}));
|
|
178
|
+
|
|
179
|
+
const PRE_COMMIT_VERIFIED = Object.freeze(replacementTuple({
|
|
180
|
+
phase: "pre-commit",
|
|
181
|
+
publicationState: "not-published",
|
|
182
|
+
commitState: "not-committed",
|
|
183
|
+
verification: "original-mapping-verified",
|
|
184
|
+
durability: "not-requested",
|
|
185
|
+
}));
|
|
186
|
+
|
|
187
|
+
const POST_COMMIT_PUBLISHED = Object.freeze(replacementTuple({
|
|
188
|
+
phase: "post-commit",
|
|
189
|
+
publicationState: "published",
|
|
190
|
+
commitState: "exchange-committed",
|
|
191
|
+
verification: "verified",
|
|
192
|
+
durability: "indeterminate",
|
|
193
|
+
}));
|
|
194
|
+
|
|
195
|
+
const POST_COMMIT_INDETERMINATE = Object.freeze(replacementTuple({
|
|
196
|
+
phase: "post-commit",
|
|
197
|
+
publicationState: "indeterminate",
|
|
198
|
+
commitState: "indeterminate",
|
|
199
|
+
verification: "failed",
|
|
200
|
+
durability: "indeterminate",
|
|
201
|
+
}));
|
|
202
|
+
|
|
203
|
+
function replacementError(message, tuple, cause, kind = HARNESS_ERROR_KINDS.ATOMIC_REPLACE_FAILED) {
|
|
204
|
+
const diagnostic = cause?.code ?? cause?.message;
|
|
205
|
+
return mechanismError(
|
|
206
|
+
kind,
|
|
207
|
+
diagnostic ? `${message}: ${diagnostic}` : message,
|
|
208
|
+
{ ...tuple },
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function observeReplacementMapping({ sourceRoot, targetPath, targetParent, parent, source, target }) {
|
|
213
|
+
try {
|
|
214
|
+
const liveParent = await canonicalDirectory(targetParent, "targetParent");
|
|
215
|
+
if (!sameIdentity(liveParent.identity, parent.identity)) return "other";
|
|
216
|
+
const liveSource = await describeTree(sourceRoot, "sourceRoot", { requireMember: false });
|
|
217
|
+
const liveTarget = await describeTree(targetPath, "target", { requireMember: false });
|
|
218
|
+
if (sameSnapshot(liveSource.snapshot, source.snapshot) && sameSnapshot(liveTarget.snapshot, target.snapshot)) {
|
|
219
|
+
return "original";
|
|
220
|
+
}
|
|
221
|
+
if (sameSnapshot(liveSource.snapshot, target.snapshot) && sameSnapshot(liveTarget.snapshot, source.snapshot)) {
|
|
222
|
+
return "exchanged";
|
|
223
|
+
}
|
|
224
|
+
} catch {
|
|
225
|
+
// An unobservable side is neither a proved original nor exchanged mapping.
|
|
226
|
+
}
|
|
227
|
+
return "other";
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function syncDirectoryBound(directory, expectedIdentity) {
|
|
231
|
+
if (FS_CONSTANTS.O_NOFOLLOW === undefined || FS_CONSTANTS.O_DIRECTORY === undefined) {
|
|
232
|
+
throw new Error("O_NOFOLLOW and O_DIRECTORY are required for directory synchronization");
|
|
233
|
+
}
|
|
234
|
+
const handle = await open(
|
|
235
|
+
directory,
|
|
236
|
+
FS_CONSTANTS.O_RDONLY | FS_CONSTANTS.O_DIRECTORY | FS_CONSTANTS.O_NOFOLLOW,
|
|
237
|
+
);
|
|
238
|
+
try {
|
|
239
|
+
const current = await handle.stat({ bigint: true });
|
|
240
|
+
if (!current.isDirectory() || !sameIdentity(directoryIdentity(current), expectedIdentity)) {
|
|
241
|
+
throw new Error("directory identity changed before synchronization");
|
|
242
|
+
}
|
|
243
|
+
await handle.sync();
|
|
244
|
+
} finally {
|
|
245
|
+
await handle.close();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function trustedNativeResult(value, expectedPlatform) {
|
|
250
|
+
if (!value || typeof value !== "object" || Array.isArray(value) ||
|
|
251
|
+
!Number.isInteger(value.status) || typeof value.committed !== "boolean" ||
|
|
252
|
+
value.platform !== expectedPlatform) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
const keys = Object.keys(value).sort().join(",");
|
|
256
|
+
if (value.status === 0) {
|
|
257
|
+
if (value.committed !== true || keys !== "committed,platform,status") return null;
|
|
258
|
+
} else if (keys !== "committed,error,platform,status" ||
|
|
259
|
+
typeof value.error !== "string" || value.error.length === 0) {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
return value;
|
|
263
|
+
}
|
|
264
|
+
|
|
128
265
|
/** Mechanically freezes one sibling-directory publication manifest. */
|
|
129
266
|
export async function createFixedSetPublicationManifest({ sourceRoot, targetParent, targetSegment } = {}) {
|
|
130
267
|
const segment = assertSegment(targetSegment, "targetSegment");
|
|
@@ -287,3 +424,144 @@ export async function publishFixedSet({ sourceRoot, targetParent, targetSegment,
|
|
|
287
424
|
memberCount: manifest.members.length,
|
|
288
425
|
});
|
|
289
426
|
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Atomically exchanges one complete staged fixed set with one existing target.
|
|
430
|
+
* A successful call leaves the displaced target at sourceRoot. This operation
|
|
431
|
+
* never deletes either side, retries the exchange, or attempts rollback.
|
|
432
|
+
*/
|
|
433
|
+
export async function replaceFixedSetAtomic({ sourceRoot, targetParent, targetSegment } = {}) {
|
|
434
|
+
assertNormalizedAbsolutePath(sourceRoot, "sourceRoot");
|
|
435
|
+
assertNormalizedAbsolutePath(targetParent, "targetParent");
|
|
436
|
+
const segment = assertSegment(targetSegment, "targetSegment");
|
|
437
|
+
|
|
438
|
+
let parent;
|
|
439
|
+
let sourceRootIdentity;
|
|
440
|
+
try {
|
|
441
|
+
parent = await canonicalDirectory(targetParent, "targetParent");
|
|
442
|
+
sourceRootIdentity = await canonicalDirectory(sourceRoot, "sourceRoot");
|
|
443
|
+
} catch (cause) {
|
|
444
|
+
throw replacementError("fixed-set replacement input cannot be observed", PRE_COMMIT_NOT_RUN, cause);
|
|
445
|
+
}
|
|
446
|
+
if (path.dirname(sourceRootIdentity.path) !== parent.path) {
|
|
447
|
+
throw new TypeError("sourceRoot and target must share the same canonical parent");
|
|
448
|
+
}
|
|
449
|
+
const sourceSegment = path.basename(sourceRootIdentity.path);
|
|
450
|
+
if (sourceSegment === segment) throw new TypeError("source and target segments must differ");
|
|
451
|
+
const targetPath = path.join(parent.path, segment);
|
|
452
|
+
|
|
453
|
+
let source;
|
|
454
|
+
let target;
|
|
455
|
+
try {
|
|
456
|
+
source = await describeTree(sourceRootIdentity.path, "sourceRoot");
|
|
457
|
+
target = await describeTree(targetPath, "target", { requireMember: false });
|
|
458
|
+
} catch (cause) {
|
|
459
|
+
throw replacementError("fixed-set replacement trees are unsafe", PRE_COMMIT_NOT_RUN, cause);
|
|
460
|
+
}
|
|
461
|
+
const context = {
|
|
462
|
+
sourceRoot: sourceRootIdentity.path,
|
|
463
|
+
targetPath,
|
|
464
|
+
targetParent: parent.path,
|
|
465
|
+
parent,
|
|
466
|
+
source,
|
|
467
|
+
target,
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
const runtimePlatform = stableNativePlatformKey();
|
|
471
|
+
if (runtimePlatform === null) {
|
|
472
|
+
throw replacementError(
|
|
473
|
+
"fixed-set replacement is unsupported on this runtime",
|
|
474
|
+
PRE_COMMIT_NOT_RUN,
|
|
475
|
+
null,
|
|
476
|
+
HARNESS_ERROR_KINDS.UNSUPPORTED_PLATFORM,
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
let loaded;
|
|
481
|
+
try {
|
|
482
|
+
loaded = await loadNativeAddon();
|
|
483
|
+
if (loaded?.platform !== runtimePlatform) {
|
|
484
|
+
throw new Error("stable native closure platform does not match the runtime");
|
|
485
|
+
}
|
|
486
|
+
} catch (cause) {
|
|
487
|
+
throw replacementError("stable native closure cannot be loaded", PRE_COMMIT_NOT_RUN, cause);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if ((await observeReplacementMapping(context)) !== "original") {
|
|
491
|
+
throw replacementError("fixed-set replacement input drifted before commit", PRE_COMMIT_NOT_RUN);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const addon = loaded?.addon;
|
|
495
|
+
let parentHandle = null;
|
|
496
|
+
let nativeResult;
|
|
497
|
+
let exchangeFailure = null;
|
|
498
|
+
let closeFailure = null;
|
|
499
|
+
try {
|
|
500
|
+
if (!addon || typeof addon.openParentDirectory !== "function" ||
|
|
501
|
+
typeof addon.closeParentDirectory !== "function" ||
|
|
502
|
+
typeof addon.exchangeDirectories !== "function") {
|
|
503
|
+
throw new Error("stable native addon does not expose the directory exchange closure");
|
|
504
|
+
}
|
|
505
|
+
parentHandle = addon.openParentDirectory(parent.path);
|
|
506
|
+
if (parentHandle?.status) {
|
|
507
|
+
throw new Error(parentHandle.error ?? "parent directory could not be opened");
|
|
508
|
+
}
|
|
509
|
+
nativeResult = addon.exchangeDirectories(
|
|
510
|
+
parentHandle,
|
|
511
|
+
sourceSegment,
|
|
512
|
+
segment,
|
|
513
|
+
parent.identity,
|
|
514
|
+
source.snapshot.root,
|
|
515
|
+
target.snapshot.root,
|
|
516
|
+
);
|
|
517
|
+
} catch (cause) {
|
|
518
|
+
exchangeFailure = cause;
|
|
519
|
+
} finally {
|
|
520
|
+
if (parentHandle && !parentHandle.status && typeof addon?.closeParentDirectory === "function") {
|
|
521
|
+
try {
|
|
522
|
+
const closed = addon.closeParentDirectory(parentHandle);
|
|
523
|
+
if (closed !== true) throw new Error(closed?.error ?? "parent directory handle did not close cleanly");
|
|
524
|
+
} catch (cause) {
|
|
525
|
+
closeFailure = cause;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const trusted = exchangeFailure === null
|
|
531
|
+
? trustedNativeResult(nativeResult, runtimePlatform.startsWith("darwin-") ? "darwin" : "linux")
|
|
532
|
+
: null;
|
|
533
|
+
const mapping = await observeReplacementMapping(context);
|
|
534
|
+
const successfulExchange = closeFailure === null && trusted?.status === 0 &&
|
|
535
|
+
trusted.committed === true && mapping === "exchanged";
|
|
536
|
+
|
|
537
|
+
if (!successfulExchange) {
|
|
538
|
+
const cause = exchangeFailure ?? closeFailure ??
|
|
539
|
+
new Error(trusted?.error ?? "native directory exchange result is not trustworthy");
|
|
540
|
+
if (mapping === "original" && trusted?.committed !== true) {
|
|
541
|
+
throw replacementError("directory exchange did not commit", PRE_COMMIT_VERIFIED, cause);
|
|
542
|
+
}
|
|
543
|
+
if (mapping === "exchanged") {
|
|
544
|
+
throw replacementError("directory exchange committed without a trustworthy success result", POST_COMMIT_PUBLISHED, cause);
|
|
545
|
+
}
|
|
546
|
+
throw replacementError("directory exchange state is indeterminate", POST_COMMIT_INDETERMINATE, cause);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
try {
|
|
550
|
+
await syncDirectoryBound(targetPath, source.snapshot.root);
|
|
551
|
+
await syncDirectoryBound(sourceRootIdentity.path, target.snapshot.root);
|
|
552
|
+
await syncDirectoryBound(parent.path, parent.identity);
|
|
553
|
+
} catch (cause) {
|
|
554
|
+
throw replacementError("directory exchange synchronization failed", POST_COMMIT_PUBLISHED, cause);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return Object.freeze({
|
|
558
|
+
path: targetPath,
|
|
559
|
+
displacedTargetPath: sourceRootIdentity.path,
|
|
560
|
+
publicationState: "published",
|
|
561
|
+
commitState: "exchange-committed",
|
|
562
|
+
verification: "verified",
|
|
563
|
+
durability: "namespace-synced",
|
|
564
|
+
publishedClosureDigest: source.snapshot.closureDigest,
|
|
565
|
+
displacedClosureDigest: target.snapshot.closureDigest,
|
|
566
|
+
});
|
|
567
|
+
}
|
package/src/index.mjs
CHANGED
|
@@ -199,4 +199,4 @@ export {
|
|
|
199
199
|
close,
|
|
200
200
|
} from "./state-store.mjs";
|
|
201
201
|
export { createFilesystemRootBinding, readFileBound } from "./bound-read.mjs";
|
|
202
|
-
export { createFixedSetPublicationManifest, publishFixedSet } from "./fixed-set-publication.mjs";
|
|
202
|
+
export { createFixedSetPublicationManifest, publishFixedSet, replaceFixedSetAtomic } from "./fixed-set-publication.mjs";
|
|
@@ -16,7 +16,7 @@ const PLATFORMS = Object.freeze({
|
|
|
16
16
|
"linux-arm64-gnu": { os: "linux", arch: "arm64", libc: "glibc" },
|
|
17
17
|
"linux-x64-gnu": { os: "linux", arch: "x64", libc: "glibc" },
|
|
18
18
|
});
|
|
19
|
-
const EXPORTS = Object.freeze(["closeParentDirectory", "observeFilesystemTreeNative", "openParentDirectory", "platform", "readFileBoundNative", "renameDirectoryNoReplace"]);
|
|
19
|
+
const EXPORTS = Object.freeze(["closeParentDirectory", "exchangeDirectories", "observeFilesystemTreeNative", "openParentDirectory", "platform", "readFileBoundNative", "renameDirectoryNoReplace"]);
|
|
20
20
|
const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
21
21
|
const bindingGypSha256 = sha256(readFileSync(BINDING_GYP));
|
|
22
22
|
|
package/src/native/bound_read.c
CHANGED
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
#ifndef RENAME_EXCL
|
|
20
20
|
#error "RENAME_EXCL is unavailable"
|
|
21
21
|
#endif
|
|
22
|
+
#ifndef RENAME_SWAP
|
|
23
|
+
#error "RENAME_SWAP is unavailable"
|
|
24
|
+
#endif
|
|
22
25
|
#define PLATFORM_NAME "darwin"
|
|
23
26
|
#elif defined(__linux__)
|
|
24
27
|
#include <linux/fs.h>
|
|
@@ -26,6 +29,9 @@
|
|
|
26
29
|
#ifndef RENAME_NOREPLACE
|
|
27
30
|
#error "RENAME_NOREPLACE is unavailable"
|
|
28
31
|
#endif
|
|
32
|
+
#ifndef RENAME_EXCHANGE
|
|
33
|
+
#error "RENAME_EXCHANGE is unavailable"
|
|
34
|
+
#endif
|
|
29
35
|
#ifndef SYS_renameat2
|
|
30
36
|
#error "SYS_renameat2 is unavailable"
|
|
31
37
|
#endif
|
|
@@ -331,6 +337,38 @@ static int read_decimal_property(napi_env env, napi_value object, const char *na
|
|
|
331
337
|
return 1;
|
|
332
338
|
}
|
|
333
339
|
|
|
340
|
+
static int read_directory_identity(napi_env env, napi_value object, uint64_t *device,
|
|
341
|
+
uint64_t *inode, int32_t *mode) {
|
|
342
|
+
napi_value value;
|
|
343
|
+
if (!read_decimal_property(env, object, "device", device) ||
|
|
344
|
+
!read_decimal_property(env, object, "inode", inode) ||
|
|
345
|
+
napi_get_named_property(env, object, "mode", &value) != napi_ok ||
|
|
346
|
+
napi_get_value_int32(env, value, mode) != napi_ok || *mode < 0 || *mode > 0777) {
|
|
347
|
+
napi_throw_type_error(env, NULL, "expected directory identity requires device, inode, and mode");
|
|
348
|
+
return 0;
|
|
349
|
+
}
|
|
350
|
+
return 1;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
static int matches_directory_identity(const struct stat *actual, uint64_t device,
|
|
354
|
+
uint64_t inode, int32_t mode) {
|
|
355
|
+
return S_ISDIR(actual->st_mode) && (uint64_t)actual->st_dev == device &&
|
|
356
|
+
(uint64_t)actual->st_ino == inode &&
|
|
357
|
+
(actual->st_mode & 0777) == (mode_t)mode;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
static napi_value rename_success_result(napi_env env) {
|
|
361
|
+
napi_value result, value;
|
|
362
|
+
NAPI_OK(napi_create_object(env, &result), "cannot create native result");
|
|
363
|
+
NAPI_OK(napi_create_int32(env, 0, &value), "cannot encode native status");
|
|
364
|
+
NAPI_OK(napi_set_named_property(env, result, "status", value), "cannot set native status");
|
|
365
|
+
NAPI_OK(napi_get_boolean(env, true, &value), "cannot encode commit state");
|
|
366
|
+
NAPI_OK(napi_set_named_property(env, result, "committed", value), "cannot set commit state");
|
|
367
|
+
NAPI_OK(napi_create_string_utf8(env, PLATFORM_NAME, NAPI_AUTO_LENGTH, &value), "cannot encode platform");
|
|
368
|
+
NAPI_OK(napi_set_named_property(env, result, "platform", value), "cannot set platform");
|
|
369
|
+
return result;
|
|
370
|
+
}
|
|
371
|
+
|
|
334
372
|
static int unwrap_parent(napi_env env, napi_value value, parent_handle **output) {
|
|
335
373
|
bool tagged = false;
|
|
336
374
|
if (napi_check_object_type_tag(env, value, &PARENT_HANDLE_TAG, &tagged) != napi_ok || !tagged ||
|
|
@@ -356,8 +394,13 @@ static napi_value OpenParentDirectory(napi_env env, napi_callback_info info) {
|
|
|
356
394
|
parent_handle *handle = calloc(1, sizeof(*handle));
|
|
357
395
|
if (handle == NULL) { close(fd); return throw_last(env, "cannot allocate parent directory handle"); }
|
|
358
396
|
handle->fd = fd;
|
|
359
|
-
if (napi_create_object(env, &result) != napi_ok
|
|
360
|
-
|
|
397
|
+
if (napi_create_object(env, &result) != napi_ok) {
|
|
398
|
+
close(fd); free(handle); return throw_last(env, "cannot create parent directory handle");
|
|
399
|
+
}
|
|
400
|
+
if (napi_type_tag_object(env, result, &PARENT_HANDLE_TAG) != napi_ok) {
|
|
401
|
+
close(fd); free(handle); return throw_last(env, "cannot tag parent directory handle");
|
|
402
|
+
}
|
|
403
|
+
if (napi_wrap(env, result, handle, finalize_parent, NULL, NULL) != napi_ok) {
|
|
361
404
|
close(fd); free(handle); return throw_last(env, "cannot create parent directory handle");
|
|
362
405
|
}
|
|
363
406
|
return result;
|
|
@@ -369,15 +412,17 @@ static napi_value CloseParentDirectory(napi_env env, napi_callback_info info) {
|
|
|
369
412
|
parent_handle *handle = NULL;
|
|
370
413
|
NAPI_OK(napi_get_cb_info(env, info, &argc, argv, NULL, NULL), "cannot read arguments");
|
|
371
414
|
if (argc != 1 || !unwrap_parent(env, argv[0], &handle)) return NULL;
|
|
372
|
-
|
|
415
|
+
int fd = handle->fd;
|
|
373
416
|
handle->closed = 1;
|
|
417
|
+
handle->fd = -1;
|
|
418
|
+
if (close(fd) != 0) return rename_errno_result(env, errno, 0, strerror(errno));
|
|
374
419
|
NAPI_OK(napi_get_boolean(env, true, &result), "cannot encode close result");
|
|
375
420
|
return result;
|
|
376
421
|
}
|
|
377
422
|
|
|
378
423
|
static napi_value RenameDirectoryNoReplace(napi_env env, napi_callback_info info) {
|
|
379
424
|
size_t argc = 4;
|
|
380
|
-
napi_value argv[4], value
|
|
425
|
+
napi_value argv[4], value;
|
|
381
426
|
parent_handle *parent = NULL;
|
|
382
427
|
char source[NAME_MAX + 1], target[NAME_MAX + 1];
|
|
383
428
|
uint64_t expected_device = 0, expected_inode = 0;
|
|
@@ -411,14 +456,63 @@ static napi_value RenameDirectoryNoReplace(napi_env env, napi_callback_info info
|
|
|
411
456
|
if (fstatat(parent->fd, target, &target_after, AT_SYMLINK_NOFOLLOW) != 0 || !S_ISDIR(target_after.st_mode) ||
|
|
412
457
|
target_after.st_dev != source_stat.st_dev || target_after.st_ino != source_stat.st_ino ||
|
|
413
458
|
(target_after.st_mode & 0777) != (source_stat.st_mode & 0777)) return rename_errno_result(env, EIO, 1, "target identity mismatch after committed rename");
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
459
|
+
return rename_success_result(env);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
static napi_value ExchangeDirectories(napi_env env, napi_callback_info info) {
|
|
463
|
+
size_t argc = 6;
|
|
464
|
+
napi_value argv[6];
|
|
465
|
+
parent_handle *parent = NULL;
|
|
466
|
+
char source[NAME_MAX + 1], target[NAME_MAX + 1];
|
|
467
|
+
uint64_t parent_device = 0, parent_inode = 0;
|
|
468
|
+
uint64_t source_device = 0, source_inode = 0;
|
|
469
|
+
uint64_t target_device = 0, target_inode = 0;
|
|
470
|
+
int32_t parent_mode = 0, source_mode = 0, target_mode = 0;
|
|
471
|
+
NAPI_OK(napi_get_cb_info(env, info, &argc, argv, NULL, NULL), "cannot read arguments");
|
|
472
|
+
if (argc != 6 || !unwrap_parent(env, argv[0], &parent) ||
|
|
473
|
+
!read_parent_segment(env, argv[1], source) ||
|
|
474
|
+
!read_parent_segment(env, argv[2], target)) return NULL;
|
|
475
|
+
if (strcmp(source, target) == 0) return throw_last(env, "sourceSegment and targetSegment must differ");
|
|
476
|
+
if (!read_directory_identity(env, argv[3], &parent_device, &parent_inode, &parent_mode) ||
|
|
477
|
+
!read_directory_identity(env, argv[4], &source_device, &source_inode, &source_mode) ||
|
|
478
|
+
!read_directory_identity(env, argv[5], &target_device, &target_inode, &target_mode)) return NULL;
|
|
479
|
+
|
|
480
|
+
struct stat parent_stat, source_stat, target_stat;
|
|
481
|
+
if (fstat(parent->fd, &parent_stat) != 0) return rename_errno_result(env, errno, 0, strerror(errno));
|
|
482
|
+
if (!matches_directory_identity(&parent_stat, parent_device, parent_inode, parent_mode)) {
|
|
483
|
+
return rename_errno_result(env, EPROTO, 0, "parent identity changed before exchange");
|
|
484
|
+
}
|
|
485
|
+
if (fstatat(parent->fd, source, &source_stat, AT_SYMLINK_NOFOLLOW) != 0) {
|
|
486
|
+
return rename_errno_result(env, errno, 0, strerror(errno));
|
|
487
|
+
}
|
|
488
|
+
if (!matches_directory_identity(&source_stat, source_device, source_inode, source_mode)) {
|
|
489
|
+
return rename_errno_result(env, EPROTO, 0, "source identity changed before exchange");
|
|
490
|
+
}
|
|
491
|
+
if (fstatat(parent->fd, target, &target_stat, AT_SYMLINK_NOFOLLOW) != 0) {
|
|
492
|
+
return rename_errno_result(env, errno, 0, strerror(errno));
|
|
493
|
+
}
|
|
494
|
+
if (!matches_directory_identity(&target_stat, target_device, target_inode, target_mode)) {
|
|
495
|
+
return rename_errno_result(env, EPROTO, 0, "target identity changed before exchange");
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
int rc;
|
|
499
|
+
#if defined(__APPLE__)
|
|
500
|
+
rc = renameatx_np(parent->fd, source, parent->fd, target, RENAME_SWAP);
|
|
501
|
+
#else
|
|
502
|
+
rc = (int)syscall(SYS_renameat2, parent->fd, source, parent->fd, target, RENAME_EXCHANGE);
|
|
503
|
+
#endif
|
|
504
|
+
if (rc != 0) return rename_errno_result(env, errno, 0, strerror(errno));
|
|
505
|
+
|
|
506
|
+
struct stat source_after, target_after;
|
|
507
|
+
if (fstatat(parent->fd, source, &source_after, AT_SYMLINK_NOFOLLOW) != 0 ||
|
|
508
|
+
!matches_directory_identity(&source_after, target_device, target_inode, target_mode)) {
|
|
509
|
+
return rename_errno_result(env, EIO, 1, "source identity mismatch after committed exchange");
|
|
510
|
+
}
|
|
511
|
+
if (fstatat(parent->fd, target, &target_after, AT_SYMLINK_NOFOLLOW) != 0 ||
|
|
512
|
+
!matches_directory_identity(&target_after, source_device, source_inode, source_mode)) {
|
|
513
|
+
return rename_errno_result(env, EIO, 1, "target identity mismatch after committed exchange");
|
|
514
|
+
}
|
|
515
|
+
return rename_success_result(env);
|
|
422
516
|
}
|
|
423
517
|
|
|
424
518
|
/* A complete bound-tree observation is deliberately implemented beside the
|
|
@@ -570,6 +664,8 @@ static napi_value Init(napi_env env, napi_value exports) {
|
|
|
570
664
|
NAPI_OK(napi_set_named_property(env, exports, "openParentDirectory", value), "cannot export openParentDirectory");
|
|
571
665
|
NAPI_OK(napi_create_function(env, "closeParentDirectory", NAPI_AUTO_LENGTH, CloseParentDirectory, NULL, &value), "cannot export closeParentDirectory");
|
|
572
666
|
NAPI_OK(napi_set_named_property(env, exports, "closeParentDirectory", value), "cannot export closeParentDirectory");
|
|
667
|
+
NAPI_OK(napi_create_function(env, "exchangeDirectories", NAPI_AUTO_LENGTH, ExchangeDirectories, NULL, &value), "cannot export exchangeDirectories");
|
|
668
|
+
NAPI_OK(napi_set_named_property(env, exports, "exchangeDirectories", value), "cannot export exchangeDirectories");
|
|
573
669
|
NAPI_OK(napi_create_function(env, "renameDirectoryNoReplace", NAPI_AUTO_LENGTH, RenameDirectoryNoReplace, NULL, &value), "cannot export renameDirectoryNoReplace");
|
|
574
670
|
NAPI_OK(napi_set_named_property(env, exports, "renameDirectoryNoReplace", value), "cannot export renameDirectoryNoReplace");
|
|
575
671
|
NAPI_OK(napi_create_string_utf8(env, PLATFORM_NAME, NAPI_AUTO_LENGTH, &value), "cannot encode platform");
|
package/src/native/loader.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import { fileURLToPath } from "node:url";
|
|
|
9
9
|
const PACKAGE_ROOT = path.dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
const MANIFEST_PATH = path.join(PACKAGE_ROOT, "prebuild-manifest.json");
|
|
11
11
|
const SHA256_PATTERN = /^[0-9a-f]{64}$/u;
|
|
12
|
-
const EXPECTED_EXPORTS = Object.freeze(["closeParentDirectory", "observeFilesystemTreeNative", "openParentDirectory", "platform", "readFileBoundNative", "renameDirectoryNoReplace"]);
|
|
12
|
+
const EXPECTED_EXPORTS = Object.freeze(["closeParentDirectory", "exchangeDirectories", "observeFilesystemTreeNative", "openParentDirectory", "platform", "readFileBoundNative", "renameDirectoryNoReplace"]);
|
|
13
13
|
const PLATFORM_KEYS = Object.freeze(["darwin-arm64", "darwin-x64", "linux-arm64-gnu", "linux-x64-gnu"]);
|
|
14
14
|
|
|
15
15
|
function fail(message) {
|
|
@@ -20,11 +20,15 @@ function digest(bytes) {
|
|
|
20
20
|
return createHash("sha256").update(bytes).digest("hex");
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
export function stableNativePlatformKey({
|
|
24
|
+
platform = process.platform,
|
|
25
|
+
arch = process.arch,
|
|
26
|
+
glibcVersionRuntime = process.report?.getReport?.()?.header?.glibcVersionRuntime,
|
|
27
|
+
} = {}) {
|
|
28
|
+
if (platform === "darwin" && ["arm64", "x64"].includes(arch)) return `darwin-${arch}`;
|
|
29
|
+
if (platform === "linux" && ["arm64", "x64"].includes(arch) &&
|
|
30
|
+
typeof glibcVersionRuntime === "string") {
|
|
31
|
+
return `linux-${arch}-gnu`;
|
|
28
32
|
}
|
|
29
33
|
return null;
|
|
30
34
|
}
|
|
@@ -65,7 +69,7 @@ export async function loadNativeBoundReadAddon() {
|
|
|
65
69
|
if (FS_CONSTANTS.O_NOFOLLOW === undefined || FS_CONSTANTS.O_DIRECTORY === undefined) fail("required no-follow flags are unavailable");
|
|
66
70
|
const entries = await parseManifest();
|
|
67
71
|
await verifyClosure(entries);
|
|
68
|
-
const key =
|
|
72
|
+
const key = stableNativePlatformKey();
|
|
69
73
|
const entry = entries.find((candidate) => candidate.platformKey === key);
|
|
70
74
|
if (!entry) fail(`UNSUPPORTED: fixed platform matrix has no runtime ${process.platform}-${process.arch}`);
|
|
71
75
|
const binaryPath = path.join(PACKAGE_ROOT, entry.binary);
|
|
@@ -84,7 +88,8 @@ export async function loadNativeBoundReadAddon() {
|
|
|
84
88
|
try { addon = createRequire(import.meta.url)(binaryReal); } catch (cause) { throw new Error(`filesystemBoundRead: native load failed: ${cause?.message ?? "unknown"}`, { cause }); }
|
|
85
89
|
if (JSON.stringify(Object.keys(addon).sort()) !== JSON.stringify([...EXPECTED_EXPORTS].sort()) || addon.platform !== entry.os ||
|
|
86
90
|
typeof addon.readFileBoundNative !== "function" || typeof addon.openParentDirectory !== "function" ||
|
|
87
|
-
typeof addon.closeParentDirectory !== "function" || typeof addon.
|
|
91
|
+
typeof addon.closeParentDirectory !== "function" || typeof addon.exchangeDirectories !== "function" ||
|
|
92
|
+
typeof addon.renameDirectoryNoReplace !== "function") {
|
|
88
93
|
fail("native exports do not match the fixed manifest");
|
|
89
94
|
}
|
|
90
95
|
const after = await lstat(binaryPath);
|
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
"libc": "none",
|
|
11
11
|
"binary": "prebuilds/darwin-arm64/bound_read.darwin-arm64.node",
|
|
12
12
|
"mode": 420,
|
|
13
|
-
"sha256": "
|
|
13
|
+
"sha256": "afcda237922692e147a800d7d291496d7055e5054bf09ba2e3d2f1ac5d1e445d",
|
|
14
14
|
"napi": 10,
|
|
15
15
|
"exports": [
|
|
16
16
|
"closeParentDirectory",
|
|
17
|
+
"exchangeDirectories",
|
|
17
18
|
"observeFilesystemTreeNative",
|
|
18
19
|
"openParentDirectory",
|
|
19
20
|
"platform",
|
|
@@ -28,10 +29,11 @@
|
|
|
28
29
|
"libc": "none",
|
|
29
30
|
"binary": "prebuilds/darwin-x64/bound_read.darwin-x64.node",
|
|
30
31
|
"mode": 420,
|
|
31
|
-
"sha256": "
|
|
32
|
+
"sha256": "d3316e4d9dfb8f99ec64b0e495760bb12e4ce95f8522d9779971a82d5ee0c7fb",
|
|
32
33
|
"napi": 10,
|
|
33
34
|
"exports": [
|
|
34
35
|
"closeParentDirectory",
|
|
36
|
+
"exchangeDirectories",
|
|
35
37
|
"observeFilesystemTreeNative",
|
|
36
38
|
"openParentDirectory",
|
|
37
39
|
"platform",
|
|
@@ -46,10 +48,11 @@
|
|
|
46
48
|
"libc": "glibc",
|
|
47
49
|
"binary": "prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node",
|
|
48
50
|
"mode": 420,
|
|
49
|
-
"sha256": "
|
|
51
|
+
"sha256": "748f6154d01b8873100f288dafba1b6bc542596951ab7978231e67daa9122e5b",
|
|
50
52
|
"napi": 10,
|
|
51
53
|
"exports": [
|
|
52
54
|
"closeParentDirectory",
|
|
55
|
+
"exchangeDirectories",
|
|
53
56
|
"observeFilesystemTreeNative",
|
|
54
57
|
"openParentDirectory",
|
|
55
58
|
"platform",
|
|
@@ -64,10 +67,11 @@
|
|
|
64
67
|
"libc": "glibc",
|
|
65
68
|
"binary": "prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node",
|
|
66
69
|
"mode": 420,
|
|
67
|
-
"sha256": "
|
|
70
|
+
"sha256": "ec40dc02bb666ccae585c78f59211951dcc2001bf71c828a59b0d5b821ebfae0",
|
|
68
71
|
"napi": 10,
|
|
69
72
|
"exports": [
|
|
70
73
|
"closeParentDirectory",
|
|
74
|
+
"exchangeDirectories",
|
|
71
75
|
"observeFilesystemTreeNative",
|
|
72
76
|
"openParentDirectory",
|
|
73
77
|
"platform",
|
|
@@ -8,33 +8,33 @@
|
|
|
8
8
|
},
|
|
9
9
|
"source": {
|
|
10
10
|
"path": "packages/skill-family-harness-node/src/native/bound_read.c",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "367b21caadcf3c4d0f13c11fdc90efcfa2603bb5396298cf4a5b7d3c597f1e0b"
|
|
12
12
|
},
|
|
13
13
|
"buildRecipe": {
|
|
14
14
|
"path": "packages/skill-family-harness-node/src/native/binding.gyp",
|
|
15
15
|
"sha256": "1631420afe903b1978a856dbeadd37a655a704cd7ded9d1ab6696f87e1116d7f"
|
|
16
16
|
},
|
|
17
|
-
"manifestSha256": "
|
|
17
|
+
"manifestSha256": "a730d45fab7a61eb5d39f898684d55ace6fc01ea149e4b0f101985b0726c4a41",
|
|
18
18
|
"inputs": [
|
|
19
19
|
{
|
|
20
20
|
"platformKey": "darwin-arm64",
|
|
21
|
-
"platformReceiptSha256": "
|
|
22
|
-
"binarySha256": "
|
|
21
|
+
"platformReceiptSha256": "243a29778b3430f3c895871e88fcbe6f19f8786f02679d4a03611bfd6220848d",
|
|
22
|
+
"binarySha256": "afcda237922692e147a800d7d291496d7055e5054bf09ba2e3d2f1ac5d1e445d"
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
25
|
"platformKey": "darwin-x64",
|
|
26
|
-
"platformReceiptSha256": "
|
|
27
|
-
"binarySha256": "
|
|
26
|
+
"platformReceiptSha256": "c7b95b77003d1ef8b299f17bda96d65e034237c8f61bc2a1033e99b8c5760954",
|
|
27
|
+
"binarySha256": "d3316e4d9dfb8f99ec64b0e495760bb12e4ce95f8522d9779971a82d5ee0c7fb"
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
"platformKey": "linux-arm64-gnu",
|
|
31
|
-
"platformReceiptSha256": "
|
|
32
|
-
"binarySha256": "
|
|
31
|
+
"platformReceiptSha256": "9ed354775568eea2c954440c501dfeb5404e84e9381afa34e2f8ff0c1fa082b1",
|
|
32
|
+
"binarySha256": "748f6154d01b8873100f288dafba1b6bc542596951ab7978231e67daa9122e5b"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"platformKey": "linux-x64-gnu",
|
|
36
|
-
"platformReceiptSha256": "
|
|
37
|
-
"binarySha256": "
|
|
36
|
+
"platformReceiptSha256": "5e8cf755503058e7164ae36652df811d7c16c47ab5c65fe9a4748486574b2b0e",
|
|
37
|
+
"binarySha256": "ec40dc02bb666ccae585c78f59211951dcc2001bf71c828a59b0d5b821ebfae0"
|
|
38
38
|
}
|
|
39
39
|
]
|
|
40
40
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"status": "stable",
|
|
5
5
|
"source": {
|
|
6
6
|
"path": "packages/skill-family-harness-node/src/native/bound_read.c",
|
|
7
|
-
"sha256": "
|
|
7
|
+
"sha256": "367b21caadcf3c4d0f13c11fdc90efcfa2603bb5396298cf4a5b7d3c597f1e0b"
|
|
8
8
|
},
|
|
9
9
|
"buildRecipe": {
|
|
10
10
|
"path": "packages/skill-family-harness-node/src/native/binding.gyp",
|
|
@@ -14,25 +14,25 @@
|
|
|
14
14
|
{
|
|
15
15
|
"platformKey": "darwin-arm64",
|
|
16
16
|
"binary": "prebuilds/darwin-arm64/bound_read.darwin-arm64.node",
|
|
17
|
-
"sha256": "
|
|
17
|
+
"sha256": "afcda237922692e147a800d7d291496d7055e5054bf09ba2e3d2f1ac5d1e445d",
|
|
18
18
|
"license": "Apache-2.0"
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
"platformKey": "darwin-x64",
|
|
22
22
|
"binary": "prebuilds/darwin-x64/bound_read.darwin-x64.node",
|
|
23
|
-
"sha256": "
|
|
23
|
+
"sha256": "d3316e4d9dfb8f99ec64b0e495760bb12e4ce95f8522d9779971a82d5ee0c7fb",
|
|
24
24
|
"license": "Apache-2.0"
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"platformKey": "linux-arm64-gnu",
|
|
28
28
|
"binary": "prebuilds/linux-arm64-gnu/bound_read.linux-arm64-gnu.node",
|
|
29
|
-
"sha256": "
|
|
29
|
+
"sha256": "748f6154d01b8873100f288dafba1b6bc542596951ab7978231e67daa9122e5b",
|
|
30
30
|
"license": "Apache-2.0"
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
"platformKey": "linux-x64-gnu",
|
|
34
34
|
"binary": "prebuilds/linux-x64-gnu/bound_read.linux-x64-gnu.node",
|
|
35
|
-
"sha256": "
|
|
35
|
+
"sha256": "ec40dc02bb666ccae585c78f59211951dcc2001bf71c828a59b0d5b821ebfae0",
|
|
36
36
|
"license": "Apache-2.0"
|
|
37
37
|
}
|
|
38
38
|
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/version.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Exact Foundation release coordinate shared by the public Harness exports. */
|
|
2
|
-
export const FOUNDATION_PACKAGE_VERSION = "0.
|
|
2
|
+
export const FOUNDATION_PACKAGE_VERSION = "0.18.0";
|