@lqc123qwe/car-runtime 1.8.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.
- package/README.md +29 -1
- package/dist/SHA256SUMS +1 -0
- package/dist/authz/authz.d.ts +44 -0
- package/dist/authz/authz.js +65 -0
- package/dist/car-runtime-1.10.0.tgz +0 -0
- package/dist/car-runtime-1.10.0.tgz.sig.bundle +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +825 -0
- package/dist/dx/doctor.d.ts +62 -0
- package/dist/dx/doctor.js +127 -0
- package/dist/governance/dump.d.ts +47 -0
- package/dist/governance/dump.js +17 -0
- package/dist/host/facade.d.ts +13 -0
- package/dist/host/facade.js +154 -0
- package/dist/host/hostGateway.d.ts +117 -0
- package/dist/host/hostGateway.js +65 -0
- package/dist/host/mappings.d.ts +27 -0
- package/dist/host/mappings.js +52 -0
- package/dist/host/stdio.d.ts +14 -0
- package/dist/host/stdio.js +83 -0
- package/dist/kernel/context.d.ts +76 -0
- package/dist/kernel/context.js +203 -0
- package/dist/kernel/events.d.ts +37 -0
- package/dist/kernel/events.js +82 -0
- package/dist/load/config.d.ts +79 -0
- package/dist/load/config.js +285 -0
- package/dist/load/loader.d.ts +72 -0
- package/dist/load/loader.js +162 -0
- package/dist/load/registry.d.ts +44 -0
- package/dist/load/registry.js +58 -0
- package/dist/load/report.d.ts +98 -0
- package/dist/load/report.js +301 -0
- package/dist/load/sigGate.d.ts +22 -0
- package/dist/load/sigGate.js +68 -0
- package/dist/load/sign.d.ts +25 -0
- package/dist/load/sign.js +47 -0
- package/dist/load/verifier.d.ts +35 -0
- package/dist/load/verifier.js +56 -0
- package/dist/loop/goal.d.ts +31 -0
- package/dist/loop/goal.js +34 -0
- package/dist/loop/recover.d.ts +17 -0
- package/dist/loop/recover.js +39 -0
- package/dist/loop/stop.d.ts +79 -0
- package/dist/loop/stop.js +151 -0
- package/dist/mcp/gateway.d.ts +96 -0
- package/dist/mcp/gateway.js +69 -0
- package/dist/mcp/httpClient.d.ts +27 -0
- package/dist/mcp/httpClient.js +196 -0
- package/dist/mcp/stdioClient.d.ts +8 -0
- package/dist/mcp/stdioClient.js +73 -0
- package/dist/ptc/budget.d.ts +21 -0
- package/dist/ptc/budget.js +36 -0
- package/dist/ptc/erasable.d.ts +14 -0
- package/dist/ptc/erasable.js +27 -0
- package/dist/ptc/runCode.d.ts +65 -0
- package/dist/ptc/runCode.js +140 -0
- package/dist/ptc/schemaRender.d.ts +15 -0
- package/dist/ptc/schemaRender.js +90 -0
- package/dist/ptc/sdk.d.ts +27 -0
- package/dist/ptc/sdk.js +23 -0
- package/dist/ptc/worker-entry.d.ts +1 -0
- package/dist/ptc/worker-entry.js +71 -0
- package/dist/release-audit.jsonl +4 -0
- package/dist/runtime-core/chatStep.d.ts +40 -0
- package/dist/runtime-core/chatStep.js +104 -0
- package/dist/runtime-core/credentials.d.ts +49 -0
- package/dist/runtime-core/credentials.js +118 -0
- package/dist/runtime-core/errors.d.ts +30 -0
- package/dist/runtime-core/errors.js +36 -0
- package/dist/runtime-core/llm.d.ts +76 -0
- package/dist/runtime-core/llm.js +553 -0
- package/dist/runtime-core/redaction.d.ts +39 -0
- package/dist/runtime-core/redaction.js +79 -0
- package/dist/runtime-core/telemetry.d.ts +88 -0
- package/dist/runtime-core/telemetry.js +332 -0
- package/dist/runtime-core/tools.d.ts +15 -0
- package/dist/runtime-core/tools.js +12 -0
- package/dist/runtime-core/trace.d.ts +55 -0
- package/dist/runtime-core/trace.js +137 -0
- package/dist/runtime-core/types.d.ts +84 -0
- package/dist/runtime-core/types.js +13 -0
- package/dist/sandbox/sandbox.d.ts +68 -0
- package/dist/sandbox/sandbox.js +153 -0
- package/dist/sbom.json +263 -0
- package/dist/seam.d.ts +21 -0
- package/dist/seam.js +20 -0
- package/dist/security/secrets-baseline.d.ts +43 -0
- package/dist/security/secrets-baseline.js +176 -0
- package/dist/security/secrets.d.ts +45 -0
- package/dist/security/secrets.js +138 -0
- package/dist/session/export.d.ts +55 -0
- package/dist/session/export.js +81 -0
- package/dist/session/fork.d.ts +51 -0
- package/dist/session/fork.js +63 -0
- package/dist/session/format.d.ts +21 -0
- package/dist/session/format.js +49 -0
- package/dist/session/indexStore.d.ts +44 -0
- package/dist/session/indexStore.js +265 -0
- package/dist/session/log.d.ts +79 -0
- package/dist/session/log.js +157 -0
- package/dist/session/store.d.ts +9 -0
- package/dist/session/store.js +37 -0
- package/dist/telemetry/metrics.d.ts +25 -0
- package/dist/telemetry/metrics.js +26 -0
- package/package.json +17 -1
- package/src/mcp/gateway.ts +10 -2
- package/src/mcp/httpClient.ts +23 -5
- package/src/runtime-core/trace.ts +5 -0
- package/src/seam.ts +21 -0
- package/.gitattributes +0 -11
- package/.github/workflows/ci.yml +0 -293
- package/.github/workflows/release.yml +0 -54
- package/adr/ADR-001.md +0 -4
- package/adr/ADR-002.md +0 -5
- package/adr/ADR-003.md +0 -35
- package/adr/host-onboarding.md +0 -68
- package/adr/mlps-audit-checklist.md +0 -41
- package/adr/win32-spike.md +0 -45
- package/docs/plugin-authoring.md +0 -82
- package/native/landlock-run/Makefile +0 -19
- package/native/landlock-run/landlock-run.c +0 -162
- package/scripts/contract-check.ts +0 -56
- package/scripts/invariant-n1.ts +0 -96
- package/scripts/package-check.ts +0 -29
- package/scripts/ptc-baseline/run-baseline.ts +0 -118
- package/scripts/release-artifacts.ts +0 -111
- package/scripts/release-pipeline.ts +0 -245
- package/scripts/secrets-baseline.ts +0 -33
- package/scripts/secrets-scan.ts +0 -56
- package/scripts/win32-ffi-debug.ts +0 -143
- package/scripts/win32-koffi-spike.ts +0 -537
- package/test/contract/eventmap.golden.json +0 -7
- package/test/fixtures/hello-plugin.ts +0 -5
- package/test/kernel.spec.ts +0 -136
- package/test/registry-e2e/run-e2e.ts +0 -213
- package/test/s11.spec.ts +0 -129
- package/test/s12.spec.ts +0 -135
- package/test/s13.spec.ts +0 -117
- package/test/s14.spec.ts +0 -117
- package/test/s15.spec.ts +0 -80
- package/test/s16.spec.ts +0 -133
- package/test/s17.spec.ts +0 -102
- package/test/s18.spec.ts +0 -81
- package/test/s19.spec.ts +0 -138
- package/test/s2.spec.ts +0 -245
- package/test/s20.spec.ts +0 -241
- package/test/s21.spec.ts +0 -231
- package/test/s22-m7-store-format.spec.ts +0 -135
- package/test/s23-m7-export-index.spec.ts +0 -189
- package/test/s24-signature-gate.spec.ts +0 -185
- package/test/s25-m8-runtime-core.spec.ts +0 -600
- package/test/s26-signature-closeout.spec.ts +0 -349
- package/test/s27-host-load-wiring.spec.ts +0 -134
- package/test/s28-session-index-followup.spec.ts +0 -236
- package/test/s29-otel-producer.spec.ts +0 -252
- package/test/s3.spec.ts +0 -179
- package/test/s30-release-artifacts.spec.ts +0 -127
- package/test/s31-real-model-wiring.spec.ts +0 -329
- package/test/s32-telemetry-mcp-ptc.spec.ts +0 -218
- package/test/s33-anthropic-adapter.spec.ts +0 -354
- package/test/s34-mcp-remote-transport.spec.ts +0 -250
- package/test/s35-config-ts-zod.spec.ts +0 -145
- package/test/s36-host-tool-surface.spec.ts +0 -250
- package/test/s37-projection-dedupe.spec.ts +0 -161
- package/test/s38-config-jiti.spec.ts +0 -116
- package/test/s39-otlp-span-semantics.spec.ts +0 -100
- package/test/s4.spec.ts +0 -169
- package/test/s40-metrics-cumulative.spec.ts +0 -67
- package/test/s41-mcp-serve-turn-span.spec.ts +0 -148
- package/test/s42-traceparent-propagation.spec.ts +0 -415
- package/test/s5.spec.ts +0 -95
- package/test/s6.spec.ts +0 -169
- package/test/s7.spec.ts +0 -136
- package/test/s7b-baseline.spec.ts +0 -20
- package/test/s9.spec.ts +0 -89
- package/test/sandbox-escape/j17-orchestrate.sh +0 -30
- package/test/sandbox-escape/run-matrix.sh +0 -98
- package/tsconfig.json +0 -18
|
@@ -1,41 +0,0 @@
|
|
|
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:可选定时导出(若企业深访显示备份自动化是采购关键)。
|
|
34
|
-
|
|
35
|
-
## D-4 追记 · MLPS-G1 处置定稿(2026-09-09 主理人裁决)
|
|
36
|
-
|
|
37
|
-
> **裁决:维持部署方配套,运行时不实装定时导出**(M3 backlog 保留观察)。
|
|
38
|
-
>
|
|
39
|
-
> 依据:①运行时侧 c) 项证据链已闭环(append-only + SHA-256 哈希链 + verifyChain + 零删除路径 + 导出副本/完整性清单能力),「定期备份调度」属部署方运维域;②等保三级才是审计备份强约束场景,rc 阶段无企业等保客户在途,提前实装属过度供给;③零依赖红线(G-04)下新增定时器为纯增量代码重量。
|
|
40
|
-
>
|
|
41
|
-
> 关闭条件重开:企业深访中「备份自动化」成为采购关键,或首个等保定级客户接入时,从 M3 backlog 提级(预估 0.5 人日:定时器 + 导出副本 + 完整性清单 + 关闭开关)。
|
package/adr/win32-spike.md
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
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/docs/plugin-authoring.md
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
# CAR 插件作者指南(M6 DX)
|
|
2
|
-
|
|
3
|
-
面向插件作者的写码红线与热重载实操。本文所述行为均为实测口径(POC-2 + S1–S3 红线),不是"建议"。
|
|
4
|
-
|
|
5
|
-
## 插件文件形态
|
|
6
|
-
|
|
7
|
-
CAR 免编译加载插件(Node type-stripping 载体),一个插件 = 一个 `.ts` 文件:
|
|
8
|
-
|
|
9
|
-
```ts
|
|
10
|
-
// my-plugin.ts
|
|
11
|
-
export const manifest = { name: 'my-plugin', version: '1.2.3' } // 命名导出,可省略(省略则按文件名合成)
|
|
12
|
-
|
|
13
|
-
export default function apply(api) {
|
|
14
|
-
api.registerTool({ name: 'hello', run: async () => 'world' })
|
|
15
|
-
api.registerCommand({ name: 'hi', run: async (args) => console.log('hi', args) })
|
|
16
|
-
}
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
- `manifest.version` 必须是**精确 semver**(`x.y.z`),版本区间仅用于 peer 校验;
|
|
20
|
-
- `manifest.peers` 声明 peer 约束:`{ peer, range, optional? }`;冲突即加载期硬报错,
|
|
21
|
-
放宽需 `manifest.peerPolicyOverride = { relaxed: true, reason: '…' }`(reason 必填,审计留痕);
|
|
22
|
-
- 插件源码只接受 **erasable TS**(禁 enum/namespace/装饰器等需转换的语法,loader 挂点显式拒绝)。
|
|
23
|
-
|
|
24
|
-
## 写码红线(违反 = 运行期错位,已实证)
|
|
25
|
-
|
|
26
|
-
### 1. invalidate 必须包裹插件 fiber 作用域 ctx,而非根 ctx
|
|
27
|
-
|
|
28
|
-
POC-2 实证:把根 ctx 透传给插件工厂会导致 effect 注册到错误 fiber——**effect 注册错位、
|
|
29
|
-
dispose 回卷不到**。热重载失效旧实例时,只能失效插件自身作用域的 ctx(`api` 句柄),
|
|
30
|
-
根 ctx 与其它插件不受影响。插件作者视角:不要保存/传递宿主根 ctx,只使用 `apply(api)` 收到的 `api`。
|
|
31
|
-
|
|
32
|
-
### 2. apply 不能返回非 disposer 对象
|
|
33
|
-
|
|
34
|
-
cordis 语义:`apply` 返回值 = effect body。CAR 内核同步约定——`apply` 同步完成注册,
|
|
35
|
-
需要异步初始化请走 `ctx.effect(async () => { … })`。返回 Promise/普通对象 = 加载期显式报错。
|
|
36
|
-
|
|
37
|
-
### 3. getRegisteredTools 在 bindCore 前调用即抛 CAR-STUB
|
|
38
|
-
|
|
39
|
-
Stub 语义分层(红线 5):
|
|
40
|
-
- **注册类动作**(`registerTool`/`registerCommand`)Stub 期始终可用——收集待注册项,`bindCore` 后冲刷;
|
|
41
|
-
- **能力查询**(`getRegisteredTools`)依赖宿主绑定,bindCore 前一调用即抛
|
|
42
|
-
`CAR-STUB`——用启动期显式失败换运行期时序正确。
|
|
43
|
-
|
|
44
|
-
### 4. 旧 ctx 访问抛 CAR-INVALIDATED
|
|
45
|
-
|
|
46
|
-
`car reload`(或 `ReloadManager.reload()`)后,旧插件实例的 `api` 句柄**任何属性访问**都抛
|
|
47
|
-
`CAR-INVALIDATED: … (旧上下文已失效,请重新加载)`。持有旧句柄的闭包不会静默失效——显式报错定位。
|
|
48
|
-
|
|
49
|
-
## 热重载实操
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
car reload my-plugin.ts # 单文件
|
|
53
|
-
car reload ./plugins/ # 目录(扫描 *.ts,排除 .d.ts/.spec.ts)
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
五阶段流水线(任一 FAIL 短路,后续 SKIPPED,FAIL 附文件+原因定位):
|
|
57
|
-
|
|
58
|
-
| 阶段 | 内容 | 典型 FAIL |
|
|
59
|
-
|------|------|-----------|
|
|
60
|
-
| discover | 扫描插件文件;git 直载来源产生 CAR-W-GIT-DIRECT 告警(E-03) | 路径不存在 / 无 .ts |
|
|
61
|
-
| parse | 读 manifest(命名导出,缺失按文件名合成)+ parseManifest | version 非精确 semver |
|
|
62
|
-
| validate | peer 约束校验,冲突链进报告 `conflicts` | peer 区间不符且未豁免 |
|
|
63
|
-
| topo | peer 依赖边 Kahn 拓扑排序 | 依赖环(CAR-E-DEPCYCLE) |
|
|
64
|
-
| register | mountPlugin 逐个装配(拓扑序) | 工厂非默认导出 / erasable 违规 / 重名 |
|
|
65
|
-
|
|
66
|
-
成功输出含 `installId`、`startedTime`、`durationMs`(QS-05 基线 P95 ≤ 800ms)与 `warnings`。
|
|
67
|
-
|
|
68
|
-
程序化热重载:
|
|
69
|
-
|
|
70
|
-
```ts
|
|
71
|
-
import { ReloadManager } from '../src/load/report.ts'
|
|
72
|
-
const mgr = new ReloadManager()
|
|
73
|
-
const { report, plugins } = await mgr.reload('./plugins/') // epoch 每次 +1 击穿模块缓存
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
## peer 冲突链示例
|
|
77
|
-
|
|
78
|
-
```
|
|
79
|
-
[validate] FAIL — ./plugins/b.ts: peer dep-p@^2.0.0 required but 1.0.0 installed (path: manifest.peers)
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
冲突链定位三要素:**文件**(谁声明的)、**区间 vs 实际版本**、**依赖路径**。按此修版本或走豁免流程。
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# landlock-run 构建单源(M3部署设计增补 §2.1.2:J-01 单点编译,J-05/J-17 消费产物,禁止平台内重编译)
|
|
2
|
-
# 目标三元组:linux-x64-gnu;产物指纹随 J-01 SBOM 下发,消费方启动时校验 sha256
|
|
3
|
-
CC ?= cc
|
|
4
|
-
CFLAGS ?= -O2 -std=c11 -Wall -Wextra -Werror
|
|
5
|
-
TARGET := landlock-run
|
|
6
|
-
|
|
7
|
-
.PHONY: all clean verify
|
|
8
|
-
|
|
9
|
-
all: $(TARGET)
|
|
10
|
-
|
|
11
|
-
$(TARGET): landlock-run.c
|
|
12
|
-
$(CC) $(CFLAGS) -o $@ $<
|
|
13
|
-
|
|
14
|
-
# 消费方(J-05/J-17)产物指纹校验入口
|
|
15
|
-
verify: $(TARGET)
|
|
16
|
-
sha256sum $(TARGET)
|
|
17
|
-
|
|
18
|
-
clean:
|
|
19
|
-
rm -f $(TARGET)
|
|
@@ -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)
|
package/scripts/invariant-n1.ts
DELETED
|
@@ -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)
|
package/scripts/package-check.ts
DELETED
|
@@ -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)
|