@sema-agent/server 7.51.0 → 7.52.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 (198) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/USAGE.md +83 -20
  4. package/dist/adoption/plan.d.ts +4 -4
  5. package/dist/adoption/plan.js +11 -0
  6. package/dist/approval-card.d.ts +26 -2
  7. package/dist/approval-card.js +9 -0
  8. package/dist/approval-hmac.d.ts +1 -1
  9. package/dist/approval-reconciler.d.ts +2 -2
  10. package/dist/approval-reconciler.js +1 -1
  11. package/dist/bench/l8/artifact.d.ts +1 -1
  12. package/dist/bench/s1/arms.d.ts +1 -1
  13. package/dist/bench/s1/live-deps.d.ts +1 -1
  14. package/dist/bench/s1/oracle.d.ts +1 -1
  15. package/dist/bench/s1/row.d.ts +1 -1
  16. package/dist/boot/budget-tracing.d.ts +1 -1
  17. package/dist/boot/budget-tracing.js +2 -5
  18. package/dist/boot/coordinators.js +3 -1
  19. package/dist/boot/deferred-sandbox-path-env.d.ts +1 -1
  20. package/dist/boot/engine-lease.d.ts +1 -1
  21. package/dist/boot/execution-env.d.ts +1 -1
  22. package/dist/boot/governance-seams.d.ts +2 -2
  23. package/dist/boot/leader.d.ts +1 -1
  24. package/dist/boot/memory-boundary.d.ts +2 -2
  25. package/dist/boot/memory-consolidation.d.ts +6 -6
  26. package/dist/boot/org-memory.d.ts +1 -1
  27. package/dist/boot/parked-revive-gate.d.ts +3 -3
  28. package/dist/boot/reapers.d.ts +2 -2
  29. package/dist/boot/reapers.js +17 -4
  30. package/dist/boot/resolve-spec.d.ts +12 -0
  31. package/dist/boot/resolve-spec.js +29 -7
  32. package/dist/boot/retention-lane.d.ts +2 -2
  33. package/dist/boot/runner-deps.d.ts +17 -7
  34. package/dist/boot/runner-deps.js +4 -2
  35. package/dist/boot/runtime-caps.d.ts +23 -0
  36. package/dist/boot/runtime-caps.js +24 -2
  37. package/dist/boot/session-faces.d.ts +1 -1
  38. package/dist/boot/session-faces.js +14 -3
  39. package/dist/boot/stores.js +31 -1
  40. package/dist/boot/task-list-lane.d.ts +2 -2
  41. package/dist/brain.js +51 -31
  42. package/dist/budget.d.ts +2 -2
  43. package/dist/capabilities/center-plugins.d.ts +1 -1
  44. package/dist/capabilities/collab-workflows.d.ts +1 -1
  45. package/dist/capabilities/hands-lane.d.ts +3 -3
  46. package/dist/capabilities/scenarios.d.ts +3 -3
  47. package/dist/capabilities/skills.d.ts +1 -1
  48. package/dist/config-center/hot-keys-registry.js +5 -0
  49. package/dist/config-center/http-client.d.ts +2 -2
  50. package/dist/config-center/read-face.d.ts +1 -1
  51. package/dist/config-center/restart-signal.d.ts +1 -1
  52. package/dist/config-types.d.ts +78 -18
  53. package/dist/config.js +45 -10
  54. package/dist/deployment-governance.d.ts +1 -1
  55. package/dist/device-enrollment.d.ts +2 -2
  56. package/dist/device-store.d.ts +3 -3
  57. package/dist/device-ws-protocol.d.ts +2 -2
  58. package/dist/env-facts.d.ts +1 -1
  59. package/dist/execution-lane-caps.d.ts +1 -1
  60. package/dist/fleet/fleet-bus.d.ts +1 -1
  61. package/dist/fleet/fleet-reconciler.d.ts +1 -1
  62. package/dist/fleet/fleet-terminal-window.d.ts +1 -1
  63. package/dist/http/active-run-conflict.d.ts +2 -2
  64. package/dist/http/principal-gate.d.ts +1 -1
  65. package/dist/http/route-ctx.d.ts +24 -4
  66. package/dist/http/routes/admin-memory-optout.d.ts +48 -0
  67. package/dist/http/routes/admin-memory-optout.js +75 -0
  68. package/dist/http/routes/agents-roster.d.ts +1 -1
  69. package/dist/http/routes/approvals-assistant.js +21 -21
  70. package/dist/http/routes/capabilities.js +20 -9
  71. package/dist/http/routes/diagnostics.d.ts +1 -1
  72. package/dist/http/routes/leader.d.ts +1 -1
  73. package/dist/http/routes/memory-bundle.d.ts +1 -1
  74. package/dist/http/routes/memory-bundle.js +3 -1
  75. package/dist/http/routes/memory-compliance.d.ts +4 -4
  76. package/dist/http/routes/memory-origin.d.ts +4 -4
  77. package/dist/http/routes/memory-origin.js +3 -1
  78. package/dist/http/routes/memory-policy.js +6 -2
  79. package/dist/http/routes/retention-ops.d.ts +1 -1
  80. package/dist/http/routes/rules.d.ts +5 -2
  81. package/dist/http/routes/rules.js +84 -5
  82. package/dist/http/routes/run-memory-capture.d.ts +34 -0
  83. package/dist/http/routes/run-memory-capture.js +97 -0
  84. package/dist/http/routes/runs.d.ts +1 -0
  85. package/dist/http/routes/runs.js +212 -6
  86. package/dist/http/routes/session-sync.d.ts +4 -6
  87. package/dist/http/routes/session-sync.js +60 -47
  88. package/dist/http/routes/sessions.js +61 -188
  89. package/dist/http/routes/shared-memory.d.ts +1 -1
  90. package/dist/http/routes/tasks.js +1 -1
  91. package/dist/http/routes/workflows.js +19 -0
  92. package/dist/http/server.d.ts +35 -13
  93. package/dist/http/server.js +105 -12
  94. package/dist/http/verify-rounds.d.ts +1 -1
  95. package/dist/http/wire-types.d.ts +41 -8
  96. package/dist/leader/endpoint.d.ts +1 -1
  97. package/dist/leader/leader.d.ts +1 -1
  98. package/dist/leader/repair-oracle.d.ts +2 -2
  99. package/dist/leader/repair-oracle.js +1 -1
  100. package/dist/main.js +13 -6
  101. package/dist/memory-bundle-engine.d.ts +1 -1
  102. package/dist/memory-operator-faces.d.ts +5 -5
  103. package/dist/memory-origin-fence.d.ts +50 -0
  104. package/dist/memory-origin-fence.js +15 -0
  105. package/dist/memory-posture.d.ts +13 -1
  106. package/dist/memory-posture.js +1 -0
  107. package/dist/memory-scope.d.ts +6 -1
  108. package/dist/memory-scope.js +3 -2
  109. package/dist/model-select.d.ts +2 -2
  110. package/dist/observability/fail-open.d.ts +17 -5
  111. package/dist/observability/fail-open.js +17 -5
  112. package/dist/orchestration/hardened-vm-runner.d.ts +2 -2
  113. package/dist/orchestration/workflow-completion-inbox.d.ts +2 -2
  114. package/dist/plugins/background-shell-support.d.ts +2 -2
  115. package/dist/plugins/blob-backend.d.ts +7 -2
  116. package/dist/plugins/blob-backend.js +22 -18
  117. package/dist/plugins/caching-session-store.d.ts +7 -3
  118. package/dist/plugins/checkpoint-store-sql.d.ts +2 -2
  119. package/dist/plugins/file-history-store-sql.d.ts +170 -0
  120. package/dist/plugins/file-history-store-sql.js +827 -0
  121. package/dist/plugins/file-run-store.d.ts +44 -5
  122. package/dist/plugins/file-run-store.js +85 -13
  123. package/dist/plugins/fork-routing-session-store.d.ts +1 -1
  124. package/dist/plugins/fork-routing-session-store.js +2 -2
  125. package/dist/plugins/leader-run-store-sql.d.ts +1 -1
  126. package/dist/plugins/local-session-store.d.ts +9 -1
  127. package/dist/plugins/memory-embedder-fingerprint.d.ts +1 -1
  128. package/dist/plugins/memory-optout-grant-store-sql.d.ts +139 -0
  129. package/dist/plugins/memory-optout-grant-store-sql.js +157 -0
  130. package/dist/plugins/memory-origin-law.d.ts +2 -2
  131. package/dist/plugins/memory-run-store.d.ts +4 -3
  132. package/dist/plugins/memory-run-store.js +2 -1
  133. package/dist/plugins/permission-rule-store-file.d.ts +16 -6
  134. package/dist/plugins/permission-rule-store-file.js +77 -13
  135. package/dist/plugins/permission-rule-store-sql.d.ts +156 -8
  136. package/dist/plugins/permission-rule-store-sql.js +123 -21
  137. package/dist/plugins/pg-pool.js +59 -0
  138. package/dist/plugins/pg-safe-json.d.ts +1 -1
  139. package/dist/plugins/remote-env-device.d.ts +1 -1
  140. package/dist/plugins/remote-env-k8s.d.ts +1 -1
  141. package/dist/plugins/retention-lane-store-sql.d.ts +3 -3
  142. package/dist/plugins/retention-store-sql.d.ts +7 -7
  143. package/dist/plugins/retention-store-sql.js +6 -0
  144. package/dist/plugins/run-store-sql.d.ts +9 -3
  145. package/dist/plugins/run-store-sql.js +2 -2
  146. package/dist/plugins/send-user-file.d.ts +1 -1
  147. package/dist/plugins/session-placement.d.ts +1 -1
  148. package/dist/plugins/sql-errors.d.ts +1 -1
  149. package/dist/plugins/store-backend.d.ts +59 -29
  150. package/dist/plugins/store-backend.js +75 -15
  151. package/dist/plugins/store-contracts.d.ts +5 -3
  152. package/dist/plugins/task-list-store-sql.d.ts +1 -1
  153. package/dist/plugins/tidb-pool.js +59 -0
  154. package/dist/plugins/web-search.d.ts +1 -1
  155. package/dist/prompts-domain-validate.js +1 -1
  156. package/dist/resource-suspend.d.ts +1 -1
  157. package/dist/resource-window.d.ts +3 -3
  158. package/dist/rules-consent.d.ts +231 -9
  159. package/dist/rules-consent.js +411 -13
  160. package/dist/run-local.js +1 -0
  161. package/dist/runs.d.ts +15 -1
  162. package/dist/runs.js +19 -0
  163. package/dist/runtime-caps-resolver.d.ts +36 -1
  164. package/dist/runtime-caps-resolver.js +25 -0
  165. package/dist/runtime-governance.d.ts +1 -1
  166. package/dist/security.d.ts +5 -5
  167. package/dist/security.js +1 -1
  168. package/dist/session-sync.d.ts +52 -37
  169. package/dist/session-sync.js +19 -16
  170. package/dist/shared-memory-scope-authorizer.d.ts +1 -1
  171. package/dist/sighup-idle.d.ts +1 -1
  172. package/dist/spec-fields.d.ts +22 -1
  173. package/dist/spec-fields.js +11 -0
  174. package/dist/store-live-probe.d.ts +2 -2
  175. package/dist/task-a2a.d.ts +1 -1
  176. package/dist/task-cwd.d.ts +1 -1
  177. package/dist/task-mcp.d.ts +3 -3
  178. package/dist/task-settings.d.ts +3 -3
  179. package/dist/task-workflow.d.ts +1 -1
  180. package/dist/text-bidi.d.ts +2 -2
  181. package/dist/tool-approval.d.ts +191 -9
  182. package/dist/tool-approval.js +248 -29
  183. package/dist/trace/core-keyset-guard.d.ts +1 -1
  184. package/dist/trace/engine-notice-wire.d.ts +6 -4
  185. package/dist/trace/engine-notice-wire.js +2 -0
  186. package/dist/trace/ledger-events.d.ts +1 -1
  187. package/dist/trace/ledger-sink.d.ts +2 -2
  188. package/dist/trace/project.d.ts +3 -2
  189. package/dist/trace/project.js +1 -0
  190. package/dist/turn-activity.d.ts +1 -1
  191. package/dist/wall-clock-jump-guard.d.ts +3 -3
  192. package/package.json +2 -2
  193. package/dist/http/tar.d.ts +0 -17
  194. package/dist/http/tar.js +0 -53
  195. package/dist/http/workspace-content.d.ts +0 -11
  196. package/dist/http/workspace-content.js +0 -20
  197. package/dist/plugins/file-snapshot-store-sql.d.ts +0 -170
  198. package/dist/plugins/file-snapshot-store-sql.js +0 -213
@@ -0,0 +1,50 @@
1
+ /**
2
+ * design/383 S-6 / S-50 §三(S-49 F3)—— 带 `origin` 标的记忆条目在 wire 读口上的 **fence-or-refuse**。
3
+ *
4
+ * ## 病(S-6 禁形)
5
+ * 记忆条目的 `origin` 标(design/336 provenance 标记族)是 additive 键。旧 SDK / 旧壳的 strict/strip parse 会
6
+ * **静默剥掉**它 ⇒ 一条「外源已标」的条目摊平成无标文本,消费端再把它当洁净内容引用/回写 = 出处被洗掉。
7
+ * core 无法在 wire 上强制(它不认识客户端),383 §3.4 显式授权 server 裁三点,S-50 §三裁定如下。
8
+ *
9
+ * ## 三点裁定(逐字)
10
+ * 1. **判定点 = 端点级能力位,不猜客户端版本**:server 无 per-client 版本可见性。凡投影带标条目的读口,
11
+ * **缺省不投带标条目**;消费方以查询参数 `originAware=1` 声明「我认识 origin 键族」后才放行。参数缺席 =
12
+ * 老客户端假定成立 ⇒ fence。
13
+ * 2. **粒度 = 整条目扣留,不做字段降级**:字段级(剥 origin 只给文本)恰是被禁的摊平。整条目扣留 + 计数披露
14
+ * (`refusedUntrusted` 计数拒的既有先例形):响应带 `withheldOriginTagged: <n>`(additive,**只在 n>0 时铸**——
15
+ * 只记真),消费端机读得出「有 n 条因我没声明能力而被围栏」。
16
+ * 3. **能力位形 = 每请求查询参数,不是每客户端注册**:注册制引入一张会陈旧的表;查询参数让「谁认识键族」由
17
+ * 当次调用自证,零状态。SDK 新版缺省带 `originAware=1`;旧 SDK 不带 ⇒ 结构性 fence。
18
+ *
19
+ * ## 判「带标」的谓词
20
+ * 条目对象上 **own** `origin` 键且值非 `undefined`。不读 `origin` 的内部形(那是 core 的开放判别式),只判在场。
21
+ *
22
+ * ## 射程(S-50 §三.1 点名的读口,本仓现状三处)
23
+ * · `GET /v1/memory/export?scope=`(memory-policy.ts,属主自助条目读,分页/全量两腿)
24
+ * · `GET /v1/memory/origin/external?scopes=`(memory-origin.ts,外源标记人面 —— 它的每条都带标,缺省下
25
+ * 全部扣留 + 计数:operator 的工具必须声明能力位才看得见,这正是「围栏」的意思,不是 bug)
26
+ * · `POST /v1/memory/export`(memory-bundle.ts,治理携出包 —— 包经 SDK 落盘再 import,旧 SDK 剥标后 import
27
+ * 会把无标条目当洁净内容收进另一台 = 跨库洗出处;所以包也围栏,计数键落在包顶层)
28
+ * 不围栏的:`GET /v1/memory/entries/:id/provenance`(单条出处账,消费它的人就是来读 origin 的,且响应根不是
29
+ * 条目)、`GET /v1/memory/origin/clearances`(`buildClearanceRowWire` 深白名单投影,已是显式形)。
30
+ *
31
+ * ## 读面行为收窄 = L3(S-50 §三.3 成文)
32
+ * 存量消费者若依赖读到带标条目(现状 = origin 键族尚未在这些读口上稳定出现;真实射程 = 蒸馏产物到货后的新条目),
33
+ * 从本版起须带 `originAware=1`。判据入 383 片3 判据面。
34
+ */
35
+ /** 查询参数名与合法值(闭集:只认字面 `1`;`true`/`yes` 不认 —— 能力位不做宽松解析)。 */
36
+ export declare const ORIGIN_AWARE_QUERY = "originAware";
37
+ /** 从 `URLSearchParams` 读能力位:`originAware=1` ⇒ true;缺席/其它值 ⇒ false(老客户端假定)。 */
38
+ export declare function readOriginAware(params: URLSearchParams): boolean;
39
+ /** 「这条条目带 origin 标吗」—— own 键在场且非 undefined。纯谓词,不读 origin 的形。 */
40
+ export declare function isOriginTagged(entry: unknown): boolean;
41
+ /**
42
+ * 围栏(`build*`:纯数据)。`originAware=true` ⇒ 原数组同一引用、无计数键(零行为差);否则扣留带标条目,
43
+ * 且**只在扣留数 >0 时**铸 `withheldOriginTagged`(只记真:一个恒在场的 `0` 会让消费端把「没围栏」误读成
44
+ * 「围栏了但没扣」)。
45
+ */
46
+ export declare function buildOriginFence<T>(entries: readonly T[], originAware: boolean): {
47
+ entries: readonly T[];
48
+ withheldOriginTagged?: number;
49
+ };
50
+ //# sourceMappingURL=memory-origin-fence.d.ts.map
@@ -0,0 +1,15 @@
1
+ export const ORIGIN_AWARE_QUERY = "originAware";
2
+ export function readOriginAware(params) {
3
+ return params.get(ORIGIN_AWARE_QUERY) === "1";
4
+ }
5
+ export function isOriginTagged(entry) {
6
+ return typeof entry === "object" && entry !== null && Object.prototype.hasOwnProperty.call(entry, "origin") && entry.origin !== undefined;
7
+ }
8
+ export function buildOriginFence(entries, originAware) {
9
+ if (originAware)
10
+ return { entries };
11
+ const kept = entries.filter((e) => !isOriginTagged(e));
12
+ const withheld = entries.length - kept.length;
13
+ return withheld > 0 ? { entries: kept, withheldOriginTagged: withheld } : { entries: kept };
14
+ }
15
+ //# sourceMappingURL=memory-origin-fence.js.map
@@ -74,10 +74,22 @@ export interface MemoryPosture {
74
74
  * 怎么处置」,是运维诊断料,刻意不进 `GET /v1/capabilities` 的窄投影。
75
75
  */
76
76
  provenance: "off" | "carry" | null;
77
+ /**
78
+ * design/383 §3.1(core 7.0.0 片3,S-49 S-1b):本部署的 **capture 姿态**(`MEMORY_CAPTURE_POLICY`)——
79
+ * per-principal `allowMemoryOptOut` verdict 被读进的那个框(`open` fail-safe / `governed` fail-closed /
80
+ * `capture-required` 全拒)。
81
+ *
82
+ * 🔴 `null` = **本部署没设这个键** ⇒ 引擎按它自己的缺省跑。**不是**「等于 open」——
83
+ * 与 `provenance` / `delegationEvidence` 同一条诚实纪律:本读面不替 core 宣布它的缺省。
84
+ *
85
+ * 受众 = **operator 面独占**(同上两位):它回答的是「这台机器怎么裁 opt-out 声明」,是运维诊断料,
86
+ * 刻意不进 `GET /v1/capabilities` 的窄投影。
87
+ */
88
+ capturePolicy: "open" | "governed" | "capture-required" | null;
77
89
  }
78
90
  /** 推导入参 = 装配现场的真事实(不在本函数里重算任何一件)。 */
79
91
  export interface MemoryPostureFacts {
80
- config: Pick<ServiceConfig, "memoryEngineEnabled" | "memoryEngineBackend" | "requirePrincipal" | "memoryEmbedder" | "memoryDelegationEvidence" | "memoryProvenance">;
92
+ config: Pick<ServiceConfig, "memoryEngineEnabled" | "memoryEngineBackend" | "requirePrincipal" | "memoryEmbedder" | "memoryDelegationEvidence" | "memoryProvenance" | "memoryCapturePolicy">;
81
93
  /** 引擎这一腿真的接上了吗(`memoryEngine !== undefined`)—— 装配结果。 */
82
94
  engineWired: boolean;
83
95
  /** 两支 SQL 后端从**实例 getter** 读到的档位;file 腿传 `undefined`(无此面)。 */
@@ -20,6 +20,7 @@ export function buildMemoryPosture(facts) {
20
20
  embedder: embedder !== undefined ? { model: embedder.model, dimensions: embedder.dimensions } : null,
21
21
  delegationEvidence: config.memoryDelegationEvidence ?? null,
22
22
  provenance: config.memoryProvenance ?? null,
23
+ capturePolicy: config.memoryCapturePolicy ?? null,
23
24
  };
24
25
  }
25
26
  export function projectMemoryEngineCapability(posture) {
@@ -104,10 +104,15 @@ export declare function memorySpecForRequest(scope: string | undefined, memoryWr
104
104
  * core legacy 语义,零迁移)。 */
105
105
  originPolicy?: {
106
106
  multiTenant: boolean;
107
- }): {
107
+ },
108
+ /** design/383 §2.1(S-49):请求的 `memoryCapture:"off"` 声明 ⇒ `TaskSpec.memory.capture:"off"`(单成员闭集,
109
+ * 拼写门在 prepareSpec 的 400 门 + core 的 `config.memory_capture_spelling`)。缺席 ⇒ 键不铸(缺席就是照常采集
110
+ * 的唯一形)。与 `memoryWrite` 正交:那是「本 run 只读平面」,这是「本会话永远不进长期记忆」。 */
111
+ memoryCapture?: "off"): {
108
112
  scopes: string[];
109
113
  writeScope?: string | null;
110
114
  scopeContract?: "v2";
111
115
  scopeOrigins?: Record<string, "deployment" | "request">;
116
+ capture?: "off";
112
117
  } | undefined;
113
118
  //# sourceMappingURL=memory-scope.d.ts.map
@@ -69,7 +69,7 @@ export function memoryEngineBackendFor(config, fallbackRoot) {
69
69
  const dir = join(root, "memory");
70
70
  return { backend: new FileMemoryEngineBackend(dir, { controlDir: deriveControlPlaneDir(root, dir) }), root };
71
71
  }
72
- export function memorySpecForRequest(scope, memoryWrite, defaultScopes, originPolicy) {
72
+ export function memorySpecForRequest(scope, memoryWrite, defaultScopes, originPolicy, memoryCapture) {
73
73
  if (!scope)
74
74
  return undefined;
75
75
  const isV2 = isV2ScopeKey;
@@ -86,6 +86,7 @@ export function memorySpecForRequest(scope, memoryWrite, defaultScopes, originPo
86
86
  }
87
87
  }
88
88
  const origins = Object.keys(stamped).length > 0 ? { scopeOrigins: stamped } : {};
89
- return memoryWrite === false ? { ...layered, writeScope: null, ...v2, ...origins } : { ...layered, ...v2, ...origins };
89
+ const capture = memoryCapture === "off" ? { capture: "off" } : {};
90
+ return memoryWrite === false ? { ...layered, writeScope: null, ...v2, ...origins, ...capture } : { ...layered, ...v2, ...origins, ...capture };
90
91
  }
91
92
  //# sourceMappingURL=memory-scope.js.map
@@ -42,7 +42,7 @@ export interface ModelNotAllowed {
42
42
  * #233 / A-002.8 —— **归一条款**:membership 判定前把 ref 归一到**终端 catalog 条目**再比。
43
43
  *
44
44
  * 为什么不能拿字符串直接跟名单比:`expandTiers` 的增广目录里,档位词(`pro`)与 CC 别名(`sonnet`)
45
- * 的值与 `config.models[<name>]` 是**同一个对象引用**——裸串比较会让 `@pro` 绕过一张按 name 写的名单
45
+ * 的值与 `config.models[<name>]` 是**同一个对象引用**——裸串比较会让 `@pro` 跳过一张按 name 写的名单
46
46
  * (同一只模型,两个键)。所以两边(ref 与每个名单条目)都先经 {@link matchCatalogModel} 解到目录条目,
47
47
  * 再按 **引用相等** 判同。id 形的 ref 不需要特别处理:`matchCatalogModel` 本身就是 name+id 双键索引,
48
48
  * 它会把 id 解成目录键,引用相等接着就对上了。
@@ -73,7 +73,7 @@ export declare function terminalCatalogName(ref: string, catalog: Record<string,
73
73
  /**
74
74
  * [865]② name+id 双键目录索引:返回 ref 命中的目录 **name 键**(spec.model 的规范形),未知 → undefined。
75
75
  * - name 命中用 Object.hasOwn(🔴 不用真值索引:Object.prototype 继承键 "constructor"/"__proto__" 等在裸
76
- * 索引下是真值,会绕过 allow-list——沿袭原实现的纪律)。
76
+ * 索引下是真值,会跳过 allow-list——沿袭原实现的纪律)。
77
77
  * - id 命中扫目录值(`Model.id`),跳过 "default" 别名键(它与其指向的真身共享 id,解析到别名会让 /model
78
78
  * 面板与实际路由再度撕裂);同 id 多 name 时取目录序第一个(与 /v1/models 列表序一致,确定性)。
79
79
  */
@@ -9,9 +9,13 @@ export interface FailOpenTagEntry {
9
9
  }
10
10
  /** 闭集 tag 词表。形=`<repo>.<domain>.<site>`(跨仓同形,便于三仓遥测并表)。 */
11
11
  export declare const FAIL_OPEN_TAGS: {
12
+ readonly "server.memory-optout-grant.onfault-hook-threw": {
13
+ readonly cls: "F";
14
+ readonly note: "design/383 S-1(applyMemoryOptOutGrant):本地 grant 腿故障时的**披露钩子**(onFault → warn 日志)自己抛了。钩子只是观测面:吞掉它保住的是「resolver 不因一行日志失败而 reject」(那会把一条腿的故障扩成整车故障 —— core 的 resolver-throw 降级臂作废同一次解析里 center 已答好的 allowWorkflows/allowFork);代价是**那一行披露丢了**,而 `allowMemoryOptOut` 键缺席这件事本身不受影响(fail-closed/fail-safe 极性由 core 按姿态判)。本计数让「披露钩子一直在炸」显形。";
15
+ };
12
16
  readonly "server.run-store.file-hydrate-unreadable": {
13
17
  readonly cls: "P-DEBT";
14
- readonly note: "件6(#43 案 [4281]):`FileRunStore.hydrate()` 的 `readdirSync(runs/)` 失败 ⇒ 引擎以**空账**起动。旧形是空 catch + return:data root 不可读(权限/挂载丢失/路径被换走)时零告警,此后每一次 getRun 恒 miss —— 而「恒 miss」正是 #43 现场看到的形,一次环境故障被伪装成「本来就没有这条 run」。方向问题在于吞掉之后被读成的是「证不出有行 ⇒ 没有行」,两句话不等价。**不拒启是刻意保留的行为**(拒启是行为变更,另立),所以这条留作债:响亮化(error 级启动日志带 runs 绝对路径+异常文案)+ 本计数,让它在遥测里显形而不是只活在一行日志里。终局=给 file 后端一条可选的 fail-closed 启动门。";
18
+ readonly note: "件6(#43 案 [4281]):`FileRunStore.hydrate()` 的 `readdirSync(runs/)` 失败 ⇒ 引擎以**空账**起动。旧形是空 catch + return:data root 不可读(权限/挂载丢失/路径被换走)时零告警,此后每一次 getRun 恒 miss —— 而「恒 miss」正是 #43 现场看到的形,一次环境故障被伪装成「本来就没有这条 run」。方向问题在于吞掉之后被读成的是「证不出有行 ⇒ 没有行」,两句话不等价。**缺省不拒启是刻意保留的行为**,所以这条留作债:响亮化(error 级启动日志带 runs 绝对路径+异常文案)+ 本计数,让它在遥测里显形而不是只活在一行日志里。⚠️ 2026-08-29(S-46 件B)起终局格已兑现:部署可用 `RUN_STORE_STRICT_HYDRATE=on` 把这一格换成 **fail-closed 启动门**(拒启,遗言同样带绝对路径+异常文案);本计数因此只在**缺省 off** 档上产生 —— 读遥测时别把「计数为零」直接读成「没有部署踩到这条路」,也可能是它们开了拒启门、根本起不来。";
15
19
  };
16
20
  readonly "server.fork-routing.placed-probe-host-error": {
17
21
  readonly cls: "F";
@@ -129,10 +133,6 @@ export declare const FAIL_OPEN_TAGS: {
129
133
  readonly cls: "F";
130
134
  readonly note: "[4913]:durable park 投影腿在 strip 之前拿结果里的 checkpointToken 换 `pendingAction.toolCallId`(cs.get 一次读),那次读**抛了**(store 抖动 / 滚动升级期 checkpoint 格式 version guard / 行已被并发消费)⇒ 本次 park 的 wire 投影**缺 `toolCallId` 键**,其余键(gate/checkpointId)照旧。放行的最坏后果 = 消费方(cli 判别子 v3 写者②)退回该键到货前的行为——同族多兄弟 durable park 分不出主角、诚实全不标(cli 侧 S3 局限,成文的旧现状),纯展示/关联面,不参与任何门/CAS/resume 判定,故 F 类。绝不挡终局路径是承重的:park 投影点全在 done/suspended 事件写链上,让它抛会把一次 store 抖动升级成整条 run 终局写失败。必须留痕:键缺席与「tool-less park 本就无键」([1995]② OMITTED 契约)在 wire 上同形,不计数则「投影为什么总缺」在遥测里永不显形。";
131
135
  };
132
- readonly "server.model-key.degrade-hop-borrows-gateway-key": {
133
- readonly cls: "P-DEBT";
134
- readonly note: "[4998] BROKEN 实锤的**留痕半场**(#345 codex 对抗复审 R1-[high] 的可采纳部分,亲验落实)。`brain.ts` 的反应式降级 hop 在降级目标**没有自己的凭据**时回落主网关 key(`own ?? config.gatewayApiKey`),而那只 fallback brain 是用 `fallbackModel.baseUrl` 当 `config.baseUrl` 造的 —— 于是 core 5.57.0 的配对裁决器看到的是「部署凭据 + 它自己的声明根」⇒ 判 `paired` ⇒ 照发。🔴 **上游的法在这条路上结构性看不见问题**:不是 core 漏判,是**本仓把一个撒谎的 config 递给了它**(把主网关的 key 与降级目标的根说成一对)。真读数:`test/brain.test.ts` 的「既有腿不回归」格实测 `Bearer primary-gateway-key` 打到 `http://degrade-gw.test/v1/chat/completions`。为什么记债而不是本批修:正解要么是「off-root 且无自 key ⇒ 降级链按不可用后退」(改的是**可用性**轴,会让一类既有部署的降级当场失效),要么是「把目标自己的凭据改走 per-model 位(`Model.headers`)让 core 判得见」(改核心装配 + 既有钉字节),两者都是独立设计件、不是提货批的尾巴。判据 = 解不出目标自己的 key ∧ 目标 baseUrl 既不是 `gatewayBaseUrl` 也不在 `gatewayFallbackUrls`(后者按同文件成文契约 = 同一逻辑网关/同一 key 有效域,不算换家),归一用 core 的 `normalizeBaseUrl`(与本仓其它配对判定同一只)。⚠️ 与已撤线的 `off-route-model-uses-gateway-key` **不是同一条路**:那条的铸点是 side-query/WebFetch 的 per-model auth 座,而座位恒以**被请求的那只**模型被调用、位置在装饰器栈**之外** —— 它从来没有、也不可能观察到降级 hop 换上的 `fallbackModel`。所以这不是「撤早了」,是一条**一直没人看**的姊妹路。";
135
- };
136
136
  readonly "server.exec-lane.ssh-host-key-unverified": {
137
137
  readonly cls: "P-DEBT";
138
138
  readonly note: "#296(A-052.2,clay 裁定 C-R21):`REMOTE_EXEC=ssh` 且**两只主机密钥旋钮都缺席**(`SSH_HOST_FINGERPRINT` / `SSH_KNOWN_HOSTS`)⇒ 本次连接接受**任何**主机密钥,等价 `ssh -o StrictHostKeyChecking=no`。放行的最坏后果:一次中间人拿到这条会话的**全部命令、全部产物、以及 agent 在那台机器上的全部动作**(私钥仍在控制面手里、拿不走,design/61 §5 #2)。⇒ 保护型欠账,不是体验回退。**方向是裁定的、不是遗漏**:本 lane 的既有部署形是「AI 编排批量部署到一批 SSH 可达的机器」,那些机器的指纹通常不在任何 known_hosts 里,默认拒连 = 一次静默的可用性断裂;C-R21 裁「在场即严格,缺席即响亮」。补偿三件:①**每连接**一条 `ssh_host_key_unverified` warn(不是每进程一次 —— 一条长期裸连的 lane 必须在每条连接上都看得见);②本计数(遥测里「有多少条连接是裸连」是一个可观测的数,曲线应当只降不升);③两旋钮任一在场即**严格校验、不符拒连**(拒因带两半指纹 + `ssh-keyscan` 指路)。终局 = 部署把旋钮配上(那时本计数归零);旋钮不配而想收紧的部署级开关(「无旋钮即拒启」)是下一件,不在 C-R21 射程。";
@@ -141,6 +141,18 @@ export declare const FAIL_OPEN_TAGS: {
141
141
  readonly cls: "F";
142
142
  readonly note: "#343(黑板 [5100] Q3):park 墓碑表(#329 迟到回决的 wire approvalId → 店内坐标指路条)**全局帽已满、而本 principal 还没到自己的 per-principal 子帽** ⇒ 这条新墓碑**不落**(拒新,一格都不挤别人)。放行的最坏后果 = 这一只 ask 的 **live 腿指路便利**缺席,壳按 Yes 拿到的是 404 `unknown_or_other_replica` —— 也就是 #329 之前的那一格逐字行为,既不会错兑也不会双兑;**完整兑付面一个字节没动**(park 行本体在 durable 店未失,`GET /v1/approvals` 列表 + `/v1/approvals/:sessionId/decide` 仍可回决),故 F 类而不是保护型。为什么方向是「拒新」而不是修前的「FIFO 挤最旧」:那个形让**甲的用量决定乙的可用性**(已鉴权租户量产 park 终局 ask 即可把别家墓碑全部挤出,merge-rescan 744 confirmed),而 park 是常态路由、不需要任何异常手法。必须留痕:「表被别人打满」与「这张卡本来就没有墓碑」在 wire 上完全同形,不计数则运维只能靠「用户说按 Yes 老是 404」这种间接症状发现,而那正是 [4845] 排障代价实证过的那条路。detail 带 owner(判「压力来自谁」的第一手线索)。本 tag 有**两条臂、两个根因**,detail 判别:(a) 全局帽满且本 principal 未超子帽 ⇒ 拒新(detail 带 owner);(b) 本 principal 超子帽但名下条目**全在飞**、无一可逐 ⇒ 拒新(detail 带 `reason=all-inflight`)。超子帽且**有**可逐条目的常态臂逐自己最旧的一条(量产者自伤,设计内有界表纪律)才不走本 tag。";
143
143
  };
144
+ readonly "server.approvals.stale-echoed-token-unreadable": {
145
+ readonly cls: "F";
146
+ readonly note: "S-02 codex R1-[medium](验真后修):decide 的 409 `approval_stale` 臂对**调用方回显的旧 token** 做归属判定(`cs.get(binding.checkpointToken)`),那次读**抛了**(store 抖动 / 版本守卫 / 行恰被清理)。修前这里是静默 `catch(()=>null)`,「读不出」被折成「确认不在库」——叠上 S-02 的 404 收口后,一次 store 抖动就会把合法旧 token 的 409+terminal 谎报成 404「没有这条审批」。修后:unreadable ⇒ **保持 409**(`terminal` 不披露——证不出归属就不说,与 2026-07-26 纪律同向;404 折叠只在读成功且未命中时走,探测方无法自控 store 故障 ⇒ 谕示封闭不破)。放行的最坏后果 = 该次拒体 `terminal` 诚实缺席(`currentPending` 由它自己的读口与属主复核另判,不受本臂牵连),壳退回重拉列表,judgment 面 fail-safe,故 F 类。留痕理由:这次读是 terminal 披露与 404 折叠两个判定的输入,长期抖动必须在遥测显形。";
147
+ };
148
+ readonly "server.approvals.stale-current-pending-unreadable": {
149
+ readonly cls: "F";
150
+ readonly note: "S-02 件2([5522]④→[5525]):409 `approval_stale` 拒体铸 additive 指路键 `currentPending`(本会话当前 pending 的 D-1 坐标,壳一跳重定位)时,当前 pending 行的 `cs.get(token)` 读**抛了**(store 抖动 / 滚动升级期 checkpoint 格式 version guard / 行恰被并发消费)⇒ 本次拒体**缺 `currentPending` 键**,409 本体与 `terminal` 判定照旧。放行的最坏后果 = 壳退回该键到货前的行为(重拉 GET /v1/approvals 自行重定位),纯指路/便利面,不参与任何门/CAS/resume 判定,故 F 类(同族先例:`server.park.toolcallid-read-failed`,同为 cs.get 投影读失败臂)。必须留痕:键缺席与「此刻确实没有 pending」在 wire 上同形,不计数则「指路为什么总缺」在遥测里永不显形。";
151
+ };
152
+ readonly "server.file-history.retention-trim-failed": {
153
+ readonly cls: "F";
154
+ readonly note: "S-15 twin 保留条款(core 7.0.0 design/381 §片2 的 SQL 孪生):boundary 提交后的自剪 pass(core 共享选择函数 `fileHistoryBoundariesToKeep` + 既有 `reap` 级联)失败 ⇒ 本轮 boundary **已提交**、rewind 座照常,只是这一轮没剪。放行的是「GC 失败不失败用户这一轮」(core InMemory/File 参照同形 `reap(...).catch(() => {})`,但参照是无声吞,本仓走留痕三件套);最坏后果 = 该 scope 暂时超上限一个 boundary,下一次提交的 pass 按**当前全集**重算、连它一起剪(不累积欠账)。detail 带 scope / entry / 异常文案。";
155
+ };
144
156
  };
145
157
  /** 词表键推导的闭集类型——未登记的 tag 传不进 {@link recordFailOpen}(编译期拒)。 */
146
158
  export type FailOpenTag = keyof typeof FAIL_OPEN_TAGS;
@@ -1,8 +1,12 @@
1
1
  import { appendFileSync } from "node:fs";
2
2
  export const FAIL_OPEN_TAGS = {
3
+ "server.memory-optout-grant.onfault-hook-threw": {
4
+ cls: "F",
5
+ note: "design/383 S-1(applyMemoryOptOutGrant):本地 grant 腿故障时的**披露钩子**(onFault → warn 日志)自己抛了。钩子只是观测面:吞掉它保住的是「resolver 不因一行日志失败而 reject」(那会把一条腿的故障扩成整车故障 —— core 的 resolver-throw 降级臂作废同一次解析里 center 已答好的 allowWorkflows/allowFork);代价是**那一行披露丢了**,而 `allowMemoryOptOut` 键缺席这件事本身不受影响(fail-closed/fail-safe 极性由 core 按姿态判)。本计数让「披露钩子一直在炸」显形。",
6
+ },
3
7
  "server.run-store.file-hydrate-unreadable": {
4
8
  cls: "P-DEBT",
5
- note: "件6(#43 案 [4281]):`FileRunStore.hydrate()` 的 `readdirSync(runs/)` 失败 ⇒ 引擎以**空账**起动。旧形是空 catch + return:data root 不可读(权限/挂载丢失/路径被换走)时零告警,此后每一次 getRun 恒 miss —— 而「恒 miss」正是 #43 现场看到的形,一次环境故障被伪装成「本来就没有这条 run」。方向问题在于吞掉之后被读成的是「证不出有行 ⇒ 没有行」,两句话不等价。**不拒启是刻意保留的行为**(拒启是行为变更,另立),所以这条留作债:响亮化(error 级启动日志带 runs 绝对路径+异常文案)+ 本计数,让它在遥测里显形而不是只活在一行日志里。终局=给 file 后端一条可选的 fail-closed 启动门。",
9
+ note: "件6(#43 案 [4281]):`FileRunStore.hydrate()` 的 `readdirSync(runs/)` 失败 ⇒ 引擎以**空账**起动。旧形是空 catch + return:data root 不可读(权限/挂载丢失/路径被换走)时零告警,此后每一次 getRun 恒 miss —— 而「恒 miss」正是 #43 现场看到的形,一次环境故障被伪装成「本来就没有这条 run」。方向问题在于吞掉之后被读成的是「证不出有行 ⇒ 没有行」,两句话不等价。**缺省不拒启是刻意保留的行为**,所以这条留作债:响亮化(error 级启动日志带 runs 绝对路径+异常文案)+ 本计数,让它在遥测里显形而不是只活在一行日志里。⚠️ 2026-08-29(S-46 件B)起终局格已兑现:部署可用 `RUN_STORE_STRICT_HYDRATE=on` 把这一格换成 **fail-closed 启动门**(拒启,遗言同样带绝对路径+异常文案);本计数因此只在**缺省 off** 档上产生 —— 读遥测时别把「计数为零」直接读成「没有部署踩到这条路」,也可能是它们开了拒启门、根本起不来。",
6
10
  },
7
11
  "server.fork-routing.placed-probe-host-error": {
8
12
  cls: "F",
@@ -120,10 +124,6 @@ export const FAIL_OPEN_TAGS = {
120
124
  cls: "F",
121
125
  note: "[4913]:durable park 投影腿在 strip 之前拿结果里的 checkpointToken 换 `pendingAction.toolCallId`(cs.get 一次读),那次读**抛了**(store 抖动 / 滚动升级期 checkpoint 格式 version guard / 行已被并发消费)⇒ 本次 park 的 wire 投影**缺 `toolCallId` 键**,其余键(gate/checkpointId)照旧。放行的最坏后果 = 消费方(cli 判别子 v3 写者②)退回该键到货前的行为——同族多兄弟 durable park 分不出主角、诚实全不标(cli 侧 S3 局限,成文的旧现状),纯展示/关联面,不参与任何门/CAS/resume 判定,故 F 类。绝不挡终局路径是承重的:park 投影点全在 done/suspended 事件写链上,让它抛会把一次 store 抖动升级成整条 run 终局写失败。必须留痕:键缺席与「tool-less park 本就无键」([1995]② OMITTED 契约)在 wire 上同形,不计数则「投影为什么总缺」在遥测里永不显形。",
122
126
  },
123
- "server.model-key.degrade-hop-borrows-gateway-key": {
124
- cls: "P-DEBT",
125
- note: "[4998] BROKEN 实锤的**留痕半场**(#345 codex 对抗复审 R1-[high] 的可采纳部分,亲验落实)。`brain.ts` 的反应式降级 hop 在降级目标**没有自己的凭据**时回落主网关 key(`own ?? config.gatewayApiKey`),而那只 fallback brain 是用 `fallbackModel.baseUrl` 当 `config.baseUrl` 造的 —— 于是 core 5.57.0 的配对裁决器看到的是「部署凭据 + 它自己的声明根」⇒ 判 `paired` ⇒ 照发。🔴 **上游的法在这条路上结构性看不见问题**:不是 core 漏判,是**本仓把一个撒谎的 config 递给了它**(把主网关的 key 与降级目标的根说成一对)。真读数:`test/brain.test.ts` 的「既有腿不回归」格实测 `Bearer primary-gateway-key` 打到 `http://degrade-gw.test/v1/chat/completions`。为什么记债而不是本批修:正解要么是「off-root 且无自 key ⇒ 降级链按不可用后退」(改的是**可用性**轴,会让一类既有部署的降级当场失效),要么是「把目标自己的凭据改走 per-model 位(`Model.headers`)让 core 判得见」(改核心装配 + 既有钉字节),两者都是独立设计件、不是提货批的尾巴。判据 = 解不出目标自己的 key ∧ 目标 baseUrl 既不是 `gatewayBaseUrl` 也不在 `gatewayFallbackUrls`(后者按同文件成文契约 = 同一逻辑网关/同一 key 有效域,不算换家),归一用 core 的 `normalizeBaseUrl`(与本仓其它配对判定同一只)。⚠️ 与已撤线的 `off-route-model-uses-gateway-key` **不是同一条路**:那条的铸点是 side-query/WebFetch 的 per-model auth 座,而座位恒以**被请求的那只**模型被调用、位置在装饰器栈**之外** —— 它从来没有、也不可能观察到降级 hop 换上的 `fallbackModel`。所以这不是「撤早了」,是一条**一直没人看**的姊妹路。",
126
- },
127
127
  "server.exec-lane.ssh-host-key-unverified": {
128
128
  cls: "P-DEBT",
129
129
  note: "#296(A-052.2,clay 裁定 C-R21):`REMOTE_EXEC=ssh` 且**两只主机密钥旋钮都缺席**(`SSH_HOST_FINGERPRINT` / `SSH_KNOWN_HOSTS`)⇒ 本次连接接受**任何**主机密钥,等价 `ssh -o StrictHostKeyChecking=no`。放行的最坏后果:一次中间人拿到这条会话的**全部命令、全部产物、以及 agent 在那台机器上的全部动作**(私钥仍在控制面手里、拿不走,design/61 §5 #2)。⇒ 保护型欠账,不是体验回退。**方向是裁定的、不是遗漏**:本 lane 的既有部署形是「AI 编排批量部署到一批 SSH 可达的机器」,那些机器的指纹通常不在任何 known_hosts 里,默认拒连 = 一次静默的可用性断裂;C-R21 裁「在场即严格,缺席即响亮」。补偿三件:①**每连接**一条 `ssh_host_key_unverified` warn(不是每进程一次 —— 一条长期裸连的 lane 必须在每条连接上都看得见);②本计数(遥测里「有多少条连接是裸连」是一个可观测的数,曲线应当只降不升);③两旋钮任一在场即**严格校验、不符拒连**(拒因带两半指纹 + `ssh-keyscan` 指路)。终局 = 部署把旋钮配上(那时本计数归零);旋钮不配而想收紧的部署级开关(「无旋钮即拒启」)是下一件,不在 C-R21 射程。",
@@ -132,6 +132,18 @@ export const FAIL_OPEN_TAGS = {
132
132
  cls: "F",
133
133
  note: "#343(黑板 [5100] Q3):park 墓碑表(#329 迟到回决的 wire approvalId → 店内坐标指路条)**全局帽已满、而本 principal 还没到自己的 per-principal 子帽** ⇒ 这条新墓碑**不落**(拒新,一格都不挤别人)。放行的最坏后果 = 这一只 ask 的 **live 腿指路便利**缺席,壳按 Yes 拿到的是 404 `unknown_or_other_replica` —— 也就是 #329 之前的那一格逐字行为,既不会错兑也不会双兑;**完整兑付面一个字节没动**(park 行本体在 durable 店未失,`GET /v1/approvals` 列表 + `/v1/approvals/:sessionId/decide` 仍可回决),故 F 类而不是保护型。为什么方向是「拒新」而不是修前的「FIFO 挤最旧」:那个形让**甲的用量决定乙的可用性**(已鉴权租户量产 park 终局 ask 即可把别家墓碑全部挤出,merge-rescan 744 confirmed),而 park 是常态路由、不需要任何异常手法。必须留痕:「表被别人打满」与「这张卡本来就没有墓碑」在 wire 上完全同形,不计数则运维只能靠「用户说按 Yes 老是 404」这种间接症状发现,而那正是 [4845] 排障代价实证过的那条路。detail 带 owner(判「压力来自谁」的第一手线索)。本 tag 有**两条臂、两个根因**,detail 判别:(a) 全局帽满且本 principal 未超子帽 ⇒ 拒新(detail 带 owner);(b) 本 principal 超子帽但名下条目**全在飞**、无一可逐 ⇒ 拒新(detail 带 `reason=all-inflight`)。超子帽且**有**可逐条目的常态臂逐自己最旧的一条(量产者自伤,设计内有界表纪律)才不走本 tag。",
134
134
  },
135
+ "server.approvals.stale-echoed-token-unreadable": {
136
+ cls: "F",
137
+ note: "S-02 codex R1-[medium](验真后修):decide 的 409 `approval_stale` 臂对**调用方回显的旧 token** 做归属判定(`cs.get(binding.checkpointToken)`),那次读**抛了**(store 抖动 / 版本守卫 / 行恰被清理)。修前这里是静默 `catch(()=>null)`,「读不出」被折成「确认不在库」——叠上 S-02 的 404 收口后,一次 store 抖动就会把合法旧 token 的 409+terminal 谎报成 404「没有这条审批」。修后:unreadable ⇒ **保持 409**(`terminal` 不披露——证不出归属就不说,与 2026-07-26 纪律同向;404 折叠只在读成功且未命中时走,探测方无法自控 store 故障 ⇒ 谕示封闭不破)。放行的最坏后果 = 该次拒体 `terminal` 诚实缺席(`currentPending` 由它自己的读口与属主复核另判,不受本臂牵连),壳退回重拉列表,judgment 面 fail-safe,故 F 类。留痕理由:这次读是 terminal 披露与 404 折叠两个判定的输入,长期抖动必须在遥测显形。",
138
+ },
139
+ "server.approvals.stale-current-pending-unreadable": {
140
+ cls: "F",
141
+ note: "S-02 件2([5522]④→[5525]):409 `approval_stale` 拒体铸 additive 指路键 `currentPending`(本会话当前 pending 的 D-1 坐标,壳一跳重定位)时,当前 pending 行的 `cs.get(token)` 读**抛了**(store 抖动 / 滚动升级期 checkpoint 格式 version guard / 行恰被并发消费)⇒ 本次拒体**缺 `currentPending` 键**,409 本体与 `terminal` 判定照旧。放行的最坏后果 = 壳退回该键到货前的行为(重拉 GET /v1/approvals 自行重定位),纯指路/便利面,不参与任何门/CAS/resume 判定,故 F 类(同族先例:`server.park.toolcallid-read-failed`,同为 cs.get 投影读失败臂)。必须留痕:键缺席与「此刻确实没有 pending」在 wire 上同形,不计数则「指路为什么总缺」在遥测里永不显形。",
142
+ },
143
+ "server.file-history.retention-trim-failed": {
144
+ cls: "F",
145
+ note: "S-15 twin 保留条款(core 7.0.0 design/381 §片2 的 SQL 孪生):boundary 提交后的自剪 pass(core 共享选择函数 `fileHistoryBoundariesToKeep` + 既有 `reap` 级联)失败 ⇒ 本轮 boundary **已提交**、rewind 座照常,只是这一轮没剪。放行的是「GC 失败不失败用户这一轮」(core InMemory/File 参照同形 `reap(...).catch(() => {})`,但参照是无声吞,本仓走留痕三件套);最坏后果 = 该 scope 暂时超上限一个 boundary,下一次提交的 pass 按**当前全集**重算、连它一起剪(不累积欠账)。detail 带 scope / entry / 异常文案。",
146
+ },
135
147
  };
136
148
  export function failOpenTagForDroppedFrame(frameType) {
137
149
  switch (frameType) {
@@ -22,12 +22,12 @@ import { type WorkflowScriptRunner, type WorkflowPrimitives } from "@sema-agent/
22
22
  * 4. ERRORS from host fns are marshalled to a CONTEXT-LOCAL `Error` (the beyond-battery leak core's
23
23
  * battery misses).
24
24
  * 5. Data crossing INTO a host fn (bridged args) and OUT to the host caller (the result + meta) is
25
- * sterilized of the `__proto__`/`constructor`/`prototype` tampering gadget ({@link scrubProtoDeep}) —
25
+ * sterilized of the `__proto__`/`constructor`/`prototype` rewriting gadget ({@link scrubProtoDeep}) —
26
26
  * the marshal-bridge boundary-crossing case — and marshalled to plain data (never a live cross-realm object
27
27
  * carrying getter/toJSON/then traps).
28
28
  *
29
29
  * 🔴 SCOPE (v1, clay 2026-06-23): this is BOUNDARY isolation (no host-code readout / unintended host execution / cross-tenant credential
30
- * reach / host prototype tampering) — VALIDATED. It is NOT full RESOURCE isolation: an in-process `vm`
30
+ * reach / host prototype rewriting) — VALIDATED. It is NOT full RESOURCE isolation: an in-process `vm`
31
31
  * cannot hard-cap heap, so a runaway-allocation script can OOM the shared process (a resource-exhaustion issue, NOT a data-boundary issue). v1
32
32
  * mitigates with caps (maxAgents + a wall-clock timeout + a per-sync-chunk vm timeout) + monitoring. For HARD
33
33
  * heap/CPU isolation use {@link import("./hardened-vm-worker-runner.js").createWorkerHardenedVmRunner} — the
@@ -77,7 +77,7 @@ export declare const SERVED_FENCE_MS: number;
77
77
  * 只吐一条(entry 的存储 status 列仍归一,那是账本的双终态列,不动;RAW status 本就随 payload extras
78
78
  * 透传)。这与 {@link taskNotificationStreamKey} 的 RAW 语义对齐(stream 侧早已保 RAW)。
79
79
  *
80
- * 编码=防碰撞元组:域/taskId/status 走 JSON 数组(调用方自由串伪造不了分隔符);`#seq` 尾折在 JSON 之外
80
+ * 编码=防碰撞元组:域/taskId/status 走 JSON 数组(调用方自由串自造不了分隔符);`#seq` 尾折在 JSON 之外
81
81
  * ——JSON 段恒以 `"]` 结尾,`#digits` 尾无歧义,于是 {@link foldKeyFamily} 的剥尾语义 AND SQL 双生子
82
82
  * (workflow-run-store-sql 的 `regexp_replace(run_id, '#[0-9]+$', '')` 家族比对)原样成立——把 seq 折进
83
83
  * JSON 元组会让 SQL 侧家族 ack/fence 永 miss(那是另一批的真库改动面,本修有意不碰)。
@@ -284,7 +284,7 @@ priority?: SystemInjectionPriority): WorkflowCompletionInboxEntry;
284
284
  * review, 1.78) — which under the old rule silently killed the multi-tenant push for every sync-leg workflow;
285
285
  * 2. the payload's `principal` — core threads `spec.principal`, which in THIS service is ALWAYS the VERIFIED
286
286
  * identity (the 1.55 F-fix invariant, main.ts: gated door = BFF-verified header, direct door = JWT `sub`,
287
- * never the spoofable header; core 1.187 hardened the `TaskSpec.principal` contract to exactly this).
287
+ * never the self-asserted header; core 1.187 hardened the `TaskSpec.principal` contract to exactly this).
288
288
  * Captured at SPAWN time → immutable, no delete-then-reclaim window. This is also the same value core's own
289
289
  * workflow scope guard (`canAccessWorkflowRun`) keys off;
290
290
  * 3. neither → single-user stamps `owner: null` (no tenancy boundary); multi-tenant (`requirePrincipal`)
@@ -6,10 +6,10 @@
6
6
  * adapter (E2B, kata/k8s, …) must enforce identically, so a transport author only writes the 4 transport
7
7
  * primitives and cannot accidentally re-implement (or skip) an invariant:
8
8
  *
9
- * - **opaque shellId** — a fresh `bg_<n>_<uuid>` brand, NEVER derived from the provider's job/pid (越权红线,
9
+ * - **opaque shellId** — a fresh `bg_<n>_<uuid>` brand, NEVER derived from the provider's job/pid (超范围红线,
10
10
  * design/103 §3.8). The provider id lives only inside the driver state.
11
11
  * - **owned-id isolation** — `poll`/`kill` look the shellId up in THIS manager's registry; an id from another env
12
- * (or a forged one) is `not_found`, never resolved against the provider (design/103 §3.8 / interface 越权契约).
12
+ * (or a forged one) is `not_found`, never resolved against the provider (design/103 §3.8 / interface 超范围契约).
13
13
  * - **maxConcurrent** — counts only `running` shells (terminal entries are kept for residual polling but free their
14
14
  * slot, so "TaskStop one first" is actionable — matches the TOC `maxConcurrent 终态占槽` review fix).
15
15
  * - **timeout hard wall** — a control-plane timer kills the shell and flips it to `killed` at the bounded BG
@@ -50,7 +50,9 @@ export declare class SqlBlobBackend implements BlobBackend {
50
50
  private readonly pool;
51
51
  /** Effective per-blob cap: explicit ctor value wins; else tidb=SQL_BLOB_DEFAULT_MAX_BYTES, pg=uncapped. */
52
52
  private readonly capBytes;
53
- constructor(dialect: "tidb" | "pg", pool: MysqlPool | PgPool, maxBytes?: number);
53
+ /** Blob-domain table (S-15 blob 域隔离:file-history 域用自己的表,reap/GC 不跨域)。代码内字面量,非用户输入。 */
54
+ private readonly table;
55
+ constructor(dialect: "tidb" | "pg", pool: MysqlPool | PgPool, maxBytes?: number, table?: string);
54
56
  /** The created_at-refreshing content-addressed upsert — VERBATIM the tidb/pg stores' `putBlobRow`. Refreshes
55
57
  * created_at even on a dedup hit so a re-used/just-written blob is "touched=now" and GC-grace-protected.
56
58
  * (a): an over-cap blob is REJECTED (typed {@link BlobTooLargeError}) BEFORE the driver ever sees the bytes —
@@ -103,7 +105,7 @@ export interface MinioBlobBackendConfig {
103
105
  * importManifest content-address check the SQL stores already do.
104
106
  */
105
107
  /** 鲁棒性批2 C2/C3/C6(2026-08-03):S3 兼容端点 IO 的统一 abort 上界(fleet-client C1 定常量形
106
- * 先例)。半开端点此前各挂 undici 默认 ~300s headersTimeout——blob GET 钉死 restore/session-import
108
+ * 先例)。半开端点此前各挂 undici 默认 ~300s headersTimeout——blob GET 卡住 restore/session-import
107
109
  * 同步路径,SendUserFile 工具调用永不返回。120s 对慢链路 ~64MiB 单 PUT/GET 仍宽裕(presignTtlSec
108
110
  * 注释同口径),远小于病形。send-user-file 与 leader/diffup 同 import 此常量。 */
109
111
  export declare const OBJECT_STORE_IO_TIMEOUT_MS = 120000;
@@ -117,9 +119,12 @@ export declare class MinioBlobBackend implements BlobBackend {
117
119
  * path). The file-snapshot stores ALWAYS pass the pool, so the live reference-tracking always has the index. */
118
120
  private readonly indexPool?;
119
121
  private readonly indexDialect?;
122
+ /** Blob-domain index table (S-15 域隔离,SqlBlobBackend.table 同款;默认 = E19 纪元的 snapshot_blob)。 */
123
+ private readonly indexTable;
120
124
  constructor(cfg: MinioBlobBackendConfig, index?: {
121
125
  dialect: "tidb" | "pg";
122
126
  pool: MysqlPool | PgPool;
127
+ table?: string;
123
128
  });
124
129
  private objectKey;
125
130
  private presign;
@@ -27,48 +27,50 @@ export class SqlBlobBackend {
27
27
  dialect;
28
28
  pool;
29
29
  capBytes;
30
- constructor(dialect, pool, maxBytes) {
30
+ table;
31
+ constructor(dialect, pool, maxBytes, table = "snapshot_blob") {
31
32
  this.dialect = dialect;
32
33
  this.pool = pool;
33
34
  this.capBytes = maxBytes ?? (dialect === "tidb" ? SQL_BLOB_DEFAULT_MAX_BYTES : Number.POSITIVE_INFINITY);
35
+ this.table = table;
34
36
  }
35
37
  async putBlob(hash, bytes) {
36
38
  if (bytes.byteLength > this.capBytes)
37
39
  throw new BlobTooLargeError(bytes.byteLength, this.capBytes);
38
40
  if (this.dialect === "tidb") {
39
- await this.pool.query("INSERT INTO snapshot_blob (blob_hash, byte_len, bytes, created_at) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE created_at = VALUES(created_at)", [hash, bytes.byteLength, Buffer.from(bytes), new Date()]);
41
+ await this.pool.query(`INSERT INTO ${this.table} (blob_hash, byte_len, bytes, created_at) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE created_at = VALUES(created_at)`, [hash, bytes.byteLength, Buffer.from(bytes), new Date()]);
40
42
  }
41
43
  else {
42
- await this.pool.query("INSERT INTO snapshot_blob (blob_hash, byte_len, bytes, created_at) VALUES ($1,$2,$3,$4) ON CONFLICT (blob_hash) DO UPDATE SET created_at = EXCLUDED.created_at", [hash, bytes.byteLength, Buffer.from(bytes), new Date()]);
44
+ await this.pool.query(`INSERT INTO ${this.table} (blob_hash, byte_len, bytes, created_at) VALUES ($1,$2,$3,$4) ON CONFLICT (blob_hash) DO UPDATE SET created_at = EXCLUDED.created_at`, [hash, bytes.byteLength, Buffer.from(bytes), new Date()]);
43
45
  }
44
46
  }
45
47
  async getBlob(hash) {
46
48
  if (this.dialect === "tidb") {
47
- const [rows] = await this.pool.query("SELECT bytes FROM snapshot_blob WHERE blob_hash = ?", [hash]);
49
+ const [rows] = await this.pool.query(`SELECT bytes FROM ${this.table} WHERE blob_hash = ?`, [hash]);
48
50
  return rows[0]?.bytes != null ? new Uint8Array(rows[0].bytes) : undefined;
49
51
  }
50
- const b = await this.pool.query("SELECT bytes FROM snapshot_blob WHERE blob_hash = $1", [hash]);
52
+ const b = await this.pool.query(`SELECT bytes FROM ${this.table} WHERE blob_hash = $1`, [hash]);
51
53
  return b.rows[0]?.bytes != null ? new Uint8Array(b.rows[0].bytes) : undefined;
52
54
  }
53
55
  async hasBlobs(hashes) {
54
- return sqlHasBlobs(this.dialect, this.pool, hashes);
56
+ return sqlHasBlobs(this.dialect, this.pool, hashes, this.table);
55
57
  }
56
58
  async deleteBlobs(hashes) {
57
- const deleted = await sqlDeleteIndexRowsPastGrace(this.dialect, this.pool, hashes);
59
+ const deleted = await sqlDeleteIndexRowsPastGrace(this.dialect, this.pool, hashes, this.table);
58
60
  return deleted.length;
59
61
  }
60
62
  }
61
- async function sqlHasBlobs(dialect, pool, hashes) {
63
+ async function sqlHasBlobs(dialect, pool, hashes, table = "snapshot_blob") {
62
64
  if (hashes.length === 0)
63
65
  return new Set();
64
66
  const distinct = [...new Set(hashes)];
65
67
  if (dialect === "tidb") {
66
68
  const ph = distinct.map(() => "?").join(",");
67
- const [rows] = await pool.query(`SELECT blob_hash FROM snapshot_blob WHERE blob_hash IN (${ph})`, distinct);
69
+ const [rows] = await pool.query(`SELECT blob_hash FROM ${table} WHERE blob_hash IN (${ph})`, distinct);
68
70
  return new Set(rows.map((r) => String(r.blob_hash)));
69
71
  }
70
72
  const ph = distinct.map((_, i) => `$${i + 1}`).join(",");
71
- const r = await pool.query(`SELECT blob_hash FROM snapshot_blob WHERE blob_hash IN (${ph})`, distinct);
73
+ const r = await pool.query(`SELECT blob_hash FROM ${table} WHERE blob_hash IN (${ph})`, distinct);
72
74
  return new Set(r.rows.map((row) => row.blob_hash));
73
75
  }
74
76
  export async function sqlBlobSizes(dialect, pool, hashes) {
@@ -94,7 +96,7 @@ export async function sqlBlobSizes(dialect, pool, hashes) {
94
96
  }
95
97
  return out;
96
98
  }
97
- async function sqlDeleteIndexRowsPastGrace(dialect, pool, hashes) {
99
+ async function sqlDeleteIndexRowsPastGrace(dialect, pool, hashes, table = "snapshot_blob") {
98
100
  if (hashes.length === 0)
99
101
  return [];
100
102
  const distinct = [...new Set(hashes)];
@@ -102,20 +104,20 @@ async function sqlDeleteIndexRowsPastGrace(dialect, pool, hashes) {
102
104
  if (dialect === "tidb") {
103
105
  const ph = distinct.map(() => "?").join(",");
104
106
  const grace = await pool
105
- .query(`SELECT blob_hash FROM snapshot_blob WHERE created_at < ? AND blob_hash IN (${ph})`, [cutoff, ...distinct])
107
+ .query(`SELECT blob_hash FROM ${table} WHERE created_at < ? AND blob_hash IN (${ph})`, [cutoff, ...distinct])
106
108
  .then(([rows]) => rows.map((r) => String(r.blob_hash)))
107
109
  .catch(() => []);
108
110
  if (grace.length === 0)
109
111
  return [];
110
112
  const gph = grace.map(() => "?").join(",");
111
113
  await pool
112
- .query(`DELETE FROM snapshot_blob WHERE created_at < ? AND blob_hash IN (${gph})`, [cutoff, ...grace])
114
+ .query(`DELETE FROM ${table} WHERE created_at < ? AND blob_hash IN (${gph})`, [cutoff, ...grace])
113
115
  .catch(() => undefined);
114
116
  return grace;
115
117
  }
116
118
  const ph = distinct.map((_, i) => `$${i + 2}`).join(",");
117
119
  const r = await pool
118
- .query(`DELETE FROM snapshot_blob WHERE created_at < $1 AND blob_hash IN (${ph}) RETURNING blob_hash`, [cutoff, ...distinct])
120
+ .query(`DELETE FROM ${table} WHERE created_at < $1 AND blob_hash IN (${ph}) RETURNING blob_hash`, [cutoff, ...distinct])
119
121
  .catch(() => undefined);
120
122
  return (r?.rows ?? []).map((row) => row.blob_hash);
121
123
  }
@@ -126,10 +128,12 @@ export class MinioBlobBackend {
126
128
  ttl;
127
129
  indexPool;
128
130
  indexDialect;
131
+ indexTable;
129
132
  constructor(cfg, index) {
130
133
  this.cfg = cfg;
131
134
  this.keyPrefix = cfg.keyPrefix ?? "blobs/";
132
135
  this.ttl = cfg.presignTtlSec ?? 3600;
136
+ this.indexTable = index?.table ?? "snapshot_blob";
133
137
  if (index) {
134
138
  this.indexDialect = index.dialect;
135
139
  this.indexPool = index.pool;
@@ -159,10 +163,10 @@ export class MinioBlobBackend {
159
163
  }
160
164
  if (this.indexPool && this.indexDialect) {
161
165
  if (this.indexDialect === "tidb") {
162
- await this.indexPool.query("INSERT INTO snapshot_blob (blob_hash, byte_len, bytes, created_at) VALUES (?,?,NULL,?) ON DUPLICATE KEY UPDATE created_at = VALUES(created_at)", [hash, bytes.byteLength, new Date()]);
166
+ await this.indexPool.query(`INSERT INTO ${this.indexTable} (blob_hash, byte_len, bytes, created_at) VALUES (?,?,NULL,?) ON DUPLICATE KEY UPDATE created_at = VALUES(created_at)`, [hash, bytes.byteLength, new Date()]);
163
167
  }
164
168
  else {
165
- await this.indexPool.query("INSERT INTO snapshot_blob (blob_hash, byte_len, bytes, created_at) VALUES ($1,$2,NULL,$3) ON CONFLICT (blob_hash) DO UPDATE SET created_at = EXCLUDED.created_at", [hash, bytes.byteLength, new Date()]);
169
+ await this.indexPool.query(`INSERT INTO ${this.indexTable} (blob_hash, byte_len, bytes, created_at) VALUES ($1,$2,NULL,$3) ON CONFLICT (blob_hash) DO UPDATE SET created_at = EXCLUDED.created_at`, [hash, bytes.byteLength, new Date()]);
166
170
  }
167
171
  }
168
172
  }
@@ -188,7 +192,7 @@ export class MinioBlobBackend {
188
192
  }
189
193
  async hasBlobs(hashes) {
190
194
  if (this.indexPool && this.indexDialect)
191
- return sqlHasBlobs(this.indexDialect, this.indexPool, hashes);
195
+ return sqlHasBlobs(this.indexDialect, this.indexPool, hashes, this.indexTable);
192
196
  const distinct = [...new Set(hashes)];
193
197
  const present = new Set();
194
198
  await mapBounded(distinct, MINIO_OP_CONCURRENCY, async (hash) => {
@@ -254,7 +258,7 @@ export class MinioBlobBackend {
254
258
  return 0;
255
259
  let toDelete;
256
260
  if (this.indexPool && this.indexDialect) {
257
- toDelete = await sqlDeleteIndexRowsPastGrace(this.indexDialect, this.indexPool, hashes);
261
+ toDelete = await sqlDeleteIndexRowsPastGrace(this.indexDialect, this.indexPool, hashes, this.indexTable);
258
262
  }
259
263
  else {
260
264
  toDelete = [...new Set(hashes)];
@@ -55,8 +55,12 @@ export declare class CachingSessionStore implements SessionStore {
55
55
  * run per session (→ listSessions `lastRunId`). MUST be forwarded: the local backend's Runner store is THIS cache
56
56
  * wrapper, so without forwarding `noteTaskRun?.()` silently no-ops and lastRunId stays null (review BLOCKER). Pure
57
57
  * pass-through to the inner store's in-mem map — no cache interaction. (TiDB/PG don't implement it → not bound.)
58
- * Return type matches core's `SessionStore.noteTaskRun?(): void | Promise<void>` (review: don't narrow to void). */
59
- readonly noteTaskRun?: (sessionId: string, taskId: string) => void | Promise<void>;
58
+ * Return type matches core's `SessionStore.noteTaskRun?(): void | Promise<void>` (review: don't narrow to void).
59
+ * #499(core 5.65 增量批):第三参 = engine runId。绑定用的是 `.bind(inner)`(整函数),运行期本就
60
+ * 三参全转 —— 这里把**声明**对齐到 core 的三参形,免得读这行类型的人以为缝上只有两参(fork-routing
61
+ * 的两参形正是在运行期真丢过第三参的那个病,同批修)。内店(LocalSessionStore)今日仍是两参消费,
62
+ * runId 落地候 sessions 列表 wire 键的 spec 节拍(见本批提货报告件1b)。 */
63
+ readonly noteTaskRun?: (sessionId: string, taskId: string, runId?: string) => void | Promise<void>;
60
64
  /** Write-once auto-title (SQL twins + local sidecar carry it; pure pass-through). */
61
65
  readonly setTitleIfNull?: (sessionId: string, title: string) => Promise<boolean>;
62
66
  readonly probeTitle?: (sessionId: string) => Promise<"none" | "untitled" | "titled">;
@@ -85,7 +89,7 @@ export declare class CachingSessionStore implements SessionStore {
85
89
  /** [2024](cli 取证)2c session-sync P1d-β — delegated STAGED import seam. 此前 staging 族不在本类的逐方法
86
90
  * 转发白名单里 ⇒ 默认部署(SESSION_CACHE_TTL_SEC=300 把真店包进本类)PUSH Phase A 恒 501
87
91
  * `capability.session_store_required`,而 capabilities().sessionSync 谓词三项(backend/exportEntries/
88
- * fileSnapshotStore)都被转发了 ⇒ 照报 true(能力面谎)。转发形:handle 的 `commit` 是唯一写真 id 的
92
+ * fileHistoryStore)都被转发了 ⇒ 照报 true(能力面谎)。转发形:handle 的 `commit` 是唯一写真 id 的
89
93
  * 时刻,成功后逐出该 id 的暖 handle + S25 指纹(importEntries/replaceEntries 同纪律);`abort` 只丢
90
94
  * staging 行、不碰真 id ⇒ 不逐出。 */
91
95
  readonly beginImportStaging?: (realSessionId: string, token: string) => StagingHandle;
@@ -14,7 +14,7 @@ import { type SqlDriver } from "./sql-driver.js";
14
14
  */
15
15
  export declare const TERMINAL_BACKSTOP_MS: number;
16
16
  /**
17
- * design/80 D-D (adversarial fix): the crash-safe `terminal_at_ms` backstop must fall STRICTLY AFTER any SLA
17
+ * design/80 D-D (cross-review fix): the crash-safe `terminal_at_ms` backstop must fall STRICTLY AFTER any SLA
18
18
  * `deadline`, never AT it. `terminal_at_ms = max(createdAt+backstop, deadline)` made the two coincide whenever an
19
19
  * operator tuned APPROVAL_TERMINAL_BACKSTOP_MS at/below the SLA — and reapExpired's terminal_at_ms-branch (which
20
20
  * has NO gate_kind filter) then abort-EXPIRED a human/irreversible_ask gate in the SAME tick the deny-sweep
@@ -116,7 +116,7 @@ export interface PendingCheckpoint {
116
116
  * 与同行 `tool_input` 在 mint 就 `redactDeep` 是**同一条**纪律(#209 件4 把 `shadowedRule` 修在读面
117
117
  * 是因为那条路的行早已落库、改铸点救不了存量;本键是**新**列,没有存量行要救,修在铸点更彻底:
118
118
  * 秘密从此连**落库**都不落,不是落了库再在两条读面各洗一遍)。
119
- * 3. **脱敏之后**才截长(codex 对抗复审 [medium],验真后修):`redactSecrets` 会**变长**,先按限长校验
119
+ * 3. **脱敏之后**才截长(codex 交叉复审 [medium],验真后修):`redactSecrets` 会**变长**,先按限长校验
120
120
  * 原文再脱敏会产出超限文本,而 SQL 腿有**回读**(列 → 本函数再跑一遍)、LOCAL 腿没有 ⇒ 同一份素材
121
121
  * 两个后端给出不同卡面。顺序反过来之后本函数对自己的输出**幂等**,两条腿同形。完整论证见
122
122
  * `RuleOfferRawSchema` 顶注。