@sema-agent/server 7.51.0 → 7.52.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/USAGE.md +103 -24
  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/device-lane.js +2 -0
  21. package/dist/boot/engine-lease.d.ts +1 -1
  22. package/dist/boot/execution-env.d.ts +1 -1
  23. package/dist/boot/governance-seams.d.ts +2 -2
  24. package/dist/boot/leader.d.ts +1 -1
  25. package/dist/boot/memory-boundary.d.ts +2 -2
  26. package/dist/boot/memory-consolidation.d.ts +6 -6
  27. package/dist/boot/org-memory.d.ts +1 -1
  28. package/dist/boot/parked-revive-gate.d.ts +3 -3
  29. package/dist/boot/reapers.d.ts +2 -2
  30. package/dist/boot/reapers.js +17 -4
  31. package/dist/boot/resolve-spec.d.ts +12 -0
  32. package/dist/boot/resolve-spec.js +29 -7
  33. package/dist/boot/retention-lane.d.ts +2 -2
  34. package/dist/boot/runner-deps.d.ts +17 -7
  35. package/dist/boot/runner-deps.js +4 -2
  36. package/dist/boot/runtime-caps.d.ts +23 -0
  37. package/dist/boot/runtime-caps.js +24 -2
  38. package/dist/boot/session-faces.d.ts +1 -1
  39. package/dist/boot/session-faces.js +14 -3
  40. package/dist/boot/stores.js +31 -1
  41. package/dist/boot/task-list-lane.d.ts +2 -2
  42. package/dist/brain.js +51 -31
  43. package/dist/budget.d.ts +2 -2
  44. package/dist/capabilities/center-plugins.d.ts +1 -1
  45. package/dist/capabilities/collab-workflows.d.ts +1 -1
  46. package/dist/capabilities/hands-lane.d.ts +3 -3
  47. package/dist/capabilities/scenarios.d.ts +3 -3
  48. package/dist/capabilities/skills.d.ts +1 -1
  49. package/dist/config-center/hot-keys-registry.js +5 -0
  50. package/dist/config-center/http-client.d.ts +2 -2
  51. package/dist/config-center/read-face.d.ts +1 -1
  52. package/dist/config-center/restart-signal.d.ts +1 -1
  53. package/dist/config-types.d.ts +100 -25
  54. package/dist/config.d.ts +11 -3
  55. package/dist/config.js +91 -15
  56. package/dist/deployment-governance.d.ts +1 -1
  57. package/dist/device-enrollment.d.ts +2 -2
  58. package/dist/device-store.d.ts +3 -3
  59. package/dist/device-ws-hub.d.ts +4 -2
  60. package/dist/device-ws-hub.js +9 -3
  61. package/dist/device-ws-protocol.d.ts +2 -2
  62. package/dist/env-facts.d.ts +1 -1
  63. package/dist/execution-lane-caps.d.ts +1 -1
  64. package/dist/fleet/fleet-bus.d.ts +5 -2
  65. package/dist/fleet/fleet-reconciler.d.ts +1 -1
  66. package/dist/fleet/fleet-terminal-window.d.ts +1 -1
  67. package/dist/hooks/hook-runner.d.ts +8 -1
  68. package/dist/http/active-run-conflict.d.ts +2 -2
  69. package/dist/http/principal-gate.d.ts +1 -1
  70. package/dist/http/route-ctx.d.ts +27 -5
  71. package/dist/http/routes/admin-memory-optout.d.ts +48 -0
  72. package/dist/http/routes/admin-memory-optout.js +75 -0
  73. package/dist/http/routes/agents-roster.d.ts +1 -1
  74. package/dist/http/routes/approvals-assistant.js +21 -21
  75. package/dist/http/routes/capabilities.js +20 -9
  76. package/dist/http/routes/diagnostics.d.ts +1 -1
  77. package/dist/http/routes/leader.d.ts +1 -1
  78. package/dist/http/routes/memory-bundle.d.ts +1 -1
  79. package/dist/http/routes/memory-bundle.js +2 -1
  80. package/dist/http/routes/memory-compliance.d.ts +4 -4
  81. package/dist/http/routes/memory-origin.d.ts +4 -4
  82. package/dist/http/routes/memory-origin.js +3 -1
  83. package/dist/http/routes/memory-policy.js +6 -2
  84. package/dist/http/routes/retention-ops.d.ts +1 -1
  85. package/dist/http/routes/rules.d.ts +5 -2
  86. package/dist/http/routes/rules.js +84 -5
  87. package/dist/http/routes/run-memory-capture.d.ts +34 -0
  88. package/dist/http/routes/run-memory-capture.js +97 -0
  89. package/dist/http/routes/runs.d.ts +1 -0
  90. package/dist/http/routes/runs.js +212 -6
  91. package/dist/http/routes/session-sync.d.ts +4 -6
  92. package/dist/http/routes/session-sync.js +60 -47
  93. package/dist/http/routes/sessions.js +61 -188
  94. package/dist/http/routes/shared-memory.d.ts +1 -1
  95. package/dist/http/routes/tasks.js +1 -1
  96. package/dist/http/routes/workflows.js +19 -0
  97. package/dist/http/server.d.ts +35 -13
  98. package/dist/http/server.js +110 -14
  99. package/dist/http/verify-rounds.d.ts +1 -1
  100. package/dist/http/wire-types.d.ts +41 -8
  101. package/dist/leader/endpoint.d.ts +1 -1
  102. package/dist/leader/leader.d.ts +1 -1
  103. package/dist/leader/repair-oracle.d.ts +2 -2
  104. package/dist/leader/repair-oracle.js +1 -1
  105. package/dist/main.js +14 -6
  106. package/dist/memory-bundle-engine.d.ts +1 -1
  107. package/dist/memory-operator-faces.d.ts +5 -5
  108. package/dist/memory-origin-fence.d.ts +35 -0
  109. package/dist/memory-origin-fence.js +66 -0
  110. package/dist/memory-posture.d.ts +13 -1
  111. package/dist/memory-posture.js +1 -0
  112. package/dist/memory-scope.d.ts +6 -1
  113. package/dist/memory-scope.js +3 -2
  114. package/dist/model-select.d.ts +2 -2
  115. package/dist/observability/fail-open.d.ts +17 -5
  116. package/dist/observability/fail-open.js +17 -5
  117. package/dist/orchestration/hardened-vm-runner.d.ts +2 -2
  118. package/dist/orchestration/workflow-completion-inbox.d.ts +2 -2
  119. package/dist/plugins/background-shell-support.d.ts +2 -2
  120. package/dist/plugins/blob-backend.d.ts +7 -2
  121. package/dist/plugins/blob-backend.js +22 -18
  122. package/dist/plugins/caching-session-store.d.ts +7 -3
  123. package/dist/plugins/checkpoint-store-sql.d.ts +2 -2
  124. package/dist/plugins/file-history-store-sql.d.ts +170 -0
  125. package/dist/plugins/file-history-store-sql.js +827 -0
  126. package/dist/plugins/file-run-store.d.ts +44 -5
  127. package/dist/plugins/file-run-store.js +85 -13
  128. package/dist/plugins/fork-routing-session-store.d.ts +1 -1
  129. package/dist/plugins/fork-routing-session-store.js +2 -2
  130. package/dist/plugins/leader-run-store-sql.d.ts +1 -1
  131. package/dist/plugins/local-session-store.d.ts +9 -1
  132. package/dist/plugins/memory-embedder-fingerprint.d.ts +1 -1
  133. package/dist/plugins/memory-optout-grant-store-sql.d.ts +139 -0
  134. package/dist/plugins/memory-optout-grant-store-sql.js +157 -0
  135. package/dist/plugins/memory-origin-law.d.ts +2 -2
  136. package/dist/plugins/memory-run-store.d.ts +4 -3
  137. package/dist/plugins/memory-run-store.js +2 -1
  138. package/dist/plugins/permission-rule-store-file.d.ts +16 -6
  139. package/dist/plugins/permission-rule-store-file.js +77 -13
  140. package/dist/plugins/permission-rule-store-sql.d.ts +156 -8
  141. package/dist/plugins/permission-rule-store-sql.js +123 -21
  142. package/dist/plugins/pg-pool.js +59 -0
  143. package/dist/plugins/pg-safe-json.d.ts +1 -1
  144. package/dist/plugins/remote-env-device.d.ts +1 -1
  145. package/dist/plugins/remote-env-k8s.d.ts +1 -1
  146. package/dist/plugins/retention-lane-store-sql.d.ts +3 -3
  147. package/dist/plugins/retention-store-sql.d.ts +7 -7
  148. package/dist/plugins/retention-store-sql.js +6 -0
  149. package/dist/plugins/run-store-sql.d.ts +9 -3
  150. package/dist/plugins/run-store-sql.js +2 -2
  151. package/dist/plugins/send-user-file.d.ts +1 -1
  152. package/dist/plugins/session-placement.d.ts +1 -1
  153. package/dist/plugins/sql-errors.d.ts +1 -1
  154. package/dist/plugins/store-backend.d.ts +59 -29
  155. package/dist/plugins/store-backend.js +75 -15
  156. package/dist/plugins/store-contracts.d.ts +5 -3
  157. package/dist/plugins/task-list-store-sql.d.ts +1 -1
  158. package/dist/plugins/tidb-pool.js +59 -0
  159. package/dist/plugins/web-search.d.ts +1 -1
  160. package/dist/prompts-domain-validate.js +1 -1
  161. package/dist/resource-suspend.d.ts +1 -1
  162. package/dist/resource-window.d.ts +3 -3
  163. package/dist/rules-consent.d.ts +231 -9
  164. package/dist/rules-consent.js +411 -13
  165. package/dist/run-local.js +29 -4
  166. package/dist/runs.d.ts +15 -1
  167. package/dist/runs.js +19 -0
  168. package/dist/runtime-caps-resolver.d.ts +36 -1
  169. package/dist/runtime-caps-resolver.js +25 -0
  170. package/dist/runtime-governance.d.ts +1 -1
  171. package/dist/security.d.ts +5 -5
  172. package/dist/security.js +1 -1
  173. package/dist/session-sync.d.ts +52 -37
  174. package/dist/session-sync.js +19 -16
  175. package/dist/shared-memory-scope-authorizer.d.ts +1 -1
  176. package/dist/sighup-idle.d.ts +1 -1
  177. package/dist/spec-fields.d.ts +22 -1
  178. package/dist/spec-fields.js +11 -0
  179. package/dist/store-live-probe.d.ts +2 -2
  180. package/dist/task-a2a.d.ts +1 -1
  181. package/dist/task-cwd.d.ts +1 -1
  182. package/dist/task-mcp.d.ts +3 -3
  183. package/dist/task-settings.d.ts +3 -3
  184. package/dist/task-workflow.d.ts +1 -1
  185. package/dist/text-bidi.d.ts +2 -2
  186. package/dist/tool-approval.d.ts +191 -9
  187. package/dist/tool-approval.js +248 -29
  188. package/dist/trace/core-keyset-guard.d.ts +3 -3
  189. package/dist/trace/engine-notice-wire.d.ts +6 -4
  190. package/dist/trace/engine-notice-wire.js +2 -0
  191. package/dist/trace/ledger-events.d.ts +1 -1
  192. package/dist/trace/ledger-sink.d.ts +2 -2
  193. package/dist/trace/project.d.ts +8 -2
  194. package/dist/trace/project.js +10 -0
  195. package/dist/turn-activity.d.ts +1 -1
  196. package/dist/wall-clock-jump-guard.d.ts +3 -3
  197. package/package.json +3 -3
  198. package/dist/http/tar.d.ts +0 -17
  199. package/dist/http/tar.js +0 -53
  200. package/dist/http/workspace-content.d.ts +0 -11
  201. package/dist/http/workspace-content.js +0 -20
  202. package/dist/plugins/file-snapshot-store-sql.d.ts +0 -170
  203. package/dist/plugins/file-snapshot-store-sql.js +0 -213
@@ -6,7 +6,7 @@
6
6
  * compiling unchanged. All imports below are top-level `import type` (erased — never a load edge,
7
7
  * see test/module-cycle-gate.test.ts header on verbatimModuleSyntax). */
8
8
  import type { SealedKeyPoison } from "./sealed-key.js";
9
- import type { A2aServerSpec, CompliancePosture, LockedKey, McpServerSpec, Model, ModelRoles, RetentionPolicy } from "@sema-agent/core";
9
+ import type { A2aServerSpec, CompliancePosture, FileHistoryRetentionPolicy, LockedKey, McpServerSpec, Model, ModelRoles, RetentionPolicy } from "@sema-agent/core";
10
10
  import type { ApprovalHmacKey, PrincipalJwtKey } from "./auth-keys.js";
11
11
  import type { ElicitationThrottle } from "./elicitation.js";
12
12
  import type { QuestionThrottle } from "./question.js";
@@ -183,6 +183,26 @@ export interface StreamApprovalConfig {
183
183
  /** 判据 5(遗孤最终可判)的上界,量 immutable 的 `createdAtMs`。`STREAM_APPROVAL_ORPHAN_TTL_MS`,
184
184
  * 默认 **7d**,有界 [60000, 90d]。 */
185
185
  orphanTtlMs: number;
186
+ /**
187
+ * S-43(案A,#374 / core #481)—— **零活流铸造**的 ask 的悬挂窗。`UNREACHED_ASK_TTL_MS`,
188
+ * 默认 **3600000**(1h),有界 [60000, 86400000](60s ~ 24h),坏值/越界**拒启**(#210 A 档)。
189
+ *
190
+ * 三问(behavior-facing 硬条款):
191
+ * · **谁需要**:workflow 恒后台跑(CC 220 parity)⇒ 子代 ask 到达时 (owner, session) 键域常无活 SSE。
192
+ * 改形前那一刻即答 `"unavailable"`,合成整条命令死拒([5443] 车道2 实证)。悬挂让人稍后经
193
+ * `/v1/approvals` / 壳队列 / attach 补投三通道回来答它。1h 是「人不在场但当天会回来」的尺度。
194
+ * · **谁被伤**:悬挂 ask 占一格写侧准入配额(`STREAM_APPROVAL_ADMIT_MAX_PER_TASK`,默认 32)且占一条
195
+ * 持久行,窗越长积压越久;显式声明无人值守的部署(`UNATTENDED_APPROVAL_POLICY=deny`)**完全不受
196
+ * 本键影响**(那条腿在铸造时刻即拒,追记 F1)。
197
+ * · **补偿**:本键本身(调小即回到接近旧行为;60s 下界不给「等于零窗」的形——那等价于关掉案A,
198
+ * 要那个语义请用 `deny` 政策或 `STREAM_ASK_WINDOW_MS=0` 的 windowZero 腿)。
199
+ *
200
+ * 🔴 跨旋钮不变量(与 `WINDOW + ADHOC_GRACE < ORPHAN_TTL` 同族、同理由):
201
+ * **`UNREACHED_ASK_TTL_MS + ADHOC_GRACE` 必须严格小于 `ORPHAN_TTL`** —— 悬挂行的 `expiresAtMs ≈
202
+ * createdAtMs + 本键`,判据 4 在 `expiresAtMs + 宽限` 触发而判据 5 在 `createdAtMs + orphanTtl` 触发;
203
+ * 不等式不成立 ⇒ 遗孤兜底必先到,每条悬挂行的归因都被写成 `orphan_ttl_exceeded`。boot 期校验,拒启。
204
+ */
205
+ unreachedTtlMs: number;
186
206
  }
187
207
  export interface ImageBakeConfig {
188
208
  enabled: boolean;
@@ -251,7 +271,7 @@ export interface ServiceConfigFlat {
251
271
  /** Prompt-cache breakpoints on system + last tool (stable prefix). Default true. */
252
272
  cacheBreakpoints: boolean;
253
273
  /** `ANTHROPIC_MAX_RETRIES` —— **缺席不铸键**,同 `gatewayMaxRetries` 的理由(见其顶注):
254
- * env 没配时以前会铸成 `2` 再显式传给引擎,把引擎默认钉死在 server 这一层。 */
274
+ * env 没配时以前会铸成 `2` 再显式传给引擎,把引擎默认写死在 server 这一层。 */
255
275
  maxRetries?: number;
256
276
  };
257
277
  /** Resilience brain stack (1.38, design/27): tiered timeouts + an optional circuit breaker layered
@@ -348,10 +368,6 @@ export interface ServiceConfigFlat {
348
368
  /** B1 /health 活体 store 探测:后台缓存探针间隔 ms(STORE_PROBE_INTERVAL_MS;0=显式关,负/非数 boot
349
369
  * 响亮拒)。只对 SQL 后端接线;探针原语=backend.dbNowMs()(S10 时钟探针的真 DB 往返,零新 SQL 面)。 */
350
370
  storeProbeIntervalMs: number;
351
- /** Rewind snapshot bounds: byte cap in MB (env REWIND_SNAPSHOT_MAX_MB). Unset = core DEFAULT_SNAPSHOT_BOUNDS
352
- * (256 MiB / 10000 files as of core 2026-06-26 — was 2000/64MiB). BYTE-ONLY knob: raises OR lowers just the byte
353
- * cap; file-count/ignoreDirs stay core defaults (more knobs when a case shows up). */
354
- rewindSnapshotMaxMb?: number;
355
371
  /** Long-term memory (design/138 S1, clay 2026-07-08): the injection-first file-based memory ENGINE
356
372
  * (core `RunnerDeps.memoryBackend` + `MemoryEngine`) is the ONLY memory plane — the legacy MemoryStore
357
373
  * path (MEMORY_BACKEND/EMBEDDING_* SQL/file note stores) was dropped without migration. Default ON;
@@ -378,13 +394,13 @@ export interface ServiceConfigFlat {
378
394
  * · `true` ⇒ 部署自证有推断**看不见**的持久通道(自定义 writer / MCP 记忆服务 / remote lane 与记忆根
379
395
  * 共享挂载)。它撤回引擎的只读披露,并在 remote lane 上**恢复** `# Memory` 写指令(core 5.26.0 r13/r14
380
396
  * 收窄的官方恢复路径);
381
- * · `false` ⇒ 强制披露只读/无持久面,并关掉文件工具往记忆根的写通道(披露不会被存储事实打脸)。
397
+ * · `false` ⇒ 强制披露只读/无持久面,并关掉文件工具往记忆根的写通道(披露不会被存储事实推翻)。
382
398
  *
383
399
  * 🔴 **`false` 自 core 5.27.0 起还多一层**([3612] F2,#231 提货):declared-false 的会话是**受限会话**
384
400
  * —— materialize/search/harvest 一律按**已提交账**(ledger + shadow)供给,磁盘上无事务背书的分歧
385
401
  * 既不收编也不供给,而是留盘 + 响亮点名(`restricted_divergence`,`HarvestRejectionCode` 新成员),
386
402
  * 等下一次**非受限**会话走正常门收编。这补上了 5.26.0 自己登记的那条残余(读侧入站同步收编磁盘分歧
387
- * **与会话意图无关**)——在那之前,这里写的「披露不会被存储事实打脸」只是半真话:披露说「本机存不下」,
403
+ * **与会话意图无关**)——在那之前,这里写的「披露不会被存储事实推翻」只是半真话:披露说「本机存不下」,
388
404
  * 引擎却仍可能把别处落进记忆根的字节收进这次会话的账。
389
405
  * ⚠️ **这一层的射程 = file 记忆引擎**(`MEMORY_ENGINE_BACKEND` 未设的默认单机形):受限视图是
390
406
  * `FileMemoryEngineBackend.restrictedAdoptionView` 的实装,而 `engine.materialize` 取它走的是
@@ -482,6 +498,28 @@ export interface ServiceConfigFlat {
482
498
  * ⚠️ **不冻进 checkpoint**:resume 的那条腿跟**当前**部署配置走(core 成文)。
483
499
  * 运维读面 = `GET /v1/diagnostics/wiring` 的 `memoryPosture.provenance`(`null`=本部署未设)。 */
484
500
  memoryProvenance?: "off" | "carry";
501
+ /** design/383 §3.1(core 7.0.0 片3,S-49 S-1b):本部署的记忆 **capture 姿态** —— per-principal
502
+ * `RuntimeCaps.allowMemoryOptOut` verdict 被读进的那个框。env `MEMORY_CAPTURE_POLICY`,三词闭集:
503
+ * · `open`(≡ 缺席时 core 的缺省)—— `TaskSpec.memory.capture:"off"` 声明按面值生效;只有 resolver 的
504
+ * **显式** `false` 拒它(`memory.capture_optout_denied`,terminal)。resolver **故障**(throw / 非
505
+ * boolean / 缺席)⇒ opt-out **仍生效** + `onError({phase:"config"})` 具名披露 —— 极性反转臂:隐私轴上
506
+ * 不可逆的一侧是「把用户明说不要记的会话记进去」,fail-safe 朝它的反面;
507
+ * · `governed` —— per-principal verdict **必答**(混合车队:部分用户强制留存)。`true` 放行、`false` 拒;
508
+ * resolver 故障/verdict 缺席 ⇒ **拒跑任务**(fail-closed:部署显式声明合规侧的不可逆更重);
509
+ * · `capture-required` —— 全体声明具名拒(部署级强制留存,一行配置,不依赖 resolver)。
510
+ *
511
+ * 🔴 **缺席 = 不铸键**(不复制上游缺省;`memoryProvenance` 同姿态)。坏值 ⇒ 启动期响亮拒:`governed` 与
512
+ * `open` 的故障极性**相反**,一个被静默折成缺省的拼写错误会让合规部署在故障窗里放行留存必需用户的 opt-out。
513
+ * core 侧 `prepareConfigDoors` 另有同判门(`config.memory_capture_policy`),两道同向不冲突。
514
+ *
515
+ * 🔴 **`governed` 的半配置拒启**:该姿态要求本部署有 per-principal verdict 源。本批唯一源 = SQL 后端上的
516
+ * `memory_optout_grant` 表(S-2;center 键 `EntitlementRuntimeCaps.allowMemoryOptOut` 是 settings-schema
517
+ * 的独立小件,到货前不算源)。governed 而无源 ⇒ 每个 opt-out 声明都被 core 拒且没人知道为什么 ⇒
518
+ * `boot/runtime-caps.ts` 的 `assertMemoryCapturePolicyWirable` 在启动期拒启(与 consolidation 阀门 D1 同姿态)。
519
+ *
520
+ * ⚠️ **部署席 ONLY**:TaskSpec 无同名键;不冻进 checkpoint(resume 腿跟当前部署配置走,core 成文)。
521
+ * 运维读面 = `GET /v1/diagnostics/wiring` 的 `memoryPosture.capturePolicy`(`null`=本部署未设)。 */
522
+ memoryCapturePolicy?: "open" | "governed" | "capture-required";
485
523
  /**
486
524
  * design/378(core 5.58 design/376 片②)—— 记忆 **consolidation driver 阀门**的三根旋钮。
487
525
  * **恒在场**(不是可选块,与 {@link retentionSweep} 同姿态):三根都有文档化的缺省,而缺省本身
@@ -587,6 +625,11 @@ export interface ServiceConfigFlat {
587
625
  execTimeoutMs?: number;
588
626
  /** `DEVICE_MAX_INFLIGHT_PER_DEVICE`(§11-O3 clay 已裁 = 4)。 */
589
627
  maxInflightPerDevice?: number;
628
+ /** `DEVICE_MAX_PREAUTH_PER_REMOTE`(默认 8;字面 `off` = 显式停用 per-remote 层)= 未鉴权
629
+ * 握手的 per-remote 并发帽(S-04/A-075.52:帽键是裸 remoteAddress——XFF 反代 / 公司 NAT 部署
630
+ * 形会把全员折叠成单键,per-remote 层退化成共享预算而非公平层,`off` + 全局帽 64 是诚实形)。
631
+ * 坏值拒启(#210 A 档),词表与拒因见 config.ts `parseDevicePreAuthPerRemoteEnv`。 */
632
+ maxPreAuthPerRemote?: number | "off";
590
633
  /** `DEVICE_SUPERSEDE`(默认 `stale_only`)= 同 deviceId 二连的顶替策略(§5.2)。 */
591
634
  supersede?: "stale_only" | "deny";
592
635
  } | {
@@ -662,6 +705,19 @@ export interface ServiceConfigFlat {
662
705
  * service and a run-local on the same box agree on one data dir (boot-locked single-instance). Only the
663
706
  * `local` backend reads it. */
664
707
  localDataRoot?: string;
708
+ /**
709
+ * S-46 件B(`RUN_STORE_STRICT_HYDRATE`,普查表第 29 行的**终局格**)—— file 后端的**可选** fail-closed
710
+ * 启动门。`true` ⇒ `FileRunStore.hydrate()` 列不出 `runs/` 时**拒启**(error 级遗言带绝对路径 + 异常
711
+ * 文案,然后抛);缺省 **false** ⇒ 件6 的既有形逐字不变(带病起动 + 一行 error + P-DEBT 计数)。
712
+ *
713
+ * 为什么默认是 off:件6 当年把「不拒启」写成了刻意保留的行为,默认翻面等于替所有存量部署做主。
714
+ * 为什么值得有这根旋钮:空账起动之后每一次 `getRun` 恒 miss,而「恒 miss」与「本来就没有这条 run」
715
+ * 在 wire 上同形(#43 现场就是这么被误读的)—— 把 run 账本当真账本用的部署有权要求「读不出就别起」。
716
+ * 坏值**拒启**(#210 A 档):静默回默认会让 operator 要的 fail-closed 被无声吃掉。
717
+ *
718
+ * 只有 `local`(file)后端读它;SQL 后端的 hydrate 不经这条路。
719
+ */
720
+ runStoreStrictHydrate: boolean;
665
721
  /** PostgreSQL connection (used iff dbBackend === "pg"). Mirror of `tidb`. Env: PG_HOST/PG_PORT/PG_USER/
666
722
  * PG_PASSWORD/PG_DATABASE/PG_POOL_SIZE. */
667
723
  pg?: {
@@ -703,6 +759,15 @@ export interface ServiceConfigFlat {
703
759
  * 实证,大字节归对象存储);`SNAPSHOT_BLOB_ALLOW_SQL_BYTES=true` 显式接受 SQL 店 + (a) 的帽
704
760
  * (单机/测试台形)。严格 "true" 判——这是安全姿势开关,不收宽松真值。 */
705
761
  snapshotBlobAllowSql: boolean;
762
+ /** S-15 twin 保留条款(core design/381 §片2):per-file rewind 历史的**边界上限**,键形与 core
763
+ * `FileStorageBackendOptions.fileHistory.retention.keep` **同域同名**(local 腿原样透传给 core 的 File 店;
764
+ * SQL 双生店构造期同调 core `resolveFileHistoryRetention` —— 一套语义,三条车道)。缺席 = core 缺省
765
+ * (100,由 core 兑现,本仓不复制那个数);`{ keep: "unbounded" }` 是唯一的「不剪」拼法。坏值在
766
+ * loadConfig 就以 core 的 `config.retention_policy_invalid` 拒(运维接线处),绝不静默回落。
767
+ * env:`FILE_HISTORY_RETENTION_KEEP`。 */
768
+ fileHistory?: {
769
+ retention?: FileHistoryRetentionPolicy;
770
+ };
706
771
  /** plugins 域 clone host 白名单(`PLUGINS_ALLOW_HOSTS`,缺省 github.com;契约 [1361]① 双保险面,
707
772
  * 消费在 capabilities/center-plugins.ts)。 */
708
773
  pluginsAllowHosts: string[];
@@ -718,8 +783,8 @@ export interface ServiceConfigFlat {
718
783
  * 复审 F10:同 tick 邻居(workflowOrphanGraceMs/attachmentUnboundTtlMs/scratchpadSweepTtlMs)都有
719
784
  * 旋钮——存量对象巨大的部署要调慢/停掉,不该被迫取消 MinIO 配置(那会连带关掉整个附件面)。 */
720
785
  attachmentOrphanGraceMs: number;
721
- /** workspace 浏览面(#3,[1894]①)单文件读上限(字节;`WORKSPACE_FILE_MAX_BYTES`,默认 8MiB)。
722
- * 超限=413 结构化信封 {sizeBytes, limit} 指路 archive 口。软旋钮:坏值走默认。 */
786
+ /** ⚠️ INERT since S-15(workspace 浏览面随整树快照纪元退役,消费点归零)。键**保留**:center 热推
787
+ * 可能仍携带它,删键会让整份推送按未知键处置——正式摘除走 settings-schema 的弃用车道。 */
723
788
  workspaceFileMaxBytes: number;
724
789
  /** SendUserFile dual-track public-link issuance (design settled cross-repo 2026-07-14; S3_PUBLIC_* are
725
790
  * [766]② cross-repo contract names — never rename). Enabled iff the adapter-held MinIO/S3 creds are set:
@@ -764,7 +829,7 @@ export interface ServiceConfigFlat {
764
829
  * service-to-service token, else anything in-cluster can submit & bill directly). `ALLOW_UNAUTHED_WRITES`.
765
830
  * 有意设计(文档化):本开关只豁免「零凭证形态」的 fail-closed 503;一旦配了凭证
766
831
  * (SERVICE_AUTH_TOKEN 或 TOKENS 名录),凭证必须使用——unauthed 仍 401,两 env 不叠加
767
- * (半配置绕行=后门,刻意优先级)。 */
832
+ * (半配置绕行=隐藏入口,刻意优先级)。 */
768
833
  allowUnauthedWrites?: boolean;
769
834
  /** Optional READ-ONLY token that ALSO authorizes GET /metrics + /metrics/summary (in addition to
770
835
  * authToken). Set the SAME value fleet-wide so a control plane (sema-registry) can pull every worker's
@@ -893,7 +958,7 @@ export interface ServiceConfigFlat {
893
958
  * 它是**零进展 slice 循环**的兜底(两条总额才是主约束),与重启环的 `maxSuspends` 是两个独立计数。
894
959
  * core 的 JSDoc 逐字点名了本仓这个角色:"a host driving resumes unattended **should set this**"。
895
960
  *
896
- * 🔴 **它与两条 totals 的冻结语义不同**(codex 对抗复审 R1-[medium],验真后改口):core **不把它写进
961
+ * 🔴 **它与两条 totals 的冻结语义不同**(codex 交叉复审 R1-[medium],验真后改口):core **不把它写进
897
962
  * 账本**(`debitLedger` 的返回形里没有这一格),每片现读 `spec.resourceSuspend.maxSlices` 去比账本上
898
963
  * 累计的 `sliceCount`。⇒ 本仓在**每一片**(含续跑)供值是**载荷不是冗余**:只在首片供值等于让续跑腿
899
964
  * 供 `undefined` = 上限当场消失。代价如实登记:滚动部署改这根 env,已 park 的链按**新**值判 —— 部署
@@ -1081,20 +1146,30 @@ export interface ServiceConfigFlat {
1081
1146
  * Running rows are never reaped (that is the orphan-grace sweep's job). Default 30 days, floor 1m.
1082
1147
  * `WORKFLOW_RUN_RETENTION_MS`. */
1083
1148
  workflowRunRetentionMs: number;
1084
- /** design/166-T1 — core 5.8.0 部署级 token 治理窗(`RunnerDeps.usageWindows`):滑动窗内跨任务
1085
- * token 预算,key=principal(缺席=GLOBAL 一口锅,per-key 各一份额度——混流部署语义见 USAGE.md)。
1086
- * `USAGE_WINDOWS` 单键 JSON 数组(每项 {windowMs,maxTokens,anchor:"first-use"|"rolling"});
1087
- * 缺省未设=关(零行为变化)。fail-loud:坏 JSON/空数组/非正整数/未知 anchor boot throw
1088
- * (parseUsageWindows)。窗形变更≈重开账(first-use 桶按 windowMs 配对,老桶下次 charge 即弃)。 */
1149
+ /** design/166-T1 — core 5.8.0 部署级治理窗(`RunnerDeps.usageWindows`):滑动窗内跨任务预算,
1150
+ * key=principal(缺席=GLOBAL 一口锅,per-key 各一份额度——混流部署语义见 USAGE.md)。
1151
+ * `USAGE_WINDOWS` 单键 JSON 数组(每项 {windowMs, maxTokens?, maxCostUsd?, anchor});
1152
+ * 缺省未设=关(零行为变化)。S-18(core 7.0.0):`maxCostUsd` 是与 maxTokens **对等**的第二只
1153
+ * ceiling(绝对美元;whichever fills first 即耗尽;缺席的 ceiling 不参与——非 0、非 )——两者
1154
+ * each optional on its own、**at least one required**(双缺=parseUsageWindows 响亮拒)。fail-loud:
1155
+ * 坏 JSON/空数组/坏值/未知 anchor → boot throw(parseUsageWindows)。窗形变更≈重开账(first-use
1156
+ * 桶按 windowMs 配对,老桶下次 charge 即弃)。$ 窗要求 run 可计价:无价目模型撞 $ 窗=core 门口拒
1157
+ * (`config.usage_window_unpriced`),USAGE.md §9.2。 */
1089
1158
  usageWindows?: readonly {
1090
1159
  windowMs: number;
1091
- maxTokens: number;
1160
+ maxTokens?: number;
1161
+ maxCostUsd?: number;
1092
1162
  anchor: "first-use" | "rolling";
1093
1163
  }[];
1094
1164
  /** core 1.364 durable background agents([1503] 提货单③):reaper 经 `TaskRegistry.reapDurableAgents`
1095
1165
  * (联合 reap——条件删赢了才 release 转录 session;裸 store.reap 会 strand 转录)按 scope 清 TERMINAL
1096
1166
  * `a*` 行,老于此值的删。SQL twins 走 DISTINCT-scope 枚举;local FileBackgroundAgentStore 单租户
1097
- * scope="default"。Default 7 days, floor 1m。`BG_AGENT_RETENTION_MS`。 */
1167
+ * scope="default"。Default 7 days, floor 1m。`BG_AGENT_RETENTION_MS`。
1168
+ * 🔴 S-03(#292②,设计判定 2026-08-30):这根旋钮**同时是子代转录(placed 分区)的生效留存视界**——
1169
+ * 联合 reap 行赢删才 release 转录,分区孤儿腿 `olderThanMs` 同源(core `reapDurableAgentsLane` 亲核),
1170
+ * retention lane 对 placed 显式让权(retention-store-sql.ts 第五腿)。core 词表常量
1171
+ * `SUBAGENT_TRANSCRIPT_RETENTION_DAYS_DEFAULT`(30d)在 core dist 零消费,本服务未按它跑;天数形
1172
+ * 专用旋钮无 core policy 座,候 core 扩座后再铸(生效值已投 wiring_static 日志)。 */
1098
1173
  backgroundAgentRetentionMs: number;
1099
1174
  /** 同批:stale-running 翻转窗——running 行的写者心跳(core 60s)断更超过此值 = 宿主进程死,行翻
1100
1175
  * failed/stoppedBy=system(诚实终态,跨实例读面永不虚构存活)。core 契约要求 ≥10min(60s 心跳的
@@ -1289,7 +1364,7 @@ export interface ServiceConfigFlat {
1289
1364
  * 放行、不会减**,而车二**没有撤销面**——列出 / 删除自己已存规则的口子没建,core 的
1290
1365
  * `removePersistedRule` 在包里但本仓零调用点。一次误导入的全局规则因此会一直生效,租户自己拿它没
1291
1366
  * 办法,只能找运维改库。#203 交付的正是那条撤销面本身(`GET /v1/rules` + `DELETE /v1/rules`,后者
1292
- * 恒经 `removePersistedRule` 产墓碑)加上 operator 的越权回收腿 ⇒ **前置条件兑现,默认翻转**。
1367
+ * 恒经 `removePersistedRule` 产墓碑)加上 operator 的超范围回收腿 ⇒ **前置条件兑现,默认翻转**。
1293
1368
  * 记这段史不是怀旧:哪天有人想把某条「只加放行」的车道默认打开,判据就是这一条 —— 收得回来才敢开。
1294
1369
  *
1295
1370
  * 显式 `PERMISSION_RULES_ENABLED=false` 仍然关得掉(默认翻转不吃部署侧的表态);升级到本版的既有
@@ -1357,7 +1432,7 @@ export interface ServiceConfigFlat {
1357
1432
  * 🔴 **射程边界(成文,不是遗漏)**:
1358
1433
  * · 唯一让本旋钮**无处施加**的键是 `TOOL_APPROVAL_ENABLED=false`(协调器整体不构造,core 走它自己的
1359
1434
  * headless 姿态)。**不造推断腿**:旋钮只在协调器在场时有意义,这一句是成文而不是代码分支。
1360
- * · ⚠️ **与 `STREAM_APPROVAL_ENABLED` 正交**(codex 对抗复审 R2-F2 验真后纠正 —— 本注上一版把两者
1435
+ * · ⚠️ **与 `STREAM_APPROVAL_ENABLED` 正交**(codex 交叉复审 R2-F2 验真后纠正 —— 本注上一版把两者
1361
1436
  * 写成了一回事,是错的):协议开关关掉只是不注入 `askStore`/协议窗,协调器照常在场,于是 R-13 的
1362
1437
  * D1 窗到期臂**照样**走 park 路由。这不是漏网:设计稿 §2 明写 park 的承载是 **core checkpoint**
1363
1438
  * 而不是 server 的 ask 行,「无 askStore 但有 durable 设施」正是 R-13 点名的**主受益部署**。
@@ -1382,7 +1457,7 @@ export interface ServiceConfigFlat {
1382
1457
  workflowAgentsReadOnly: boolean;
1383
1458
  /** Sandbox-image-pool BAKE job API (IMAGE-API-DESIGN.md §P2): enable /v1/images/bakes* (operator-submit + the
1384
1459
  * bake-runner claim/ingest control plane). Requires a TiDB pool (the durable bake row/event/lease tables) AND
1385
- * a NON-EMPTY `operatorPrincipals` (boot-asserted — never a world-writable build-host-RCE door). Default OFF.
1460
+ * a NON-EMPTY `operatorPrincipals` (boot-asserted — never an unrestricted-write build-host exec door). Default OFF.
1386
1461
  * `IMAGE_BAKES_ENABLED=true`. */
1387
1462
  imageBakes: ImageBakeConfig;
1388
1463
  /** prove-before-scale stopgap: log every tool call (pre+post, incl. resumed legs) for diagnosis. `TOOL_TRACE=true`. Default OFF. */
@@ -1390,7 +1465,7 @@ export interface ServiceConfigFlat {
1390
1465
  /** Task Trace API (S1): persist the agent's reasoning/thinking into the durable run event log (for the CC-style
1391
1466
  * trace UI). Default ON; `TRACE_THINKING=false` disables capture for sensitive deployments. Redacted on persist. */
1392
1467
  traceThinking: boolean;
1393
- /** #246(core 5.30.0 readFace 部署座席):READ 容纳面。缺席=不铸(core 引擎默认 roots,server 不钉死
1468
+ /** #246(core 5.30.0 readFace 部署座席):READ 容纳面。缺席=不铸(core 引擎默认 roots,server 不写死
1394
1469
  * 上游默认);显式坏词启动期响亮拒。只影响读面,写面永不受它管(core 契约)。 */
1395
1470
  readFace?: "open" | "roots";
1396
1471
  /** #246:部署级 READ deny 追加表(additive-only,core 内建表永不可缩)。env=JSON 数组
@@ -1509,14 +1584,14 @@ export interface ServiceConfigFlat {
1509
1584
  configLocalDir?: string;
1510
1585
  }
1511
1586
  /** 组:store(持久化)—— DB 引擎三态、session 后端、SQL coords、快照 BLOB / SendUserFile 对象存储。 */
1512
- export type ServiceStoreConfig = Pick<ServiceConfigFlat, "sessionBackend" | "sessionCacheTtlSec" | "storeProbeIntervalMs" | "rewindSnapshotMaxMb" | "dbBackend" | "dbBackendExplicit" | "localDataRoot" | "tidb" | "pg" | "dbQueryTimeoutMs" | "snapshotBlobStore" | "snapshotBlobSqlMaxBytes" | "snapshotBlobAllowSql" | "sendUserFile">;
1587
+ export type ServiceStoreConfig = Pick<ServiceConfigFlat, "sessionBackend" | "sessionCacheTtlSec" | "storeProbeIntervalMs" | "dbBackend" | "dbBackendExplicit" | "localDataRoot" | "runStoreStrictHydrate" | "tidb" | "pg" | "dbQueryTimeoutMs" | "snapshotBlobStore" | "snapshotBlobSqlMaxBytes" | "snapshotBlobAllowSql" | "fileHistory" | "sendUserFile">;
1513
1588
  /** 组:modelPlane(模型面)—— 网关坐标、Anthropic 路线、韧性旋钮、主/廉价 model entry、role 表、降级梯。 */
1514
1589
  export type ServiceModelPlaneConfig = Pick<ServiceConfigFlat, "gatewayBaseUrl" | "gatewayApiKey" | "gatewayFallbackUrls" | "gatewayMaxRetries" | "anthropic" | "resilience" | "model" | "models" | "modelApiKeyEnv" | "modelApiKeys" | "modelQuotaWeights" | "tiers" | "projects" | "roles" | "atModelAllowlist" | "cascadeLadder" | "degrade">;
1515
1590
  /** 组:approval(审批 / HITL 门)。`directDoorActive` 无 env 解析腿(装配层三域合取的产物),但语义上
1516
1591
  * 就是本组的门状态,故进组;`parseApprovalDomain` 的返回类型相应是 `Omit<…, "directDoorActive">`。 */
1517
1592
  export type ServiceApprovalConfig = Pick<ServiceConfigFlat, "approvalRequire" | "approvalDeny" | "approvalTimeoutSec" | "approvalAutoBudget" | "approvalNeverAuto" | "approvalHmacKeys" | "durableApproval" | "directApprovalDoor" | "directDoorActive" | "resourceSuspend" | "resourceSuspendTtlSec" | "resourceSuspendTotalTokens" | "resourceSuspendTotalBudgetUsd" | "resourceSuspendMaxSlices" | "askQuestionEnabled" | "questionThrottle" | "toolApprovalEnabled" | "permissionRulesEnabled" | "permissionRulesEnabledExplicit" | "streamAskWindowMarginMs" | "streamApproval" | "unattendedApprovalPolicy" | "mcpElicitation" | "sensitiveWritePatterns" | "manualModeShellGate">;
1518
1593
  /** 组:memory(记忆面 + TOC 同步腿)。 */
1519
- export type ServiceMemoryConfig = Pick<ServiceConfigFlat, "memoryEngineEnabled" | "memoryEngineDir" | "memoryEngineRemoteLaneAllowed" | "memoryEngineBackend" | "memoryScope" | "memoryPersistenceCapable" | "memorySync" | "memoryEmbedder" | "memoryOrgAdmissionMode" | "memoryDelegationEvidence" | "memoryProvenance" | "memoryConsolidationDriver" | "memoryOrgDirectoryJson" | "memoryOrgGrantTtlMs" | "memoryOrgUnavailableBackoffMs" | "projectMemoryEnabled" | "syncImportLeaseStaleSec">;
1594
+ export type ServiceMemoryConfig = Pick<ServiceConfigFlat, "memoryEngineEnabled" | "memoryEngineDir" | "memoryEngineRemoteLaneAllowed" | "memoryEngineBackend" | "memoryScope" | "memoryPersistenceCapable" | "memorySync" | "memoryEmbedder" | "memoryOrgAdmissionMode" | "memoryDelegationEvidence" | "memoryProvenance" | "memoryCapturePolicy" | "memoryConsolidationDriver" | "memoryOrgDirectoryJson" | "memoryOrgGrantTtlMs" | "memoryOrgUnavailableBackoffMs" | "projectMemoryEnabled" | "syncImportLeaseStaleSec">;
1520
1595
  /** 组:auth(鉴权 / 身份 / 治理棒)。`commandPolicy` 只有 sema-registry 腿(无 env 标量形),故 env 解析
1521
1596
  * 函数不产出它,但它与 `autonomy` 是同一根治理棒的两半,归本组。
1522
1597
  * design/170 的三件部署治理声明(`compliancePosture`/`lockedConfigKeys`/`retentionPolicy`)同归本组:
package/dist/config.d.ts CHANGED
@@ -15,11 +15,19 @@ export { RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC } from "./config
15
15
  export declare function parseAutonomy(raw: string | undefined): Autonomy | undefined;
16
16
  /** design/166-T1 — parse `USAGE_WINDOWS`(单键 JSON 数组;逐窗散键在窗数可变时组合爆炸,JSON 是唯一
17
17
  * 封闭形)。unset/空串 → undefined(关)。fail-loud 族(numEnv 同纪律):坏 JSON/非数组/空数组/
18
- * 非正有限整数/未知 anchor 全部 boot throw 点名——「设了但无意义」静默关掉=部署方以为有治理实际没有。
19
- * server 严于 core(core 允 maxTokens=0=恒拒锅;这里要求正整数,0 配置显然是笔误不是意图)。 */
18
+ * 坏值/未知 anchor 全部 boot throw 点名——「设了但无意义」静默关掉=部署方以为有治理实际没有。
19
+ * S-18(core 7.0.0 `UsageWindow.maxCostUsd`,dist/core/usage-window-store.d.ts:44-68 亲读):
20
+ * `maxTokens` 与 `maxCostUsd` 是两只**独立** ceiling——each optional on its own、**at least one
21
+ * required**(双缺=拒:a window with neither admits nothing it can govern),whichever fills first
22
+ * 即耗尽;缺席的 ceiling 不参与(非 0、非 ∞)。修前这里的白名单重铸只认三键,$ 键配了也被静默
23
+ * 剥掉——正是本函数要消灭的那族病自己。
24
+ * server 严于 core(core 允两 ceiling 取 0=恒满锁死窗;这里都要求正数,0 配置显然是笔误不是意图);
25
+ * `maxCostUsd` 另提前执行 core 的 micro-USD 门(×1e6 仍有限——超界 ceiling 在账本单位里读作 ∞,
26
+ * 「看起来被治理、实际什么都不拦」,core 在门口拒,这里拒在 boot)。 */
20
27
  export declare function parseUsageWindows(raw: string | undefined): readonly {
21
28
  windowMs: number;
22
- maxTokens: number;
29
+ maxTokens?: number;
30
+ maxCostUsd?: number;
23
31
  anchor: "first-use" | "rolling";
24
32
  }[] | undefined;
25
33
  /** Numeric env with validation: a non-numeric value FAILS at startup instead of silently becoming `NaN`
package/dist/config.js CHANGED
@@ -2,7 +2,7 @@ import { readFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { BUILTIN_COMPLIANCE_DENIES, COMPLIANCE_CAPABILITIES, compileReadDeny, resolveReadDenyBuiltins, CODE_AGENT_PROMPT, formatUserScope, isThinkingLevel, PROTOCOL_TABLE, protocolOf, RECOMMENDED_SENSITIVE_PATTERNS, RETIRED_TOOL_NAMES, resolveLockedKeys, validatePermissionRules } from "@sema-agent/core";
5
+ import { BUILTIN_COMPLIANCE_DENIES, COMPLIANCE_CAPABILITIES, compileReadDeny, resolveReadDenyBuiltins, CODE_AGENT_PROMPT, formatUserScope, isThinkingLevel, PROTOCOL_TABLE, protocolOf, RECOMMENDED_SENSITIVE_PATTERNS, RETIRED_TOOL_NAMES, resolveFileHistoryRetention, resolveLockedKeys, validatePermissionRules } from "@sema-agent/core";
6
6
  import { ROSTER_PRIMARY_ROLES, ROSTER_CHEAP_ROLES } from "@sema-agent/settings-schema";
7
7
  import { parseApprovalHmacKeys, parsePrincipalJwks } from "./auth-keys.js";
8
8
  import { DEFAULT_ELICITATION_THROTTLE } from "./elicitation.js";
@@ -38,18 +38,38 @@ export function parseUsageWindows(raw) {
38
38
  parsed = JSON.parse(v);
39
39
  }
40
40
  catch (e) {
41
- throw new Error(`env USAGE_WINDOWS is not valid JSON: ${String(e)} — expected e.g. [{"windowMs":18000000,"maxTokens":5000000,"anchor":"first-use"}]`);
41
+ throw new Error(`env USAGE_WINDOWS is not valid JSON: ${String(e)} — expected e.g. [{"windowMs":18000000,"maxTokens":5000000,"anchor":"first-use"}] or [{"windowMs":604800000,"maxCostUsd":25,"anchor":"rolling"}]`);
42
42
  }
43
43
  if (!Array.isArray(parsed) || parsed.length === 0) {
44
- throw new Error(`env USAGE_WINDOWS must be a NON-EMPTY JSON array of {windowMs,maxTokens,anchor} (an empty array is a governance no-op nobody chose — unset the env to turn windows off)`);
44
+ throw new Error(`env USAGE_WINDOWS must be a NON-EMPTY JSON array of {windowMs, maxTokens?, maxCostUsd?, anchor} (an empty array is a governance no-op nobody chose — unset the env to turn windows off)`);
45
45
  }
46
46
  const out = parsed.map((w, i) => {
47
47
  const o = w;
48
48
  const posInt = (n) => typeof n === "number" && Number.isInteger(n) && n > 0;
49
- if (typeof w !== "object" || w === null || !posInt(o.windowMs) || !posInt(o.maxTokens) || (o.anchor !== "first-use" && o.anchor !== "rolling")) {
50
- throw new Error(`env USAGE_WINDOWS[${i}] is invalid: expected {windowMs: positive int ms, maxTokens: positive int, anchor: "first-use"|"rolling"} (got ${JSON.stringify(w)})`);
49
+ const posUsd = (n) => typeof n === "number" && Number.isFinite(n) && n > 0 && Number.isFinite(n * 1e6);
50
+ if (typeof w !== "object" || w === null || !posInt(o.windowMs) || (o.anchor !== "first-use" && o.anchor !== "rolling")) {
51
+ throw new Error(`env USAGE_WINDOWS[${i}] is invalid: expected {windowMs: positive int ms, maxTokens?: positive int, maxCostUsd?: positive USD, anchor: "first-use"|"rolling"} (got ${JSON.stringify(w)})`);
51
52
  }
52
- return Object.freeze({ windowMs: o.windowMs, maxTokens: o.maxTokens, anchor: o.anchor });
53
+ for (const k of Object.keys(w)) {
54
+ if (k !== "windowMs" && k !== "maxTokens" && k !== "maxCostUsd" && k !== "anchor") {
55
+ throw new Error(`env USAGE_WINDOWS[${i}] is invalid: unknown key \`${k}\` — the window shape is the CLOSED set {windowMs, maxTokens?, maxCostUsd?, anchor}; a misspelled ceiling (e.g. \`maxCostUSD\`) must not silently leave the deployment ungoverned (got ${JSON.stringify(w)})`);
56
+ }
57
+ }
58
+ if (o.maxTokens === undefined && o.maxCostUsd === undefined) {
59
+ throw new Error(`env USAGE_WINDOWS[${i}] is invalid: at least one ceiling is required — \`maxTokens\` (tokens) and/or \`maxCostUsd\` (absolute USD); a window with neither admits nothing it can govern (got ${JSON.stringify(w)})`);
60
+ }
61
+ if (o.maxTokens !== undefined && !posInt(o.maxTokens)) {
62
+ throw new Error(`env USAGE_WINDOWS[${i}] is invalid: \`maxTokens\` (when present) must be a positive integer (got ${JSON.stringify(w)})`);
63
+ }
64
+ if (o.maxCostUsd !== undefined && !posUsd(o.maxCostUsd)) {
65
+ throw new Error(`env USAGE_WINDOWS[${i}] is invalid: \`maxCostUsd\` (when present) must be a positive finite USD amount that stays finite in micro-USD (× 1e6) (got ${JSON.stringify(w)})`);
66
+ }
67
+ return Object.freeze({
68
+ windowMs: o.windowMs,
69
+ ...(o.maxTokens !== undefined ? { maxTokens: o.maxTokens } : {}),
70
+ ...(o.maxCostUsd !== undefined ? { maxCostUsd: o.maxCostUsd } : {}),
71
+ anchor: o.anchor,
72
+ });
53
73
  });
54
74
  return Object.freeze(out);
55
75
  }
@@ -252,6 +272,35 @@ function optNumEnvStrict(name) {
252
272
  return undefined;
253
273
  return parseNumOrFail(name, raw);
254
274
  }
275
+ function parseDevicePreAuthPerRemoteEnv() {
276
+ const raw = process.env.DEVICE_MAX_PREAUTH_PER_REMOTE;
277
+ if (raw === undefined || raw === "")
278
+ return undefined;
279
+ if (raw === "off")
280
+ return "off";
281
+ if (!/^[1-9]\d*$/.test(raw)) {
282
+ throw new Error(`env DEVICE_MAX_PREAUTH_PER_REMOTE=${JSON.stringify(raw)} must be a positive integer or the literal "off" — ` +
283
+ `a malformed cap would otherwise run as the default (8) while the operator believes their value is in force ` +
284
+ `(#210: deployment knobs never fall back silently). "0" is not a valid cap (it would refuse every device handshake); ` +
285
+ `use "off" to disable the per-remote layer explicitly (reverse-proxy / NAT deployments collapse source IPs, A-075.52).`);
286
+ }
287
+ const n = Number(raw);
288
+ if (!Number.isSafeInteger(n)) {
289
+ throw new Error(`env DEVICE_MAX_PREAUTH_PER_REMOTE=${raw.length > 24 ? `${raw.slice(0, 24)}…(${raw.length} digits)` : raw} overflows the safe-integer range — ` +
290
+ `Number() would read it as ${n}, and a cap of Infinity silently disables the per-remote layer without taking the explicit "off" arm. ` +
291
+ `Use a value within Number.MAX_SAFE_INTEGER, or the literal "off".`);
292
+ }
293
+ return n;
294
+ }
295
+ function parseFileHistoryRetention() {
296
+ const raw = process.env.FILE_HISTORY_RETENTION_KEEP;
297
+ if (raw === undefined || raw === "")
298
+ return undefined;
299
+ const keep = raw === "unbounded" ? "unbounded" : /^(?:0|[1-9]\d*)$/.test(raw) ? Number(raw) : raw;
300
+ const policy = { keep };
301
+ resolveFileHistoryRetention("FILE_HISTORY_RETENTION_KEEP", policy);
302
+ return { retention: policy };
303
+ }
255
304
  function optFinitePositiveEnv(name) {
256
305
  const raw = process.env[name];
257
306
  if (raw === undefined || raw === "")
@@ -503,10 +552,10 @@ function parseStoreDomain(ctx) {
503
552
  sessionBackend,
504
553
  sessionCacheTtlSec: numEnv("SESSION_CACHE_TTL_SEC", "300"),
505
554
  storeProbeIntervalMs: numEnvBounded("STORE_PROBE_INTERVAL_MS", "15000", 0, 3_600_000),
506
- rewindSnapshotMaxMb: optFinitePositiveEnv("REWIND_SNAPSHOT_MAX_MB"),
507
555
  dbBackend,
508
556
  dbBackendExplicit: dbBackendSet,
509
557
  localDataRoot,
558
+ runStoreStrictHydrate: parseRunStoreStrictHydrate(process.env.RUN_STORE_STRICT_HYDRATE),
510
559
  tidb: needsDb && dbBackend === "mysql"
511
560
  ? {
512
561
  host: env("MYSQL_HOST"),
@@ -541,6 +590,7 @@ function parseStoreDomain(ctx) {
541
590
  : undefined,
542
591
  snapshotBlobSqlMaxBytes: optFinitePositiveEnv("SNAPSHOT_BLOB_SQL_MAX_BYTES"),
543
592
  snapshotBlobAllowSql: boolEnv("SNAPSHOT_BLOB_ALLOW_SQL_BYTES", false),
593
+ fileHistory: parseFileHistoryRetention(),
544
594
  sendUserFile: (process.env.MINIO_ENDPOINT || process.env.S3_ENDPOINT) && process.env.MINIO_ACCESS_KEY && process.env.MINIO_SECRET_KEY
545
595
  ? {
546
596
  endpoint: (process.env.MINIO_ENDPOINT || process.env.S3_ENDPOINT),
@@ -803,7 +853,7 @@ export function formatUnmatchableToolNames(source, bad) {
803
853
  return (`${source} names tool(s) that can never match a live tool (these lists are RAW whole-name comparisons — core 5.0.0 RB-476 removed the alias/auto-prefix folds): ` +
804
854
  bad.map((b) => `${presentToolName(b.name)} → ${b.guidance}`).join("; ")).replace(CONTROL_AND_BIDI_STRIP_RE, "");
805
855
  }
806
- function streamApprovalConfig() {
856
+ function streamApprovalConfig(gates) {
807
857
  const cfg = {
808
858
  enabled: boolEnv("STREAM_APPROVAL_ENABLED", true),
809
859
  windowMs: numEnv("STREAM_ASK_WINDOW_MS", "300000"),
@@ -814,10 +864,15 @@ function streamApprovalConfig() {
814
864
  pendingGraceMs: numEnvBounded("STREAM_APPROVAL_PENDING_GRACE_MS", "30000", 0, 3_600_000),
815
865
  adhocGraceMs: numEnvBounded("STREAM_APPROVAL_ADHOC_GRACE_MS", "60000", 0, 86_400_000),
816
866
  orphanTtlMs: numEnvBounded("STREAM_APPROVAL_ORPHAN_TTL_MS", String(7 * 24 * 60 * 60 * 1000), 60_000, 90 * 24 * 60 * 60 * 1000),
867
+ unreachedTtlMs: numEnvBounded("UNREACHED_ASK_TTL_MS", String(60 * 60 * 1000), 60_000, 24 * 60 * 60 * 1000),
817
868
  };
818
869
  if (cfg.windowMs + cfg.adhocGraceMs >= cfg.orphanTtlMs) {
819
870
  throw new Error(`env STREAM_ASK_WINDOW_MS=${cfg.windowMs} + STREAM_APPROVAL_ADHOC_GRACE_MS=${cfg.adhocGraceMs} must be STRICTLY LESS than STREAM_APPROVAL_ORPHAN_TTL_MS=${cfg.orphanTtlMs} — the adhoc arm fires at (createdAt + window + grace) while the orphan backstop fires at (createdAt + ttl); if the backstop wins, every adhoc-leg row is attributed "orphan_ttl_exceeded" instead of "adhoc_leg_no_durable_domain" (attribution honesty, design/172 §6.1)`);
820
871
  }
872
+ const suspendedRowsPossible = cfg.enabled && gates.toolApprovalEnabled && gates.unattendedPolicy !== "deny";
873
+ if (suspendedRowsPossible && cfg.unreachedTtlMs + cfg.adhocGraceMs >= cfg.orphanTtlMs) {
874
+ throw new Error(`env UNREACHED_ASK_TTL_MS=${cfg.unreachedTtlMs} + STREAM_APPROVAL_ADHOC_GRACE_MS=${cfg.adhocGraceMs} must be STRICTLY LESS than STREAM_APPROVAL_ORPHAN_TTL_MS=${cfg.orphanTtlMs} — an ask minted with NO live stream (case A: a workflow child ask) waits UNREACHED_ASK_TTL_MS, so its row expires at (createdAt + that window); the orphan backstop fires at (createdAt + ttl). If the backstop wins, every suspended row is attributed "orphan_ttl_exceeded" instead of its real cause (attribution honesty, same rule as the STREAM_ASK_WINDOW_MS line above)`);
875
+ }
821
876
  if (!Number.isInteger(cfg.reconcileBatch)) {
822
877
  throw new Error(`env STREAM_APPROVAL_RECONCILE_BATCH=${cfg.reconcileBatch} must be an INTEGER row count`);
823
878
  }
@@ -836,6 +891,8 @@ function unattendedApprovalPolicyEnv() {
836
891
  }
837
892
  function parseApprovalDomain(ctx) {
838
893
  const { postureOn } = ctx;
894
+ const unattendedPolicy = unattendedApprovalPolicyEnv();
895
+ const toolApprovalEnabled = postureOn("TOOL_APPROVAL_ENABLED");
839
896
  const directApprovalDoor = boolEnv("DIRECT_APPROVAL_DOOR", false);
840
897
  const dgHmacKeys = parseApprovalHmacKeys(process.env.APPROVAL_HMAC_KEYS, announceKeySetRejection("APPROVAL_HMAC_KEYS"));
841
898
  const durableApproval = boolEnv("DURABLE_APPROVAL", false);
@@ -903,12 +960,12 @@ function parseApprovalDomain(ctx) {
903
960
  maxTotalPerRun: numEnvBounded("QUESTION_MAX_TOTAL_PER_RUN", String(DEFAULT_QUESTION_THROTTLE.maxTotalPerRun), 1, 10_000),
904
961
  ttlMs: numEnvBounded("QUESTION_TTL_MS", String(DEFAULT_QUESTION_THROTTLE.ttlMs), 1_000, 3_600_000),
905
962
  },
906
- toolApprovalEnabled: postureOn("TOOL_APPROVAL_ENABLED"),
963
+ toolApprovalEnabled,
907
964
  permissionRulesEnabled: boolEnv("PERMISSION_RULES_ENABLED", true),
908
965
  permissionRulesEnabledExplicit: parseBoolWord("PERMISSION_RULES_ENABLED", process.env["PERMISSION_RULES_ENABLED"]) !== undefined,
909
966
  streamAskWindowMarginMs: numEnv("STREAM_ASK_WINDOW_MARGIN_MS", "10000"),
910
- streamApproval: streamApprovalConfig(),
911
- unattendedApprovalPolicy: unattendedApprovalPolicyEnv(),
967
+ streamApproval: streamApprovalConfig({ unattendedPolicy, toolApprovalEnabled }),
968
+ unattendedApprovalPolicy: unattendedPolicy,
912
969
  approvalTimeoutSec: numEnv("APPROVAL_TIMEOUT_SEC", "0"),
913
970
  };
914
971
  }
@@ -939,6 +996,20 @@ function parseMemoryPersistenceCapable(raw) {
939
996
  `\`# Memory\` write instruction on a remote execution lane) or ${MEMORY_ENGINE_OFF_WORDS.join(" | ")} (it CANNOT — ` +
940
997
  `forces the read-only disclosure). Leave it UNSET to let the engine infer from the mounted roster.`);
941
998
  }
999
+ function parseRunStoreStrictHydrate(raw) {
1000
+ const w = (raw ?? "").trim().toLowerCase();
1001
+ if (w === "")
1002
+ return false;
1003
+ if (MEMORY_ENGINE_ON_WORDS.includes(w))
1004
+ return true;
1005
+ if (MEMORY_ENGINE_OFF_WORDS.includes(w))
1006
+ return false;
1007
+ throw new Error(`env RUN_STORE_STRICT_HYDRATE="${raw}" is not a known on/off word — use one of: ${MEMORY_ENGINE_ON_WORDS.join(" | ")} ` +
1008
+ "(the file run-store REFUSES TO BOOT when it cannot list its runs/ directory) or " +
1009
+ `${MEMORY_ENGINE_OFF_WORDS.join(" | ")} (the default: boot anyway with an empty ledger, disclosed by the ` +
1010
+ "file_run_store_hydrate_unreadable error line + the server.run-store.file-hydrate-unreadable counter). " +
1011
+ "An unrecognized value must not fold to the default: that would silently swallow an operator's explicit fail-closed request.");
1012
+ }
942
1013
  const SANDBOX_PKG_SOURCES = ["global", "cn", "custom", "none"];
943
1014
  function pkgSourceEnv() {
944
1015
  const v = env("SANDBOX_PKG_SOURCE", "global");
@@ -1144,6 +1215,9 @@ function parseMemoryDomain(ctx) {
1144
1215
  ...(process.env.MEMORY_PROVENANCE !== undefined
1145
1216
  ? { memoryProvenance: enumEnv("MEMORY_PROVENANCE", "carry", ["off", "carry"]) }
1146
1217
  : {}),
1218
+ ...(process.env.MEMORY_CAPTURE_POLICY !== undefined
1219
+ ? { memoryCapturePolicy: enumEnv("MEMORY_CAPTURE_POLICY", "open", ["open", "governed", "capture-required"]) }
1220
+ : {}),
1147
1221
  memoryConsolidationDriver: parseMemoryConsolidationDriver(),
1148
1222
  ...(process.env.DELEGATION_MAX_CONCURRENT !== undefined || process.env.DELEGATION_MAX_PER_SESSION !== undefined
1149
1223
  ? {
@@ -1400,6 +1474,7 @@ function parseOrchestrationDomain(ctx) {
1400
1474
  ...((n) => (n !== undefined ? { reconnectGraceMs: n } : {}))(optNumEnvStrict("DEVICE_RECONNECT_GRACE_MS")),
1401
1475
  ...((n) => (n !== undefined ? { execTimeoutMs: n } : {}))(optNumEnvStrict("DEVICE_EXEC_TIMEOUT_MS")),
1402
1476
  ...((n) => (n !== undefined ? { maxInflightPerDevice: n } : {}))(optNumEnvStrict("DEVICE_MAX_INFLIGHT_PER_DEVICE")),
1477
+ ...((n) => (n !== undefined ? { maxPreAuthPerRemote: n } : {}))(parseDevicePreAuthPerRemoteEnv()),
1403
1478
  ...(process.env.DEVICE_SUPERSEDE ? { supersede: enumEnv("DEVICE_SUPERSEDE", "stale_only", ["stale_only", "deny"]) } : {}),
1404
1479
  }
1405
1480
  :
@@ -1669,8 +1744,9 @@ function parseA2aServeSkills(raw) {
1669
1744
  });
1670
1745
  }
1671
1746
  const STORE_GROUP_KEYS = [
1672
- "sessionBackend", "sessionCacheTtlSec", "storeProbeIntervalMs", "rewindSnapshotMaxMb", "dbBackend", "dbBackendExplicit", "localDataRoot",
1673
- "tidb", "pg", "dbQueryTimeoutMs", "snapshotBlobStore", "snapshotBlobSqlMaxBytes", "snapshotBlobAllowSql", "sendUserFile",
1747
+ "sessionBackend", "sessionCacheTtlSec", "storeProbeIntervalMs", "dbBackend", "dbBackendExplicit", "localDataRoot",
1748
+ "runStoreStrictHydrate",
1749
+ "tidb", "pg", "dbQueryTimeoutMs", "snapshotBlobStore", "snapshotBlobSqlMaxBytes", "snapshotBlobAllowSql", "fileHistory", "sendUserFile",
1674
1750
  ];
1675
1751
  const MODEL_PLANE_GROUP_KEYS = [
1676
1752
  "gatewayBaseUrl", "gatewayApiKey", "gatewayFallbackUrls", "gatewayMaxRetries", "anthropic", "resilience", "model", "models",
@@ -1684,7 +1760,7 @@ const APPROVAL_GROUP_KEYS = [
1684
1760
  ];
1685
1761
  const MEMORY_GROUP_KEYS = [
1686
1762
  "memoryEngineEnabled", "memoryEngineDir", "memoryEngineRemoteLaneAllowed", "memoryEngineBackend", "memoryScope",
1687
- "memoryPersistenceCapable", "memorySync", "memoryEmbedder", "memoryOrgAdmissionMode", "memoryDelegationEvidence", "memoryProvenance", "memoryConsolidationDriver", "memoryOrgDirectoryJson", "memoryOrgGrantTtlMs", "memoryOrgUnavailableBackoffMs",
1763
+ "memoryPersistenceCapable", "memorySync", "memoryEmbedder", "memoryOrgAdmissionMode", "memoryDelegationEvidence", "memoryProvenance", "memoryCapturePolicy", "memoryConsolidationDriver", "memoryOrgDirectoryJson", "memoryOrgGrantTtlMs", "memoryOrgUnavailableBackoffMs",
1688
1764
  "projectMemoryEnabled", "syncImportLeaseStaleSec",
1689
1765
  ];
1690
1766
  const AUTH_GROUP_KEYS = [
@@ -1792,7 +1868,7 @@ export function loadConfig() {
1792
1868
  if (dgOperators.length === 0)
1793
1869
  missing.push("OPERATOR_PRINCIPALS (else isOperator is true-for-all)");
1794
1870
  if (!requirePrincipal)
1795
- missing.push("REQUIRE_PRINCIPAL=true (a direct door is multi-tenant; without it an absent principal is anonymous and the spoofable header governs owner/quota checks)");
1871
+ missing.push("REQUIRE_PRINCIPAL=true (a direct door is multi-tenant; without it an absent principal is anonymous and the self-asserted header governs owner/quota checks)");
1796
1872
  if (missing.length) {
1797
1873
  throw new Error(`DIRECT_APPROVAL_DOOR=true but D-G anchors are missing: ${missing.join(", ")} — refusing to start a half-open direct door (design/80 D-G boot invariant)`);
1798
1874
  }
@@ -57,7 +57,7 @@ export interface DurableApprovalSeat {
57
57
  */
58
58
  export declare function createDurableQuestionGate(live: LiveQuestionFace | undefined): ToolPolicy;
59
59
  /**
60
- * 沙箱 lane 上**相对形**写目标的守卫补层用 env(codex 对抗复审 round1 finding 1,红先复现)。
60
+ * 沙箱 lane 上**相对形**写目标的守卫补层用 env(codex 交叉复审 round1 finding 1,红先复现)。
61
61
  *
62
62
  * 缺口:沙箱 lane 的守卫策略拿不到 `rootPath`(沙箱 cwd 不是 server 能猜的,#165 裁定 1),而
63
63
  * `DeferredSandboxPathEnv.absolutePath` 对相对形一律报错。core 的 `canonicalizeTarget` 在
@@ -52,7 +52,7 @@ export interface DeviceEnrollRateLimiter {
52
52
  export interface EnrollDeviceInput {
53
53
  /** 明文 token(**只**在这里出现,落库前立刻摘要)。 */
54
54
  token: string;
55
- /** 请求方**验证过**的复合身份(SSO/凭据链产出,恒不取自可伪造头)。 */
55
+ /** 请求方**验证过**的复合身份(SSO/凭据链产出,恒不取自可自造头)。 */
56
56
  requester: DeviceOwner;
57
57
  /** 🔴 `deviceId` 与 `owner` **不在**这里:前者恒由 server 铸(调用方选 id = 让请求方挑自己的设备身份,
58
58
  * 与 §6「server 铸 dev_<ulid>」相悖),后者恒取自**验证过的** requester —— 两个字段都不接受请求体供给。
@@ -76,7 +76,7 @@ export interface AdmitSessionValue {
76
76
  /**
77
77
  * 判决**当刻**的连接世代(§4.6 fencing token)。
78
78
  *
79
- * 🔴 **准入判决是一个快照,不是一把锁**(codex 对抗轮 F4,判真但归属下一车):`admitSession` 返回后、
79
+ * 🔴 **准入判决是一个快照,不是一把锁**(codex 交叉轮 F4,判真但归属下一车):`admitSession` 返回后、
80
80
  * 调用方真正下发指令前,一次 `revokeDevice` 可以提交 —— 它会作废租约并把 `conn_generation` 推进一格,
81
81
  * 但它**追不回**已经基于旧判决发出去的指令。这不是本层能关掉的窗(§6 注③ 刻意不落指令表,所以持久层
82
82
  * 没有「派发」这个写点可以带谓词);设计给的答案是**在下发边界重新断言世代**(§4.6 步 2/3:每一次带
@@ -146,7 +146,7 @@ export declare function assertIssueTokenShape(input: IssueEnrollTokenInput): voi
146
146
  export declare function assertRevokeShape(input: RevokeDeviceInput): void;
147
147
  export declare function assertAcquireShape(input: AcquireConnectionInput): void;
148
148
  /** token 摘要必须是**规范形**(小写 sha256 hex):大小写混形在 VARBINARY/COLLATE "C" 下是两个键,
149
- * 于是同一个明文 token 能被消费两次 —— 这是 §7-T5 ③「一次性 CAS」的直接绕过面。 */
149
+ * 于是同一个明文 token 能被消费两次 —— 这是 §7-T5 ③「一次性 CAS」的直接跳过面。 */
150
150
  export declare function assertEnrollTokenHashShape(tokenHash: string): void;
151
151
  /**
152
152
  * 闭集列的**读侧**解码 —— 三个 `parse*` 是这些词表的唯一解码点(SQL 店读回的字符串必须经此)。
@@ -154,7 +154,7 @@ export declare function assertEnrollTokenHashShape(tokenHash: string): void;
154
154
  * 🔴 认不出的值 = **抛**,不是静默 cast(#157:安全轴的未知词是 fail-loud 而不是 fail-open)。
155
155
  * 一行 `status='disabled'` 的设备如果被 `as DeviceStatus` 静默放行,准入链的 `status === "revoked"`
156
156
  * 判据就对它返回「不是吊销的」⇒ 一台被管理面停用的设备照常领活。写侧不可能产出这些值(入参是
157
- * TS 闭集),所以出现即「有人绕过本店直接写库」或「跨版本降级读新词」,两者都必须响亮。
157
+ * TS 闭集),所以出现即「有人跳过本店直接写库」或「跨版本降级读新词」,两者都必须响亮。
158
158
  */
159
159
  export declare function parseDeviceStatus(v: string): DeviceStatus;
160
160
  export declare function parseDevicePlatformOs(v: string): DevicePlatformOs;
@@ -189,7 +189,7 @@ export interface IssueEnrollTokenInput {
189
189
  }
190
190
  export interface ConsumeEnrollTokenInput {
191
191
  tokenHash: string;
192
- /** 请求方**验证过**的复合身份(不取自可伪造头);与 token 的 target 逐字节比对。 */
192
+ /** 请求方**验证过**的复合身份(不取自可自造头);与 token 的 target 逐字节比对。 */
193
193
  requester: DeviceOwner;
194
194
  device: NewDeviceInput;
195
195
  }
@@ -151,8 +151,10 @@ export interface DeviceWsHubOptions {
151
151
  chunkBufferMaxBytes?: number;
152
152
  /** codex R1-F3:本副本**未鉴权**连接的并发帽(超额在 upgrade 阶段就 503 拒)。 */
153
153
  maxPreAuthConnections?: number;
154
- /** codex R1-F3:单来源地址的未鉴权连接并发帽。 */
155
- maxPreAuthConnectionsPerRemote?: number;
154
+ /** codex R1-F3:单来源地址的未鉴权连接并发帽。S-04(A-075.52):字面 `off` = 显式停用该层——
155
+ * 帽键是裸 remoteAddress,XFF 反代 / 公司 NAT 部署形把全员折叠成单键,这层从公平层退化成
156
+ * 「全员共享一份预算」;停层后全局帽(`maxPreAuthConnections`)照常兜底。 */
157
+ maxPreAuthConnectionsPerRemote?: number | "off";
156
158
  }
157
159
  export interface DeviceWsHub {
158
160
  /** 把 `GET /v1/device/ws` 的 upgrade 处理器挂到裸 `http.Server` 上。 */