skill-family-harness-node 0.19.0 → 0.19.2
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 +32 -0
- package/CHANGELOG.zh-CN.md +32 -0
- package/README.md +9 -15
- package/README.zh-CN.md +9 -15
- package/candidate/mechanisms-cli.mjs +196 -27
- package/package.json +2 -2
- package/release-notes/0.19.1.yaml +19 -0
- package/release-notes/0.19.2.yaml +15 -0
- package/src/version.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
<!-- release-skill:changelog:start version=0.19.2 locale=en baseline=sha256:ac3f4b15f249649e6bbae8c6a21e48f28fa206346108b21958a8daa22d85f243 -->
|
|
4
|
+
## [0.19.2] - 2026-09-08
|
|
5
|
+
|
|
6
|
+
Harness 0.19.2 aligns its package version with the Foundation Adoption Review distribution patch. Existing mechanisms and runtime behavior are unchanged.
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
|
|
10
|
+
- Aligns the package version and release documentation with Foundation 0.19.2 without changing Harness implementation or driver behavior.
|
|
11
|
+
|
|
12
|
+
### Upgrade Notes
|
|
13
|
+
|
|
14
|
+
Pin all three Foundation packages to exactly 0.19.2. This patch adds no Harness capability and does not change host descriptors, drivers, since values, or maturity.
|
|
15
|
+
<!-- release-skill:changelog:end version=0.19.2 locale=en -->
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
<!-- release-skill:changelog:start version=0.19.1 locale=en baseline=sha256:76ded57ddca31105929a12a1b198bd6da407090e353dbf90096c17da5532a3ea -->
|
|
19
|
+
## [0.19.1] - 2026-09-08
|
|
20
|
+
|
|
21
|
+
Harness 0.19.1 makes the candidate mechanism-batch entry point settle on the current transfer's actual completion or failure while preserving caller ownership of programmatic streams.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Waits for the current write callback, handles premature input and output closure, and removes only listeners installed by the batch helper.
|
|
26
|
+
- Stops the library helper's own reading and releases its buffer after input overflow without destroying a caller-owned stream.
|
|
27
|
+
- Releases CLI-owned standard input after an oversized request is reported, allowing the process to exit 2 while the upstream pipe remains open.
|
|
28
|
+
|
|
29
|
+
### Upgrade Notes
|
|
30
|
+
|
|
31
|
+
Pin all three Foundation packages to exactly 0.19.1. The batch operation, capacity policy, per-item order, and single-request CLI remain unchanged. This entry is candidate and requires re-verification after an upgrade.
|
|
32
|
+
<!-- release-skill:changelog:end version=0.19.1 locale=en -->
|
|
33
|
+
|
|
34
|
+
|
|
3
35
|
<!-- release-skill:changelog:start version=0.19.0 locale=en baseline=sha256:c3b3a06b509cc53719c2de75d1edacd22c1711c99c5f7687ad91f350976262e3 -->
|
|
4
36
|
## [0.19.0] - 2026-09-07
|
|
5
37
|
|
package/CHANGELOG.zh-CN.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# 变更日志
|
|
2
2
|
|
|
3
|
+
<!-- release-skill:changelog:start version=0.19.2 locale=zh-CN baseline=sha256:aef79e7341fbe6581d798fd5d7371315c023b4a0c1733b094b13ce4166bfbcf3 -->
|
|
4
|
+
## [0.19.2] - 2026-09-08
|
|
5
|
+
|
|
6
|
+
Harness 0.19.2 将包版本与 Foundation Adoption Review 分发补丁对齐。既有机制和运行时行为保持不变。
|
|
7
|
+
|
|
8
|
+
### 变更
|
|
9
|
+
|
|
10
|
+
- 包版本和发布说明与 Foundation 0.19.2 数值对齐;Harness 实现和 driver 行为不变。
|
|
11
|
+
|
|
12
|
+
### 升级说明
|
|
13
|
+
|
|
14
|
+
三个 Foundation 包须一起精确锁定到 0.19.2。本补丁不增加 Harness 能力,不改变宿主 descriptor、driver、since 字段或成熟度。
|
|
15
|
+
<!-- release-skill:changelog:end version=0.19.2 locale=zh-CN -->
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
<!-- release-skill:changelog:start version=0.19.1 locale=zh-CN baseline=sha256:398aacf147e429237ff5b72fa1d22652afb76eb49da388eeb333d073bbd0a032 -->
|
|
19
|
+
## [0.19.1] - 2026-09-08
|
|
20
|
+
|
|
21
|
+
Harness 0.19.1 让候选机制批量入口等待本次传输真正完成或失败,同时保留程序化入口中调用方对流的所有权。
|
|
22
|
+
|
|
23
|
+
### 修复
|
|
24
|
+
|
|
25
|
+
- 等待本次写入回调,处理输入与输出提前关闭,并且只移除批量辅助函数自己安装的监听器。
|
|
26
|
+
- 输入超限后停止库辅助函数自己的读取并释放缓存,不销毁调用方拥有的流。
|
|
27
|
+
- 超限错误写出后释放 CLI 自己拥有的标准输入,使上游管道保持打开时进程仍能自行退出 2。
|
|
28
|
+
|
|
29
|
+
### 升级说明
|
|
30
|
+
|
|
31
|
+
三个 Foundation 包须一起精确锁定到 0.19.1。批量操作、容量政策、逐项顺序和旧单请求 CLI 保持不变。该入口仍是候选能力,升级后须重新验证。
|
|
32
|
+
<!-- release-skill:changelog:end version=0.19.1 locale=zh-CN -->
|
|
33
|
+
|
|
34
|
+
|
|
3
35
|
<!-- release-skill:changelog:start version=0.19.0 locale=zh-CN baseline=sha256:e0a716c69bbc9033af79ab9b1d1b607ba997b5324bb8301dfc8e0b7f4072dab2 -->
|
|
4
36
|
## [0.19.0] - 2026-09-07
|
|
5
37
|
|
package/README.md
CHANGED
|
@@ -4,28 +4,22 @@
|
|
|
4
4
|
|
|
5
5
|
# skill-family-harness-node
|
|
6
6
|
|
|
7
|
-
<!-- release-skill:release-version: 0.19.
|
|
7
|
+
<!-- release-skill:release-version: 0.19.2 -->
|
|
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.19.
|
|
12
|
+
**0.19.2** (2026-09-08)
|
|
13
13
|
|
|
14
|
-
Harness 0.19.
|
|
15
|
-
|
|
16
|
-
**Added**
|
|
17
|
-
|
|
18
|
-
- Adds runMechanismCliBatch({input, output, error}) on the existing fixed mechanism CLI, reading one batch request and returning per-item results with inputIndex, exitCode, and the original single-request response.
|
|
19
|
-
- Adds the explicit --batch mode to the official Bundle-projected mechanisms-cli.mjs. Without --batch the old single-request path is unchanged.
|
|
14
|
+
Harness 0.19.2 aligns its package version with the Foundation Adoption Review distribution patch. Existing mechanisms and runtime behavior are unchanged.
|
|
20
15
|
|
|
21
16
|
**Changed**
|
|
22
17
|
|
|
23
|
-
-
|
|
24
|
-
- A single item's mechanism failure is recorded in its result position and sibling items continue; the whole batch exits 2 when any item failed.
|
|
18
|
+
- Aligns the package version and release documentation with Foundation 0.19.2 without changing Harness implementation or driver behavior.
|
|
25
19
|
|
|
26
20
|
**Upgrade Notes**
|
|
27
21
|
|
|
28
|
-
Pin all three Foundation packages to exactly 0.19.
|
|
22
|
+
Pin all three Foundation packages to exactly 0.19.2. This patch adds no Harness capability and does not change host descriptors, drivers, since values, or maturity.
|
|
29
23
|
<!-- release-skill:managed:end id=latest-release -->
|
|
30
24
|
|
|
31
25
|
## Problem It Solves
|
|
@@ -38,7 +32,7 @@ The Harness consumes `skill-family-contracts` (a workspace dependency), reusing
|
|
|
38
32
|
|
|
39
33
|
## Installation and Minimal Example
|
|
40
34
|
|
|
41
|
-
Version 0.
|
|
35
|
+
Version 0.19.2 is the local source candidate. Build all three tarballs into one temporary directory and install those exact files for a candidate check:
|
|
42
36
|
|
|
43
37
|
```sh
|
|
44
38
|
pack_dir="$(mktemp -d)"
|
|
@@ -46,13 +40,13 @@ pack_dir="$(mktemp -d)"
|
|
|
46
40
|
(cd packages/skill-family-harness-node && pnpm pack --pack-destination "$pack_dir")
|
|
47
41
|
(cd packages/skill-family-engineering-kit && pnpm pack --pack-destination "$pack_dir")
|
|
48
42
|
mkdir "$pack_dir/consumer" && (cd "$pack_dir/consumer" && npm init -y)
|
|
49
|
-
(cd "$pack_dir/consumer" && npm install "$pack_dir/skill-family-contracts-0.
|
|
43
|
+
(cd "$pack_dir/consumer" && npm install "$pack_dir/skill-family-contracts-0.19.2.tgz" "$pack_dir/skill-family-harness-node-0.19.2.tgz" "$pack_dir/skill-family-engineering-kit-0.19.2.tgz")
|
|
50
44
|
```
|
|
51
45
|
|
|
52
46
|
After publication, use the registry coordinate:
|
|
53
47
|
|
|
54
48
|
```sh
|
|
55
|
-
npm install skill-family-harness-node@0.19.
|
|
49
|
+
npm install skill-family-harness-node@0.19.2
|
|
56
50
|
npm info skill-family-harness-node --help
|
|
57
51
|
```
|
|
58
52
|
|
|
@@ -261,4 +255,4 @@ When the actual threat includes malicious concurrency, return a minimal upstream
|
|
|
261
255
|
|
|
262
256
|
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.
|
|
263
257
|
|
|
264
|
-
Version 0.
|
|
258
|
+
Version 0.19.2 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,28 +5,22 @@
|
|
|
5
5
|
|
|
6
6
|
# skill-family-harness-node
|
|
7
7
|
|
|
8
|
-
<!-- release-skill:release-version: 0.19.
|
|
8
|
+
<!-- release-skill:release-version: 0.19.2 -->
|
|
9
9
|
|
|
10
10
|
Contracts 机制协议的**唯一默认 Node 实现**。这是一个薄运行时(thin runtime):只实现机制协议,不引入业务语义,不做第二语言实现。
|
|
11
11
|
|
|
12
12
|
<!-- release-skill:managed:start id=latest-release -->
|
|
13
|
-
**0.19.
|
|
13
|
+
**0.19.2** (2026-09-08)
|
|
14
14
|
|
|
15
|
-
Harness 0.19.
|
|
16
|
-
|
|
17
|
-
**新增**
|
|
18
|
-
|
|
19
|
-
- 新增 runMechanismCliBatch({input, output, error}):读入一个批量请求,逐项返回 inputIndex、exitCode 与原单请求响应。
|
|
20
|
-
- 官方 Bundle 投影的 mechanisms-cli.mjs 新增显式 --batch 模式。不带 --batch 时旧单请求路径不变。
|
|
15
|
+
Harness 0.19.2 将包版本与 Foundation Adoption Review 分发补丁对齐。既有机制和运行时行为保持不变。
|
|
21
16
|
|
|
22
17
|
**变更**
|
|
23
18
|
|
|
24
|
-
-
|
|
25
|
-
- 单项机制失败写入该项结果位置,其余项继续执行;任一机制项失败时整批退出码为 2。
|
|
19
|
+
- 包版本和发布说明与 Foundation 0.19.2 数值对齐;Harness 实现和 driver 行为不变。
|
|
26
20
|
|
|
27
21
|
**升级说明**
|
|
28
22
|
|
|
29
|
-
三个 Foundation 包须一起精确锁定到 0.19.
|
|
23
|
+
三个 Foundation 包须一起精确锁定到 0.19.2。本补丁不增加 Harness 能力,不改变宿主 descriptor、driver、since 字段或成熟度。
|
|
30
24
|
<!-- release-skill:managed:end id=latest-release -->
|
|
31
25
|
|
|
32
26
|
## 解决的问题
|
|
@@ -39,7 +33,7 @@ Harness 消费 `skill-family-contracts`(工作区依赖),复用其方言
|
|
|
39
33
|
|
|
40
34
|
## 安装和最小示例
|
|
41
35
|
|
|
42
|
-
0.
|
|
36
|
+
0.19.2 是本地源码候选。候选验证先把三个包分别打入同一个临时目录,再安装这三个精确 tarball:
|
|
43
37
|
|
|
44
38
|
```sh
|
|
45
39
|
pack_dir="$(mktemp -d)"
|
|
@@ -47,13 +41,13 @@ pack_dir="$(mktemp -d)"
|
|
|
47
41
|
(cd packages/skill-family-harness-node && pnpm pack --pack-destination "$pack_dir")
|
|
48
42
|
(cd packages/skill-family-engineering-kit && pnpm pack --pack-destination "$pack_dir")
|
|
49
43
|
mkdir "$pack_dir/consumer" && (cd "$pack_dir/consumer" && npm init -y)
|
|
50
|
-
(cd "$pack_dir/consumer" && npm install "$pack_dir/skill-family-contracts-0.
|
|
44
|
+
(cd "$pack_dir/consumer" && npm install "$pack_dir/skill-family-contracts-0.19.2.tgz" "$pack_dir/skill-family-harness-node-0.19.2.tgz" "$pack_dir/skill-family-engineering-kit-0.19.2.tgz")
|
|
51
45
|
```
|
|
52
46
|
|
|
53
47
|
发布后再使用 registry 坐标:
|
|
54
48
|
|
|
55
49
|
```sh
|
|
56
|
-
npm install skill-family-harness-node@0.19.
|
|
50
|
+
npm install skill-family-harness-node@0.19.2
|
|
57
51
|
npm info skill-family-harness-node --help
|
|
58
52
|
```
|
|
59
53
|
|
|
@@ -261,4 +255,4 @@ v2 机制会重算每个 path-backed output 和 evidence Resource 的真实字
|
|
|
261
255
|
|
|
262
256
|
另一个独立候选 `observeExecutableIdentity({ boundRoots, lookup, interpreterPolicy? })` 只对调用方显式提供的根和查找路径做逐次只读观察,供正式启动前紧邻重观察。`/usr/bin/env` shebang 通过显式 `pathEntries` 找到解释器时,结果保留解释器候选的完整 symlink chain,不折叠成最终文件。它不属于 `host-adapter`,也不证明 wrapper 控制流、ambient `PATH`、fd-exec/内核映像、签名信任、跨调用缓存、宿主支持/生命周期或领域接受;这些语义仍由调用方负责。候选入口存在不等于宿主已获资格。
|
|
263
257
|
|
|
264
|
-
0.
|
|
258
|
+
0.19.2 是本地源码候选,远端可用性须由对应的 release-skill 发布后证据证明。候选检查使用本地已验证的三包 tarball;版本标记、单元测试或安装成功都不等于契约接入完成、迁移完成或真实宿主资格。
|
|
@@ -176,21 +176,45 @@ function batchRefusal(kind, message) {
|
|
|
176
176
|
* path releases this function's listeners; refusal never terminates the
|
|
177
177
|
* caller process.
|
|
178
178
|
*/
|
|
179
|
-
function readBoundedBatchInput(input, byteLimit) {
|
|
179
|
+
function readBoundedBatchInput(input, byteLimit, outputWatch) {
|
|
180
180
|
return new Promise((resolve, reject) => {
|
|
181
181
|
const chunks = [];
|
|
182
182
|
let received = 0;
|
|
183
|
+
let settled = false;
|
|
184
|
+
let preexistingErrorFallback;
|
|
185
|
+
let deferredFailure;
|
|
186
|
+
let stopWatchingOutput = () => {};
|
|
187
|
+
const startedFlowing = input.readableFlowing !== true && input.listenerCount("data") === 0;
|
|
183
188
|
const cleanup = () => {
|
|
184
189
|
input.removeListener("data", onData);
|
|
185
190
|
input.removeListener("end", onEnd);
|
|
186
191
|
input.removeListener("error", onError);
|
|
192
|
+
input.removeListener("close", onClose);
|
|
193
|
+
stopWatchingOutput();
|
|
194
|
+
if (preexistingErrorFallback !== undefined) {
|
|
195
|
+
clearImmediate(preexistingErrorFallback);
|
|
196
|
+
preexistingErrorFallback = undefined;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
const rejectRead = (cause) => {
|
|
200
|
+
if (settled) return;
|
|
201
|
+
settled = true;
|
|
202
|
+
cleanup();
|
|
203
|
+
chunks.length = 0;
|
|
204
|
+
if (
|
|
205
|
+
startedFlowing &&
|
|
206
|
+
input.readableFlowing === true &&
|
|
207
|
+
input.listenerCount("data") === 0
|
|
208
|
+
) {
|
|
209
|
+
input.pause();
|
|
210
|
+
}
|
|
211
|
+
reject(cause);
|
|
187
212
|
};
|
|
188
213
|
const onData = (chunk) => {
|
|
189
214
|
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
190
215
|
const next = received + bytes.length;
|
|
191
216
|
if (next > byteLimit) {
|
|
192
|
-
|
|
193
|
-
reject(
|
|
217
|
+
rejectRead(
|
|
194
218
|
batchRefusal(
|
|
195
219
|
"batch-input-limit",
|
|
196
220
|
`mechanism batch input exceeds ${byteLimit} raw UTF-8 bytes`,
|
|
@@ -202,16 +226,45 @@ function readBoundedBatchInput(input, byteLimit) {
|
|
|
202
226
|
chunks.push(bytes);
|
|
203
227
|
};
|
|
204
228
|
const onEnd = () => {
|
|
229
|
+
if (settled) return;
|
|
230
|
+
settled = true;
|
|
205
231
|
cleanup();
|
|
206
|
-
|
|
232
|
+
const bytes = Buffer.concat(chunks, received);
|
|
233
|
+
chunks.length = 0;
|
|
234
|
+
resolve(bytes);
|
|
207
235
|
};
|
|
208
236
|
const onError = (cause) => {
|
|
209
|
-
|
|
210
|
-
|
|
237
|
+
rejectRead(deferredFailure ?? cause);
|
|
238
|
+
};
|
|
239
|
+
const onClose = () => {
|
|
240
|
+
rejectRead(
|
|
241
|
+
deferredFailure ?? input.errored ?? new Error("mechanism batch input closed before the request ended"),
|
|
242
|
+
);
|
|
243
|
+
};
|
|
244
|
+
const onOutputFailure = (cause) => {
|
|
245
|
+
if (input.errored) {
|
|
246
|
+
deferredFailure ??= cause;
|
|
247
|
+
if (preexistingErrorFallback === undefined) {
|
|
248
|
+
preexistingErrorFallback = setImmediate(() => rejectRead(deferredFailure));
|
|
249
|
+
}
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
rejectRead(cause);
|
|
211
253
|
};
|
|
212
254
|
input.on("data", onData);
|
|
213
255
|
input.on("end", onEnd);
|
|
214
256
|
input.on("error", onError);
|
|
257
|
+
input.on("close", onClose);
|
|
258
|
+
// A destroy(error) may have set `closed` while its public error event is
|
|
259
|
+
// still queued. Keep our listener through that event; the immediate also
|
|
260
|
+
// handles a stream whose error event had already been observed by callers.
|
|
261
|
+
if (input.errored) {
|
|
262
|
+
preexistingErrorFallback = setImmediate(() => rejectRead(deferredFailure ?? input.errored));
|
|
263
|
+
} else if (input.closed || input.readableEnded || input.destroyed) {
|
|
264
|
+
onClose();
|
|
265
|
+
}
|
|
266
|
+
stopWatchingOutput = outputWatch.onFailure(onOutputFailure);
|
|
267
|
+
if (settled) stopWatchingOutput();
|
|
215
268
|
});
|
|
216
269
|
}
|
|
217
270
|
|
|
@@ -250,31 +303,121 @@ function assertMechanismBatchShape(request) {
|
|
|
250
303
|
}
|
|
251
304
|
}
|
|
252
305
|
|
|
306
|
+
// Keeps only the batch call's short-lived listeners active before the actual
|
|
307
|
+
// write begins. This closes the gap where an already-destroyed Writable still
|
|
308
|
+
// owes its public error event, without ending or otherwise managing the stream.
|
|
309
|
+
function watchBatchWritable(stream, channelName) {
|
|
310
|
+
let failure = stream.errored;
|
|
311
|
+
let failureListener;
|
|
312
|
+
let pendingErrorFallback;
|
|
313
|
+
let resolvePendingError;
|
|
314
|
+
const pendingError = failure
|
|
315
|
+
? new Promise((resolve) => {
|
|
316
|
+
resolvePendingError = resolve;
|
|
317
|
+
pendingErrorFallback = setImmediate(resolve);
|
|
318
|
+
})
|
|
319
|
+
: undefined;
|
|
320
|
+
const onError = (cause) => {
|
|
321
|
+
failure ??= cause;
|
|
322
|
+
failureListener?.(failure);
|
|
323
|
+
if (resolvePendingError) {
|
|
324
|
+
clearImmediate(pendingErrorFallback);
|
|
325
|
+
pendingErrorFallback = undefined;
|
|
326
|
+
const resolve = resolvePendingError;
|
|
327
|
+
resolvePendingError = undefined;
|
|
328
|
+
resolve();
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
const onClose = () => {
|
|
332
|
+
failure ??= stream.errored ?? new Error(
|
|
333
|
+
`mechanism batch ${channelName} closed before its write completed`,
|
|
334
|
+
);
|
|
335
|
+
failureListener?.(failure);
|
|
336
|
+
};
|
|
337
|
+
stream.on("error", onError);
|
|
338
|
+
stream.on("close", onClose);
|
|
339
|
+
if (!failure && (stream.closed || stream.writableEnded || stream.destroyed)) onClose();
|
|
340
|
+
return {
|
|
341
|
+
pendingError,
|
|
342
|
+
failure: () => failure,
|
|
343
|
+
onFailure(listener) {
|
|
344
|
+
failureListener = listener;
|
|
345
|
+
if (failure) listener(failure);
|
|
346
|
+
return () => {
|
|
347
|
+
if (failureListener === listener) failureListener = undefined;
|
|
348
|
+
};
|
|
349
|
+
},
|
|
350
|
+
release() {
|
|
351
|
+
failureListener = undefined;
|
|
352
|
+
stream.removeListener("error", onError);
|
|
353
|
+
stream.removeListener("close", onClose);
|
|
354
|
+
if (pendingErrorFallback !== undefined) clearImmediate(pendingErrorFallback);
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
253
359
|
/**
|
|
254
|
-
* Waits
|
|
255
|
-
*
|
|
360
|
+
* Waits for this write's callback rather than treating the writable's buffer
|
|
361
|
+
* signal as delivery completion. The caller retains ownership of the stream,
|
|
362
|
+
* so completion neither ends it nor waits for the whole stream to finish.
|
|
256
363
|
*/
|
|
257
|
-
function writeAll(stream, text) {
|
|
364
|
+
function writeAll(stream, text, channelName) {
|
|
258
365
|
return new Promise((resolve, reject) => {
|
|
259
|
-
|
|
260
|
-
|
|
366
|
+
let settled = false;
|
|
367
|
+
let callbackFailureFallback;
|
|
368
|
+
const cleanup = () => {
|
|
369
|
+
stream.removeListener("error", onError);
|
|
370
|
+
stream.removeListener("close", onClose);
|
|
371
|
+
if (callbackFailureFallback !== undefined) {
|
|
372
|
+
clearImmediate(callbackFailureFallback);
|
|
373
|
+
callbackFailureFallback = undefined;
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
const rejectWrite = (cause) => {
|
|
377
|
+
if (settled) return;
|
|
378
|
+
settled = true;
|
|
379
|
+
cleanup();
|
|
261
380
|
reject(cause);
|
|
262
381
|
};
|
|
263
|
-
const
|
|
264
|
-
|
|
382
|
+
const onError = (cause) => {
|
|
383
|
+
rejectWrite(cause);
|
|
384
|
+
};
|
|
385
|
+
const onClose = () => {
|
|
386
|
+
rejectWrite(
|
|
387
|
+
stream.errored ?? new Error(`mechanism batch ${channelName} closed before its write completed`),
|
|
388
|
+
);
|
|
389
|
+
};
|
|
390
|
+
const onWrite = (cause) => {
|
|
391
|
+
if (settled) return;
|
|
392
|
+
if (cause) {
|
|
393
|
+
// A standard Writable emits its matching error just after invoking the
|
|
394
|
+
// write callback. Keep our listener through that event so the failure
|
|
395
|
+
// cannot become an unhandled error; the immediate is a fallback for a
|
|
396
|
+
// writable-like caller that reports only through the callback.
|
|
397
|
+
callbackFailureFallback = setImmediate(() => rejectWrite(cause));
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
settled = true;
|
|
401
|
+
cleanup();
|
|
265
402
|
resolve();
|
|
266
403
|
};
|
|
267
404
|
stream.on("error", onError);
|
|
405
|
+
stream.on("close", onClose);
|
|
406
|
+
// As with input, an errored destroy may still owe its public error event.
|
|
407
|
+
// Waiting for that event preserves the cause and prevents it from becoming
|
|
408
|
+
// unhandled after an eager preflight rejection.
|
|
409
|
+
if (stream.errored) {
|
|
410
|
+
callbackFailureFallback = setImmediate(() => rejectWrite(stream.errored));
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (stream.closed || stream.writableEnded || stream.destroyed) {
|
|
414
|
+
onClose();
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
268
417
|
try {
|
|
269
|
-
|
|
270
|
-
stream.removeListener("error", onError);
|
|
271
|
-
resolve();
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
stream.once("drain", onDrain);
|
|
418
|
+
stream.write(text, onWrite);
|
|
275
419
|
} catch (cause) {
|
|
276
|
-
|
|
277
|
-
reject(cause);
|
|
420
|
+
rejectWrite(cause);
|
|
278
421
|
}
|
|
279
422
|
});
|
|
280
423
|
}
|
|
@@ -298,8 +441,16 @@ export async function runMechanismCliBatch({
|
|
|
298
441
|
error = stderr,
|
|
299
442
|
invoke = invokeFoundationMechanism,
|
|
300
443
|
} = {}) {
|
|
444
|
+
const outputWatch = watchBatchWritable(output, "output");
|
|
445
|
+
const errorWatch = watchBatchWritable(error, "error output");
|
|
301
446
|
try {
|
|
302
|
-
|
|
447
|
+
// Start the input read synchronously so a queued input error is covered
|
|
448
|
+
// before any output preflight can await or fail this batch.
|
|
449
|
+
const bytes = await readBoundedBatchInput(
|
|
450
|
+
input,
|
|
451
|
+
MECHANISM_BATCH_POLICY.inputByteLimit,
|
|
452
|
+
outputWatch,
|
|
453
|
+
);
|
|
303
454
|
const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
304
455
|
const request = JSON.parse(text);
|
|
305
456
|
assertMechanismBatchShape(request);
|
|
@@ -337,18 +488,34 @@ export async function runMechanismCliBatch({
|
|
|
337
488
|
fragments.push(fragment);
|
|
338
489
|
if (exitCode === 2) anyItemFailure = true;
|
|
339
490
|
}
|
|
491
|
+
const outputFailure = outputWatch.failure();
|
|
492
|
+
outputWatch.release();
|
|
493
|
+
if (outputFailure) throw outputFailure;
|
|
340
494
|
await writeAll(
|
|
341
495
|
output,
|
|
342
496
|
`${BATCH_OUTPUT_PREFIX}${fragments.join(",")}${BATCH_OUTPUT_SUFFIX}`,
|
|
497
|
+
"output",
|
|
343
498
|
);
|
|
499
|
+
if (errorWatch.pendingError) await errorWatch.pendingError;
|
|
500
|
+
errorWatch.release();
|
|
344
501
|
return anyItemFailure ? 2 : 0;
|
|
345
502
|
} catch (cause) {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
503
|
+
const pendingErrors = [outputWatch.pendingError, errorWatch.pendingError].filter(Boolean);
|
|
504
|
+
if (pendingErrors.length > 0) await Promise.all(pendingErrors);
|
|
505
|
+
outputWatch.release();
|
|
506
|
+
const errorFailure = errorWatch.failure();
|
|
507
|
+
errorWatch.release();
|
|
508
|
+
if (!errorFailure) {
|
|
509
|
+
try {
|
|
510
|
+
await writeAll(error, `${JSON.stringify(errorResponse(cause))}\n`, "error output");
|
|
511
|
+
} catch {
|
|
512
|
+
// The error channel itself failed; the batch still exits failed.
|
|
513
|
+
}
|
|
350
514
|
}
|
|
351
515
|
return 2;
|
|
516
|
+
} finally {
|
|
517
|
+
outputWatch.release();
|
|
518
|
+
errorWatch.release();
|
|
352
519
|
}
|
|
353
520
|
}
|
|
354
521
|
|
|
@@ -363,7 +530,9 @@ if (process.argv[1] && realpathSync(fileURLToPath(import.meta.url)) === realpath
|
|
|
363
530
|
);
|
|
364
531
|
process.exitCode = 2;
|
|
365
532
|
} else {
|
|
366
|
-
|
|
533
|
+
const exitCode = await runMechanismCliBatch();
|
|
534
|
+
if (exitCode === 2 && !stdin.destroyed) stdin.destroy();
|
|
535
|
+
process.exitCode = exitCode;
|
|
367
536
|
}
|
|
368
537
|
} else {
|
|
369
538
|
process.exitCode = await runMechanismCli();
|
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.19.
|
|
13
|
+
"skill-family-contracts": "0.19.2",
|
|
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.19.
|
|
52
|
+
"version": "0.19.2",
|
|
53
53
|
"scripts": {
|
|
54
54
|
"check": "node --test",
|
|
55
55
|
"test": "node --test"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
version: 0.19.1
|
|
2
|
+
date: 2026-09-08
|
|
3
|
+
locales:
|
|
4
|
+
en:
|
|
5
|
+
summary: Harness 0.19.1 makes the candidate mechanism-batch entry point settle on the current transfer's actual completion or failure while preserving caller ownership of programmatic streams.
|
|
6
|
+
changes:
|
|
7
|
+
fixed:
|
|
8
|
+
- Waits for the current write callback, handles premature input and output closure, and removes only listeners installed by the batch helper.
|
|
9
|
+
- Stops the library helper's own reading and releases its buffer after input overflow without destroying a caller-owned stream.
|
|
10
|
+
- Releases CLI-owned standard input after an oversized request is reported, allowing the process to exit 2 while the upstream pipe remains open.
|
|
11
|
+
upgradeNotes: Pin all three Foundation packages to exactly 0.19.1. The batch operation, capacity policy, per-item order, and single-request CLI remain unchanged. This entry is candidate and requires re-verification after an upgrade.
|
|
12
|
+
zh-CN:
|
|
13
|
+
summary: Harness 0.19.1 让候选机制批量入口等待本次传输真正完成或失败,同时保留程序化入口中调用方对流的所有权。
|
|
14
|
+
changes:
|
|
15
|
+
fixed:
|
|
16
|
+
- 等待本次写入回调,处理输入与输出提前关闭,并且只移除批量辅助函数自己安装的监听器。
|
|
17
|
+
- 输入超限后停止库辅助函数自己的读取并释放缓存,不销毁调用方拥有的流。
|
|
18
|
+
- 超限错误写出后释放 CLI 自己拥有的标准输入,使上游管道保持打开时进程仍能自行退出 2。
|
|
19
|
+
upgradeNotes: 三个 Foundation 包须一起精确锁定到 0.19.1。批量操作、容量政策、逐项顺序和旧单请求 CLI 保持不变。该入口仍是候选能力,升级后须重新验证。
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
version: 0.19.2
|
|
2
|
+
date: 2026-09-08
|
|
3
|
+
locales:
|
|
4
|
+
en:
|
|
5
|
+
summary: Harness 0.19.2 aligns its package version with the Foundation Adoption Review distribution patch. Existing mechanisms and runtime behavior are unchanged.
|
|
6
|
+
changes:
|
|
7
|
+
changed:
|
|
8
|
+
- Aligns the package version and release documentation with Foundation 0.19.2 without changing Harness implementation or driver behavior.
|
|
9
|
+
upgradeNotes: Pin all three Foundation packages to exactly 0.19.2. This patch adds no Harness capability and does not change host descriptors, drivers, since values, or maturity.
|
|
10
|
+
zh-CN:
|
|
11
|
+
summary: Harness 0.19.2 将包版本与 Foundation Adoption Review 分发补丁对齐。既有机制和运行时行为保持不变。
|
|
12
|
+
changes:
|
|
13
|
+
changed:
|
|
14
|
+
- 包版本和发布说明与 Foundation 0.19.2 数值对齐;Harness 实现和 driver 行为不变。
|
|
15
|
+
upgradeNotes: 三个 Foundation 包须一起精确锁定到 0.19.2。本补丁不增加 Harness 能力,不改变宿主 descriptor、driver、since 字段或成熟度。
|
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.19.
|
|
2
|
+
export const FOUNDATION_PACKAGE_VERSION = "0.19.2";
|