@sema-agent/server 7.144.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 (305) hide show
  1. package/MIGRATION.md +621 -0
  2. package/README.md +3 -0
  3. package/README.zh-CN.md +1 -0
  4. package/USAGE.md +233 -54
  5. package/deploy/facts/CHANGES.json +25 -0
  6. package/deploy/facts/README.md +57 -0
  7. package/deploy/facts/lanes.json +54 -0
  8. package/deploy/facts/lanes.schema.json +57 -0
  9. package/deploy/facts/sandbox-rbac.json +113 -0
  10. package/deploy/facts/sandbox-rbac.schema.json +74 -0
  11. package/deploy/facts/sealed-key.json +9 -0
  12. package/deploy/facts/sealed-key.schema.json +52 -0
  13. package/deploy/sema-up/chart/templates/_helpers.tpl +19 -0
  14. package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +47 -7
  15. package/deploy/sema-up/chart/templates/server-v4pro.yaml +4 -0
  16. package/deploy/sema-up/chart/templates/server.yaml +4 -0
  17. package/deploy/sema-up/chart/values.yaml +7 -0
  18. package/deploy/sema-up/stack/sema-stack-cluster.yaml +30 -14
  19. package/dist/adoption/import-limits.d.ts +6 -0
  20. package/dist/adoption/import-limits.js +7 -0
  21. package/dist/adoption/import.d.ts +62 -0
  22. package/dist/adoption/import.js +107 -0
  23. package/dist/adoption/wire.d.ts +52 -0
  24. package/dist/adoption/wire.js +9 -0
  25. package/dist/approval-ask-machine.d.ts +2 -2
  26. package/dist/approval-card.d.ts +3 -12
  27. package/dist/approval-card.js +0 -4
  28. package/dist/approval-gate-currency.d.ts +1 -1
  29. package/dist/approval-gate-currency.js +1 -1
  30. package/dist/approval-reconciler.d.ts +1 -1
  31. package/dist/audit.d.ts +2 -21
  32. package/dist/audit.js +0 -9
  33. package/dist/boot/deployment-mods.d.ts +11 -1
  34. package/dist/boot/deployment-mods.js +15 -2
  35. package/dist/boot/device-lane.js +2 -0
  36. package/dist/boot/engine-lease.d.ts +193 -21
  37. package/dist/boot/engine-lease.js +146 -20
  38. package/dist/boot/execution-env.d.ts +4 -0
  39. package/dist/boot/execution-env.js +12 -0
  40. package/dist/boot/exit-boundary.d.ts +50 -0
  41. package/dist/boot/exit-boundary.js +32 -0
  42. package/dist/boot/exit-last.d.ts +20 -4
  43. package/dist/boot/exit-last.js +21 -4
  44. package/dist/boot/governance-seams.js +1 -0
  45. package/dist/boot/memory-consolidation.d.ts +29 -17
  46. package/dist/boot/memory-consolidation.js +26 -15
  47. package/dist/boot/reapers.d.ts +12 -4
  48. package/dist/boot/reapers.js +39 -15
  49. package/dist/boot/resolve-spec.d.ts +2 -0
  50. package/dist/boot/resolve-spec.js +38 -15
  51. package/dist/boot/runner-deps.js +1 -1
  52. package/dist/boot/session-faces.d.ts +1 -1
  53. package/dist/boot/session-faces.js +6 -4
  54. package/dist/boot/shutdown.js +27 -9
  55. package/dist/boot/stage-01-config.js +1 -1
  56. package/dist/boot/stage-03-store-backend.d.ts +1 -0
  57. package/dist/boot/stage-03-store-backend.js +2 -2
  58. package/dist/boot/stage-04-stores.d.ts +1 -0
  59. package/dist/boot/stage-05-execution-env.d.ts +3 -0
  60. package/dist/boot/stage-05-execution-env.js +3 -3
  61. package/dist/boot/stage-06-runners.d.ts +3 -0
  62. package/dist/boot/stage-06-runners.js +11 -10
  63. package/dist/boot/stage-07-capability-layer.d.ts +3 -0
  64. package/dist/boot/stage-07-capability-layer.js +7 -2
  65. package/dist/boot/stage-08-reapers.d.ts +3 -0
  66. package/dist/boot/stage-08-reapers.js +3 -3
  67. package/dist/boot/stage-09-leader.d.ts +4 -1
  68. package/dist/boot/stage-10-http-server.d.ts +4 -1
  69. package/dist/boot/stage-10-http-server.js +4 -3
  70. package/dist/boot/stores.d.ts +1 -0
  71. package/dist/boot/stores.js +35 -19
  72. package/dist/boot/workflow-orchestration.d.ts +1 -1
  73. package/dist/boot/workflow-orchestration.js +2 -1
  74. package/dist/capabilities/composite-members.d.ts +3 -1
  75. package/dist/capabilities/composite-members.js +3 -1
  76. package/dist/capabilities/scenarios.d.ts +2 -2
  77. package/dist/capability-facts.d.ts +18 -1
  78. package/dist/capability-facts.js +25 -7
  79. package/dist/checkpoint-ctx-seal-m28.d.ts +1 -1
  80. package/dist/config-catalog.js +13 -4
  81. package/dist/config-d-migration.d.ts +3 -2
  82. package/dist/config-provider.d.ts +2 -2
  83. package/dist/config-types.d.ts +43 -4
  84. package/dist/config.d.ts +6 -1
  85. package/dist/config.js +67 -11
  86. package/dist/deleted-session-ids.d.ts +19 -0
  87. package/dist/deleted-session-ids.js +26 -0
  88. package/dist/deploy-facts.d.ts +155 -0
  89. package/dist/deploy-facts.js +94 -0
  90. package/dist/device-ws-hub.d.ts +48 -3
  91. package/dist/device-ws-hub.js +48 -2
  92. package/dist/error-code-family.d.ts +35 -0
  93. package/dist/error-code-family.js +26 -0
  94. package/dist/fleet/drain-entry.d.ts +39 -3
  95. package/dist/fleet/drain-entry.js +6 -0
  96. package/dist/fleet/fleet-bus.d.ts +15 -4
  97. package/dist/fleet/fleet-bus.js +17 -3
  98. package/dist/hooks/branch-transcript.d.ts +1 -1
  99. package/dist/hooks/branch-transcript.js +1 -0
  100. package/dist/hooks/classic-stations.d.ts +25 -8
  101. package/dist/hooks/classic-stations.js +29 -3
  102. package/dist/hooks/hook-runner.d.ts +33 -16
  103. package/dist/hooks/hook-runner.js +166 -73
  104. package/dist/hooks/hook-source.d.ts +14 -2
  105. package/dist/hooks/hook-source.js +8 -0
  106. package/dist/http/active-run-conflict.d.ts +1 -1
  107. package/dist/http/active-run-conflict.js +1 -1
  108. package/dist/http/admission.js +13 -2
  109. package/dist/http/checkpoint-ctx.d.ts +60 -71
  110. package/dist/http/checkpoint-ctx.js +35 -40
  111. package/dist/http/dispatch.js +18 -2
  112. package/dist/http/durable-run-leg.d.ts +5 -0
  113. package/dist/http/durable-run-leg.js +8 -6
  114. package/dist/http/execution-context.d.ts +4 -0
  115. package/dist/http/execution-context.js +9 -0
  116. package/dist/http/health-self-report.d.ts +6 -2
  117. package/dist/http/health-self-report.js +3 -2
  118. package/dist/http/memory-compliance-failure-reply.d.ts +1 -1
  119. package/dist/http/memory-compliance-failure-reply.js +2 -15
  120. package/dist/http/memory-control-plane-corrupt-reply.d.ts +25 -0
  121. package/dist/http/memory-control-plane-corrupt-reply.js +19 -0
  122. package/dist/http/principal-gate.d.ts +35 -12
  123. package/dist/http/principal-gate.js +16 -12
  124. package/dist/http/request-guards.d.ts +1 -1
  125. package/dist/http/request-guards.js +5 -5
  126. package/dist/http/resume-legs.d.ts +22 -0
  127. package/dist/http/resume-legs.js +31 -20
  128. package/dist/http/route-ctx.d.ts +26 -10
  129. package/dist/http/route-table.js +2 -0
  130. package/dist/http/routes/adoption.d.ts +11 -0
  131. package/dist/http/routes/adoption.js +80 -6
  132. package/dist/http/routes/agents-roster.js +1 -0
  133. package/dist/http/routes/approvals-assistant.d.ts +20 -1
  134. package/dist/http/routes/approvals-assistant.js +29 -7
  135. package/dist/http/routes/background.js +1 -0
  136. package/dist/http/routes/capabilities.js +4 -1
  137. package/dist/http/routes/diagnostics.d.ts +9 -14
  138. package/dist/http/routes/diagnostics.js +10 -20
  139. package/dist/http/routes/engine.d.ts +31 -0
  140. package/dist/http/routes/engine.js +81 -0
  141. package/dist/http/routes/memory-bundle.js +3 -0
  142. package/dist/http/routes/memory-consolidation.js +3 -5
  143. package/dist/http/routes/memory-origin.d.ts +1 -1
  144. package/dist/http/routes/memory-origin.js +2 -4
  145. package/dist/http/routes/memory-policy.js +5 -1
  146. package/dist/http/routes/observability.js +3 -6
  147. package/dist/http/routes/rules.d.ts +1 -1
  148. package/dist/http/routes/rules.js +5 -2
  149. package/dist/http/routes/runs.js +7 -3
  150. package/dist/http/routes/session-sync.js +24 -25
  151. package/dist/http/routes/sessions-list.d.ts +1 -1
  152. package/dist/http/routes/sessions.js +4 -0
  153. package/dist/http/routes/tasks.js +35 -14
  154. package/dist/http/routes/trace-usage.js +1 -1
  155. package/dist/http/run-concurrency-cap.d.ts +86 -0
  156. package/dist/http/run-concurrency-cap.js +54 -0
  157. package/dist/http/run-meta.d.ts +14 -7
  158. package/dist/http/run-meta.js +5 -2
  159. package/dist/http/server.d.ts +27 -16
  160. package/dist/http/server.js +21 -3
  161. package/dist/http/stored-body-replay.d.ts +9 -6
  162. package/dist/http/stored-body-replay.js +19 -5
  163. package/dist/http/submit-ctx.d.ts +12 -0
  164. package/dist/http/submit-ctx.js +23 -0
  165. package/dist/http/wire-types.d.ts +8 -3
  166. package/dist/json-body-limit.d.ts +8 -0
  167. package/dist/json-body-limit.js +2 -0
  168. package/dist/leader/diffout.d.ts +1 -1
  169. package/dist/local-data-root.d.ts +2 -2
  170. package/dist/memory-operator-faces.d.ts +9 -4
  171. package/dist/memory-scope-stores.d.ts +9 -7
  172. package/dist/memory-scope-stores.js +3 -14
  173. package/dist/memory-scope.d.ts +36 -0
  174. package/dist/memory-scope.js +32 -1
  175. package/dist/net-address.d.ts +53 -0
  176. package/dist/net-address.js +111 -0
  177. package/dist/observability/fail-open.d.ts +28 -12
  178. package/dist/observability/fail-open.js +28 -12
  179. package/dist/orchestration/workflow-notify-journal.js +2 -3
  180. package/dist/org-delivery-gate.js +1 -1
  181. package/dist/peer-directory.d.ts +6 -12
  182. package/dist/peer-directory.js +5 -1
  183. package/dist/plugins/approval-ask-store-file.d.ts +16 -2
  184. package/dist/plugins/approval-ask-store-file.js +21 -1
  185. package/dist/plugins/approval-ask-store-memory.d.ts +8 -2
  186. package/dist/plugins/approval-ask-store-memory.js +11 -8
  187. package/dist/plugins/caching-session-store.d.ts +6 -1
  188. package/dist/plugins/caching-session-store.js +6 -0
  189. package/dist/plugins/checkpoint-store-face.d.ts +426 -0
  190. package/dist/plugins/checkpoint-store-face.js +152 -0
  191. package/dist/plugins/file-run-store.d.ts +13 -7
  192. package/dist/plugins/file-run-store.js +12 -13
  193. package/dist/plugins/fork-routing-session-store.d.ts +6 -2
  194. package/dist/plugins/fork-routing-session-store.js +2 -2
  195. package/dist/plugins/imported-layer-root-retirement.d.ts +10 -8
  196. package/dist/plugins/local-checkpoint-store.d.ts +16 -5
  197. package/dist/plugins/local-checkpoint-store.js +25 -6
  198. package/dist/plugins/local-session-store.d.ts +80 -21
  199. package/dist/plugins/local-session-store.js +79 -22
  200. package/dist/plugins/memory-engine-pg.d.ts +29 -6
  201. package/dist/plugins/memory-engine-pg.js +96 -214
  202. package/dist/plugins/memory-engine-sql-apply.d.ts +121 -0
  203. package/dist/plugins/memory-engine-sql-apply.js +358 -0
  204. package/dist/plugins/memory-engine-tidb.d.ts +35 -9
  205. package/dist/plugins/memory-engine-tidb.js +97 -227
  206. package/dist/plugins/memory-key-guards.d.ts +1 -1
  207. package/dist/plugins/memory-origin-law.d.ts +3 -3
  208. package/dist/plugins/memory-run-store.d.ts +2 -0
  209. package/dist/plugins/memory-run-store.js +6 -0
  210. package/dist/plugins/one-time-migrations.d.ts +32 -56
  211. package/dist/plugins/one-time-migrations.js +27 -119
  212. package/dist/plugins/permission-rule-store-file.d.ts +17 -3
  213. package/dist/plugins/permission-rule-store-file.js +8 -1
  214. package/dist/plugins/pg-pool.js +1 -1
  215. package/dist/plugins/pg-session-storage.d.ts +17 -4
  216. package/dist/plugins/pg-session-storage.js +8 -4
  217. package/dist/plugins/run-store-sql.d.ts +4 -0
  218. package/dist/plugins/run-store-sql.js +5 -2
  219. package/dist/plugins/schema-ensure-legs.js +2 -2
  220. package/dist/plugins/session-fork.d.ts +31 -9
  221. package/dist/plugins/session-fork.js +3 -2
  222. package/dist/plugins/session-placement.d.ts +4 -2
  223. package/dist/plugins/session-store.d.ts +1 -1
  224. package/dist/plugins/sql-json-column.d.ts +5 -1
  225. package/dist/plugins/store-backend.d.ts +11 -8
  226. package/dist/plugins/store-backend.js +8 -7
  227. package/dist/plugins/store-contracts.d.ts +1 -11
  228. package/dist/plugins/store-contracts.js +0 -9
  229. package/dist/plugins/store-sweep.d.ts +63 -0
  230. package/dist/plugins/store-sweep.js +48 -0
  231. package/dist/plugins/tidb-pool.js +1 -1
  232. package/dist/plugins/tidb-session-store.d.ts +16 -3
  233. package/dist/plugins/tidb-session-store.js +8 -4
  234. package/dist/prompt-screen.d.ts +18 -0
  235. package/dist/prompt-screen.js +5 -0
  236. package/dist/rules-consent.d.ts +56 -51
  237. package/dist/rules-consent.js +86 -14
  238. package/dist/run-acceptance.d.ts +40 -10
  239. package/dist/run-acceptance.js +38 -5
  240. package/dist/run-cancel-context.d.ts +4 -1
  241. package/dist/run-cancel-context.js +2 -1
  242. package/dist/runs.js +2 -2
  243. package/dist/sandbox-egress-policy.js +6 -11
  244. package/dist/sandbox-rbac-rules.d.ts +88 -0
  245. package/dist/sandbox-rbac-rules.js +80 -0
  246. package/dist/sealed-key-layout.d.ts +17 -0
  247. package/dist/sealed-key-layout.js +4 -0
  248. package/dist/sealed-key.js +5 -5
  249. package/dist/security.d.ts +48 -9
  250. package/dist/security.js +9 -2
  251. package/dist/session-agent.d.ts +39 -0
  252. package/dist/session-agent.js +16 -0
  253. package/dist/session-error-wire.d.ts +4 -4
  254. package/dist/session-notify-door.d.ts +5 -3
  255. package/dist/session-resident-background.d.ts +57 -0
  256. package/dist/session-resident-background.js +20 -0
  257. package/dist/session-sync-kernel.d.ts +53 -14
  258. package/dist/session-sync-kernel.js +70 -1
  259. package/dist/session-sync.d.ts +5 -3
  260. package/dist/session-workspace.d.ts +1 -0
  261. package/dist/session-workspace.js +1 -0
  262. package/dist/sighup-idle.d.ts +9 -7
  263. package/dist/sighup-idle.js +4 -5
  264. package/dist/spec-fields.d.ts +16 -0
  265. package/dist/spec-fields.js +7 -0
  266. package/dist/stored-request-env-credentials-m38.d.ts +3 -0
  267. package/dist/stored-request-env-credentials-m38.js +8 -0
  268. package/dist/stored-request-prompt-profile-m30.d.ts +1 -1
  269. package/dist/{plugins → stores-sql}/checkpoint-store-sql.d.ts +33 -429
  270. package/dist/{plugins → stores-sql}/checkpoint-store-sql.js +70 -209
  271. package/dist/{plugins → stores-sql}/mailbox-store-sql.d.ts +2 -2
  272. package/dist/{plugins → stores-sql}/mailbox-store-sql.js +4 -4
  273. package/dist/{plugins → stores-sql}/roster-store-sql.d.ts +2 -2
  274. package/dist/{plugins → stores-sql}/roster-store-sql.js +1 -1
  275. package/dist/{plugins → stores-sql}/tool-result-store-sql.d.ts +2 -2
  276. package/dist/{plugins → stores-sql}/tool-result-store-sql.js +6 -6
  277. package/dist/{plugins → stores-sql}/workflow-journal-store-sql.d.ts +7 -12
  278. package/dist/{plugins → stores-sql}/workflow-journal-store-sql.js +3 -8
  279. package/dist/task-settings.d.ts +59 -4
  280. package/dist/task-settings.js +50 -4
  281. package/dist/tool-approval.d.ts +2 -2
  282. package/dist/trace/core-keyset-guard.d.ts +8 -7
  283. package/dist/trace/engine-notice-wire.d.ts +1 -1
  284. package/dist/trace/engine-notice-wire.js +2 -0
  285. package/dist/trace/exit-seat.d.ts +6 -0
  286. package/dist/trace/exit-seat.js +1 -0
  287. package/dist/trace/model-segment.d.ts +3 -2
  288. package/dist/trace/model-segment.js +16 -8
  289. package/dist/trace/project.js +9 -0
  290. package/dist/trace/redact.d.ts +79 -4
  291. package/dist/trace/redact.js +523 -123
  292. package/dist/trace/self-minted-wire.d.ts +391 -1
  293. package/dist/trace/self-minted-wire.js +145 -0
  294. package/dist/trace/wire-projection-faces.d.ts +1 -1
  295. package/dist/trace/wire-projection-faces.js +1 -1
  296. package/dist/usage-analytics.d.ts +4 -0
  297. package/package.json +12 -4
  298. package/scripts/drain-check-local.mjs +8 -29
  299. package/scripts/drain-check.mysql.sql +3 -24
  300. package/scripts/drain-check.pg.sql +3 -24
  301. package/skills/update-config.md +1 -1
  302. package/wire-changes/server@7.145.0.json +146 -0
  303. package/wire-changes/server@7.146.0.json +3221 -0
  304. package/dist/approval-batch-census.d.ts +0 -27
  305. package/dist/approval-batch-census.js +0 -27
package/dist/runs.js CHANGED
@@ -285,7 +285,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
285
285
  const startedAt = Date.now();
286
286
  metrics?.addGauge("runs_active", 1);
287
287
  fleetPublisher?.onStart();
288
- const cancelCtrl = createRunAbort();
288
+ const cancelCtrl = createRunAbort(principal ?? null);
289
289
  inflight?.set(taskId, cancelCtrl);
290
290
  markChildrenStoppedByUserOnAbort(cancelCtrl.signal, taskId, principal);
291
291
  const preemptEligible = verify === undefined && cascade === undefined;
@@ -486,7 +486,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
486
486
  }
487
487
  fleetPublisher?.onEvent(ev);
488
488
  await userInputLedger?.observeLegEvent(stream, ev);
489
- acceptance?.observe(ev);
489
+ await acceptance?.observe(ev);
490
490
  await workspaceRecorder?.observe(ev);
491
491
  await flushPromptManifest();
492
492
  switch (ev.type) {
@@ -1,4 +1,5 @@
1
- import { BlockList, isIP } from "node:net";
1
+ import { isIP } from "node:net";
2
+ import { createCidrSet, parseCidr as readCidrText } from "./net-address.js";
2
3
  import { recordFailOpen } from "./observability/fail-open.js";
3
4
  import { seatText } from "./trace/exit-seat.js";
4
5
  export const SANDBOX_IPV6_WORDS = ["enabled", "disabled"];
@@ -155,22 +156,16 @@ function nodeAddresses(json) {
155
156
  return out;
156
157
  }
157
158
  function parseCidr(cidr) {
158
- const slash = cidr.indexOf("/");
159
- const net = slash < 0 ? cidr : cidr.slice(0, slash);
160
- const fam = isIP(net);
161
- const max = fam === 6 ? 128 : 32;
162
- const prefix = slash < 0 ? max : Number(cidr.slice(slash + 1));
163
- if (fam === 0 || !/^\d+$/.test(slash < 0 ? String(max) : cidr.slice(slash + 1)) || prefix > max)
159
+ const r = readCidrText(cidr);
160
+ if (!r.ok)
164
161
  throw new Unjudgeable(`ipBlock cidr ${JSON.stringify(cidr)} does not parse`);
165
- return { net, prefix, family: fam === 6 ? "ipv6" : "ipv4" };
162
+ return r.cidr;
166
163
  }
167
164
  function inCidr(address, cidr) {
168
165
  const c = parseCidr(cidr);
169
166
  if ((isIP(address) === 6 ? "ipv6" : "ipv4") !== c.family)
170
167
  return false;
171
- const list = new BlockList();
172
- list.addSubnet(c.net, c.prefix, c.family);
173
- return list.check(address, c.family);
168
+ return createCidrSet([c]).has(address);
174
169
  }
175
170
  function ruleAllows(rule, address) {
176
171
  if (rule.to === undefined)
@@ -0,0 +1,88 @@
1
+ /**
2
+ * 沙箱车道(`REMOTE_EXEC=k8s`)的服务账号在沙箱命名空间 / 集群里需要哪些 RBAC 规则,以及每条规则由哪个引擎特性开关决定 ——
3
+ * 「引擎需要哪条规则由哪个开关决定」**只写在这一张表里**。
4
+ *
5
+ * ## 谁读它(都是读者,没有第二份)
6
+ * · `scripts/gen-deploy-lanes.ts`:把本表渲成部署产物 —— chart `templates/sandbox-rbac.yaml` 的 Role / ClusterRole 规则区(开关条件规则包在 helm 条件里)、
7
+ * chart `templates/_helpers.tpl` 里把同一个开关值写进引擎旋钮的助手体、cluster 静态清单的同两个规则区(静态清单不能条件渲,引擎 env 里两个旋钮都没设,
8
+ * 开关条件规则渲成注释掉的规则块)、`docs/DEPLOY-PREREQS.md` 的规则表区,以及给取用方的只读数据 `deploy/facts/sandbox-rbac.json`。
9
+ * 「入库文件 ≡ 生成物」由 `--check` 与 `test/deploy-lane-projections.test.ts` 钉;行为由 `test/sandbox-rbac-table.test.ts` 钉。
10
+ * · secrets 那一行的动词**直接就是** {@link SANDBOX_SECRET_SEAT_VERBS}`.env`(起服门问的、适配器真发的同一张表)—— 不另抄一份。
11
+ * pvc 那一行的动词 ⊇ 适配器对 `persistentvolumeclaims` 真发的每个调用(`test/sandbox-rbac-table.test.ts` 读 `remote-env-k8s.ts` 对拍)。
12
+ *
13
+ * ## 形
14
+ * · `when`:`"always"`(恒授)或一个**特性开关词**({@link SANDBOX_RBAC_SWITCH_WORDS},闭集;加词必须同时给 {@link SANDBOX_RBAC_SWITCHES} 一行 —— 缺行是编译错)。
15
+ * · 开关 = 引擎旋钮的一个状态:`bool` 形 = 旋钮取某个词时开(`WORKSPACE_CACHE=volume`),`names` 形 = 旋钮是 NAME,NAME 名单、非空时开(`K8S_SANDBOX_ENV`)。
16
+ * 随包 chart 里同一个值(`chartValue`)既写引擎旋钮、又决定 Role 带不带那几行规则。
17
+ * · `need`:这一行的动词引擎适配器**真调**(`called`,`why` 里写着调用它的适配器方法〔`remote-env-k8s.ts#<方法名>`〕,源码钉对拍)还是只是**兼容保留**
18
+ * (`retained`:chart 与静态清单一直授着、适配器源码里零调用)。同一个资源的动词按 `need` 拆成两行;chart 与静态清单把同资源的行并回一条规则照授(本版不删 `retained`)。
19
+ * · 给取用方的数据 `deploy/facts/sandbox-rbac.json` = `{ schemaVersion, rules: [{ scope, apiGroups, resources, verbs, when, need, why }] }`(随包;npm 子路径同名),
20
+ * 形由本文件的 {@link SANDBOX_RBAC_FACTS_SCHEMA} 定义(和表放一起;生成器把它写成 `deploy/facts/sandbox-rbac.schema.json`)。
21
+ * 形变的加性 / 破坏性判法、登记与 `schemaVersion` 的派生是随包部署事实的统一规则,见 `src/deploy-facts.ts` 头注 —— 本表不另立一套。
22
+ */
23
+ import type { WorkspaceCacheWord } from "./workspace-cache.js";
24
+ import { type FactsRootSchema } from "./deploy-facts.js";
25
+ /** `deploy/facts/sandbox-rbac.json` 的形版本(从形变登记表派生,见 `src/deploy-facts.ts` 的 `factsSchemaVersion`)。 */
26
+ export declare const SANDBOX_RBAC_SCHEMA_VERSION: number;
27
+ export declare const SANDBOX_RBAC_SCOPES: readonly ["Role", "ClusterRole"];
28
+ export type SandboxRbacScope = (typeof SANDBOX_RBAC_SCOPES)[number];
29
+ /** Kubernetes RBAC 动词闭集(规则只收这些词)。 */
30
+ export declare const SANDBOX_RBAC_VERBS: readonly ["get", "list", "watch", "create", "update", "patch", "delete", "deletecollection"];
31
+ export declare const SANDBOX_RBAC_ALWAYS: "always";
32
+ /** 特性开关词:引擎旋钮的一个状态。词表是闭集 —— {@link SANDBOX_RBAC_SWITCHES} 对它做穷尽映射(新增一个词而不给开关行 ⇒ 编译错)。 */
33
+ export declare const SANDBOX_RBAC_SWITCH_WORDS: readonly ["WORKSPACE_CACHE=volume", "K8S_SANDBOX_ENV"];
34
+ export type SandboxRbacSwitchWord = (typeof SANDBOX_RBAC_SWITCH_WORDS)[number];
35
+ /** `when` 的全词表:恒授 + 开关词。 */
36
+ export declare const SANDBOX_RBAC_WHEN_WORDS: readonly ["always", "WORKSPACE_CACHE=volume", "K8S_SANDBOX_ENV"];
37
+ export type SandboxRbacWhen = (typeof SANDBOX_RBAC_WHEN_WORDS)[number];
38
+ export type SandboxRbacSwitch = {
39
+ /** 旋钮取 {@link envValue} 时开。 */
40
+ readonly kind: "bool";
41
+ readonly env: string;
42
+ readonly envValue: WorkspaceCacheWord;
43
+ /** 随包 chart 的值路径,恒 `sandbox.<键>`;布尔。 */
44
+ readonly chartValue: string;
45
+ readonly meaning: string;
46
+ } | {
47
+ /** 旋钮是 NAME,NAME 名单,非空时开。 */
48
+ readonly kind: "names";
49
+ readonly env: string;
50
+ /** 随包 chart 的值路径,恒 `sandbox.<键>`;名单字符串。 */
51
+ readonly chartValue: string;
52
+ readonly meaning: string;
53
+ };
54
+ export declare const SANDBOX_RBAC_SWITCHES: {
55
+ readonly [W in SandboxRbacSwitchWord]: SandboxRbacSwitch;
56
+ };
57
+ /** 一行动词的引擎用度:`called` = 适配器源码真调;`retained` = 兼容保留(chart 有、适配器零调用)。 */
58
+ export declare const SANDBOX_RBAC_NEEDS: readonly ["called", "retained"];
59
+ export type SandboxRbacNeed = (typeof SANDBOX_RBAC_NEEDS)[number];
60
+ export interface SandboxRbacRule {
61
+ readonly scope: SandboxRbacScope;
62
+ readonly apiGroups: readonly string[];
63
+ readonly resources: readonly string[];
64
+ readonly verbs: readonly string[];
65
+ readonly when: SandboxRbacWhen;
66
+ readonly need: SandboxRbacNeed;
67
+ /** 这一行为什么在(渲成产物里规则上方的注释;也是 `deploy/facts/sandbox-rbac.json` 的 `why`);`called` 行写着调用方的 `remote-env-k8s.ts#<方法名>` 坐标。 */
68
+ readonly why: string;
69
+ }
70
+ /**
71
+ * 规则表(表序 = 产物里的规则序)。`called` 行的调用坐标 = `remote-env-k8s.ts` 里的方法名(不写行号,免得每改一行适配器就漂);
72
+ * `test/sandbox-rbac-table.test.ts` 读该文件逐方法核「这一行的动词 ⊆ 那些方法真发的调用」,并核 `retained` 行没有任何调用。
73
+ */
74
+ export declare const SANDBOX_RBAC_RULES: readonly SandboxRbacRule[];
75
+ /** 一份完整的表(生成器的输入;测试用变异夹具替换它)。 */
76
+ export interface SandboxRbacModel {
77
+ readonly switches: {
78
+ readonly [W in SandboxRbacSwitchWord]: SandboxRbacSwitch;
79
+ };
80
+ readonly rules: readonly SandboxRbacRule[];
81
+ }
82
+ export declare const SANDBOX_RBAC_MODEL: SandboxRbacModel;
83
+ /**
84
+ * `deploy/facts/sandbox-rbac.json` 的 Schema(随包方言,见 `src/deploy-facts.ts`)。闭集词表直接取本文件的常量:加一个 `when` / `need` / 范围 / 动词词
85
+ * = 闭集加词 = 破坏性形变(读方对闭集做穷尽判别),要登记、要抬版。一条规则的身份 = (范围, apiGroups, resources, need)—— 同一资源按 `need` 拆成两行。
86
+ */
87
+ export declare const SANDBOX_RBAC_FACTS_SCHEMA: FactsRootSchema;
88
+ //# sourceMappingURL=sandbox-rbac-rules.d.ts.map
@@ -0,0 +1,80 @@
1
+ import { SANDBOX_SECRET_SEAT_VERBS } from "./plugins/k8s-exec-protocol.js";
2
+ import { FACTS_SCHEMA_DIALECT, factsSchemaVersion } from "./deploy-facts.js";
3
+ export const SANDBOX_RBAC_SCHEMA_VERSION = factsSchemaVersion("sandbox-rbac");
4
+ export const SANDBOX_RBAC_SCOPES = ["Role", "ClusterRole"];
5
+ export const SANDBOX_RBAC_VERBS = ["get", "list", "watch", "create", "update", "patch", "delete", "deletecollection"];
6
+ export const SANDBOX_RBAC_ALWAYS = "always";
7
+ export const SANDBOX_RBAC_SWITCH_WORDS = ["WORKSPACE_CACHE=volume", "K8S_SANDBOX_ENV"];
8
+ export const SANDBOX_RBAC_WHEN_WORDS = [SANDBOX_RBAC_ALWAYS, ...SANDBOX_RBAC_SWITCH_WORDS];
9
+ export const SANDBOX_RBAC_SWITCHES = {
10
+ "WORKSPACE_CACHE=volume": { kind: "bool", env: "WORKSPACE_CACHE", envValue: "volume", chartValue: "sandbox.workspaceCacheVolume", meaning: "按工作区空间的构建缓存卷开着" },
11
+ K8S_SANDBOX_ENV: { kind: "names", env: "K8S_SANDBOX_ENV", chartValue: "sandbox.sandboxEnv", meaning: "沙箱带外 env 名单非空(名单点到的值留在服务进程环境里)" },
12
+ };
13
+ export const SANDBOX_RBAC_NEEDS = ["called", "retained"];
14
+ export const SANDBOX_RBAC_RULES = [
15
+ { scope: "Role", apiGroups: [""], resources: ["pods"], verbs: ["get", "list", "create", "delete"], when: "always", need: "called", why: "沙箱 pod 的生命周期:remote-env-k8s.ts#createPod 建(POST pods)并读就绪(GET pods/<名>)· remote-env-k8s.ts#listSandboxPods 按标签列(GET pods?labelSelector)· remote-env-k8s.ts#reconnect 读存活(GET pods/<名>)· remote-env-k8s.ts#deletePod 删(DELETE pods/<名>)" },
16
+ { scope: "Role", apiGroups: [""], resources: ["pods"], verbs: ["watch"], when: "always", need: "retained", why: "兼容保留:chart 一直授着,适配器没有 watch 调用" },
17
+ { scope: "Role", apiGroups: [""], resources: ["pods/exec"], verbs: ["create", "get"], when: "always", need: "called", why: "在沙箱 pod 里执行命令:remote-env-k8s.ts#execUrl 拼 pods/<名>/exec 的连接地址" },
18
+ { scope: "Role", apiGroups: [""], resources: ["pods/attach"], verbs: ["create", "get"], when: "always", need: "retained", why: "兼容保留:chart 一直授着,适配器没有 attach 调用" },
19
+ { scope: "Role", apiGroups: [""], resources: ["pods/log"], verbs: ["get"], when: "always", need: "retained", why: "兼容保留:chart 一直授着,适配器没有读日志的调用" },
20
+ {
21
+ scope: "Role",
22
+ apiGroups: [""],
23
+ resources: ["persistentvolumeclaims"],
24
+ verbs: ["get", "create", "deletecollection"],
25
+ when: "WORKSPACE_CACHE=volume",
26
+ need: "called",
27
+ why: "按工作区空间的构建缓存卷:remote-env-k8s.ts#ensureCacheClaim 按名读、没有就建(GET / POST persistentvolumeclaims)· remote-env-k8s.ts#cacheClaimBound 按名读 · remote-env-k8s.ts#reclaimSpaceCache 空间结束按标签回收(DELETE persistentvolumeclaims?labelSelector = deletecollection);缺它 ⇒ 缓存卷拿不到,适配器按 fail-open 照起、不挂卷(计数 server.workspace-cache.unavailable)",
28
+ },
29
+ { scope: "Role", apiGroups: [""], resources: ["persistentvolumeclaims"], verbs: ["delete"], when: "WORKSPACE_CACHE=volume", need: "retained", why: "兼容保留:chart 一直授着,适配器回收只发按标签的 deletecollection,没有按名 delete 的调用" },
30
+ {
31
+ scope: "Role",
32
+ apiGroups: [""],
33
+ resources: ["secrets"],
34
+ verbs: SANDBOX_SECRET_SEAT_VERBS.env,
35
+ when: "K8S_SANDBOX_ENV",
36
+ need: "called",
37
+ why: "沙箱秘密座:动词取自 SANDBOX_SECRET_SEAT_VERBS.env —— 起服门(boot/execution-env.ts ensureSandboxSecretSeat)问的与 remote-env-k8s.ts#createOwnedSecret 为 env Secret 真发的(POST secrets)是同一行;起服门只在有效转发集合非空(名单点到的键在服务进程环境里有值)时问:答 denied ⇒ server 起服拒,问不到 ⇒ 告警 sandbox_secret_seat_unverified 照起;名单非空而有效集合为空时本行是保守多授(安装器看不到服务进程的运行时环境,引擎此时既不问也不调)",
38
+ },
39
+ { scope: "Role", apiGroups: ["networking.k8s.io"], resources: ["networkpolicies"], verbs: ["list"], when: "always", need: "called", why: "起服的出网自检读本 ns 的 NetworkPolicy 集合:remote-env-k8s.ts#egressPolicyGaps(GET .../networkpolicies)" },
40
+ { scope: "Role", apiGroups: ["networking.k8s.io"], resources: ["networkpolicies"], verbs: ["get"], when: "always", need: "retained", why: "兼容保留:chart 一直授着,适配器只读集合(list),没有按名 get" },
41
+ { scope: "ClusterRole", apiGroups: [""], resources: ["nodes"], verbs: ["list"], when: "always", need: "called", why: "出网自检读节点表(节点是集群级对象 ⇒ ClusterRole + Binding 给同一只服务账号):remote-env-k8s.ts#egressPolicyGaps(GET /api/v1/nodes)" },
42
+ { scope: "ClusterRole", apiGroups: [""], resources: ["nodes"], verbs: ["get"], when: "always", need: "retained", why: "兼容保留:chart 一直授着,适配器只读集合(list),没有按名 get" },
43
+ ];
44
+ export const SANDBOX_RBAC_MODEL = { switches: SANDBOX_RBAC_SWITCHES, rules: SANDBOX_RBAC_RULES };
45
+ const nonEmptyNames = (description) => ({ type: "array", minItems: 1, uniqueItems: true, items: { type: "string" }, description });
46
+ export const SANDBOX_RBAC_FACTS_SCHEMA = {
47
+ $schema: FACTS_SCHEMA_DIALECT,
48
+ description: "k8s 沙箱车道(REMOTE_EXEC=k8s)的服务账号在沙箱命名空间 / 集群里需要的 RBAC 规则;每条规则何时授、引擎适配器真调还是兼容保留。",
49
+ type: "object",
50
+ additionalProperties: false,
51
+ required: ["schemaVersion", "rules"],
52
+ properties: {
53
+ schemaVersion: { type: "integer", const: SANDBOX_RBAC_SCHEMA_VERSION, description: "本份数据的形版本;读方不认识的版本整份拒读。" },
54
+ rules: {
55
+ type: "array",
56
+ minItems: 1,
57
+ "x-uniqueKey": ["scope", "apiGroups", "resources", "need"],
58
+ description: "规则行;同一资源的动词按 need 拆成两行,授权时并回一条规则(Kubernetes 对同一资源的多条规则取并集)。",
59
+ items: {
60
+ type: "object",
61
+ additionalProperties: false,
62
+ required: ["scope", "apiGroups", "resources", "verbs", "when", "need", "why"],
63
+ properties: {
64
+ scope: { type: "string", enum: SANDBOX_RBAC_SCOPES, description: "Role = 沙箱命名空间里的 Role;ClusterRole = 集群级对象(节点)要的 ClusterRole,绑给同一只服务账号。" },
65
+ apiGroups: nonEmptyNames("API 组;核心组写空串。"),
66
+ resources: nonEmptyNames("资源名(子资源写 pods/exec 这种形)。"),
67
+ verbs: { type: "array", minItems: 1, uniqueItems: true, items: { type: "string", enum: SANDBOX_RBAC_VERBS }, description: "动词。" },
68
+ when: {
69
+ type: "string",
70
+ enum: SANDBOX_RBAC_WHEN_WORDS,
71
+ description: "always = 恒授;WORKSPACE_CACHE=volume = 引擎开着按工作区空间的缓存卷时授(引擎用到它的条件同一个);K8S_SANDBOX_ENV = 引擎的沙箱 env 名单非空时授 —— 这是保守的授权条件,引擎真正问 / 调用的条件是名单点到的键在服务进程环境里有值,两者之差写在该行 why 里。",
72
+ },
73
+ need: { type: "string", enum: SANDBOX_RBAC_NEEDS, description: "called = 引擎适配器源码真发这些调用;retained = 随包 chart 一直授着、适配器源码里零调用(兼容保留)。" },
74
+ why: { type: "string", description: "这一行为什么在(called 行写着调用它的适配器方法)。" },
75
+ },
76
+ },
77
+ },
78
+ },
79
+ };
80
+ //# sourceMappingURL=sandbox-rbac-rules.js.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * 钥匙保管目录的布局常量 —— 引擎建目录、写钥匙文件、建停驻行封签子目录时用的**唯一**值(`src/sealed-key.ts` 读它们)。
3
+ *
4
+ * 单独成一只零依赖的叶子模块:随包部署事实 `deploy/facts/sealed-key.json`(`src/deploy-facts.ts` 投影、`scripts/gen-deploy-lanes.ts` 生成)
5
+ * 读的是同一份值,生成器不必为了三个常量把整只保管模块(libsodium / 中心上报)装载进来。
6
+ */
7
+ /** 保管目录的权限位:每次建店都 `mkdir` + `chmod` 成它(目录预先存在时 `mkdir` 的 mode 不生效,所以另 `chmod` 一次)。 */
8
+ export declare const SEALED_KEY_DIR_MODE = 448;
9
+ /** 钥匙文件 `<publicKeyId>.json` 的权限位(`writeFile` 的 mode 受 umask 掩,所以另 `chmod` 一次)。 */
10
+ export declare const SEALED_KEY_FILE_MODE = 384;
11
+ /**
12
+ * 停驻行凭据封签那一域的保管子目录名(在模型钥匙保管目录之下:`<保管目录>/checkpoint-ctx-keys/`)。**一个域一对钥匙**:模型钥匙(`ModelEntry.sealedApiKey`,
13
+ * 浏览器对着 caps 公钥裸封)与停驻行封签各用自己目录里的钥匙对 —— 一边的开箱口在另一边的密文上恒「不持有那把钥匙」,不是靠哪一侧多判一句信封。
14
+ * 放在模型钥匙目录之下是为了「多副本共享 / 跨重启持久」只有一个目录要管(整个保管目录);模型钥匙店只读这一层的 `*.json`,子目录不进它的视野。
15
+ */
16
+ export declare const CHECKPOINT_CTX_KEY_SUBDIR = "checkpoint-ctx-keys";
17
+ //# sourceMappingURL=sealed-key-layout.d.ts.map
@@ -0,0 +1,4 @@
1
+ export const SEALED_KEY_DIR_MODE = 0o700;
2
+ export const SEALED_KEY_FILE_MODE = 0o600;
3
+ export const CHECKPOINT_CTX_KEY_SUBDIR = "checkpoint-ctx-keys";
4
+ //# sourceMappingURL=sealed-key-layout.js.map
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { chmod, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  import _sodium from "libsodium-wrappers";
5
+ import { CHECKPOINT_CTX_KEY_SUBDIR, SEALED_KEY_DIR_MODE, SEALED_KEY_FILE_MODE } from "./sealed-key-layout.js";
5
6
  import { buildDisplaySafeUrl } from "./trace/redact.js";
6
7
  import { createCenterCredentialSource } from "./center-credential.js";
7
8
  import { centerFetch } from "./web-admin-client/center-request.js";
@@ -61,8 +62,8 @@ export function judgeSealedKeyPublicKey(publicKeyB64) {
61
62
  export async function ensureSealedKeyStore(dir, logger) {
62
63
  await _sodium.ready;
63
64
  const sodium = _sodium;
64
- await mkdir(dir, { recursive: true, mode: 0o700 });
65
- await chmod(dir, 0o700);
65
+ await mkdir(dir, { recursive: true, mode: SEALED_KEY_DIR_MODE });
66
+ await chmod(dir, SEALED_KEY_DIR_MODE);
66
67
  const keys = new Map();
67
68
  for (const name of (await readdir(dir)).filter((n) => n.endsWith(".json")).sort()) {
68
69
  const path = join(dir, name);
@@ -99,8 +100,8 @@ export async function ensureSealedKeyStore(dir, logger) {
99
100
  createdAt: new Date().toISOString(),
100
101
  };
101
102
  const path = join(dir, `${record.publicKeyId}.json`);
102
- await writeFile(path, JSON.stringify(record, null, 2) + "\n", { mode: 0o600 });
103
- await chmod(path, 0o600);
103
+ await writeFile(path, JSON.stringify(record, null, 2) + "\n", { mode: SEALED_KEY_FILE_MODE });
104
+ await chmod(path, SEALED_KEY_FILE_MODE);
104
105
  keys.set(record.publicKeyId, { publicKey: kp.publicKey, privateKey: kp.privateKey, publicKeyB64, createdAt: record.createdAt });
105
106
  logger?.info("sealed_key_generated", { publicKeyId: record.publicKeyId, publicKey: publicKeyB64, dir, note: "new X25519 sealed-box keypair in this directory — the private key exists only here: keep the directory across restarts and identical on every replica that has to open what it sealed, and back it up as a private key" });
106
107
  }
@@ -120,7 +121,6 @@ export async function ensureSealedKeyStore(dir, logger) {
120
121
  },
121
122
  };
122
123
  }
123
- const CHECKPOINT_CTX_KEY_SUBDIR = "checkpoint-ctx-keys";
124
124
  function mintCheckpointCtxKeyStore(store) {
125
125
  return store;
126
126
  }
@@ -4,10 +4,10 @@ import type { ServiceConfig } from "./config-types.js";
4
4
  import { type DeploymentShapeInput } from "./deployment-shape.js";
5
5
  import type { TaskRequestBody } from "./http/wire-types.js";
6
6
  import type { StagingHandle } from "./session-sync-kernel.js";
7
- import type { ClientSessionRowWrite, ReplaySessionRowWrite, ReplaySessionRowWriteOutcome, SessionRowPatch, SessionRowWriteOutcome } from "./session-row.js";
7
+ import type { ClientSessionRowWrite, ReplaySessionRowWrite, ReplaySessionRowWriteOutcome, SessionRowAnswer, SessionRowPatch, SessionRowWriteOutcome } from "./session-row.js";
8
8
  import type { ExecutionPlacementSeat } from "./execution-placement.js";
9
9
  import type { SessionUnreadable } from "./session-error-wire.js";
10
- import type { ForkTarget, ForkToOutcome } from "./plugins/session-fork.js";
10
+ import type { ForkPlacement, ForkTarget, ForkToOutcome } from "./plugins/session-fork.js";
11
11
  /** One row of the session-list projection (CC /resume picker) — the durable session abstraction's enumeration
12
12
  * output (§0.5). Mirrors {@link RunStore}'s shipped `SessionSummary` shape so the wire response is unchanged. */
13
13
  export interface SessionListItem {
@@ -104,7 +104,7 @@ export type SessionOwnerCell = {
104
104
  readonly placed: boolean;
105
105
  };
106
106
  /** 会话行属主门的调用方:验过的主体(`gatedPrincipal`)+ 它是不是 **fleet 调用方**(dev-open 部署上的任何调用方,或出示了这台部署的 service 凭据、
107
- * 不报主体的调用方)。唯一铸点 `http/principal-gate.ts` 的 `sessionCallerOf`。 */
107
+ * 不报主体的调用方 —— 除非部署声明 `SERVICE_TOKEN_ACTS_FOR_DEPLOYMENT=false`)。唯一铸点 `http/principal-gate.ts` 的 `sessionCallerOf`。 */
108
108
  export interface SessionCaller {
109
109
  readonly principal: Principal | undefined;
110
110
  readonly fleet: boolean;
@@ -113,9 +113,13 @@ export interface SessionCaller {
113
113
  * 🔴 **会话行属主门上「无主行放行」臂的唯一判据**([ref] 读面律;会话面谓词 `sessionOwnerReadable`、fork / 删除 / 同步族的存量互认臂 {@link legacyUnownedSessionOk}、
114
114
  * 审批豁免面、notify 面的无主臂都经它,不各写一份):
115
115
  * · 无主的**普通**会话行 ⇒ 成立(各臂照它自己的其余条件);
116
- * · 无主的 **placed** 行(委派子代:属主 = 委派树根的属主,`delegated-session-owner.ts`)⇒ 只对 **fleet 调用方**成立({@link SessionCaller}:dev-open 部署上的任何调用方,
117
- * 或出示了 service 凭据、不报主体的调用方 —— 只配 token 的单用户部署、BFF 的系统调用)。任何具名主体(含经 BFF 代发的)与没有 service 凭据的无身份调用方都不成立:
118
- * 这一行要么属于一棵无身份调用方的树,要么属主还没定下(升级前写下、回填还没判出 / 判断时来源还没有属主)—— 后者把它当公共行,就是把别人那棵树的子代转录交给拿到 id 的人。
116
+ * · 无主的 **placed** 行(委派子代:属主 = 引擎判的委派树属主,三只持久会话店建行那一笔原样记 `placement.owner`,`null` = 根匿名;`delegated-session-owner.ts` 头注)⇒ 只对 **fleet 调用方**成立({@link SessionCaller}:dev-open 部署上的任何调用方,
117
+ * 或出示了 service 凭据、不报主体的调用方 —— 只配 token 的单用户部署、BFF 的系统调用;部署声明 `SERVICE_TOKEN_ACTS_FOR_DEPLOYMENT=false` 时后者不是 fleet)。
118
+ * 任何具名主体(含经 BFF 代发的)与无身份调用方(没有 service 凭据的;声明形上不报主体的 service 凭据)都不成立:
119
+ * 这一行要么属于一棵无身份调用方的树(引擎交的属主是 `null`),要么是属主从没被记下的行 —— 7.144.0 之前写下的存量(7.132.x 及更早建行不记属主;7.133.0–7.143.x 按本服务当年的推断写下,
120
+ * 推断那一刻来源还没有属主的仍无主;SQL 两店由一次性迁移 M23 在它的窗内回填),以及 local 文件店上 7.144.x 及更早建行两笔之间崩溃留下的行(7.145.0 起建行格先、存档后,不再新生)、
121
+ * 同一数据根上 run-local 腿经 core 店写下的 placed 存档(local 没有回填臂)
122
+ * —— 后者把它当公共行,就是把别人那棵树的子代转录交给拿到 id 的人。
119
123
  * · 有主的行 ⇒ 不在本臂。
120
124
  */
121
125
  export declare function ownerlessRowOpen(cell: SessionOwnerCell, caller: SessionCaller): boolean;
@@ -172,6 +176,20 @@ export declare function legacyUnownedSessionOk(deployment: DeploymentShapeInput<
172
176
  * 单实体读/写点(fork/delete/同步族/workspace)**一律**用三条件齐的 {@link legacyUnownedSessionOk},不要用本函数。
173
177
  */
174
178
  export declare function legacyUnownedAmnestyOpen(deployment: DeploymentShapeInput<"principal">, gateOwner: string | null): boolean;
179
+ /** [ref] 存量哨兵窄互认的行属主字面量 —— cli ≤1.0.70 在本地单用户模式自发的 principal 头,存量会话的属主列就是它(引擎不发明这个字面量)。 */
180
+ export declare const LEGACY_SHELL_SENTINEL_OWNER = "anon:shell-live";
181
+ /**
182
+ * 🔴 **会话属主等价律 —— 「行上记下的属主」与「这一次的调用方 / 住客」是不是同一位,唯一判官**(两只窄互认臂收在这一处,读者不各写一份):
183
+ * · 字面相等(含两边都无主 —— 单用户形无身份的调用方与无主的行);
184
+ * · [ref] 窄互认第二臂的身份半支:单机形 ∧ 行无主 ∧ 对方恰为 `default`({@link legacyUnownedAmnestyOpen});
185
+ * · [ref] 存量哨兵臂:非 `requirePrincipal` ∧ 行恰为 {@link LEGACY_SHELL_SENTINEL_OWNER} ∧ 对方没有主体。
186
+ * **有方向、不传递**:`rowOwner` 是行上记下的(或删前记下的)属主,`principal` 是这一次的调用方 / 住客(没有主体 = `null`);
187
+ * 两条臂各自只认它那一对,不由它们推出 `default` ≡ 哨兵、也不反过来认「`default` 的行 ≡ 无主体的调用方」。
188
+ * 读者:`createAuthorizer` 的会话属主门(有主的行)、`http/principal-gate.ts` 的 `runOwnerReadable`(有主的行)、`deleted-session-ids.ts` 的墓碑读点(删前属主 vs 本次住客)。
189
+ * 不在本判官里的两件(它们问的不是「是不是同一位」):无主的委派子代 placed 行只对 fleet 调用方开放(读面律 {@link ownerlessRowOpen});
190
+ * 删除 / fork / 同步族的放行另有 fleet 臂与 placed 判({@link legacyUnownedSessionOk} = 本判官的第二臂 ∧ {@link ownerlessRowOpen})。
191
+ */
192
+ export declare function sameSessionOwner(deployment: DeploymentShapeInput<"principal">, rowOwner: string | null, principal: string | null): boolean;
175
193
  /**
176
194
  * 本仓会话店的面 = core `SessionStore` + 本仓的行面(登记 / 列表 / 换装 / 同步……)。
177
195
  * 🔴 **按 id 读一行只有一只**:core 的 `headOf(id)`(`{ owner, incarnation, leafId }`,同一行一次读;core 7.55.0 [ref] S2)。
@@ -184,7 +202,7 @@ export type OwnerAwareSessionStore = SessionStore & {
184
202
  register?: (sessionId: string, owner: string | null) => Promise<void>;
185
203
  /** [ref] placed 分区的元组读口(店的既有读口,一行 PK 读;`undefined` = 普通会话或无此行)。店不交它 = 不实现 placement = 没有 placed 行(无 repo 的内存店)。两个读者:
186
204
  * ① 会话面的属主门只在属主为 NULL 时读它一次(无主的 placed 行不是公共行,`http/principal-gate.ts` 的 `sessionOwnerCellOf`);
187
- * ② 会话视图读它投影体顶层 `agent` 键(`audit.ts` 的 `sessionAgentOf`,只在视图读上付):店没有这一面 ⇒ 键缺席,读面不凭别处另铸子代身份。 */
205
+ * ② `agent` 的唯一读口 `session-agent.ts` 的 `readSessionAgent`(会话视图体顶层、运维审批队列的停驻行同读):店没有这一面 ⇒ 键缺席,读面不凭别处另铸子代身份。 */
188
206
  placementOf?: (sessionId: string) => Promise<SessionPlacementRecord | undefined>;
189
207
  /** E16 — enumerate the caller's sessions (owner-filtered on the session store's OWN owner column).
190
208
  * `includeUnowned` = [ref] 窄互认第二臂的**第三条件**(`owner IS NULL`)的 SQL 承载位:置真时
@@ -200,6 +218,19 @@ export type OwnerAwareSessionStore = SessionStore & {
200
218
  limit: number;
201
219
  q?: string;
202
220
  }) => Promise<SessionListItem[]>;
221
+ /** The SAME list face as {@link listSessions} (same SELECT, same mapper, same predicate and keyset) with each row's occupant — the row's owner and
222
+ * incarnation as read in that one read. The incarnation never enters the row (it is not a wire fact: `session-incarnation.ts`); this pair is for the
223
+ * in-process reader that must address a row by occupant — the peer directory's `PeerSessionRecord.occupant`. `listSessions` is this face's rows. */
224
+ listSessionRows?: (opts: {
225
+ owner?: string;
226
+ includeUnowned?: boolean;
227
+ cursor?: {
228
+ lastActivityAt: string;
229
+ sessionId: string;
230
+ };
231
+ limit: number;
232
+ q?: string;
233
+ }) => Promise<Array<SessionRowAnswer<SessionListItem>>>;
203
234
  /** ONE session's list row — the SAME projection {@link listSessions} emits for it (same columns, same mapper). `undefined` = not a
204
235
  * row of the list face (no such session, or a placed / delegation-transcript session, which never lists). Reader: the 2c
205
236
  * manifest (`title` / `pinned`). */
@@ -214,8 +245,12 @@ export type OwnerAwareSessionStore = SessionStore & {
214
245
  writeSessionRowCells?: SessionRowCellsWriter;
215
246
  /** E17 — fork a session's whole history to a NEW session; null if the source is unknown. The fork row records `owner` when it is a
216
247
  * string, else (`null`) the SOURCE row's owner — a copy of an owned history is never anonymous (core `SessionStore.fork`, 7.55.0 S2).
217
- * On the three stores it is the projection `forkTo(…, { id: uuidv7() })` of {@link forkTo} (one private body). */
218
- fork?: (sourceId: string, owner: string | null) => Promise<string | null>;
248
+ * On the three stores it is the projection `forkTo(…, { id: uuidv7(), placement? })` of {@link forkTo} (one private body). `opts.placement`
249
+ * (core `SessionStore.fork`; engine-internal: a background fork child's transcript) forks INTO the delegation partition — the creating write
250
+ * lays the tuple down as a placed acquire does; a store that declares no partition ignores it. */
251
+ fork?: (sourceId: string, owner: string | null, opts?: {
252
+ placement?: ForkPlacement;
253
+ }) => Promise<string | null>;
219
254
  /** fork 续写的店契约(三店同一只私有体;规则与结果闭集在 `plugins/session-fork.ts`):把源会话整份历史拷进调用方给的目标 id;`target.key` 存进新行那一格 fork 键。
220
255
  * `null` = 源不在;`created` / `replayed`(同属主同键)/ `occupied{sameOwner}`(撞上别的行)。目标行原子新建(SQL 主键排他、meta 先;local 两把写锁 + 格先)。 */
221
256
  forkTo?: (sourceId: string, owner: string | null, target: ForkTarget) => Promise<ForkToOutcome | null>;
@@ -260,6 +295,10 @@ export type OwnerAwareSessionStore = SessionStore & {
260
295
  * Phase-B route streams batches into (`appendBatch`) then commits (`commit`, the atomic swap) / aborts. The shadow
261
296
  * rows have NO session_meta → invisible to wake until commit writes meta LAST = the sole commit point. */
262
297
  beginImportStaging?: (realSessionId: string, token: string) => StagingHandle;
298
+ /** 带戳存档的早拒口(只有 local 店有这一形:更早的版本写下的属主戳留在引擎存档头上,一次性迁移改写不了的;SQL 孪生没有 ⇒ 不交这一口,调用方按「没有带戳存档」处置)。
299
+ * 只读、零写、不持锁;带戳 ⇒ 抛 `StampedSessionArchiveError`(`session-error-wire.ts` 映射 409 `conflict.session_archive_stamped`),没有存档 / 匿名存档 ⇒ 放行。
300
+ * 这是拒绝判对「这份存档带不带戳」的**唯一**读口(fork 的源与改日志的提交也问同一只);读者:会话同步导入第一阶段对按 id 分类出的 fork / stale(没带同意)的早拒。 */
301
+ assertArchiveAnonymous?: (sessionId: string) => Promise<void>;
263
302
  /** 2c session-sync P1d-β (staged-row inspection) — read the rows STAGED under a `${realSessionId}#stg-${token}` id
264
303
  * (NO session_meta probe — `exportEntries` returns null for an unregistered id), oldest-first. The import gate is now
265
304
  * the per-line `StreamingImportValidator` (core 1.143.0) driven during the stream, so this is no longer the
package/dist/security.js CHANGED
@@ -39,6 +39,14 @@ export function legacyUnownedSessionOk(deployment, caller, cell) {
39
39
  export function legacyUnownedAmnestyOpen(deployment, gateOwner) {
40
40
  return !DEPLOYMENT_SHAPE_PREDICATES.multiTenant.holds(deployment) && gateOwner === LEGACY_UNOWNED_AMNESTY_TENANT;
41
41
  }
42
+ export const LEGACY_SHELL_SENTINEL_OWNER = "anon:shell-live";
43
+ export function sameSessionOwner(deployment, rowOwner, principal) {
44
+ if (rowOwner === principal)
45
+ return true;
46
+ if (rowOwner === null)
47
+ return legacyUnownedAmnestyOpen(deployment, principal);
48
+ return !DEPLOYMENT_SHAPE_PREDICATES.principalRequired.holds(deployment) && rowOwner === LEGACY_SHELL_SENTINEL_OWNER && principal === null;
49
+ }
42
50
  const UUIDV7_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
43
51
  export function isUuidV7(s) {
44
52
  return UUIDV7_RE.test(s);
@@ -111,8 +119,7 @@ export function createAuthorizer(config, sessionStore) {
111
119
  await register(requested, principal);
112
120
  owner = await readOwner(requested);
113
121
  }
114
- const legacyShellOwner = !config.requirePrincipal && owner === "anon:shell-live" && principal === undefined;
115
- if (!legacyShellOwner && owner != null && owner !== principal) {
122
+ if (owner != null && !sameSessionOwner(config, owner, principal ?? null)) {
116
123
  throw principal
117
124
  ? new HttpError(403, "session does not belong to this principal")
118
125
  : new HttpError(401, `session is principal-owned; missing principal header '${config.principalHeader}'`);
@@ -0,0 +1,39 @@
1
+ /**
2
+ * 🔴 **一只会话在委派树里的位置 —— 一个来源、一只读口、一种 wire 写法**(`agent: { id?, parentSessionId?, rootSessionId? }`)。
3
+ *
4
+ * 来源只有一个:会话行上**存下来的委派元组**(core `SessionPlacementRecord`)—— 引擎在**后台**委派子代(带耐久 agent 行、店声明了委派分区)建会话那一笔交给店、
5
+ * 店原样记下,首写不可变。三只持久会话店都存:SQL 两孪生在 `session_meta` 的 `placement_*` 列(`plugins/session-placement.ts` 的 `placementFromRow`),
6
+ * local 后端(7.141.0 起)在会话存档行头(core 文件 repo)。读口是店的 `placementOf`(一行 PK 探针;店不交它 = 不存元组)。
7
+ *
8
+ * 读者(同一只读口 {@link readSessionAgent}、同一只投影 {@link sessionAgentOf},逐值同答):
9
+ * · 会话视图 `GET /v1/sessions/:id` 体顶层的 `agent`(视图读才付探针;探针抛 ⇒ 视图读响亮失败,`boot/session-faces.ts`);
10
+ * · 运维审批队列 `GET /v1/approvals` 的停驻行(`pending[]` 的 `checkpoint` 族)与 `/v1/approvals/stream` 的 `pending` 帧:行的身份就是它那只会话
11
+ * (决断口 `/v1/approvals/:sessionId/decide`),行上的 `agent` = 同一只会话在视图上的 `agent`;一行读不出 ⇒ 只这一行缺键、F 类兜底计数
12
+ * (`http/routes/approvals-assistant.ts`)。
13
+ * 不另立第二只读法:fleet 行 / 后台 agent 行上的同名事实是引擎别的记录,读面不拿它们回填或改写这里的答案。
14
+ *
15
+ * 投影律(不另铸):`id` = 元组的 `handle`(耐久 agent 行的 `a…` 键,与 fleet 行的 `id`、`GET /v1/agents/roster` 的 `handle` 同一个串);`parentSessionId` /
16
+ * `rootSessionId` = 元组的同名格(派它的会话 / 委派树的根会话);元组缺哪一格这里就缺哪一格;元组的 `scope`(租户键)/ `placedAt` / `kind` 不上 wire。
17
+ * 键在场 = 这是一只委派子代的会话(元组只存了 `kind` 时是 `{}`);键缺席 = 不是 placed 行(宿主会话、同步委派的子代、`subagent_type: "fork"` 的后台子代 ——
18
+ * 它的会话是父会话的一只普通分叉;`DB_BACKEND=local` 到 7.140.x 停驻时拷进宿主店的子代转录,那一份是普通会话行)、这台部署的店不存元组(无 repo 的内存店
19
+ * 〔`DB_BACKEND=memory`〕、嵌入方自建且不声明委派分区的店),或那一读没读成 —— 消费方一律读作「这台 server 没有说」,不读作「不是子代」。
20
+ */
21
+ import type { SessionPlacementRecord } from "@sema-agent/core";
22
+ /** 一只会话在委派树里的位置(wire 键 `agent` 的值形)。 */
23
+ export interface SessionAgent {
24
+ id?: string;
25
+ parentSessionId?: string;
26
+ rootSessionId?: string;
27
+ }
28
+ /** 交委派元组读口的会话店面(本仓三只持久会话店与缓存包装层都交;内存店不交)。 */
29
+ export interface PlacementReadingSessionStore {
30
+ readonly placementOf?: (sessionId: string) => Promise<SessionPlacementRecord | undefined>;
31
+ }
32
+ /** {@link SessionAgent} 的唯一投影:店答出的委派元组 → `agent` 的值;`undefined`(普通会话 / 店没有这一面)⇒ 调用方不铸键。 */
33
+ export declare function sessionAgentOf(placement: SessionPlacementRecord | undefined): SessionAgent | undefined;
34
+ /**
35
+ * `agent` 的**唯一读口**:店的 `placementOf` 一读 → {@link sessionAgentOf}。店不交 `placementOf` ⇒ `undefined`(这台部署不存元组,不探)。
36
+ * 探针抛 ⇒ 原样上抛 —— 读不出怎么说归读者(视图读响亮失败;队列只缺那一行的键并计数),本函数不替任何一面把故障说成「不是子代」。
37
+ */
38
+ export declare function readSessionAgent(store: PlacementReadingSessionStore | undefined, sessionId: string): Promise<SessionAgent | undefined>;
39
+ //# sourceMappingURL=session-agent.d.ts.map
@@ -0,0 +1,16 @@
1
+ export function sessionAgentOf(placement) {
2
+ if (placement === undefined)
3
+ return undefined;
4
+ return {
5
+ ...(placement.handle !== undefined ? { id: placement.handle } : {}),
6
+ ...(placement.parentSessionId !== undefined ? { parentSessionId: placement.parentSessionId } : {}),
7
+ ...(placement.rootSessionId !== undefined ? { rootSessionId: placement.rootSessionId } : {}),
8
+ };
9
+ }
10
+ export async function readSessionAgent(store, sessionId) {
11
+ const placementOf = store?.placementOf;
12
+ if (placementOf === undefined)
13
+ return undefined;
14
+ return sessionAgentOf(await placementOf.call(store, sessionId));
15
+ }
16
+ //# sourceMappingURL=session-agent.js.map
@@ -49,8 +49,8 @@
49
49
  * 由 {@link sessionErrorWireOf} 按**型**先认:两面同答 409 `conflict.session_archive_stamped` + 固定句 {@link STAMPED_SESSION_ARCHIVE_SENTENCE}(与上一句的属主变更拒同状态、不同码:
50
50
  * 两件事的出路相反 —— 属主变更从第一阶段重来,带戳存档删掉会话再推 —— 机读方按码分支,不读句子)。
51
51
  * open 面上它是 run 的写撞上真并发写,落成那条 run 的 `failed` 终局(`terminal.code: "conflict"`),提交腿不另作处置。
52
- * 带戳存档的具名拒在提交里排在一切同步冲突判之前(`local-session-store.ts` 的 `assertArchiveAnonymousHeld`):改日志的推送的每一形(干净追加 / 载荷分叉 / fork / stale,带同意与否)都答它的这一句 ——
53
- * 带戳存档的日志换不了,同步冲突的出路(带同意重来)无从谈起。
52
+ * 带戳存档的具名拒在提交里排在一切同步冲突判之前(`local-session-store.ts` 的 `assertArchiveAnonymous`,带戳判的唯一读口;fork 的源与第一阶段的早拒问的是同一只):改日志的推送的每一形(干净追加 / 载荷分叉 / fork / stale,带同意与否)都答它的这一句 ——
53
+ * 带戳存档的日志换不了,同步冲突的出路(带同意重来)无从谈起。第一阶段(`POST …/sync/import`)按 id 分类出 fork / stale 而没带同意时,通用冲突的出路同样是死路 ⇒ 路由先问这一口,带戳 ⇒ 第一阶段就答这一句(不等带同意的第二轮)。
54
54
  * · `storage` / `unknown` —— 留白:故障类,通用 500 就是它们今天的应答。
55
55
  *
56
56
  * core 给闭集加一个词 ⇒ 下面的 `switch` 编译红,由人裁定它在两面上的答复,而不是让它静默落进某个恰好存在的臂
@@ -92,8 +92,8 @@ export declare const PLACED_SESSION_SENTENCE = "this session holds a delegated a
92
92
  */
93
93
  export declare const STAGED_IMPORT_OWNER_CHANGED_SENTENCE = "session owner changed while the import streamed; nothing was imported \u2014 start the sync again";
94
94
  /**
95
- * local 店的具名拒(唯一铸点 `plugins/local-session-store.ts`):这只会话的引擎存档仍带更早版本写下的属主戳(一次性迁移 M33 改写不了它,或数据根跳过了带这次迁移的版本)
96
- * ⇒ 不在它上面造新存档(fork),也不原地换它的日志(改日志的推送);零写。`SessionError.code` 是 core 闭集的 `conflict`;句子(给运维日志,点名会话 id)归本错,
95
+ * local 店的具名拒(唯一铸点 `plugins/local-session-store.ts` 的 `assertArchiveAnonymous`):这只会话的引擎存档仍带更早版本写下的属主戳(一次性迁移 M33 改写不了它,或数据根跳过了带这次迁移的版本)
96
+ * ⇒ 不在它上面造新存档(fork),也不原地换它的日志(改日志的推送,含第一阶段对 fork / stale 的早拒);零写。`SessionError.code` 是 core 闭集的 `conflict`;句子(给运维日志,点名会话 id)归本错,
97
97
  * wire 上发自己的码 `conflict.session_archive_stamped` + 固定句 {@link STAMPED_SESSION_ARCHIVE_SENTENCE}(两面同一码同一句;映射 {@link sessionErrorWireOf}):
98
98
  * 同一个 core 词 `conflict` 在 import 面上还有属主变更那一件事(出路相反),机读方要能按码把两件事分开。
99
99
  */
@@ -24,9 +24,11 @@
24
24
  *
25
25
  * ## 调用方(两只,都经 {@link SessionNotifyDoor.deliver},不各写一份臂)
26
26
  * · `POST /v1/sessions/:id/notify`(`http/routes/notify-wake.ts`):入站鉴权、验型、属主门在路由;属主 = 会话行的属主。
27
- * · **Runner 之外的后台子代完成**({@link createBackgroundCompletionRelay}):停驻赎回腿的复活、操作员 `handle.resume` —— 这些续跑不是哪一次 run 内的调用,
28
- * 没有调用车道(`ToolExecuteContext.onTaskNotification`),引擎把完成交给委派工具挂载期的 `background.notify`;那只 sink 在 `capabilities/scenarios.ts` 接一次,
29
- * 交到委派树根会话的这只门。Runner 之内的调用照旧走调用车道(引擎 `resolveCompletionSink` = 调用 ?? 挂载),这只门不经手。
27
+ * · **Runner 之外的后台子代完成**({@link createBackgroundCompletionRelay}):操作员 `handle.resume`,与**停驻落在别的进程**(另一副本 / 重启之前)的停驻赎回腿复活 ——
28
+ * 这些续跑不是哪一次 run 内的调用,本进程也没有它们欠账的车道,引擎把完成交给委派工具挂载期的 `background.notify`;那只 sink 在 `capabilities/scenarios.ts`
29
+ * 接一次,交到委派树根会话的这只门(停驻赎回那一形引擎另说一次 `delegation.resume_lane_missing`,运维受众)。本进程持有停驻的赎回(core 7.63.0 起)是停驻
30
+ * 周期的延续:完成回到周期欠账的那条车道(父 run 的车道;父 run 已收场 ⇒ 根会话的活 run 或下一轮),这只门不经手。Runner 之内的调用照旧走调用车道
31
+ * (引擎 `resolveCompletionSink` = 调用 ?? 停驻车道 ?? 挂载)。
30
32
  */
31
33
  import type { ExternalNotificationInput, SessionHead, SubagentToolOptions, TaskStream } from "@sema-agent/core";
32
34
  import { type WorkflowCompletionInbox } from "./orchestration/workflow-completion-inbox.js";
@@ -0,0 +1,57 @@
1
+ /**
2
+ * `/health.inflight` 计法表第四类 —— **会话驻留的后台 shell / monitor**。
3
+ *
4
+ * ## 病
5
+ * 引擎在飞数原先只数 HTTP 腿与 leader 腿。模型在会话里起的后台 shell / monitor(`run_in_background`)活过起它的那一条腿 —— 腿一收,它在这份数里是 0:
6
+ * 读 `/health` 判「这台引擎是否空闲」的外部判官看不见它们,只好另拼一条谓词 —— 同一个事实两个读法。
7
+ *
8
+ * ## 两个事实(本文件只产总数里的第三项 `sessionResident`)
9
+ * `/health.inflight`(总数,{@link inflightTotal})= 引擎此刻托管着多少活 = `http + leader + sessionResident`,可观测事实;
10
+ * `drainState.blockingInflight`(阻断集,{@link blockingInflightOf})= 还有没有必须等完的活 = `http + leader`,排空 / 孤儿窗 / 附着租约自退 / SIGHUP 空闲窗判停读它。
11
+ * 本文件产出的 `sessionResident` **只进总数、不进阻断集**:驻留 shell 不是一条腿,等它没有意义;一只不退出的 shell(`while true`)若阻断,崩溃孤儿引擎就永生。
12
+ * 窗到 / 排空完成,退出边界的后台收割收掉它们,retain 行按 retain 既有语义活过受控退。
13
+ *
14
+ * ## 读法(本文件是唯一属主)
15
+ * 行集 = core `TaskRegistry.liveTasks(access)` 的投影:`type ∈ {background_bash, monitor}` ∧ `status === "running"` ∧ `sessionScoped` ∧ 非 retain
16
+ * ({@link isSessionResidentShellRow})。
17
+ * · **选座 `liveTasks`,不读 `list()`**:`list()` 是显示投影,硬帽 500 行(core 注:resolution must NEVER ride the display-capped list());
18
+ * 进程级的在飞总数是外部判官判「引擎是否空闲」的依据,不能骑显示帽 —— 超过 500 行时 `list()` 会静默少数,方向恰是「以为空闲」。`liveTasks` 无显示帽。
19
+ * · **retain 行不计**:显式 retain 的行是「交付的活服务」,按 core 契约活过引擎的受控退(退出边界对它 `retained_skipped`),
20
+ * 不是「引擎托管着、会随它收掉」的活 —— 报它,判官会把一个本就该活过引擎的服务读成引擎的负载。
21
+ * · **只数 shell 与 monitor**:后台 agent / workflow 行属计法表的别的类,不在这一类里。
22
+ * · **非会话驻留行不数**:挂在 run 腿上的后台行(`sessionScoped` 为假)随腿登记在 HTTP 腿项里,数它是第二次。
23
+ *
24
+ * ## 枚举源
25
+ * core 的 `canAccess` 要求访问元组带 `scope`(缺 scope 一律拒),`list` / `liveTasks` 因此只读「调用方可达行」,没有进程总数的读口(core 不立新 getter)。
26
+ * 进程级的数要由本仓自己枚举「本进程有哪些会话」:枚举源 = **围栏表**(`boot/execution-env.ts` 的 `SessionScopedRegistries.sessionOwners`):
27
+ * 每次提交经 resolve-spec 写一笔(会话 id → 属主),删会话的 E21 级联清一笔,LRU 有界。scope 轴 = {@link sessionChildrenScope}(会话属主 `?? "default"`,
28
+ * 删除门 / 换绑门 / 结清口 / 会话后台读面同一个取法,不另取)。
29
+ *
30
+ * ## 读到的边界(成文,不静默)
31
+ * · 围栏表是 LRU(上界 4096 会话):被挤出表的会话,它的驻留行在这里不可见 —— 回到改前的读数(不是新的错),不会多数;
32
+ * · 不经 resolve-spec 的内部车道(服务为自己起的 run)不写围栏表,它们起的会话驻留行同样不可见;
33
+ * · 删会话的级联在 `dropSessionScoped` 之后、后台收割之前失败(会话行因此没删)时,围栏表已先清:该会话还在跑的驻留行在下一次提交重新围栏之前不可见
34
+ * (同样回到改前的读数;修法是把 `dropSession` 的「清秘密」与「忘身份」拆开,属围栏表的属主,不在本读法里);
35
+ * · 每次读是 O(围栏表会话数 × 注册表行数)(`liveTasks` 逐会话走一遍注册表);表的上界 4096、注册表行数小,量级是毫秒。
36
+ *
37
+ * ## 读不出 ⇒ 非空闲(响亮,不替成 0)
38
+ * 本函数在 `/health` 应答和崩溃遗言(uncaught 处理器里读总数)里被调,**不许抛**(崩溃遗言里一次抛会吞掉遗言本身)。判停路径(排空 / 租约 / SIGHUP)读阻断集,
39
+ * 不求值本函数。某个会话的 `liveTasks` 读失败 ⇒ 该会话按 **1 只在跑**计(读不出 ⇒ 非空闲:判官宁可当有活)并经 `onUnreadable` 报出 ——
40
+ * 调用方必须给报口(装配层一会话一行 `session_resident_background_unreadable`),不存在静默的 0。
41
+ */
42
+ import { type TaskListRow, type TaskRegistry } from "@sema-agent/core";
43
+ /** 枚举源的形:会话 id → 会话属主(`null` = 匿名)。`Map` 满足它。 */
44
+ export type SessionOwners = Iterable<readonly [sessionId: string, owner: string | null]>;
45
+ /** 这一类里的行判:唯一写处(计数与单测同读)。 */
46
+ export declare function isSessionResidentShellRow(row: TaskListRow): boolean;
47
+ /** 一个会话的驻留行读失败的报口(由调用方给出:装配层记运维行)。 */
48
+ export type UnreadableSessionReport = (sessionId: string, err: unknown) => void;
49
+ /**
50
+ * 本进程里会话驻留的、仍在跑的后台 shell / monitor 行数。`registry` 缺省 = 进程级单例;参数只供测试换桩(只用到 `liveTasks`)。
51
+ * `onUnreadable` 必给:读失败的会话按 1 计并经它报出(文件头「读不出 ⇒ 非空闲」)。读法与边界见文件头。
52
+ */
53
+ export declare function countSessionResidentBackground(sessions: SessionOwners, opts: {
54
+ readonly registry?: Pick<TaskRegistry, "liveTasks">;
55
+ readonly onUnreadable: UnreadableSessionReport;
56
+ }): number;
57
+ //# sourceMappingURL=session-resident-background.d.ts.map