@sema-agent/server 7.145.0 → 7.146.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 (275) hide show
  1. package/MIGRATION.md +492 -0
  2. package/USAGE.md +218 -47
  3. package/deploy/facts/CHANGES.json +25 -0
  4. package/deploy/facts/README.md +57 -0
  5. package/deploy/facts/lanes.json +54 -0
  6. package/deploy/facts/lanes.schema.json +57 -0
  7. package/deploy/facts/sandbox-rbac.json +113 -0
  8. package/deploy/facts/sandbox-rbac.schema.json +74 -0
  9. package/deploy/facts/sealed-key.json +9 -0
  10. package/deploy/facts/sealed-key.schema.json +52 -0
  11. package/deploy/sema-up/chart/templates/_helpers.tpl +19 -0
  12. package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +47 -7
  13. package/deploy/sema-up/chart/templates/server-v4pro.yaml +4 -0
  14. package/deploy/sema-up/chart/templates/server.yaml +4 -0
  15. package/deploy/sema-up/chart/values.yaml +7 -0
  16. package/deploy/sema-up/stack/sema-stack-cluster.yaml +30 -14
  17. package/dist/adoption/import-limits.d.ts +6 -0
  18. package/dist/adoption/import-limits.js +7 -0
  19. package/dist/adoption/import.d.ts +62 -0
  20. package/dist/adoption/import.js +107 -0
  21. package/dist/adoption/wire.d.ts +52 -0
  22. package/dist/adoption/wire.js +9 -0
  23. package/dist/approval-ask-machine.d.ts +2 -2
  24. package/dist/approval-card.d.ts +3 -12
  25. package/dist/approval-card.js +0 -4
  26. package/dist/approval-gate-currency.d.ts +1 -1
  27. package/dist/approval-gate-currency.js +1 -1
  28. package/dist/approval-reconciler.d.ts +1 -1
  29. package/dist/boot/deployment-mods.d.ts +11 -1
  30. package/dist/boot/deployment-mods.js +15 -2
  31. package/dist/boot/device-lane.js +2 -0
  32. package/dist/boot/engine-lease.d.ts +193 -21
  33. package/dist/boot/engine-lease.js +146 -20
  34. package/dist/boot/execution-env.d.ts +4 -0
  35. package/dist/boot/execution-env.js +12 -0
  36. package/dist/boot/exit-boundary.d.ts +50 -0
  37. package/dist/boot/exit-boundary.js +32 -0
  38. package/dist/boot/exit-last.d.ts +20 -4
  39. package/dist/boot/exit-last.js +21 -4
  40. package/dist/boot/governance-seams.js +1 -0
  41. package/dist/boot/memory-consolidation.d.ts +29 -17
  42. package/dist/boot/memory-consolidation.js +26 -15
  43. package/dist/boot/reapers.d.ts +12 -4
  44. package/dist/boot/reapers.js +39 -15
  45. package/dist/boot/resolve-spec.d.ts +2 -0
  46. package/dist/boot/resolve-spec.js +38 -15
  47. package/dist/boot/runner-deps.js +1 -1
  48. package/dist/boot/session-faces.js +3 -1
  49. package/dist/boot/shutdown.js +27 -9
  50. package/dist/boot/stage-01-config.js +1 -1
  51. package/dist/boot/stage-03-store-backend.d.ts +1 -0
  52. package/dist/boot/stage-03-store-backend.js +2 -2
  53. package/dist/boot/stage-04-stores.d.ts +1 -0
  54. package/dist/boot/stage-05-execution-env.d.ts +3 -0
  55. package/dist/boot/stage-05-execution-env.js +3 -3
  56. package/dist/boot/stage-06-runners.d.ts +3 -0
  57. package/dist/boot/stage-06-runners.js +11 -10
  58. package/dist/boot/stage-07-capability-layer.d.ts +3 -0
  59. package/dist/boot/stage-07-capability-layer.js +7 -2
  60. package/dist/boot/stage-08-reapers.d.ts +3 -0
  61. package/dist/boot/stage-08-reapers.js +3 -3
  62. package/dist/boot/stage-09-leader.d.ts +3 -0
  63. package/dist/boot/stage-10-http-server.d.ts +3 -0
  64. package/dist/boot/stage-10-http-server.js +3 -2
  65. package/dist/boot/stores.d.ts +1 -0
  66. package/dist/boot/stores.js +35 -19
  67. package/dist/boot/workflow-orchestration.d.ts +1 -1
  68. package/dist/boot/workflow-orchestration.js +2 -1
  69. package/dist/capabilities/composite-members.d.ts +3 -1
  70. package/dist/capabilities/composite-members.js +3 -1
  71. package/dist/capabilities/scenarios.d.ts +2 -2
  72. package/dist/capability-facts.d.ts +18 -1
  73. package/dist/capability-facts.js +25 -7
  74. package/dist/checkpoint-ctx-seal-m28.d.ts +1 -1
  75. package/dist/config-catalog.js +13 -4
  76. package/dist/config-provider.d.ts +2 -2
  77. package/dist/config-types.d.ts +43 -4
  78. package/dist/config.d.ts +6 -1
  79. package/dist/config.js +67 -11
  80. package/dist/deleted-session-ids.d.ts +19 -0
  81. package/dist/deleted-session-ids.js +26 -0
  82. package/dist/deploy-facts.d.ts +155 -0
  83. package/dist/deploy-facts.js +94 -0
  84. package/dist/device-ws-hub.d.ts +48 -3
  85. package/dist/device-ws-hub.js +48 -2
  86. package/dist/error-code-family.d.ts +35 -0
  87. package/dist/error-code-family.js +26 -0
  88. package/dist/fleet/drain-entry.d.ts +39 -3
  89. package/dist/fleet/drain-entry.js +6 -0
  90. package/dist/fleet/fleet-bus.d.ts +2 -1
  91. package/dist/hooks/branch-transcript.d.ts +1 -1
  92. package/dist/hooks/branch-transcript.js +1 -0
  93. package/dist/hooks/classic-stations.d.ts +25 -8
  94. package/dist/hooks/classic-stations.js +29 -3
  95. package/dist/hooks/hook-runner.d.ts +33 -16
  96. package/dist/hooks/hook-runner.js +166 -73
  97. package/dist/hooks/hook-source.d.ts +14 -2
  98. package/dist/hooks/hook-source.js +8 -0
  99. package/dist/http/active-run-conflict.d.ts +1 -1
  100. package/dist/http/active-run-conflict.js +1 -1
  101. package/dist/http/admission.js +11 -1
  102. package/dist/http/checkpoint-ctx.d.ts +59 -71
  103. package/dist/http/checkpoint-ctx.js +34 -40
  104. package/dist/http/dispatch.js +18 -2
  105. package/dist/http/durable-run-leg.d.ts +5 -0
  106. package/dist/http/durable-run-leg.js +1 -0
  107. package/dist/http/execution-context.d.ts +4 -0
  108. package/dist/http/execution-context.js +9 -0
  109. package/dist/http/health-self-report.d.ts +6 -2
  110. package/dist/http/health-self-report.js +3 -2
  111. package/dist/http/memory-compliance-failure-reply.d.ts +1 -1
  112. package/dist/http/memory-compliance-failure-reply.js +2 -15
  113. package/dist/http/memory-control-plane-corrupt-reply.d.ts +25 -0
  114. package/dist/http/memory-control-plane-corrupt-reply.js +19 -0
  115. package/dist/http/principal-gate.d.ts +35 -12
  116. package/dist/http/principal-gate.js +16 -12
  117. package/dist/http/request-guards.d.ts +1 -1
  118. package/dist/http/request-guards.js +5 -5
  119. package/dist/http/resume-legs.d.ts +22 -0
  120. package/dist/http/resume-legs.js +26 -17
  121. package/dist/http/route-ctx.d.ts +11 -10
  122. package/dist/http/route-table.js +2 -0
  123. package/dist/http/routes/adoption.d.ts +11 -0
  124. package/dist/http/routes/adoption.js +80 -6
  125. package/dist/http/routes/agents-roster.js +1 -0
  126. package/dist/http/routes/approvals-assistant.js +2 -1
  127. package/dist/http/routes/background.js +1 -0
  128. package/dist/http/routes/capabilities.js +4 -1
  129. package/dist/http/routes/diagnostics.d.ts +9 -14
  130. package/dist/http/routes/diagnostics.js +10 -20
  131. package/dist/http/routes/engine.d.ts +31 -0
  132. package/dist/http/routes/engine.js +81 -0
  133. package/dist/http/routes/memory-bundle.js +3 -0
  134. package/dist/http/routes/memory-consolidation.js +3 -5
  135. package/dist/http/routes/memory-origin.d.ts +1 -1
  136. package/dist/http/routes/memory-origin.js +2 -4
  137. package/dist/http/routes/memory-policy.js +5 -1
  138. package/dist/http/routes/observability.js +3 -6
  139. package/dist/http/routes/runs.js +7 -3
  140. package/dist/http/routes/session-sync.js +12 -25
  141. package/dist/http/routes/sessions-list.d.ts +1 -1
  142. package/dist/http/routes/sessions.js +4 -0
  143. package/dist/http/routes/tasks.js +23 -5
  144. package/dist/http/routes/trace-usage.js +1 -1
  145. package/dist/http/run-concurrency-cap.d.ts +86 -0
  146. package/dist/http/run-concurrency-cap.js +54 -0
  147. package/dist/http/server.d.ts +15 -12
  148. package/dist/http/server.js +18 -3
  149. package/dist/http/stored-body-replay.d.ts +9 -6
  150. package/dist/http/stored-body-replay.js +19 -5
  151. package/dist/http/wire-types.d.ts +8 -3
  152. package/dist/json-body-limit.d.ts +8 -0
  153. package/dist/json-body-limit.js +2 -0
  154. package/dist/leader/diffout.d.ts +1 -1
  155. package/dist/local-data-root.d.ts +2 -2
  156. package/dist/memory-operator-faces.d.ts +9 -4
  157. package/dist/memory-scope-stores.d.ts +9 -7
  158. package/dist/memory-scope-stores.js +3 -14
  159. package/dist/memory-scope.d.ts +36 -0
  160. package/dist/memory-scope.js +32 -1
  161. package/dist/net-address.d.ts +53 -0
  162. package/dist/net-address.js +111 -0
  163. package/dist/observability/fail-open.d.ts +14 -10
  164. package/dist/observability/fail-open.js +14 -10
  165. package/dist/orchestration/workflow-notify-journal.js +2 -3
  166. package/dist/org-delivery-gate.js +1 -1
  167. package/dist/peer-directory.d.ts +6 -12
  168. package/dist/peer-directory.js +5 -1
  169. package/dist/plugins/caching-session-store.d.ts +3 -1
  170. package/dist/plugins/caching-session-store.js +3 -0
  171. package/dist/plugins/checkpoint-store-face.d.ts +426 -0
  172. package/dist/plugins/checkpoint-store-face.js +152 -0
  173. package/dist/plugins/file-run-store.d.ts +0 -6
  174. package/dist/plugins/file-run-store.js +1 -12
  175. package/dist/plugins/fork-routing-session-store.d.ts +6 -2
  176. package/dist/plugins/fork-routing-session-store.js +2 -2
  177. package/dist/plugins/imported-layer-root-retirement.d.ts +10 -8
  178. package/dist/plugins/local-checkpoint-store.d.ts +2 -2
  179. package/dist/plugins/local-checkpoint-store.js +2 -1
  180. package/dist/plugins/local-session-store.d.ts +28 -5
  181. package/dist/plugins/local-session-store.js +32 -8
  182. package/dist/plugins/memory-engine-pg.d.ts +29 -6
  183. package/dist/plugins/memory-engine-pg.js +96 -214
  184. package/dist/plugins/memory-engine-sql-apply.d.ts +121 -0
  185. package/dist/plugins/memory-engine-sql-apply.js +358 -0
  186. package/dist/plugins/memory-engine-tidb.d.ts +35 -9
  187. package/dist/plugins/memory-engine-tidb.js +97 -227
  188. package/dist/plugins/memory-key-guards.d.ts +1 -1
  189. package/dist/plugins/memory-origin-law.d.ts +3 -3
  190. package/dist/plugins/one-time-migrations.d.ts +32 -56
  191. package/dist/plugins/one-time-migrations.js +25 -117
  192. package/dist/plugins/permission-rule-store-file.d.ts +17 -3
  193. package/dist/plugins/permission-rule-store-file.js +8 -1
  194. package/dist/plugins/pg-pool.js +1 -1
  195. package/dist/plugins/pg-session-storage.d.ts +17 -4
  196. package/dist/plugins/pg-session-storage.js +8 -4
  197. package/dist/plugins/run-store-sql.js +2 -2
  198. package/dist/plugins/schema-ensure-legs.js +2 -2
  199. package/dist/plugins/session-fork.d.ts +31 -9
  200. package/dist/plugins/session-fork.js +3 -2
  201. package/dist/plugins/session-placement.d.ts +4 -2
  202. package/dist/plugins/sql-json-column.d.ts +5 -1
  203. package/dist/plugins/store-backend.d.ts +11 -8
  204. package/dist/plugins/store-backend.js +8 -7
  205. package/dist/plugins/store-contracts.d.ts +1 -11
  206. package/dist/plugins/store-contracts.js +0 -9
  207. package/dist/plugins/store-sweep.d.ts +63 -0
  208. package/dist/plugins/store-sweep.js +48 -0
  209. package/dist/plugins/tidb-pool.js +1 -1
  210. package/dist/plugins/tidb-session-store.d.ts +16 -3
  211. package/dist/plugins/tidb-session-store.js +8 -4
  212. package/dist/prompt-screen.d.ts +6 -9
  213. package/dist/prompt-screen.js +2 -5
  214. package/dist/rules-consent.d.ts +20 -27
  215. package/dist/rules-consent.js +82 -6
  216. package/dist/run-cancel-context.d.ts +4 -1
  217. package/dist/run-cancel-context.js +2 -1
  218. package/dist/runs.js +1 -1
  219. package/dist/sandbox-egress-policy.js +6 -11
  220. package/dist/sandbox-rbac-rules.d.ts +88 -0
  221. package/dist/sandbox-rbac-rules.js +80 -0
  222. package/dist/sealed-key-layout.d.ts +17 -0
  223. package/dist/sealed-key-layout.js +4 -0
  224. package/dist/sealed-key.js +5 -5
  225. package/dist/security.d.ts +38 -6
  226. package/dist/security.js +9 -2
  227. package/dist/session-notify-door.d.ts +5 -3
  228. package/dist/session-resident-background.d.ts +57 -0
  229. package/dist/session-resident-background.js +20 -0
  230. package/dist/session-sync-kernel.d.ts +53 -14
  231. package/dist/session-sync-kernel.js +70 -1
  232. package/dist/session-sync.d.ts +5 -3
  233. package/dist/session-workspace.d.ts +1 -0
  234. package/dist/session-workspace.js +1 -0
  235. package/dist/sighup-idle.d.ts +9 -7
  236. package/dist/sighup-idle.js +4 -5
  237. package/dist/spec-fields.d.ts +16 -0
  238. package/dist/spec-fields.js +7 -0
  239. package/dist/stored-request-env-credentials-m38.d.ts +3 -0
  240. package/dist/stored-request-env-credentials-m38.js +8 -0
  241. package/dist/stored-request-prompt-profile-m30.d.ts +1 -1
  242. package/dist/{plugins → stores-sql}/checkpoint-store-sql.d.ts +13 -427
  243. package/dist/{plugins → stores-sql}/checkpoint-store-sql.js +33 -194
  244. package/dist/{plugins → stores-sql}/mailbox-store-sql.d.ts +2 -2
  245. package/dist/{plugins → stores-sql}/mailbox-store-sql.js +4 -4
  246. package/dist/{plugins → stores-sql}/roster-store-sql.d.ts +2 -2
  247. package/dist/{plugins → stores-sql}/roster-store-sql.js +1 -1
  248. package/dist/{plugins → stores-sql}/tool-result-store-sql.d.ts +2 -2
  249. package/dist/{plugins → stores-sql}/tool-result-store-sql.js +6 -6
  250. package/dist/{plugins → stores-sql}/workflow-journal-store-sql.d.ts +7 -12
  251. package/dist/{plugins → stores-sql}/workflow-journal-store-sql.js +3 -8
  252. package/dist/task-settings.d.ts +59 -4
  253. package/dist/task-settings.js +50 -4
  254. package/dist/tool-approval.d.ts +2 -2
  255. package/dist/trace/core-keyset-guard.d.ts +8 -7
  256. package/dist/trace/engine-notice-wire.d.ts +1 -1
  257. package/dist/trace/engine-notice-wire.js +2 -0
  258. package/dist/trace/exit-seat.d.ts +6 -0
  259. package/dist/trace/exit-seat.js +1 -0
  260. package/dist/trace/project.js +9 -0
  261. package/dist/trace/redact.d.ts +13 -2
  262. package/dist/trace/redact.js +305 -24
  263. package/dist/trace/self-minted-wire.d.ts +326 -8
  264. package/dist/trace/self-minted-wire.js +125 -7
  265. package/dist/trace/wire-projection-faces.d.ts +1 -1
  266. package/dist/trace/wire-projection-faces.js +1 -1
  267. package/dist/usage-analytics.d.ts +4 -0
  268. package/package.json +12 -4
  269. package/scripts/drain-check-local.mjs +8 -29
  270. package/scripts/drain-check.mysql.sql +3 -24
  271. package/scripts/drain-check.pg.sql +3 -24
  272. package/skills/update-config.md +1 -1
  273. package/wire-changes/server@7.146.0.json +3221 -0
  274. package/dist/approval-batch-census.d.ts +0 -27
  275. package/dist/approval-batch-census.js +0 -27
package/dist/config.d.ts CHANGED
@@ -502,11 +502,16 @@ export declare function parseCapEnv(name: string, raw: string | undefined): {
502
502
  * 这里**不改解析**(让配置根跟着 `LOCAL_DATA_ROOT` 走,会让现有部署的 config.d 当场失踪 —— 代价比
503
503
  * 分家本身大得多)。只回一个「分家了,两个根分别是什么」的事实,由 boot 期打告警,合一旋钮 = `CONFIG_LOCAL_DIR`。
504
504
  *
505
- * @returns 分家时回两个根;一致(或已被 CONFIG_LOCAL_DIR/AGENT_DATA_DIR 统一)时回 undefined。
505
+ * 判的是「**一声不响**的分家」,不是「两个根不同」:合一旋钮一旦给出就是运维表过的态 —— `CONFIG_LOCAL_DIR`
506
+ * 等于数据根 = 合一,不等 = 有意分家,两者都不报。只有配置根是**推导**来的(`AGENT_DATA_DIR` / 家目录缺省)
507
+ * 且 ≠ 数据根,才可能是没人表过态的分家 ⇒ 报。
508
+ *
509
+ * @returns 一声不响的分家时回两个根;一致、或 `CONFIG_LOCAL_DIR` 显式在场(等 = 合一,不等 = 有意分家)时回 undefined。
506
510
  */
507
511
  export declare function splitLocalRoots(a: {
508
512
  /** optional:类型上可缺席(config-types 里是 `localDataRoot?`)——缺席时无从比较,判为不分家。 */
509
513
  localDataRoot: string | undefined;
514
+ /** `CONFIG_LOCAL_DIR`(`loadConfig` 已把空串归为缺席):在场 ⇒ 运维用合一旋钮表过态,本函数不再判。 */
510
515
  configLocalDir: string | undefined;
511
516
  agentDataDir: string | undefined;
512
517
  home: string;
package/dist/config.js CHANGED
@@ -3,7 +3,7 @@ import { DEFAULT_AT_MENTION, DEFAULT_MODEL_REF, mintCapabilityKeys } from "./mod
3
3
  import { homedir } from "node:os";
4
4
  import { isAbsolute, join, posix as posixPath } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
- import { compileAllowList, inheritEnvPolicyProblem, BUILTIN_COMPLIANCE_DENIES, COMPLIANCE_CAPABILITIES, CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES, compileReadDeny, resolveReadDenyBuiltins, isModelPriced, PROTOCOL_TABLE, RECOMMENDED_SENSITIVE_PATTERNS, judgeRuleToolName, resolveFileHistoryRetention, resolveLockedKeys, validatePermissionRules, WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable, principalInboxConsumerOf, carriesCredential } from "@sema-agent/core";
6
+ import { compileAllowList, inheritEnvPolicyProblem, BUILTIN_COMPLIANCE_DENIES, COMPLIANCE_CAPABILITIES, CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES, compileReadDeny, resolveReadDenyBuiltins, isModelPriced, PROTOCOL_TABLE, RECOMMENDED_SENSITIVE_PATTERNS, judgeRuleToolName, resolveFileHistoryRetention, resolveLockedKeys, validatePermissionRules, WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable, principalInboxConsumerOf, carriesCredential, isReplicableScope } from "@sema-agent/core";
7
7
  import { ROSTER_PRIMARY_ROLES, ROSTER_CHEAP_ROLES, WEB_ADMIN_ENV, retiredEnvRefusal, stripControlAndBidi } from "@sema-agent/settings-schema";
8
8
  import { parseApprovalHmacKeys, parsePrincipalJwks } from "./auth-keys.js";
9
9
  import { DEFAULT_ELICITATION_THROTTLE } from "./elicitation.js";
@@ -16,6 +16,7 @@ import { projectLayerOwnerOf } from "./project-layer-key.js";
16
16
  import { DEFAULT_QUESTION_THROTTLE } from "./question.js";
17
17
  import { GIT_API_KINDS, isGitApiKind } from "./git-api-kind.js";
18
18
  import { CONFIG_PROVIDER_WORDS, RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC, RESILIENCE_DEFAULTS, TASK_WRITE_FACE_ESCALATIONS, MAX_TIMER_DELAY_MS, AUTO_MODE_CLASSIFIER_TIMEOUT_BOUNDS, DRAIN_GRACE_FLOOR_MS, DRAIN_SECOND_SIGNAL_MIN_GAP_BOUNDS, DRAIN_SECOND_SIGNAL_MIN_GAP_DEFAULT_MS, FIRST_BYTE_TIMEOUT_BOUNDS, FLEET_LEASE_OUTAGE_MAX_DEFAULT_MS, fleetScopeOf, DEVICE_DELIVERY_TIMEOUT_BOUNDS, DEVICE_EXEC_TIMEOUT_BOUNDS, inWholeNumberBounds, wholeNumberBoundsRule } from "./config-types.js";
19
+ import { parseTrustedProxyCidr } from "./net-address.js";
19
20
  import { DEVICE_HELLO_ACK_BOUNDS } from "./device-ws-protocol.js";
20
21
  import { FLEET_ADVERTISE_ADDRESS_ENV, FLEET_GENERATION_ENV, FLEET_SCOPE_ENV, SERVING_LEASE_TTL_FLOOR_MS, isCanonicalGeneration } from "@sema-agent/settings-schema/fleet";
21
22
  import { ScopeId } from "@sema-agent/settings-schema/api/scopes";
@@ -59,6 +60,7 @@ import { allowlistedEnv } from "./server-secret-env.js";
59
60
  import { ENV_NAME_ALLOWLIST_KNOBS, ENV_NAME_ALLOWLIST_KNOB_NAMES, describeAllowlistPostures } from "./env-name-allowlist-knobs.js";
60
61
  import { APPROVAL_ASK_RETENTION_DEFAULT_MS, APPROVAL_ASK_RETENTION_FLOOR_MS } from "./approval-ask-retention.js";
61
62
  import { parseModsDeclaration } from "./mods-declaration.js";
63
+ import { ADOPTION_IMPORT_MAX_SESSIONS_DEFAULT, ADOPTION_IMPORT_MAX_SESSIONS_FLOOR, ADOPTION_IMPORT_MAX_SESSION_BYTES_CEILING, ADOPTION_IMPORT_MAX_SESSION_BYTES_DEFAULT, ADOPTION_IMPORT_MAX_SESSION_BYTES_FLOOR } from "./adoption/import-limits.js";
62
64
  import { buildDeclaredEngineWithoutCoordinatesRefusal, missingSqlCoordinateKeys } from "./sql-engine-coordinates.js";
63
65
  export { RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC } from "./config-types.js";
64
66
  function csv(name) {
@@ -274,6 +276,11 @@ function clampEnvWithWarn(name, fallback, min, max = Number.POSITIVE_INFINITY) {
274
276
  CONFIG_WARNINGS.push({ env: name, raw: env(name, fallback), used: String(used), kind: "clamped" });
275
277
  return used;
276
278
  }
279
+ function countEnvClamped(name, fallback, min, max = Number.POSITIVE_INFINITY) {
280
+ if (!Number.isInteger(numEnv(name, String(fallback))))
281
+ throw new Error(`env ${name}=${env(name, String(fallback))} must be a whole number (it is a count)`);
282
+ return clampEnvWithWarn(name, String(fallback), min, max);
283
+ }
277
284
  function numEnvOr(name, fallback) {
278
285
  const raw = process.env[name];
279
286
  if (raw === undefined || raw === "")
@@ -439,6 +446,19 @@ function parseDevicePreAuthPerRemoteEnv() {
439
446
  }
440
447
  return n;
441
448
  }
449
+ function parseDeviceTrustedProxyCidrsEnv(raw) {
450
+ if (raw === undefined || raw === "")
451
+ return undefined;
452
+ const items = raw.split(",").map((item) => item.replace(/^[ \t]+|[ \t]+$/g, ""));
453
+ return items.map((item, i) => {
454
+ const r = parseTrustedProxyCidr(item);
455
+ if (r.ok)
456
+ return r.text;
457
+ throw new Error(`env DEVICE_TRUSTED_PROXY_CIDRS item ${i + 1} of ${items.length} (${JSON.stringify(item)}) ${r.flaw}. ` +
458
+ `This list names the reverse proxies whose X-Forwarded-For the device pre-auth cap trusts — list only your own proxies, comma-separated ` +
459
+ `(e.g. 10.0.0.0/8,fd00::/8); unset it to read no proxy header at all (the cap is then keyed on the socket peer).`);
460
+ });
461
+ }
442
462
  function parseFileHistoryRetention() {
443
463
  const raw = process.env.FILE_HISTORY_RETENTION_KEEP;
444
464
  if (raw === undefined || raw === "")
@@ -1590,19 +1610,30 @@ function parentPidEnv() {
1590
1610
  return { parentPid: n };
1591
1611
  }
1592
1612
  const ENGINE_LINGER_FLOOR_MS = 1_000;
1613
+ const ENGINE_HANDOVER_LINGER_FLOOR_MS = 60_000;
1593
1614
  const ENGINE_LINGER_CEIL_MS = 24 * 60 * 60 * 1_000;
1594
- function lingerMsEnv(name, raw, defaultMs, disableHint) {
1615
+ function engineWindowZeroSentence(zero) {
1616
+ switch (zero.kind) {
1617
+ case "refused":
1618
+ return `There is no "disable" value here — ${zero.disableHint}`;
1619
+ case "noGrace":
1620
+ return `the literal "0" is the one value below the floor accepted here — ${zero.offHint}`;
1621
+ }
1622
+ }
1623
+ function lingerMsEnv(name, raw, defaultMs, floorMs, zero) {
1595
1624
  if (raw === undefined || raw.trim() === "")
1596
1625
  return defaultMs;
1626
+ if (zero.kind === "noGrace" && raw.trim() === "0")
1627
+ return 0;
1597
1628
  const n = parseNumOrFail(name, raw);
1598
- if (!Number.isSafeInteger(n) || n < ENGINE_LINGER_FLOOR_MS || n > ENGINE_LINGER_CEIL_MS) {
1629
+ if (!Number.isSafeInteger(n) || n < floorMs || n > ENGINE_LINGER_CEIL_MS) {
1599
1630
  throw new Error(`env ${name}="${raw}" must be an INTEGER number of MILLISECONDS in ` +
1600
- `[${ENGINE_LINGER_FLOOR_MS}, ${ENGINE_LINGER_CEIL_MS}] (1s … 24h) — it is how long ` +
1631
+ `[${floorMs}, ${ENGINE_LINGER_CEIL_MS}] (${floorMs / 1_000}s … 24h) — it is how long ` +
1601
1632
  `"nobody attached, no run in flight, no HTTP touch" must hold CONTINUOUSLY before the engine self-exits. ` +
1602
1633
  `A value like "60" is the seconds-for-milliseconds slip: a 60ms window kills the engine inside a shell's own ` +
1603
1634
  `restart gap. A value like "1e100" passes a naive "is it a positive integer" check but the elapsed clock can ` +
1604
1635
  `NEVER reach it, which silently turns self-exit off while looking configured. ` +
1605
- `There is no "disable" value here — ${disableHint}`);
1636
+ engineWindowZeroSentence(zero));
1606
1637
  }
1607
1638
  return n;
1608
1639
  }
@@ -1757,6 +1788,10 @@ function parseMemoryDomain(ctx) {
1757
1788
  if (scope === undefined) {
1758
1789
  throw new Error("MEMORY_SYNC_URL is set but no sync scope is derivable (memory engine off or multi-tenant without an explicit scope) — set MEMORY_SYNC_SCOPE, or run the single-user file memory engine with a typed MEMORY_SCOPE (e.g. user:<tenant>) so it provides one");
1759
1790
  }
1791
+ if (!isReplicableScope(scope)) {
1792
+ throw new Error(`${memorySyncScopeRaw !== undefined ? "MEMORY_SYNC_SCOPE" : "MEMORY_SCOPE"} names a userlocal: memory layer as the sync layer — that layer family never leaves the store it is written in ` +
1793
+ "(the engine refuses to sync it before any read or request), so this sync leg would fail every round. Set MEMORY_SYNC_SCOPE to a layer that may be replicated, or unset MEMORY_SYNC_URL.");
1794
+ }
1760
1795
  if (carriesCredential(scope)) {
1761
1796
  throw new Error(`${memorySyncScopeRaw !== undefined ? "MEMORY_SYNC_SCOPE" : "MEMORY_SCOPE"} (the memory sync layer's name) holds a credential-shaped part — the layer name travels verbatim in every sync request path and report, so it is refused, never cleaned: rename the layer`);
1762
1797
  }
@@ -1915,6 +1950,7 @@ function parseAuthDomain(ctx) {
1915
1950
  ...parseGovernanceDeclarations(),
1916
1951
  operatorPrincipals: csv("OPERATOR_PRINCIPALS"),
1917
1952
  operatorSingleUser: boolEnv("OPERATOR_SINGLE_USER", false),
1953
+ serviceTokenActsForDeployment: boolEnv("SERVICE_TOKEN_ACTS_FOR_DEPLOYMENT", true),
1918
1954
  configCatalogEnabled: boolEnv("CONFIG_CATALOG_ENABLED", true),
1919
1955
  principalJwtPubkeys,
1920
1956
  principalJwtIss,
@@ -2062,6 +2098,7 @@ function parseOrchestrationDomain(ctx) {
2062
2098
  ...((n) => (n !== undefined ? { maxInflightPerDevice: n } : {}))(parseDeviceBoundedIntEnv("DEVICE_MAX_INFLIGHT_PER_DEVICE", process.env.DEVICE_MAX_INFLIGHT_PER_DEVICE, DEVICE_HELLO_ACK_BOUNDS.maxInflight)),
2063
2099
  ...((n) => (n !== undefined ? { maxPreAuthPerRemote: n } : {}))(parseDevicePreAuthPerRemoteEnv()),
2064
2100
  ...((n) => (n !== undefined ? { enrollTokenTtlSec: n } : {}))(parseDeviceEnrollTokenTtlEnv()),
2101
+ ...((n) => (n !== undefined ? { trustedProxyCidrs: n } : {}))(parseDeviceTrustedProxyCidrsEnv(process.env.DEVICE_TRUSTED_PROXY_CIDRS)),
2065
2102
  relay: parseDeviceRelayUrlEnv(process.env.DEVICE_RELAY_URL),
2066
2103
  ...(process.env.DEVICE_SUPERSEDE ? { supersede: enumEnv("DEVICE_SUPERSEDE", "stale_only", ["stale_only", "deny"]) } : {}),
2067
2104
  }
@@ -2158,6 +2195,8 @@ function parseLimitsHttpDomain() {
2158
2195
  ? { attachmentMimeAllowlist: csv("ATTACHMENT_MIME_ALLOWLIST").map((s) => s.toLowerCase()) }
2159
2196
  : {}),
2160
2197
  attachmentUnboundTtlMs: clampEnvWithWarn("ATTACHMENT_UNBOUND_TTL_MS", String(24 * 3600 * 1000), LIMITS_HTTP_FLOORS.attachmentUnboundTtlMs),
2198
+ adoptionImportMaxSessions: countEnvClamped("ADOPTION_IMPORT_MAX_SESSIONS", ADOPTION_IMPORT_MAX_SESSIONS_DEFAULT, ADOPTION_IMPORT_MAX_SESSIONS_FLOOR),
2199
+ adoptionImportMaxSessionBytes: countEnvClamped("ADOPTION_IMPORT_MAX_SESSION_BYTES", ADOPTION_IMPORT_MAX_SESSION_BYTES_DEFAULT, ADOPTION_IMPORT_MAX_SESSION_BYTES_FLOOR, ADOPTION_IMPORT_MAX_SESSION_BYTES_CEILING),
2161
2200
  infraCostRates: {
2162
2201
  toolCallMicroUsd: clampEnvWithWarn("INFRA_COST_TOOL_CALL", "0", 0),
2163
2202
  sandboxSecMicroUsd: clampEnvWithWarn("INFRA_COST_SANDBOX_SEC", "0", 0),
@@ -2173,9 +2212,23 @@ function parseLimitsHttpDomain() {
2173
2212
  sighupIdleGraceMs: clampEnvWithWarn("SIGHUP_IDLE_GRACE_MS", String(120_000), 5_000),
2174
2213
  ...parentPidEnv(),
2175
2214
  engineAutoExit: boolEnv("ENGINE_AUTO_EXIT", false),
2176
- engineLingerMs: lingerMsEnv("ENGINE_LINGER_MS", process.env.ENGINE_LINGER_MS, 60_000, "set ENGINE_AUTO_EXIT=false to turn self-exit off entirely."),
2177
- engineOrphanLingerMs: lingerMsEnv("ENGINE_ORPHAN_LINGER_MS", process.env.ENGINE_ORPHAN_LINGER_MS, 2_000, "this window only applies once the SEMA_PARENT_PID process is gone — unset SEMA_PARENT_PID to stop watching the parent at all."),
2215
+ engineLingerMs: lingerMsEnv("ENGINE_LINGER_MS", process.env.ENGINE_LINGER_MS, 60_000, ENGINE_LINGER_FLOOR_MS, { kind: "refused", disableHint: "set ENGINE_AUTO_EXIT=false to turn self-exit off entirely." }),
2216
+ engineOrphanLingerMs: lingerMsEnv("ENGINE_ORPHAN_LINGER_MS", process.env.ENGINE_ORPHAN_LINGER_MS, 2_000, ENGINE_LINGER_FLOOR_MS, {
2217
+ kind: "refused",
2218
+ disableHint: "this window only applies once the SEMA_PARENT_PID process is gone — unset SEMA_PARENT_PID to stop watching the parent at all.",
2219
+ }),
2220
+ engineFirstAttachMs: lingerMsEnv("ENGINE_FIRST_ATTACH_MS", process.env.ENGINE_FIRST_ATTACH_MS, 180_000, ENGINE_LINGER_FLOOR_MS, {
2221
+ kind: "noGrace",
2222
+ offHint: "it means no extra grace before the first non-probe request (the window is then ENGINE_LINGER_MS, as before this knob existed); " +
2223
+ "set ENGINE_AUTO_EXIT=false to turn self-exit off entirely.",
2224
+ }),
2225
+ engineHandoverLingerMs: lingerMsEnv("ENGINE_HANDOVER_LINGER_MS", process.env.ENGINE_HANDOVER_LINGER_MS, 1_800_000, ENGINE_HANDOVER_LINGER_FLOOR_MS, {
2226
+ kind: "refused",
2227
+ disableHint: "this window only applies after a shell hands the engine over (POST /v1/engine/handover) — a shell that never does so never uses it; set ENGINE_AUTO_EXIT=false and unset SEMA_PARENT_PID to turn self-exit off entirely.",
2228
+ }),
2178
2229
  rateLimitPerMin: numEnv("RATE_LIMIT_RPM", "0"),
2230
+ runMaxConcurrent: nonNegativeIntEnv("RUN_MAX_CONCURRENT", "在飞 run 全局并发帽"),
2231
+ runMaxConcurrentPerPrincipal: nonNegativeIntEnv("RUN_MAX_CONCURRENT_PER_PRINCIPAL", "按主体在飞 run 并发帽"),
2179
2232
  maxTaskCostUsd: numEnv("MAX_TASK_COST_USD", "0"),
2180
2233
  maxTaskTokens: numEnv("MAX_TASK_TOKENS", "0"),
2181
2234
  maxPrincipalCostUsd: numEnv("MAX_PRINCIPAL_COST_USD", "0"),
@@ -2484,7 +2537,7 @@ const MEMORY_GROUP_KEYS = [
2484
2537
  const AUTH_GROUP_KEYS = [
2485
2538
  "authToken", "authTokens", "allowUnauthedWrites", "corsOrigins", "principalHeader", "requirePrincipal", "autonomy",
2486
2539
  "commandPolicy", "compliancePosture", "lockedConfigKeys", "retentionPolicy", "retentionSweep",
2487
- "operatorPrincipals", "operatorSingleUser", "configCatalogEnabled", "principalJwtPubkeys", "principalJwtIss", "principalJwtAud",
2540
+ "operatorPrincipals", "operatorSingleUser", "serviceTokenActsForDeployment", "configCatalogEnabled", "principalJwtPubkeys", "principalJwtIss", "principalJwtAud",
2488
2541
  "principalJwtMaxTtlSec", "authBridgeIssuer", "bindHost", "bindHostSource",
2489
2542
  ];
2490
2543
  const ORCHESTRATION_GROUP_KEYS = [
@@ -2501,8 +2554,9 @@ const ORCHESTRATION_GROUP_KEYS = [
2501
2554
  const LIMITS_HTTP_GROUP_KEYS = [
2502
2555
  "port", "attachmentOrphanGraceMs", "workspaceFileMaxBytes", "attachmentMaxBytes", "attachmentMimeAllowlist",
2503
2556
  "attachmentUnboundTtlMs", "infraCostRates", "drainGraceMs", "drainSecondSignalMinGapMs", "sighupIdleGraceMs", "parentPid",
2504
- "engineAutoExit", "engineLingerMs", "engineOrphanLingerMs",
2505
- "rateLimitPerMin", "maxTaskCostUsd",
2557
+ "engineAutoExit", "engineLingerMs", "engineOrphanLingerMs", "engineFirstAttachMs", "engineHandoverLingerMs",
2558
+ "rateLimitPerMin", "runMaxConcurrent", "runMaxConcurrentPerPrincipal", "maxTaskCostUsd",
2559
+ "adoptionImportMaxSessions", "adoptionImportMaxSessionBytes",
2506
2560
  "maxTaskTokens", "maxPrincipalCostUsd", "costQuotaWindowSec", "reapIntervalSec", "runStaleSec", "toolResultTtlSec",
2507
2561
  ];
2508
2562
  const OBSERVABILITY_GROUP_KEYS = ["metricsToken", "traceToken", "toolTrace", "traceThinking", "logLevel", "otel"];
@@ -2729,7 +2783,9 @@ export function parseCapEnv(name, raw) {
2729
2783
  return { value: n };
2730
2784
  }
2731
2785
  export function splitLocalRoots(a) {
2732
- const configRoot = a.configLocalDir ?? a.agentDataDir ?? join(a.home, ".ai-agent");
2786
+ if (a.configLocalDir !== undefined)
2787
+ return undefined;
2788
+ const configRoot = a.agentDataDir ?? join(a.home, ".ai-agent");
2733
2789
  if (!a.localDataRoot || a.localDataRoot === configRoot)
2734
2790
  return undefined;
2735
2791
  return { dataRoot: a.localDataRoot, configRoot };
@@ -0,0 +1,19 @@
1
+ import type { DeploymentShapeInput } from "./deployment-shape.js";
2
+ /** 表的条数上限(一条约百字节量级)。引擎的会话暂存自己按会话数有上限(超了整会话驱逐最旧);本表的上限比它宽得多。 */
3
+ export declare const DELETED_SESSION_ID_CAP = 50000;
4
+ /**
5
+ * **铸点**(唯一):本进程的在线删除协调器在删除会话本体之前记一笔 —— 围栏先立,id 才空出来。删除随后失败也无害:
6
+ * 这一笔只拒「别的属主」,而会话还在时别的属主本来就用不了它。
7
+ * 同一只 id 再删一次:这一次的删前属主对已记的每一位都等价(同一只等价律判)⇒ 收进集合(前几位一位不丢 —— 等价律有方向、不传递,只记最新一位会让
8
+ * 「哨兵删 → 无主重登再删 → `default` 用」绕过「哨兵 ≢ `default`」);对某一位不等价 ⇒ 记成「谁都不是」。后一次删除从不覆盖前一位(前一位的暂存仍可能在本进程)。
9
+ * `deployment` = 部署形(等价律的窄互认臂读它;调用方递 `ServiceConfig` 整份)。
10
+ */
11
+ export declare function retireSessionId(sessionId: string, owner: string | null, deployment: DeploymentShapeInput<"principal">): void;
12
+ /**
13
+ * **读点**(唯一;执行上下文解析器 `http/execution-context.ts` 步 ① 调):这只 id 在本进程被删过、而删前的属主不是 `owner` ⇒ `true`(解析器拒)。
14
+ * `owner` = 这条执行要服务的属主(根会话行上那一读的住客);`undefined` = 判不出 ⇒ 删过就为真(fail-closed)。
15
+ * 同一位 = 对**每一位**删前属主,会话属主等价律 `sameSessionOwner(deployment, 删前属主, owner)` 都成立(删前属主是行上记下的那一格,本次住客是这一次的调用方);
16
+ * 被互不等价的属主各删过一次的 id 对谁都为真。
17
+ */
18
+ export declare function sessionIdRetiredFor(sessionId: string, owner: string | null | undefined, deployment: DeploymentShapeInput<"principal">): boolean;
19
+ //# sourceMappingURL=deleted-session-ids.d.ts.map
@@ -0,0 +1,26 @@
1
+ import { recordFailOpen } from "./observability/fail-open.js";
2
+ import { sameSessionOwner } from "./security.js";
3
+ export const DELETED_SESSION_ID_CAP = 50_000;
4
+ const DELETED_UNDER_SEVERAL = Symbol("deleted under several owners");
5
+ const formerOwners = new Map();
6
+ export function retireSessionId(sessionId, owner, deployment) {
7
+ const prior = formerOwners.get(sessionId);
8
+ const recorded = prior === undefined ? [owner] : sessionIdRetiredFor(sessionId, owner, deployment) ? DELETED_UNDER_SEVERAL : prior === DELETED_UNDER_SEVERAL || prior.includes(owner) ? prior : [...prior, owner];
9
+ formerOwners.delete(sessionId);
10
+ formerOwners.set(sessionId, recorded);
11
+ if (formerOwners.size <= DELETED_SESSION_ID_CAP)
12
+ return;
13
+ const oldest = formerOwners.keys().next().value;
14
+ if (oldest !== undefined)
15
+ formerOwners.delete(oldest);
16
+ recordFailOpen("server.session-id-tombstone.evicted", `cap=${DELETED_SESSION_ID_CAP}`);
17
+ }
18
+ export function sessionIdRetiredFor(sessionId, owner, deployment) {
19
+ const former = formerOwners.get(sessionId);
20
+ if (former === undefined)
21
+ return false;
22
+ if (owner === undefined || former === DELETED_UNDER_SEVERAL)
23
+ return true;
24
+ return !former.every((f) => sameSessionOwner(deployment, f, owner));
25
+ }
26
+ //# sourceMappingURL=deleted-session-ids.js.map
@@ -0,0 +1,155 @@
1
+ /**
2
+ * 随包的引擎部署事实(npm 包内 `deploy/facts/`)—— 成员集、Schema 方言、形变登记表的**唯一定义处**,以及其中两份成员(车道 / 钥匙保管目录)的投影与 Schema。
3
+ *
4
+ * ## 成员律
5
+ * `deploy/facts/` 只装**引擎自己的部署要求**:引擎发出的 API 调用、引擎按名读的键与目录、引擎要求先存在的条件。每一份都有引擎属主,都是生成物
6
+ * (唯一生成器 `scripts/gen-deploy-lanes.ts`,`--check` 与 `test/deploy-lane-projections.test.ts` 钉「入库 ≡ 生成物」),从不手写。
7
+ * 部署方拥有的策略实例(引擎读来诊断、不据以发调用的东西,例如沙箱出网的 NetworkPolicy)不是成员。
8
+ * 每个成员 `<m>` = 数据 `<m>.json` + 一份机器可读 Schema `<m>.schema.json`;另有一份形变登记 `CHANGES.json`。三类文件都是 npm 子路径
9
+ * `@sema-agent/server/deploy/facts/<文件>`(子路径 = 包内文件路径)。
10
+ * · `lanes`:每条执行车道的租户档、隔离位、必需键 —— 投影自 settings-schema 车道表与执行环境能力块({@link LANE_FACTS_SCHEMA};行由生成器从同一个车道模型渲出,
11
+ * 安装器那张旧车道表的 `users` / `isolation` 两列也从这些行派生,同源零双写);
12
+ * · `sandbox-rbac`:k8s 沙箱服务账号的 RBAC 规则表 —— 表与 Schema 都在 `src/sandbox-rbac-rules.ts`(和表放一起);
13
+ * · `sealed-key`:钥匙保管目录的安装契约 —— 投影自 `src/sealed-key-layout.ts` 的布局常量与配置读法「`SEALED_KEY_DIR` 须是绝对路径」({@link SEALED_KEY_FACTS})。
14
+ *
15
+ * ## 契约:「未登记的加性形变,或未抬版的破坏性形变 ⇒ 红」
16
+ * 发车链对**上一个已发 tag** 里的同名 Schema 求差(`scripts/deploy-facts-gate.ts`;CI 浅克隆读不到 tag,这道判据在发车链跑),
17
+ * 差异逐条分成加性 / 破坏性,与 {@link FACTS_SCHEMA_CHANGES} 里本版新登记的条目对账。**加性只有一种:新增一个可选属性**;其余任何形变
18
+ * (删属性、改类型、可选 ⇄ 必有、新增必有属性、`items` 形变、闭集增 / 删词、`const` 变、`minItems` / `uniqueItems` / `x-uniqueKey` 变)都是破坏性。
19
+ * 值的含义变了而 Schema 不变(例如重新定义某个闭集词)门判不出 —— 改动者登记一条 `breaking` 并写明 `why`,复审按这一条问。
20
+ *
21
+ * ## 版本
22
+ * 每个成员的 `schemaVersion` **从登记表派生**({@link factsSchemaVersion}):首发那一版 = 1,此后每个登记了 `breaking` 的版本 +1。不另写常量 ——
23
+ * 登记一条破坏性形变就抬了版,「改了形忘了抬版」只剩「改了形没登记」一种,门对着已发 tag 抓它。
24
+ * 读方义务:用**同一个包里**那份 Schema 校验;不认识的 `schemaVersion` 整份拒读;同一版本内的未知键忽略。
25
+ */
26
+ import { TENANT_SEPARATION_WORDS } from "@sema-agent/settings-schema/execution-lanes";
27
+ /** 包内目录(npm 子路径前缀 `@sema-agent/server/deploy/facts/`)。 */
28
+ export declare const DEPLOY_FACTS_DIR = "deploy/facts";
29
+ /** 成员闭集。加一个成员 = 这里加一个词 + 生成器给它数据与 Schema + 登记表一条 `introduced`(缺任一处 ⇒ 编译错或生成抛)。 */
30
+ export declare const DEPLOY_FACTS_MEMBERS: readonly ["lanes", "sandbox-rbac", "sealed-key"];
31
+ export type DeployFactsMember = (typeof DEPLOY_FACTS_MEMBERS)[number];
32
+ /** 形变登记表的文件名(与成员同目录;不是成员,没有自己的 Schema)。 */
33
+ export declare const DEPLOY_FACTS_CHANGES_FILE = "CHANGES.json";
34
+ /** 方言 = JSON Schema 2020-12 的一个闭子集;根上的 `$schema` 恒为它。 */
35
+ export declare const FACTS_SCHEMA_DIALECT = "https://json-schema.org/draft/2020-12/schema";
36
+ /**
37
+ * 方言的节点形(类型就是方言:多写一个关键字 = 编译错;`scripts/deploy-facts-gate.ts` 在 JSON 面再判一次,拿到的是入库文件与已发 tag 上的文件)。
38
+ * · 标量:`string`(可带 `enum` 闭集或 `const`)/ `integer`(可带 `const`)/ `boolean`;
39
+ * · 对象:`properties` + `required`(⊆ properties)+ `additionalProperties: false`(恒关:未登记的键不许出现);
40
+ * · 数组:`items`(必有)+ 可选 `minItems`;标量元素可声明 `uniqueItems: true`,对象元素可声明 `x-uniqueKey`(这几个属性的值组合在数组里唯一;
41
+ * 成员必须是元素的必有属性、且是标量或标量数组 —— 对象成员里加可选属性按上面的判法是加性,却会让旧读方算出的唯一性失真;
42
+ * 标准校验器把它当注解忽略,本仓的校验器执行它)。
43
+ * · JSON 键一律只认自有的(`toString` / `constructor` / `__proto__` 这类原型链上的名字不算「在」)。
44
+ * · `description` 任何节点都可带,差异判法不看它(值含义的变化走人工登记)。
45
+ */
46
+ export type FactsScalarSchema = {
47
+ readonly type: "string";
48
+ readonly enum?: readonly string[];
49
+ readonly const?: string;
50
+ readonly description?: string;
51
+ } | {
52
+ readonly type: "integer";
53
+ readonly const?: number;
54
+ readonly description?: string;
55
+ } | {
56
+ readonly type: "boolean";
57
+ readonly description?: string;
58
+ };
59
+ export interface FactsObjectSchema {
60
+ readonly type: "object";
61
+ readonly properties: {
62
+ readonly [key: string]: FactsNodeSchema;
63
+ };
64
+ readonly required: readonly string[];
65
+ readonly additionalProperties: false;
66
+ readonly description?: string;
67
+ }
68
+ export interface FactsArraySchema {
69
+ readonly type: "array";
70
+ readonly items: FactsNodeSchema;
71
+ readonly minItems?: number;
72
+ readonly uniqueItems?: true;
73
+ readonly "x-uniqueKey"?: readonly string[];
74
+ readonly description?: string;
75
+ }
76
+ export type FactsNodeSchema = FactsScalarSchema | FactsObjectSchema | FactsArraySchema;
77
+ /**
78
+ * 每种节点允许的关键字(运行期闭集;`scripts/deploy-facts-gate.ts` 的方言判读它,对入库文件与已发 tag 上的文件都判)。
79
+ * `satisfies` 让这张表只列类型里有的键;类型里多出来而表里没列的键,生成器在渲 Schema 时当场抛「方言外的关键字」。根节点另多一个 `$schema`。
80
+ */
81
+ export declare const FACTS_SCHEMA_KEYWORDS: {
82
+ readonly string: readonly ["type", "enum", "const", "description"];
83
+ readonly integer: readonly ["type", "const", "description"];
84
+ readonly boolean: readonly ["type", "description"];
85
+ readonly object: readonly ["type", "properties", "required", "additionalProperties", "description"];
86
+ readonly array: readonly ["type", "items", "minItems", "uniqueItems", "x-uniqueKey", "description"];
87
+ };
88
+ /** 根:一个对象节点 + `$schema`;`schemaVersion` 必有且形恒为 `{ type: "integer", const: <版本> }`(门把这一处当版本通道,不当形变)。 */
89
+ export interface FactsRootSchema extends FactsObjectSchema {
90
+ readonly $schema: typeof FACTS_SCHEMA_DIALECT;
91
+ readonly properties: {
92
+ readonly schemaVersion: {
93
+ readonly type: "integer";
94
+ readonly const: number;
95
+ readonly description?: string;
96
+ };
97
+ readonly [key: string]: FactsNodeSchema;
98
+ };
99
+ }
100
+ export declare const FACTS_SCHEMA_CHANGE_KINDS: readonly ["introduced", "additive", "breaking"];
101
+ export type FactsSchemaChangeKind = (typeof FACTS_SCHEMA_CHANGE_KINDS)[number];
102
+ /**
103
+ * 一条形变登记。`pointer` = 实例路径:`""` 是整份,`/lanes/[]/isolation` 是 `lanes` 数组每一行的 `isolation`(`[]` = 数组元素)。
104
+ * `version` = 这条形变随哪个 server 版本发出;已发版本的条目只读(门:基线 tag 上的每一条在本树里原样还在)。
105
+ */
106
+ export interface FactsSchemaChange {
107
+ readonly version: string;
108
+ readonly file: DeployFactsMember;
109
+ readonly kind: FactsSchemaChangeKind;
110
+ readonly pointer: string;
111
+ readonly why: string;
112
+ }
113
+ /**
114
+ * 形变登记表(只追加;生成器把它写成随包 `CHANGES.json` 与 README 的变更节)。
115
+ * 新条目的 `version` 写下一个要发的 server 版本;发车链在版本号抬起后核「本版新登记的条目都是这个版本」。
116
+ */
117
+ export declare const FACTS_SCHEMA_CHANGES: readonly FactsSchemaChange[];
118
+ /**
119
+ * 成员当前的 `schemaVersion`:登记表里恰一条 `introduced`(那一版 = 1),此后每个登记了 `breaking` 的**不同**版本 +1
120
+ * (同一版本里登记几条破坏性形变都只抬一次;首发那一版里的 `breaking` 不算 —— 那时还没有发出去的形)。
121
+ * 登记表不合这个形(零条 / 多条 `introduced`)⇒ 抛:版本号无从派生,不猜。
122
+ */
123
+ export declare function factsSchemaVersion(member: DeployFactsMember, changes?: readonly FactsSchemaChange[]): number;
124
+ /** 隔离位闭集:手跑在不在 OS 级隔离边界里(执行环境能力块的 `isolation` 位;安装器旧车道表的 `isolation` 列也读这两个词)。 */
125
+ export declare const LANE_ISOLATION_WORDS: readonly ["supported", "unsupported"];
126
+ export type LaneIsolationWord = (typeof LANE_ISOLATION_WORDS)[number];
127
+ /** `lanes.json` 的一行(行序 = settings-schema 车道词序)。 */
128
+ export interface LaneFactsRow {
129
+ readonly lane: string;
130
+ readonly tenantSeparation: (typeof TENANT_SEPARATION_WORDS)[number];
131
+ readonly isolation: LaneIsolationWord;
132
+ readonly requiredEnv: readonly string[];
133
+ readonly requiredSecretEnv: readonly string[];
134
+ }
135
+ export declare const LANE_FACTS_SCHEMA: FactsRootSchema;
136
+ /** 钥匙保管目录对安装器的要求(闭集;每个词的意思写在 Schema 的 `description` 里)。 */
137
+ export declare const SEALED_KEY_REQUIREMENTS: readonly ["persist-across-restart", "shared-by-all-replicas", "writable", "whole-directory"];
138
+ export type SealedKeyRequirement = (typeof SEALED_KEY_REQUIREMENTS)[number];
139
+ /** 保管目录路径形的闭集:只收绝对路径(相对路径起服拒)。 */
140
+ export declare const SEALED_KEY_DIR_PATH_FORMS: readonly ["absolute"];
141
+ /**
142
+ * `sealed-key.json` 的内容:安装契约要求**显式设一个绝对路径的 `SEALED_KEY_DIR`**,安装器据此挂卷。
143
+ * 没设它时引擎回落到数据根下的 `sealed-key/`(数据根自身还有一条回落链)—— 那是引擎内部行为,安装器不依赖它,所以不在这份事实里。
144
+ */
145
+ export declare const SEALED_KEY_FACTS: {
146
+ readonly schemaVersion: number;
147
+ readonly dirEnv: "SEALED_KEY_DIR";
148
+ readonly dirPath: "absolute";
149
+ readonly subdirs: readonly ["checkpoint-ctx-keys"];
150
+ readonly dirMode: string;
151
+ readonly fileMode: string;
152
+ readonly requirements: readonly ["persist-across-restart", "shared-by-all-replicas", "writable", "whole-directory"];
153
+ };
154
+ export declare const SEALED_KEY_FACTS_SCHEMA: FactsRootSchema;
155
+ //# sourceMappingURL=deploy-facts.d.ts.map
@@ -0,0 +1,94 @@
1
+ import { TENANT_SEPARATION_WORDS } from "@sema-agent/settings-schema/execution-lanes";
2
+ import { CHECKPOINT_CTX_KEY_SUBDIR, SEALED_KEY_DIR_MODE, SEALED_KEY_FILE_MODE } from "./sealed-key-layout.js";
3
+ export const DEPLOY_FACTS_DIR = "deploy/facts";
4
+ export const DEPLOY_FACTS_MEMBERS = ["lanes", "sandbox-rbac", "sealed-key"];
5
+ export const DEPLOY_FACTS_CHANGES_FILE = "CHANGES.json";
6
+ export const FACTS_SCHEMA_DIALECT = "https://json-schema.org/draft/2020-12/schema";
7
+ export const FACTS_SCHEMA_KEYWORDS = {
8
+ string: ["type", "enum", "const", "description"],
9
+ integer: ["type", "const", "description"],
10
+ boolean: ["type", "description"],
11
+ object: ["type", "properties", "required", "additionalProperties", "description"],
12
+ array: ["type", "items", "minItems", "uniqueItems", "x-uniqueKey", "description"],
13
+ };
14
+ export const FACTS_SCHEMA_CHANGE_KINDS = ["introduced", "additive", "breaking"];
15
+ export const FACTS_SCHEMA_CHANGES = [
16
+ { version: "7.146.0", file: "lanes", kind: "introduced", pointer: "", why: "首发:每条执行车道的租户档、隔离位与必需键,行集 = 所依 settings-schema 的全部车道词" },
17
+ { version: "7.146.0", file: "sandbox-rbac", kind: "introduced", pointer: "", why: "首发:k8s 沙箱服务账号的 RBAC 规则表(每条规则何时授、引擎真调还是兼容保留)" },
18
+ { version: "7.146.0", file: "sealed-key", kind: "introduced", pointer: "", why: "首发:钥匙保管目录的安装契约(显式绝对 SEALED_KEY_DIR、整个目录跨重启持久且各副本共享、目录与文件的权限位)" },
19
+ ];
20
+ export function factsSchemaVersion(member, changes = FACTS_SCHEMA_CHANGES) {
21
+ const own = changes.filter((c) => c.file === member);
22
+ const intro = own.filter((c) => c.kind === "introduced");
23
+ const [first] = intro;
24
+ if (intro.length !== 1 || first === undefined)
25
+ throw new Error(`deploy-facts: member "${member}" needs exactly one "introduced" entry in FACTS_SCHEMA_CHANGES (found ${intro.length}) — its schemaVersion cannot be derived`);
26
+ return 1 + new Set(own.filter((c) => c.kind === "breaking" && c.version !== first.version).map((c) => c.version)).size;
27
+ }
28
+ export const LANE_ISOLATION_WORDS = ["supported", "unsupported"];
29
+ const envNames = (description) => ({ type: "array", uniqueItems: true, items: { type: "string" }, description });
30
+ export const LANE_FACTS_SCHEMA = {
31
+ $schema: FACTS_SCHEMA_DIALECT,
32
+ description: "引擎的执行车道事实:每条车道分不分租户、手跑在不在 OS 级隔离边界里、选它时引擎起服必须读到的键。行集 = 本包所依 settings-schema 车道表的全部车道词(含没有安装器接线的车道)。",
33
+ type: "object",
34
+ additionalProperties: false,
35
+ required: ["schemaVersion", "lanes"],
36
+ properties: {
37
+ schemaVersion: { type: "integer", const: factsSchemaVersion("lanes"), description: "本份数据的形版本;读方不认识的版本整份拒读。" },
38
+ lanes: {
39
+ type: "array",
40
+ minItems: 1,
41
+ "x-uniqueKey": ["lane"],
42
+ description: "每条车道一行,行序 = 车道表次序。",
43
+ items: {
44
+ type: "object",
45
+ additionalProperties: false,
46
+ required: ["lane", "tenantSeparation", "isolation", "requiredEnv", "requiredSecretEnv"],
47
+ properties: {
48
+ lane: { type: "string", description: "车道词,即 REMOTE_EXEC 的取值。" },
49
+ tenantSeparation: { type: "string", enum: TENANT_SEPARATION_WORDS, description: "supported = 可在多租户部署(REQUIRE_PRINCIPAL=true)上用;single-user-only = 只给单用户部署,多租户部署选它起服即拒。" },
50
+ isolation: { type: "string", enum: LANE_ISOLATION_WORDS, description: "supported = 手跑在 OS 级隔离边界里(独立沙箱);unsupported = 手跑在没有 OS 级隔离的地方(服务容器本身、远端主机、设备)。" },
51
+ requiredEnv: envNames("选这条车道时引擎起服必须读到的非密键(缺任一 ⇒ 起服拒)。"),
52
+ requiredSecretEnv: envNames("选这条车道时引擎起服必须读到的密钥键(缺任一 ⇒ 起服拒;值由运维自备)。"),
53
+ },
54
+ },
55
+ },
56
+ },
57
+ };
58
+ export const SEALED_KEY_REQUIREMENTS = ["persist-across-restart", "shared-by-all-replicas", "writable", "whole-directory"];
59
+ export const SEALED_KEY_DIR_PATH_FORMS = ["absolute"];
60
+ const octal = (mode) => mode.toString(8).padStart(4, "0");
61
+ const SEALED_KEY_DIR_ENV = "SEALED_KEY_DIR";
62
+ export const SEALED_KEY_FACTS = {
63
+ schemaVersion: factsSchemaVersion("sealed-key"),
64
+ dirEnv: SEALED_KEY_DIR_ENV,
65
+ dirPath: SEALED_KEY_DIR_PATH_FORMS[0],
66
+ subdirs: [CHECKPOINT_CTX_KEY_SUBDIR],
67
+ dirMode: octal(SEALED_KEY_DIR_MODE),
68
+ fileMode: octal(SEALED_KEY_FILE_MODE),
69
+ requirements: SEALED_KEY_REQUIREMENTS,
70
+ };
71
+ export const SEALED_KEY_FACTS_SCHEMA = {
72
+ $schema: FACTS_SCHEMA_DIALECT,
73
+ description: "引擎钥匙保管目录的安装契约:sealed 形模型钥匙的 X25519 钥匙对与停驻行凭据封签的钥匙都在这个目录里,私钥只存在这里。",
74
+ type: "object",
75
+ additionalProperties: false,
76
+ required: ["schemaVersion", "dirEnv", "dirPath", "subdirs", "dirMode", "fileMode", "requirements"],
77
+ properties: {
78
+ schemaVersion: { type: "integer", const: SEALED_KEY_FACTS.schemaVersion, description: "本份数据的形版本;读方不认识的版本整份拒读。" },
79
+ dirEnv: { type: "string", const: SEALED_KEY_FACTS.dirEnv, description: "安装器必须给引擎设的环境变量:值 = 保管目录的挂载位。" },
80
+ dirPath: { type: "string", enum: SEALED_KEY_DIR_PATH_FORMS, description: "absolute = 值必须是绝对路径(相对路径起服即拒;设了而目录建不了保管店也起服拒)。" },
81
+ subdirs: { type: "array", uniqueItems: true, items: { type: "string" }, description: "引擎在保管目录之下自建的子目录(随目录一起持久、一起共享)。" },
82
+ dirMode: { type: "string", const: SEALED_KEY_FACTS.dirMode, description: "引擎每次起服把保管目录设成的权限位(四位八进制);目录必须允许引擎进程改它的权限。" },
83
+ fileMode: { type: "string", const: SEALED_KEY_FACTS.fileMode, description: "引擎写钥匙文件时设的权限位(四位八进制)。" },
84
+ requirements: {
85
+ type: "array",
86
+ minItems: 1,
87
+ uniqueItems: true,
88
+ items: { type: "string", enum: SEALED_KEY_REQUIREMENTS },
89
+ description: "persist-across-restart = 目录跨进程重启保留(不然重启后封给旧钥匙的密文与停驻行凭据开不开);shared-by-all-replicas = 同一部署的每个副本挂同一份目录(多副本要支持多处读写的存储;不然 A 副本封的东西 B 副本开不开);" +
90
+ "writable = 引擎对目录可写(空目录起服时生成一把钥匙;只读挂载 ⇒ 建不了保管店);whole-directory = 持久与共享的单位是整个目录,含 subdirs 列出的子目录。",
91
+ },
92
+ },
93
+ };
94
+ //# sourceMappingURL=deploy-facts.js.map
@@ -38,6 +38,7 @@
38
38
  import http from "node:http";
39
39
  import { type DeviceOwner, type DeviceStore } from "./device-store.js";
40
40
  import type { DeviceEnrollment, DeviceRejection } from "./device-enrollment.js";
41
+ import { type CidrSet } from "./net-address.js";
41
42
  import { type DeviceChunkKind, type DeviceInstructionArgsByKind, type DeviceInstructionKind, type DeviceInstructionOutcome } from "./device-ws-protocol.js";
42
43
  /** 交给调用方的一片上行流(`data` 已从 base64 解回字节)。 */
43
44
  export interface DeviceChunkDelivery {
@@ -159,10 +160,14 @@ export interface DeviceWsHubOptions {
159
160
  chunkBufferMaxBytes?: number;
160
161
  /** codex R1-F3:本副本**未鉴权**连接的并发帽(超额在 upgrade 阶段就 503 拒)。 */
161
162
  maxPreAuthConnections?: number;
162
- /** codex R1-F3:单来源地址的未鉴权连接并发帽。[ref]([ref]):字面 `off` = 显式停用该层——
163
- * 帽键是裸 remoteAddress,XFF 反代 / 公司 NAT 部署形把全员折叠成单键,这层从公平层退化成
164
- * 「全员共享一份预算」;停层后全局帽(`maxPreAuthConnections`)照常兜底。 */
163
+ /** codex R1-F3:单来源地址的未鉴权连接并发帽。[ref]([ref]):字面 `off` = 显式停用该层;停层后全局帽
164
+ * (`maxPreAuthConnections`)照常兜底。帽键 = 真实对端的规范形({@link readPreAuthRemoteKey}):名单
165
+ * {@link trustedProxyCidrs} 空时就是 socket 对端 —— 反代 / 公司 NAT 后全员同一 socket 对端,这层是「全员共享一份预算」;
166
+ * 把自己的反代写进名单,这层就按反代转来的真实来源计。 */
165
167
  maxPreAuthConnectionsPerRemote?: number | "off";
168
+ /** [ref]:可信代理名单(CIDR 闭集;env `DEVICE_TRUSTED_PROXY_CIDRS`,配置层逐项校验)。缺省空 = 不读任何代理头(帽键 =
169
+ * socket 对端,今日行为)。socket 对端落在名单内才读 `X-Forwarded-For`。坏项构造时抛(装配缝,与 helloAck 界同姿势)。 */
170
+ trustedProxyCidrs?: readonly string[];
166
171
  }
167
172
  export interface DeviceWsHub {
168
173
  /** 把 `GET /v1/device/ws` 的 upgrade 处理器挂到裸 `http.Server` 上。 */
@@ -204,5 +209,45 @@ export interface DeviceWsHub {
204
209
  declaredSupports(deviceId: string): ReadonlySet<DeviceInstructionKind> | undefined;
205
210
  close(): Promise<void>;
206
211
  }
212
+ /** per-remote 帽键的来源(闭集;指标 `device_preauth_key_total{source}` 与 cap-hit 行 `keySource` 同一张表)。 */
213
+ export declare const DEVICE_PREAUTH_KEY_SOURCES: readonly ["socket", "xff", "fallback"];
214
+ export type DevicePreAuthKeySource = (typeof DEVICE_PREAUTH_KEY_SOURCES)[number];
215
+ /** 对端是可信代理而转发头展不开的缘由(闭集;warn `device_preauth_forwarded_unusable{reason}`)。 */
216
+ export declare const DEVICE_FORWARDED_UNUSABLE_REASONS: readonly ["absent", "malformed", "all_trusted", "too_many_headers"];
217
+ export type DeviceForwardedUnusableReason = (typeof DEVICE_FORWARDED_UNUSABLE_REASONS)[number];
218
+ export type DevicePreAuthKey = {
219
+ readonly key: string;
220
+ readonly source: "socket" | "xff";
221
+ } | {
222
+ readonly key: string;
223
+ readonly source: "fallback";
224
+ readonly reason: DeviceForwardedUnusableReason;
225
+ };
226
+ /**
227
+ * 可信对端的升级请求最多带几行头,汇聚端还认它的 `X-Forwarded-For`。node 对头行数超过它的上限(`server.maxHeadersCount`,缺省约 1000 行)
228
+ * 的请求要么**静默**丢掉多余的头、要么直接答 431(版本行为)。丢头那一形里,反代追加在末尾的那一行 XFF 正在尾部,丢了它,剩下的就只有
229
+ * 客户端自己垫在前面的那一行 —— 所以行数到这个数(或运维给 server 设的更低上限)⇒ 不采 XFF(回落 `too_many_headers`);答 431 那一形里
230
+ * 请求到不了这里。两形下垫在前面的 XFF 都不会成为帽键。一次设备握手连同反代自己加的头远不到 100 行;这个数刻意远低于 node 的缺省上限,
231
+ * 不随 node 版本漂。
232
+ */
233
+ export declare const DEVICE_PREAUTH_FORWARDED_HEADER_LINES_MAX = 100;
234
+ /** 转发头的一次读数(只在对端可信时取):`header` = node 合并后的 `X-Forwarded-For`;`complete` = 请求头行数低于可信读数上限。 */
235
+ export interface ForwardedForReading {
236
+ readonly header: string | readonly string[] | undefined;
237
+ readonly complete: boolean;
238
+ }
239
+ /**
240
+ * [ref]:per-remote 帽键 = **本连接真实对端的规范形**(`canonicalAddress`;IPv4-mapped 折成点分 IPv4 —— 同一台主机直连与经代理
241
+ * 是同一个键,不是两份名额)。
242
+ *
243
+ * 真实对端只有一个来源 = socket 对端;唯一的展开:socket 对端落在可信代理名单内 ⇒ 读 `X-Forwarded-For`(node 把重复头按到达序
244
+ * 以 `, ` 合并 = 同一张列表),**自右向左**逐项:名单内 = 又一层自己的代理,继续左移;第一个名单外的地址 = 键。选中项**以左**是
245
+ * 客户端自己写的段,不解析 —— 往那里塞什么都改不了键,也逼不出回落。
246
+ *
247
+ * 展不开(头行数到顶 / 头缺席 / 某项不是地址 / 全在名单内)⇒ 键 = socket 对端,`source=fallback`。🔴 这不是 fail-open,**不走 `recordFailOpen`**:
248
+ * 回落键把整只代理折成一个键,比展开更严(收紧方向),与名单空的今日行为同键;调用方另打一行带 reason 的 warn 指路。
249
+ * 名单外的对端:转发头读口一次都不调用(惰性 `readForwardedFor`;伪造 XFF 零效果)。不读 `Forwarded` / `X-Real-IP` / PROXY protocol —— 对端只有这一种展开。
250
+ */
251
+ export declare function readPreAuthRemoteKey(peer: string | undefined, readForwardedFor: () => ForwardedForReading, trusted: CidrSet): DevicePreAuthKey;
207
252
  export declare function createDeviceWsHub(options: DeviceWsHubOptions): DeviceWsHub;
208
253
  //# sourceMappingURL=device-ws-hub.d.ts.map