@lqc123qwe/car-runtime 1.9.0 → 1.10.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.
Files changed (176) hide show
  1. package/README.md +17 -1
  2. package/dist/SHA256SUMS +1 -0
  3. package/dist/authz/authz.d.ts +44 -0
  4. package/dist/authz/authz.js +65 -0
  5. package/dist/car-runtime-1.10.0.tgz +0 -0
  6. package/dist/car-runtime-1.10.0.tgz.sig.bundle +1 -0
  7. package/dist/cli.d.ts +1 -0
  8. package/dist/cli.js +825 -0
  9. package/dist/dx/doctor.d.ts +62 -0
  10. package/dist/dx/doctor.js +127 -0
  11. package/dist/governance/dump.d.ts +47 -0
  12. package/dist/governance/dump.js +17 -0
  13. package/dist/host/facade.d.ts +13 -0
  14. package/dist/host/facade.js +154 -0
  15. package/dist/host/hostGateway.d.ts +117 -0
  16. package/dist/host/hostGateway.js +65 -0
  17. package/dist/host/mappings.d.ts +27 -0
  18. package/dist/host/mappings.js +52 -0
  19. package/dist/host/stdio.d.ts +14 -0
  20. package/dist/host/stdio.js +83 -0
  21. package/dist/kernel/context.d.ts +76 -0
  22. package/dist/kernel/context.js +203 -0
  23. package/dist/kernel/events.d.ts +37 -0
  24. package/dist/kernel/events.js +82 -0
  25. package/dist/load/config.d.ts +79 -0
  26. package/dist/load/config.js +285 -0
  27. package/dist/load/loader.d.ts +72 -0
  28. package/dist/load/loader.js +162 -0
  29. package/dist/load/registry.d.ts +44 -0
  30. package/dist/load/registry.js +58 -0
  31. package/dist/load/report.d.ts +98 -0
  32. package/dist/load/report.js +301 -0
  33. package/dist/load/sigGate.d.ts +22 -0
  34. package/dist/load/sigGate.js +68 -0
  35. package/dist/load/sign.d.ts +25 -0
  36. package/dist/load/sign.js +47 -0
  37. package/dist/load/verifier.d.ts +35 -0
  38. package/dist/load/verifier.js +56 -0
  39. package/dist/loop/goal.d.ts +31 -0
  40. package/dist/loop/goal.js +34 -0
  41. package/dist/loop/recover.d.ts +17 -0
  42. package/dist/loop/recover.js +39 -0
  43. package/dist/loop/stop.d.ts +79 -0
  44. package/dist/loop/stop.js +151 -0
  45. package/dist/mcp/gateway.d.ts +96 -0
  46. package/dist/mcp/gateway.js +69 -0
  47. package/dist/mcp/httpClient.d.ts +27 -0
  48. package/dist/mcp/httpClient.js +196 -0
  49. package/dist/mcp/stdioClient.d.ts +8 -0
  50. package/dist/mcp/stdioClient.js +73 -0
  51. package/dist/ptc/budget.d.ts +21 -0
  52. package/dist/ptc/budget.js +36 -0
  53. package/dist/ptc/erasable.d.ts +14 -0
  54. package/dist/ptc/erasable.js +27 -0
  55. package/dist/ptc/runCode.d.ts +65 -0
  56. package/dist/ptc/runCode.js +140 -0
  57. package/dist/ptc/schemaRender.d.ts +15 -0
  58. package/dist/ptc/schemaRender.js +90 -0
  59. package/dist/ptc/sdk.d.ts +27 -0
  60. package/dist/ptc/sdk.js +23 -0
  61. package/dist/ptc/worker-entry.d.ts +1 -0
  62. package/dist/ptc/worker-entry.js +71 -0
  63. package/dist/release-audit.jsonl +4 -0
  64. package/dist/runtime-core/chatStep.d.ts +40 -0
  65. package/dist/runtime-core/chatStep.js +104 -0
  66. package/dist/runtime-core/credentials.d.ts +49 -0
  67. package/dist/runtime-core/credentials.js +118 -0
  68. package/dist/runtime-core/errors.d.ts +30 -0
  69. package/dist/runtime-core/errors.js +36 -0
  70. package/dist/runtime-core/llm.d.ts +76 -0
  71. package/dist/runtime-core/llm.js +553 -0
  72. package/dist/runtime-core/redaction.d.ts +39 -0
  73. package/dist/runtime-core/redaction.js +79 -0
  74. package/dist/runtime-core/telemetry.d.ts +88 -0
  75. package/dist/runtime-core/telemetry.js +332 -0
  76. package/dist/runtime-core/tools.d.ts +15 -0
  77. package/dist/runtime-core/tools.js +12 -0
  78. package/dist/runtime-core/trace.d.ts +55 -0
  79. package/dist/runtime-core/trace.js +137 -0
  80. package/dist/runtime-core/types.d.ts +84 -0
  81. package/dist/runtime-core/types.js +13 -0
  82. package/dist/sandbox/sandbox.d.ts +68 -0
  83. package/dist/sandbox/sandbox.js +153 -0
  84. package/dist/sbom.json +263 -0
  85. package/dist/seam.d.ts +21 -0
  86. package/dist/seam.js +20 -0
  87. package/dist/security/secrets-baseline.d.ts +43 -0
  88. package/dist/security/secrets-baseline.js +176 -0
  89. package/dist/security/secrets.d.ts +45 -0
  90. package/dist/security/secrets.js +138 -0
  91. package/dist/session/export.d.ts +55 -0
  92. package/dist/session/export.js +81 -0
  93. package/dist/session/fork.d.ts +51 -0
  94. package/dist/session/fork.js +63 -0
  95. package/dist/session/format.d.ts +21 -0
  96. package/dist/session/format.js +49 -0
  97. package/dist/session/indexStore.d.ts +44 -0
  98. package/dist/session/indexStore.js +265 -0
  99. package/dist/session/log.d.ts +79 -0
  100. package/dist/session/log.js +157 -0
  101. package/dist/session/store.d.ts +9 -0
  102. package/dist/session/store.js +37 -0
  103. package/dist/telemetry/metrics.d.ts +25 -0
  104. package/dist/telemetry/metrics.js +26 -0
  105. package/package.json +17 -1
  106. package/src/seam.ts +21 -0
  107. package/.gitattributes +0 -11
  108. package/.github/workflows/ci.yml +0 -293
  109. package/.github/workflows/release.yml +0 -54
  110. package/adr/ADR-001.md +0 -4
  111. package/adr/ADR-002.md +0 -5
  112. package/adr/ADR-003.md +0 -35
  113. package/adr/host-onboarding.md +0 -68
  114. package/adr/mlps-audit-checklist.md +0 -41
  115. package/adr/win32-spike.md +0 -45
  116. package/docs/plugin-authoring.md +0 -82
  117. package/native/landlock-run/Makefile +0 -19
  118. package/native/landlock-run/landlock-run.c +0 -162
  119. package/scripts/contract-check.ts +0 -56
  120. package/scripts/invariant-n1.ts +0 -96
  121. package/scripts/package-check.ts +0 -29
  122. package/scripts/ptc-baseline/run-baseline.ts +0 -118
  123. package/scripts/release-artifacts.ts +0 -111
  124. package/scripts/release-pipeline.ts +0 -245
  125. package/scripts/secrets-baseline.ts +0 -33
  126. package/scripts/secrets-scan.ts +0 -56
  127. package/scripts/win32-ffi-debug.ts +0 -143
  128. package/scripts/win32-koffi-spike.ts +0 -537
  129. package/test/contract/eventmap.golden.json +0 -7
  130. package/test/fixtures/hello-plugin.ts +0 -5
  131. package/test/kernel.spec.ts +0 -136
  132. package/test/registry-e2e/run-e2e.ts +0 -213
  133. package/test/s11.spec.ts +0 -129
  134. package/test/s12.spec.ts +0 -135
  135. package/test/s13.spec.ts +0 -117
  136. package/test/s14.spec.ts +0 -117
  137. package/test/s15.spec.ts +0 -80
  138. package/test/s16.spec.ts +0 -133
  139. package/test/s17.spec.ts +0 -102
  140. package/test/s18.spec.ts +0 -81
  141. package/test/s19.spec.ts +0 -138
  142. package/test/s2.spec.ts +0 -245
  143. package/test/s20.spec.ts +0 -241
  144. package/test/s21.spec.ts +0 -231
  145. package/test/s22-m7-store-format.spec.ts +0 -135
  146. package/test/s23-m7-export-index.spec.ts +0 -189
  147. package/test/s24-signature-gate.spec.ts +0 -185
  148. package/test/s25-m8-runtime-core.spec.ts +0 -600
  149. package/test/s26-signature-closeout.spec.ts +0 -349
  150. package/test/s27-host-load-wiring.spec.ts +0 -134
  151. package/test/s28-session-index-followup.spec.ts +0 -236
  152. package/test/s29-otel-producer.spec.ts +0 -252
  153. package/test/s3.spec.ts +0 -179
  154. package/test/s30-release-artifacts.spec.ts +0 -127
  155. package/test/s31-real-model-wiring.spec.ts +0 -329
  156. package/test/s32-telemetry-mcp-ptc.spec.ts +0 -218
  157. package/test/s33-anthropic-adapter.spec.ts +0 -354
  158. package/test/s34-mcp-remote-transport.spec.ts +0 -250
  159. package/test/s35-config-ts-zod.spec.ts +0 -145
  160. package/test/s36-host-tool-surface.spec.ts +0 -250
  161. package/test/s37-projection-dedupe.spec.ts +0 -161
  162. package/test/s38-config-jiti.spec.ts +0 -116
  163. package/test/s39-otlp-span-semantics.spec.ts +0 -100
  164. package/test/s4.spec.ts +0 -169
  165. package/test/s40-metrics-cumulative.spec.ts +0 -67
  166. package/test/s41-mcp-serve-turn-span.spec.ts +0 -148
  167. package/test/s42-traceparent-propagation.spec.ts +0 -415
  168. package/test/s43-remote-http-traceparent.spec.ts +0 -262
  169. package/test/s5.spec.ts +0 -95
  170. package/test/s6.spec.ts +0 -169
  171. package/test/s7.spec.ts +0 -136
  172. package/test/s7b-baseline.spec.ts +0 -20
  173. package/test/s9.spec.ts +0 -89
  174. package/test/sandbox-escape/j17-orchestrate.sh +0 -30
  175. package/test/sandbox-escape/run-matrix.sh +0 -98
  176. package/tsconfig.json +0 -18
@@ -1,162 +0,0 @@
1
- /**
2
- * landlock-run — CAR F3 沙箱 Linux 后端(self-restrict-then-exec,fail-closed)
3
- *
4
- * 接口契约(src/sandbox/sandbox.ts 调用面,不得单方面变更):
5
- * landlock-run --probe 探测内核 Landlock 可用性:可用 exit 0,不可用 exit 1
6
- * landlock-run --ro -- argv... 只读沙箱:全盘可读可执行,任何写一律拒绝
7
- * landlock-run --rw -- argv... 工作区写沙箱:仅当前工作目录(cwd)可写,其余全盘只读
8
- *
9
- * 语义(《系统设计》F3 / 安全设计 §5.3 / M3部署设计增补 §2.1.2):
10
- * - allowlist 制:Landlock ruleset 未覆盖路径的 handled 访问位一律拒绝(fail-closed);
11
- * - handled 位 = ABI1 全集(EXECUTE/WRITE_FILE/READ_FILE/READ_DIR/REMOVE_DIR/REMOVE_FILE/
12
- * MAKE_CHAR/MAKE_DIR/MAKE_REG/MAKE_SOCK/MAKE_FIFO/MAKE_TRANS/MAKE_SYM)
13
- * + ABI≥3 追加 TRUNCATE;
14
- * ABI2 REFER 不处理(跨目录 re-link 由内核默认拒绝——逃逸面收紧而非放松);
15
- * - PR_SET_NO_NEW_PRIVS 先置位再 restrict_self(setuid 提权失效);
16
- * - 用例/审计要求:任何一条访问位拒绝都表现为目标系统调用的 EACCES,由上层用例断言。
17
- *
18
- * ABI 兼容:直接 syscall(x86_64: 444/445/446),不依赖 glibc 封装(glibc < 2.34 也可编译)。
19
- * 构建单源(M3 §2.1.2):J-01 单点编译 linux-x64-gnu,J-05/J-17 消费同一二进制,禁止平台内重编译。
20
- */
21
- #define _GNU_SOURCE
22
- #include <errno.h>
23
- #include <fcntl.h>
24
- #include <linux/prctl.h>
25
- #include <sched.h>
26
- #include <stdint.h>
27
- #include <stdio.h>
28
- #include <stdlib.h>
29
- #include <string.h>
30
- #include <sys/prctl.h>
31
- #include <sys/stat.h>
32
- #include <sys/syscall.h>
33
- #include <unistd.h>
34
-
35
- /* ── Landlock ABI(x86_64 syscall numbers,内核 ≥ 5.15 环境,J-17 环境自检兜底) ── */
36
- #define SYS_LANDLOCK_CREATE_RULESET 444
37
- #define SYS_LANDLOCK_ADD_RULE 445
38
- #define SYS_LANDLOCK_RESTRICT_SELF 446
39
-
40
- #define LANDLOCK_CREATE_RULESET_VERSION 1U
41
-
42
- /* Landlock FS 访问位(linux/landlock.h 同值,手工定义以摆脱头文件版本依赖) */
43
- #define LL_FS_EXECUTE (1ULL << 0)
44
- #define LL_FS_WRITE_FILE (1ULL << 1)
45
- #define LL_FS_READ_FILE (1ULL << 2)
46
- #define LL_FS_READ_DIR (1ULL << 3)
47
- #define LL_FS_REMOVE_DIR (1ULL << 4)
48
- #define LL_FS_REMOVE_FILE (1ULL << 5)
49
- #define LL_FS_MAKE_CHAR (1ULL << 6)
50
- #define LL_FS_MAKE_DIR (1ULL << 7)
51
- #define LL_FS_MAKE_REG (1ULL << 8)
52
- #define LL_FS_MAKE_SOCK (1ULL << 9)
53
- #define LL_FS_MAKE_FIFO (1ULL << 10)
54
- #define LL_FS_MAKE_TRANS (1ULL << 11)
55
- #define LL_FS_MAKE_SYM (1ULL << 12)
56
- /* ABI v2 (1<<13 REFER) 有跨目录授权约束,不纳入 handled(跨目录 mv/link 默认拒绝,收紧面) */
57
- #define LL_FS_TRUNCATE (1ULL << 14) /* ABI v3 (kernel ≥ 6.2) */
58
-
59
- #define LL_ABI1_WRITE_BITS (LL_FS_WRITE_FILE | LL_FS_REMOVE_DIR | LL_FS_REMOVE_FILE | \
60
- LL_FS_MAKE_CHAR | LL_FS_MAKE_DIR | LL_FS_MAKE_REG | \
61
- LL_FS_MAKE_SOCK | LL_FS_MAKE_FIFO | LL_FS_MAKE_TRANS | \
62
- LL_FS_MAKE_SYM)
63
- #define LL_ABI3_EXTRA LL_FS_TRUNCATE
64
-
65
- #define LANDLOCK_RULE_PATH_BENEATH 1U /* linux/landlock.h 同值 */
66
-
67
- struct ll_ruleset_attr { uint64_t handled_access_fs; };
68
- struct ll_path_beneath_attr { uint64_t allowed_access; int parent_fd; };
69
-
70
- static int ll_create_ruleset(uint64_t handled, uint32_t *abi_out) {
71
- /* 先查 ABI 版本(不创建 ruleset) */
72
- int abi = (int)syscall(SYS_LANDLOCK_CREATE_RULESET, NULL, 0,
73
- LANDLOCK_CREATE_RULESET_VERSION);
74
- if (abi < 0) return -1; /* EOPNOTSUPP: LSM 未启用 / ENOSYS: 内核过旧 / EINVAL */
75
- *abi_out = (uint32_t)abi;
76
-
77
- struct ll_ruleset_attr attr = { .handled_access_fs = handled };
78
- return (int)syscall(SYS_LANDLOCK_CREATE_RULESET, &attr, sizeof(attr), 0U);
79
- }
80
-
81
- static int ll_add_path_rule(int ruleset_fd, uint64_t access, const char *path) {
82
- int fd = open(path, O_PATH | O_CLOEXEC);
83
- if (fd < 0) return -1;
84
- struct ll_path_beneath_attr rule = { .allowed_access = access, .parent_fd = fd };
85
- int rc = (int)syscall(SYS_LANDLOCK_ADD_RULE, ruleset_fd,
86
- LANDLOCK_RULE_PATH_BENEATH, &rule, 0U);
87
- /* LANDLOCK_RULE_PATH_BENEATH = 1(linux/landlock.h) */
88
- close(fd);
89
- return rc;
90
- }
91
-
92
- static int ll_restrict(int ruleset_fd) {
93
- if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0) return -1;
94
- return (int)syscall(SYS_LANDLOCK_RESTRICT_SELF, ruleset_fd, 0U);
95
- }
96
-
97
- /* probe:Landlock 可探测(内核 ≥ 5.13 + LSM 启用)exit 0,否则 exit 1 */
98
- static int do_probe(void) {
99
- uint32_t abi = 0;
100
- int fd = ll_create_ruleset(LL_FS_EXECUTE | LL_FS_WRITE_FILE, &abi);
101
- if (fd < 0) return 1;
102
- close(fd);
103
- return 0;
104
- }
105
-
106
- static void usage_exit(void) {
107
- fprintf(stderr,
108
- "usage: landlock-run --probe\n"
109
- " landlock-run --ro -- argv...\n"
110
- " landlock-run --rw -- argv...\n");
111
- exit(64); /* 契约:非法用法固定 64(逃逸矩阵 E 类断言面) */
112
- }
113
-
114
- int main(int argc, char **argv) {
115
- if (argc < 2) usage_exit();
116
-
117
- if (strcmp(argv[1], "--probe") == 0) return do_probe();
118
-
119
- int rw;
120
- if (strcmp(argv[1], "--ro") == 0) rw = 0;
121
- else if (strcmp(argv[1], "--rw") == 0) rw = 1;
122
- else usage_exit();
123
-
124
- if (argc < 4 || strcmp(argv[2], "--") != 0) usage_exit();
125
- char **cmd = &argv[3];
126
-
127
- uint32_t abi = 0;
128
- uint64_t handled = LL_FS_EXECUTE | LL_FS_READ_FILE | LL_FS_READ_DIR | LL_ABI1_WRITE_BITS;
129
- /* 建 ruleset;ABI≥3 时重建以纳入 TRUNCATE(否则 truncate 不受控 = 收窄覆盖面) */
130
- int fd = ll_create_ruleset(handled, &abi);
131
- if (fd < 0) { perror("landlock-run: create_ruleset"); return 65; }
132
- if (abi >= 3) {
133
- close(fd);
134
- handled |= LL_ABI3_EXTRA;
135
- fd = ll_create_ruleset(handled, &abi);
136
- if (fd < 0) { perror("landlock-run: create_ruleset(abi3)"); return 65; }
137
- }
138
-
139
- /* 全盘读/执行放行(运行面),未覆盖写位 = 全盘默认拒绝(fail-closed) */
140
- if (ll_add_path_rule(fd, LL_FS_EXECUTE | LL_FS_READ_FILE | LL_FS_READ_DIR, "/") != 0) {
141
- perror("landlock-run: add rule /"); return 65;
142
- }
143
- /* /dev/null 写放行:null 设备无逃逸面,是 shell 重定向/工具链运行必需(dsh 同款惯例) */
144
- uint64_t null_bits = LL_FS_WRITE_FILE | (abi >= 3 ? LL_FS_TRUNCATE : 0);
145
- if (ll_add_path_rule(fd, null_bits, "/dev/null") != 0) {
146
- perror("landlock-run: add rule /dev/null"); return 65;
147
- }
148
- if (rw) {
149
- uint64_t write_bits = LL_ABI1_WRITE_BITS | (abi >= 3 ? LL_ABI3_EXTRA : 0);
150
- /* 仅 cwd(工作区)可写——M3 §2.1.1 步骤 4 强约束:用例必须在原生 FS 工作区内跑 */
151
- if (ll_add_path_rule(fd, write_bits, ".") != 0) {
152
- perror("landlock-run: add rule ."); return 65;
153
- }
154
- }
155
-
156
- if (ll_restrict(fd) != 0) { perror("landlock-run: restrict_self"); return 65; }
157
- close(fd);
158
-
159
- execvp(cmd[0], cmd);
160
- perror("landlock-run: execvp");
161
- return 66;
162
- }
@@ -1,56 +0,0 @@
1
- /**
2
- * J-09 contract · SessionEventMap 快照只增不删 + 公共 API 兼容(M2部署设计增补 §2.3)
3
- *
4
- * 口径:golden 基线(test/contract/eventmap.golden.json)⊆ 当前签名——
5
- * 删键/改语义 = FAIL(破坏跨宿主与审计回放兼容);加键 = PASS(演进合法)。
6
- * 断言源 = src/session/log.ts 的运行时签名(用 SessionLog 实例与类型导出反射,非正则扫源码)。
7
- */
8
- import { SessionLog, type SessionEvent } from '../src/session/log.ts'
9
- import { readFileSync } from 'node:fs'
10
-
11
- const golden = JSON.parse(readFileSync('test/contract/eventmap.golden.json', 'utf8')) as {
12
- eventKinds: string[]; actors: string[]; sessionEventFields: string[]; sessionLogApi: string[]
13
- }
14
-
15
- let failed = 0
16
- const check = (name: string, ok: boolean, detail?: string) => {
17
- console.log(`[${ok ? 'PASS' : 'FAIL'}] ${name}${detail ? ' ' + detail : ''}`)
18
- if (!ok) failed++
19
- }
20
-
21
- // ① 运行时反射:真实 append 路径产生的字段集
22
- const log = new SessionLog('contract-check')
23
- const ev: SessionEvent = log.append('user', 'user', 'T0', { text: 'x' })
24
- const evFields = Object.keys(ev).sort()
25
- const missingFields = golden.sessionEventFields.filter(f => !evFields.includes(f))
26
- check('SessionEvent 字段只增不删', missingFields.length === 0, missingFields.length ? `缺失: ${missingFields.join(',')}` : `当前: ${evFields.length} 字段`)
27
-
28
- // ② SessionLog 公共方法面(golden 中每个 API 必须仍存在且可调用签名合法)
29
- const missingApi = golden.sessionLogApi.filter(m => typeof (log as unknown as Record<string, unknown>)[m] !== 'function'
30
- && typeof (SessionLog as unknown as Record<string, unknown>)[m] !== 'function')
31
- check('SessionLog 公共 API 只增不删', missingApi.length === 0, missingApi.length ? `缺失: ${missingApi.join(',')}` : `${golden.sessionLogApi.length} 项全在`)
32
-
33
- // ③ EventKind/Actor 枚举面:golden 的每个 kind 必须仍可 append 且进入投影
34
- // (编译期联合类型无法运行时枚举——用 golden 驱动正向验证:每个 kind append 成功 + deriveMessages 投影语义不变)
35
- let kindsOk = true
36
- for (const kind of golden.eventKinds) {
37
- try {
38
- const l = new SessionLog('kc-' + kind)
39
- const actor = kind === 'user' ? 'user' : kind === 'assistant' ? 'model' : 'runtime'
40
- l.append(actor as never, kind as never, 'T', { probe: kind })
41
- } catch { kindsOk = false }
42
- }
43
- check('EventKind 枚举全兼容', kindsOk)
44
-
45
- // ④ 投影语义(CLI/宿主消费的 Envelope 面):四类消息映射规则不变
46
- const l2 = new SessionLog('proj')
47
- l2.append('user', 'user', 'T', { text: 'u' })
48
- l2.append('model', 'assistant', 'T', { text: 'a' })
49
- l2.append('model', 'toolCall', 'T', { name: 't' })
50
- l2.append('runtime', 'toolResult', 'T', { ok: true })
51
- const proj = l2.deriveMessages()
52
- const roles = proj.map(m => m.role)
53
- check('deriveMessages 投影语义不变', JSON.stringify(roles) === JSON.stringify(['user', 'assistant', 'assistant', 'toolResult']), roles.join(','))
54
-
55
- console.log(`----\ncontract: ${4 - failed}/4 PASS, ${failed} FAIL`)
56
- process.exit(failed === 0 ? 0 : 1)
@@ -1,96 +0,0 @@
1
- /**
2
- * J-08 invariant-n1 · N1 不变量常驻门禁(M2部署设计增补 §2.3)
3
- *
4
- * 口径:N1 =「Model-visible means logged」(F2)——请求时快照 === 日志前缀投影。
5
- * 本脚本为 CI 承载体:100 个确定性回放 fixtures(固定种子 PRNG)× 三类断言:
6
- * ① 正向:每 fixture 的全部快照与当前日志前缀投影逐字节一致(assertModelVisibleLogged)
7
- * ② 哈希链:verifyChain 全绿;篡改任一事件 payload 必然断链(防假绿自检——G-11 教训:
8
- * 「看起来在测」的门禁必须先证明它能 FAIL)
9
- * ③ 事件种类覆盖:8 种 EventKind 在 100 fixtures 上全覆盖(≥95% 判据按 8/8 执行)
10
- * 任一失败 exit 1;输出 JSONL 明细 + 汇总行(审计口径与 J-05/J-18 一致)。
11
- */
12
- import { SessionLog } from '../src/session/log.ts'
13
- import { writeFileSync, mkdirSync } from 'node:fs'
14
-
15
- /** 固定种子 PRNG(mulberry32)——fixtures 可复现,重跑不漂移 */
16
- function prng(seed: number): () => number {
17
- let a = seed >>> 0
18
- return () => {
19
- a |= 0; a = (a + 0x6d2b79f5) | 0
20
- let t = Math.imul(a ^ (a >>> 15), 1 | a)
21
- t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t
22
- return ((t ^ (t >>> 14)) >>> 0) / 4294967296
23
- }
24
- }
25
-
26
- const KINDS = ['user', 'assistant', 'toolCall', 'toolResult', 'turnEnd', 'goalUpdate', 'hostRaw', 'fork'] as const
27
- type Kind = (typeof KINDS)[number]
28
-
29
- const ACTOR_BY_KIND: Record<Kind, string> = {
30
- user: 'user', assistant: 'model', toolCall: 'model', toolResult: 'runtime',
31
- turnEnd: 'runtime', goalUpdate: 'runtime', hostRaw: 'runtime', fork: 'runtime',
32
- }
33
-
34
- function payloadFor(kind: Kind, i: number): unknown {
35
- switch (kind) {
36
- case 'user': return { text: `msg-${i}` }
37
- case 'assistant': return { text: `reply-${i}` }
38
- case 'toolCall': return { name: 'tool', args: { n: i } }
39
- case 'toolResult': return { ok: true, value: i }
40
- case 'turnEnd': return { reason: 'end_turn' }
41
- case 'goalUpdate': return { goal: `g-${i}` }
42
- case 'hostRaw': return { raw: `r-${i}` }
43
- case 'fork': return { from: `s-${i}` }
44
- }
45
- }
46
-
47
- /** 构造一个含随机事件序列与随机快照点的 fixture */
48
- function buildFixture(seed: number): { log: SessionLog; kindsSeen: Set<Kind> } {
49
- const rand = prng(seed)
50
- const log = new SessionLog(`N1-F${String(seed).padStart(3, '0')}`)
51
- const kindsSeen = new Set<Kind>()
52
- const turns = 3 + Math.floor(rand() * 10)
53
- for (let t = 0; t < turns; t++) {
54
- // 每轮先随机登记 0-2 个快照(快照点 = 当前事件数,投影边界随机化)
55
- const snaps = Math.floor(rand() * 3)
56
- for (let s = 0; s < snaps; s++) log.snapshotModelRequest()
57
- const events = 1 + Math.floor(rand() * 6)
58
- for (let e = 0; e < events; e++) {
59
- const kind = KINDS[Math.floor(rand() * KINDS.length)]
60
- kindsSeen.add(kind)
61
- log.append(ACTOR_BY_KIND[kind] as never, kind, `T${t}`, payloadFor(kind, log.events.length))
62
- }
63
- }
64
- log.snapshotModelRequest() // 收尾快照:覆盖最后一条事件之后的前缀
65
- return { log, kindsSeen }
66
- }
67
-
68
- // ── 主流程 ──
69
- const REPLAYS = 100
70
- const results: Array<{ fixture: string; events: number; snapshots: number; ok: boolean; chainOk: boolean; tamperDetected: boolean }> = []
71
- const kindCoverage = new Map<Kind, number>(KINDS.map(k => [k, 0]))
72
- let failed = 0
73
-
74
- for (let i = 0; i < REPLAYS; i++) {
75
- const { log, kindsSeen } = buildFixture(i)
76
- const fwd = log.assertModelVisibleLogged()
77
- const chainOk = log.verifyChain() === null
78
-
79
- // 防假绿自检:篡改中间事件 payload 必须被 verifyChain 检出
80
- const tampered = SessionLog.fromEvents(log.sessionId, log.events.map((e, idx) =>
81
- idx === Math.min(1, e.seq) ? { ...e, payload: { TAMPERED: true } } : e))
82
- const tamperDetected = tampered.verifyChain() !== null
83
-
84
- for (const k of kindsSeen) kindCoverage.set(k, (kindCoverage.get(k) ?? 0) + 1)
85
- const ok = fwd.ok && chainOk && tamperDetected
86
- if (!ok) failed++
87
- results.push({ fixture: log.sessionId, events: log.events.length, snapshots: log.stats().snapshots, ok: fwd.ok, chainOk, tamperDetected })
88
- }
89
-
90
- const covered = [...kindCoverage.values()].filter(n => n > 0).length
91
- const coverage = covered / KINDS.length
92
- const summary = { gate: 'J-08-invariant-n1', replays: REPLAYS, passed: REPLAYS - failed, failed, kindCoverage: `${covered}/${KINDS.length}`, coverage, ok: failed === 0 && coverage >= 0.95, ts: Date.now() }
93
- console.log(JSON.stringify(summary))
94
- mkdirSync('dist/ci', { recursive: true })
95
- writeFileSync('dist/ci/invariant-n1.jsonl', results.map(r => JSON.stringify(r)).join('\n') + '\n')
96
- process.exit(summary.ok ? 0 : 1)
@@ -1,29 +0,0 @@
1
- /**
2
- * J-13/14/15 package-* · npm pack 干跑 + 内容断言(M2部署设计增补 §2.3 / §3)
3
- *
4
- * 三平台同名同判据(files 白名单语义平台无关);断言:
5
- * ① pack 退出码 0(干跑可打包);
6
- * ② 文件数 ≥ 50(rc.1 基线 54 files——塌方即 FAIL,防 files 白名单被误改);
7
- * ③ 包名 = package.json name(防命名漂移——rc.1 tag 落后包名事故的常驻防线)。
8
- */
9
- import { execFileSync } from 'node:child_process'
10
- import { readFileSync } from 'node:fs'
11
-
12
- const pkg = JSON.parse(readFileSync('package.json', 'utf8'))
13
- const out = execFileSync(process.platform === 'win32' ? 'npm.cmd' : 'npm',
14
- ['pack', '--dry-run', '--json'], { encoding: 'utf8', shell: process.platform === 'win32' })
15
- const info = JSON.parse(out)[0]
16
-
17
- const checks: Array<[string, boolean, string]> = [
18
- ['pack exit 0', true, `${info.name}@${info.version}`],
19
- ['file count >= 50', (info.files?.length ?? 0) >= 50, `${info.files?.length ?? 0} files / ${info.size} bytes`],
20
- ['name no-drift', info.name === pkg.name, `${info.name} == ${pkg.name}`],
21
- ]
22
-
23
- let failed = 0
24
- for (const [name, ok, detail] of checks) {
25
- console.log(`[${ok ? 'PASS' : 'FAIL'}] ${name} ${detail}`)
26
- if (!ok) failed++
27
- }
28
- console.log(`----\npackage-check(${process.platform}): ${checks.length - failed}/${checks.length} PASS`)
29
- process.exit(failed === 0 ? 0 : 1)
@@ -1,118 +0,0 @@
1
- /**
2
- * T-7 · PTC 成功率评测基线(M4-S19 实装,S20 使用)
3
- *
4
- * 口径(M4系统设计增补 T-7 / PRD P1-4 验收:PTC 成功率 ≥ 非 PTC 基线):
5
- * - 12 任务 × 五类(纯计算 2 / 单工具 2 / 多工具编排 3 / 错误恢复 3 / 预算边界 2);
6
- * 判定口径:E 类(预算边界)= 护栏正确触发即成功(两侧对称);
7
- * - 每任务每形态 ≥N 次(默认 5,--runs 调整);对照组 = 等价非 PTC 逐步工具调用(同一工具注册表);
8
- * - 输出:JSONL 逐次明细 + 汇总(PTC 成功率 vs 非 PTC 基线 + P1-4 判定);
9
- * - 运行:node --experimental-transform-types scripts/ptc-baseline/run-baseline.ts [--runs 5]
10
- */
11
- import { writeFileSync, mkdirSync } from 'node:fs'
12
- import { runCode, makePtcToolDefinition, type ToolBridge } from '../../src/ptc/runCode.ts'
13
- import { convergeBudget } from '../../src/ptc/budget.ts'
14
-
15
- // ── 评测工具注册表(与 PTC/非 PTC 两侧共享——对照公平性) ──
16
- const TOOLS = new Map<string, ToolBridge & { description?: string; paramHint?: string }>([
17
- ['math_add', { run: async (a: unknown) => { const p = a as { x: number; y: number }; return p.x + p.y }, description: '加法', paramHint: 'args: { x: number; y: number }' }],
18
- ['math_mul', { run: async (a: unknown) => { const p = a as { x: number; y: number }; return p.x * p.y }, description: '乘法' }],
19
- ['str_len', { run: async (a: unknown) => (a as { s: string }).s.length, description: '字符串长度' }],
20
- ['flaky', { run: async (a: unknown) => { if ((a as { fail?: boolean }).fail) throw new Error('simulated failure'); return 'recovered' }, description: '可失败工具' }],
21
- ])
22
-
23
- interface Task {
24
- id: string; cls: 'compute' | 'single-tool' | 'orchestration' | 'error-recovery' | 'budget-edge'
25
- name: string
26
- /** PTC 程序体(erasable async 函数体) */
27
- code: string
28
- description: string
29
- /** 期望判定:结果语义正确;E 类 = 护栏正确触发 */
30
- expect: (result: unknown, error?: string) => boolean
31
- /** 等 PTC 护栏触发后判成功的 E 类,非 PTC 对照跳过(无对应逐步语义) */
32
- ptcOnly?: boolean
33
- budget?: { maxWallMs?: number; maxOutputBytes?: number }
34
- }
35
-
36
- const TASKS: Task[] = [
37
- { id: 'C1', cls: 'compute', name: '纯计算-算术', code: 'const a = 6 * 7\nreturn { answer: a }', description: '纯算术验证', expect: r => (r as { answer: number }).answer === 42 },
38
- { id: 'C2', cls: 'compute', name: '纯计算-字符串', code: 'const s = "car-runtime"\nreturn { len: s.length, upper: s.toUpperCase() }', description: '字符串处理', expect: r => (r as { len: number }).len === 11 },
39
- { id: 'S1', cls: 'single-tool', name: '单工具-加法', code: 'const r = await tools.math_add({ x: 20, y: 22 })\nreturn { sum: r }', description: '单工具调用', expect: r => (r as { sum: number }).sum === 42 },
40
- { id: 'S2', cls: 'single-tool', name: '单工具-字符串', code: 'const n = await tools.str_len({ s: "hello" })\nreturn { n }', description: '单工具调用', expect: r => (r as { n: number }).n === 5 },
41
- { id: 'O1', cls: 'orchestration', name: '编排-串行依赖', code: 'const a = await tools.math_add({ x: 2, y: 3 })\nconst b = await tools.math_mul({ x: a, y: 4 })\nreturn { b }', description: '串行依赖链', expect: r => (r as { b: number }).b === 20 },
42
- { id: 'O2', cls: 'orchestration', name: '编排-并行', code: 'const [a, b] = await Promise.all([tools.math_add({ x: 1, y: 1 }), tools.math_mul({ x: 3, y: 3 })])\nreturn { a, b }', description: 'Promise.all 并行', expect: r => (r as { a: number; b: number }).a === 2 && (r as { a: number; b: number }).b === 9 },
43
- { id: 'O3', cls: 'orchestration', name: '编排-混合', code: 'const parts = []\nfor (const p of [{ x: 1, y: 2 }, { x: 3, y: 4 }]) parts.push(await tools.math_add(p))\nreturn { parts, total: parts[0] + parts[1] }', description: '循环+聚合', expect: r => (r as { total: number }).total === 10 },
44
- { id: 'E1', cls: 'error-recovery', name: '恢复-try/catch', code: 'try { await tools.flaky({ fail: true }) } catch (e) { return { caught: true } }\nreturn { caught: false }', description: '工具异常捕获', expect: r => (r as { caught: boolean }).caught === true },
45
- { id: 'E2', cls: 'error-recovery', name: '恢复-部分失败继续', code: 'const out: string[] = []\ntry { await tools.flaky({ fail: true }) } catch { out.push("skip") }\nout.push(await tools.flaky({}))\nreturn { out }', description: '部分失败继续', expect: r => (r as { out: string[] }).out.length === 2 },
46
- { id: 'E3', cls: 'error-recovery', name: '恢复-重试语义', code: 'let ok = false\nfor (let i = 0; i < 3; i++) { try { ok = (await tools.flaky({ fail: i < 1 })) === "recovered"; if (ok) break } catch {} }\nreturn { ok }', description: '重试直至成功', expect: r => (r as { ok: boolean }).ok === true },
47
- { id: 'B1', cls: 'budget-edge', name: '预算-输出上限', code: 'return { blob: "x".repeat(9 * 1024 * 1024) }', description: '输出超 8MiB 测试上限', budget: { maxOutputBytes: 8 * 1024 * 1024, maxWallMs: 10_000 }, expect: (_r, e) => !!e?.startsWith('budget-exceeded (maxOutputBytes'), ptcOnly: true },
48
- { id: 'B2', cls: 'budget-edge', name: '预算-墙钟上限', code: 'while (true) { await new Promise(r => setTimeout(r, 10)) }\nreturn null', description: '死循环墙钟护栏', budget: { maxWallMs: 400 }, expect: (_r, e) => !!e?.startsWith('budget-exceeded (maxWallMs'), ptcOnly: true },
49
- ]
50
-
51
- // ── 非 PTC 对照(逐步工具调用,与 PTC 侧同注册表) ──
52
- function nonPtcEquivalent(task: Task, run: (name: string, args: unknown) => Promise<unknown>): Promise<{ ok: boolean; error?: string }> {
53
- const call = (n: string, a: unknown) => run(n, a)
54
- switch (task.id) {
55
- case 'C1': return call('math_mul', { x: 6, y: 7 }).then(v => ({ ok: (v as number) === 42 }))
56
- case 'C2': return call('str_len', { s: 'car-runtime' }).then(v => ({ ok: (v as number) === 11 }))
57
- case 'S1': return call('math_add', { x: 20, y: 22 }).then(v => ({ ok: (v as number) === 42 }))
58
- case 'S2': return call('str_len', { s: 'hello' }).then(v => ({ ok: (v as number) === 5 }))
59
- case 'O1': return call('math_add', { x: 2, y: 3 }).then(a => call('math_mul', { x: a, y: 4 })).then(b => ({ ok: (b as number) === 20 }))
60
- case 'O2': return Promise.all([call('math_add', { x: 1, y: 1 }), call('math_mul', { x: 3, y: 3 })]).then(([a, b]) => ({ ok: a === 2 && b === 9 }))
61
- case 'O3': return call('math_add', { x: 1, y: 2 }).then(a => call('math_add', { x: 3, y: 4 }).then(b => ({ ok: (a as number) + (b as number) === 10 })))
62
- case 'E1': return call('flaky', { fail: true }).then(() => ({ ok: false }), e => ({ ok: String(e).includes('simulated failure') }))
63
- case 'E2': return call('flaky', { fail: true }).catch(() => call('flaky', {})).then(v => ({ ok: v === 'recovered' }))
64
- case 'E3': return call('flaky', { fail: true }).catch(() => call('flaky', {})).then(v => ({ ok: v === 'recovered' }))
65
- default: return Promise.resolve({ ok: true }) // B 类 ptcOnly 跳过
66
- }
67
- }
68
-
69
- async function main() {
70
- const runs = Math.max(1, Number(process.argv[process.argv.indexOf('--runs') + 1] ?? 5) || 5)
71
- const lines: Record<string, unknown>[] = []
72
- const ptcDef = makePtcToolDefinition({ tools: TOOLS })
73
- const summary: Record<string, { ptc: number; non: number; total: number }> = {}
74
-
75
- for (const task of TASKS) {
76
- let ok = 0
77
- for (let i = 0; i < runs; i++) {
78
- try {
79
- if (task.cls === 'budget-edge') {
80
- // E 类经 runCode 直接驱动(护栏触发即成功)
81
- const r = await runCode({ code: task.code, description: task.description, toolCallId: `${task.id}-${i}`, budget: convergeBudget(task.budget) }, { tools: TOOLS })
82
- const pass = task.expect(r.result, r.error)
83
- if (pass) ok++
84
- lines.push({ task: task.id, cls: task.cls, run: i, ok: pass, error: r.error, budgetExceeded: r.budgetExceeded })
85
- } else {
86
- const r = await runCode({ code: task.code, description: task.description, toolCallId: `${task.id}-${i}`, budget: { maxWallMs: 10_000 } }, { tools: TOOLS })
87
- const pass = r.ok && task.expect(r.result, r.error)
88
- if (pass) ok++
89
- lines.push({ task: task.id, cls: task.cls, run: i, ok: pass, error: r.error })
90
- }
91
- } catch (e) { lines.push({ task: task.id, cls: task.cls, run: i, ok: false, error: String(e) }) }
92
- }
93
- // 非 PTC 对照
94
- let nonOk = 0
95
- if (!task.ptcOnly) {
96
- for (let i = 0; i < runs; i++) {
97
- const r = await nonPtcEquivalent(task, (n, a) => (TOOLS.get(n) as ToolBridge).run(a))
98
- if (r.ok) nonOk++
99
- }
100
- } else nonOk = runs // ptcOnly 不参与对照,按满分避免拉低基线
101
- summary[task.cls] = summary[task.cls] ?? { ptc: 0, non: 0, total: 0 }
102
- summary[task.cls].ptc += ok; summary[task.cls].non += nonOk; summary[task.cls].total += runs
103
- console.log(`${task.id} ${task.name}: PTC ${ok}/${runs}${task.ptcOnly ? '(ptcOnly)' : ` | 非PTC ${nonOk}/${runs}`}`)
104
- }
105
-
106
- const totalPtc = Object.values(summary).reduce((s, v) => s + v.ptc, 0)
107
- const totalNon = Object.values(summary).reduce((s, v) => s + v.non, 0)
108
- const totalN = Object.values(summary).reduce((s, v) => s + v.total, 0)
109
- const rate = (x: number) => ((x / totalN) * 100).toFixed(1) + '%'
110
- console.log(`\n==== PTC 成功率 ${totalPtc}/${totalN} = ${rate(totalPtc)} | 非 PTC 基线 ${totalNon}/${totalN} = ${rate(totalNon)} ====`)
111
- console.log(`P1-4 判定:${totalPtc >= totalNon ? '✅ PTC ≥ 非 PTC 基线' : '❌ PTC < 非 PTC 基线(需分析失败任务)'}`)
112
- mkdirSync('dist/ptc-baseline', { recursive: true })
113
- writeFileSync('dist/ptc-baseline/detail.jsonl', lines.map(l => JSON.stringify(l)).join('\n') + '\n')
114
- writeFileSync('dist/ptc-baseline/summary.json', JSON.stringify({ runs, ptc: totalPtc, non: totalNon, total: totalN, byClass: summary }, null, 2))
115
- console.log('明细:dist/ptc-baseline/detail.jsonl | 汇总:dist/ptc-baseline/summary.json')
116
- }
117
-
118
- main()
@@ -1,111 +0,0 @@
1
- /**
2
- * 1.3-S1 · 发布制品构建(W4):本地 release-pipeline.ts 与 CI j16 双端复用的单一事实源
3
- *
4
- * 口径(1.3 规划 W4-1/W4-4 / D-14 / D-15):
5
- * - 同一仓树 → 逐字节同制品(git archive HEAD;G-11 教训:哈希对裸字节求);
6
- * - **资产集五件套**(releaseAssetNames 单一事实源——G-10 need-list、CI upload、测试三方同源):
7
- * car-runtime-<v>.tgz / car-runtime-<v>.tgz.sig.bundle / SHA256SUMS / sbom.json / release-audit.jsonl
8
- * car-release.pub 自 1.3 移除(D-14:keyless 下验证锚 = bundle 证书 + Rekor,不再是本地公钥);
9
- * - **签名不在此层**(D-15 双轨):bundle 由签名轨产出——本地 keypair no-tlog(预演态,
10
- * release-pipeline G-08)/ CI keyless OIDC+Rekor(正式态,j16);本脚本只构建与审计;
11
- * - 构建审计:CLI 入口(CI 调用形态)写 release-audit.jsonl(archive/sbom/sha256 记录);
12
- * 程序化调用(pipeline)经 onRecord 接入调用方审计流。
13
- * - 零依赖(node:crypto/fs/child_process)。
14
- */
15
- import { execSync } from 'node:child_process'
16
- import { createHash } from 'node:crypto'
17
- import { mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs'
18
- import { join } from 'node:path'
19
- import { pathToFileURL } from 'node:url'
20
-
21
- export interface BuildRecord { stage: string; detail: string; ts: number }
22
-
23
- export interface BuildResult {
24
- tgzPath: string
25
- sha256: string
26
- sumsPath: string
27
- sbomPath: string
28
- components: number
29
- bytes: number
30
- records: BuildRecord[]
31
- }
32
-
33
- const sha256 = (s: string | Buffer) => createHash('sha256').update(s).digest('hex')
34
-
35
- function walk(dir: string, ext: string): string[] {
36
- let out: string[] = []
37
- for (const name of readdirSync(dir)) {
38
- const p = join(dir, name)
39
- if (statSync(p).isDirectory()) out = out.concat(walk(p, ext))
40
- else if (p.endsWith(ext)) out.push(p)
41
- }
42
- return out
43
- }
44
-
45
- /** 资产集清单(五件套;D-14:car-release.pub 移除,签名 bundle 由签名轨产出后入列) */
46
- export function releaseAssetNames(version: string): string[] {
47
- return [
48
- `car-runtime-${version}.tgz`,
49
- `car-runtime-${version}.tgz.sig.bundle`,
50
- 'SHA256SUMS',
51
- 'sbom.json',
52
- 'release-audit.jsonl',
53
- ]
54
- }
55
-
56
- export function buildReleaseArtifacts(opts: {
57
- version: string
58
- root?: string
59
- dist?: string
60
- onRecord?: (stage: string, detail: string) => void
61
- }): BuildResult {
62
- const root = opts.root ?? new URL('..', import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, '$1')
63
- const dist = opts.dist ?? join(root, 'dist')
64
- const version = opts.version
65
- const records: BuildRecord[] = []
66
- const rec = (stage: string, detail: string) => {
67
- const r = { stage, detail, ts: Date.now() }
68
- records.push(r)
69
- opts.onRecord?.(stage, detail)
70
- }
71
-
72
- mkdirSync(dist, { recursive: true })
73
- const tgzPath = join(dist, `car-runtime-${version}.tgz`)
74
- execSync(`git archive --format=tar.gz -o "${tgzPath}" HEAD`, { cwd: root })
75
- const bytes = readFileSync(tgzPath)
76
- rec('archive', `git archive HEAD → car-runtime-${version}.tgz(${bytes.length} bytes,树哈希锚定)`)
77
-
78
- // SHA256SUMS + 自检(G-11 口径:对裸字节求哈希;门禁不可写死为 true——落盘读回复核)
79
- const sumsPath = join(dist, 'SHA256SUMS')
80
- writeFileSync(sumsPath, `${sha256(bytes)} car-runtime-${version}.tgz\n`)
81
- const [recorded, recordedName] = readFileSync(sumsPath, 'utf-8').trim().split(/\s+/)
82
- if (recorded !== sha256(bytes) || recordedName !== `car-runtime-${version}.tgz`) {
83
- throw new Error(`CAR-E-ARTIFACT: SHA256SUMS 自检失败——记录 ${recorded} ≠ 制品裸字节哈希(G-11 教训口径)`)
84
- }
85
- rec('sha256sums', `SHA256SUMS ${recorded!.slice(0, 16)}…(裸字节复核通过)`)
86
-
87
- // SBOM(G-05 同构:TS 直跑形态制品清单 + 零运行时依赖)
88
- const files = [...walk(join(root, 'src'), '.ts'), join(root, 'package.json'), join(root, 'README.md')]
89
- const sbom = {
90
- bomFormat: 'CAR-SBOM', specVersion: '0.1', version,
91
- components: files.map(f => ({ type: 'file', path: f.replace(root, '').replace(/\\/g, '/'), sha256: sha256(readFileSync(f, 'utf-8')) })),
92
- runtimeDependencies: [],
93
- }
94
- const sbomPath = join(dist, 'sbom.json')
95
- writeFileSync(sbomPath, JSON.stringify(sbom, null, 2))
96
- rec('sbom', `${files.length} 个组件(运行时依赖 0 项)`)
97
-
98
- return { tgzPath, sha256: sha256(bytes), sumsPath, sbomPath, components: files.length, bytes: bytes.length, records }
99
- }
100
-
101
- /** CLI 入口(CI j16 调用形态):构建 + 写构建审计 release-audit.jsonl(五件套之五) */
102
- function main(): void {
103
- const version = (JSON.parse(readFileSync(join(new URL('..', import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, '$1'), 'package.json'), 'utf-8')) as { version: string }).version
104
- const result = buildReleaseArtifacts({ version })
105
- const auditPath = join(result.tgzPath, '..', 'release-audit.jsonl')
106
- writeFileSync(auditPath, result.records.map(r => JSON.stringify(r)).join('\n') + '\n')
107
- console.log(`release artifacts: v${version} → tgz(${result.bytes}B, sha256 ${result.sha256.slice(0, 16)}…) + SHA256SUMS + sbom(${result.components} components) + release-audit.jsonl`)
108
- console.log(`assets (D-14 五件套): ${releaseAssetNames(version).join(', ')}`)
109
- }
110
-
111
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) main()