@lqc123qwe/car-runtime 1.9.0 → 1.11.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 (182) hide show
  1. package/README.md +17 -1
  2. package/dist/SHA256SUMS +1 -0
  3. package/dist/authz/authz.d.ts +46 -0
  4. package/dist/authz/authz.js +76 -0
  5. package/dist/car-runtime-1.11.0.tgz +0 -0
  6. package/dist/car-runtime-1.11.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 +82 -0
  44. package/dist/loop/stop.js +184 -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 +43 -0
  65. package/dist/runtime-core/chatStep.js +121 -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 +638 -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 +104 -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 +24 -0
  86. package/dist/seam.js +23 -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/authz/authz.ts +12 -0
  107. package/src/loop/stop.ts +43 -10
  108. package/src/runtime-core/chatStep.ts +22 -2
  109. package/src/runtime-core/llm.ts +96 -13
  110. package/src/runtime-core/types.ts +22 -1
  111. package/src/seam.ts +24 -0
  112. package/src/session/log.ts +1 -1
  113. package/.gitattributes +0 -11
  114. package/.github/workflows/ci.yml +0 -293
  115. package/.github/workflows/release.yml +0 -54
  116. package/adr/ADR-001.md +0 -4
  117. package/adr/ADR-002.md +0 -5
  118. package/adr/ADR-003.md +0 -35
  119. package/adr/host-onboarding.md +0 -68
  120. package/adr/mlps-audit-checklist.md +0 -41
  121. package/adr/win32-spike.md +0 -45
  122. package/docs/plugin-authoring.md +0 -82
  123. package/native/landlock-run/Makefile +0 -19
  124. package/native/landlock-run/landlock-run.c +0 -162
  125. package/scripts/contract-check.ts +0 -56
  126. package/scripts/invariant-n1.ts +0 -96
  127. package/scripts/package-check.ts +0 -29
  128. package/scripts/ptc-baseline/run-baseline.ts +0 -118
  129. package/scripts/release-artifacts.ts +0 -111
  130. package/scripts/release-pipeline.ts +0 -245
  131. package/scripts/secrets-baseline.ts +0 -33
  132. package/scripts/secrets-scan.ts +0 -56
  133. package/scripts/win32-ffi-debug.ts +0 -143
  134. package/scripts/win32-koffi-spike.ts +0 -537
  135. package/test/contract/eventmap.golden.json +0 -7
  136. package/test/fixtures/hello-plugin.ts +0 -5
  137. package/test/kernel.spec.ts +0 -136
  138. package/test/registry-e2e/run-e2e.ts +0 -213
  139. package/test/s11.spec.ts +0 -129
  140. package/test/s12.spec.ts +0 -135
  141. package/test/s13.spec.ts +0 -117
  142. package/test/s14.spec.ts +0 -117
  143. package/test/s15.spec.ts +0 -80
  144. package/test/s16.spec.ts +0 -133
  145. package/test/s17.spec.ts +0 -102
  146. package/test/s18.spec.ts +0 -81
  147. package/test/s19.spec.ts +0 -138
  148. package/test/s2.spec.ts +0 -245
  149. package/test/s20.spec.ts +0 -241
  150. package/test/s21.spec.ts +0 -231
  151. package/test/s22-m7-store-format.spec.ts +0 -135
  152. package/test/s23-m7-export-index.spec.ts +0 -189
  153. package/test/s24-signature-gate.spec.ts +0 -185
  154. package/test/s25-m8-runtime-core.spec.ts +0 -600
  155. package/test/s26-signature-closeout.spec.ts +0 -349
  156. package/test/s27-host-load-wiring.spec.ts +0 -134
  157. package/test/s28-session-index-followup.spec.ts +0 -236
  158. package/test/s29-otel-producer.spec.ts +0 -252
  159. package/test/s3.spec.ts +0 -179
  160. package/test/s30-release-artifacts.spec.ts +0 -127
  161. package/test/s31-real-model-wiring.spec.ts +0 -329
  162. package/test/s32-telemetry-mcp-ptc.spec.ts +0 -218
  163. package/test/s33-anthropic-adapter.spec.ts +0 -354
  164. package/test/s34-mcp-remote-transport.spec.ts +0 -250
  165. package/test/s35-config-ts-zod.spec.ts +0 -145
  166. package/test/s36-host-tool-surface.spec.ts +0 -250
  167. package/test/s37-projection-dedupe.spec.ts +0 -161
  168. package/test/s38-config-jiti.spec.ts +0 -116
  169. package/test/s39-otlp-span-semantics.spec.ts +0 -100
  170. package/test/s4.spec.ts +0 -169
  171. package/test/s40-metrics-cumulative.spec.ts +0 -67
  172. package/test/s41-mcp-serve-turn-span.spec.ts +0 -148
  173. package/test/s42-traceparent-propagation.spec.ts +0 -415
  174. package/test/s43-remote-http-traceparent.spec.ts +0 -262
  175. package/test/s5.spec.ts +0 -95
  176. package/test/s6.spec.ts +0 -169
  177. package/test/s7.spec.ts +0 -136
  178. package/test/s7b-baseline.spec.ts +0 -20
  179. package/test/s9.spec.ts +0 -89
  180. package/test/sandbox-escape/j17-orchestrate.sh +0 -30
  181. package/test/sandbox-escape/run-matrix.sh +0 -98
  182. package/tsconfig.json +0 -18
@@ -1,245 +0,0 @@
1
- /**
2
- * 发布工程预演(本地 dry-run 版 8 阶段流水线,对齐《部署设计》§4/§6)
3
- *
4
- * 真实执行:门禁 13 项中本地可跑的 9 项(测试/不变量/密钥扫描/版本一致性/SBOM/校验和/
5
- * 逃逸矩阵定义校验/依赖零原生审计/dist-tag 晋级模拟+回滚演练)
6
- * DRY-RUN:npm publish(OIDC provenance)/ cosign keyless 签名 / GitHub Releases 归档
7
- * ——需远端凭据与仓库,预演报告标注 DRY-RUN 与正式执行前置条件
8
- *
9
- * 运行:node --experimental-transform-types scripts/release-pipeline.ts
10
- */
11
- import { execSync, execFileSync } from 'node:child_process'
12
- import { createHash } from 'node:crypto'
13
- import { readFileSync, writeFileSync, readdirSync, statSync, mkdirSync, existsSync } from 'node:fs'
14
- import { join } from 'node:path'
15
- import { buildReleaseArtifacts, releaseAssetNames } from './release-artifacts.ts'
16
-
17
- const ROOT = new URL('..', import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, '$1')
18
- const DIST = join(ROOT, 'dist')
19
- const VERSION = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf-8')).version as string
20
- const audit: any[] = []
21
- const auditLog = (stage: string, gate: string, verdict: string, detail: Record<string, unknown> = {}) =>
22
- audit.push({ stage, gate, verdict, ts: Date.now(), ...detail })
23
- const sha256 = (s: string | Buffer) => createHash('sha256').update(s).digest('hex')
24
-
25
- function walk(dir: string, ext: string): string[] {
26
- const out: string[] = []
27
- for (const name of readdirSync(dir)) {
28
- const p = join(dir, name)
29
- if (statSync(p).isDirectory()) out.push(...walk(p, ext))
30
- else if (p.endsWith(ext)) out.push(p)
31
- }
32
- return out
33
- }
34
-
35
- const results: Array<{ stage: string; gate: string; verdict: string; detail?: string }> = []
36
- const record = (stage: string, gate: string, ok: boolean, detail = '', dryRun = false) => {
37
- const verdict = dryRun ? 'DRY-RUN' : ok ? 'PASS' : 'FAIL'
38
- results.push({ stage, gate, verdict, detail })
39
- auditLog(stage, gate, verdict, { detail })
40
- if (!dryRun && !ok) throw new Error(`GATE FAILED: ${stage}/${gate} ${detail}`)
41
- }
42
-
43
- // ── 阶段 1:PR 门禁(int)──
44
- {
45
- const stage = 'S1-PR 门禁'
46
- // reporter 钉死 tap:非 TTY 默认 reporter 随 Node 版本漂移(23.x 输出 spec 格式,正则解析不到 # pass/# fail)
47
- const out = execSync('node --experimental-transform-types --test --test-reporter=tap test/*.spec.ts', { cwd: ROOT, encoding: 'utf-8' })
48
- const pass = /# pass (\d+)/.exec(out)?.[1]
49
- const fail = /# fail (\d+)/.exec(out)?.[1]
50
- record(stage, 'G-01 单测全绿', fail === '0', `${pass} pass / ${fail} fail`)
51
- record(stage, 'G-02 N1 日志不变量断言', out.includes('48/48') || fail === '0', 'assertModelVisibleLogged 内嵌于测试套件')
52
- // 密钥扫描(gitleaks 等价简化规则集:真实 CI 用 gitleaks 全量规则)
53
- // 豁免清单与 scripts/secrets-scan.ts(CI 权威版)保持同步:规则库 + E-5 万级基准集生成器
54
- // (内含仿真密钥模板,命中属预期)+ 对应标定 spec;豁免须显式列文件路径(M2-S10 口径)。
55
- // 同步失守教训:E-5 入库时只扩了 CI 清单,本地 G-03 漏扩 → 复跑误 FAIL(2026-09-26 已对齐)
56
- const secretPatterns = [/sk-[A-Za-z0-9]{16,}/, /ghp_[A-Za-z0-9]{20,}/, /BEGIN (RSA |EC )?PRIVATE KEY/, /AKIA[A-Z0-9]{12,}/]
57
- const scanExempt = (f: string) => {
58
- const n = f.replaceAll('\\', '/')
59
- return /\/src\/security\/secrets(\.ts|-baseline\.ts)$/.test(n)
60
- || /\/test\/s7(b-baseline)?\.spec\.ts$/.test(n)
61
- || /\/scripts\/secrets-baseline\.ts$/.test(n)
62
- }
63
- let hits = 0
64
- let scanned = 0
65
- for (const f of [...walk(join(ROOT, 'src'), '.ts'), ...walk(join(ROOT, 'test'), '.ts')]) {
66
- if (scanExempt(f)) continue
67
- scanned++
68
- const content = readFileSync(f, 'utf-8')
69
- for (const p of secretPatterns) if (p.test(content)) hits++
70
- }
71
- record(stage, 'G-03 硬编码密钥扫描 = 0', hits === 0, `${hits} hits(扫描 ${scanned} 个 .ts,豁免:secrets.ts 规则库 + s7 标定基准,豁免清单见脚本注记)`)
72
- // 依赖审计:1.5-S3(D-22)zod 为首个常规运行时依赖 → 门禁口径从「零依赖断言」切换为
73
- // npm audit(omit=dev,high 阻塞)——与 ci.yml j01 依赖审计同款;audit 不可达(离线)= DRY-RUN
74
- const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf-8'))
75
- const depCount = Object.keys(pkg.dependencies ?? {}).length
76
- let auditOk = true
77
- let auditDetail = '无运行时依赖(audit 免除)'
78
- if (depCount > 0) {
79
- if (process.env.REMOTE_CHECK === '1') {
80
- try {
81
- execSync('npm audit --omit=dev --audit-level=high', { cwd: ROOT, stdio: 'pipe' })
82
- auditDetail = `运行时依赖 ${depCount} 项(D-22/D-24:zod ^3 + jiti ^2);npm audit --omit=dev --audit-level=high 0 findings`
83
- } catch {
84
- auditOk = false
85
- auditDetail = `运行时依赖 ${depCount} 项;npm audit --audit-level=high 有 findings(阻塞——G-04 D-22 口径)`
86
- }
87
- } else {
88
- auditDetail = `运行时依赖 ${depCount} 项(D-22/D-24:zod ^3 + jiti ^2);npm audit 在 REMOTE_CHECK=1 实跑(DRY-RUN)`
89
- }
90
- }
91
- record(stage, 'G-04 依赖审计', auditOk, auditDetail)
92
- }
93
-
94
- // ── 阶段 2:构建与制品 ──
95
- {
96
- const stage = 'S2-构建与制品'
97
- // 1.3-S2(W4-1):制品构建抽至 release-artifacts.ts(本地流水线与 CI j16 双端单一事实源——
98
- // 同仓树逐字节同制品);此处产出 tgz + SHA256SUMS + sbom,G-11 在 S6 独立复核
99
- const built = buildReleaseArtifacts({
100
- version: VERSION,
101
- onRecord: (s, detail) => auditLog(stage, s.toUpperCase(), 'PASS', { detail }),
102
- })
103
- record(stage, 'G-05 SBOM 生成', true, `${built.components} 个组件(运行时依赖 0 项;tar.gz ${built.bytes} bytes 已构建——W4 单一事实源)`)
104
- // 版本一致性:精确 semver(含 prerelease——1.0-rc.x 是 rc 渠道合法形态;禁止 build 元数据与 loose 版本)
105
- const ok = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/.test(VERSION) && !/\+/.test(VERSION)
106
- record(stage, 'G-06 版本一致性', ok, `version=${VERSION}(精确 semver 含 prerelease——rc 渠道合法形态;npm 不可变语义:禁止覆盖已发布版本)`)
107
- }
108
-
109
- // ── 阶段 3:沙箱逃逸回归(Linux 门禁,WSL2 实跑后凭审计产物转 PASS)──
110
- {
111
- const stage = 'S3-沙箱逃逸回归'
112
- const matrixFile = join(ROOT, '..', 'deepseek-harness', 'm0-poc', 'poc4-sandbox-escape.ts')
113
- // Linux 实跑审计产物(WSL2 内跑完后回拷,20 行逐用例 verdict=PASS)
114
- const auditLinux = join(ROOT, '..', 'deepseek-harness', 'm0-poc', 'poc4-audit-linux.jsonl')
115
- let linuxPass = false
116
- let linuxDetail = '20 条用例 × 5 类(v0 冻结);尚未在 Linux/WSL2 实跑——AL-04 P1:任一逃逸阻塞发布'
117
- let linuxDryRun = process.platform !== 'linux'
118
- if (existsSync(auditLinux)) {
119
- const cases = readFileSync(auditLinux, 'utf-8').trim().split('\n').filter(Boolean).map(l => JSON.parse(l))
120
- const passN = cases.filter(c => c.verdict === 'PASS').length
121
- linuxPass = cases.length === 20 && passN === 20
122
- linuxDryRun = false
123
- linuxDetail = linuxPass
124
- ? `20/20 PASS(backend=${cases[0]?.backend ?? '?'})→ AL-04 满足`
125
- : `审计产物不完整/含非 PASS:${cases.length} 行,${passN} PASS——AL-04 P1 任一逃逸即阻塞`
126
- } else if (!existsSync(matrixFile)) {
127
- linuxPass = false
128
- linuxDryRun = false
129
- linuxDetail = '逃逸矩阵用例定义文件缺失'
130
- }
131
- record(stage, 'G-07 逃逸矩阵实跑', linuxPass, linuxDetail, linuxDryRun)
132
- }
133
-
134
- // ── 阶段 4-5:provenance 与远端归档(远端依赖,DRY-RUN)──
135
- // G-08 双轨语义(1.3 D-15):本地 keypair no-tlog = 预演态(S6b);CI j16 keyless OIDC+Rekor = 正式态
136
- // G-10:REMOTE_CHECK=1 且具备 GH_TOKEN 时实查 Release 资产齐备性,否则 DRY-RUN
137
- {
138
- const stage = 'S4-provenance与远端归档'
139
- let g10Ok = false
140
- let g10Detail = '需远端仓库(远端)'
141
- let g10Dry = true
142
- if (process.env.GH_TOKEN && process.env.REMOTE_CHECK === '1') {
143
- try {
144
- const ghBin = 'D:/WorkBuddy/agent/tools/bin/gh.exe'
145
- // tag 形态双兼容:v 前缀(v1.0.0 惯例)与无前缀(1.1.0 实挂形态)都查,并集去重——
146
- // 硬编码单形态会把真实归档误判 FAIL(4c84b17 G-09 期望 tag 动态化同类教训,2026-09-27)
147
- const fetchAssets = (tag: string): string[] => {
148
- try {
149
- return execFileSync(ghBin, ['api', `repos/lqc624/car-runtime/releases/tags/${tag}`, '--jq', '.assets[].name'], { encoding: 'utf-8', env: process.env }).trim().split('\n').filter(Boolean)
150
- } catch { return [] }
151
- }
152
- const assets = [...new Set([...fetchAssets(`v${VERSION}`), ...fetchAssets(VERSION)])]
153
- // 资产清单单一事实源(1.3 W4-4/R-3):releaseAssetNames(五件套,无 car-release.pub)
154
- const need = releaseAssetNames(VERSION)
155
- const missing = need.filter(n => !assets.includes(n))
156
- g10Ok = missing.length === 0
157
- g10Dry = false
158
- g10Detail = g10Ok
159
- ? `Release v${VERSION} 归档齐备(${assets.length} assets:tgz+签名bundle+SHA256SUMS+SBOM+审计——D-14 五件套)`
160
- : `Release 资产缺失:${missing.join(', ')}`
161
- } catch { /* 远端不可达或 Release 不存在 → 保持 DRY-RUN */ }
162
- }
163
- record(stage, 'G-10 GitHub Releases 归档(napi 二进制+SBOM+校验和)', g10Ok, g10Detail, g10Dry)
164
- // G-09:REMOTE_CHECK=1 时实查 npm registry——版本在架 + 期望 dist-tag + provenance 证明。
165
- // 期望 tag 按版本形态动态选择(与 release.yml publish tag 同口径,M5 §1.1):
166
- // prerelease(1.0.0-rc.1)→ rc;稳定版(1.0.0)→ latest——1.0 发布后 rc tag 保留指向 rc.1,
167
- // 硬编码查 rc 会把 1.0.0 的 post-publish 复跑误判 FAIL(09-26 与 release.yml 同批修口径)
168
- const expectedTag = VERSION.includes('-') ? 'rc' : 'latest'
169
- let g9Ok = false
170
- let g9Detail = '需 npm publish 通道(远端)'
171
- let g9Dry = true
172
- if (process.env.REMOTE_CHECK === '1') {
173
- try {
174
- const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm'
175
- const view = execFileSync(npmBin, ['view', '@lqc123qwe/car-runtime', 'version', `dist-tags.${expectedTag}`, 'dist.attestations.provenance.predicateType', '--json'], { encoding: 'utf-8', shell: process.platform === 'win32' })
176
- const j = JSON.parse(view)
177
- const onRegistry = j.version === VERSION
178
- const tagOk = j[`dist-tags.${expectedTag}`] === VERSION
179
- const provenance = typeof j['dist.attestations.provenance.predicateType'] === 'string' && j['dist.attestations.provenance.predicateType'].includes('slsa.dev/provenance')
180
- g9Ok = onRegistry && tagOk && provenance
181
- g9Dry = false
182
- g9Detail = g9Ok
183
- ? `npm 在架 @lqc123qwe/car-runtime@${VERSION}(${expectedTag} tag ✓ + SLSA provenance ✓,Actions OIDC 可信发布)`
184
- : `registry 不满足:version=${j.version} ${expectedTag}=${j[`dist-tags.${expectedTag}`]} provenance=${j['dist.attestations.provenance.predicateType'] ?? '无'}`
185
- } catch { /* registry 不可达或包未发布 → 保持 DRY-RUN */ }
186
- }
187
- record(stage, 'G-09 npm provenance(OIDC 可信发布)', g9Ok, g9Detail, g9Dry)
188
- }
189
-
190
- // ── 阶段 6:制品校验(G-11:对 S2 产出的制品做独立复核,不重建不写死) ──
191
- const TARBALL = join(DIST, `car-runtime-${VERSION}.tgz`)
192
- {
193
- const stage = 'S6-制品封装'
194
- // 校验和必须对「裸字节」求哈希——曾误写为 sha256(bytes.toString('base64')),
195
- // 记录值成为 base64 字符串的哈希,外部 sha256sum 校验必然失败(rc.1 已发生)。
196
- const bytes = readFileSync(TARBALL)
197
- const actual = sha256(bytes)
198
- const [recorded, recordedName] = readFileSync(join(DIST, 'SHA256SUMS'), 'utf-8').trim().split(/\s+/)
199
- const ok = recorded === actual && recordedName === `car-runtime-${VERSION}.tgz`
200
- record(stage, 'G-11 制品校验和(SHA-256)', ok, ok
201
- ? `car-runtime-${VERSION}.tgz (${bytes.length} bytes) → SHA256SUMS ${recorded!.slice(0, 16)}…(S2 产出独立复核,外部 sha256sum 可验证)`
202
- : `校验和自检失败:SHA256SUMS 记录 ${recorded} ≠ 制品裸字节哈希 ${actual}`)
203
- }
204
-
205
- // ── 阶段 6b:cosign 本地实签 + 验签(G-08,keypair 模式;CI 正式发布切换 keyless OIDC)──
206
- {
207
- const stage = 'S6b-cosign签名'
208
- const cosignBin = process.env.COSIGN_BIN ?? 'D:/WorkBuddy/agent/tools/bin/cosign.exe'
209
- const keysDir = 'D:/WorkBuddy/agent/tools/cosign-keys'
210
- const sigConfig = join(keysDir, 'no-tlog.json')
211
- const bundle = join(DIST, `car-runtime-${VERSION}.tgz.sig.bundle`)
212
- const env = { ...process.env, COSIGN_PASSWORD: readFileSync(join(keysDir, 'car-release.password'), 'utf-8').trim() }
213
- execFileSync(cosignBin, ['sign-blob', '--key', join(keysDir, 'car-release.key'),
214
- '--signing-config', sigConfig, '--bundle', bundle, TARBALL], { env })
215
- execFileSync(cosignBin, ['verify-blob', '--key', join(keysDir, 'car-release.pub'),
216
- '--insecure-ignore-tlog', '--bundle', bundle, TARBALL], { env })
217
- record(stage, 'G-08 cosign 签名', existsSync(bundle), `本地 keypair 实签+验签 PASS(no-tlog 模式,bundle: car-runtime-${VERSION}.tgz.sig.bundle)——D-15 预演轨;正式轨 = CI j16 keyless OIDC+Rekor`)
218
- }
219
-
220
- // ── 阶段 7:dist-tag 晋级模拟 + 回滚演练(发布渠道语义)──
221
- {
222
- const stage = 'S7-dist-tag 晋级模拟'
223
- // 本地模拟 npm 三渠道:beta → rc → latest(等价金丝雀)
224
- const registry = existsSync(join(DIST, 'registry-sim.json'))
225
- ? JSON.parse(readFileSync(join(DIST, 'registry-sim.json'), 'utf-8'))
226
- : { 'beta': null, 'rc': null, 'latest': null, immutable: [] }
227
- registry['beta'] = VERSION
228
- writeFileSync(join(DIST, 'registry-sim.json'), JSON.stringify(registry, null, 2))
229
- record(stage, 'G-12 渠道晋级:beta → rc → latest', registry['beta'] === VERSION, JSON.stringify(registry))
230
- // 回滚演练:latest 回指旧版本 + 前滚补丁纪律(npm 版本不可变 → 回滚=dist-tag 回指)
231
- registry['latest'] = registry['rc'] ?? registry['beta']
232
- writeFileSync(join(DIST, 'registry-sim.json'), JSON.stringify(registry, null, 2))
233
- record(stage, 'G-13 回滚演练:latest 回指 + 前滚补丁', true, 'npm 版本不可变(禁止覆盖已发布版本);回滚=dist-tag 回指,破坏性变更走 major+reader 兼容(SessionEventMap 只增不删)')
234
- }
235
-
236
- // ── 阶段 8:审计留痕 ──
237
- writeFileSync(join(DIST, 'release-audit.jsonl'), audit.map(a => JSON.stringify(a)).join('\n') + '\n')
238
-
239
- // ── 汇总 ──
240
- console.log('\n===== 发布工程预演结果 =====')
241
- for (const r of results) console.log(` [${r.verdict.padEnd(7)}] ${r.stage} / ${r.gate}${r.detail ? ' — ' + r.detail : ''}`)
242
- const pass = results.filter(r => r.verdict === 'PASS').length
243
- const dry = results.filter(r => r.verdict === 'DRY-RUN').length
244
- console.log(`\n合计:${pass} PASS / ${dry} DRY-RUN / 0 FAIL(制品:dist/ 五件套——tar.gz + 签名bundle + SHA256SUMS + sbom + release-audit)`)
245
- console.log('DRY-RUN 项正式执行前置:GitHub 仓库 + OIDC + npm publish 凭据 + cosign + Linux CI runner(逃逸矩阵 G-07);正式签名轨 = CI j16 keyless(D-15)')
@@ -1,33 +0,0 @@
1
- /**
2
- * E-5 secrets 基准集 CLI(M2 退出标准 #4 终验入口)
3
- *
4
- * 用法:node --experimental-transform-types scripts/secrets-baseline.ts
5
- * 产出:dist/ci/secrets-baseline.json(计数与聚合指标,无样本原文)
6
- * 退出码:判据全过 = 0;任一判据不满足 = 1(漏报 ≥1.5% / FPR >5% / precision <80%)
7
- */
8
- import { mkdirSync, writeFileSync } from 'node:fs'
9
- import { runBaseline } from '../src/security/secrets-baseline.ts'
10
-
11
- const result = runBaseline(1000, 9000)
12
- const report = {
13
- generatedAt: new Date().toISOString(),
14
- seedPos: 20260918,
15
- seedNeg: 20260919,
16
- ...result,
17
- }
18
- mkdirSync('dist/ci', { recursive: true })
19
- writeFileSync('dist/ci/secrets-baseline.json', JSON.stringify(report, null, 2))
20
-
21
- console.log('E-5 secrets baseline (1:9, 1000 pos + 9000 neg):')
22
- console.log(` TP=${result.tp} FN=${result.fn} FP=${result.fp} TN=${result.tn}`)
23
- console.log(` missRate=${(result.missRate * 100).toFixed(2)}% (criterion <1.5%)`)
24
- console.log(` FPR=${(result.fpr * 100).toFixed(2)}% (criterion <=5%)`)
25
- console.log(` precision=${(result.precision * 100).toFixed(2)}% (criterion >=80%)`)
26
- console.log(` report: dist/ci/secrets-baseline.json`)
27
- if (!result.pass) {
28
- console.log(` missByClass=${JSON.stringify(result.missByClass)}`)
29
- console.log(` fpByClass=${JSON.stringify(result.fpByClass)}`)
30
- console.error('E-5 CRITERIA VIOLATED')
31
- process.exit(1)
32
- }
33
- console.log(' PASS')
@@ -1,56 +0,0 @@
1
- /**
2
- * J-01 lint-scan-build · 仓库密钥扫描门禁(M2部署设计增补 §2.3 / ci.yml 幽灵注释兑现项)
3
- *
4
- * 口径:扫 git 跟踪文件(git ls-files),命中即 exit 1——补 CI 供应链第二道门(s7.spec 的
5
- * 规则库是样本级断言,本脚本是仓库级常驻扫描;两者共用同一模式集口径)。
6
- * 规则集(高置信,宁缺勿滥——低置信规则走人工评审,不进 fail-closed 门禁):
7
- * GitHub PAT / npm token / AWS AKIA / PEM 私钥块 / 通用密钥赋值 / ≥60 位连续 base64
8
- * 豁免:环境变量名引用(process.env.X / ${X})不算命中;文本行内示例占位不算命中。
9
- */
10
- import { execFileSync } from 'node:child_process'
11
- import { readFileSync } from 'node:fs'
12
-
13
- const RULES: Array<[string, RegExp]> = [
14
- ['github-pat', /gh[pousr]_[A-Za-z0-9]{20,}/],
15
- ['npm-token', /npm_[A-Za-z0-9]{30,}/],
16
- ['aws-access-key', /AKIA[0-9A-Z]{16}/],
17
- ['pem-private-key', /-----BEGIN [A-Z ]*PRIVATE KEY-----/],
18
- ['generic-secret-assign', /(?:password|passwd|secret|api[_-]?key|access[_-]?token)\s*[:=]\s*['"][^'"]{12,}['"]/i],
19
- ['long-base64-blob', /[A-Za-z0-9+/]{80,}={0,2}/],
20
- ]
21
-
22
- const tracked = execFileSync('git', ['ls-files'], { encoding: 'utf8' })
23
- .split(/\r?\n/).filter(Boolean)
24
-
25
- let findings = 0
26
- for (const file of tracked) {
27
- let content: string
28
- try { content = readFileSync(file, 'utf8') } catch { continue /* 二进制/不可读:跳过(sha 类二进制物无密钥文本面) */ }
29
- if (content.includes('\u0000')) continue // 二进制
30
- content.split('\n').forEach((line, i) => {
31
- for (const [name, re] of RULES) {
32
- if (!re.test(line)) continue
33
- // 豁免 1:环境变量引用(运行时注入,非硬编码)
34
- if (name === 'generic-secret-assign' && /process\.env\.|\$\{[A-Z_]+\}/.test(line)) continue
35
- // 豁免 2:锁文件 integrity 字段(sha512- 前缀的依赖指纹,非密钥)
36
- if (name === 'long-base64-blob' && /"integrity":\s*"sha512-/.test(line)) continue
37
- // 豁免 3:规则/样本/基准集定义文件自身(模式定义与基准集生成处必然"命中")
38
- // - secrets.ts = 规则库;secrets-baseline.ts/s7b.spec.ts = E-5 万级基准集生成器(内含仿真密钥模板)
39
- if (
40
- file.endsWith('secrets-scan.ts') ||
41
- /^src\/security\/secrets(\.ts|-baseline\.ts)$/.test(file) ||
42
- /^test\/s7(b-baseline)?\.spec\.ts$/.test(file) ||
43
- file === 'scripts/secrets-baseline.ts'
44
- ) continue
45
- console.error(` [${name}] ${file}:${i + 1} ${line.trim().slice(0, 90)}`)
46
- findings++
47
- }
48
- })
49
- }
50
-
51
- const summary = JSON.stringify({ gate: 'J-01-secrets-scan', files: tracked.length, findings, ok: findings === 0, ts: Date.now() })
52
- if (findings > 0) {
53
- console.error(`FAIL ${summary}`)
54
- process.exit(1)
55
- }
56
- console.log(`PASS ${summary}`)
@@ -1,143 +0,0 @@
1
- /**
2
- * win32 FFI 诊断矩阵(M5-S26 临时诊断,非门禁):定位 windows-2025 runner 上
3
- * CreateProcessAsUserW 子进程 0xC0000142(STATUS_DLL_INIT_FAILED)的确切触发条件。
4
- * 矩阵:API(AsUser/WithToken/normal)× 环境块(NULL/显式 Unicode)×
5
- * 标志(CREATE_NO_WINDOW/DETACHED_PROCESS)× 程序(cmd/whoami/node)× token(原发/受限)。
6
- * 本机(非提权交互会话)全绿为基线;runner 上首个失败行即回归点。
7
- */
8
- import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'
9
- import { tmpdir } from 'node:os'
10
- import path from 'node:path'
11
-
12
- const koffi = (await import('koffi')).default ?? (await import('koffi') as any)
13
- const kernel32 = koffi.load('kernel32.dll')
14
- const advapi32 = koffi.load('advapi32.dll')
15
-
16
- const CREATE_NO_WINDOW = 0x08000000
17
- const CREATE_SUSPENDED = 0x00000004
18
- const CREATE_UNICODE_ENVIRONMENT = 0x00000400
19
- const DETACHED_PROCESS = 0x00000008
20
- const STILL_ACTIVE = 259
21
- const PSEUDO_CURRENT_PROCESS = BigInt.asUintN(64, -1n)
22
- const SystemRoot = process.env.SystemRoot ?? 'C:\\Windows'
23
-
24
- const SID_AND_ATTRIBUTES = koffi.struct('SID_AND_ATTRIBUTES', { Sid: 'void *', Attributes: 'uint32' })
25
- const CloseHandle = kernel32.func('int32 __stdcall CloseHandle(uint64 hObject)')
26
- const WaitForSingleObject = kernel32.func('uint32 __stdcall WaitForSingleObject(uint64 hHandle, uint32 dwMilliseconds)')
27
- const ResumeThread = kernel32.func('uint32 __stdcall ResumeThread(uint64 hThread)')
28
- const GetExitCodeProcess = kernel32.func('int32 __stdcall GetExitCodeProcess(uint64 hProcess, void *lpExitCode)')
29
- const OpenProcessToken = advapi32.func('int32 __stdcall OpenProcessToken(uint64 ProcessHandle, uint32 DesiredAccess, void *TokenHandle)')
30
- const CreateRestrictedToken = advapi32.func('int32 __stdcall CreateRestrictedToken(uint64 ExistingTokenHandle, uint32 Flags, uint32 DisableSidCount, SID_AND_ATTRIBUTES *SidsToDisable, uint32 DeletePrivilegeCount, void *PrivilegesToDelete, uint32 RestrictedSidCount, void *SidsToRestrict, void *NewTokenHandle)')
31
- const CreateProcessAsUserW = advapi32.func('int32 __stdcall CreateProcessAsUserW(uint64 hToken, void *lpApplicationName, void *lpCommandLine, void *pa, void *ta, int32 bInherit, uint32 flags, void *env, void *dir, void *si, void *pi)')
32
- const CreateProcessW = kernel32.func('int32 __stdcall CreateProcessW(void *lpApplicationName, void *lpCommandLine, void *pa, void *ta, int32 bInherit, uint32 flags, void *env, void *dir, void *si, void *pi)')
33
- const CreateProcessWithTokenW = advapi32.func('int32 __stdcall CreateProcessWithTokenW(uint64 hToken, uint32 logonFlags, void *lpApplicationName, void *lpCommandLine, uint32 flags, void *env, void *dir, void *si, void *pi)')
34
-
35
- function buildEnvBlock(): Buffer {
36
- const env: Record<string, string> = {}
37
- for (const [k, v] of Object.entries(process.env)) if (v !== undefined) env[k] = v
38
- if (env.SystemRoot === undefined) env.SystemRoot = SystemRoot
39
- if (env.windir === undefined) env.windir = SystemRoot
40
- if (env.SystemDrive === undefined) env.SystemDrive = 'C:'
41
- return Buffer.from(Object.entries(env).map(([k, v]) => `${k}=${v}`).join('\0') + '\0\0', 'utf16le')
42
- }
43
-
44
- function buildAdminSid(): Buffer {
45
- const b = Buffer.alloc(16)
46
- b[0] = 1; b[1] = 2; b.set([0, 0, 0, 0, 0, 5], 2)
47
- b.writeUInt32LE(32, 8); b.writeUInt32LE(544, 12)
48
- return b
49
- }
50
-
51
- function hexExit(code: number): string {
52
- return code === STILL_ACTIVE ? 'STILL_ACTIVE' : (code >= 0x80000000 ? `0x${code.toString(16).toUpperCase()}` : String(code))
53
- }
54
-
55
- interface Variant {
56
- label: string
57
- api: 'normal' | 'asUser' | 'withToken'
58
- token: 'plain' | 'restricted'
59
- env: 'null' | 'block'
60
- flags: number
61
- program: 'cmd' | 'whoami' | 'node'
62
- explicitAppName?: boolean
63
- }
64
-
65
- const results: string[] = []
66
- async function runVariant(v: Variant, hToken: bigint, hRestricted: bigint, execPath: string): Promise<void> {
67
- const dir = mkdtempSync(path.join(tmpdir(), 'car-dbg-'))
68
- const outFile = path.join(dir, 'out.txt')
69
- const program = v.program === 'cmd' ? path.join(SystemRoot, 'System32', 'cmd.exe')
70
- : v.program === 'whoami' ? path.join(SystemRoot, 'System32', 'whoami.exe')
71
- : execPath
72
- const cmdline = v.program === 'cmd'
73
- ? `"${program}" /c exit 0`
74
- : v.program === 'whoami'
75
- ? `"${program}" /priv /fo list`
76
- : `"${program}" -e process.exit(0)`
77
- const cmdBuf = Buffer.from(cmdline + '\0', 'utf16le')
78
- const si = Buffer.alloc(104); si.writeUInt32LE(104, 0)
79
- const pi = Buffer.alloc(24)
80
- const envArg = v.env === 'block' ? buildEnvBlock() : null
81
- const flags = v.env === 'block' ? (v.flags | CREATE_UNICODE_ENVIRONMENT) : v.flags
82
- const tok = v.token === 'restricted' ? hRestricted : hToken
83
- let ok = 0
84
- try {
85
- if (v.api === 'normal') {
86
- ok = CreateProcessW(null, cmdBuf, null, null, 0, flags, envArg, null, si, pi)
87
- } else if (v.api === 'asUser') {
88
- ok = CreateProcessAsUserW(tok, null, cmdBuf, null, null, 0, flags, envArg, null, si, pi)
89
- } else {
90
- ok = CreateProcessWithTokenW(tok, 0, null, cmdBuf, flags, envArg, null, si, pi)
91
- }
92
- if (!ok) {
93
- results.push(`${v.label}: CREATE_FAILED rc=0`)
94
- return
95
- }
96
- const hProcess = pi.readBigUInt64LE(0)
97
- const hThread = pi.readBigUInt64LE(8)
98
- const wait = WaitForSingleObject(hProcess, 30000)
99
- const codeBuf = Buffer.alloc(4)
100
- GetExitCodeProcess(hProcess, codeBuf)
101
- const code = codeBuf.readUInt32LE(0)
102
- CloseHandle(hThread); CloseHandle(hProcess)
103
- results.push(`${v.label}: created=1 wait=${wait === 0 ? 'signaled' : 'timeout'} exit=${hexExit(code)}`)
104
- } catch (e) {
105
- results.push(`${v.label}: JS_ERROR ${e instanceof Error ? e.message : String(e)}`)
106
- } finally {
107
- rmSync(dir, { recursive: true, force: true })
108
- }
109
- }
110
-
111
- async function main() {
112
- console.log('=== win32 FFI debug matrix ===')
113
- const hTokBuf = Buffer.alloc(8)
114
- if (!OpenProcessToken(PSEUDO_CURRENT_PROCESS, 0x1 | 0x2 | 0x8, hTokBuf)) throw new Error('OpenProcessToken failed')
115
- const hToken = hTokBuf.readBigUInt64LE(0)
116
- const sidBuf = buildAdminSid()
117
- const hRestrBuf = Buffer.alloc(8)
118
- if (!CreateRestrictedToken(hToken, 0x1, 1, [{ Sid: sidBuf, Attributes: 0 }], 0, null, 0, null, hRestrBuf)) throw new Error('CreateRestrictedToken failed')
119
- const hRestricted = hRestrBuf.readBigUInt64LE(0)
120
- const execPath = process.execPath
121
-
122
- const matrix: Variant[] = [
123
- { label: '01 normal cmd nowin env=null ', api: 'normal', token: 'plain', env: 'null', flags: CREATE_NO_WINDOW, program: 'cmd' },
124
- { label: '02 asUser plain cmd nowin env=null ', api: 'asUser', token: 'plain', env: 'null', flags: CREATE_NO_WINDOW, program: 'cmd' },
125
- { label: '03 asUser plain cmd nowin env=block ', api: 'asUser', token: 'plain', env: 'block', flags: CREATE_NO_WINDOW, program: 'cmd' },
126
- { label: '04 asUser plain cmd DETACH env=block ', api: 'asUser', token: 'plain', env: 'block', flags: DETACHED_PROCESS, program: 'cmd' },
127
- { label: '05 asUser plain whoami nowin env=block', api: 'asUser', token: 'plain', env: 'block', flags: CREATE_NO_WINDOW, program: 'whoami' },
128
- { label: '06 asUser restr cmd nowin env=null ', api: 'asUser', token: 'restricted', env: 'null', flags: CREATE_NO_WINDOW, program: 'cmd' },
129
- { label: '07 asUser restr cmd nowin env=block ', api: 'asUser', token: 'restricted', env: 'block', flags: CREATE_NO_WINDOW, program: 'cmd' },
130
- { label: '08 asUser restr cmd DETACH env=block ', api: 'asUser', token: 'restricted', env: 'block', flags: DETACHED_PROCESS, program: 'cmd' },
131
- { label: '09 asUser restr whoami nowin env=block', api: 'asUser', token: 'restricted', env: 'block', flags: CREATE_NO_WINDOW, program: 'whoami' },
132
- { label: '10 withToken plain cmd nowin env=block ', api: 'withToken', token: 'plain', env: 'block', flags: CREATE_NO_WINDOW, program: 'cmd' },
133
- { label: '11 withToken restr cmd nowin env=block ', api: 'withToken', token: 'restricted', env: 'block', flags: CREATE_NO_WINDOW, program: 'cmd' },
134
- { label: '12 normal node nowin env=null ', api: 'normal', token: 'plain', env: 'null', flags: CREATE_NO_WINDOW, program: 'node' },
135
- { label: '13 asUser restr node nowin env=block', api: 'asUser', token: 'restricted', env: 'block', flags: CREATE_NO_WINDOW, program: 'node' },
136
- { label: '14 asUser restr node DETACH env=block ', api: 'asUser', token: 'restricted', env: 'block', flags: DETACHED_PROCESS, program: 'node' },
137
- ]
138
- for (const v of matrix) await runVariant(v, hToken, hRestricted, execPath)
139
- console.log(results.join('\n'))
140
- CloseHandle(hRestricted); CloseHandle(hToken)
141
- }
142
-
143
- main().catch((e) => { console.error('FATAL:', e instanceof Error ? e.message : e); process.exitCode = 1 })