@sema-agent/server 7.140.0 → 7.142.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 (296) hide show
  1. package/MIGRATION.md +505 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +142 -25
  5. package/dist/approval-ask-audit-store.d.ts +54 -38
  6. package/dist/approval-ask-audit-store.js +58 -39
  7. package/dist/approval-batch-census.d.ts +27 -0
  8. package/dist/approval-batch-census.js +27 -0
  9. package/dist/approval-card.d.ts +39 -24
  10. package/dist/approval-card.js +6 -3
  11. package/dist/approval-crash-convergence.d.ts +17 -7
  12. package/dist/approval-crash-convergence.js +94 -22
  13. package/dist/approval-deny-reasons.d.ts +34 -30
  14. package/dist/approval-deny-reasons.js +21 -6
  15. package/dist/approval-policy-settlement.d.ts +1 -1
  16. package/dist/approval-reconciler.d.ts +33 -1
  17. package/dist/approval-reconciler.js +43 -40
  18. package/dist/approval.d.ts +15 -0
  19. package/dist/approval.js +10 -0
  20. package/dist/audit.d.ts +4 -2
  21. package/dist/audit.js +2 -2
  22. package/dist/auth-bridge.d.ts +9 -6
  23. package/dist/auth-bridge.js +9 -3
  24. package/dist/boot/adoption.js +2 -1
  25. package/dist/boot/budget-tracing.d.ts +16 -0
  26. package/dist/boot/budget-tracing.js +10 -6
  27. package/dist/boot/device-lane.d.ts +11 -0
  28. package/dist/boot/device-lane.js +16 -0
  29. package/dist/boot/engine-lease.d.ts +24 -3
  30. package/dist/boot/engine-lease.js +9 -2
  31. package/dist/boot/execution-env.d.ts +24 -4
  32. package/dist/boot/execution-env.js +34 -27
  33. package/dist/boot/execution-lane.js +2 -1
  34. package/dist/boot/exit-boundary.js +5 -4
  35. package/dist/boot/memory-boundary.d.ts +2 -2
  36. package/dist/boot/memory-consolidation.d.ts +1 -1
  37. package/dist/boot/memory-consolidation.js +3 -2
  38. package/dist/boot/parked-revive-gate.js +2 -1
  39. package/dist/boot/permission-rules-audit.d.ts +0 -28
  40. package/dist/boot/permission-rules-audit.js +2 -1
  41. package/dist/boot/read-deny-anchor-audit.js +2 -1
  42. package/dist/boot/reapers.d.ts +2 -2
  43. package/dist/boot/reapers.js +4 -3
  44. package/dist/boot/resolve-spec.js +18 -6
  45. package/dist/boot/retention-lane.js +5 -3
  46. package/dist/boot/runner-deps.js +16 -13
  47. package/dist/boot/runtime-caps.js +3 -2
  48. package/dist/boot/session-faces.d.ts +1 -1
  49. package/dist/boot/session-faces.js +8 -6
  50. package/dist/boot/shutdown.js +8 -2
  51. package/dist/boot/stage-04-stores.js +3 -2
  52. package/dist/boot/stage-05-execution-env.d.ts +1 -1
  53. package/dist/boot/stage-05-execution-env.js +11 -7
  54. package/dist/boot/stage-06-runners.d.ts +1 -3
  55. package/dist/boot/stage-06-runners.js +3 -2
  56. package/dist/boot/stage-07-capability-layer.d.ts +1 -2
  57. package/dist/boot/stage-07-capability-layer.js +10 -11
  58. package/dist/boot/stage-08-reapers.d.ts +1 -2
  59. package/dist/boot/stage-08-reapers.js +3 -2
  60. package/dist/boot/stage-09-leader.d.ts +1 -2
  61. package/dist/boot/stage-10-http-server.d.ts +1 -2
  62. package/dist/boot/stage-10-http-server.js +6 -4
  63. package/dist/boot/stores.js +13 -6
  64. package/dist/boot/web-admin.js +18 -17
  65. package/dist/boot/webfetch-summarize-lane.js +2 -1
  66. package/dist/boot/workflow-orchestration.js +3 -2
  67. package/dist/capabilities/builtin-tools.d.ts +2 -1
  68. package/dist/capabilities/builtin-tools.js +2 -4
  69. package/dist/capabilities/composite-members.d.ts +2 -1
  70. package/dist/capabilities/composite-members.js +2 -1
  71. package/dist/capabilities/scenarios.d.ts +1 -1
  72. package/dist/capability-facts.d.ts +33 -0
  73. package/dist/capability-facts.js +32 -0
  74. package/dist/card-void.d.ts +38 -0
  75. package/dist/card-void.js +23 -0
  76. package/dist/config-catalog.js +12 -8
  77. package/dist/config-provider.d.ts +13 -13
  78. package/dist/config-provider.js +19 -33
  79. package/dist/config-types.d.ts +39 -10
  80. package/dist/config.d.ts +6 -0
  81. package/dist/config.js +72 -49
  82. package/dist/delegated-session-owner.d.ts +21 -19
  83. package/dist/delegated-session-owner.js +1 -1
  84. package/dist/device-store.d.ts +7 -0
  85. package/dist/device-store.js +3 -0
  86. package/dist/execution-config-root.d.ts +22 -0
  87. package/dist/execution-config-root.js +35 -0
  88. package/dist/execution-lane-caps.d.ts +50 -2
  89. package/dist/execution-lane-caps.js +90 -13
  90. package/dist/execution-placement.d.ts +3 -0
  91. package/dist/execution-placement.js +7 -0
  92. package/dist/fleet/drain-entry.d.ts +3 -0
  93. package/dist/fleet/fleet-bus.d.ts +6 -3
  94. package/dist/hooks/hook-runner.d.ts +6 -0
  95. package/dist/hooks/hook-runner.js +30 -1
  96. package/dist/hosted-posture.d.ts +8 -0
  97. package/dist/hosted-posture.js +4 -0
  98. package/dist/http/admission.js +3 -3
  99. package/dist/http/caller-trust.d.ts +3 -2
  100. package/dist/http/caller-trust.js +2 -1
  101. package/dist/http/dispatch.js +8 -2
  102. package/dist/http/execution-context.d.ts +2 -2
  103. package/dist/http/idempotency.d.ts +1 -1
  104. package/dist/http/live-frame.d.ts +24 -0
  105. package/dist/http/live-frame.js +16 -0
  106. package/dist/http/principal-gate.d.ts +3 -1
  107. package/dist/http/principal-gate.js +17 -8
  108. package/dist/http/resume-legs.js +42 -29
  109. package/dist/http/route-table.js +4 -0
  110. package/dist/http/routes/approvals-assistant.js +1 -0
  111. package/dist/http/routes/attachments.js +18 -0
  112. package/dist/http/routes/capabilities.js +10 -6
  113. package/dist/http/routes/diagnostics.d.ts +0 -19
  114. package/dist/http/routes/diagnostics.js +16 -2
  115. package/dist/http/routes/memory-bundle.d.ts +2 -2
  116. package/dist/http/routes/memory-bundle.js +1 -1
  117. package/dist/http/routes/memory-compliance.js +1 -1
  118. package/dist/http/routes/memory-policy.js +27 -7
  119. package/dist/http/routes/run-permission-mode.d.ts +75 -0
  120. package/dist/http/routes/run-permission-mode.js +170 -0
  121. package/dist/http/routes/runs.js +3 -3
  122. package/dist/http/routes/session-memory-sync.d.ts +63 -0
  123. package/dist/http/routes/session-memory-sync.js +157 -0
  124. package/dist/http/routes/session-sync.d.ts +3 -6
  125. package/dist/http/routes/session-sync.js +7 -2
  126. package/dist/http/routes/sessions-list.d.ts +2 -1
  127. package/dist/http/routes/sessions.js +5 -4
  128. package/dist/http/routes/tasks.js +70 -71
  129. package/dist/http/run-read-face.d.ts +7 -0
  130. package/dist/http/run-read-face.js +10 -4
  131. package/dist/http/send.d.ts +6 -1
  132. package/dist/http/send.js +10 -3
  133. package/dist/http/server.d.ts +20 -7
  134. package/dist/http/server.js +2 -4
  135. package/dist/http/sse-log.d.ts +3 -8
  136. package/dist/http/stored-body-replay.d.ts +3 -3
  137. package/dist/http/stored-body-replay.js +3 -1
  138. package/dist/http/submit-replay.d.ts +4 -3
  139. package/dist/http/submit-replay.js +2 -2
  140. package/dist/http/sync-submit-body.d.ts +21 -4
  141. package/dist/http/sync-submit-body.js +13 -0
  142. package/dist/identity-source.d.ts +21 -2
  143. package/dist/identity-source.js +6 -0
  144. package/dist/instruction-source-notice.d.ts +7 -0
  145. package/dist/instruction-source-notice.js +6 -6
  146. package/dist/mcp-assertion-header.d.ts +4 -0
  147. package/dist/mcp-assertion-header.js +5 -0
  148. package/dist/memory-incident-sink.d.ts +19 -6
  149. package/dist/memory-incident-sink.js +8 -4
  150. package/dist/memory-posture.d.ts +1 -1
  151. package/dist/memory-scope.d.ts +4 -4
  152. package/dist/memory-scope.js +1 -1
  153. package/dist/memory-sync-client.d.ts +35 -23
  154. package/dist/memory-sync-client.js +122 -52
  155. package/dist/memory-sync-identity.d.ts +24 -0
  156. package/dist/memory-sync-identity.js +57 -0
  157. package/dist/memory-sync.d.ts +7 -1
  158. package/dist/memory-sync.js +6 -3
  159. package/dist/observability/fail-open.d.ts +57 -12
  160. package/dist/observability/fail-open.js +50 -9
  161. package/dist/observability/metrics.js +0 -1
  162. package/dist/observability/run-terminal.d.ts +93 -31
  163. package/dist/observability/run-terminal.js +175 -171
  164. package/dist/observability/secret-env-scrub.d.ts +20 -1
  165. package/dist/observability/secret-env-scrub.js +14 -0
  166. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -2
  167. package/dist/orchestration/workflow-completion-inbox.js +13 -5
  168. package/dist/orchestration/workflow-notify-journal.d.ts +5 -3
  169. package/dist/orchestration/workflow-notify-journal.js +2 -1
  170. package/dist/permission-mode-admission.d.ts +68 -0
  171. package/dist/permission-mode-admission.js +23 -0
  172. package/dist/permission-rule-list.d.ts +55 -6
  173. package/dist/permission-rule-list.js +52 -5
  174. package/dist/plugins/approval-ask-store-file.d.ts +14 -4
  175. package/dist/plugins/approval-ask-store-file.js +48 -7
  176. package/dist/plugins/approval-ask-store-memory.d.ts +2 -1
  177. package/dist/plugins/approval-ask-store-memory.js +9 -8
  178. package/dist/plugins/approval-ask-store-sql.d.ts +42 -10
  179. package/dist/plugins/approval-ask-store-sql.js +49 -19
  180. package/dist/plugins/caching-session-store.d.ts +6 -0
  181. package/dist/plugins/caching-session-store.js +3 -0
  182. package/dist/plugins/checkpoint-store-sql.d.ts +65 -17
  183. package/dist/plugins/checkpoint-store-sql.js +74 -3
  184. package/dist/plugins/device-store-sql.d.ts +5 -2
  185. package/dist/plugins/device-store-sql.js +4 -1
  186. package/dist/plugins/execution-placement-sql.d.ts +11 -1
  187. package/dist/plugins/execution-placement-sql.js +12 -1
  188. package/dist/plugins/file-run-store.d.ts +7 -2
  189. package/dist/plugins/file-run-store.js +12 -2
  190. package/dist/plugins/fork-routing-session-store.d.ts +10 -32
  191. package/dist/plugins/fork-routing-session-store.js +2 -61
  192. package/dist/plugins/k8s-exec-protocol.d.ts +4 -1
  193. package/dist/plugins/k8s-exec-protocol.js +6 -1
  194. package/dist/plugins/local-checkpoint-store.d.ts +10 -1
  195. package/dist/plugins/local-checkpoint-store.js +22 -0
  196. package/dist/plugins/local-session-store.d.ts +260 -39
  197. package/dist/plugins/local-session-store.js +582 -57
  198. package/dist/plugins/local-session-window.d.ts +17 -3
  199. package/dist/plugins/local-session-window.js +7 -13
  200. package/dist/plugins/local-task-attachment-store.d.ts +12 -2
  201. package/dist/plugins/local-task-attachment-store.js +67 -43
  202. package/dist/plugins/memory-key-guards.d.ts +3 -3
  203. package/dist/plugins/memory-run-store.d.ts +7 -2
  204. package/dist/plugins/memory-run-store.js +12 -2
  205. package/dist/plugins/one-time-migrations.d.ts +68 -2
  206. package/dist/plugins/one-time-migrations.js +120 -4
  207. package/dist/plugins/pg-pool.js +3 -2
  208. package/dist/plugins/pg-session-storage.d.ts +2 -0
  209. package/dist/plugins/pg-session-storage.js +3 -2
  210. package/dist/plugins/remote-env-e2b.d.ts +47 -7
  211. package/dist/plugins/remote-env-e2b.js +242 -46
  212. package/dist/plugins/remote-env-k8s.d.ts +51 -6
  213. package/dist/plugins/remote-env-k8s.js +234 -44
  214. package/dist/plugins/remote-env-local-docker.d.ts +40 -14
  215. package/dist/plugins/remote-env-local-docker.js +127 -33
  216. package/dist/plugins/run-store-sql.d.ts +11 -3
  217. package/dist/plugins/run-store-sql.js +13 -4
  218. package/dist/plugins/sandbox-read-floor.d.ts +273 -0
  219. package/dist/plugins/sandbox-read-floor.js +608 -0
  220. package/dist/plugins/session-ownership.d.ts +2 -1
  221. package/dist/plugins/session-placement.d.ts +17 -4
  222. package/dist/plugins/session-placement.js +2 -1
  223. package/dist/plugins/session-store.d.ts +18 -20
  224. package/dist/plugins/session-store.js +4 -12
  225. package/dist/plugins/store-backend.d.ts +41 -7
  226. package/dist/plugins/store-backend.js +22 -4
  227. package/dist/plugins/store-contracts.d.ts +35 -3
  228. package/dist/plugins/store-contracts.js +3 -0
  229. package/dist/plugins/task-attachment-store.d.ts +112 -14
  230. package/dist/plugins/task-attachment-store.js +221 -28
  231. package/dist/plugins/tidb-pool.js +2 -1
  232. package/dist/plugins/tidb-session-store.d.ts +4 -1
  233. package/dist/plugins/tidb-session-store.js +3 -2
  234. package/dist/plugins/workspace-fill.d.ts +60 -0
  235. package/dist/plugins/workspace-fill.js +106 -0
  236. package/dist/project-bundle.d.ts +38 -0
  237. package/dist/project-bundle.js +609 -0
  238. package/dist/run-cancel-context.d.ts +34 -0
  239. package/dist/run-cancel-context.js +7 -0
  240. package/dist/run-local.js +2 -8
  241. package/dist/runs.d.ts +39 -12
  242. package/dist/runs.js +69 -55
  243. package/dist/sandbox-egress-policy.d.ts +4 -1
  244. package/dist/sandbox-egress-policy.js +3 -1
  245. package/dist/sandbox-git-plane.d.ts +31 -12
  246. package/dist/sandbox-git-plane.js +24 -4
  247. package/dist/security.d.ts +11 -2
  248. package/dist/security.js +8 -0
  249. package/dist/server-secret-env.d.ts +2 -1
  250. package/dist/server-secret-env.js +2 -4
  251. package/dist/session-error-wire.d.ts +46 -4
  252. package/dist/session-error-wire.js +23 -0
  253. package/dist/session-incarnation.d.ts +9 -3
  254. package/dist/session-sync-kernel.d.ts +42 -1
  255. package/dist/session-sync-kernel.js +17 -0
  256. package/dist/session-sync.js +16 -4
  257. package/dist/sql-engine-coordinates.d.ts +49 -0
  258. package/dist/sql-engine-coordinates.js +27 -0
  259. package/dist/standing-answer-grant.d.ts +31 -2
  260. package/dist/standing-answer-grant.js +14 -2
  261. package/dist/store-posture.d.ts +1 -1
  262. package/dist/task-a2a.d.ts +1 -1
  263. package/dist/task-mcp.d.ts +1 -1
  264. package/dist/task-settings.d.ts +62 -27
  265. package/dist/task-settings.js +105 -29
  266. package/dist/terminal.d.ts +97 -20
  267. package/dist/terminal.js +107 -18
  268. package/dist/tool-approval.d.ts +4 -3
  269. package/dist/tool-approval.js +12 -14
  270. package/dist/trace/core-keyset-guard.d.ts +196 -2
  271. package/dist/trace/engine-notice-wire.d.ts +8 -2
  272. package/dist/trace/engine-notice-wire.js +4 -2
  273. package/dist/trace/exit-seat.d.ts +9 -1
  274. package/dist/trace/exit-seat.js +1 -0
  275. package/dist/trace/ledger-sink.d.ts +8 -4
  276. package/dist/trace/ledger-sink.js +2 -2
  277. package/dist/trace/model-segment.d.ts +2 -2
  278. package/dist/trace/project.d.ts +18 -1
  279. package/dist/trace/project.js +16 -0
  280. package/dist/trace/redact.js +323 -90
  281. package/dist/trace/self-minted-wire.d.ts +419 -4
  282. package/dist/trace/self-minted-wire.js +145 -0
  283. package/dist/trace/wire-projection-faces.d.ts +1 -1
  284. package/dist/trace/wire-projection-faces.js +1 -1
  285. package/dist/verifier-pause-card.d.ts +8 -0
  286. package/dist/verifier-pause-card.js +19 -0
  287. package/dist/web-admin-client/apply-effective.d.ts +5 -7
  288. package/dist/web-admin-client/apply-effective.js +22 -2
  289. package/dist/web-admin-client/candidate-gate.d.ts +1 -1
  290. package/dist/web-admin-client/effective-keys.js +2 -0
  291. package/dist/web-admin-client/read-warnings.d.ts +2 -2
  292. package/dist/web-admin-client/types.d.ts +4 -0
  293. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -2
  294. package/package.json +2 -2
  295. package/wire-changes/server@7.141.0.json +367 -0
  296. package/wire-changes/server@7.142.0.json +639 -0
@@ -1,7 +1,7 @@
1
1
  import { type SessionStore } from "@sema-agent/core";
2
2
  import type { ServiceConfig } from "../config-types.js";
3
3
  import type { StoreBackend } from "./store-backend.js";
4
- import { type DelegationRootWitness, type DelegationTree, type OrphanOwnerLog } from "../delegated-session-owner.js";
4
+ import { type DelegationRootWitness, type OrphanOwnerLog } from "../delegated-session-owner.js";
5
5
  import type { OwnerAwareSessionStore } from "../security.js";
6
6
  /**
7
7
  * Session-store plugin seam.
@@ -37,31 +37,29 @@ export declare function createSessionStore(config: Pick<ServiceConfig, "sessionB
37
37
  * (`evictPolicy="delete"`)。本函数自己**不做**、也无法通用地做"这个 store 实例的 release 是否安全"的
38
38
  * 判别(`SessionStore` 接口没有暴露这个信息,类型层面区分不了两种店形)——上文"误判方向永远偏保守"
39
39
  * 只覆盖 `getLeafId` 那道校验,不覆盖"传了一个 release=delete 的店进来"这个前提违反。**调用方必须
40
- * 保证只在真正 durable(release 非破坏性)的店上调用本函数**——server 侧的强制点在 main.ts 装配区:
41
- * `ensureChildSessionDurable` 闭包只在 `config.sessionBackend === "tidb"` 时构造。
40
+ * 保证只在真正 durable(release 非破坏性)的店上调用本函数**——server 侧的强制点在 stage-07 装配区:
41
+ * `ensureChildSessionDurable` 闭包只在部署的子代转录档位是 `full` 时构造(core `resolveSubagentTranscriptTier`:会话店声明了 durable 的委派分区 ——
42
+ * 三只持久会话店;无 repo 的内存店不声明,档位不是 `full`,挂点不装)。
42
43
  */
43
44
  export declare function ensureSessionDurable(hostStore: SessionStore, sessionId: string): Promise<void>;
44
45
  /**
45
- * [ref] 主动晋升版 ensureChildSessionDurable(cli TiDB 真机撞获:1.263 的只查不迁形在拆店生产形下
46
- * 恒否决 park——bg 子会话住 subRunner 私有 TTL 店、从不在 host 店,core 收 veto 即 expire checkpoint
47
- * (14ms~2s)+ settle failed,赎回窗口不存在):
48
- * 0. 属主([ref],`delegated-session-owner.ts`):按委派继承律判一次(唯一判据 `judgeDelegatedOwner`)—— 来源集 = 见证的根这一只(调用方交出的委派树根 + 调用方判断那一刻
46
+ * core `SubagentToolOptions.background.ensureChildSessionDurable` 的实现体 —— 停驻挂点的**见证**:这只后台子代的转录此刻是 host 店里一只有真内容、
47
+ * 属主按委派继承律定得下来的会话,才放行停驻;任何一步不成立 ⇒ 抛 = 否决(core 收 veto 即 expire checkpoint + settle failed,pre-153 行为,不放大)。
48
+ *
49
+ * 规则(`fork-routing-session-store.ts` 头注):一只后台子代的 durable 行在,它的转录就是 host 店委派分区里的一只 placed 行。挂点只装在声明了
50
+ * durable 委派分区的部署上(stage-07:core `resolveSubagentTranscriptTier(...) === "full"`),那里 core 给每只新起的后台子代铸 placement、
51
+ * 转录生来就在 host 店 —— 所以这里**只验、不搬**(此前的「晋升」把私店的转录整树拷进 host 店,随这条规则退役)。
52
+ * 0. 属主(`delegated-session-owner.ts`):按委派继承律判一次(唯一判据 `judgeDelegatedOwner`)—— 来源集 = 见证的根这一只(调用方交出的委派树根 + 调用方判断那一刻
49
53
  * 读到的根行住客:请求腿 = 本请求经鉴权解析的会话与受理时那一读,`/decide` 赎回腿 = 行上的 `rootSessionId` 与写口放行的那一位(认领之前解析器那一读);core 的挂点签名只给子代 id,
50
- * 场景层把见证绑进闭包交下来)。此刻根行在而住客不是见证的那一位(删了、同 id 被重新登记)⇒ **拒**(`assertRootStillWitnessed`:不按新住客晋升 / 认领);
54
+ * 场景层把见证绑进闭包交下来)。此刻根行在而住客不是见证的那一位(删了、同 id 被重新登记)⇒ **拒**(`assertRootStillWitnessed`);
51
55
  * 缺席(行上没有根)或根行此刻不在 ⇒ 孤行哨兵。
52
- * 1. 快路:已在 host 店且有真内容 → 通过,**前提是那一行的属主按律定得下来**(`settleDelegatedOwner`:记着别的委派树 ⇒ 抛;记着这一棵 ⇒ 按它记录的根、父再判一次
53
- * (与 M23 同读数),无主 ⇒ 认领(CAS 子行,写判断时的读数)、没落下 ⇒ 抛(只有记录委派树的 SQL 店;local 店的行不记委派树 ⇒ 不认领);此刻属主不是律给的那一只 ⇒ 那是别人的同 id 会话,
54
- * 不是这只子代的转录 ⇒ 抛 = 停驻否决;与晋升建行前那一道同一只函数)。
55
- * 2. 晋升:routing.promoteToHost 把子会话整树从私店拷进 host 店(拷贝→私店 forget→路由记 host,
56
- * 三步见 ForkRoutingSessionStore.promoteToHost),宿主行在建行那一笔里带第 0 步的属主;写的是哨兵 ⇒ 写落下之后一行 warn。
57
- * 3. 复验:同一把 `ensureSessionDurable` 严检(leaf 非空)+ 同一只属主定夺 —— 晋升产物必须达到与快路完全相同的
58
- * 标准,任何一步失败=如实 reject=veto=pre-153 行为(不放大)。
56
+ * 1. 在不在、有没有真内容:{@link ensureSessionDurable}(`requireExisting` 打开 + 叶子非空 + forget 收尾)。任何失败(不在、空壳、读故障)⇒ 原样上抛 = 否决
57
+ * (读故障也否决:说不准转录在不在,就不让一次审批押在它上面)。这里**不判它是不是 placed 行**:复活的是旧行时 id 取行上的会话(升级前停驻、当年晋升进来的普通行),
58
+ * 它照样是这只子代的转录;判据只问「host 店里有这份真内容」。
59
+ * 2. 属主定夺(`settleDelegatedOwner`):记着别的委派树 ⇒ 抛;记着这一棵 ⇒ 按它记录的根、父再判一次(与 M23 同读数),无主 ⇒ 认领(CAS 子行,写判断时的读数)、没落下 ⇒ 抛;
60
+ * 此刻属主不是律给的那一只 ⇒ 那是别人的同 id 会话,不是这只子代的转录 ⇒ 抛 = 停驻否决。认领写下的是哨兵 ⇒ 写落下之后一行 warn(`site: "park"`)。
59
61
  * 宿主店必须读得出属主(`delegatedOwnerHostOf`:`headOf`;记录委派树的店另交 `placementOf` + `claimOwnerless`,两只同在或同缺;生产的 SQL / local 店与缓存包装层都满足),
60
62
  * 否则响亮拒。
61
- * 调用方纪律与 {@link ensureSessionDurable} 同款:hostStore 必须是 release 非破坏性的真 durable 店
62
- * (stage-07 的 `config.sessionBackend === "tidb"` 门不变 —— 该词在 local 后端上同样归一成 `tidb`,所以本腿在 local 宿主店上也跑)。
63
63
  */
64
- export declare function ensureChildSessionDurableWithPromotion(hostStore: OwnerAwareSessionStore, routing: {
65
- promoteToHost(sessionId: string, owner: string | null, tree: DelegationTree): Promise<void>;
66
- }, sessionId: string, root: DelegationRootWitness | undefined, log: OrphanOwnerLog): Promise<void>;
64
+ export declare function attestChildSessionDurable(hostStore: OwnerAwareSessionStore, sessionId: string, root: DelegationRootWitness | undefined, log: OrphanOwnerLog): Promise<void>;
67
65
  //# sourceMappingURL=session-store.d.ts.map
@@ -23,24 +23,16 @@ export async function ensureSessionDurable(hostStore, sessionId) {
23
23
  await hostStore.forget?.(acquired.sessionId);
24
24
  }
25
25
  }
26
- export async function ensureChildSessionDurableWithPromotion(hostStore, routing, sessionId, root, log) {
26
+ export async function attestChildSessionDurable(hostStore, sessionId, root, log) {
27
27
  const host = delegatedOwnerHostOf(hostStore);
28
28
  if (host === undefined) {
29
29
  throw new Error(`session "${sessionId}" cannot be parked: the host session store cannot read a session's owner (headOf), or records delegation trees without being able to claim under them (placementOf / claimOwnerless), so the delegated transcript's owner cannot be derived or checked`);
30
30
  }
31
31
  const judged = await readDelegatedOwner(sessionId, root === undefined ? {} : { rootSessionId: root.sessionId }, sourceFacesVia(host.headOf));
32
32
  assertRootStillWitnessed(sessionId, root, judged);
33
- let promoted = false;
34
- try {
35
- await ensureSessionDurable(hostStore, sessionId);
36
- }
37
- catch {
38
- await routing.promoteToHost(sessionId, judged.inherited.owner, judged.tree);
39
- await ensureSessionDurable(hostStore, sessionId);
40
- promoted = true;
41
- }
33
+ await ensureSessionDurable(hostStore, sessionId);
42
34
  const settled = await settleDelegatedOwner(host, sessionId, judged.inherited.owner, judged.tree);
43
- if (promoted || settled.claimed)
44
- reportWrittenOwner(log, "promotion", sessionId, settled.judged ?? judged);
35
+ if (settled.claimed)
36
+ reportWrittenOwner(log, "park", sessionId, settled.judged ?? judged);
45
37
  }
46
38
  //# sourceMappingURL=session-store.js.map
@@ -5,6 +5,7 @@ import { type OutcomeSink } from "./file-outcome-sink.js";
5
5
  import { type InboxWarn } from "./workflow-run-store-sql.js";
6
6
  import { FileRunStore } from "./file-run-store.js";
7
7
  import { type OneTimeMigrationReport } from "./one-time-migrations.js";
8
+ import type { LocalSessionRows } from "./local-session-window.js";
8
9
  import type { SessionHeadReader } from "./caching-session-store.js";
9
10
  import { LocalCheckpointStore } from "./local-checkpoint-store.js";
10
11
  import { FileResumeAnchorStore } from "./file-resume-anchor-store.js";
@@ -325,16 +326,19 @@ export interface StoreBackend {
325
326
  info?(msg: string, meta?: unknown): void;
326
327
  }): CheckpointStoreFull;
327
328
  /**
328
- * 待决 checkpoint 的**只读**读口(**必选**成员)—— 审批账本留存律(`approval-ask-retention.ts` 的挂靠集)读「还有哪些会话停驻着」就走这一口,不走 {@link checkpoint}。
329
+ * 待决 checkpoint 的**只读**读口(**必选**成员)—— 「前世 / 别的副本留下的停驻」的读法,不走 {@link checkpoint}。两个读者:
330
+ * · 审批账本留存律(`approval-ask-retention.ts` 的挂靠集)读「还有哪些会话停驻着」(`listPendingSessionIds`);
331
+ * · local 车道起服收敛(`approval-crash-convergence.ts`)对审计判为孤儿的前世 `PARKING` 问跑对账器判据 ① 的候选读口(`findCheckpointCandidatesForAsk`:
332
+ * 停车行其实已提交 ⇒ `PARKED`,不收 `VOID`)。
329
333
  * 区别只有一条:这一口**不牵动任何别的店**。local 车道的 `checkpoint()` 是 durable 停驻面的装配点 —— 它给 run 店接上 [ref] 的收割探针(停驻行的收割从 no-op 变生效),
330
- * 那是开了 `DURABLE_APPROVAL` 的部署才该发生的事;留存腿在没开它的部署上同样要读(前世 / 别的副本留下的停驻不因这一世的开关消失,看不见就会把还能被决的停驻的问删掉),
331
- * 所以读和接线必须分口。SQL 孪生本来无状态(接线靠 JOIN),两口同一只。
334
+ * 那是开了 `DURABLE_APPROVAL` 的部署才该发生的事;两个读者在没开它的部署上同样要读(前世 / 别的副本留下的停驻不因这一世的开关消失:留存腿看不见就会把还能被决的停驻的问删掉,
335
+ * 起服收敛看不见就会把前世已提交的停驻的问收成 `VOID`),所以读和接线必须分口。SQL 孪生本来无状态(接线靠 JOIN),两口同一只。
332
336
  *
333
- * 🔴 为什么必选而不是跟着 {@link checkpoint} 一起可选:留存腿读不到这一口的后果在**删除方向**(挂靠集里少了待决停驻 ⇒ 还能被决的停驻的问按龄被删),
334
- * 「有 checkpoint 店却没交读口」若只是运行期的 `undefined`,就是一次静默少保护;必选 ⇒ 漏实现是**编译红**。没有 checkpoint 店的后端(`checkpoint` 缺席)交一只恒空的读口
335
- * (`{ listPendingSessionIds: async () => [] }` —— 没有店 ⇒ 没有停驻,这是事实不是缺省)。
337
+ * 🔴 为什么必选而不是跟着 {@link checkpoint} 一起可选:读不到这一口的后果都在**不可逆方向**(挂靠集里少了待决停驻 ⇒ 还能被决的停驻的问按龄被删;判据 ① 恒不命中 ⇒
338
+ * 已提交的停驻的问被收成终态),「有 checkpoint 店却没交读口」若只是运行期的 `undefined`,就是一次静默少保护;必选 ⇒ 漏实现是**编译红**。没有 checkpoint 店的后端
339
+ * (`checkpoint` 缺席)交一只恒空的读口(`{ listPendingSessionIds: async () => [], findCheckpointCandidatesForAsk: async () => [] }` —— 没有店 ⇒ 没有停驻,这是事实不是缺省)。
336
340
  */
337
- checkpointReadView(): Pick<CheckpointStoreFull, "listPendingSessionIds">;
341
+ checkpointReadView(): Pick<CheckpointStoreFull, "listPendingSessionIds" | "findCheckpointCandidatesForAsk">;
338
342
  toolResult?(): ToolResultStoreFull;
339
343
  imageIndex?(): ImageIndex;
340
344
  imageBake?(): ImageBake;
@@ -408,6 +412,36 @@ export interface StoreBackend {
408
412
  * 编辑被拒)。云形缺 MinIO ⇒ fail-loud(启动了才是假安全);`SNAPSHOT_BLOB_ALLOW_SQL_BYTES=true` =
409
413
  * 显式接受 SQL 店 + per-blob 帽(单机/测试台形)。local 后端不经此门(file 店,无 SQL 包墙)。 */
410
414
  export declare function assertCloudSnapshotBlobPosture(kind: "mysql" | "pg" | "local", config: Pick<ServiceConfig, "snapshotBlobStore" | "snapshotBlobAllowSql">): void;
415
+ /**
416
+ * LOCAL backend (clay 2026-06-25 seamless local↔cloud; P0.5 variant-2 + variant-1 file-backed 2026-06-26): a DB-less StoreBackend
417
+ * so a locally-run HTTP service serves the SAME contract a cloud worker does (runs + trace + sessions + resume;
418
+ * long-term memory rides the [ref] memory ENGINE, not this backend). Sessions / per-session policy / file snapshots are
419
+ * FILE-BACKED via core's `FileStorageBackend` over `root`, so a session SURVIVES a process restart and the resume
420
+ * picker (GET /v1/sessions → LocalSessionStore.listSessions) is restart-visible — the 2c "first local, then sync"
421
+ * hard prerequisite.
422
+ *
423
+ * The session store stays the §0.5 owner-aware {@link LocalSessionStore} wrapper, but over a fresh `FileSessionRepo`
424
+ * (NOT `fileBackend.sessionStore`, which is core's bare `TtlSessionStore` lacking the §0.5 owner/list/fork/delete
425
+ * ops); policy/snapshot delegate to the fileBackend's own file stores. The fileBackend takes a coarse boot
426
+ * single-instance lock on `root/LOCK` (an `O_EXCL`+`linkSync` PIDFILE, not flock — [ref] F4; same-host only:
427
+ * liveness = `process.kill(pid,0)`, so a foreign PID namespace or NFS defeats it, see DEPLOY-PREREQS) —
428
+ * `close()` MUST `dispose()` it so a graceful restart can
429
+ * re-acquire (a stale lock from a dead PID is pruned by core, but graceful restart relies on the release).
430
+ *
431
+ * P0.5 variant-1 (clay 2026-06-26): `run()`/`resumeAnchor()` are now FILE-backed too (FileRunStore /
432
+ * FileResumeAnchorStore over core 1.138.0's crash-safe fs primitives — atomicWriteFile/writeThenLink/AppendLog), so the
433
+ * runs ledger + trace event log + E18 anchors SURVIVE a process restart, completing variant-2's promised follow-on. The
434
+ * picker still lists from the session abstraction (variant-2), but a restart now also re-reads run history + resume
435
+ * anchors. Session owners are persisted too (7.119.0: in the session-meta sidecar, next to the incarnation — see
436
+ * LocalSessionStore), so an owner survives a restart like the SQL twins' column does. The cloud-only stores
437
+ * (checkpoint/approval/toolResult/imageIndex/imageBake/breaker/costQuota/rateLimiter) are OMITTED → the consumers'
438
+ * `backend?.X?.()` gates degrade them exactly like a no-DB env.
439
+ */
440
+ /**
441
+ * The local session store's rows for the session view's window (`local-session-window.ts`): the local backend's own session store — its row
442
+ * projection and its log export — and `undefined` on any other backend. The one place that answers "is there a local store to read rows from".
443
+ */
444
+ export declare function localSessionRowsOf(backend: StoreBackend | undefined): LocalSessionRows | undefined;
411
445
  /**
412
446
  * 启动日志里「跨副本 or 进程内」那一格的标签。
413
447
  *
@@ -17,6 +17,7 @@ import { NO_SQL_MIGRATIONS, assertExecutionPlacementSwitchAdmissibleOnPool, back
17
17
  import { deployedExecutionLanes } from "../execution-lane-caps.js";
18
18
  import { assertRetiredSchemaAbsentOnPool } from "./schema-consistency.js";
19
19
  import { LocalSessionStore, createLocalSessionRepo } from "./local-session-store.js";
20
+ import { SESSION_DURABILITY, SESSION_PLACEMENTS } from "./session-placement.js";
20
21
  import { LocalCheckpointStore } from "./local-checkpoint-store.js";
21
22
  import { FileResumeAnchorStore } from "./file-resume-anchor-store.js";
22
23
  import { TiDBApprovalExemptionStore, PgApprovalExemptionStore, FileApprovalExemptionStore } from "./approval-exemption-store.js";
@@ -150,7 +151,7 @@ class TiDBBackend {
150
151
  workflowParkDecisions() { return (this.parkDecisionsInst ??= new SqlWorkflowParkDecisionStore(this.run())); }
151
152
  parkDecisionsInst;
152
153
  sharedMemoryStore(authorizer) { return new TiDBSharedMemoryStore(this.pool, authorizer); }
153
- checkpoint(logger) { return new TiDBCheckpointStore(this.pool, logger, { retention: sqlStoreRetention(this.config.retentionSweep) }); }
154
+ checkpoint(logger) { return new TiDBCheckpointStore(this.pool, logger, { retention: sqlStoreRetention(this.config.retentionSweep), claims: this.run() }); }
154
155
  checkpointReadView() { return this.checkpoint(); }
155
156
  toolResult() { return new TiDBToolResultStore(this.pool, { retention: sqlStoreRetention(this.config.retentionSweep) }); }
156
157
  imageIndex() { return new TiDBImageIndex(this.pool); }
@@ -204,7 +205,7 @@ class PgBackend {
204
205
  workflowParkDecisions() { return (this.parkDecisionsInst ??= new SqlWorkflowParkDecisionStore(this.run())); }
205
206
  parkDecisionsInst;
206
207
  sharedMemoryStore(authorizer) { return new PgSharedMemoryStore(this.pool, authorizer); }
207
- checkpoint(logger) { return new PgCheckpointStore(this.pool, logger, { retention: sqlStoreRetention(this.config.retentionSweep) }); }
208
+ checkpoint(logger) { return new PgCheckpointStore(this.pool, logger, { retention: sqlStoreRetention(this.config.retentionSweep), claims: this.run() }); }
208
209
  checkpointReadView() { return this.checkpoint(); }
209
210
  toolResult() { return new PgToolResultStore(this.pool, { retention: sqlStoreRetention(this.config.retentionSweep) }); }
210
211
  imageIndex() { return new PgImageIndex(this.pool); }
@@ -222,6 +223,9 @@ class PgBackend {
222
223
  pgPool() { return this.pool; }
223
224
  async dbNowMs() { const res = await this.pool.query("SELECT ROUND(EXTRACT(EPOCH FROM now())*1000) AS ms"); return Number(res.rows[0]?.ms); }
224
225
  }
226
+ export function localSessionRowsOf(backend) {
227
+ return backend instanceof LocalBackend ? backend.session() : undefined;
228
+ }
225
229
  class LocalBackend {
226
230
  kind = "local";
227
231
  fileBackend;
@@ -265,7 +269,11 @@ class LocalBackend {
265
269
  error: (msg, fields) => void (this.onError ?? this.onWarn)?.(msg, fields),
266
270
  strictHydrate: config.runStoreStrictHydrate,
267
271
  });
268
- this.sessionStore = new LocalSessionStore(createLocalSessionRepo(this.fileBackend.root), this.runStore, this.fileBackend.root, { log: createLogger(config.logLevel) });
272
+ this.sessionStore = new LocalSessionStore(createLocalSessionRepo(this.fileBackend.root), this.runStore, this.fileBackend.root, {
273
+ log: createLogger(config.logLevel),
274
+ durability: SESSION_DURABILITY,
275
+ placements: SESSION_PLACEMENTS,
276
+ });
269
277
  this.parkDecisionLedgerRoot = config.workflowRunStoreBackend === "memory" ? undefined : join(this.fileBackend.root, "workflows");
270
278
  this.resumeAnchorStore = new FileResumeAnchorStore(this.fileBackend.root);
271
279
  this.approvalExemptionStore = new FileApprovalExemptionStore(this.fileBackend.root, { warn: corruptReadWarn });
@@ -283,9 +291,12 @@ class LocalBackend {
283
291
  ? undefined
284
292
  : (plane) => fileSessionCaptureRecordStore(plane?.controlDir ?? ownControlDir);
285
293
  }
286
- async ensureSchema() { }
294
+ async ensureSchema() {
295
+ await this.sessionStore.settleArchiveSnapshots();
296
+ }
287
297
  async runOneTimeMigrations() {
288
298
  const holderDeathCodeRowsBackfilled = this.runStore.migrateLegacyHolderDeathCodes();
299
+ const archives = await this.sessionStore.migrateLocalArchives();
289
300
  const placements = await backfillLocalExecutionPlacements(this.runStore.sessionIdsWithRuns(), this.sessionStore.executionPlacementBackfill, { lanes: this.executionLanes });
290
301
  return {
291
302
  ...NO_SQL_MIGRATIONS,
@@ -293,6 +304,12 @@ class LocalBackend {
293
304
  executionPlacementsBackfilled: placements.backfilled,
294
305
  executionPlacementSessionsUnreadable: placements.unreadable,
295
306
  askExecutionLanesBackfilled: backfillLocalAskExecutionLanes(this.approvalAskStore, { lanes: this.executionLanes }),
307
+ askJournalRecordsUpgraded: this.approvalAskStore.upgradeJournalEnvelope(),
308
+ localArchiveRowFactsBackfilled: archives.rowFactsBackfilled,
309
+ localArchivesAnonymized: archives.anonymized,
310
+ localArchivesKeptStamped: archives.keptStamped,
311
+ localArchivesUnreadable: archives.unreadable,
312
+ localArchiveSnapshotsRecovered: archives.snapshotsRecovered,
296
313
  };
297
314
  }
298
315
  async close() {
@@ -336,6 +353,7 @@ class LocalBackend {
336
353
  return (this.checkpointStoreInst ??= new LocalCheckpointStore(this.fileBackend.root, {
337
354
  getActiveTaskId: (sessionId) => this.runStore.getActiveTaskId(sessionId),
338
355
  readSessionOwner: async (sessionId) => (await this.sessionStore.headOf(sessionId))?.owner,
356
+ claims: this.runStore,
339
357
  }));
340
358
  }
341
359
  checkpoint() {
@@ -20,6 +20,7 @@
20
20
  */
21
21
  import type { PeerSessionTempo, TaskResult, TaskStatus } from "@sema-agent/core";
22
22
  import type { RunIdentity } from "../identity-source.js";
23
+ import type { SessionUnreadable } from "../session-error-wire.js";
23
24
  /**
24
25
  * **File 店族的 boot 期拒启基类**([ref];把 `RunStoreStrictHydrateError` 的那条论证升成一条族规)。
25
26
  *
@@ -307,6 +308,13 @@ export type CreateRunOutcome = {
307
308
  activeTaskId?: undefined;
308
309
  sessionAbsent: true;
309
310
  };
311
+ /**
312
+ * 认领栅栏交给 `write` 的那一行 run 此刻的事实(SQL = 锁住那一行读的;进程内 = 同步段里读的)。认领与 `running` 只认 taskId,而续跑腿让同一个 taskId
313
+ * 停驻之后回到 `running`、认领仍在 —— 「接下这笔写的那条腿还是不是这条 run 的腿」要按腿问的写者拿它问(今天:副本 `instanceId`,续跑的 CAS 赢点把它改成接手的副本)。
314
+ */
315
+ export interface FencedRun {
316
+ readonly instanceId: string | null;
317
+ }
310
318
  /** SQL 认领栅栏的结局:`held: false` = 认领此刻不是这条 run 的(或它已不在跑),`write` 一次都没调。 */
311
319
  export type ClaimFenced<T> = {
312
320
  readonly held: true;
@@ -325,13 +333,13 @@ export type FenceLanded = typeof FENCE_LANDED;
325
333
  * **宿主会话的认领当写栅栏用 —— 进程内那一形**(属主 = run 店;Memory / File 两孪生同律)。认领 = 会话 → 持有它的 taskId,
326
334
  * 由 {@link CreateRunOutcome} 的建行(或续跑腿的 markResuming)取得、终局 / 收割放掉 —— 「这次受理还持有会话吗」只问它,不另记身份。
327
335
  *
328
- * `whileHoldingClaim(sessionId, taskId, write)`:**只有** `taskId` 此刻持有 `sessionId` 的认领、且这条 run 是活的(`running`)时才调 `write`、答 `true`,
336
+ * `whileHoldingClaim(sessionId, taskId, write)`:**只有** `taskId` 此刻持有 `sessionId` 的认领、且这条 run 是活的(`running`)时才调 `write`(交给它那一行的 {@link FencedRun})、答 `true`,
329
337
  * 同一拍把这条 run 的活性戳刷新(一次栅栏写也是一次心跳);否则一次都不调、答 `false`。整只是**同步**的 —— `write` 也是:判、刷新、`write` 落定是同一个同步段,
330
338
  * 中间没有让出,所以认领在 `write` 落定之前挪不动(这一条由类型担保 —— `write` 必须同步交回 {@link FENCE_LANDED},不靠「这里恰好没有 await」)。
331
339
  * SQL 孪生的栅栏是一个事务(`run-store-sql.ts` `SqlRunStore.whileHoldingClaim`,`write` 收那只事务连接,结局 {@link ClaimFenced}),不是本型。
332
340
  */
333
341
  export interface InProcessClaimFence {
334
- whileHoldingClaim(sessionId: string, taskId: string, write: () => FenceLanded): boolean;
342
+ whileHoldingClaim(sessionId: string, taskId: string, write: (run: FencedRun) => FenceLanded): boolean;
335
343
  }
336
344
  /**
337
345
  * `RunRecord["status"]`(= `"running" | TaskStatus`)的**唯一分类表** —— 词表的属主 = 判据的属主,
@@ -436,7 +444,7 @@ export declare function isUnsuccessfulRunStatus(status: RunRecord["status"]): bo
436
444
  * 未知词的方向与 {@link isTerminalRunStatus} 同向保守:判**不是** park。理由是消费点会据此做**不可逆**
437
445
  * 动作(cancel 的恢复把手会 CAS-expire 一张 checkpoint);对一个本进程读不懂的状态,少动一次远好过错杀。
438
446
  */
439
- export declare function isParkedRunStatus(status: RunRecord["status"]): boolean;
447
+ export declare function isParkedRunStatus(status: RunRecord["status"]): status is ParkedRunStatus;
440
448
  /**
441
449
  * 会话的**活动词**(core `PeerSessionTempo`)——「这条会话现在在忙还是闲」的**唯一**一张表([ref])。
442
450
  *
@@ -524,6 +532,9 @@ export interface SessionSummary {
524
532
  /** 行标签(`src/session-row.ts` 的 `sessionRowTagOf`):这一位住客「PATCH 能写的每一格」的单向摘要,present-iff 这一行可被条件写(店有行格写原语 ∧ 行上有化身);
525
533
  * 只经店的列表行映射铸(`sessionRowTagKey`)。没有会话行的 lister(legacy task_run 聚合 / 内存孪生)缺席。wire 半场见 `security.ts SessionListItem.rowTag`(同名同义)。 */
526
534
  rowTag?: string;
535
+ /** 这只会话的存档读不出:present-iff 头的叶子读不出(唯一铸点 `session-error-wire.ts` 的 `sessionUnreadableOf`;码与读口同表)。叶子是行上一列的店(SQL 孪生)
536
+ * 与没有会话行的 lister 恒缺席。wire 半场见 `security.ts SessionListItem.unreadable`(同名同义)。 */
537
+ unreadable?: SessionUnreadable;
527
538
  }
528
539
  /**
529
540
  * [ref] —— 会话**枚举面**读 run 账里「每会话的 run 投影」的窄读面:`runCount` / `lastStatus` / `lastRunId` / `lastTaskRunId` 四位,
@@ -565,6 +576,27 @@ export interface LatestRunRef {
565
576
  owner: string | null;
566
577
  createdAt: string;
567
578
  }
579
+ /** 引擎对一条腿实际挂上的记忆读集的观测(core `TaskResult.effectiveMemoryScopes` 的值;core 包根不单独导出这只型,从结果型取)。 */
580
+ export type EffectiveMemoryScopes = NonNullable<PersistedTaskResult["effectiveMemoryScopes"]>;
581
+ /**
582
+ * 「这个会话**最近一条已结腿**与它的记忆观测」的窄读面(run 店的 `latestMemoryObservationForSession` 的回值)—— 会话记忆同步触发口
583
+ * (`POST /v1/sessions/:id/memory/sync`)的**会话读集**只读这一只:读集 = 最近一条已结腿结果里引擎铸的观测(引擎一处铸、随 run 行持久;路由不重铸 spec)。
584
+ *
585
+ * 一条规则,三只实现同答:
586
+ * · 已结 = {@link isTerminalRunStatus}(成功 / 非成功终局;在飞与停在门上的腿还占着会话的认领,不算;读不懂的词同向保守判「未结」)。两孪生调这只谓词;
587
+ * SQL 两方言按本文件的惯例写字面词集 `status IN ('completed','failed','blocked')`,与谓词的同源由 `test/source-hygiene-gate.test.ts` 的方言对 parity 门钉;
588
+ * 取最近一条(排序口径与 {@link LatestRunRef} 逐字同一句:`created_at DESC, task_id DESC`);
589
+ * · `effectiveMemoryScopes` = 那一条腿结果里的观测**原样**(三态:`mounted` / `memoryless` / `none`,读法归调用方);
590
+ * · 那一条腿结了而**没有观测**(结果为空 / 结果里没有这一键 —— prepare 没走完)⇒ 交回那一条、观测缺席,**不往更早的腿找**
591
+ * (读集是「最近一条已结腿」的,不是「最近一条有观测的腿」的)。
592
+ * 结果列读不出 ⇒ 抛(与 `getRun` 同一个失败方向:不把一条结了的腿当成没有结果)。
593
+ */
594
+ export interface SettledLegMemoryObservation {
595
+ readonly taskId: string;
596
+ readonly effectiveMemoryScopes: EffectiveMemoryScopes | undefined;
597
+ }
598
+ /** {@link SettledLegMemoryObservation} 的一只投影(三只实现共用):一条已结腿的结果 → 读口的回值。 */
599
+ export declare function settledLegObservationOf(taskId: string, result: PersistedTaskResult | null): SettledLegMemoryObservation;
568
600
  export interface RunEvent {
569
601
  seq: number;
570
602
  type: string;
@@ -56,4 +56,7 @@ export function sessionTempoOfRunStatus(lastStatus) {
56
56
  export function runProvenElsewhere(run, thisInstanceId) {
57
57
  return typeof run.instanceId === "string" && run.instanceId.length > 0 && thisInstanceId !== undefined && run.instanceId !== thisInstanceId;
58
58
  }
59
+ export function settledLegObservationOf(taskId, result) {
60
+ return { taskId, effectiveMemoryScopes: result?.effectiveMemoryScopes ?? undefined };
61
+ }
59
62
  //# sourceMappingURL=store-contracts.js.map
@@ -21,12 +21,16 @@
21
21
  * - 隔离键排序规则:TiDB 表级 utf8mb4_bin / PG owner 列 COLLATE "C"(bga F2 同姿,'=' 字节等价)。
22
22
  * owner 值恒为 server 端从凭据推导(非用户输入),PAD SPACE 近撞面不可达,VARBINARY 键列不必要。
23
23
  * - 布尔列:无(遵 1.287.0 归一约定,若未来加,PG 用 SMALLINT 0/1)。
24
- * - 绑定:`bind` 把行钉到 sessionId(重复绑定=改绑,同 owner 内 last-wins;同 session 多 task 共享
25
- * 自然成立——listBySession 是物化输入)。未绑定行 = 上传后从未被任何 task 引用 ⇒ reapUnbound(TTL)。
24
+ * - 绑定:`bind` 把行钉到 sessionId,两条律都在店里、都不是先读后写(契约见 {@link TaskAttachmentStore.bind}):
25
+ * **一经绑定不再改绑**(UPDATE 谓词只放行未绑的行与已绑本会话的行,行级原子;同会话重绑 —— 首交重试、续跑腿重放存量体 —— 不改 `bound_at_ms`);
26
+ * **一只会话一只种子**(未绑的项目包只在这只会话还没有项目包时绑上:SQL 两方言是锚行加锁的一只事务,进程内的店是一段不被打断的判与改:内存店同一段同步,local 店的变更口同排一条串行段、bind 判在副本上、落成的行才合回)。
27
+ * 同 session 多 task 共享自然成立(listBySession 是物化输入)。
28
+ * 未绑定行 = 上传后从未被任何 task 引用 ⇒ reapUnbound(TTL)。
26
29
  */
27
30
  import { z } from "zod";
28
31
  import type { Pool } from "mysql2/promise";
29
32
  import type { PgQueryFn } from "./pg-query.js";
33
+ import type { Pool as PgPool } from "pg";
30
34
  import { type IndexSpec } from "./ensure-index.js";
31
35
  /**
32
36
  * 一条附件 meta 的**形属主**(整仓唯一那份键集)。
@@ -55,8 +59,14 @@ export interface TaskAttachmentStore {
55
59
  /** meta(不带 bytes);owner 不匹配/不存在 → null(无 oracle)。 */
56
60
  get(owner: string, id: string): Promise<TaskAttachmentRecord | null>;
57
61
  getContent(owner: string, id: string): Promise<Uint8Array | null>;
58
- /** 把 ids 钉到 sessionId(owner 门在 WHERE);返回真绑到的行数——调用方以 count===ids.length 判全成。 */
59
- bind(owner: string, ids: string[], sessionId: string, boundAtMs: number): Promise<number>;
62
+ /**
63
+ * 把 ids(请求序)钉到 sessionId。绑定的两条律都在店里判,判与写对这只会话的其它绑定是原子的(不是先读后写):
64
+ * · **一经绑定不再改绑** —— 已绑别的会话的行 ⇒ `bound_elsewhere`(行不动);已绑本会话的行命中、不改 `boundAtMs`;
65
+ * · **一只会话一只种子** —— 一只**未绑的项目包**只在这只会话此刻没有项目包时绑上,否则 ⇒ `bundle_seed_fixed`(同一次调用里请求序靠前的那只先占);
66
+ * 两次并发绑定各带一只新项目包到同一会话 ⇒ 恰一只绑上。
67
+ * 属主门在店里(owner 不匹配 / 不存在的 id 两边都不出现,无 oracle)。`bound` 按请求序、不重复。
68
+ */
69
+ bind(owner: string, ids: readonly string[], sessionId: string, boundAtMs: number): Promise<AttachmentStoreBindOutcome>;
60
70
  /** 物化输入(env 建立时)——按 session 单键查(绑定时已 owner 验过;session uuid 不可枚举)。 */
61
71
  listBySession(sessionId: string): Promise<TaskAttachmentRecord[]>;
62
72
  delete(owner: string, id: string): Promise<boolean>;
@@ -89,22 +99,108 @@ export interface AttachmentBytesStore {
89
99
  * (created_at_ms, id)分配 `attachments/<name>`;同名撞车时后者前缀 id 头 8 位。名字在上传时已
90
100
  * 消毒为安全 basename([A-Za-z0-9._-]),拼接后仍是安全相对路径。 */
91
101
  export declare function materializedRelPaths(atts: TaskAttachmentRecord[]): Map<string, string>;
92
- /** 半场②(提交时绑定 + objective 告知)的可测核。fresh 腿硬验证(未知/他人 id → throw(mapTo400));
93
- * resume 腿降级(warn+跳过——4xx 会砖存量 task;记录不可变 ⇒ 在场行的 notice 字节恒稳)。
94
- * notice 路径从**绑定后的全 session 集**推导(与物化共用 materializedRelPaths),只罗列本次引用的文件。 */
102
+ /**
103
+ * 提交体 `attachmentIds` 绑定时的拒(闭集;码与状态的唯一映射 = {@link attachmentBindRefusalAnswer})。
104
+ * · `bound_elsewhere` —— 这只附件已绑在别的会话上(一经绑定不再改绑;每个会话各上传一次);
105
+ * · `bundle_lane_not_isolated` —— 项目包只交给 OS 隔离边界内的车道(`laneIsolated`),这条腿的车道不是;
106
+ * · `bundle_seed_fixed` —— 本会话的种子已定(最早绑定的那只项目包),第二只项目包不收。
107
+ */
108
+ export type AttachmentBindRefusal = {
109
+ readonly kind: "bound_elsewhere";
110
+ readonly id: string;
111
+ } | {
112
+ readonly kind: "bundle_lane_not_isolated";
113
+ readonly id: string;
114
+ readonly lane: string;
115
+ } | {
116
+ readonly kind: "bundle_seed_fixed";
117
+ readonly id: string;
118
+ };
119
+ /** 店在一次绑定里答的拒(闭集的子集:车道那一只是提交侧对这条腿的判,店不判)。 */
120
+ export type AttachmentStoreBindRefusal = Extract<AttachmentBindRefusal, {
121
+ readonly kind: "bound_elsewhere" | "bundle_seed_fixed";
122
+ }>;
123
+ /** 一次 {@link TaskAttachmentStore.bind} 的答:此刻绑在这只会话上的 id(请求序)与店拒掉的 id;两边都不在的 id = 不在 / 他人的。 */
124
+ export interface AttachmentStoreBindOutcome {
125
+ readonly bound: readonly string[];
126
+ readonly refused: readonly AttachmentStoreBindRefusal[];
127
+ }
128
+ /**
129
+ * 进程内的店(local 文件店、测试内存店)的绑定律 —— {@link TaskAttachmentStore.bind} 那一条律在进程内的**唯一**写法:判与改在**同一段同步**里
130
+ * (本函数不 await),于是调用方只要不在本函数前后放开(内存店:整条 `bind` 就是这一段;local 店:`bind` 与店的其它变更口同排一条串行段),
131
+ * 同一进程里两次并发绑定不可能都读到「这只会话还没有种子」(local 契约本就是单进程,`local-task-attachment-store.ts` 头注)。
132
+ * 改的是传进来的 `rows` 里的行对象本身 —— 内存店传自己的索引;需要落盘的店传**副本**,只把落盘落成的那些 `claimed` 行合回自己的索引
133
+ * (`claimed` = 这一次新绑上的行,即副本里被改的那些行对象;索引只反映盘上已有的)。
134
+ */
135
+ export declare function bindRowsInProcess(rows: ReadonlyMap<string, TaskAttachmentRecord>, owner: string, ids: readonly string[], sessionId: string, boundAtMs: number): {
136
+ readonly outcome: AttachmentStoreBindOutcome;
137
+ readonly claimed: readonly TaskAttachmentRecord[];
138
+ };
139
+ /** 绑定拒的码 → 状态:唯一属主表(一个码在本服务上只有一个状态;`<名>_HTTP_STATUS` 是本仓这类表的名形,码目录门按名形读它的键)。 */
140
+ export declare const ATTACHMENT_BIND_HTTP_STATUS: {
141
+ readonly "attachment.bound_elsewhere": 409;
142
+ readonly "attachment.bundle_lane_not_isolated": 409;
143
+ readonly "attachment.bundle_seed_fixed": 409;
144
+ };
145
+ export type AttachmentBindCode = keyof typeof ATTACHMENT_BIND_HTTP_STATUS;
146
+ /** 一只绑定拒在 wire 上的答(状态取自 {@link ATTACHMENT_BIND_HTTP_STATUS} + 码 + 句 + 体键);穷举 switch —— 闭集多一种而这里没跟 ⇒ 编译红。 */
147
+ export declare function attachmentBindRefusalAnswer(r: AttachmentBindRefusal): {
148
+ readonly status: number;
149
+ readonly errorCode: AttachmentBindCode;
150
+ readonly message: string;
151
+ readonly extra: Record<string, string>;
152
+ };
153
+ /** 会话里**附件**那一类的行(项目包行不在其中)—— 告知的路径分配与物化读的唯一过滤读口:过滤在路径分配之前(种子行不挤动附件的撞名前缀)。 */
154
+ export declare function listAttachmentsBySession(store: TaskAttachmentStore, sessionId: string): Promise<TaskAttachmentRecord[]>;
155
+ /**
156
+ * 会话种子的读口:最早绑定的项目包行 + 它的字节,字节**必核**行上的 `sha256` 与 `sizeBytes`(种子身份 = 上传那一刻的摘要;local 店读侧不核,这里核)。
157
+ * 三种答:没有种子 ⇒ `undefined`;种子在、字节在、摘要对 ⇒ `{ record, bytes }`;店里的定局坏了(行在、字节没了 / 摘要不符)⇒ `{ record, broken }`。
158
+ * 店读本身抛(连接断、超时)⇒ 原样抛:那是一时的失败,不是这只会话的定答 —— 调用方据此分「换一次恢复点重读」与「这只环境此后同一个拒」。
159
+ */
160
+ export declare function seedOfSession(store: TaskAttachmentStore, sessionId: string): Promise<{
161
+ readonly record: TaskAttachmentRecord;
162
+ readonly bytes: Uint8Array;
163
+ } | {
164
+ readonly record: TaskAttachmentRecord;
165
+ readonly broken: string;
166
+ } | undefined>;
167
+ /**
168
+ * 半场②(提交时绑定 + objective 告知)的可测核。fresh 腿硬验证(未知/他人 id → `onMissing` throw;拒 → `onRefused` throw);
169
+ * resume 腿降级(warn+跳过 —— 4xx 会砖存量 task;记录不可变 ⇒ 在场行的 notice 字节恒稳)。判序:
170
+ * ① 行在不在(属主门);② 已绑别的会话 ⇒ `bound_elsewhere`;③ 项目包 × 车道不隔离 ⇒ `bundle_lane_not_isolated`;
171
+ * ④ 本次提交体里第二只新项目包 ⇒ `bundle_seed_fixed`(只看提交体本身,不读店);
172
+ * ⑤ 绑定 —— 「本会话已有种子」与「不改绑」两条律只在店里判({@link TaskAttachmentStore.bind},判与写原子:并发的两次提交在那里分胜负,这里不先读):
173
+ * 提交体那只新项目包先**单独**绑,店拒了它 ⇒ `onRefused` 在体里其余 id 绑上之前就到(新提交腿上,店拒了种子候选的提交,体里其余 id 一只都不绑;读与绑之间才被别的会话抢走的附件不在此列 —— 先绑上的不回滚,请求仍 409);其余 id 随后一次绑。
174
+ * notice 只罗列本次引用的**附件**(项目包是工作区本身,不进告知),路径从绑定后的全会话附件集推导(与物化共用 {@link materializedRelPaths})。
175
+ */
95
176
  export declare function bindAttachmentsForTask(opts: {
96
177
  store: TaskAttachmentStore;
97
178
  owner: string;
98
179
  ids: string[];
99
180
  sessionId: string;
181
+ /** 这条腿的执行车道(执行上下文那一读)与它是否在 OS 隔离边界内(`laneIsolated` 那一判,调用方判一次交进来)。 */
182
+ lane: {
183
+ readonly word: string;
184
+ readonly isolated: boolean;
185
+ };
100
186
  leg: "fresh" | "resume";
101
187
  nowMs: number;
102
188
  onMissing: (id: string) => void | never;
189
+ onRefused: (refusal: AttachmentBindRefusal) => void | never;
103
190
  }): Promise<{
104
191
  notice: string | undefined;
105
192
  }>;
106
- /** 半场③(env 建立时物化)的可测核。**fail-loud**:objective 已宣告文件在场,静默缺文件=模型按幻影
107
- * 文件行动,比 env 建立失败更糟 ⇒ 任一写失败即 throw。幂等:重建沙箱/resume 重写同路径同字节。 */
193
+ /** 会话绑定的附件按工作区收它们的样子:行 + 物化相对路径(与 objective 告知同一份推导 {@link materializedRelPaths})。
194
+ * 两只物化座(没有恢复点的车道 = 执行环境建立时 {@link materializeAttachmentsInto};隔离车道 = 恢复点的填充 `workspace-fill.ts`)读的是这一份。 */
195
+ export interface AttachmentPlacement {
196
+ readonly record: TaskAttachmentRecord;
197
+ readonly relPath: string;
198
+ }
199
+ export declare function attachmentPlacementsOf(store: TaskAttachmentStore, sessionId: string): Promise<AttachmentPlacement[]>;
200
+ /** 一只要放进工作区的附件的字节;读不到 ⇒ 抛(fail-loud:objective 已宣告它在场)。 */
201
+ export declare function attachmentBytesOf(store: TaskAttachmentStore, rec: TaskAttachmentRecord): Promise<Uint8Array>;
202
+ /** 半场③(没有恢复点的车道:env 建立时物化)的可测核。**fail-loud**:objective 已宣告文件在场,静默缺文件=模型按幻影
203
+ * 文件行动,比 env 建立失败更糟 ⇒ 任一写失败即 throw。幂等:重建 env / resume 重写同路径同字节。 */
108
204
  export declare function materializeAttachmentsInto(env: {
109
205
  createDir(path: string, options?: {
110
206
  recursive?: boolean;
@@ -129,10 +225,10 @@ export declare function ensureTiDBTaskAttachmentSchema(pool: Pool): Promise<void
129
225
  /** [ref]:本 store 的索引**声明**(两方言共用一份)。PG 侧由下面的 `ensurePgTaskAttachmentSchema` 应用,MySQL 侧由 `tidb-pool.ts` 的中央 `ensureSchema` 应用(那里内联 `KEY` 已在 `CREATE TABLE` 里 ⇒ 新建库探到即零 DDL,存量库缺谁补谁)。加索引以外的 schema 变更仍归运维,见 `plugins/ensure-index.ts` 头注。 */
130
226
  export declare const TASK_ATTACHMENT_INDEXES: readonly IndexSpec[];
131
227
  export declare function ensurePgTaskAttachmentSchema(q: PgQueryFn): Promise<void>;
132
- /** bytes → 4 MiB 块序列(共享切块器,两方言 + 完整性校验共用)。 */
133
228
  export declare class TiDBTaskAttachmentStore implements TaskAttachmentStore {
134
229
  private readonly pool;
135
230
  private readonly bytes;
231
+ private readonly db;
136
232
  constructor(pool: Pool, bytes: AttachmentBytesStore);
137
233
  put(rec: TaskAttachmentRecord & {
138
234
  content: Uint8Array;
@@ -143,7 +239,7 @@ export declare class TiDBTaskAttachmentStore implements TaskAttachmentStore {
143
239
  * 失败只吞不抛——E21 purge 不被对象存储抖动卡死(blob-backend 异步删裁定同旨);无 meta 的对象
144
240
  * 经 API 不可达,彻底清收敛记 follow-up。 */
145
241
  private deleteBytesIfUnreferenced;
146
- bind(owner: string, ids: string[], sessionId: string, boundAtMs: number): Promise<number>;
242
+ bind(owner: string, ids: readonly string[], sessionId: string, boundAtMs: number): Promise<AttachmentStoreBindOutcome>;
147
243
  listBySession(sessionId: string): Promise<TaskAttachmentRecord[]>;
148
244
  delete(owner: string, id: string): Promise<boolean>;
149
245
  deleteBySession(sessionId: string): Promise<number>;
@@ -160,16 +256,18 @@ export declare class TiDBTaskAttachmentStore implements TaskAttachmentStore {
160
256
  private filterStillOrphan;
161
257
  }
162
258
  export declare class PgTaskAttachmentStore implements TaskAttachmentStore {
163
- private readonly q;
164
259
  private readonly bytes;
165
- constructor(q: PgQueryFn, bytes: AttachmentBytesStore);
260
+ private readonly db;
261
+ private readonly q;
262
+ /** 收池(不是裸 `PgQueryFn`):绑定的种子腿要一只事务({@link bindOnSql}),事务要一条借出的连接。 */
263
+ constructor(pool: PgPool, bytes: AttachmentBytesStore);
166
264
  put(rec: TaskAttachmentRecord & {
167
265
  content: Uint8Array;
168
266
  }): Promise<void>;
169
267
  get(owner: string, id: string): Promise<TaskAttachmentRecord | null>;
170
268
  getContent(owner: string, id: string): Promise<Uint8Array | null>;
171
269
  private deleteBytesIfUnreferenced;
172
- bind(owner: string, ids: string[], sessionId: string, boundAtMs: number): Promise<number>;
270
+ bind(owner: string, ids: readonly string[], sessionId: string, boundAtMs: number): Promise<AttachmentStoreBindOutcome>;
173
271
  listBySession(sessionId: string): Promise<TaskAttachmentRecord[]>;
174
272
  delete(owner: string, id: string): Promise<boolean>;
175
273
  deleteBySession(sessionId: string): Promise<number>;