skill-family-harness-node 0.5.0 → 0.7.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 CHANGED
@@ -1,5 +1,44 @@
1
1
  # Changelog
2
2
 
3
+ <!-- release-skill:changelog:start version=0.7.0 locale=en baseline=sha256:062a81c93d213982e9d6beda1e574959ba98b18170f0c703efb0dd71573a3e77 -->
4
+ ## [0.7.0] - 2026-08-21
5
+
6
+ Lockstep version bump with the Foundation 0.7.0 line; the thin mechanism runtime is unchanged.
7
+
8
+ ### Changed
9
+
10
+ - No capability change - HARNESS_CAPABILITIES stays at 21 items and every exported mechanism (atomic contained writes, path containment, strict authority read, resource closure, digests, bounded subprocess supervision, URL credential redaction) keeps its 0.6.0 contract; the package version moves in lockstep with the Foundation line because the three leaf packages share one public version coordinate.
11
+
12
+ ### Upgrade Notes
13
+
14
+ Version 0.7.0 carries no harness surface change. Consumers keep their existing pins; the harness computeResourceClosure resource closure remains distinct from the Kit plan closure introduced by engineering-kit 0.7.0 and the two are not interchangeable.
15
+ <!-- release-skill:changelog:end version=0.7.0 locale=en -->
16
+
17
+
18
+ <!-- release-skill:changelog:start version=0.6.0 locale=en baseline=sha256:c67e713aebe53382c651655a2be9e78ace8da6a1b7852c1e6acadcb383ff2422 -->
19
+ ## [0.6.0] - 2026-08-21
20
+
21
+ This release adds bounded subprocess supervision (FND-ADR-012), completes the Foundation strict authority read path with contained nested directory preparation (FG-1), adds pre-persistence URL credential redaction (FG-2), and grows HARNESS_CAPABILITIES from 18 to 21.
22
+
23
+ ### Added
24
+
25
+ - Adds superviseProcess with validateTimeoutPolicy, WATCHDOG_REASONS, TERMINATION_REASONS, PROCESS_STATUSES and ENVELOPE_GUARANTEES (FND-ADR-012) - one bounded spawn, liveness by explicit events, consumer-supplied timeout policy, SIGTERM then grace then SIGKILL against the process group, and a single closed-enum termination envelope; the mechanism never restarts the supervised process and never holds timeout values.
26
+ - Adds readFileStrict (FG-1), the read-side twin of the strict write path - containment first, symlink refusal with O_NOFOLLOW, regular-file identity re-assertion on the opened handle (dev/ino), and a sha256 digest receipt of the exact bytes read; an optional expectedSha256 content guard fails closed before any delivery.
27
+ - Adds the createParents option to publishFileExclusive (FG-1) - the missing portion of the parent chain is prepared as real directories inside the containment layer and every intermediate entry is re-verified as a real directory; symlink components are still refused and no entry is ever replaced.
28
+ - Adds redactUrlCredentials with REDACTED_URL_PLACEHOLDER (FG-2) - strips the userinfo component from any URL before the value reaches disk or logs; unparseable input degrades to an opaque placeholder and never leaks to the output.
29
+ - Re-exports the contracts-owned token estimate consumption contract (consumeTokenEstimate, consumeTokenEstimateStrict and companions) next to estimateTokens, and carries the authoritative token estimator estimateTokens with the skill-family-token-estimate CLI (audit remediation C1).
30
+
31
+ ### Changed
32
+
33
+ - Grows HARNESS_CAPABILITIES from 18 to 21 (adds supervise-process, strict-read and url-credential-redaction); the strict write path keeps its no-replace, byte-verified receipt semantics unchanged.
34
+ - Keeps business semantics, retry/restart policy, budget thresholds, and the decision of which values are URLs under consumer ownership; the harness owns mechanism only.
35
+
36
+ ### Upgrade Notes
37
+
38
+ Version 0.6.0 is the Foundation capability completion line. The createParents option of publishFileExclusive is a Foundation-side profile behavior change under the 2026-08-19 discipline; consumers needing contained nested publication must pin exactly 0.6.0.
39
+ <!-- release-skill:changelog:end version=0.6.0 locale=en -->
40
+
41
+
3
42
  <!-- release-skill:changelog:start version=0.5.0 locale=en baseline=sha256:03eca87a8c9bea814e741518168696f17c869bdb71baf2046cef4c3596d9ebf3 -->
4
43
  ## [0.5.0] - 2026-08-16
5
44
 
@@ -1,5 +1,44 @@
1
1
  # 变更日志
2
2
 
3
+ <!-- release-skill:changelog:start version=0.7.0 locale=zh-CN baseline=sha256:b3c207e2dd4134b75e5219842ee64a43c6da964763a86f7e360f0d0b60667516 -->
4
+ ## [0.7.0] - 2026-08-21
5
+
6
+ 随 Foundation 0.7.0 线锁步升版;薄机制运行时不变。
7
+
8
+ ### 变更
9
+
10
+ - 能力无变更——HARNESS_CAPABILITIES 保持 21 项,全部导出机制(原子受收容写、路径收容、严格权威读取、资源闭包、摘要、有界子进程监督、URL 凭证脱敏)保持 0.6.0 合同;包版本随 Foundation 线锁步,因为三个叶子包共用同一公开版本坐标。
11
+
12
+ ### 升级说明
13
+
14
+ 0.7.0 不携带任何 harness 表面变更。消费者保持既有锁定;harness computeResourceClosure 的资源闭包与 engineering-kit 0.7.0 引入的 Kit 计划闭包仍然形状不同、用途不同,二者不能互换。
15
+ <!-- release-skill:changelog:end version=0.7.0 locale=zh-CN -->
16
+
17
+
18
+ <!-- release-skill:changelog:start version=0.6.0 locale=zh-CN baseline=sha256:6a4a94f692909faf2090445638988b27841a8bca67a85fd7812864d656299175 -->
19
+ ## [0.6.0] - 2026-08-21
20
+
21
+ 本版新增有界子进程监督(FND-ADR-012),补齐 Foundation 严格权威读取路径与受收容嵌套目录准备(FG-1),新增落盘前 URL 凭证脱敏(FG-2),HARNESS_CAPABILITIES 从 18 项增至 21 项。
22
+
23
+ ### 新增
24
+
25
+ - 新增 superviseProcess 与 validateTimeoutPolicy、WATCHDOG_REASONS、TERMINATION_REASONS、PROCESS_STATUSES、ENVELOPE_GUARANTEES(FND-ADR-012):单次有界 spawn、显式事件判活、消费者提供的超时策略、对进程组 SIGTERM→宽限→SIGKILL、单一闭集枚举终止信封;机制从不重启被监督进程,也从不持有超时数值。
26
+ - 新增 readFileStrict(FG-1):严格写入路径的读取孪生体——先收容、拒绝符号链接(O_NOFOLLOW)、在已打开句柄上复核常规文件身份(dev/ino),并返回所读精确字节的 sha256 摘要回执;可选 expectedSha256 内容守卫在任何交付前失败关闭。
27
+ - publishFileExclusive 新增 createParents 选项(FG-1):父目录链缺失部分在收容层内准备为真实目录,每个中间条目都复核为真实目录;符号链接组件仍被拒绝,任何条目都不被替换。
28
+ - 新增 redactUrlCredentials 与 REDACTED_URL_PLACEHOLDER(FG-2):任何 URL 的 userinfo 部分在数值到达磁盘或日志前剥离;不可解析输入退化为不透明占位符,绝不泄漏到输出。
29
+ - 在 estimateTokens 旁转导出 contracts 拥有的 token 估算消费合同(consumeTokenEstimate、consumeTokenEstimateStrict 及伴生常量),并承载权威词元估算器 estimateTokens 与 skill-family-token-estimate CLI(审计整改 C1)。
30
+
31
+ ### 变更
32
+
33
+ - HARNESS_CAPABILITIES 从 18 项增至 21 项(新增 supervise-process、strict-read、url-credential-redaction);严格写入路径的不替换、字节验证回执语义保持不变。
34
+ - 业务语义、重试/重启策略、预算阈值与「哪些值是 URL」的判定继续归消费者所有;harness 只拥有机制。
35
+
36
+ ### 升级说明
37
+
38
+ 0.6.0 是 Foundation 能力补齐线。publishFileExclusive 的 createParents 选项是按 2026-08-19 纪律的 Foundation 侧 profile 行为变更;需要受收容嵌套发布的消费者必须精确锁定 0.6.0。
39
+ <!-- release-skill:changelog:end version=0.6.0 locale=zh-CN -->
40
+
41
+
3
42
  <!-- release-skill:changelog:start version=0.5.0 locale=zh-CN baseline=sha256:003472359596147fe26ad2ba53a82303a217c28cb89a6afacdaa955793001f71 -->
4
43
  ## [0.5.0] - 2026-08-16
5
44
 
package/README.md CHANGED
@@ -4,31 +4,22 @@
4
4
 
5
5
  # skill-family-harness-node
6
6
 
7
- <!-- release-skill:release-version: 0.5.0 -->
7
+ <!-- release-skill:release-version: 0.7.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.5.0** (2026-08-16)
12
+ **0.7.0** (2026-08-21)
13
13
 
14
- This release adds the declared read surface assertion (FND-ADR-010) and the structured surface scanner (FND-ADR-011) to the stable Harness surface, growing HARNESS_CAPABILITIES from 16 to 18 entries, and adds three dependency-reviewed runtime libraries.
15
-
16
- **Added**
17
-
18
- - Adds assertDeclaredReadSurface (FND-ADR-010): a no-execution, syntax-surface-only assertion that every node:fs named import inside a declared module set stays inside the consumer-declared read surface, with a closed violation vocabulary and a frozen declared-read-surface-result envelope.
19
- - Adds scanSurfaceStructured (FND-ADR-011): the structured sibling of scanSurface. IP-shaped candidates enter a single standard parse entry (ipaddr.js) with consumer-declared CIDR approval and fail closed when unparseable; scoped and unscoped coordinates, registries and hosts need consumer-declared approval; format adapters (pnpm-lockfile via @pnpm/lockfile.fs with yaml AST comment regions, and tree-json) parse structurally with no position-level exemptions; binary and symlink policies fail closed. The closed nine-rule vocabulary travels in details.rule of the structured-scan-violation mechanism error.
20
- - Adds three dependency-reviewed runtime libraries for the structured scanner, reviewed per FND-ADR-006 section-4 analogy (dependency-closure pre-review, executor self-review pending independent review): ipaddr.js 2.5.0 (MIT, zero dependencies), yaml 2.9.0 (ISC), and @pnpm/lockfile.fs 1001.1.35 (MIT, 18 transitive dependencies all inside the approved public coordinates).
21
- - Grows HARNESS_CAPABILITIES from 16 to 18 entries for the two new mechanisms.
14
+ Lockstep version bump with the Foundation 0.7.0 line; the thin mechanism runtime is unchanged.
22
15
 
23
16
  **Changed**
24
17
 
25
- - Documents the relationship of the two policy documents to workspace-private leak policies: a workspace-private leak-policy.json instance document is not a subset, not isomorphic and not a migration target of the surface-scan-policy or structured-scan-policy schemas the documents share rule vocabulary and fail-closed semantics by design, but their byte-level shapes are independent and must not be compared for compatibility. scanSurface is the execution-core generalization projection: the public, consumer-parameterized form of the same mechanism family, without any private identity, path, or approval-list interpretation of its own.
26
- - Records the cost of the dependency-review decision honestly: the harness tarball grows with the @pnpm/lockfile.fs closure, the thin-runtime property changes from zero third-party runtime dependencies to three reviewed ones, and the pnpm-lockfile adapter writes one temporary lockfile copy under the OS temp directory (removed afterwards). The engineering-kit offline-consumer verification gates now derive the complete third-party production closure of the three Foundation packages mechanically (identity-deduplicated, npm: alias-aware, range-scoped override selectors) instead of a single-package closure, so the review decision is continuously verified against the real installed bytes.
27
- - Keeps mechanism purity: no execution of scanned files, no model calls, no network; symlinked entries are never followed.
18
+ - No capability change - HARNESS_CAPABILITIES stays at 21 items and every exported mechanism (atomic contained writes, path containment, strict authority read, resource closure, digests, bounded subprocess supervision, URL credential redaction) keeps its 0.6.0 contract; the package version moves in lockstep with the Foundation line because the three leaf packages share one public version coordinate.
28
19
 
29
20
  **Upgrade Notes**
30
21
 
31
- Version 0.5.0 is the FND-ADR-010/011 harness line. Mechanism imports use the stable capability names published in HARNESS_CAPABILITIES; structured-scan policies must pass the structured-scan-policy contract validation of contract-spec 1.5.0.
22
+ Version 0.7.0 carries no harness surface change. Consumers keep their existing pins; the harness computeResourceClosure resource closure remains distinct from the Kit plan closure introduced by engineering-kit 0.7.0 and the two are not interchangeable.
32
23
  <!-- release-skill:managed:end id=latest-release -->
33
24
 
34
25
  ## Problem It Solves
@@ -42,14 +33,14 @@ The Harness consumes `skill-family-contracts` (a workspace dependency), reusing
42
33
  ## Installation and Minimal Example
43
34
 
44
35
  ```sh
45
- npm install skill-family-harness-node@0.5.0
36
+ npm install skill-family-harness-node@0.7.0
46
37
  npm info skill-family-harness-node --help
47
38
  ```
48
39
 
49
40
  The minimal example shows validating a contract document inside Node:
50
41
 
51
42
  ```js
52
- // Run from an empty directory: npm install skill-family-harness-node@0.5.0
43
+ // Run from an empty directory: npm install skill-family-harness-node@0.7.0
53
44
  import { validateContractDocument } from "skill-family-harness-node";
54
45
 
55
46
  const document = {
package/README.zh-CN.md CHANGED
@@ -5,31 +5,22 @@
5
5
 
6
6
  # skill-family-harness-node
7
7
 
8
- <!-- release-skill:release-version: 0.5.0 -->
8
+ <!-- release-skill:release-version: 0.7.0 -->
9
9
 
10
10
  Contracts 机制协议的**唯一默认 Node 实现**。这是一个薄运行时(thin runtime):只实现机制协议,不引入业务语义,不做第二语言实现。
11
11
 
12
12
  <!-- release-skill:managed:start id=latest-release -->
13
- **0.5.0** (2026-08-16)
13
+ **0.7.0** (2026-08-21)
14
14
 
15
- 本版在稳定 Harness 面上新增声明读取表面断言(FND-ADR-010)与结构化表面扫描器(FND-ADR-011),HARNESS_CAPABILITIES 从 16 项增至 18 项,并引入三个经依赖评审的运行时库。
16
-
17
- **新增**
18
-
19
- - 新增 assertDeclaredReadSurface(FND-ADR-010):不执行、仅语法面的断言,声明模块集内的每个 node:fs 具名导入必须落在消费者声明的读取表面内,违规词汇表为闭集,返回冻结的 declared-read-surface-result 信封。
20
- - 新增 scanSurfaceStructured(FND-ADR-011):scanSurface 的结构化兄弟。IP 形候选统一进入单一标准解析入口(ipaddr.js),按消费者声明的 CIDR 批准,不可解析即失败关闭;坐标(scoped 与非 scoped)、注册表与主机均需消费者声明批准;格式适配器(pnpm-lockfile 经 @pnpm/lockfile.fs + yaml AST 注释区域,tree-json 经 JSON.parse)结构化解析且无位置级豁免;二进制与符号链接策略失败关闭。闭集九规则词汇表经 structured-scan-violation 机制错误的 details.rule 承载。
21
- - 为结构化扫描器引入三个经依赖评审的运行时库,评审按 FND-ADR-006 第 4 节类比执行(依赖闭包预审,执行者自审,独立复核另行安排):ipaddr.js 2.5.0(MIT,零依赖)、yaml 2.9.0(ISC)、@pnpm/lockfile.fs 1001.1.35(MIT,18 个传递依赖全部在批准公共坐标内)。
22
- - HARNESS_CAPABILITIES 从 16 项增至 18 项,覆盖两个新机制。
15
+ Foundation 0.7.0 线锁步升版;薄机制运行时不变。
23
16
 
24
17
  **变更**
25
18
 
26
- - 显式说明两个策略文档与工作区私有 leak 策略的关系:工作区私有的 leak-policy.json 实例文档既不是 surface-scan-policy structured-scan-policy schema 的子集、也不同构、更不是迁移目标——两类文档按设计共享规则词汇与失败关闭语义,但字节级形状相互独立,不得比较兼容性。scanSurface 是执行内核通用化投影:同一机制族的公开、消费者参数化形态,自身不解释任何私有身份、路径或批准清单。
27
- - 如实记录依赖评审决策的成本:harness tarball 随 @pnpm/lockfile.fs 闭包扩大;薄运行时属性从零第三方运行时依赖变为三个经评审依赖;pnpm-lockfile 适配器会在 OS 临时目录写一份临时锁文件副本(用后即删)。engineering-kit 的离线消费者验证门随之把第三方闭包推导从单包闭包机械扩展为三个 Foundation 包的完整生产闭包(真实身份去重、npm: 别名感知、range-scoped override selector),使评审决策持续对着真实安装字节被验证。
28
- - 保持机制纯度:不执行被扫描文件、无模型调用、无网络;永不跟随符号链接。
19
+ - 能力无变更——HARNESS_CAPABILITIES 保持 21 项,全部导出机制(原子受收容写、路径收容、严格权威读取、资源闭包、摘要、有界子进程监督、URL 凭证脱敏)保持 0.6.0 合同;包版本随 Foundation 线锁步,因为三个叶子包共用同一公开版本坐标。
29
20
 
30
21
  **升级说明**
31
22
 
32
- 0.5.0 FND-ADR-010/011 harness 线。机制导入使用 HARNESS_CAPABILITIES 公布的稳定能力名;structured-scan 策略必须通过契约规格 1.5.0 structured-scan-policy 契约校验。
23
+ 0.7.0 不携带任何 harness 表面变更。消费者保持既有锁定;harness computeResourceClosure 的资源闭包与 engineering-kit 0.7.0 引入的 Kit 计划闭包仍然形状不同、用途不同,二者不能互换。
33
24
  <!-- release-skill:managed:end id=latest-release -->
34
25
 
35
26
  ## 解决的问题
@@ -43,14 +34,14 @@ Harness 消费 `skill-family-contracts`(工作区依赖),复用其方言
43
34
  ## 安装和最小示例
44
35
 
45
36
  ```sh
46
- npm install skill-family-harness-node@0.5.0
37
+ npm install skill-family-harness-node@0.7.0
47
38
  npm info skill-family-harness-node --help
48
39
  ```
49
40
 
50
41
  最小示例演示在 Node 内校验一份契约文档:
51
42
 
52
43
  ```js
53
- // 从空目录运行:npm install skill-family-harness-node@0.5.0
44
+ // 从空目录运行:npm install skill-family-harness-node@0.7.0
54
45
  import { validateContractDocument } from "skill-family-harness-node";
55
46
 
56
47
  const document = {
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"npx projen\".",
3
3
  "author": "广州市风荷科技有限公司",
4
+ "bin": {
5
+ "skill-family-token-estimate": "./src/token-estimate-cli.mjs"
6
+ },
4
7
  "bugs": {
5
8
  "url": "https://github.com/ifoohoo/skill-family-harness-node/issues"
6
9
  },
7
10
  "dependencies": {
8
11
  "@pnpm/lockfile.fs": "1001.1.35",
9
12
  "ipaddr.js": "2.5.0",
10
- "skill-family-contracts": "0.5.0",
13
+ "skill-family-contracts": "0.7.0",
11
14
  "yaml": "2.9.0"
12
15
  },
13
16
  "description": "Thin Node.js mechanism runtime for Skill Family engineering contracts.",
@@ -42,7 +45,7 @@
42
45
  "url": "https://github.com/ifoohoo/skill-family-harness-node.git"
43
46
  },
44
47
  "type": "module",
45
- "version": "0.5.0",
48
+ "version": "0.7.0",
46
49
  "scripts": {
47
50
  "check": "node --test",
48
51
  "test": "node --test"
@@ -0,0 +1,29 @@
1
+ version: 0.6.0
2
+ date: 2026-08-21
3
+ locales:
4
+ en:
5
+ summary: This release adds bounded subprocess supervision (FND-ADR-012), completes the Foundation strict authority read path with contained nested directory preparation (FG-1), adds pre-persistence URL credential redaction (FG-2), and grows HARNESS_CAPABILITIES from 18 to 21.
6
+ changes:
7
+ added:
8
+ - Adds superviseProcess with validateTimeoutPolicy, WATCHDOG_REASONS, TERMINATION_REASONS, PROCESS_STATUSES and ENVELOPE_GUARANTEES (FND-ADR-012) - one bounded spawn, liveness by explicit events, consumer-supplied timeout policy, SIGTERM then grace then SIGKILL against the process group, and a single closed-enum termination envelope; the mechanism never restarts the supervised process and never holds timeout values.
9
+ - Adds readFileStrict (FG-1), the read-side twin of the strict write path - containment first, symlink refusal with O_NOFOLLOW, regular-file identity re-assertion on the opened handle (dev/ino), and a sha256 digest receipt of the exact bytes read; an optional expectedSha256 content guard fails closed before any delivery.
10
+ - Adds the createParents option to publishFileExclusive (FG-1) - the missing portion of the parent chain is prepared as real directories inside the containment layer and every intermediate entry is re-verified as a real directory; symlink components are still refused and no entry is ever replaced.
11
+ - Adds redactUrlCredentials with REDACTED_URL_PLACEHOLDER (FG-2) - strips the userinfo component from any URL before the value reaches disk or logs; unparseable input degrades to an opaque placeholder and never leaks to the output.
12
+ - Re-exports the contracts-owned token estimate consumption contract (consumeTokenEstimate, consumeTokenEstimateStrict and companions) next to estimateTokens, and carries the authoritative token estimator estimateTokens with the skill-family-token-estimate CLI (audit remediation C1).
13
+ changed:
14
+ - Grows HARNESS_CAPABILITIES from 18 to 21 (adds supervise-process, strict-read and url-credential-redaction); the strict write path keeps its no-replace, byte-verified receipt semantics unchanged.
15
+ - Keeps business semantics, retry/restart policy, budget thresholds, and the decision of which values are URLs under consumer ownership; the harness owns mechanism only.
16
+ upgradeNotes: Version 0.6.0 is the Foundation capability completion line. The createParents option of publishFileExclusive is a Foundation-side profile behavior change under the 2026-08-19 discipline; consumers needing contained nested publication must pin exactly 0.6.0.
17
+ zh-CN:
18
+ summary: 本版新增有界子进程监督(FND-ADR-012),补齐 Foundation 严格权威读取路径与受收容嵌套目录准备(FG-1),新增落盘前 URL 凭证脱敏(FG-2),HARNESS_CAPABILITIES 从 18 项增至 21 项。
19
+ changes:
20
+ added:
21
+ - 新增 superviseProcess 与 validateTimeoutPolicy、WATCHDOG_REASONS、TERMINATION_REASONS、PROCESS_STATUSES、ENVELOPE_GUARANTEES(FND-ADR-012):单次有界 spawn、显式事件判活、消费者提供的超时策略、对进程组 SIGTERM→宽限→SIGKILL、单一闭集枚举终止信封;机制从不重启被监督进程,也从不持有超时数值。
22
+ - 新增 readFileStrict(FG-1):严格写入路径的读取孪生体——先收容、拒绝符号链接(O_NOFOLLOW)、在已打开句柄上复核常规文件身份(dev/ino),并返回所读精确字节的 sha256 摘要回执;可选 expectedSha256 内容守卫在任何交付前失败关闭。
23
+ - publishFileExclusive 新增 createParents 选项(FG-1):父目录链缺失部分在收容层内准备为真实目录,每个中间条目都复核为真实目录;符号链接组件仍被拒绝,任何条目都不被替换。
24
+ - 新增 redactUrlCredentials 与 REDACTED_URL_PLACEHOLDER(FG-2):任何 URL 的 userinfo 部分在数值到达磁盘或日志前剥离;不可解析输入退化为不透明占位符,绝不泄漏到输出。
25
+ - 在 estimateTokens 旁转导出 contracts 拥有的 token 估算消费合同(consumeTokenEstimate、consumeTokenEstimateStrict 及伴生常量),并承载权威词元估算器 estimateTokens 与 skill-family-token-estimate CLI(审计整改 C1)。
26
+ changed:
27
+ - HARNESS_CAPABILITIES 从 18 项增至 21 项(新增 supervise-process、strict-read、url-credential-redaction);严格写入路径的不替换、字节验证回执语义保持不变。
28
+ - 业务语义、重试/重启策略、预算阈值与「哪些值是 URL」的判定继续归消费者所有;harness 只拥有机制。
29
+ upgradeNotes: 0.6.0 是 Foundation 能力补齐线。publishFileExclusive 的 createParents 选项是按 2026-08-19 纪律的 Foundation 侧 profile 行为变更;需要受收容嵌套发布的消费者必须精确锁定 0.6.0。
@@ -0,0 +1,15 @@
1
+ version: 0.7.0
2
+ date: 2026-08-21
3
+ locales:
4
+ en:
5
+ summary: Lockstep version bump with the Foundation 0.7.0 line; the thin mechanism runtime is unchanged.
6
+ changes:
7
+ changed:
8
+ - No capability change - HARNESS_CAPABILITIES stays at 21 items and every exported mechanism (atomic contained writes, path containment, strict authority read, resource closure, digests, bounded subprocess supervision, URL credential redaction) keeps its 0.6.0 contract; the package version moves in lockstep with the Foundation line because the three leaf packages share one public version coordinate.
9
+ upgradeNotes: Version 0.7.0 carries no harness surface change. Consumers keep their existing pins; the harness computeResourceClosure resource closure remains distinct from the Kit plan closure introduced by engineering-kit 0.7.0 and the two are not interchangeable.
10
+ zh-CN:
11
+ summary: 随 Foundation 0.7.0 线锁步升版;薄机制运行时不变。
12
+ changes:
13
+ changed:
14
+ - 能力无变更——HARNESS_CAPABILITIES 保持 21 项,全部导出机制(原子受收容写、路径收容、严格权威读取、资源闭包、摘要、有界子进程监督、URL 凭证脱敏)保持 0.6.0 合同;包版本随 Foundation 线锁步,因为三个叶子包共用同一公开版本坐标。
15
+ upgradeNotes: 0.7.0 不携带任何 harness 表面变更。消费者保持既有锁定;harness computeResourceClosure 的资源闭包与 engineering-kit 0.7.0 引入的 Kit 计划闭包仍然形状不同、用途不同,二者不能互换。
package/src/atomic.mjs CHANGED
@@ -90,7 +90,30 @@ async function runTestHook(name, context) {
90
90
  if (typeof testHooks?.[name] === "function") await testHooks[name](context);
91
91
  }
92
92
 
93
- async function strictTarget(root, relPath, { target = "optional" } = {}) {
93
+ async function createParentDirectory(cursor, relPath) {
94
+ try {
95
+ await mkdir(cursor);
96
+ } catch (cause) {
97
+ // EEXIST is a benign concurrent creation race: re-verify the entry below.
98
+ if (cause?.code !== "EEXIST") {
99
+ throw mechanismError(
100
+ HARNESS_ERROR_KINDS.INVALID_ROOT,
101
+ "strict file operation could not create a missing parent directory",
102
+ { input: relPath, code: cause?.code },
103
+ );
104
+ }
105
+ }
106
+ const created = await lstat(cursor);
107
+ if (created.isSymbolicLink() || !created.isDirectory()) {
108
+ throw mechanismError(
109
+ HARNESS_ERROR_KINDS.UNSAFE_STATE_ENTRY,
110
+ "strict file operation requires real intermediate directories",
111
+ { input: relPath },
112
+ );
113
+ }
114
+ }
115
+
116
+ async function strictTarget(root, relPath, { target = "optional", createParents = false } = {}) {
94
117
  const lexical = await resolveContained(root, relPath);
95
118
  const rootReal = await realpath(root);
96
119
  const relative = path.relative(rootReal, lexical);
@@ -102,6 +125,10 @@ async function strictTarget(root, relPath, { target = "optional" } = {}) {
102
125
  try {
103
126
  stats = await lstat(cursor);
104
127
  } catch (cause) {
128
+ if (createParents && cause?.code === "ENOENT") {
129
+ await createParentDirectory(cursor, relPath);
130
+ continue;
131
+ }
105
132
  throw mechanismError(
106
133
  HARNESS_ERROR_KINDS.INVALID_ROOT,
107
134
  "strict file operation requires every parent directory to exist",
@@ -267,17 +294,25 @@ function strictFailure(kind, operation, cause, relPath, committed, verified) {
267
294
  }
268
295
 
269
296
  /**
270
- * Creates one file without replacing any existing directory entry. The
271
- * parent must already exist and contain no symlink components. The returned
297
+ * Creates one file without replacing any existing directory entry. Every
298
+ * parent must be a real directory and contain no symlink components; with
299
+ * `createParents` enabled, the missing portion of the parent chain is created
300
+ * as real directories (any symlink component is still refused). The returned
272
301
  * receipt is emitted only after byte/mode/identity verification and directory
273
302
  * fsync have completed.
274
303
  */
275
- export async function publishFileExclusive(root, relPath, data, { mode = 0o644 } = {}) {
304
+ export async function publishFileExclusive(
305
+ root,
306
+ relPath,
307
+ data,
308
+ { mode = 0o644, createParents = false } = {},
309
+ ) {
276
310
  assertWritableData(data, "publishFileExclusive");
277
311
  const normalizedMode = normalizeMode(mode);
278
312
  const bytes = bytesOf(data);
279
313
  const { targetPath, directory, directoryStats, directoryReal } = await strictTarget(root, relPath, {
280
314
  target: "absent",
315
+ createParents,
281
316
  });
282
317
  const staging = path.join(
283
318
  directory,
package/src/errors.mjs CHANGED
@@ -70,6 +70,10 @@ export const HARNESS_ERROR_KINDS = Object.freeze({
70
70
  STRUCTURED_SCAN_VIOLATION: "structured-scan-violation",
71
71
  STRUCTURED_SCAN_INVALID: "structured-scan-invalid",
72
72
  UPPER_BOUND_EXCEEDED: "upper-bound-exceeded",
73
+ // Bounded subprocess supervision (FND-ADR-012). Both kinds report through
74
+ // SFC2004 with details.kind; the AUD namespace never enters here.
75
+ TIMEOUT_POLICY_INVALID: "timeout-policy-invalid",
76
+ SUPERVISE_PROCESS_FAILED: "supervise-process-failed",
73
77
  });
74
78
 
75
79
  /**
package/src/index.mjs CHANGED
@@ -29,6 +29,11 @@ export const HARNESS_CAPABILITIES = Object.freeze([
29
29
  "structured-scan",
30
30
  "token-estimation",
31
31
  "upper-bound-guard",
32
+ "supervise-process",
33
+ // Foundation capability completion (FG-1/FG-2): strict authority reads and
34
+ // pre-persistence URL credential redaction.
35
+ "strict-read",
36
+ "url-credential-redaction",
32
37
  ]);
33
38
 
34
39
  export const HARNESS_EXCLUSIONS = Object.freeze([
@@ -44,6 +49,14 @@ export { HarnessError, HARNESS_ERROR_KINDS, mechanismError } from "./errors.mjs"
44
49
 
45
50
  export { classifyPathInput, resolveContained, readFileContained } from "./paths.mjs";
46
51
 
52
+ // Strict contained read (FG-1): no-follow, regular-file identity assertion,
53
+ // and a post-read sha256 digest receipt for existing authority files.
54
+ export { readFileStrict } from "./strict-read.mjs";
55
+
56
+ // Generic URL credential redaction (FG-2): strip userinfo before any value
57
+ // reaches disk or logs; unparseable input degrades to an opaque placeholder.
58
+ export { redactUrlCredentials, REDACTED_URL_PLACEHOLDER } from "./url-redaction.mjs";
59
+
47
60
  export { writeFileAtomic, publishFileExclusive, publishFileOrReplace, replaceFileAtomic } from "./atomic.mjs";
48
61
 
49
62
  export {
@@ -72,7 +85,26 @@ export { assertDeclaredReadSurface } from "./declared-read-surface.mjs";
72
85
 
73
86
  export { scanSurfaceStructured, STRUCTURED_SCAN_RULES, isIpv6ShapedRun, extractIpCandidates, normalizeIpToken, classifyIpToken, collectLockfileCommentRegions } from "./structured-scan.mjs";
74
87
 
75
- export { estimateTokenUpperBound } from "./token-estimate.mjs";
88
+ export {
89
+ estimateTokenUpperBound,
90
+ estimateTokens,
91
+ isCjkCodePoint,
92
+ CJK_CODE_POINT_RANGES,
93
+ TOKEN_ESTIMATOR_ID,
94
+ TOKEN_ESTIMATOR_VERSION,
95
+ TOKEN_ESTIMATION_ALGORITHM,
96
+ } from "./token-estimate.mjs";
97
+
98
+ // Minimal consumption contract of the token estimate record (SG-33): the
99
+ // contracts-owned consumption functions are re-exported next to the estimator
100
+ // so mechanism consumers keep one import surface. Pure functions only.
101
+ export {
102
+ TOKEN_ESTIMATE_CONSUMPTION,
103
+ TOKEN_ESTIMATE_CONSUMPTION_REASONS,
104
+ TOKEN_ESTIMATE_CONSUMPTION_ERROR_KIND,
105
+ consumeTokenEstimate,
106
+ consumeTokenEstimateStrict,
107
+ } from "skill-family-contracts";
76
108
 
77
109
  export {
78
110
  openUsageGuard,
@@ -81,6 +113,21 @@ export {
81
113
  closeUsageGuard,
82
114
  } from "./budget-guard.mjs";
83
115
 
116
+ // Bounded subprocess supervision (FND-ADR-012). Mechanism only: one bounded
117
+ // spawn, liveness by explicit events, consumer-supplied timeout policy,
118
+ // SIGTERM -> grace -> SIGKILL against the process group, terminal-progress
119
+ // observation, and a single closed-enum termination envelope. The mechanism
120
+ // never restarts the supervised process and never holds timeout values;
121
+ // retry/restart policy and budget thresholds stay consumer-owned.
122
+ export {
123
+ superviseProcess,
124
+ validateTimeoutPolicy,
125
+ WATCHDOG_REASONS,
126
+ TERMINATION_REASONS,
127
+ PROCESS_STATUSES,
128
+ ENVELOPE_GUARANTEES,
129
+ } from "./supervise-process.mjs";
130
+
84
131
  export { digestBytes, computeResourceClosure, closureContains } from "./closure.mjs";
85
132
 
86
133
  export {
@@ -0,0 +1,148 @@
1
+ import { constants as FS_CONSTANTS } from "node:fs";
2
+ import { lstat, open } from "node:fs/promises";
3
+ import { digestBytes } from "./closure.mjs";
4
+ import { HARNESS_ERROR_KINDS, mechanismError } from "./errors.mjs";
5
+ import { resolveContained } from "./paths.mjs";
6
+
7
+ /**
8
+ * Strict contained read (FG-1).
9
+ *
10
+ * readFileStrict is the read-side twin of the strict write path: it reads one
11
+ * existing authority file through the containment layer and proves, in order,
12
+ *
13
+ * 1. containment — resolveContained rejects traversal and escape
14
+ * classes before any read;
15
+ * 2. no-follow — a symbolic-link target is refused outright (even
16
+ * when it resolves inside the root), and the file is
17
+ * opened with O_NOFOLLOW so a link swapped in after
18
+ * the identity check can never be followed;
19
+ * 3. regular identity — the target must be one ordinary file; the handle
20
+ * opened for reading is re-statted and compared
21
+ * against the pre-open identity (dev/ino);
22
+ * 4. digest receipt — the returned receipt carries the sha256 of the
23
+ * exact bytes that were read, so a consumer can
24
+ * recompute and bind the read without trusting the
25
+ * transport.
26
+ *
27
+ * When the consumer already holds a frozen digest (an approval authority, a
28
+ * frozen plan), pass expectedSha256: any difference fails closed with the
29
+ * stable content-guard-rejected kind and the content is not delivered.
30
+ */
31
+
32
+ const SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;
33
+
34
+ function sameIdentity(left, right) {
35
+ return left.dev === right.dev && left.ino === right.ino;
36
+ }
37
+
38
+ /**
39
+ * Strictly reads one contained regular file.
40
+ *
41
+ * @param {string} root workspace root directory.
42
+ * @param {string} relPath contained relative path.
43
+ * @param {object} [options]
44
+ * @param {"utf8"} [options.encoding] decode content as a utf8 string;
45
+ * Buffer content when omitted. No
46
+ * other encoding is accepted.
47
+ * @param {string} [options.expectedSha256] frozen lowercase sha256 hex digest
48
+ * the read bytes must match.
49
+ * @returns {Promise<{path: string, content: Buffer|string, sha256: string, bytes: number, mode: number}>}
50
+ * frozen receipt emitted after the read; `sha256` is the digest of
51
+ * exactly the bytes returned in `content`.
52
+ */
53
+ export async function readFileStrict(root, relPath, { encoding, expectedSha256 } = {}) {
54
+ if (encoding !== undefined && encoding !== "utf8") {
55
+ throw new TypeError('readFileStrict: encoding must be "utf8" or undefined');
56
+ }
57
+ if (expectedSha256 !== undefined && (typeof expectedSha256 !== "string" || !SHA256_HEX_PATTERN.test(expectedSha256))) {
58
+ throw new TypeError("readFileStrict: expectedSha256 must be a lowercase sha256 hex digest");
59
+ }
60
+
61
+ const target = await resolveContained(root, relPath);
62
+
63
+ let before;
64
+ try {
65
+ before = await lstat(target);
66
+ } catch (cause) {
67
+ if (cause?.code === "ENOENT" || cause?.code === "ENOTDIR") {
68
+ throw mechanismError(
69
+ HARNESS_ERROR_KINDS.MISSING_RESOURCE,
70
+ "strict read target does not exist",
71
+ { input: relPath },
72
+ );
73
+ }
74
+ throw mechanismError(
75
+ HARNESS_ERROR_KINDS.READ_FAILED,
76
+ `strict read cannot inspect the target: ${cause?.code ?? "unknown"}`,
77
+ { input: relPath },
78
+ );
79
+ }
80
+ if (before.isSymbolicLink()) {
81
+ throw mechanismError(
82
+ HARNESS_ERROR_KINDS.UNSAFE_STATE_ENTRY,
83
+ "strict read refuses a symbolic-link target (no-follow)",
84
+ { input: relPath },
85
+ );
86
+ }
87
+ if (!before.isFile()) {
88
+ throw mechanismError(
89
+ HARNESS_ERROR_KINDS.READ_FAILED,
90
+ "strict read requires one ordinary file",
91
+ { input: relPath, actual: before.isDirectory() ? "directory" : "special" },
92
+ );
93
+ }
94
+
95
+ const noFollow = FS_CONSTANTS.O_NOFOLLOW ?? 0;
96
+ let handle;
97
+ try {
98
+ handle = await open(target, FS_CONSTANTS.O_RDONLY | noFollow);
99
+ } catch (cause) {
100
+ if (cause?.code === "ENOENT") {
101
+ throw mechanismError(
102
+ HARNESS_ERROR_KINDS.MISSING_RESOURCE,
103
+ "strict read target disappeared before open",
104
+ { input: relPath },
105
+ );
106
+ }
107
+ if (cause?.code === "ELOOP") {
108
+ throw mechanismError(
109
+ HARNESS_ERROR_KINDS.UNSAFE_STATE_ENTRY,
110
+ "strict read target became a symbolic link before open (no-follow)",
111
+ { input: relPath },
112
+ );
113
+ }
114
+ throw mechanismError(
115
+ HARNESS_ERROR_KINDS.READ_FAILED,
116
+ `strict read failed to open the target: ${cause?.code ?? "unknown"}`,
117
+ { input: relPath },
118
+ );
119
+ }
120
+ try {
121
+ const opened = await handle.stat();
122
+ if (!sameIdentity(before, opened) || !opened.isFile()) {
123
+ throw mechanismError(
124
+ HARNESS_ERROR_KINDS.UNSAFE_STATE_ENTRY,
125
+ "strict read target identity changed during inspection",
126
+ { input: relPath },
127
+ );
128
+ }
129
+ const bytes = await handle.readFile();
130
+ const sha256 = digestBytes(bytes);
131
+ if (expectedSha256 !== undefined && sha256 !== expectedSha256) {
132
+ throw mechanismError(
133
+ HARNESS_ERROR_KINDS.CONTENT_GUARD_REJECTED,
134
+ "strict read digest differs from the expected frozen digest",
135
+ { input: relPath, expectedSha256, actualSha256: sha256 },
136
+ );
137
+ }
138
+ return Object.freeze({
139
+ path: target,
140
+ content: encoding === "utf8" ? bytes.toString("utf8") : bytes,
141
+ sha256,
142
+ bytes: bytes.length,
143
+ mode: opened.mode & 0o7777,
144
+ });
145
+ } finally {
146
+ await handle.close();
147
+ }
148
+ }