@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,137 @@
1
+ /**
2
+ * 1.4-S6 · Turn 级 trace 面(W1 遥测未入链收尾):turn span 包全程、step span 挂每次 model() 调用
3
+ *
4
+ * 口径(1.4 规划 W1 遥测块 / telemetry.ts 头注释):
5
+ * - 层级:turn(根 span,traceId 在此生成)→ step(parentSpanId = turn.spanId)——同一 turn 共享 trace;
6
+ * - **挂点在装配层**(car run 的 model 回调闭包),loop 层零改动——遥测治理属调用方(telemetry.ts
7
+ * 「内容治理属调用方」口径);设计微调登记:规划原文「runTurn opts.telemetry 注入面」改为装配层
8
+ * 闭包挂点,语义等价(turn/step 层级与出站面不变),loop 层保持纯停止语义;
9
+ * - attributes 零内容面:turn_id/step 序号/outcome 枚举——无会话内容/提示词/路径。
10
+ *
11
+ * 1.7-S3(D-28):mcp-serve turn span 面——sessionTurn 挂 turn 级 span(无 step span:mcp-serve
12
+ * 无 model() 循环,step span 面外推登记为不适用)。facade 内部自增派生 turnId(T{n}),装配层
13
+ * 调用前不可知——TurnSpanHandle 加法 `setTurnId` 回填面(car run 直传 turnId 路径不受影响);
14
+ * startTurn('') 空值不设 car.turn_id 属性(回填前出站形态无占位内容)。
15
+ *
16
+ * 1.8(D-29/D-30/D-31 · W3C traceparent 跨进程传播):自研 traceId(16B hex)与 spanId(8B hex)
17
+ * 格式天然满足 W3C Trace Context(trace-id 32hex / span-id 16hex),1.8 补齐编解码与三边界载体:
18
+ * - formatTraceparent/parseTraceparent(D-29):纯字符串编解码,零依赖;parse 严格校验
19
+ * (version 00 / 32hex / 16hex / 全零拒绝),畸形返回 null——**协议面 fail-open**(挂链放弃、
20
+ * 新 trace,遥测面禁 fail-hard——BD-05 同源);flags 出站恒 '01'(本端记录中——采样为端点
21
+ * 本地决策,不跨端传播语义);
22
+ * - mcpToolSpan(D-29):car run 外呼 MCP 工具的 client span(parent = turn span——step span 已于
23
+ * model() 返回时收口,工具执行在 step 外);attributes car.mcp_server/car.tool 为**登记标识符面**
24
+ * (零内容口径扩展:注册表枚举标识,非会话内容);traceparent 供 gateway.callTool 注入
25
+ * params._meta(otel 关 = '' 不注入,线上 JSON-RPC 字节面不变);
26
+ * - startTurn 加法 opts.trace(D-30):mcp-serve 服务端挂链远端宿主——traceId 复用远端 +
27
+ * parentSpanId = 远端 spanId,car.turn span 加入宿主 trace(fail-open:畸形 traceparent 由
28
+ * 调用方 parse 为 null 后不传,新 trace);
29
+ * - ptcRelay(D-31):PTC worker 桥挂链——workerTrace 载荷(traceId/parentSpanId)经 workerData
30
+ * 进隔离体,worker 计时经 done 消息回传后 complete() 主线程单点收口 car.ptc span(facade
31
+ * time.startMs/end(atMs) 时刻覆盖;单 exporter 纪律——worker 零独立出站通道)。
32
+ *
33
+ * 1.9(D-32 · 远程 MCP Streamable HTTP 头通道):mcpToolSpan 产出的 traceparent 除 1.8 `params._meta`
34
+ * 载体外,经 gateway.callTool → transport.send 第二参透传,httpClient 注入 `traceparent` 请求头
35
+ * (W3C Trace Context HTTP 载体惯例——远程 server 主消费面)。本模块仅复用 parseTraceparent 作
36
+ * 传输层 fail-open 校验(畸形不注入不抛)——编解码与传播语义 1.8 口径零触碰。
37
+ */
38
+ import { randomBytes } from 'node:crypto';
39
+ /** 1.8-D29:W3C traceparent 出站形态(flags '01' = 本端记录中;采样为端点本地决策不跨端传播) */
40
+ export function formatTraceparent(traceId, spanId) {
41
+ return `00-${traceId}-${spanId}-01`;
42
+ }
43
+ const W3C_TP_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
44
+ /**
45
+ * 1.8-D29:W3C traceparent 解析——严格校验(version 00 / trace-id 32hex 非 / span-id 16hex 非 / 全零拒绝)。
46
+ * 畸形返回 null = 挂链放弃(调用方走新 trace——协议面 fail-open,禁 fail-hard)。
47
+ */
48
+ export function parseTraceparent(tp) {
49
+ const m = W3C_TP_RE.exec(tp.trim());
50
+ if (!m)
51
+ return null;
52
+ const traceId = m[1];
53
+ const parentSpanId = m[2];
54
+ if (/^0+$/.test(traceId) || /^0+$/.test(parentSpanId))
55
+ return null;
56
+ return { traceId, parentSpanId };
57
+ }
58
+ export class TurnTracer {
59
+ #facade;
60
+ constructor(facade) {
61
+ // 默认关(无端点)= noop 句柄(零出站零积累——telemetry 三原则)
62
+ this.#facade = facade?.enabled ? facade : null;
63
+ }
64
+ /** 1.8-D30:opts.trace = 远端挂链(mcp-serve session_turn `_meta.traceparent` 提取后传入;缺省新 trace 不变) */
65
+ startTurn(turnId, opts) {
66
+ if (!this.#facade) {
67
+ return {
68
+ traceId: '', spanId: '', setTurnId() { }, recordException() { }, end() { },
69
+ stepSpan: () => ({ end() { }, recordException() { } }),
70
+ mcpToolSpan: () => ({ traceparent: '', end() { }, recordException() { } }),
71
+ ptcRelay: () => ({ workerTrace: null, complete() { } }),
72
+ };
73
+ }
74
+ const tracer = this.#facade.getTracer();
75
+ const traceId = opts?.trace?.traceId ?? randomBytes(16).toString('hex');
76
+ const turn = tracer.startSpan('car.turn', {
77
+ attributes: turnId ? { 'car.turn_id': turnId } : {},
78
+ trace: { traceId, ...(opts?.trace?.parentSpanId ? { parentSpanId: opts.trace.parentSpanId } : {}) },
79
+ });
80
+ return {
81
+ traceId,
82
+ spanId: turn.spanId,
83
+ setTurnId(id) { turn.setAttribute('car.turn_id', id); },
84
+ recordException(e) { turn.recordException(e); },
85
+ end(outcome) {
86
+ turn.setAttribute('car.outcome', outcome);
87
+ turn.end();
88
+ },
89
+ stepSpan(step) {
90
+ const s = tracer.startSpan('car.step', {
91
+ attributes: { 'car.turn_id': turnId, 'car.step': step },
92
+ trace: { traceId, parentSpanId: turn.spanId },
93
+ });
94
+ return {
95
+ end(outcome) {
96
+ s.setAttribute('car.outcome', outcome);
97
+ s.end();
98
+ },
99
+ recordException(e) { s.recordException(e); },
100
+ };
101
+ },
102
+ // 1.8-D29:client span 挂 turn(工具执行在 step span 收口后——turn 为最近存活父)
103
+ mcpToolSpan(serverId, tool) {
104
+ const s = tracer.startSpan('car.mcp.tool', {
105
+ attributes: { 'car.mcp_server': serverId, 'car.tool': tool },
106
+ trace: { traceId, parentSpanId: turn.spanId },
107
+ });
108
+ return {
109
+ traceparent: formatTraceparent(traceId, s.spanId),
110
+ end(outcome) {
111
+ s.setAttribute('car.outcome', outcome);
112
+ s.end();
113
+ },
114
+ recordException(e) { s.recordException(e); },
115
+ };
116
+ },
117
+ // 1.8-D31:worker 侧计时 relay——span 生成收口在主线程(单 exporter 纪律)
118
+ ptcRelay() {
119
+ return {
120
+ workerTrace: { traceId, parentSpanId: turn.spanId },
121
+ complete(startMs, endMs, outcome, error) {
122
+ // 负时长防线(同时钟域不应出现——防御下游转换器语义错乱)
123
+ const s = tracer.startSpan('car.ptc', {
124
+ attributes: { 'car.turn_id': turnId },
125
+ trace: { traceId, parentSpanId: turn.spanId },
126
+ time: { startMs },
127
+ });
128
+ if (error !== undefined)
129
+ s.recordException(error instanceof Error ? error : new Error(String(error)));
130
+ s.setAttribute('car.outcome', outcome);
131
+ s.end(Math.max(endMs, startMs));
132
+ },
133
+ };
134
+ },
135
+ };
136
+ }
137
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * M8 · 运行时底座共享 DTO(《系统设计》§3.2.M8.3 / §3.3.1 O-10/O-11/O-13/O-14)
3
+ *
4
+ * 口径:
5
+ * - LlmChunk.finishReason 为不可变透传(readonly 语义,ADR-001;vLLM #53269 教训 SR-17)——
6
+ * 唯一信号源,禁止中间层改写/吞没;llm.ts 的流包装层对此做防御性断言;
7
+ * - LlmRequest.tools 含 declaredSideEffect(权限门依据;readonly 是 ADR-001 补错重试白名单前置);
8
+ * - ToolDefinition 为 M8 共享 DTO(O-10):M3 授权门/M5 MCP 网关/M8 模型请求三方复用;
9
+ * 与 loop/stop.ts 的 ToolDef 分工——ToolDefinition 是声明面(进模型 schema),ToolDef 是执行面;
10
+ * - CredentialRef 不落明文(O-13):ref 只含来源定位,值经 credentials.reveal 按需取用;
11
+ * - 规格外扩展字段(adapterId / error)以加法演进登记——与 hostRaw 同纪律,不改动规格最小集语义。
12
+ */
13
+ /** 模型请求(Provider 无关) */
14
+ export interface LlmRequest {
15
+ model: string;
16
+ messages: Array<{
17
+ role: 'system' | 'user' | 'assistant' | 'toolResult';
18
+ content: unknown;
19
+ }>;
20
+ /** 工具 schema(缓存失效因素:工具集合/模型切换/compaction,D3 §14.6) */
21
+ tools: ToolDefinition[];
22
+ maxTokens?: number;
23
+ metadata?: {
24
+ sessionId: string;
25
+ turnId: string;
26
+ traceId: string;
27
+ };
28
+ /** 多适配器在册时显式路由;缺省用默认适配器 */
29
+ adapterId?: string;
30
+ /** 1.4-S4(D-19):turn 取消信号(runTurn 的 {aborted} 形态透传)——适配器在 attempt 前 /
31
+ * chunk 间隙检查;外部取消 ≠ 失败(不 retry、不产 error chunk,静默收口由 chatStep 抛 TurnAborted) */
32
+ signal?: {
33
+ aborted: boolean;
34
+ };
35
+ }
36
+ /** 工具调用流式增量(跨 chunk 聚合出完整 ToolCall) */
37
+ export interface ToolCallDelta {
38
+ index?: number;
39
+ id?: string;
40
+ name?: string;
41
+ /** JSON 增量片段,聚合后整体 JSON.parse(半截 JSON 不解析——ADR-001 同纪律) */
42
+ argumentsDelta?: string;
43
+ }
44
+ /** 流式块(finishReason 一经产生不可被任何中间层改写) */
45
+ export interface LlmChunk {
46
+ delta?: string;
47
+ toolCallDelta?: ToolCallDelta;
48
+ finishReason?: 'stop' | 'length' | 'toolUse' | 'error' | 'aborted';
49
+ /** finishReason='error' 时的结构化原因(消费方落 turnEnd meta,不进模型消息流) */
50
+ error?: {
51
+ code: string;
52
+ message: string;
53
+ };
54
+ }
55
+ /** Provider 适配器(ACL,C-02):统一签名,消解 Provider 差异 */
56
+ export interface LlmAdapter {
57
+ /** 如 openai-compat / anthropic */
58
+ id: string;
59
+ chat(req: LlmRequest): AsyncIterable<LlmChunk>;
60
+ }
61
+ /** 可逆注册句柄(dispose = Effect 回卷) */
62
+ export interface Disposable {
63
+ dispose(): void;
64
+ }
65
+ /** 共享工具声明 DTO(O-10;注册时构造,进模型请求 schema) */
66
+ export interface ToolDefinition {
67
+ name: string;
68
+ description?: string;
69
+ /** JSON Schema 形态参数声明 */
70
+ parameters?: unknown;
71
+ /** 副作用声明(必填——M5 网关 T-22 强制点同一口径:缺声明即 write) */
72
+ declaredSideEffect: 'readonly' | 'write';
73
+ }
74
+ /** 凭据来源(§3.2.M8.3 字段表:env 需用户显式开启 fallback) */
75
+ export type CredentialSource = 'keychain' | 'env';
76
+ /** 凭据引用(O-13):只含来源定位与掩码提示,值永不入此结构 */
77
+ export interface CredentialRef {
78
+ provider: string;
79
+ source: CredentialSource;
80
+ /** 来源标识:keychain 服务名 / env 变量名 */
81
+ origin: string;
82
+ /** keychain 通道不可用等降级留痕(降级必须显式,禁静默) */
83
+ degraded?: string;
84
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * M8 · 运行时底座共享 DTO(《系统设计》§3.2.M8.3 / §3.3.1 O-10/O-11/O-13/O-14)
3
+ *
4
+ * 口径:
5
+ * - LlmChunk.finishReason 为不可变透传(readonly 语义,ADR-001;vLLM #53269 教训 SR-17)——
6
+ * 唯一信号源,禁止中间层改写/吞没;llm.ts 的流包装层对此做防御性断言;
7
+ * - LlmRequest.tools 含 declaredSideEffect(权限门依据;readonly 是 ADR-001 补错重试白名单前置);
8
+ * - ToolDefinition 为 M8 共享 DTO(O-10):M3 授权门/M5 MCP 网关/M8 模型请求三方复用;
9
+ * 与 loop/stop.ts 的 ToolDef 分工——ToolDefinition 是声明面(进模型 schema),ToolDef 是执行面;
10
+ * - CredentialRef 不落明文(O-13):ref 只含来源定位,值经 credentials.reveal 按需取用;
11
+ * - 规格外扩展字段(adapterId / error)以加法演进登记——与 hostRaw 同纪律,不改动规格最小集语义。
12
+ */
13
+ export {};
@@ -0,0 +1,68 @@
1
+ export type SandboxMode = 'read-only' | 'workspace-write' | 'danger-full-access';
2
+ export type Capability = 'fs-write' | 'net' | 'exec' | 'mcp' | 'env-read';
3
+ export interface ProbeResult {
4
+ platform: NodeJS.Platform;
5
+ landlock: boolean;
6
+ seccomp: boolean;
7
+ degraded: boolean;
8
+ /** 降级原因(审计留痕用);未降级为 null */
9
+ reason: string | null;
10
+ }
11
+ export interface SandboxAuditSink {
12
+ (event: {
13
+ kind: 'sandbox-degraded' | 'sandbox-denied' | 'sandbox-exec';
14
+ detail: Record<string, unknown>;
15
+ ts: number;
16
+ }): void;
17
+ }
18
+ /** 能力探测:Linux 用 landlock-run --probe;其余平台直接降级(可插拔真实探测) */
19
+ export declare function probeCapabilities(opts?: {
20
+ landlockRunPath?: string;
21
+ platform?: NodeJS.Platform;
22
+ }): Promise<ProbeResult>;
23
+ export interface ExecRequest {
24
+ argv: string[];
25
+ /** 声明所需能力(能力标签 = 跨域请求唯一凭证) */
26
+ capabilities: Capability[];
27
+ mode: SandboxMode;
28
+ /** 运行权限模式(F4):readonly/confirm/full——降级态下 full 亦受限为 confirm */
29
+ permissionMode: 'readonly' | 'confirm' | 'full';
30
+ authorize?: (req: ExecRequest) => Promise<boolean>;
31
+ timeoutMs?: number;
32
+ }
33
+ export interface ExecResult {
34
+ ok: boolean;
35
+ stdout: string;
36
+ stderr: string;
37
+ code: number | null;
38
+ degraded: boolean;
39
+ denied?: string;
40
+ }
41
+ /**
42
+ * M2-S7 · Seatbelt profile 生成器(darwin 后端)
43
+ *
44
+ * 口径:参照 Codex sandbox-exec 动态生成 profile(SR-19,源码级已核实)+ dsh darwin=['seatbelt'] 路由。
45
+ * 生成规则(fail-closed:默认全部拒绝,仅显式 allow):
46
+ * - 全模式:允许进程基本运行(process-exec/process-fork)+ /usr,/bin,/sbin,/System 只读;
47
+ * - read-only / workspace-write:拒绝 network*(deny-by-default 网络);
48
+ * - workspace-write:仅 {workspace} 与 CAR_HOME 临时目录允许 file-write*,.git 强制只读(受保护路径);
49
+ * - danger-full-access:不生成 profile(直跑,与 Codex 三档一致)。
50
+ */
51
+ export declare function buildSeatbeltProfile(mode: SandboxMode, workspace: string): string;
52
+ export declare class SandboxExecutor {
53
+ #private;
54
+ constructor(opts: {
55
+ probe: ProbeResult;
56
+ audit: SandboxAuditSink;
57
+ landlockRunPath?: string;
58
+ workspace: string;
59
+ });
60
+ get degraded(): boolean;
61
+ /** Q-04 定稿:降级态运行约束判定(在授权门之前生效) */
62
+ effectivePermission(permissionMode: ExecRequest['permissionMode'], capabilities: Capability[]): {
63
+ mode: 'readonly' | 'confirm' | 'full';
64
+ denied: string | null;
65
+ };
66
+ /** 沙箱内执行:argv 数组、写类能力需授权、降级态 Q-04 约束、审计全量留痕 */
67
+ exec(req: ExecRequest): Promise<ExecResult>;
68
+ }
@@ -0,0 +1,153 @@
1
+ /**
2
+ * F3 · 沙箱执行器:能力探测 + 三档模式 + 降级路径(BD-01)+ 逃逸回归接口
3
+ *
4
+ * 口径(《系统设计》F3 / 安全设计 §5.3 / Q-04 定稿 / 冻结决策② + M2系统设计增补 T-3):
5
+ * - 平台后端:linux = landlock-run(dsh 同款独立 C11 可执行:--probe 探测、--ro/--rw -- argv、
6
+ * fail-closed)+ seccomp 过滤集;darwin = Seatbelt(sandbox-exec 动态生成 profile,M2-S7 实装);
7
+ * win32 = 受限 token + Job Object(koffi FFI,S7 出 spike 方案、S8 实装——dsh win32 为 koffi 纯 JS,
8
+ * sandbox-local/src/index.ts:160-161 源码级已核实)。
9
+ * 容器内 Landlock 不可用 → 探测失败走同一降级路径(SR-01/19 口径)
10
+ * - 降级态约束(Q-04):写类操作强制确认模式(full 亦受限)、env-read 一律拒绝、
11
+ * 降级事件 + 每次降级态授权带 degraded=true 留痕、静默降级 = AL-03 P1(计数必须为 0)
12
+ * - 执行:argv 数组形式(A03 红线:禁 shell -c 拼接)、单命令 300s 上限、0 自动重试
13
+ */
14
+ import { spawn } from 'node:child_process';
15
+ /** 能力探测:Linux 用 landlock-run --probe;其余平台直接降级(可插拔真实探测) */
16
+ export async function probeCapabilities(opts = {}) {
17
+ const platform = opts.platform ?? process.platform;
18
+ if (platform !== 'linux' || !opts.landlockRunPath) {
19
+ return {
20
+ platform,
21
+ landlock: false, seccomp: false, degraded: true,
22
+ reason: platform === 'linux'
23
+ ? 'landlock-run binary not available(BD-01:需编译 native/landlock-run 或提供路径)'
24
+ : `platform ${platform} 沙箱原语未实装(S3:Linux 先行,经中间确认②)`,
25
+ };
26
+ }
27
+ const probe = await new Promise((resolve) => {
28
+ try {
29
+ const child = spawn(opts.landlockRunPath, ['--probe'], { stdio: 'ignore' });
30
+ child.on('exit', (code) => resolve({ ok: code === 0 }));
31
+ child.on('error', () => resolve({ ok: false }));
32
+ }
33
+ catch {
34
+ resolve({ ok: false });
35
+ }
36
+ });
37
+ return probe.ok
38
+ ? { platform, landlock: true, seccomp: true, degraded: false, reason: null }
39
+ : { platform, landlock: false, seccomp: false, degraded: true, reason: 'landlock-run probe failed(内核 <5.13 或 LSM 未启用)' };
40
+ }
41
+ /**
42
+ * M2-S7 · Seatbelt profile 生成器(darwin 后端)
43
+ *
44
+ * 口径:参照 Codex sandbox-exec 动态生成 profile(SR-19,源码级已核实)+ dsh darwin=['seatbelt'] 路由。
45
+ * 生成规则(fail-closed:默认全部拒绝,仅显式 allow):
46
+ * - 全模式:允许进程基本运行(process-exec/process-fork)+ /usr,/bin,/sbin,/System 只读;
47
+ * - read-only / workspace-write:拒绝 network*(deny-by-default 网络);
48
+ * - workspace-write:仅 {workspace} 与 CAR_HOME 临时目录允许 file-write*,.git 强制只读(受保护路径);
49
+ * - danger-full-access:不生成 profile(直跑,与 Codex 三档一致)。
50
+ */
51
+ export function buildSeatbeltProfile(mode, workspace) {
52
+ if (mode === 'danger-full-access')
53
+ throw new Error('CAR-E-SANDBOX: danger-full-access does not use a seatbelt profile(直跑语义)');
54
+ const ws = workspace.replace(/"/g, '\\"');
55
+ const lines = [
56
+ '(version 1)',
57
+ '(deny default)', // fail-closed
58
+ ';; 进程基本运行',
59
+ '(allow process-fork)',
60
+ '(allow process-exec)',
61
+ ';; 系统只读面',
62
+ '(allow file-read* (subpath "/usr"))',
63
+ '(allow file-read* (subpath "/bin"))',
64
+ '(allow file-read* (subpath "/sbin"))',
65
+ '(allow file-read* (subpath "/System"))',
66
+ '(allow file-read* (subpath "/private/var/db/dyld"))',
67
+ ';; 系统运行必需',
68
+ '(allow sysctl-read)',
69
+ '(allow mach-lookup)',
70
+ '(allow iokit-open)',
71
+ ];
72
+ if (mode === 'workspace-write') {
73
+ lines.push(`;; 工作区可写(.git 强制只读——受保护路径)`, `(allow file-write* (subpath "${ws}"))`, `(deny file-write* (subpath "${ws}/.git"))`, `;; 工作区读取`, `(allow file-read* (subpath "${ws}"))`, `;; 临时目录(编译器/子进程必需)`, '(allow file-write* (subpath "/private/tmp"))', '(allow file-read* (subpath "/private/tmp"))', '(allow file-write* (subpath "/private/var/tmp"))');
74
+ }
75
+ else {
76
+ lines.push(`;; read-only:工作区仅读`, `(allow file-read* (subpath "${ws}"))`, '(allow file-read* (subpath "/private/tmp"))');
77
+ }
78
+ lines.push(';; deny-by-default 网络(写类能力需显式豁免才可放行,本模板一律拒绝)', '(deny network*)');
79
+ return lines.join('\n');
80
+ }
81
+ export class SandboxExecutor {
82
+ #probe;
83
+ #audit;
84
+ #landlockRunPath;
85
+ #workspace;
86
+ constructor(opts) {
87
+ this.#probe = opts.probe;
88
+ this.#audit = opts.audit;
89
+ this.#landlockRunPath = opts.landlockRunPath;
90
+ this.#workspace = opts.workspace;
91
+ }
92
+ get degraded() { return this.#probe.degraded; }
93
+ /** Q-04 定稿:降级态运行约束判定(在授权门之前生效) */
94
+ effectivePermission(permissionMode, capabilities) {
95
+ if (!this.degraded)
96
+ return { mode: permissionMode, denied: null };
97
+ const writeish = capabilities.some(c => c === 'fs-write' || c === 'net' || c === 'exec' || c === 'mcp');
98
+ if (capabilities.includes('env-read'))
99
+ return { mode: 'confirm', denied: 'env-read denied in degraded sandbox(Q-04:一律拒绝)' };
100
+ if (writeish)
101
+ return { mode: 'confirm', denied: null }; // full 亦受限为 confirm
102
+ return { mode: permissionMode === 'full' ? 'confirm' : permissionMode, denied: null };
103
+ }
104
+ /** 沙箱内执行:argv 数组、写类能力需授权、降级态 Q-04 约束、审计全量留痕 */
105
+ async exec(req) {
106
+ const { mode: effMode, denied: degradedDenial } = this.effectivePermission(req.permissionMode, req.capabilities);
107
+ if (degradedDenial) {
108
+ this.#audit({ kind: 'sandbox-denied', detail: { argv: req.argv, reason: degradedDenial, degraded: true }, ts: Date.now() });
109
+ return { ok: false, stdout: '', stderr: degradedDenial, code: -1, degraded: true, denied: degradedDenial };
110
+ }
111
+ // 权限门:写类能力在 readonly 一律拒;confirm 需审批(降级态强制 confirm)
112
+ const writeish = req.capabilities.some(c => c === 'fs-write' || c === 'net' || c === 'exec' || c === 'mcp');
113
+ if (writeish && effMode !== 'full') {
114
+ const granted = effMode === 'confirm' ? await (req.authorize?.(req) ?? Promise.resolve(false)) : false;
115
+ if (!granted) {
116
+ this.#audit({ kind: 'sandbox-denied', detail: { argv: req.argv, mode: effMode, degraded: this.degraded }, ts: Date.now() });
117
+ return { ok: false, stdout: '', stderr: `authorization-denied (mode=${effMode}${this.degraded ? ', degraded' : ''})`, code: -1, degraded: this.degraded, denied: 'authorization-denied' };
118
+ }
119
+ }
120
+ if (this.degraded) {
121
+ // 降级态执行:无内核背书——仅透传执行并留痕(授权门已按 Q-04 收紧)
122
+ this.#audit({ kind: 'sandbox-exec', detail: { argv: req.argv, degraded: true }, ts: Date.now() });
123
+ return this.#spawn(req.argv, req.timeoutMs, true);
124
+ }
125
+ // 正常态:Linux landlock-run 包装(self-restrict-then-exec,fail-closed)
126
+ if (process.platform === 'linux' && this.#landlockRunPath) {
127
+ const ro = req.mode === 'read-only';
128
+ const wrapped = [this.#landlockRunPath, ro ? '--ro' : '--rw', '--', ...req.argv];
129
+ return this.#spawn(wrapped, req.timeoutMs, false);
130
+ }
131
+ // 正常态:macOS Seatbelt(sandbox-exec 动态 profile,M2-S7)
132
+ if (process.platform === 'darwin' && req.mode !== 'danger-full-access') {
133
+ const profile = buildSeatbeltProfile(req.mode, this.#workspace);
134
+ return this.#spawn(['sandbox-exec', '-p', profile, ...req.argv], req.timeoutMs, false);
135
+ }
136
+ return this.#spawn(req.argv, req.timeoutMs, false);
137
+ }
138
+ #spawn(argv, timeoutMs = 300_000, degraded) {
139
+ return new Promise((resolve) => {
140
+ const child = spawn(argv[0], argv.slice(1), { cwd: this.#workspace, shell: false, env: { ...process.env } });
141
+ let stdout = '', stderr = '';
142
+ const timer = setTimeout(() => child.kill('SIGKILL'), timeoutMs);
143
+ child.stdout?.on('data', d => { stdout += d; });
144
+ child.stderr?.on('data', d => { stderr += d; });
145
+ child.on('error', (e) => { clearTimeout(timer); resolve({ ok: false, stdout, stderr: String(e), code: -1, degraded }); });
146
+ child.on('exit', (code) => {
147
+ clearTimeout(timer);
148
+ this.#audit({ kind: 'sandbox-exec', detail: { argv, code, degraded }, ts: Date.now() });
149
+ resolve({ ok: code === 0, stdout, stderr, code, degraded });
150
+ });
151
+ });
152
+ }
153
+ }