@lqc123qwe/car-runtime 1.0.0-rc.1

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 (54) hide show
  1. package/.github/workflows/ci.yml +12 -0
  2. package/.github/workflows/release.yml +21 -0
  3. package/README.md +122 -0
  4. package/adr/ADR-001.md +4 -0
  5. package/adr/ADR-002.md +5 -0
  6. package/adr/ADR-003.md +35 -0
  7. package/adr/host-onboarding.md +68 -0
  8. package/adr/mlps-audit-checklist.md +33 -0
  9. package/adr/win32-spike.md +45 -0
  10. package/package.json +25 -0
  11. package/scripts/ptc-baseline/run-baseline.ts +118 -0
  12. package/scripts/release-pipeline.ts +189 -0
  13. package/scripts/win32-koffi-spike.ts +34 -0
  14. package/src/authz/authz.ts +96 -0
  15. package/src/cli.ts +122 -0
  16. package/src/governance/dump.ts +41 -0
  17. package/src/host/facade.ts +99 -0
  18. package/src/host/hostGateway.ts +98 -0
  19. package/src/host/mappings.ts +74 -0
  20. package/src/host/stdio.ts +74 -0
  21. package/src/kernel/context.ts +208 -0
  22. package/src/kernel/events.ts +99 -0
  23. package/src/load/loader.ts +165 -0
  24. package/src/load/registry.ts +88 -0
  25. package/src/load/verifier.ts +67 -0
  26. package/src/loop/goal.ts +53 -0
  27. package/src/loop/recover.ts +47 -0
  28. package/src/loop/stop.ts +200 -0
  29. package/src/mcp/gateway.ts +118 -0
  30. package/src/ptc/budget.ts +37 -0
  31. package/src/ptc/erasable.ts +28 -0
  32. package/src/ptc/runCode.ts +127 -0
  33. package/src/ptc/sdk.ts +28 -0
  34. package/src/ptc/worker-entry.ts +67 -0
  35. package/src/sandbox/sandbox.ts +199 -0
  36. package/src/security/secrets.ts +111 -0
  37. package/src/session/export.ts +110 -0
  38. package/src/session/log.ts +120 -0
  39. package/src/telemetry/metrics.ts +38 -0
  40. package/test/kernel.spec.ts +136 -0
  41. package/test/s11.spec.ts +129 -0
  42. package/test/s12.spec.ts +135 -0
  43. package/test/s13.spec.ts +117 -0
  44. package/test/s14.spec.ts +117 -0
  45. package/test/s15.spec.ts +80 -0
  46. package/test/s17.spec.ts +102 -0
  47. package/test/s18.spec.ts +72 -0
  48. package/test/s2.spec.ts +245 -0
  49. package/test/s3.spec.ts +179 -0
  50. package/test/s4.spec.ts +169 -0
  51. package/test/s5.spec.ts +95 -0
  52. package/test/s6.spec.ts +169 -0
  53. package/test/s7.spec.ts +135 -0
  54. package/test/s9.spec.ts +89 -0
@@ -0,0 +1,12 @@
1
+ name: ci
2
+ on: [push, pull_request]
3
+ jobs:
4
+ test:
5
+ runs-on: ubuntu-24.04
6
+ steps:
7
+ - uses: actions/checkout@v4
8
+ - uses: actions/setup-node@v4
9
+ with: { node-version: '22.19' }
10
+ - run: node --experimental-transform-types --test test/*.spec.ts
11
+ # N1 日志不变量断言已内嵌于测试(SessionLog.assertModelVisibleLogged)
12
+ # S3 追加门禁:沙箱逃逸回归矩阵(tests/sandbox-escape)+ 密钥扫描 + 依赖审计
@@ -0,0 +1,21 @@
1
+ name: release
2
+ on:
3
+ workflow_dispatch:
4
+ release:
5
+ types: [published]
6
+ permissions:
7
+ contents: read
8
+ id-token: write
9
+ jobs:
10
+ publish:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: actions/setup-node@v4
15
+ with:
16
+ node-version: 22
17
+ registry-url: https://registry.npmjs.org/
18
+ - name: Publish to npm with provenance
19
+ run: npm publish --provenance --tag rc
20
+ env:
21
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # CAR Runtime(S1 骨架)
2
+
3
+ Composable Agent Runtime — S1 迭代产物:微内核骨架(F1 可逆注册+依赖注入 / F9 加载-运行两阶段)+ append-only 会话日志层(F2 哈希链 / N1 不变量断言)。
4
+
5
+ ## 运行
6
+
7
+ ```bash
8
+ # 测试(零依赖,Node 22.19+ 内置能力)
9
+ node --experimental-transform-types --test test/*.spec.ts
10
+ ```
11
+
12
+ ## 结构
13
+
14
+ | 路径 | 内容 | 设计依据 |
15
+ |------|------|---------|
16
+ | `src/kernel/context.ts` | 微内核:plugin/provide/inject/effect + disposeRuntime 双模式卸载(strict-topo 逐层排空 / concurrent 对照) | ADR-002、POC-1 实证 |
17
+ | `src/session/log.ts` | append-only 会话日志:SHA-256 哈希链(CAR 自研增强)+ deriveMessages 前缀投影 + assertModelVisibleLogged(N1) | ADR-001、POC-3、dsh invariant.ts 语义 |
18
+ | `test/kernel.spec.ts` | 14 项断言(含 strict-topo 层序、异常屏障、依赖环、ctx 作用域) | UserStory US-2/US-7 口径 |
19
+ | `src/kernel/events.ts` | F7 事件总线:五模式分发(emit/serial/bail/waterfall/parallel)+ @mode 契约目录(未声明事件禁止注册/分发,启动期 A010003) | dsh typert 范式(源码级核实) |
20
+ | `src/loop/stop.ts` | F5 停止语义:step 三结果、TurnEndReason 六值、ADR-001 解析前收口+只读白名单重试、OR/AND 工具收口、取消补记成对事件、F4 权限门集成点 | ADR-001、D4-D5、POC-3 I4 |
21
+ | `src/load/loader.ts` | F6 装配:manifest 冲突链校验(精确 semver/豁免 reason 必填)、免编译加载+epoch 热重载、Stub 占位(注册类可用/能力查询 bind 前抛错)+ invalidate | POC-2、冻结决策⑥挂点 |
22
+ | `test/s2.spec.ts` | 17 项断言(五模式语义、I4 一致性、解析前收口、取消无缺口、Stub/invalidate、热重载) | US-1/US-5/US-7 口径 |
23
+ | `src/sandbox/sandbox.ts` | F3 沙箱执行器:probeCapabilities 能力探测(Linux landlock-run --probe / 其他平台显式降级 BD-01)、降级态 Q-04 约束(写类强制 confirm、env-read 拒绝)、审计全量留痕、landlock-run 包装(--ro/--rw -- argv) | 冻结决策②、Q-04 |
24
+ | `src/authz/authz.ts` | F4 授权服务:authorizationId 幂等(T-10)、120s 超时默认拒绝(EXPIRED)、能力标签 deny-by-default(T-22)、决策 100% 落审计日志 | 安全设计 §2.2 |
25
+ | `src/mcp/gateway.ts` | F8 MCP 网关:serverId 登记制(A050001)、未声明 sideEffect 注册时收敛 write(T-22 强制点)、BD-02 崩溃隔离(标记不可用不抛异常)、明文凭据 env 拒绝(§3.2.5) | 冻结决策①、US-6 |
26
+ | `test/s3.spec.ts` | 14 项断言(探测降级、Q-04 双约束、幂等、超时默认拒绝、BD-02、凭据门、权限一致性无旁路) | US-3/US-6 口径 |
27
+
28
+ ## S1 已实证的设计约束(写码红线)
29
+
30
+ 1. **effect 绑定插件 fiber 作用域**——插件工厂只收 cordis 风格的 apply 作用域 ctx,禁止透传根 ctx(POC-2 实证:错位注册导致卸载回卷不到);
31
+ 2. **卸载层序 = 消费者先于提供者**——Kahn 分层方向为 provider→consumer,teardown 必须 reverse(S1 首版即踩);
32
+ 3. **apply 返回值 = effect body**(cordis 语义)——CAR 自研内核同步约定:apply 同步完成注册,异步初始化走 effect;
33
+ 4. **加载期显式失败**——重复 provide/注册、缺服务、依赖环均在 `plugin()`/`disposeRuntime()` 调用点抛出(CAR-E-DEPCYCLE 前缀),禁止静默。
34
+
35
+ ## S2 实测修正的设计口径(写码红线续)
36
+
37
+ 5. **Stub 语义分层**:注册类动作(registerTool/registerCommand)Stub 期**始终可用**(收集待注册项,bindCore 后冲刷);仅「依赖宿主绑定的能力查询」bind 前抛 CAR-STUB——否则插件挂载即崩;
38
+ 6. **I4 一致性**:只读白名单重试(Pi 式续走)后 turn/end 仍保留 max-tokens——重试不改变终止原因口径;
39
+ 7. **AND 收口前置条件**:terminateAll 要求 finalized>0(未执行的调用不计入);权限拒绝的调用不终止 turn(模型自行调整);
40
+ 8. **沙箱测试清理**:rmSync 受 safe-delete trash 拦截会抛错——测试 fixture 清理必须 try/catch 容错。
41
+
42
+ ## S3 实测补充的设计口径(写码红线终)
43
+
44
+ 9. **T-22 强制点在注册时**:MCP 工具未声明 sideEffect 在网关注册时收敛为 write(不做延迟判定)——能力矩阵视图即最终约束视图;
45
+ 10. **BD-02 隔离语义**:崩溃/超时后 server 整体标记不可用,后续调用返回显式错误结果(不抛异常、不重试、不静默)——调用方落审计日志;
46
+ 11. **mock 计数陷阱**:tools/list 握手消耗 transport 调用次数,故障注入偏移要算上握手。
47
+
48
+ | `src/cli.ts` | N2 CLI:car run(五环节快速上手流)/ session verify / session replay / car doctor | US-1/US-4/§6.5 |
49
+ | `test/s4.spec.ts` | E2E 五环节全链路 + N2 实测 + Q-06 定标 | 业务闭环端到端 |
50
+
51
+ | `test/s5.spec.ts` | M2-S5:F10 peer 约束(严格默认/豁免/optional/caret 语义)+ F11 优先级(升序/稳定排序/向后兼容/治理视图) | 冻结决策⑥、F10/F11 AC |
52
+ | `adr/ADR-003.md` | 签名双轨定稿(D-1/D-2 裁决实装文档,S6 验签器依据) | 决议⑦ |
53
+
54
+ | `src/load/verifier.ts` | S6 验签器:minisign 离线轨(ed25519)+ Sigstore 接口预留/回退 + fail-closed 分级(校验失败硬拒绝不可配置 / 缺失 warn→enforce) | ADR-003、D-1/D-2 |
55
+ | `src/loop/goal.ts` + `src/loop/recover.ts` | F13 Goal 层(phase 持久化 goalUpdate / activation 纯内存默认 disarmed / 自动 disarm)+ 恢复层(interrupted 补记 + 未配对 toolCall 合成结果) | M2系统设计增补 T-1 |
56
+ | `test/s6.spec.ts` | 12 项断言(验签四情形、Goal 重放/幂等恢复、aggregate any/all、blockOnDeny) | D-1/D-2 裁决、US-5 |
57
+
58
+ | `src/security/secrets.ts` | S7 F12 secrets 检测:6 类模式库 + 三层检测(前缀→熵≥3.5→上下文加成/降级)+ redact 全遮蔽;D-3 标定口径内建(正负 1:9 粗标 FPR 0%/召回 ≥85%) | T-6、D-3 |
59
+ | `src/sandbox/sandbox.ts` +2 | S7 Seatbelt profile 生成器(deny default/network*/.git 受保护路径/引号转义)+ darwin 后端接入 | SR-19、dsh darwin 路由 |
60
+ | `adr/win32-spike.md` + `scripts/win32-koffi-spike.ts` | S8 Windows spike 方案冻结(三阶段+四判据+逐级回退 a/b/c) | T-3、规划三重闸门① |
61
+ | `test/s7.spec.ts` | 13 项断言(检测/脱敏/误报防护/标定粗标/profile 结构/SPIKE SKIP 口径) | F12 AC |
62
+
63
+ ## 状态
64
+
65
+ | `src/governance/dump.ts` | S9 F14 治理视图:配置树 dump(Context.governanceSnapshot)+ 事件产消图(目录 × handlerChain 按 priority 执行序) | P1-7 |
66
+ | `src/session/export.ts` | S9 F15 合规导出增强:取证包(逐文件 SHA-256 + 链锚点 + 审计元数据,断链中止)+ verifyBundle + 三标分层声明(9 核心字段三标全覆盖 + 标准特有扩展) | Q-09、决议⑧、T-7 |
67
+ | `adr/mlps-audit-checklist.md` | 等保 8.1.4.3 a–d 逐条核验(三独立来源交叉证实):a/b 满足、c/d 部分满足差距显式标注(MLPS-G1/G2/G3 处置) | D-4 |
68
+ | `test/s9.spec.ts` | 5 项断言(配置树/产消图/取证包/篡改拒绝/三标分层) | F14/F15/D-4 |
69
+
70
+ | `src/host/mappings.ts` | S11 T-1 归一化 schema(host-mappings-v1):session id 确定性派生(SH-+sha256)+ 两宿主纯数据 profile + hostRaw 零静默降级通道 | 决议⑤、T-1 |
71
+ | `src/host/hostGateway.ts` | S11 HostGateway 骨架:9 tool 会话级能力面(step 循环不暴露)+ hostId 登记制(A050001)+ RuntimeFacade 分发(snake→camel)+ 审计全留痕 | M3系统设计增补 T-1 |
72
+ | `test/s11.spec.ts` | 10 项断言(幂等派生/跨宿主隔离/归一化等价/降级留痕/登记制/9 tool 面/契约测试 v0/宿主标识静态断言) | 双宿主等价基线 |
73
+
74
+ ## 状态
75
+
76
+ | `src/host/stdio.ts` | S12 stdio ServerTransport:JSON-RPC over 换行分隔 JSON(tools/list + tools/call 分发),非法 JSON/未知 method 显式报错不崩溃 | 多宿主主形态(部署增补 §4) |
77
+ | `src/host/facade.ts` | S12 RuntimeFacade 真实实现:sessionStart 幂等建会话+登记事件落哈希链;sessionTurn 事件批归一化落盘(hostRaw 零静默);replay/verify/export 全通 | T-1 数据流 |
78
+ | `test/s12.spec.ts` | 5 项断言(stdio 双方法+容错/端到端落链+离线重放/跨宿主投影等价/审计无旁路) | 双宿主等价核心 |
79
+
80
+ ## 状态
81
+
82
+ | `src/load/registry.ts` | S13 registry 配置面:resolution 四级顺序(显式绑定硬失败不降级→白名单 priority→npm 兜底可关→离线全拒)+ 白名单即清单本身 + 决策指纹 RD-* 可落审计 | 决议⑦、T-3/T-4 |
83
+ | `test/s13.spec.ts` | 7 项断言(四级顺序/显式绑定硬失败/兜底可关/离线最高/审计全量/五类契约全链路等价/降级同步+会话隔离) | 契约测试 v1 |
84
+
85
+ ## 状态
86
+
87
+ | `src/ptc/` | S14 PTC I:budget(三层收敛只许下调 CAR-E-BUDGET)+ erasable 双挂点(入口+loader)+ worker-entry(async 函数体/tools proxy 消息桥/wallTimer)+ runCode(code+description 双必填、并发护栏 4、双层预算、makePtcToolDefinition 强制 write) | 决议③、T-2 |
88
+
89
+ ## 状态
90
+
91
+ | `src/ptc/sdk.ts` | S15 TS SDK 渲染:注册表快照→system prompt TS 声明(非 .d.ts);声明面/授权面结构一致性(渲染只接受注册表自身,漂移在结构上不可能) | T-2、dsh :26,97 口径 |
92
+ | `src/ptc/runCode.ts` +2 | S15 授权门前置(拒绝=无 worker 启动+ptc-denied 留痕,authorizationId='ptc-'+id 幂等)+ F12 出站覆盖(worker 输出回填前强制 redact,secretsRedacted 计数) | T-4、决议③红线 |
93
+
94
+ ## 状态
95
+
96
+ | `src/load/registry.ts` +2 | S17 T-5 终局对齐实装:exclude 通道(类型上仅 reason='network'——校验失败换源不可表达)+ 候选耗尽语义修正 + resolveCandidateChain 预览 + pinned 网络失败按 priority 继续 | M4安全增补registry对齐 |
97
+ | `src/telemetry/metrics.ts` | S17 采集面:3 个零内容 Counter(load_total/unsigned_confirmed/registry_decision)+ snapshot 登记表兜底通道 + assertZeroContent 红线自检 | Q-08、D-2 数据窗口前置 |
98
+ | `test/s17.spec.ts` | 9 项断言(exclude 三路径/耗尽 fail-closed/候选链预览/零内容红线/verifier 采集挂点) | T-5 定稿② |
99
+
100
+ ## 状态
101
+
102
+ | `src/cli.ts mcp-serve` | S18 CAR-as-MCP-Server 真实进程入口:--host 数据驱动白名单 + counters 挂载 + 会话结束 stderr 零内容快照(stdout 协议通道不污染) | E-6 实连前置 |
103
+ | `adr/host-onboarding.md` | 宿主接入指南:Claude Code .mcp.json / Codex config.toml 配置片段 + 五类契约手工验证清单 + 采集窗口说明 | E-6 |
104
+ | `test/s18.spec.ts` | 4 项断言(**spawn 真实子进程** stdio 全链路/跨进程幂等派生/采集快照/未知宿主 exit 2) | E-6 进程级预演 |
105
+
106
+ ## 状态
107
+
108
+ | `scripts/ptc-baseline/` | S19 T-7 评测脚本实装:12 任务五类 + 逐任务 N 次对照(非 PTC 基线)+ JSONL 明细/汇总——**首份实测:PTC 60/60=100% ≥ 非 PTC 100%,P1-4 ✅** | PRD P1-4 |
109
+
110
+ ## 状态
111
+
112
+ - S1-S4:48/48 ✅ | M2:89/89(v0.2.0)| M3:127/127(v0.3.0)| M4:S17→S18→S19→S20→S21→**S22 rc 彩排 ✅(139/139 @ 1.0.0-rc.1,9 PASS/4 DRY-RUN/0 FAIL;彩排捕获 3 真问题:koffi 依赖分类/非法 semver/tgz 膨胀信号)**
113
+ - **GO 清单**(S22-rc发布彩排报告.md §3):E-1/E-3(轨道 A)+ T-1/T-4 决策 + 试点数据 + npm pack 制品复核——全部按清单打勾即发布 1.0.0-rc.1。
114
+ - M4 待办:**S21 rc 准入核查已出(M4-rc准入核查报告.md)**——RC-4 ✅ 实测、RC-1/2/3/5/6 待评审定稿+环境动作+试点数据,工程侧证据链 80%+ 齐备、无设计/代码缺口;S22 预演 13/13 + 1.0-rc 发布(待轨道 A E-1/E-2/E-3 与 T-1/T-4 决策)。
115
+ - M4 待办:S18 Claude Code 实连 + 采集窗口开启(采集面已就位);S19 Codex 实连 + registry 增补评审;S20 enforce 决策 + PTC 评测(脚本 2 人日待实装);轨道 A(你):E-1/E-2/E-3 沙箱外。
116
+ - 环境动作清单(E-1~E-4 + E-6 宿主实连试点)与遗留移交见 delivery/M3收口终验报告.md;M4 预埋:fork 跨宿主/深度参数渲染/定时导出。
117
+ - **内核行为修复(S14 捕获)**:工具异常原会终止 turn(reason=error)——已修为「成对错误 toolResult 交回模型」(US-5/D5 语义:工具错误是结果而非 turn 中断),121 断言零回退。
118
+ - 环境动作清单(E-1~E-5)与遗留移交(T-8 等)见 delivery/M2收口终验报告.md;G-03 豁免机制:secrets 规则库/标定基准显式列文件豁免,清单变更需评审。
119
+ - M2 待办:S6 验签器实装(ADR-003)+ F13 五层停止(M2系统设计增补 T-1);S7/S8 跨平台沙箱(Windows koffi spike);S9 治理+导出;S10 收口。
120
+ - **N2 实测**:首插件跑通 3ms(目标 ≤300s,余量 10 万倍)
121
+ - **Q-06 定标回填**:装配 20 插件 <1ms;serial 分发 1000 次 6ms;日志 1 万事件追加+哈希链 125ms、回放+校验 21ms
122
+ - 待办:jiti 实装补测首载时延(载体替身已验证不变量);POC-4 Linux/WSL2 实跑;发布工程预演(dist-tag beta)
package/adr/ADR-001.md ADDED
@@ -0,0 +1,4 @@
1
+ # ADR-001 · max-tokens 处置策略
2
+ 状态: 已定稿(S2 实装)| 2026-09-02
3
+ 决策: 默认 DSH 式解析前收口(finishReason=length 不解析工具调用,禁补全半截 JSON);TurnEndReason 保留至 turn/end(endReasonTrail);只读白名单工具可选 Pi 式补错重试(preset 显式开启);finishReason 为不可变透传字段,改写/吞没触发 AL-05。
4
+ 证据: D5 §4 + SR-17(vLLM #53269 教训)+ POC-3 I4 断言(截断保留语义已验证)。
package/adr/ADR-002.md ADDED
@@ -0,0 +1,5 @@
1
+ # ADR-002 · 卸载顺序双模式接口
2
+ 状态: 已定稿 + S1 实装(strict-topo 默认)| 2026-09-04
3
+ 决策: ctx.effect 双模式——strict-topo(Kahn 分层反转逐层排空,消费者先于提供者)/ concurrent(跨 fiber 并发,对照降级模式);fiber 内一律注册逆序。
4
+ 证据: POC-1 spike(poc1-dispose-order.ts)实证屏障可行;dsh Fiber._unload 为 Promise.all 并发(fiber.ts:676),barrier 缺失——CAR 自研超越点。
5
+ S1 踩坑记录: Kahn 分层方向为 provider→consumer,teardown 必须 reverse 层序(首版实现即踩,测试捕获)。
package/adr/ADR-003.md ADDED
@@ -0,0 +1,35 @@
1
+ # ADR-003 · 插件签名信任根与 enforce 策略(D-1/D-2 裁决实装)
2
+
3
+ 状态: 已定稿(S5 定稿文档;S6 验签器实装)| 2026-09-04
4
+
5
+ ## 决策(D-1)
6
+
7
+ **双轨签名**:Sigstore keyless 主轨(与发布工程 G-08 cosign / G-09 npm provenance 同源,不另起炉灶)+ minisign 式静态密钥离线轨(解决 R-6 内网/离线验证失败)。
8
+
9
+ 信任锚三层:
10
+ 1. 内置根:CAR 项目公钥编译期 pinning;
11
+ 2. Sigstore 链:离线 bundle 验证,禁止运行时拉网(对齐部署侧禁运行时网络下载纪律);
12
+ 3. 企业自管替换点:`trustRoot` 配置可替换信任锚,替换动作写审计日志。
13
+
14
+ ## 决策(D-2)
15
+
16
+ **enforce 时点**:S6 warn 上线(签名缺失 = 告警放行 + manifestHash 指纹兜底)→ S8 末统计 unsigned 占比与误伤率 → S10 发布预演前切 enforce(缺失 = 硬拒绝)。
17
+
18
+ fail-closed 三情形分级:
19
+ | 情形 | M2 行为 |
20
+ | --- | --- |
21
+ | 签名校验失败(有签名但不对) | 硬拒绝,**不可配置放行** |
22
+ | 签名缺失 | S6-S9 warn + 指纹兜底 → S10 enforce 拒绝 |
23
+ | Rekor 透明日志不可达 | 回退静态轨;双轨皆败按缺失处理 |
24
+
25
+ 存量已装插件不回溯强制(manifestHash 指纹持续兜底,M3 企业策略项)。Git 直载维持决议⑦告警语义,不纳入 enforce。
26
+
27
+ ## 依据
28
+
29
+ - 决议⑦:分发 = 目录+签名起步(Shai-Hulud/ClawHub 供应链教训,SR-08/09/14);
30
+ - 部署侧 G-08/G-09 同源复用(M2部署设计增补 T-4);
31
+ - M1 已有 manifestHash 指纹兜底(loader.ts)。
32
+
33
+ ## 回退方案
34
+
35
+ Sigstore 生态不可用(如 Rekor 长期故障)→ 静态轨独立承载;企业场景 → trustRoot 替换为自管根。验签失败处置遵循安全设计 §4.4 装载前三段。
@@ -0,0 +1,68 @@
1
+ # E-6 宿主实连接入指南(M4-S18)
2
+
3
+ > CAR-as-MCP-Server:宿主(Claude Code / Codex)把 CAR 当作一个 MCP server 接入——
4
+ > 9 tool 会话级能力面(session_start/turn/stop/status/replay/verify/export + tool_list/tool_call)。
5
+ > 传输 = stdio JSON-RPC(换行分隔),与 M3 契约测试同一接入面(双宿主等价由契约锁定)。
6
+
7
+ ## Claude Code 接入
8
+
9
+ 项目根目录 `.mcp.json`(或 `~/.claude.json` 全局):
10
+
11
+ ```json
12
+ {
13
+ "mcpServers": {
14
+ "car": {
15
+ "command": "node",
16
+ "args": [
17
+ "--experimental-transform-types",
18
+ "{CAR_ROOT}/src/cli.ts",
19
+ "mcp-serve",
20
+ "--host", "claude-code"
21
+ ]
22
+ }
23
+ }
24
+ }
25
+ ```
26
+
27
+ ## Codex 接入
28
+
29
+ `~/.codex/config.toml`:
30
+
31
+ ```toml
32
+ [mcp_servers.car]
33
+ command = "node"
34
+ args = [
35
+ "--experimental-transform-types",
36
+ "{CAR_ROOT}/src/cli.ts",
37
+ "mcp-serve",
38
+ "--host", "codex",
39
+ ]
40
+ ```
41
+
42
+ ## 验证步骤(E-6 五类契约手工清单)
43
+
44
+ 1. `car doctor` → 确认 Node ≥22.19 与沙箱能力状态;
45
+ 2. 宿主内触发 MCP 工具列表刷新 → 应看到 9 个 `session_*` / `tool_*` 工具;
46
+ 3. 调 `session_start` → 返回 `SH-` 前缀会话 id(跨宿主隔离:两个宿主各自 start 得到不同 id);
47
+ 4. `session_turn` 提交一批宿主事件 → 返回 `{reason, steps}`;未知宿主事件应无报错(hostRaw 降级留痕,非失败);
48
+ 5. `session_verify` → `ok:true`(哈希链完整);`session_replay` → 消息流与宿主侧对话语义一致;
49
+ 6. 结束会话后检查 stderr 快照行:`zeroContent=true`(采集窗口零内容红线)。
50
+
51
+ ## 采集窗口(D-2 数据窗口 S18–S20)
52
+
53
+ - 每次会话结束(stdin 关闭)向 **stderr** 输出一行快照(stdout 为协议通道不可污染);
54
+ - 快照仅含 3 个零内容 Counter(load_total / unsigned_confirmed / registry_decision)——
55
+ `assertZeroContent` 自检内建;
56
+ - 登记表兜底:企业未开启遥测时,人工抄录快照行即可满足 S20 enforce 决策的数据输入(双通道设计)。
57
+
58
+ ## E-6 实连执行记录(2026-09-05 · 本机冒烟 ✅)
59
+
60
+ - 协议握手已实装(initialize / notifications/initialized——MCP 协议兼容,冒烟前置补齐);
61
+ - 本机端到端冒烟(真实 spawn 进程,配置同款命令行):initialize serverInfo=car-runtime 0.3.0 → tools/list 9 → session_start=SH-fa5c8838469ba4599032d35b → session_turn completed → session_verify ok → stderr 快照 zeroContent=true;
62
+ - 宿主侧配置已写入:Claude Code 项目级 `D:/WorkBuddy/agent/.mcp.json` + Codex `~/.codex/config.toml` 追加段(command 用 Node 22.22.2 绝对路径——系统 node20 不支持 --experimental-transform-types);
63
+ - 待真实宿主 GUI 会话验证(启动 Claude Code 于本工作区 / codex 会话内确认 9 tool 出现)。
64
+
65
+ ## 已知边界
66
+
67
+ - 会话状态为进程内存态(进程退出即失);跨进程持久化走 `session_export` 取证包 → S19 评估文件落盘增强;
68
+ - `tool_list`/`tool_call`(宿主工具面透传)在试点期返回占位(S19 接线宿主侧工具注册)。
@@ -0,0 +1,33 @@
1
+ # 等保 2.0 三级安全审计逐条核验清单(D-4 裁决落地 · S9)
2
+
3
+ > **核验方法**:GB/T 22239-2019《信息安全技术 网络安全等级保护基本要求》8.1.4.3 安全审计(三级,安全计算环境)
4
+ > 条目原文经**三个独立公开来源交叉证实**(腾讯云等保解读 cloud.tencent.com.cn/document/practice/664/71724、
5
+ > 深信服等保三级配置指导手册、长沙望城区政府采购需求文件 changs.ccgp-hunan.gov.cn,2026-09-04 检索)——
6
+ > 置信度由 v1.1 的「常识归纳」升级为「条目级已核验(公开文本交叉证实)」。正式测评仍以测评机构现场判定为准。
7
+
8
+ ## 逐条核验(8.1.4.3 a–d)
9
+
10
+ | 条目 | 原文(三级) | CAR 承载 | 判定 |
11
+ |------|------------|---------|------|
12
+ | a) | 应启用安全审计功能,审计覆盖到每个用户,对重要的用户行为和重要安全事件进行审计 | 会话日志全量事件覆盖四类 actor(user/model/plugin/runtime);授权决策(authorization/*)、沙箱拒绝、降级事件、供应链事件全留痕;默认开启不可关闭(检测不可静默关闭,红线 §6.3) | ✅ 满足 |
13
+ | b) | 审计记录应包括事件的日期和时间、用户、事件类型、事件是否成功及其他与审计相关的信息 | 13 项审计字段最小集:ts(ISO-8601 UTC)/actor/eventType/result/status……(安全设计 §7.2.2 Q-09) | ✅ 满足 |
14
+ | c) | 应对审计记录进行保护,定期备份,避免受到未预期的删除、修改或覆盖等 | 防未预期篡改 = append-only + SHA-256 哈希链 + verifyChain(断链即 AL-01 P0);导出前强制断链校验;文件权限 0700/0600 | ⚠️ 部分——**「定期备份」为部署方配套措施**(运行时提供导出副本 + 完整性清单,不执行自动备份调度);防覆盖 = 运行时零删除路径(仅用户显式删会话)已满足 |
15
+ | d) | 应对审计进程进行保护,防止未经授权的中断 | 审计写路径 fail-fast:盘满 A070001 → turn 以 error 收口,禁止静默丢弃(L0 审计无降级路径);审计接口对日志零写入(T4 域隔离) | ⚠️ 部分——**本机同级进程可终止运行时进程**(OS 级保护归部署方:专用账户/最小权限/主机加固);运行时侧已尽 fail-fast + 显式失败 |
16
+
17
+ ## 差距项处置(禁止静默降标)
18
+
19
+ | 编号 | 差距 | 处置 | 时机 |
20
+ |------|------|------|------|
21
+ | MLPS-G1 | c) 定期备份调度 | 部署方配套措施——部署文档提供《等保配套检查单》(导出副本周期建议 + WORM 存储选项);运行时侧 M3 评估可选定时导出 | S9 文档 / M3 评估 |
22
+ | MLPS-G2 | d) 进程级防中断 | 部署方配套措施——以专用低权限账户运行(car doctor 已检查 root/SYSTEM 并 WARN)+ OS 服务管理器托管(systemd Restart/Windows 服务恢复策略) | S9 文档 |
23
+ | MLPS-G3 | 日志留存 ≥6 个月 | 运行时默认永不清理(满足);「留存承诺」为部署方在等保定级材料中的义务声明 | 声明模板 |
24
+
25
+ ## 分层声明集成
26
+
27
+ `complianceStatement('mlps')`(src/session/export.ts)输出上述判定结论的程序化版本——
28
+ 面向客户合规问卷的答复骨架;三标(SOC 2 / GDPR / 等保)分层字段映射见 FIELD_STANDARD_MAP。
29
+
30
+ ## 遗留
31
+
32
+ - 正式测评口径以测评机构现场判定为准;本清单为产品侧证据准备。
33
+ - M3 backlog:可选定时导出(若企业深访显示备份自动化是采购关键)。
@@ -0,0 +1,45 @@
1
+ # Windows 沙箱 spike 方案(M2-S7 出方案 → S8 实装,M2系统设计增补 T-3 / 规划 §4 三重闸门第一道)
2
+
3
+ ## 路线定稿
4
+
5
+ **路线 a(推荐,证据充分)**:Node + koffi FFI 纯 JS 实现 Win32 受限 token + Job Object——
6
+ dsh win32 路径即此形态(`sandbox-local/src/index.ts:160-161`,源码级已核实),Codex 受限 token 语义为参照
7
+ (SR-19)。禁运行时网络下载沙箱组件(M2部署设计增补 D-3)——koffi 走 optionalDependencies 随包分发。
8
+
9
+ ## 三阶段 spike(S8 第一周执行,Windows 10/11 + Server 2022 双机)
10
+
11
+ ### 阶段 1:受限 token 创建(约 1 人日)
12
+
13
+ koffi 调用链:`CreateProcessAsUserW`(受限 token)或 `CreateProcessW` + `CREATE_RESTRICTED_TOKEN`
14
+ - `OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE|TOKEN_QUERY, &h)`
15
+ - `CreateRestrictedToken(h, DISABLE_MAX_PRIVILEGE, 0, NULL, 0, NULL, 1, [sids deny Administrators], &restricted)`
16
+ - 验证命令:spike 子进程内执行 `whoami /groups` → 断言 `S-1-16-0`(Untrusted IL)或 Administrators deny SID 在列;
17
+ - 写能力探针:尝试写 `%USERPROFILE%` 外路径 → 断言拒绝(fail-closed)。
18
+
19
+ ### 阶段 2:Job Object 资源配额(约 0.5 人日)
20
+
21
+ - `CreateJobObjectW` → `SetInformationJobObject(JobObjectExtendedLimitInformation)`:
22
+ 内存 256MB + 活跃进程数 32 + `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`;
23
+ - `AssignProcessToJobObject`;验证:fork 炸弹用例(POC-4 T-17)在配额内被拒。
24
+
25
+ ### 阶段 3:逃逸矩阵试跑(约 1 人日)
26
+
27
+ - POC-4 的 20 条用例以 win32 后端实跑(env-proc 类预期通过 deny 继承成立);
28
+ - 全 PASS + 审计 JSONL → 进 CI windows-2022 runner(16 Job 矩阵,M2部署设计增补)。
29
+
30
+ ## 四判据(S8 结论门)
31
+
32
+ 1. 受限 token 下 `whoami /groups` 显示剥离管理员 SID;
33
+ 2. 工作区外写操作返回 ACCESS_DENIED(fail-closed,非静默);
34
+ 3. Job Object 配额触发生效(fork 炸弹被杀,父进程存活);
35
+ 4. koffi 调用零原生编译(纯 JS 依赖链成立,npm install 干净机器可复现)。
36
+
37
+ ## 逐级回退(判据任一不可达)
38
+
39
+ a 失败 → 回退 b:`PsExec -l -e` 外部工具包装(依赖 Sysinternals,企业可接受性降);b 失败 → 回退 c:
40
+ win32 显式降级维持 S3 现状(BD-01 语义,降级需走中间确认——M2 规划 §4 第三道闸门)。
41
+
42
+ ## spike 脚本
43
+
44
+ `scripts/win32-koffi-spike.ts`——阶段 1/2 的 koffi 调用骨架(`koffi` 未安装时输出 SKIP + 判据清单,
45
+ 与 POC-4 windows-skip 后端口径一致)。
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@lqc123qwe/car-runtime",
3
+ "version": "1.0.0-rc.1",
4
+ "description": "Composable Agent Runtime (CAR) - microkernel plugin system with sandboxed PTC, append-only hashed session log, and MCP host bridge",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=22.19.0"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/lqc624/car-runtime.git"
13
+ },
14
+ "publishConfig": {
15
+ "access": "public",
16
+ "registry": "https://registry.npmjs.org/"
17
+ },
18
+ "scripts": {
19
+ "test": "node --experimental-transform-types --test test/",
20
+ "typecheck": "tsc --noEmit -p tsconfig.json"
21
+ },
22
+ "optionalDependencies": {
23
+ "koffi": "^3.2.1"
24
+ }
25
+ }
@@ -0,0 +1,118 @@
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.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()