@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.
Files changed (178) hide show
  1. package/README.md +29 -1
  2. package/dist/SHA256SUMS +1 -0
  3. package/dist/authz/authz.d.ts +44 -0
  4. package/dist/authz/authz.js +65 -0
  5. package/dist/car-runtime-1.10.0.tgz +0 -0
  6. package/dist/car-runtime-1.10.0.tgz.sig.bundle +1 -0
  7. package/dist/cli.d.ts +1 -0
  8. package/dist/cli.js +825 -0
  9. package/dist/dx/doctor.d.ts +62 -0
  10. package/dist/dx/doctor.js +127 -0
  11. package/dist/governance/dump.d.ts +47 -0
  12. package/dist/governance/dump.js +17 -0
  13. package/dist/host/facade.d.ts +13 -0
  14. package/dist/host/facade.js +154 -0
  15. package/dist/host/hostGateway.d.ts +117 -0
  16. package/dist/host/hostGateway.js +65 -0
  17. package/dist/host/mappings.d.ts +27 -0
  18. package/dist/host/mappings.js +52 -0
  19. package/dist/host/stdio.d.ts +14 -0
  20. package/dist/host/stdio.js +83 -0
  21. package/dist/kernel/context.d.ts +76 -0
  22. package/dist/kernel/context.js +203 -0
  23. package/dist/kernel/events.d.ts +37 -0
  24. package/dist/kernel/events.js +82 -0
  25. package/dist/load/config.d.ts +79 -0
  26. package/dist/load/config.js +285 -0
  27. package/dist/load/loader.d.ts +72 -0
  28. package/dist/load/loader.js +162 -0
  29. package/dist/load/registry.d.ts +44 -0
  30. package/dist/load/registry.js +58 -0
  31. package/dist/load/report.d.ts +98 -0
  32. package/dist/load/report.js +301 -0
  33. package/dist/load/sigGate.d.ts +22 -0
  34. package/dist/load/sigGate.js +68 -0
  35. package/dist/load/sign.d.ts +25 -0
  36. package/dist/load/sign.js +47 -0
  37. package/dist/load/verifier.d.ts +35 -0
  38. package/dist/load/verifier.js +56 -0
  39. package/dist/loop/goal.d.ts +31 -0
  40. package/dist/loop/goal.js +34 -0
  41. package/dist/loop/recover.d.ts +17 -0
  42. package/dist/loop/recover.js +39 -0
  43. package/dist/loop/stop.d.ts +79 -0
  44. package/dist/loop/stop.js +151 -0
  45. package/dist/mcp/gateway.d.ts +96 -0
  46. package/dist/mcp/gateway.js +69 -0
  47. package/dist/mcp/httpClient.d.ts +27 -0
  48. package/dist/mcp/httpClient.js +196 -0
  49. package/dist/mcp/stdioClient.d.ts +8 -0
  50. package/dist/mcp/stdioClient.js +73 -0
  51. package/dist/ptc/budget.d.ts +21 -0
  52. package/dist/ptc/budget.js +36 -0
  53. package/dist/ptc/erasable.d.ts +14 -0
  54. package/dist/ptc/erasable.js +27 -0
  55. package/dist/ptc/runCode.d.ts +65 -0
  56. package/dist/ptc/runCode.js +140 -0
  57. package/dist/ptc/schemaRender.d.ts +15 -0
  58. package/dist/ptc/schemaRender.js +90 -0
  59. package/dist/ptc/sdk.d.ts +27 -0
  60. package/dist/ptc/sdk.js +23 -0
  61. package/dist/ptc/worker-entry.d.ts +1 -0
  62. package/dist/ptc/worker-entry.js +71 -0
  63. package/dist/release-audit.jsonl +4 -0
  64. package/dist/runtime-core/chatStep.d.ts +40 -0
  65. package/dist/runtime-core/chatStep.js +104 -0
  66. package/dist/runtime-core/credentials.d.ts +49 -0
  67. package/dist/runtime-core/credentials.js +118 -0
  68. package/dist/runtime-core/errors.d.ts +30 -0
  69. package/dist/runtime-core/errors.js +36 -0
  70. package/dist/runtime-core/llm.d.ts +76 -0
  71. package/dist/runtime-core/llm.js +553 -0
  72. package/dist/runtime-core/redaction.d.ts +39 -0
  73. package/dist/runtime-core/redaction.js +79 -0
  74. package/dist/runtime-core/telemetry.d.ts +88 -0
  75. package/dist/runtime-core/telemetry.js +332 -0
  76. package/dist/runtime-core/tools.d.ts +15 -0
  77. package/dist/runtime-core/tools.js +12 -0
  78. package/dist/runtime-core/trace.d.ts +55 -0
  79. package/dist/runtime-core/trace.js +137 -0
  80. package/dist/runtime-core/types.d.ts +84 -0
  81. package/dist/runtime-core/types.js +13 -0
  82. package/dist/sandbox/sandbox.d.ts +68 -0
  83. package/dist/sandbox/sandbox.js +153 -0
  84. package/dist/sbom.json +263 -0
  85. package/dist/seam.d.ts +21 -0
  86. package/dist/seam.js +20 -0
  87. package/dist/security/secrets-baseline.d.ts +43 -0
  88. package/dist/security/secrets-baseline.js +176 -0
  89. package/dist/security/secrets.d.ts +45 -0
  90. package/dist/security/secrets.js +138 -0
  91. package/dist/session/export.d.ts +55 -0
  92. package/dist/session/export.js +81 -0
  93. package/dist/session/fork.d.ts +51 -0
  94. package/dist/session/fork.js +63 -0
  95. package/dist/session/format.d.ts +21 -0
  96. package/dist/session/format.js +49 -0
  97. package/dist/session/indexStore.d.ts +44 -0
  98. package/dist/session/indexStore.js +265 -0
  99. package/dist/session/log.d.ts +79 -0
  100. package/dist/session/log.js +157 -0
  101. package/dist/session/store.d.ts +9 -0
  102. package/dist/session/store.js +37 -0
  103. package/dist/telemetry/metrics.d.ts +25 -0
  104. package/dist/telemetry/metrics.js +26 -0
  105. package/package.json +17 -1
  106. package/src/mcp/gateway.ts +10 -2
  107. package/src/mcp/httpClient.ts +23 -5
  108. package/src/runtime-core/trace.ts +5 -0
  109. package/src/seam.ts +21 -0
  110. package/.gitattributes +0 -11
  111. package/.github/workflows/ci.yml +0 -293
  112. package/.github/workflows/release.yml +0 -54
  113. package/adr/ADR-001.md +0 -4
  114. package/adr/ADR-002.md +0 -5
  115. package/adr/ADR-003.md +0 -35
  116. package/adr/host-onboarding.md +0 -68
  117. package/adr/mlps-audit-checklist.md +0 -41
  118. package/adr/win32-spike.md +0 -45
  119. package/docs/plugin-authoring.md +0 -82
  120. package/native/landlock-run/Makefile +0 -19
  121. package/native/landlock-run/landlock-run.c +0 -162
  122. package/scripts/contract-check.ts +0 -56
  123. package/scripts/invariant-n1.ts +0 -96
  124. package/scripts/package-check.ts +0 -29
  125. package/scripts/ptc-baseline/run-baseline.ts +0 -118
  126. package/scripts/release-artifacts.ts +0 -111
  127. package/scripts/release-pipeline.ts +0 -245
  128. package/scripts/secrets-baseline.ts +0 -33
  129. package/scripts/secrets-scan.ts +0 -56
  130. package/scripts/win32-ffi-debug.ts +0 -143
  131. package/scripts/win32-koffi-spike.ts +0 -537
  132. package/test/contract/eventmap.golden.json +0 -7
  133. package/test/fixtures/hello-plugin.ts +0 -5
  134. package/test/kernel.spec.ts +0 -136
  135. package/test/registry-e2e/run-e2e.ts +0 -213
  136. package/test/s11.spec.ts +0 -129
  137. package/test/s12.spec.ts +0 -135
  138. package/test/s13.spec.ts +0 -117
  139. package/test/s14.spec.ts +0 -117
  140. package/test/s15.spec.ts +0 -80
  141. package/test/s16.spec.ts +0 -133
  142. package/test/s17.spec.ts +0 -102
  143. package/test/s18.spec.ts +0 -81
  144. package/test/s19.spec.ts +0 -138
  145. package/test/s2.spec.ts +0 -245
  146. package/test/s20.spec.ts +0 -241
  147. package/test/s21.spec.ts +0 -231
  148. package/test/s22-m7-store-format.spec.ts +0 -135
  149. package/test/s23-m7-export-index.spec.ts +0 -189
  150. package/test/s24-signature-gate.spec.ts +0 -185
  151. package/test/s25-m8-runtime-core.spec.ts +0 -600
  152. package/test/s26-signature-closeout.spec.ts +0 -349
  153. package/test/s27-host-load-wiring.spec.ts +0 -134
  154. package/test/s28-session-index-followup.spec.ts +0 -236
  155. package/test/s29-otel-producer.spec.ts +0 -252
  156. package/test/s3.spec.ts +0 -179
  157. package/test/s30-release-artifacts.spec.ts +0 -127
  158. package/test/s31-real-model-wiring.spec.ts +0 -329
  159. package/test/s32-telemetry-mcp-ptc.spec.ts +0 -218
  160. package/test/s33-anthropic-adapter.spec.ts +0 -354
  161. package/test/s34-mcp-remote-transport.spec.ts +0 -250
  162. package/test/s35-config-ts-zod.spec.ts +0 -145
  163. package/test/s36-host-tool-surface.spec.ts +0 -250
  164. package/test/s37-projection-dedupe.spec.ts +0 -161
  165. package/test/s38-config-jiti.spec.ts +0 -116
  166. package/test/s39-otlp-span-semantics.spec.ts +0 -100
  167. package/test/s4.spec.ts +0 -169
  168. package/test/s40-metrics-cumulative.spec.ts +0 -67
  169. package/test/s41-mcp-serve-turn-span.spec.ts +0 -148
  170. package/test/s42-traceparent-propagation.spec.ts +0 -415
  171. package/test/s5.spec.ts +0 -95
  172. package/test/s6.spec.ts +0 -169
  173. package/test/s7.spec.ts +0 -136
  174. package/test/s7b-baseline.spec.ts +0 -20
  175. package/test/s9.spec.ts +0 -89
  176. package/test/sandbox-escape/j17-orchestrate.sh +0 -30
  177. package/test/sandbox-escape/run-matrix.sh +0 -98
  178. package/tsconfig.json +0 -18
@@ -0,0 +1,138 @@
1
+ /**
2
+ * F12 · secrets 检测(完整版,M2-S7 实装加载期;运行期输出期挂点预留)
3
+ *
4
+ * 口径(M2 安全设计增补 T-6 + D-3 裁决):
5
+ * - 模式库 6 类(api-key 前缀族 / 私钥块 / 连接串 / 云凭据 / token-Bearer / 自定义正则);
6
+ * - 三层检测降误报:L1 精确前缀(Aho-Corasick 等价的朴素扫描,规模小不构成瓶颈)
7
+ * → L2 Shannon 熵(≥3.5 才升级候选,排除 'aaaa' 类低熵误报)
8
+ * → L3 上下文打分(键名共现 +1、代码引号内 +0、注释 -1;总分 ≥2 判命中);
9
+ * - 可验证交付(D-3):漏报率 <1.5%、FPR ≤5%、precision ≥80%——基准集 1:9 正负比、CI 标定;
10
+ * - 检测不可静默关闭:disable 仅允许显式配置且留痕(本层不提供总开关,红线 §6.3)。
11
+ */
12
+ /** 6 类模式库(v1:30 条精选;自定义正则经 manifest 注入需过安全评审,M3) */
13
+ export const PATTERN_LIBRARY = [
14
+ // ── api-key 前缀族 ──
15
+ { category: 'api-key', name: 'OpenAI', re: /sk-[A-Za-z0-9_-]{20,}/g, keyHints: ['openai', 'OPENAI_API_KEY'] },
16
+ { category: 'api-key', name: 'Anthropic', re: /sk-ant-[A-Za-z0-9_-]{20,}/g, keyHints: ['anthropic'] },
17
+ { category: 'api-key', name: 'GitHub PAT', re: /gh[pousr]_[A-Za-z0-9]{30,}/g, keyHints: ['github', 'GITHUB_TOKEN'] },
18
+ { category: 'api-key', name: 'Slack', re: /xox[baprs]-[A-Za-z0-9-]{10,}/g, keyHints: ['slack'] },
19
+ { category: 'api-key', name: 'AWS Access Key', re: /AKIA[0-9A-Z]{16}/g, keyHints: ['aws'] },
20
+ { category: 'api-key', name: 'Stripe', re: /[sr]k_(live|test)_[A-Za-z0-9]{20,}/g, keyHints: ['stripe'] },
21
+ { category: 'api-key', name: 'npm', re: /npm_[A-Za-z0-9]{30,}/g, keyHints: ['npm_token'] },
22
+ // ── 私钥块 ──
23
+ { category: 'private-key', name: 'PEM RSA/EC', re: /-----BEGIN (RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----/g },
24
+ { category: 'private-key', name: 'PKCS8', re: /-----BEGIN PRIVATE KEY-----/g },
25
+ { category: 'private-key', name: 'PuTTY PPK', re: /PuTTY-User-Key-File-2:/g },
26
+ // ── 连接串 ──
27
+ { category: 'connection-string', name: 'postgres', re: /postgres(ql)?:\/\/[^:\s"']+:[^@\s"']+@[^\s"']+/g, keyHints: ['database_url'] },
28
+ { category: 'connection-string', name: 'mysql', re: /mysql:\/\/[^:\s"']+:[^@\s"']+@[^\s"']+/g },
29
+ { category: 'connection-string', name: 'mongodb', re: /mongodb(\+srv)?:\/\/[^:\s"']+:[^@\s"']+@[^\s"']+/g, keyHints: ['mongo'] },
30
+ { category: 'connection-string', name: 'redis', re: /redis:\/\/:[^@\s"']+@[^\s"']+/g },
31
+ { category: 'connection-string', name: 'jdbc', re: /jdbc:[a-z0-9]+:\/\/[^:\s"']+:[^@\s"']+@/g },
32
+ // ── 云凭据 ──
33
+ { category: 'cloud-credential', name: 'AWS Secret', re: /aws(.{0,20})?(?:secret[_-]?access[_-]?key|secret)(.{0,10})?['":= ]+[A-Za-z0-9/+=]{40}/gi },
34
+ { category: 'cloud-credential', name: 'GCP service account', re: /"type":\s*"service_account"/g },
35
+ { category: 'cloud-credential', name: 'Azure conn', re: /AccountKey=[A-Za-z0-9+/=]{60,}/g },
36
+ { category: 'cloud-credential', name: 'JWT', re: /eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g, keyHints: ['token', 'jwt'] },
37
+ // ── token-Bearer ──
38
+ { category: 'bearer-token', name: 'Authorization header', re: /Authorization['":= ]+Bearer\s+[A-Za-z0-9._-]{15,}/gi, keyHints: ['authorization'] },
39
+ { category: 'bearer-token', name: 'api_key kv', re: /['"]?api[_-]?key['"]?\s*[:=]\s*['"][A-Za-z0-9._-]{16,}['"]/gi, keyHints: ['api_key'] },
40
+ { category: 'bearer-token', name: 'secret kv', re: /['"]?(client[_-]?secret|access[_-]?token)['"]?\s*[:=]\s*['"][A-Za-z0-9._-]{12,}['"]/gi },
41
+ { category: 'bearer-token', name: 'password kv', re: /['"]?password['"]?\s*[:=]\s*['"][^\s'"]{8,}['"]/gi },
42
+ ];
43
+ /** Shannon 熵(bit/char) */
44
+ export function shannonEntropy(s) {
45
+ if (!s.length)
46
+ return 0;
47
+ const freq = new Map();
48
+ for (const ch of s)
49
+ freq.set(ch, (freq.get(ch) ?? 0) + 1);
50
+ let h = 0;
51
+ for (const n of freq.values()) {
52
+ const p = n / s.length;
53
+ h -= p * Math.log2(p);
54
+ }
55
+ return h;
56
+ }
57
+ const ENTROPY_FLOOR = 3.5;
58
+ /** 已知占位/示例值(连接串口令降级用;仅 L3 减分,不提供总开关,红线 §6.3 不受影响) */
59
+ const PLACEHOLDER_VALUES = new Set([
60
+ 'password', 'changeme', 'example', 'demo', 'default', 'placeholder', 'sample',
61
+ 'test', 'pass', 'secret', 'dummy', 'xxx', 'yourpassword', 'your_password', 'your-password',
62
+ ]);
63
+ /**
64
+ * E-5 校准:熵口径修正——kv 形命中的熵只算引号内 value;无引号时只算尾部值串。
65
+ * 根因(run 基准集 FPR 27.2%):键名(password=/api_key= 等)自身多样性抬高了弱值的整体熵。
66
+ */
67
+ function entropyTarget(token) {
68
+ const quoted = /["']([^"']{1,256})["']/g;
69
+ let last = null;
70
+ let m;
71
+ while ((m = quoted.exec(token)) !== null)
72
+ last = m[1];
73
+ if (last !== null && last.length >= 4)
74
+ return last;
75
+ const run = /([A-Za-z0-9+/=._-]{12,})$/.exec(token);
76
+ return run ? run[1] : token;
77
+ }
78
+ function maskToken(t) {
79
+ if (t.length <= 8)
80
+ return '*'.repeat(t.length);
81
+ return t.slice(0, 4) + '****' + t.slice(-4);
82
+ }
83
+ /**
84
+ * 三层检测:前缀命中 → 熵过滤 → 上下文打分。
85
+ * 返回确认命中(score ≥2);低分候选不返回但可通过 returnCandidates 观察(标定用)。
86
+ */
87
+ export function scanSecrets(text, opts = {}) {
88
+ const hits = [];
89
+ const candidates = [];
90
+ for (const p of PATTERN_LIBRARY) {
91
+ p.re.lastIndex = 0;
92
+ let m;
93
+ while ((m = p.re.exec(text)) !== null) {
94
+ const token = m[0];
95
+ // L2 熵过滤:私钥块/连接串结构化模式豁免熵检查;其余需 ≥3.5(E-5 校准:熵算在 value 上)
96
+ const structured = p.category === 'private-key' || p.category === 'connection-string' || (p.category === 'cloud-credential' && p.name === 'GCP service account');
97
+ const ent = shannonEntropy(entropyTarget(token));
98
+ if (!structured && ent < ENTROPY_FLOOR)
99
+ continue;
100
+ // L3 上下文打分:L1 前缀 + L2 熵双过 = 高精度(基础 2);上下文仅加成/降级
101
+ let score = 2;
102
+ // E-5 校准:连接串占位口令降级(postgres://user:password@ 类文档示例 → 仅候选不确认)
103
+ if (p.category === 'connection-string') {
104
+ const pm = /:\/\/(?:[^:/\s"']+)?:([^@\s"']+)@/.exec(token);
105
+ if (pm && PLACEHOLDER_VALUES.has(pm[1].toLowerCase()))
106
+ score -= 2;
107
+ }
108
+ const before = text.slice(Math.max(0, m.index - 40), m.index).toLowerCase();
109
+ if (p.keyHints?.some(h => before.includes(h.toLowerCase())))
110
+ score += 1;
111
+ if (/\b(const|let|var|return|echo|export)\b/.test(before))
112
+ score += 1; // 赋值/输出语境
113
+ if (/^\s*(\/\/|#|\*)/.test(text.slice(0, m.index).split('\n').pop() ?? ''))
114
+ score -= 1; // 注释行降级
115
+ const hit = { category: p.category, name: p.name, start: m.index, end: m.index + token.length, masked: maskToken(token), score };
116
+ if (score >= 2)
117
+ hits.push(hit);
118
+ else if (opts.returnCandidates)
119
+ candidates.push(hit);
120
+ if (m.index === p.re.lastIndex)
121
+ p.re.lastIndex++; // 防零宽死循环
122
+ }
123
+ }
124
+ hits.sort((a, b) => a.start - b.start);
125
+ return { hits, candidates };
126
+ }
127
+ /** 脱敏钩子:确认命中全遮蔽后返回新文本(M8 redact 的 F12 升级实现) */
128
+ export function redactSecrets(text) {
129
+ const { hits } = scanSecrets(text);
130
+ let out = '';
131
+ let cursor = 0;
132
+ for (const h of hits) {
133
+ out += text.slice(cursor, h.start) + h.masked;
134
+ cursor = h.end;
135
+ }
136
+ out += text.slice(cursor);
137
+ return { text: out, redacted: hits.length };
138
+ }
@@ -0,0 +1,55 @@
1
+ import type { SessionLog } from './log.ts';
2
+ export interface ExportMeta {
3
+ sessionId: string;
4
+ exportedBy: string;
5
+ exportedAt: string;
6
+ runtimeVersion: string;
7
+ agentPreset?: string;
8
+ sessionRange: {
9
+ fromSeq: number;
10
+ toSeq: number;
11
+ };
12
+ }
13
+ export interface ForensicsBundle {
14
+ files: {
15
+ name: string;
16
+ content: string;
17
+ sha256: string;
18
+ }[];
19
+ manifest: {
20
+ format: 'car-forensics/1';
21
+ chainHead: string | null;
22
+ chainTail: string | null;
23
+ eventCount: number;
24
+ verifyChainAtExport: 'PASS';
25
+ meta: ExportMeta;
26
+ files: {
27
+ name: string;
28
+ sha256: string;
29
+ }[];
30
+ /** 物理布局登记(M7 存储增强):zstd 落盘时写入;逻辑口径(files[].sha256)恒为明文自证 */
31
+ storage?: {
32
+ encoding: 'zstd';
33
+ storedAs: string;
34
+ physicalSha256: string;
35
+ };
36
+ };
37
+ }
38
+ /** 取证包导出:断链即中止(不产出"带病审计包") */
39
+ export declare function exportForensicsBundle(log: SessionLog, meta: ExportMeta): ForensicsBundle;
40
+ /** 包完整性校验:逐文件 SHA-256 重算 + 链锚点一致 */
41
+ export declare function verifyBundle(bundle: ForensicsBundle): {
42
+ ok: boolean;
43
+ brokenFile: string | null;
44
+ };
45
+ /** 13 项审计字段(安全设计 §7.2.2);core9 = 不可裁剪核心字段 */
46
+ export declare const AUDIT_FIELDS: readonly ['seq+sessionId', 'ts', 'actor', 'actorDetail', 'eventType', 'resource', 'payload(redacted)', 'result/status', 'authorizationId+decidedBy+reason', 'traceId', 'tenantId', 'hash+prevHash', 'runtimeVersion+agentPreset'];
47
+ export type StandardId = 'soc2' | 'gdpr' | 'mlps';
48
+ export declare const FIELD_STANDARD_MAP: Record<string, StandardId[]>;
49
+ /** 分层声明:按标准输出字段集 + 合规声明骨架(合规问卷答复用) */
50
+ export declare function complianceStatement(std: StandardId): {
51
+ standard: StandardId;
52
+ fields: string[];
53
+ coreMissing: string[];
54
+ statement: string;
55
+ };
@@ -0,0 +1,81 @@
1
+ /**
2
+ * F15 基础增强 · 合规导出(M2-S9):取证包完整性清单 + 三标分层声明
3
+ *
4
+ * 口径(安全设计 §7.2.4 Q-09 定稿 + 决议⑧三标并集):
5
+ * - 导出包 = 事件 JSONL + manifest 清单(逐文件 SHA-256 + 链头尾锚点 + 审计元数据);
6
+ * 导出前强制 verifyHashChain(断链中止,SQ-06);运行时无上传功能(端点归用户);
7
+ * - 三标分层声明(T-7 程序化版本):13 项审计字段对 SOC 2 CC7.2 / GDPR Art.30 / 等保 2.0
8
+ * 三级的逐字段映射;对外按行业分层声明(合规问卷答复骨架);9 核心字段不可裁剪。
9
+ */
10
+ import { createHash } from 'node:crypto';
11
+ function sha256(s) { return createHash('sha256').update(s).digest('hex'); }
12
+ /** 取证包导出:断链即中止(不产出"带病审计包") */
13
+ export function exportForensicsBundle(log, meta) {
14
+ const broken = log.verifyChain();
15
+ if (broken !== null)
16
+ throw new Error(`CAR-E-EXPORT: hash chain broken at seq=${broken}——断链中止导出(禁止带病审计包外流)`);
17
+ const jsonl = log.exportJSONL();
18
+ const files = [{ name: `sessions/{sessionId}/events.jsonl`.replace('{sessionId}', meta.sessionId), content: jsonl, sha256: sha256(jsonl) }];
19
+ const events = log.events;
20
+ const manifest = {
21
+ format: 'car-forensics/1',
22
+ chainHead: log.stats().tailHash ? (events[0]?.hash ?? null) : null,
23
+ chainTail: log.stats().tailHash,
24
+ eventCount: events.length,
25
+ verifyChainAtExport: 'PASS',
26
+ meta,
27
+ files: files.map(f => ({ name: f.name, sha256: f.sha256 })),
28
+ };
29
+ const manifestJson = JSON.stringify(manifest, null, 2);
30
+ files.push({ name: 'manifest.json', content: manifestJson, sha256: sha256(manifestJson) });
31
+ return { files, manifest };
32
+ }
33
+ /** 包完整性校验:逐文件 SHA-256 重算 + 链锚点一致 */
34
+ export function verifyBundle(bundle) {
35
+ for (const f of bundle.files) {
36
+ if (sha256(f.content) !== f.sha256)
37
+ return { ok: false, brokenFile: f.name };
38
+ }
39
+ for (const fm of bundle.manifest.files) {
40
+ const actual = bundle.files.find(f => f.name === fm.name);
41
+ if (!actual || actual.sha256 !== fm.sha256)
42
+ return { ok: false, brokenFile: fm.name };
43
+ }
44
+ return { ok: true, brokenFile: null };
45
+ }
46
+ // ── 三标分层声明(T-7)──────────────────────────────────────────────
47
+ /** 13 项审计字段(安全设计 §7.2.2);core9 = 不可裁剪核心字段 */
48
+ export const AUDIT_FIELDS = [
49
+ 'seq+sessionId', 'ts', 'actor', 'actorDetail', 'eventType', 'resource', 'payload(redacted)',
50
+ 'result/status', 'authorizationId+decidedBy+reason', 'traceId', 'tenantId', 'hash+prevHash', 'runtimeVersion+agentPreset',
51
+ ];
52
+ const CORE9 = new Set(['seq+sessionId', 'ts', 'actor', 'eventType', 'result/status', 'authorizationId+decidedBy+reason', 'hash+prevHash', 'tenantId', 'payload(redacted)']);
53
+ /** 字段 → 标准条款映射(T-7 矩阵的程序化数据源;导出时按 standard 裁剪字段集) */
54
+ // 9 核心字段三标全覆盖(核心公共底座,不可裁剪);非核心为标准特有扩展(分层声明语义)
55
+ const ALL = ['soc2', 'gdpr', 'mlps'];
56
+ export const FIELD_STANDARD_MAP = {
57
+ 'seq+sessionId': ALL, // 个体事件级(SOC2 CC7.2)
58
+ 'ts': ALL, // 日期和时间(等保 8.1.4.3 b)
59
+ 'actor': ALL, // 用户(GDPR Art.30 归因)
60
+ 'actorDetail': ['gdpr'], // 处理活动主体细化(扩展)
61
+ 'eventType': ALL, // 事件类型
62
+ 'resource': ['soc2', 'mlps'], // 操作对象(扩展)
63
+ 'payload(redacted)': ALL, // 完整参数与结果(脱敏后)
64
+ 'result/status': ALL, // 事件是否成功
65
+ 'authorizationId+decidedBy+reason': ALL, // 授权凭据链(US6 凭什么)
66
+ 'traceId': ['mlps'], // 关联排查(扩展)
67
+ 'tenantId': ALL, // 归属边界
68
+ 'hash+prevHash': ALL, // 不可变自证
69
+ 'runtimeVersion+agentPreset': ['gdpr'], // 处理环境快照(扩展)
70
+ };
71
+ /** 分层声明:按标准输出字段集 + 合规声明骨架(合规问卷答复用) */
72
+ export function complianceStatement(std) {
73
+ const fields = AUDIT_FIELDS.filter(f => FIELD_STANDARD_MAP[f]?.includes(std));
74
+ const coreMissing = [...CORE9].filter(f => !fields.includes(f));
75
+ const lines = {
76
+ soc2: 'CAR 会话日志逐事件记录(actor/时间戳/工具名/脱敏后参数与结果/授权凭据),SHA-256 哈希链不可变;保留期默认永久(用户自管),建议 ≥1 年(CC7.2)。',
77
+ gdpr: '事件含 actor/actorDetail/eventType/runtimeVersion(GDPR 第 30 条处理活动可归因);数据主体权利由部署方在自有环境行使;遥测默认关、零内容字段。',
78
+ mlps: '对应 GB/T 22239-2019 三级 8.1.4.3 安全审计 a)–d)——审计覆盖四类 actor 全量事件、记录含日期时间/用户/事件类型/是否成功、哈希链防未预期篡改、审计接口零写入;日志留存 ≥6 个月由用户环境承诺(运行时不自动清理)。',
79
+ };
80
+ return { standard: std, fields, coreMissing, statement: lines[std] };
81
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * T-1④ 转正 · 跨宿主 fork/resume(M5 · DEC-4 预埋项 2/2,原 M3 §1.7 显式延后项)
3
+ *
4
+ * 口径(M3系统设计增补 §1.7 / M4 RC-3 / M5 DEC-4 用户裁决①):
5
+ * - 语义:宿主 A 发起的会话在宿主 B 续跑——fork 产出可迁移 JSONL 工件(链逐字节保留),
6
+ * resume 侧装载校验(断链即拒绝)+ 归属校验(fork 标记 target 与装载宿主不匹配即拒绝);
7
+ * - 会话 id 确定性派生复用(deriveSessionId):fork 目标会话 id = SH-(targetHostId|targetHostSessionId),
8
+ * resume 侧幂等重推导,不信任工件命名;
9
+ * - fork 标记事件(kind='fork')落链:fromSessionId/target/upToSeq 全量留痕,零静默;
10
+ * - 哈希链与 sessionId 解耦(事件哈希不含会话 id)→ 链逐字节迁移后 verifyChain 仍完整。
11
+ */
12
+ import { SessionLog } from './log.ts';
13
+ export interface ForkTarget {
14
+ hostId: string;
15
+ hostSessionId: string;
16
+ }
17
+ export interface CrossHostFork {
18
+ /** 宿主 B 侧新会话 id(SH- 确定性派生) */
19
+ sessionId: string;
20
+ /** 可迁移工件(JSONL 全文——经取证通道传递给宿主 B) */
21
+ jsonl: string;
22
+ /** 迁移的事件数(不含 fork 标记) */
23
+ migrated: number;
24
+ /** fork 后内存态日志(同进程等价验证用;跨进程走 jsonl 工件) */
25
+ log: SessionLog;
26
+ }
27
+ /** fork 标记事件 payload(resume 侧归属校验数据源) */
28
+ export interface ForkMarkerPayload {
29
+ fromSessionId: string;
30
+ targetHostId: string;
31
+ targetHostSessionId: string;
32
+ upToSeq: number;
33
+ fromTailHash: string;
34
+ }
35
+ /**
36
+ * 跨宿主 fork:源会话 [0, upToSeq) 事件逐字节迁移 + fork 标记落链。
37
+ * 源链断链即抛错(fail-visible:破损链不允许扩散为「合法」新会话)。
38
+ */
39
+ export declare function forkCrossHost(source: SessionLog, target: ForkTarget, opts?: {
40
+ upToSeq?: number;
41
+ }): CrossHostFork;
42
+ export interface LoadedFork {
43
+ log: SessionLog;
44
+ sessionId: string;
45
+ marker: ForkMarkerPayload;
46
+ }
47
+ /**
48
+ * resume 侧装载:JSONL 工件 → 校验链完整性 → 提取 fork 标记 → 确定性重推导会话 id。
49
+ * 断链 / 无 fork 标记 / 标记畸形 = 抛错(不静默降级为普通会话)。
50
+ */
51
+ export declare function loadForkedLog(jsonl: string, expected?: ForkTarget): LoadedFork;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * T-1④ 转正 · 跨宿主 fork/resume(M5 · DEC-4 预埋项 2/2,原 M3 §1.7 显式延后项)
3
+ *
4
+ * 口径(M3系统设计增补 §1.7 / M4 RC-3 / M5 DEC-4 用户裁决①):
5
+ * - 语义:宿主 A 发起的会话在宿主 B 续跑——fork 产出可迁移 JSONL 工件(链逐字节保留),
6
+ * resume 侧装载校验(断链即拒绝)+ 归属校验(fork 标记 target 与装载宿主不匹配即拒绝);
7
+ * - 会话 id 确定性派生复用(deriveSessionId):fork 目标会话 id = SH-(targetHostId|targetHostSessionId),
8
+ * resume 侧幂等重推导,不信任工件命名;
9
+ * - fork 标记事件(kind='fork')落链:fromSessionId/target/upToSeq 全量留痕,零静默;
10
+ * - 哈希链与 sessionId 解耦(事件哈希不含会话 id)→ 链逐字节迁移后 verifyChain 仍完整。
11
+ */
12
+ import { SessionLog } from './log.js';
13
+ import { deriveSessionId } from '../host/mappings.js';
14
+ /**
15
+ * 跨宿主 fork:源会话 [0, upToSeq) 事件逐字节迁移 + fork 标记落链。
16
+ * 源链断链即抛错(fail-visible:破损链不允许扩散为「合法」新会话)。
17
+ */
18
+ export function forkCrossHost(source, target, opts) {
19
+ const brokenAt = source.verifyChain();
20
+ if (brokenAt !== null)
21
+ throw new Error(`CAR-E-FORK: source chain broken at seq ${brokenAt}(断链拒绝 fork)`);
22
+ const upToSeq = opts?.upToSeq ?? source.events.length;
23
+ if (!Number.isInteger(upToSeq) || upToSeq < 0 || upToSeq > source.events.length) {
24
+ throw new Error(`CAR-E-FORK: upToSeq ${upToSeq} out of range [0, ${source.events.length}]`);
25
+ }
26
+ const sessionId = deriveSessionId(target.hostId, target.hostSessionId);
27
+ const log = SessionLog.fromEvents(sessionId, source.events.slice(0, upToSeq));
28
+ const marker = {
29
+ fromSessionId: source.sessionId,
30
+ targetHostId: target.hostId,
31
+ targetHostSessionId: target.hostSessionId,
32
+ upToSeq,
33
+ fromTailHash: upToSeq > 0 ? source.events[upToSeq - 1].hash : 'GENESIS',
34
+ };
35
+ log.append('runtime', 'fork', 'F0', marker);
36
+ const brokenAfter = log.verifyChain();
37
+ if (brokenAfter !== null)
38
+ throw new Error(`CAR-E-FORK: post-fork chain broken at seq ${brokenAfter}(内部不变量违例)`);
39
+ return { sessionId, jsonl: log.exportJSONL(), migrated: upToSeq, log };
40
+ }
41
+ /**
42
+ * resume 侧装载:JSONL 工件 → 校验链完整性 → 提取 fork 标记 → 确定性重推导会话 id。
43
+ * 断链 / 无 fork 标记 / 标记畸形 = 抛错(不静默降级为普通会话)。
44
+ */
45
+ export function loadForkedLog(jsonl, expected) {
46
+ const events = jsonl.split('\n').filter(l => l.trim()).map(l => JSON.parse(l));
47
+ const markerEvent = [...events].reverse().find(e => e.kind === 'fork');
48
+ if (!markerEvent)
49
+ throw new Error('CAR-E-FORK: no fork marker in artifact(非 fork 工件拒绝装载)');
50
+ const marker = markerEvent.payload;
51
+ if (!marker || typeof marker.targetHostId !== 'string' || typeof marker.targetHostSessionId !== 'string') {
52
+ throw new Error('CAR-E-FORK: fork marker malformed(标记缺 target 字段)');
53
+ }
54
+ if (expected && (marker.targetHostId !== expected.hostId || marker.targetHostSessionId !== expected.hostSessionId)) {
55
+ throw new Error(`CAR-E-FORK: artifact target mismatch(工件归属 ${marker.targetHostId}/${marker.targetHostSessionId},装载方 ${expected.hostId}/${expected.hostSessionId})`);
56
+ }
57
+ const sessionId = deriveSessionId(marker.targetHostId, marker.targetHostSessionId);
58
+ const log = SessionLog.fromEvents(sessionId, events);
59
+ const brokenAt = log.verifyChain();
60
+ if (brokenAt !== null)
61
+ throw new Error(`CAR-E-FORK: artifact chain broken at seq ${brokenAt}(断链拒绝装载)`);
62
+ return { log, sessionId, marker };
63
+ }
@@ -0,0 +1,21 @@
1
+ export declare const ZSTD_MAGIC: Buffer<ArrayBuffer>;
2
+ /** zstd 编解码能力探测(22.15+ 原生内置,无 flag;更早版本缺席——显式化不静默) */
3
+ export declare function zstdAvailable(): boolean;
4
+ /** 全量压缩(单 frame;取证包/归档全量口径,append 热路径不用——见文件头口径) */
5
+ export declare function compressJsonlZstd(text: string): Buffer;
6
+ /** 物理解码:按 magic 嗅探,读取方不感知文件后缀(layout-blind) */
7
+ export declare function decodeLogBuffer(buf: Buffer): {
8
+ text: string;
9
+ encoding: 'plain' | 'zstd';
10
+ };
11
+ export interface SplitJsonlResult {
12
+ lines: string[];
13
+ tornTail: boolean;
14
+ tornTailBytes: number;
15
+ }
16
+ /**
17
+ * 撕裂尾切分:写入方契约 = 每事件一行且行尾 '\n'(store.ts 单 write 原子)。
18
+ * 末段无 '\n' 结尾 = 撕裂尾(崩溃半行),无论其内容是否恰为合法 JSON——显式格式校验语义,
19
+ * 统一按「写入方契约未达成」处理,丢弃并报告字节数。
20
+ */
21
+ export declare function splitJsonlLines(text: string): SplitJsonlResult;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * 会话日志格式层(§3.2.M7.1 第 4 点——M7 存储增强选项落地)
3
+ *
4
+ * 口径(dsh format.ts:210-240 源码级已核实,对齐其「可选 zstd + 撕裂尾截断容错」语义):
5
+ * - 三种读取失败语义互斥可判:
6
+ * 撕裂尾 = 末尾无换行的残留(崩溃半行)→ 显式丢弃并报告(不静默、不算断链、不算格式错误);
7
+ * 格式错误 = 完整行 JSON 解析失败 → CAR-E-FORMAT 拒绝装载(防带病日志静默收缩);
8
+ * 断链 = 完整行哈希不连续 → brokenAt(既有语义不变,loadSessionLog 返回)。
9
+ * - 物理编码 layout-blind:按 zstd magic bytes 嗅探解码,读取方不感知 .jsonl / .jsonl.zstd;
10
+ * zstd 为 22.15+ 原生内置(无 CLI flag,Stability: 1 实验标记),更早版本无此能力——缺席 = 显式报错非静默。
11
+ * - 运行时热路径恒为明文 append + fsync(fail-fast 优先,见 store.ts);zstd 定位 = 归档/导出压缩。
12
+ */
13
+ import * as zlib from 'node:zlib';
14
+ export const ZSTD_MAGIC = Buffer.from([0x28, 0xb5, 0x2f, 0xfd]);
15
+ /** zstd 编解码能力探测(22.15+ 原生内置,无 flag;更早版本缺席——显式化不静默) */
16
+ export function zstdAvailable() {
17
+ return typeof zlib.zstdCompressSync === 'function' && typeof zlib.zstdDecompressSync === 'function';
18
+ }
19
+ function requireZstd() {
20
+ if (!zstdAvailable()) {
21
+ throw new Error('CAR-E-ZSTD: 当前 Node 无 zstd 编解码能力(22.15+/23.8+ 原生内置,更早版本无)——显式拒绝,不静默降级');
22
+ }
23
+ }
24
+ /** 全量压缩(单 frame;取证包/归档全量口径,append 热路径不用——见文件头口径) */
25
+ export function compressJsonlZstd(text) {
26
+ requireZstd();
27
+ return zlib.zstdCompressSync(Buffer.from(text, 'utf-8'));
28
+ }
29
+ /** 物理解码:按 magic 嗅探,读取方不感知文件后缀(layout-blind) */
30
+ export function decodeLogBuffer(buf) {
31
+ if (buf.length >= ZSTD_MAGIC.length && buf.subarray(0, ZSTD_MAGIC.length).equals(ZSTD_MAGIC)) {
32
+ requireZstd();
33
+ return { text: zlib.zstdDecompressSync(buf).toString('utf-8'), encoding: 'zstd' };
34
+ }
35
+ return { text: buf.toString('utf-8'), encoding: 'plain' };
36
+ }
37
+ /**
38
+ * 撕裂尾切分:写入方契约 = 每事件一行且行尾 '\n'(store.ts 单 write 原子)。
39
+ * 末段无 '\n' 结尾 = 撕裂尾(崩溃半行),无论其内容是否恰为合法 JSON——显式格式校验语义,
40
+ * 统一按「写入方契约未达成」处理,丢弃并报告字节数。
41
+ */
42
+ export function splitJsonlLines(text) {
43
+ const body = text.endsWith('\n') ? text.slice(0, -1) : text;
44
+ const parts = body.split('\n');
45
+ const tornTail = !text.endsWith('\n') && parts[parts.length - 1] !== '';
46
+ const tail = tornTail ? parts.pop() : '';
47
+ const lines = parts.filter(l => l.trim() !== '');
48
+ return { lines, tornTail, tornTailBytes: tornTail ? Buffer.byteLength(tail, 'utf-8') : 0 };
49
+ }
@@ -0,0 +1,44 @@
1
+ export interface SessionIndexRow {
2
+ sessionId: string;
3
+ file: string;
4
+ eventCount: number;
5
+ chainHead: string | null;
6
+ chainTail: string | null;
7
+ firstTs: number | null;
8
+ lastTs: number | null;
9
+ tornTail: boolean;
10
+ encoding: 'plain' | 'zstd';
11
+ }
12
+ export interface RebuildResult {
13
+ dbPath: string;
14
+ rows: SessionIndexRow[];
15
+ errors: {
16
+ file: string;
17
+ reason: string;
18
+ }[];
19
+ }
20
+ /** node:sqlite 能力探测(模块存在性;静态 import 在无能力 Node 上会崩模块加载,故动态) */
21
+ export declare function sqliteAvailable(): Promise<boolean>;
22
+ /** 单会话检索(1.2-S1 W3-1):--session <id> 经索引解析 sessionId → 文件;未收录 = null(「可索引 = 可验证」口径——断链会话不入索引) */
23
+ export declare function lookupSession(dbPath: string, sessionId: string): Promise<SessionIndexRow | null>;
24
+ /** 单行增量 upsert(1.2-S2 W3-2「更新半边」):库/表不存在则自举创建(活会话无需先 rebuild 即可入索引) */
25
+ export declare function upsertSessionRow(dbPath: string, row: SessionIndexRow): Promise<void>;
26
+ export declare class IndexUpdater {
27
+ #private;
28
+ constructor(opts: {
29
+ dbPath: string;
30
+ debounceMs?: number;
31
+ onError?: (e: Error) => void;
32
+ });
33
+ get stats(): {
34
+ updates: number;
35
+ failures: number;
36
+ };
37
+ /** append sink 链上调用(行 JSON):seq 权威增量;非预期行防御性忽略 */
38
+ record(sessionId: string, line: string, file: string): void;
39
+ /** 立即落 pending(close/exit 时点调用;幂等——无 pending 即空转) */
40
+ flush(): Promise<void>;
41
+ /** 收口:取消未决 timer + 落 pending(car run 关句柄前调用) */
42
+ close(): Promise<void>;
43
+ }
44
+ export declare function rebuildIndex(root: string, dbPath: string): Promise<RebuildResult>;