@sema-agent/server 7.137.0 → 7.139.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 (328) hide show
  1. package/MIGRATION.md +600 -142
  2. package/README.md +7 -3
  3. package/README.zh-CN.md +6 -3
  4. package/USAGE.md +132 -41
  5. package/deploy/sema-up/README.md +1 -1
  6. package/deploy/sema-up/lib/lanes.json +3 -2
  7. package/dist/adoption/wire.d.ts +3 -3
  8. package/dist/approval-card.d.ts +23 -16
  9. package/dist/approval-card.js +1 -0
  10. package/dist/audit.js +1 -0
  11. package/dist/background-agent-name.d.ts +5 -0
  12. package/dist/background-agent-name.js +5 -0
  13. package/dist/bench/s1/arms.d.ts +1 -1
  14. package/dist/bench/s1/arms.js +1 -3
  15. package/dist/bench/s1/run-firm.js +2 -2
  16. package/dist/boot/budget-tracing.d.ts +1 -12
  17. package/dist/boot/deployment-mods.d.ts +46 -16
  18. package/dist/boot/deployment-mods.js +82 -6
  19. package/dist/boot/device-lane.d.ts +8 -3
  20. package/dist/boot/device-lane.js +7 -2
  21. package/dist/boot/execution-env.d.ts +4 -2
  22. package/dist/boot/execution-env.js +16 -24
  23. package/dist/boot/execution-lane.d.ts +1 -1
  24. package/dist/boot/governance-seams.d.ts +12 -18
  25. package/dist/boot/governance-seams.js +10 -24
  26. package/dist/boot/leader.d.ts +3 -0
  27. package/dist/boot/leader.js +1 -0
  28. package/dist/boot/memory-consolidation.js +2 -2
  29. package/dist/boot/org-delivery.js +15 -2
  30. package/dist/boot/resolve-spec.d.ts +4 -1
  31. package/dist/boot/resolve-spec.js +29 -19
  32. package/dist/boot/retention-lane.d.ts +14 -8
  33. package/dist/boot/retention-lane.js +2 -8
  34. package/dist/boot/runner-deps.d.ts +10 -4
  35. package/dist/boot/runner-deps.js +2 -0
  36. package/dist/boot/session-faces.d.ts +0 -3
  37. package/dist/boot/session-faces.js +15 -8
  38. package/dist/boot/stage-02-web-admin-pull.js +2 -2
  39. package/dist/boot/stage-04-stores.d.ts +1 -12
  40. package/dist/boot/stage-05-execution-env.d.ts +3 -12
  41. package/dist/boot/stage-05-execution-env.js +8 -5
  42. package/dist/boot/stage-06-runners.d.ts +3 -12
  43. package/dist/boot/stage-06-runners.js +5 -3
  44. package/dist/boot/stage-07-capability-layer.d.ts +3 -12
  45. package/dist/boot/stage-07-capability-layer.js +2 -2
  46. package/dist/boot/stage-08-reapers.d.ts +3 -12
  47. package/dist/boot/stage-09-leader.d.ts +3 -12
  48. package/dist/boot/stage-09-leader.js +2 -3
  49. package/dist/boot/stage-10-http-server.d.ts +3 -12
  50. package/dist/boot/stage-10-http-server.js +5 -3
  51. package/dist/boot/stores.js +4 -4
  52. package/dist/boot/web-admin.d.ts +5 -2
  53. package/dist/boot/web-admin.js +5 -5
  54. package/dist/brain.js +5 -5
  55. package/dist/budget.d.ts +17 -15
  56. package/dist/budget.js +2 -2
  57. package/dist/capabilities/mcp-probe.d.ts +36 -11
  58. package/dist/capabilities/mcp-probe.js +44 -5
  59. package/dist/capabilities/scenarios.d.ts +1 -1
  60. package/dist/capability-facts.d.ts +21 -7
  61. package/dist/capability-facts.js +13 -3
  62. package/dist/checkpoint-identity.d.ts +13 -6
  63. package/dist/config-catalog.js +14 -8
  64. package/dist/config-invariants.d.ts +23 -3
  65. package/dist/config-invariants.js +42 -0
  66. package/dist/config-provider.d.ts +19 -24
  67. package/dist/config-provider.js +27 -12
  68. package/dist/config-types.d.ts +25 -12
  69. package/dist/config.d.ts +33 -0
  70. package/dist/config.js +52 -20
  71. package/dist/delegated-session-owner.d.ts +2 -2
  72. package/dist/deployment-shape.d.ts +7 -7
  73. package/dist/device-enrollment.d.ts +41 -2
  74. package/dist/device-enrollment.js +27 -12
  75. package/dist/device-store.d.ts +58 -85
  76. package/dist/device-store.js +19 -49
  77. package/dist/device-ws-hub.js +2 -1
  78. package/dist/env-name-allowlist-knobs.d.ts +5 -4
  79. package/dist/exec-cut.d.ts +60 -0
  80. package/dist/exec-cut.js +36 -0
  81. package/dist/execution-lane-caps.d.ts +19 -0
  82. package/dist/execution-lane-caps.js +10 -0
  83. package/dist/execution-placement.d.ts +259 -0
  84. package/dist/execution-placement.js +199 -0
  85. package/dist/field-conflict.d.ts +3 -2
  86. package/dist/field-conflict.js +1 -0
  87. package/dist/gate-settlement.d.ts +71 -0
  88. package/dist/gate-settlement.js +40 -0
  89. package/dist/hooks/classic-stations.d.ts +1 -0
  90. package/dist/hooks/classic-stations.js +1 -0
  91. package/dist/hooks/command-spawn.d.ts +2 -2
  92. package/dist/hooks/hook-runner.js +14 -14
  93. package/dist/hosted-posture.d.ts +2 -2
  94. package/dist/http/admission.js +10 -19
  95. package/dist/http/checkpoint-ctx.d.ts +21 -12
  96. package/dist/http/checkpoint-ctx.js +4 -2
  97. package/dist/http/claimed-run.d.ts +1 -1
  98. package/dist/http/dispatch.d.ts +4 -0
  99. package/dist/http/dispatch.js +3 -0
  100. package/dist/http/durable-run-leg.js +12 -6
  101. package/dist/http/execution-context.d.ts +218 -0
  102. package/dist/http/execution-context.js +330 -0
  103. package/dist/http/memory-compliance-failure-reply.js +3 -0
  104. package/dist/http/resume-legs.d.ts +5 -11
  105. package/dist/http/resume-legs.js +179 -104
  106. package/dist/http/route-ctx.d.ts +44 -59
  107. package/dist/http/route-ctx.js +2 -5
  108. package/dist/http/routes/a2a-serve.js +10 -4
  109. package/dist/http/routes/agents-roster.d.ts +1 -1
  110. package/dist/http/routes/agents-roster.js +5 -1
  111. package/dist/http/routes/approvals-assistant.js +6 -6
  112. package/dist/http/routes/background.d.ts +29 -1
  113. package/dist/http/routes/background.js +29 -2
  114. package/dist/http/routes/capabilities.js +12 -10
  115. package/dist/http/routes/devices.d.ts +13 -19
  116. package/dist/http/routes/devices.js +21 -26
  117. package/dist/http/routes/mcp-reconnect.d.ts +3 -2
  118. package/dist/http/routes/mcp-reconnect.js +7 -5
  119. package/dist/http/routes/memory-consolidation.d.ts +1 -1
  120. package/dist/http/routes/memory-origin.d.ts +1 -1
  121. package/dist/http/routes/rules.js +3 -3
  122. package/dist/http/routes/runs.js +11 -7
  123. package/dist/http/routes/session-sync.js +110 -15
  124. package/dist/http/routes/sessions.d.ts +6 -0
  125. package/dist/http/routes/sessions.js +119 -13
  126. package/dist/http/routes/side-query.d.ts +12 -4
  127. package/dist/http/routes/side-query.js +6 -8
  128. package/dist/http/routes/tasks.js +800 -782
  129. package/dist/http/server.d.ts +61 -39
  130. package/dist/http/server.js +11 -0
  131. package/dist/http/session-inherit-env.d.ts +3 -4
  132. package/dist/http/session-inherit-env.js +1 -1
  133. package/dist/http/wire-types.d.ts +22 -18
  134. package/dist/identity-source.d.ts +1 -1
  135. package/dist/index.d.ts +3 -0
  136. package/dist/index.js +1 -0
  137. package/dist/leader/wire.d.ts +7 -0
  138. package/dist/leader/wire.js +5 -4
  139. package/dist/mcp-assertion-header.d.ts +51 -40
  140. package/dist/mcp-assertion-header.js +10 -11
  141. package/dist/mcp-authorization-face.d.ts +27 -0
  142. package/dist/mcp-authorization-face.js +15 -0
  143. package/dist/memory-operator-faces.d.ts +13 -4
  144. package/dist/memory-operator-faces.js +1 -17
  145. package/dist/memory-sync-client.d.ts +53 -14
  146. package/dist/memory-sync-client.js +187 -92
  147. package/dist/memory-sync.d.ts +1 -1
  148. package/dist/memory-sync.js +3 -2
  149. package/dist/model-route-gap.d.ts +6 -22
  150. package/dist/model-route-gap.js +7 -5
  151. package/dist/mods-declaration.d.ts +110 -0
  152. package/dist/mods-declaration.js +109 -0
  153. package/dist/observability/cost-taxonomy.d.ts +2 -2
  154. package/dist/observability/err-text.d.ts +5 -0
  155. package/dist/observability/err-text.js +9 -0
  156. package/dist/observability/fail-open.d.ts +17 -1
  157. package/dist/observability/fail-open.js +17 -1
  158. package/dist/observability/run-terminal.js +0 -1
  159. package/dist/observability/secret-env-scrub.d.ts +1 -1
  160. package/dist/observability/secret-env-scrub.js +1 -1
  161. package/dist/orchestration/workflow-notify-journal.js +4 -4
  162. package/dist/org-memory-admission.d.ts +4 -3
  163. package/dist/org-memory-admission.js +6 -6
  164. package/dist/parked-decide.d.ts +49 -11
  165. package/dist/parked-decide.js +25 -26
  166. package/dist/parked-revive-surface.d.ts +12 -15
  167. package/dist/parked-revive-surface.js +3 -6
  168. package/dist/peer-seat.d.ts +156 -0
  169. package/dist/peer-seat.js +273 -0
  170. package/dist/permission-rule-vocab.d.ts +7 -7
  171. package/dist/permission-rule-vocab.js +3 -3
  172. package/dist/plain-string-record.d.ts +38 -0
  173. package/dist/plain-string-record.js +38 -0
  174. package/dist/plugins/approval-ask-store-file.d.ts +7 -0
  175. package/dist/plugins/approval-ask-store-file.js +11 -0
  176. package/dist/plugins/approval-ask-store-memory.d.ts +6 -0
  177. package/dist/plugins/approval-ask-store-memory.js +12 -0
  178. package/dist/plugins/approval-ask-store-sql.d.ts +22 -5
  179. package/dist/plugins/approval-ask-store-sql.js +30 -12
  180. package/dist/plugins/background-agent-store-sql.js +6 -3
  181. package/dist/plugins/caching-session-store.d.ts +11 -2
  182. package/dist/plugins/caching-session-store.js +15 -3
  183. package/dist/plugins/checkpoint-store-sql.d.ts +21 -25
  184. package/dist/plugins/checkpoint-store-sql.js +13 -15
  185. package/dist/plugins/device-store-sql.d.ts +25 -51
  186. package/dist/plugins/device-store-sql.js +49 -103
  187. package/dist/plugins/execution-placement-sql.d.ts +151 -0
  188. package/dist/plugins/execution-placement-sql.js +223 -0
  189. package/dist/plugins/file-history-store-sql.d.ts +7 -6
  190. package/dist/plugins/file-history-store-sql.js +8 -7
  191. package/dist/plugins/file-run-store.d.ts +6 -1
  192. package/dist/plugins/file-run-store.js +4 -0
  193. package/dist/plugins/fork-routing-session-store.d.ts +6 -0
  194. package/dist/plugins/fork-routing-session-store.js +16 -0
  195. package/dist/plugins/k8s-pod-reaper.d.ts +2 -8
  196. package/dist/plugins/k8s-pod-reaper.js +3 -3
  197. package/dist/plugins/local-checkpoint-store.d.ts +1 -11
  198. package/dist/plugins/local-checkpoint-store.js +0 -1
  199. package/dist/plugins/local-session-store.d.ts +183 -45
  200. package/dist/plugins/local-session-store.js +412 -105
  201. package/dist/plugins/mailbox-store-sql.d.ts +11 -3
  202. package/dist/plugins/mailbox-store-sql.js +103 -15
  203. package/dist/plugins/memory-key-guards.d.ts +4 -0
  204. package/dist/plugins/memory-key-guards.js +1 -0
  205. package/dist/plugins/memory-run-store.d.ts +1 -1
  206. package/dist/plugins/memory-sync-store-pg.d.ts +16 -5
  207. package/dist/plugins/memory-sync-store-tidb.d.ts +3 -3
  208. package/dist/plugins/one-time-migrations.d.ts +117 -6
  209. package/dist/plugins/one-time-migrations.js +383 -14
  210. package/dist/plugins/outcome-ledger-sql.d.ts +2 -2
  211. package/dist/plugins/permission-rule-store-sql.d.ts +13 -15
  212. package/dist/plugins/permission-rule-store-sql.js +34 -29
  213. package/dist/plugins/pg-pool.d.ts +7 -3
  214. package/dist/plugins/pg-session-storage.d.ts +41 -22
  215. package/dist/plugins/pg-session-storage.js +136 -55
  216. package/dist/plugins/remote-env-adb.d.ts +22 -4
  217. package/dist/plugins/remote-env-adb.js +70 -62
  218. package/dist/plugins/remote-env-device.d.ts +32 -12
  219. package/dist/plugins/remote-env-device.js +18 -17
  220. package/dist/plugins/remote-env-e2b.d.ts +19 -3
  221. package/dist/plugins/remote-env-e2b.js +41 -42
  222. package/dist/plugins/remote-env-file-error.d.ts +2 -0
  223. package/dist/plugins/remote-env-file-error.js +1 -1
  224. package/dist/plugins/remote-env-host.d.ts +23 -8
  225. package/dist/plugins/remote-env-host.js +66 -70
  226. package/dist/plugins/remote-env-k8s.d.ts +13 -16
  227. package/dist/plugins/remote-env-k8s.js +61 -54
  228. package/dist/plugins/remote-env-local-docker.d.ts +30 -5
  229. package/dist/plugins/remote-env-local-docker.js +46 -59
  230. package/dist/plugins/remote-env-ssh.d.ts +18 -4
  231. package/dist/plugins/remote-env-ssh.js +72 -56
  232. package/dist/plugins/remote-shell.d.ts +100 -3
  233. package/dist/plugins/remote-shell.js +166 -2
  234. package/dist/plugins/retention-store-sql.d.ts +23 -8
  235. package/dist/plugins/retention-store-sql.js +13 -1
  236. package/dist/plugins/run-store-sql.d.ts +13 -3
  237. package/dist/plugins/run-store-sql.js +5 -0
  238. package/dist/plugins/schema-consistency.d.ts +15 -5
  239. package/dist/plugins/schema-consistency.js +14 -8
  240. package/dist/plugins/schema-drift-sentence.d.ts +27 -0
  241. package/dist/plugins/schema-drift-sentence.js +38 -0
  242. package/dist/plugins/session-fork.d.ts +63 -12
  243. package/dist/plugins/session-fork.js +38 -21
  244. package/dist/plugins/session-ownership.d.ts +7 -0
  245. package/dist/plugins/session-ownership.js +5 -0
  246. package/dist/plugins/session-row-sql.d.ts +22 -0
  247. package/dist/plugins/session-row-sql.js +81 -0
  248. package/dist/plugins/session-store.d.ts +1 -1
  249. package/dist/plugins/session-workspace-cell.d.ts +17 -0
  250. package/dist/plugins/session-workspace-cell.js +38 -0
  251. package/dist/plugins/store-backend.d.ts +1 -1
  252. package/dist/plugins/store-backend.js +26 -14
  253. package/dist/plugins/store-contracts.d.ts +25 -5
  254. package/dist/plugins/tidb-pool.d.ts +12 -7
  255. package/dist/plugins/tidb-pool.js +15 -3
  256. package/dist/plugins/tidb-session-store.d.ts +74 -28
  257. package/dist/plugins/tidb-session-store.js +132 -53
  258. package/dist/plugins/tool-result-store-sql.d.ts +16 -8
  259. package/dist/plugins/tool-result-store-sql.js +26 -17
  260. package/dist/plugins/worktree-isolation.d.ts +3 -2
  261. package/dist/plugins/worktree-isolation.js +1 -7
  262. package/dist/project-identity.d.ts +1 -14
  263. package/dist/project-identity.js +0 -4
  264. package/dist/project-memory.d.ts +9 -15
  265. package/dist/project-memory.js +5 -4
  266. package/dist/request-key-closure.js +1 -1
  267. package/dist/request-value-closure.d.ts +6 -7
  268. package/dist/request-value-closure.js +1 -3
  269. package/dist/rules-consent.js +6 -4
  270. package/dist/run-local.d.ts +6 -0
  271. package/dist/run-local.js +36 -26
  272. package/dist/runs.d.ts +9 -2
  273. package/dist/runs.js +9 -2
  274. package/dist/sandbox-git-plane.d.ts +2 -2
  275. package/dist/security.d.ts +38 -12
  276. package/dist/security.js +8 -2
  277. package/dist/server-secret-env.d.ts +15 -8
  278. package/dist/server-secret-env.js +4 -2
  279. package/dist/session-children-retire.d.ts +27 -9
  280. package/dist/session-children-retire.js +9 -5
  281. package/dist/session-incarnation.d.ts +19 -10
  282. package/dist/session-incarnation.js +5 -3
  283. package/dist/session-row.d.ts +114 -5
  284. package/dist/session-row.js +55 -5
  285. package/dist/session-titler.d.ts +2 -2
  286. package/dist/session-workspace.d.ts +130 -0
  287. package/dist/session-workspace.js +83 -0
  288. package/dist/spec-fields.d.ts +1 -1
  289. package/dist/standing-answer-grant.d.ts +2 -13
  290. package/dist/standing-answer-grant.js +0 -7
  291. package/dist/task-a2a.d.ts +44 -14
  292. package/dist/task-a2a.js +46 -20
  293. package/dist/task-cwd.d.ts +6 -1
  294. package/dist/task-mcp.d.ts +33 -20
  295. package/dist/task-mcp.js +46 -22
  296. package/dist/tool-approval.d.ts +38 -8
  297. package/dist/tool-approval.js +57 -13
  298. package/dist/trace/core-keyset-guard.d.ts +33 -18
  299. package/dist/trace/engine-notice-wire.d.ts +1 -1
  300. package/dist/trace/engine-notice-wire.js +2 -0
  301. package/dist/trace/exit-seat.d.ts +20 -1
  302. package/dist/trace/exit-seat.js +3 -0
  303. package/dist/trace/extension-call-face.d.ts +33 -0
  304. package/dist/trace/extension-call-face.js +39 -0
  305. package/dist/trace/project.d.ts +26 -7
  306. package/dist/trace/project.js +30 -3
  307. package/dist/trace/redact.js +42 -13
  308. package/dist/trace/self-minted-wire.d.ts +240 -12
  309. package/dist/trace/self-minted-wire.js +100 -4
  310. package/dist/trace/wire-projection-faces.d.ts +6 -6
  311. package/dist/trace/wire-projection-faces.js +6 -5
  312. package/dist/url-refusal.d.ts +1 -1
  313. package/dist/user-authored-text.d.ts +2 -2
  314. package/dist/user-authored-text.js +16 -1
  315. package/dist/web-admin-client/apply-effective.d.ts +17 -3
  316. package/dist/web-admin-client/apply-effective.js +20 -10
  317. package/dist/web-admin-client/center-request.d.ts +3 -2
  318. package/dist/web-admin-client/center-request.js +2 -3
  319. package/dist/web-admin-client/skills-mcp.d.ts +8 -4
  320. package/dist/web-admin-client/skills-mcp.js +32 -18
  321. package/dist/web-admin-client/types.d.ts +11 -0
  322. package/package.json +3 -3
  323. package/wire-changes/server@7.138.0.json +446 -0
  324. package/wire-changes/server@7.139.0.json +348 -0
  325. package/dist/http/device-placement.d.ts +0 -175
  326. package/dist/http/device-placement.js +0 -145
  327. package/dist/http/mcp-remote-text.d.ts +0 -5
  328. package/dist/http/mcp-remote-text.js +0 -7
@@ -1,29 +1,46 @@
1
- import { SessionError, StoredSession, settledForkOptions } from "@sema-agent/core";
1
+ import { SessionError, StoredSession, settledForkOptions, uuidv7 } from "@sema-agent/core";
2
+ import { createHash } from "node:crypto";
2
3
  export async function forkPointOf(storage) {
3
4
  return settledForkOptions(new StoredSession(storage));
4
5
  }
5
- export function forkCopyOf(rows, sourceLeafId, point) {
6
- if (point.entryId === undefined)
7
- return { rows: [...rows], leafId: sourceLeafId };
8
- if (point.position !== "at") {
9
- throw new SessionError("invalid_fork_target", `fork point ${point.entryId} comes with a position this store does not copy (${String(point.position)}) — only the engine's "at" point is read here`);
6
+ export const FORK_LOG_ROW_SHAPE = {
7
+ idOf: (row) => row.entry_id,
8
+ parentOf: (row) => row.parent_id,
9
+ };
10
+ export function forkKeyOf(sourceId, forkNonce) {
11
+ return `fork1:${createHash("sha256").update(sourceId).update("\u0000").update(forkNonce).digest("hex")}`;
12
+ }
13
+ export function classifyForkTarget(target, existing, forkOwner) {
14
+ if ((existing.owner ?? null) !== (forkOwner ?? null))
15
+ return { outcome: "occupied", sameOwner: false };
16
+ if (target.key !== undefined && existing.forkKey === target.key)
17
+ return { outcome: "replayed", id: target.id };
18
+ return { outcome: "occupied", sameOwner: true };
19
+ }
20
+ export class ForkTargetUnsettledError extends Error {
21
+ targetId;
22
+ code = "state.fork_target_unsettled";
23
+ constructor(targetId) {
24
+ super(`fork target ${targetId} collided with another writer twice and could not be read back either time — retry the fork`);
25
+ this.targetId = targetId;
26
+ this.name = "ForkTargetUnsettledError";
10
27
  }
11
- const byId = new Map();
12
- for (const r of rows)
13
- byId.set(r.entry_id, r);
14
- const onPath = new Set();
15
- let cursor = point.entryId;
16
- while (cursor !== null) {
17
- if (onPath.has(cursor)) {
18
- throw new SessionError("invalid_session", `Entry ${cursor} is its own ancestor (parent chain cycles at ${cursor}) — start a new session, or fork from an entry above the loop`);
19
- }
20
- const row = byId.get(cursor);
21
- if (row === undefined) {
22
- throw new SessionError(cursor === point.entryId ? "invalid_fork_target" : "invalid_session", `Entry ${cursor} not found`);
28
+ }
29
+ export async function forkByFreshId(forkTo, sourceId, owner) {
30
+ const id = uuidv7();
31
+ const out = await forkTo(sourceId, owner ?? null, { id });
32
+ if (out === null)
33
+ return null;
34
+ switch (out.outcome) {
35
+ case "created":
36
+ return out.id;
37
+ case "replayed":
38
+ case "occupied":
39
+ throw new SessionError("storage", `fork of session ${sourceId}: a freshly minted id ${id} collided with an existing session (${out.outcome}) — nothing was written`);
40
+ default: {
41
+ const unknownOutcome = out;
42
+ throw new Error(`forkByFreshId: no arm for ${JSON.stringify(unknownOutcome)}`);
23
43
  }
24
- onPath.add(cursor);
25
- cursor = row.parent_id;
26
44
  }
27
- return { rows: rows.filter((r) => onPath.has(r.entry_id)), leafId: point.entryId };
28
45
  }
29
46
  //# sourceMappingURL=session-fork.js.map
@@ -22,4 +22,11 @@ export declare function forkOwnerOf(requested: string | null | undefined, source
22
22
  * 「首写」由调用方判(已记下属主的会话上的调用照旧静默不改,不经这里)。判官 = {@link isSessionOwnerValue}(core 7.56.0 [ref] `principalNameOf`)。
23
23
  */
24
24
  export declare function assertFirstOwnerWritable(sessionId: string, owner: unknown): asserts owner is string | null;
25
+ /**
26
+ * 导入者的属主 —— 换装的写腿(公开 `importEntries` / `replaceEntries` 与暂存提交)把调用方给的属主重戳进会话行之前的唯一读法:
27
+ * 主体名或 `null`(匿名)⇒ 原样交回;别的一切(空串、空白串、带控制 / 分隔 / 非连接符格式字符的串、非串)⇒ `TypeError` 响亮拒 ——
28
+ * 与引擎导入口拒同一类值时同一类错、同一句开头(引擎那一判只作用于交给它的属主,而换装交给引擎的是存档自己的戳,所以这一判在本仓)。
29
+ * 调用方在读写任何东西之前判,拒了就一个字节没写。判官 = {@link isSessionOwnerValue}(core `principalNameOf`),不在本仓另写规则。
30
+ */
31
+ export declare function importerOwnerOf(owner: unknown): string | null;
25
32
  //# sourceMappingURL=session-ownership.d.ts.map
@@ -22,4 +22,9 @@ export function assertFirstOwnerWritable(sessionId, owner) {
22
22
  throw new SessionError("storage", `register(${sessionId}): a first ownership write must carry a principal name or null (anonymous), got ${ownerValueFault(owner)} — ` +
23
23
  "a value that names nobody would leave the session claimable by anyone or ownable by no one; refusing, nothing written");
24
24
  }
25
+ export function importerOwnerOf(owner) {
26
+ if (isSessionOwnerValue(owner))
27
+ return owner;
28
+ throw new TypeError(`session owner must be a principal name or null (anonymous), got ${ownerValueFault(owner)} — an import stamps it as the session's owner; refusing, nothing written`);
29
+ }
25
30
  //# sourceMappingURL=session-ownership.js.map
@@ -0,0 +1,22 @@
1
+ /**
2
+ * **会话行格写原语的 SQL 半** —— 两孪生(`tidb-session-store.ts` / `pg-session-storage.ts`)同一只实现,方言只在语句文本里。规则在 `src/session-row.ts`
3
+ * 头注「写原语」;判序只在那里的判官 `judgeSessionRowWrite`,本模块只说事务与语句。
4
+ *
5
+ * ## 一只事务(经 `SqlDriver.connect()`)
6
+ * ① `SELECT … FROM session_meta WHERE session_id = ? FOR UPDATE` —— 判定读 = 锁读(当前读;连接初始化钉住读语义:TiDB 悲观、设不上拒启,
7
+ * `sql-driver.ts` 的 `@contract txn.read-semantics`);行期望带 `log` 时,同锁下读日志的 mark:长度 = 锁住的行上的 `leaf_seq`(每次追加与
8
+ * `leaf_id` 同句 +1,导入 / 换装 / fork 写成条目数),末条 = `session_event` 按 `seq` 倒序第一行(锁读)。追加者先锁这一行再插事件,所以持锁期间 mark 不动。
9
+ * ② 判官(行期望 → 写者身份 → 标签);不是 `write` ⇒ 回滚、一个字节不写。
10
+ * ③ `write` ⇒ 按 `session_id` 写(行已被本事务锁住)→ 提交。`updated_at` 不动(改名不是活动 —— 列表的新近序不变)。
11
+ * 应答行在**提交 / 回滚之后**由调用方的列表行读另读(新语句、新快照 —— 不在事务内用普通 `SELECT` 读:TiDB 悲观事务的普通读钉 `start_ts`,
12
+ * 等锁之后会读回判定之前的旧值),再交 `settleSessionRowWrite` 复核住客。
13
+ * 全局锁序:只锁根 `session_meta` 一行(读日志 mark 时再锁它的末条事件),不碰 `task_*`,不进放置写者「根 meta → `task_*`」锁序的后段。
14
+ */
15
+ import type { SqlDriver } from "./sql-driver.js";
16
+ import { type SessionRowAnswer, type SessionRowPatch, type SessionRowWrite, type SessionRowWriteOutcome } from "../session-row.js";
17
+ /**
18
+ * 写原语(头注):一只事务里锁根行 → 判官 → 写 / 不写;提交 / 回滚之后经 `readAnswer`(调用方店的列表行读 + 行上的住客)另读应答行。
19
+ * `db` = 店自己的池包成的 SqlDriver(连接经它的初始化);`readAnswer` 只在判官答 `write` / `changed` 时被调。
20
+ */
21
+ export declare function writeSessionRowCellsSql<Row>(db: Pick<SqlDriver, "dialect" | "connect">, sessionId: string, cells: SessionRowPatch, how: SessionRowWrite, readAnswer: (sessionId: string) => Promise<SessionRowAnswer<Row> | undefined>): Promise<SessionRowWriteOutcome<Row>>;
22
+ //# sourceMappingURL=session-row-sql.d.ts.map
@@ -0,0 +1,81 @@
1
+ import { incarnationFromCell } from "../session-incarnation.js";
2
+ import { judgeSessionRowWrite, settleSessionRowWrite, } from "../session-row.js";
3
+ function binder(dialect) {
4
+ const values = [];
5
+ return {
6
+ values,
7
+ p: (v) => {
8
+ values.push(v);
9
+ return dialect === "pg" ? `$${values.length}` : "?";
10
+ },
11
+ };
12
+ }
13
+ function lockedRowFrom(dialect, sessionId, row) {
14
+ return {
15
+ owner: row.owner === null || row.owner === undefined ? null : String(row.owner),
16
+ incarnation: incarnationFromCell(sessionId, row.incarnation, "absent"),
17
+ placed: row.placement_kind !== null && row.placement_kind !== undefined,
18
+ mirror: row.execution_role === "mirror",
19
+ cells: {
20
+ title: row.title === null || row.title === undefined ? null : String(row.title),
21
+ pinned: dialect === "pg" ? row.pinned === true : Number(row.pinned) === 1,
22
+ },
23
+ };
24
+ }
25
+ async function lockedLogMark(conn, dialect, sessionId, leafSeq) {
26
+ const b = binder(dialect);
27
+ const last = await conn.query(`SELECT entry_id FROM session_event WHERE session_id = ${b.p(sessionId)} ORDER BY seq DESC LIMIT 1 FOR UPDATE`, b.values);
28
+ const id = last.rows[0]?.entry_id;
29
+ return { length: Number(leafSeq), lastEntryId: id === undefined || id === null ? null : String(id) };
30
+ }
31
+ export async function writeSessionRowCellsSql(db, sessionId, cells, how, readAnswer) {
32
+ const dialect = db.dialect;
33
+ let verdict;
34
+ const conn = await db.connect();
35
+ try {
36
+ await conn.begin();
37
+ const read = binder(dialect);
38
+ const locked = (await conn.query(`SELECT owner, incarnation, placement_kind, execution_role, title, pinned, leaf_seq FROM session_meta WHERE session_id = ${read.p(sessionId)} FOR UPDATE`, read.values)).rows[0];
39
+ const mark = how.row.log === undefined || locked === undefined ? undefined : await lockedLogMark(conn, dialect, sessionId, locked.leaf_seq);
40
+ verdict = judgeSessionRowWrite(sessionId, how, locked === undefined ? undefined : lockedRowFrom(dialect, sessionId, locked), () => {
41
+ if (mark === undefined)
42
+ throw new Error(`writeSessionRowCellsSql: the judge asked session ${sessionId}'s log mark, but the expectation carries none`);
43
+ return mark;
44
+ });
45
+ if (verdict !== "write") {
46
+ await conn.rollback();
47
+ }
48
+ else {
49
+ const w = binder(dialect);
50
+ const sets = [];
51
+ if (cells.title !== undefined)
52
+ sets.push(`title = ${w.p(cells.title)}`);
53
+ if (cells.pinned !== undefined)
54
+ sets.push(`pinned = ${w.p(cells.pinned)}`);
55
+ if (sets.length > 0)
56
+ await conn.query(`UPDATE session_meta SET ${sets.join(", ")} WHERE session_id = ${w.p(sessionId)}`, w.values);
57
+ await conn.commit();
58
+ }
59
+ }
60
+ catch (err) {
61
+ await conn.rollback().catch(() => undefined);
62
+ throw err;
63
+ }
64
+ finally {
65
+ conn.release();
66
+ }
67
+ switch (verdict) {
68
+ case "absent":
69
+ return { kind: "absent" };
70
+ case "mirror":
71
+ return { kind: "mirror" };
72
+ case "write":
73
+ case "changed":
74
+ return settleSessionRowWrite(how, verdict, await readAnswer(sessionId));
75
+ default: {
76
+ const unknownVerdict = verdict;
77
+ throw new Error(`writeSessionRowCellsSql: no verdict ${JSON.stringify(unknownVerdict)}`);
78
+ }
79
+ }
80
+ }
81
+ //# sourceMappingURL=session-row-sql.js.map
@@ -46,7 +46,7 @@ export declare function ensureSessionDurable(hostStore: SessionStore, sessionId:
46
46
  * 恒否决 park——bg 子会话住 subRunner 私有 TTL 店、从不在 host 店,core 收 veto 即 expire checkpoint
47
47
  * (14ms~2s)+ settle failed,赎回窗口不存在):
48
48
  * 0. 属主([ref],`delegated-session-owner.ts`):按委派继承律判一次(唯一判据 `judgeDelegatedOwner`)—— 来源集 = 见证的根这一只(调用方交出的委派树根 + 调用方判断那一刻
49
- * 读到的根行住客:请求腿 = 本请求经鉴权解析的会话与受理时那一读,`/decide` 赎回腿 = 行上的 `rootSessionId` 与 claim 前后两读的那一位;core 的挂点签名只给子代 id,
49
+ * 读到的根行住客:请求腿 = 本请求经鉴权解析的会话与受理时那一读,`/decide` 赎回腿 = 行上的 `rootSessionId` 与写口放行的那一位(认领之前解析器那一读);core 的挂点签名只给子代 id,
50
50
  * 场景层把见证绑进闭包交下来)。此刻根行在而住客不是见证的那一位(删了、同 id 被重新登记)⇒ **拒**(`assertRootStillWitnessed`:不按新住客晋升 / 认领);
51
51
  * 缺席(行上没有根)或根行此刻不在 ⇒ 孤行哨兵。
52
52
  * 1. 快路:已在 host 店且有真内容 → 通过,**前提是那一行的属主按律定得下来**(`settleDelegatedOwner`:记着别的委派树 ⇒ 抛;记着这一棵 ⇒ 按它记录的根、父再判一次
@@ -0,0 +1,17 @@
1
+ import type { SessionWorkspace } from "../session-workspace.js";
2
+ import { type JsonColumnReader } from "./sql-json-column.js";
3
+ /** 列定义的唯一属主(两方言 DDL 内插它;存量库缺这一列 ⇒ 起服列册对账拒启,拒句里的 ALTER 由建表语句生成)。 */
4
+ export declare const SESSION_META_WORKSPACE_COLUMN: {
5
+ readonly name: "workspace";
6
+ readonly tidb: "workspace JSON NULL";
7
+ readonly pg: "workspace JSONB";
8
+ };
9
+ /** 这一格的形读者(三只店与帧投影同一只;写前也过它 —— 写得进的就是读得出的)。 */
10
+ export declare const readSessionWorkspaceCell: JsonColumnReader<SessionWorkspace>;
11
+ /** 这一格的失败留痕 tag(F 类;登记行在 `observability/fail-open.ts`)。记录座(写 / 帧)与本文件(读)同一只。 */
12
+ export declare const SESSION_WORKSPACE_DEGRADED: "server.session-workspace.record-degraded";
13
+ /** 值 → 这一格的文本。`encode` 是该承载的编码器(MySQL 协议与 local:`JSON.stringify`;PG:`pgSafeJsonStringify`)。写前过形读者,不合形响亮抛、什么都不写。 */
14
+ export declare function encodeSessionWorkspaceCell(workspace: SessionWorkspace, encode: (value: unknown) => string, sessionId: string): string;
15
+ /** 一格(已按文本投影;SQL NULL / local 缺键 ⇒ 调用方传 `null`)→ 值。坏体 ⇒ 读作没有记录并计一次(见文件头);读腿漏投影 ⇒ 抛。 */
16
+ export declare function decodeSessionWorkspaceCell(cell: unknown, sessionId: string): SessionWorkspace | undefined;
17
+ //# sourceMappingURL=session-workspace-cell.d.ts.map
@@ -0,0 +1,38 @@
1
+ import { PROJECT_ID_REGEX } from "@sema-agent/core";
2
+ import { z } from "zod";
3
+ import { recordFailOpen } from "../observability/fail-open.js";
4
+ import { assertJsonColumnWritable, createZodColumnReader, decodeJsonColumn, isJsonColumnBodyProblem } from "./sql-json-column.js";
5
+ export const SESSION_META_WORKSPACE_COLUMN = {
6
+ name: "workspace",
7
+ tidb: "workspace JSON NULL",
8
+ pg: "workspace JSONB",
9
+ };
10
+ const SESSION_WORKSPACE_SHAPE = z.object({
11
+ cwd: z.string().min(1),
12
+ root: z.string().min(1),
13
+ entered: z.object({ dir: z.string().min(1), byPath: z.literal(true).optional() }).optional(),
14
+ project: z.object({ id: z.string().regex(PROJECT_ID_REGEX), source: z.enum(["marker", "declared"]) }).optional(),
15
+ remote: z.boolean(),
16
+ });
17
+ export const readSessionWorkspaceCell = createZodColumnReader(SESSION_WORKSPACE_SHAPE);
18
+ export const SESSION_WORKSPACE_DEGRADED = "server.session-workspace.record-degraded";
19
+ const where = (sessionId) => `session_meta.workspace for session ${sessionId}`;
20
+ export function encodeSessionWorkspaceCell(workspace, encode, sessionId) {
21
+ const text = encode(workspace);
22
+ assertJsonColumnWritable(text, readSessionWorkspaceCell, where(sessionId));
23
+ return text;
24
+ }
25
+ export function decodeSessionWorkspaceCell(cell, sessionId) {
26
+ if (cell === null)
27
+ return undefined;
28
+ try {
29
+ return decodeJsonColumn(cell, readSessionWorkspaceCell, where(sessionId));
30
+ }
31
+ catch (e) {
32
+ if (!isJsonColumnBodyProblem(e))
33
+ throw e;
34
+ recordFailOpen(SESSION_WORKSPACE_DEGRADED, `op=read ${e.where} problem=${e.problem}`);
35
+ return undefined;
36
+ }
37
+ }
38
+ //# sourceMappingURL=session-workspace-cell.js.map
@@ -350,7 +350,7 @@ export interface StoreBackend {
350
350
  */
351
351
  leaderRun?(): LeaderRunStore;
352
352
  /**
353
- * device lane 的四表店(device-executor-lane-v2 §6,车A-2 交店 / 车A-4 接线)。**SQL 后端专有**,
353
+ * device lane 的三表店(device-executor-lane-v2 §6,车A-2 交店 / 车A-4 接线;绑定写在根会话行的执行格上,与会话店同一只池)。**SQL 后端专有**,
354
354
  * `local` 刻意省略 —— 与 {@link leaderRun} 逐字同一条理由:config 层已经把「`REMOTE_EXEC=device` 且
355
355
  * 无外部 SQL 店」整个组合拒启了(`device-durable-store` 不变量),所以 local 车道上根本不存在一个需要
356
356
  * 这四张表的 device worker。消费点按 `backend?.device?.()` 取,缺席 ⇒ `boot/device-lane.ts` 的
@@ -13,7 +13,8 @@ import { LocalWorkflowParkDecisionStore } from "../orchestration/workflow-park-d
13
13
  import { StoreBootRefusalError } from "./store-contracts.js";
14
14
  import { buildStoreOpenRefusalTail } from "./store-open-refusal.js";
15
15
  import { acquireLocalBackendWithWait } from "./store-lock-wait.js";
16
- import { NO_SQL_MIGRATIONS, runSqlOneTimeMigrations } from "./one-time-migrations.js";
16
+ import { NO_SQL_MIGRATIONS, assertExecutionPlacementSwitchAdmissibleOnPool, backfillLocalAskExecutionLanes, backfillLocalExecutionPlacements, runSqlOneTimeMigrations } from "./one-time-migrations.js";
17
+ import { deployedExecutionLanes } from "../execution-lane-caps.js";
17
18
  import { assertRetiredSchemaAbsentOnPool } from "./schema-consistency.js";
18
19
  import { LocalSessionStore, createLocalSessionRepo } from "./local-session-store.js";
19
20
  import { LocalCheckpointStore } from "./local-checkpoint-store.js";
@@ -53,7 +54,7 @@ import { TiDBSessionStore } from "./tidb-session-store.js";
53
54
  import { PgSessionStore } from "./pg-session-storage.js";
54
55
  import { TiDBSharedMemoryStore, PgSharedMemoryStore } from "./shared-memory-store-sql.js";
55
56
  import { TiDBAdoptionLogStore, PgAdoptionLogStore } from "./adoption-log-sql.js";
56
- import { TiDBRetentionStore, PgRetentionStore } from "./retention-store-sql.js";
57
+ import { TiDBRetentionStore, PgRetentionStore, sqlStoreRetention } from "./retention-store-sql.js";
57
58
  import { TiDBRetentionLaneStore, PgRetentionLaneStore } from "./retention-lane-store-sql.js";
58
59
  import { createSqlPermissionRuleStores } from "./permission-rule-store-sql.js";
59
60
  import { createFilePermissionRuleStores } from "./permission-rule-store-file.js";
@@ -139,7 +140,7 @@ class TiDBBackend {
139
140
  sessionCaptureRecords() { return () => (this.captureRecordsInst ??= new TiDBSessionCaptureRecordStore(this.pool)); }
140
141
  captureRecordsInst;
141
142
  legacySnapshotBlobSweep() { return legacySweepImpl(this.config, "tidb", this.pool); }
142
- runOneTimeMigrations() { return runSqlOneTimeMigrations("tidb", this.pool); }
143
+ runOneTimeMigrations() { return runSqlOneTimeMigrations("tidb", this.pool, { lanes: deployedExecutionLanes(this.config) }); }
143
144
  workflowJournal() { return new TiDBWorkflowJournalStore(this.pool); }
144
145
  outcomeSink() { return new TiDBOutcomeLedger(this.pool); }
145
146
  workflowRun(onWarn) { return new TiDBWorkflowRunStore(this.pool, onWarn); }
@@ -149,9 +150,9 @@ class TiDBBackend {
149
150
  workflowParkDecisions() { return (this.parkDecisionsInst ??= new SqlWorkflowParkDecisionStore(this.run())); }
150
151
  parkDecisionsInst;
151
152
  sharedMemoryStore(authorizer) { return new TiDBSharedMemoryStore(this.pool, authorizer); }
152
- checkpoint(logger) { return new TiDBCheckpointStore(this.pool, logger); }
153
+ checkpoint(logger) { return new TiDBCheckpointStore(this.pool, logger, { retention: sqlStoreRetention(this.config.retentionSweep) }); }
153
154
  checkpointReadView() { return this.checkpoint(); }
154
- toolResult() { return new TiDBToolResultStore(this.pool); }
155
+ toolResult() { return new TiDBToolResultStore(this.pool, { retention: sqlStoreRetention(this.config.retentionSweep) }); }
155
156
  imageIndex() { return new TiDBImageIndex(this.pool); }
156
157
  imageBake() { return new TiDBImageBake(this.pool); }
157
158
  leaderRun() { return new TiDBLeaderRunStore(this.pool); }
@@ -161,7 +162,7 @@ class TiDBBackend {
161
162
  breaker(onWriteFail) { return new TiDBBreakerState(this.pool, onWriteFail); }
162
163
  costQuota(limitMicroUsd, windowMs, onDegraded) { return new TiDBCostQuota(this.pool, limitMicroUsd, windowMs, undefined, onDegraded, this.config.dbQueryTimeoutMs); }
163
164
  rateLimiter(limit, onDegraded) { return new TiDBRateLimiter(this.pool, limit, undefined, undefined, onDegraded, this.config.dbQueryTimeoutMs); }
164
- session() { return new TiDBSessionStore(this.pool, { log: createLogger(this.config.logLevel) }); }
165
+ session() { return new TiDBSessionStore(this.pool, { log: createLogger(this.config.logLevel), retention: sqlStoreRetention(this.config.retentionSweep) }); }
165
166
  adoptionLog() { return new TiDBAdoptionLogStore(this.pool); }
166
167
  mysqlPool() { return this.pool; }
167
168
  pgPool() { return undefined; }
@@ -193,7 +194,7 @@ class PgBackend {
193
194
  sessionCaptureRecords() { return () => (this.captureRecordsInst ??= new PgSessionCaptureRecordStore(this.pool)); }
194
195
  captureRecordsInst;
195
196
  legacySnapshotBlobSweep() { return legacySweepImpl(this.config, "pg", this.pool); }
196
- runOneTimeMigrations() { return runSqlOneTimeMigrations("pg", this.pool); }
197
+ runOneTimeMigrations() { return runSqlOneTimeMigrations("pg", this.pool, { lanes: deployedExecutionLanes(this.config) }); }
197
198
  workflowJournal() { return new PgWorkflowJournalStore(this.pool); }
198
199
  outcomeSink() { return new PgOutcomeLedger(this.pool); }
199
200
  workflowRun(onWarn) { return new PgWorkflowRunStore(this.pool, onWarn); }
@@ -203,9 +204,9 @@ class PgBackend {
203
204
  workflowParkDecisions() { return (this.parkDecisionsInst ??= new SqlWorkflowParkDecisionStore(this.run())); }
204
205
  parkDecisionsInst;
205
206
  sharedMemoryStore(authorizer) { return new PgSharedMemoryStore(this.pool, authorizer); }
206
- checkpoint(logger) { return new PgCheckpointStore(this.pool, logger); }
207
+ checkpoint(logger) { return new PgCheckpointStore(this.pool, logger, { retention: sqlStoreRetention(this.config.retentionSweep) }); }
207
208
  checkpointReadView() { return this.checkpoint(); }
208
- toolResult() { return new PgToolResultStore(this.pool); }
209
+ toolResult() { return new PgToolResultStore(this.pool, { retention: sqlStoreRetention(this.config.retentionSweep) }); }
209
210
  imageIndex() { return new PgImageIndex(this.pool); }
210
211
  imageBake() { return new PgImageBake(this.pool); }
211
212
  leaderRun() { return new PgLeaderRunStore(this.pool); }
@@ -215,7 +216,7 @@ class PgBackend {
215
216
  breaker(onWriteFail) { return new PgBreakerState(this.pool, onWriteFail); }
216
217
  costQuota(limitMicroUsd, windowMs, onDegraded) { return new PgCostQuota(this.pool, limitMicroUsd, windowMs, undefined, onDegraded, this.config.dbQueryTimeoutMs); }
217
218
  rateLimiter(limit, onDegraded) { return new PgRateLimiter(this.pool, limit, undefined, undefined, onDegraded, this.config.dbQueryTimeoutMs); }
218
- session() { return new PgSessionStore(this.pool, { log: createLogger(this.config.logLevel) }); }
219
+ session() { return new PgSessionStore(this.pool, { log: createLogger(this.config.logLevel), retention: sqlStoreRetention(this.config.retentionSweep) }); }
219
220
  adoptionLog() { return new PgAdoptionLogStore(this.pool); }
220
221
  mysqlPool() { return undefined; }
221
222
  pgPool() { return this.pool; }
@@ -239,7 +240,9 @@ class LocalBackend {
239
240
  onError;
240
241
  discloseWarn;
241
242
  parkDecisionLedgerRoot;
243
+ executionLanes;
242
244
  constructor(root, config, onWarn, onError) {
245
+ this.executionLanes = deployedExecutionLanes(config);
243
246
  this.onWarn = onWarn;
244
247
  this.discloseWarn = onWarn ?? ((msg, meta) => console.warn(msg, meta));
245
248
  this.onError = onError;
@@ -282,7 +285,15 @@ class LocalBackend {
282
285
  }
283
286
  async ensureSchema() { }
284
287
  async runOneTimeMigrations() {
285
- return { ...NO_SQL_MIGRATIONS, holderDeathCodeRowsBackfilled: this.runStore.migrateLegacyHolderDeathCodes() };
288
+ const holderDeathCodeRowsBackfilled = this.runStore.migrateLegacyHolderDeathCodes();
289
+ const placements = await backfillLocalExecutionPlacements(this.runStore.sessionIdsWithRuns(), this.sessionStore.executionPlacementBackfill, { lanes: this.executionLanes });
290
+ return {
291
+ ...NO_SQL_MIGRATIONS,
292
+ holderDeathCodeRowsBackfilled,
293
+ executionPlacementsBackfilled: placements.backfilled,
294
+ executionPlacementSessionsUnreadable: placements.unreadable,
295
+ askExecutionLanesBackfilled: backfillLocalAskExecutionLanes(this.approvalAskStore, { lanes: this.executionLanes }),
296
+ };
286
297
  }
287
298
  async close() {
288
299
  await this.sessionStore.dispose();
@@ -364,17 +375,18 @@ export function createStoreBackend(config, onWarn, onError) {
364
375
  if (!config.pg)
365
376
  return undefined;
366
377
  const pool = createPgPool(pgPoolOptions(config.pg, config.dbQueryTimeoutMs), onWarn);
367
- return withRetiredSchemaProbe(new PgBackend(pool, config), { dialect: "pg", pool });
378
+ return withRetiredSchemaProbe(new PgBackend(pool, config), { dialect: "pg", pool }, { lanes: deployedExecutionLanes(config) });
368
379
  }
369
380
  if (!config.tidb)
370
381
  return undefined;
371
382
  const pool = createTidbPool(config);
372
- return withRetiredSchemaProbe(new TiDBBackend(pool, config), { dialect: "tidb", pool });
383
+ return withRetiredSchemaProbe(new TiDBBackend(pool, config), { dialect: "tidb", pool }, { lanes: deployedExecutionLanes(config) });
373
384
  }
374
- function withRetiredSchemaProbe(inner, target) {
385
+ function withRetiredSchemaProbe(inner, target, executionPlacementSwitch) {
375
386
  const guarded = Object.create(inner);
376
387
  guarded.ensureSchema = async () => {
377
388
  await assertRetiredSchemaAbsentOnPool(target);
389
+ await assertExecutionPlacementSwitchAdmissibleOnPool(target.dialect, target.pool, executionPlacementSwitch);
378
390
  await inner.ensureSchema();
379
391
  };
380
392
  return guarded;
@@ -276,26 +276,36 @@ export interface NewRunMeta {
276
276
  idem?: RunIdemBinding | null;
277
277
  }
278
278
  /**
279
- * `RunStore.createRun` 的结局 —— 三形,一形一句话:
279
+ * `RunStore.createRun` 的结局 —— 四形,一形一句话:
280
280
  * · `{ ok: true }` —— 新行已建、会话 claim 已拿;
281
281
  * · `{ ok: false, existing }` —— 这次提交的一把**幂等钥匙**已经指向一条行:`idem_key`(`Idempotency-Key`)或
282
282
  * `task_id`(调用方自铸的 `body.taskId`)—— 两把钥匙是**同一条律**(资源行上的唯一键),先认 `idem_key` 再认 `task_id`。
283
283
  * 这是「原子赢或观察」的**观察**臂:行原样交回,没建新行、没动会话;回执由提交面的唯一回执函数判。
284
- * · `{ ok: false, activeTaskId }` —— 会话被**别的** run 占着(双发守卫),没建行。
285
- * 另两位在每一形上恒 `undefined`(读 `created.existing` / `created.activeTaskId` 不用先窄化,判别仍是穷举的)。
284
+ * · `{ ok: false, activeTaskId }` —— 会话被**别的** run 占着(双发守卫),没建行;
285
+ * · `{ ok: false, sessionAbsent: true }` —— 根会话行不在(SQL 孪生在认领事务里锁根行读到零行:受理之后会话被删),没建行、没认领。
286
+ * 进程内两孪生(File / Memory)的账本里没有会话行,不答这一形。
287
+ * 其余几位在每一形上恒 `undefined`(读 `created.existing` / `created.activeTaskId` / `created.sessionAbsent` 不用先窄化,判别仍是穷举的)。
286
288
  */
287
289
  export type CreateRunOutcome = {
288
290
  ok: true;
289
291
  existing?: undefined;
290
292
  activeTaskId?: undefined;
293
+ sessionAbsent?: undefined;
291
294
  } | {
292
295
  ok: false;
293
296
  existing: RunRecord;
294
297
  activeTaskId?: undefined;
298
+ sessionAbsent?: undefined;
295
299
  } | {
296
300
  ok: false;
297
301
  existing?: undefined;
298
302
  activeTaskId: string;
303
+ sessionAbsent?: undefined;
304
+ } | {
305
+ ok: false;
306
+ existing?: undefined;
307
+ activeTaskId?: undefined;
308
+ sessionAbsent: true;
299
309
  };
300
310
  /** SQL 认领栅栏的结局:`held: false` = 认领此刻不是这条 run 的(或它已不在跑),`write` 一次都没调。 */
301
311
  export type ClaimFenced<T> = {
@@ -504,13 +514,16 @@ export interface SessionSummary {
504
514
  * 零迁移路线 —— result 在终局与 park 时写、认领续跑即清空([ref])⇒ 在跑 run 键诚实缺席);OMIT-when-absent,恒不铸 null。轴辨析与
505
515
  * wire 半场见 `security.ts SessionListItem.lastTaskRunId`(同名同义)。 */
506
516
  lastTaskRunId?: string;
507
- /** The session's title: the user's (`patchSessionMeta`, unconditional; `null` clears it back to automatic) or the auto-titler's
517
+ /** The session's title: the user's (`writeSessionRowCells`; `null` clears it back to automatic) or the auto-titler's
508
518
  * (cheap-model one-liner, `setTitleIfNull` — fills NULL only). null = not (yet) titled OR the lister face has no session_meta (the legacy
509
519
  * task_run aggregate / memory twins — honest degrade). Writer order: `src/session-row.ts`. */
510
520
  title: string | null;
511
- /** Pinned by the user (`patchSessionMeta`). Always present, `false` by default; the legacy task_run aggregate / memory
521
+ /** Pinned by the user (`writeSessionRowCells`). Always present, `false` by default; the legacy task_run aggregate / memory
512
522
  * twins answer `false` (no session_meta ⇒ nothing there can pin it). */
513
523
  pinned: boolean;
524
+ /** 行标签(`src/session-row.ts` 的 `sessionRowTagOf`):这一位住客「PATCH 能写的每一格」的单向摘要,present-iff 这一行可被条件写(店有行格写原语 ∧ 行上有化身);
525
+ * 只经店的列表行映射铸(`sessionRowTagKey`)。没有会话行的 lister(legacy task_run 聚合 / 内存孪生)缺席。wire 半场见 `security.ts SessionListItem.rowTag`(同名同义)。 */
526
+ rowTag?: string;
514
527
  }
515
528
  /**
516
529
  * [ref] —— 会话**枚举面**读 run 账里「每会话的 run 投影」的窄读面:`runCount` / `lastStatus` / `lastRunId` / `lastTaskRunId` 四位,
@@ -529,6 +542,13 @@ export interface SessionRunRollupSource {
529
542
  limit: number;
530
543
  }): Promise<ReadonlyArray<Pick<SessionSummary, "sessionId" | "runCount" | "lastStatus" | "lastRunId" | "lastTaskRunId">>>;
531
544
  }
545
+ /**
546
+ * 会话**此刻的 run 认领**(单活槽)的窄读面 —— run 店的 `getActiveTaskId`,同一个问题在 SQL 孪生里是写事务内对 `task_active` 的锁定读。
547
+ * local 会话店的执行席在 `markMirror` 的写锁内读它,判序与 SQL 孪生同(行不在 / 住客不同 / writer / mirror 先判,认领只拦「absent → mirror」这一笔写)。
548
+ */
549
+ export interface SessionRunClaimSource {
550
+ getActiveTaskId(sessionId: string): Promise<string | undefined>;
551
+ }
532
552
  /**
533
553
  * [ref] —— 「这个会话**最近一条腿**是哪一条 run」的窄读面(`latestRunForSession` 的回值)。
534
554
  *
@@ -38,11 +38,15 @@ export declare function createTidbPool(config: ServiceConfig): Pool;
38
38
  * ── SCHEMA POLICY (clay 裁,2026-07-26) ─────────────────────────────────────────────────────────────
39
39
  * 本仓 schema 的唯一真源 = **这份代码**(此数组 + `pg-pool.ts` 的 PG 孪生)。口径:
40
40
  *
41
- * 1. **删库重建,不做增量。** 尚未上生产、单人本地,存量库一律 DROP DATABASE 重建;因此这里**不再保留
42
- * 任何 `ALTER TABLE` 增量 seam**。历史上的 21 条 ADD COLUMN / 3 条 CREATE INDEX / 1 条 MODIFY COLUMN
43
- * 已于 2026-07-26 全部折进下面的 `CREATE TABLE`(它们承载的语义已逐条搬成列上的行内注释 —— 那些注释
44
- * 是 pre-migration 行为契约的**唯一书面记录**,删注释 = 丢语义)。
45
- * 2. **不许再新增 `ALTER TABLE` seam。** 要改列/加列/改可空性 ⇒ 直接改下面的 `CREATE TABLE`,然后重建库。
41
+ * 1. **代码是唯一真源,不做增量。** 存量库缺列由部署者按拒启句里逐字打印的 `ALTER TABLE … ADD COLUMN …`
42
+ * 自己执行:列集对账与拒启句永不改库。进程自己对**已存在的表**发的 DDL 只有两种 —— 幂等加索引(`ensureIndex`,
43
+ * 只动索引、不碰列)与登记在册的一次性迁移(每条有窗、有登记行 `one-time-migrations.ts`);加列 / 改列 / 改名 /
44
+ * 删表只有后者做。因此这里**不再保留任何 `ALTER TABLE` 增量 seam**。历史上的 21 条
45
+ * ADD COLUMN / 3 条 CREATE INDEX / 1 条 MODIFY COLUMN 已于 2026-07-26 全部折进下面的 `CREATE TABLE`
46
+ * (它们承载的语义已逐条搬成列上的行内注释 —— 那些注释是 pre-migration 行为契约的**唯一书面记录**,
47
+ * 删注释 = 丢语义)。
48
+ * 2. **不许再新增 `ALTER TABLE` seam。** 要加列 ⇒ 直接改下面的 `CREATE TABLE`:新建库随它到货;带数据的存量库由列集对账拒启并逐列打印 `ALTER`,部署者自己执行(**不重建带数据的库**)。
49
+ * 改列类型 / 改可空性 / 改写存量字节 —— 列集对账只比列名、看不见这类漂移 —— 另走一次性迁移(带窗)或在升级步里写明手工语句。只有空库 / 开发机可以整库重建(销毁全部数据)。
46
50
  * (再开增量通道就等于把「代码是真源」变回「代码 + 一串按时间排的补丁才是真源」。)
47
51
  * 3. 生成的全集 SQL 基线导出到 `docs/schema/baseline-mysql.sql`(由本数组机器生成;它是**产物**,不是真源
48
52
  * —— 冲突时以本文件为准并重新生成)。
@@ -83,8 +87,9 @@ export declare const SCHEMA_INDEXES: readonly IndexSpec[];
83
87
  *
84
88
  * Per the SCHEMA POLICY on {@link SCHEMA_STATEMENTS} this is a pure CREATE run — there are NO additive
85
89
  * ALTER/CREATE-INDEX seams and no information_schema probes any more (2026-07-26): the schema contract is
86
- * drop-and-recreate, so every column / index / nullability lives in its `CREATE TABLE`. Do not reintroduce a
87
- * migration loop here; change the CREATE and rebuild the database.
90
+ * code-as-single-source, so every column / index / nullability lives in its `CREATE TABLE`; an existing database that lacks
91
+ * a column is refused at boot with the ALTER statements printed and its deployer runs them. Do not reintroduce a
92
+ * migration loop here; change the CREATE (only a throwaway dev database may be recreated, which destroys all of its data).
88
93
  *
89
94
  * Concurrency: callers run this concurrently — tests fan ALL integration suites out in parallel (each
90
95
  * beforeAll calls ensureSchema), and in production every replica calls it at boot (`--scale service=4`).
@@ -7,6 +7,8 @@ import { TIDB_ADOPTION_LOG_STATEMENTS } from "./adoption-log-sql.js";
7
7
  import { TIDB_PERMISSION_RULE_STATEMENTS } from "./permission-rule-store-sql.js";
8
8
  import { TIDB_RETENTION_STATEMENTS } from "./retention-store-sql.js";
9
9
  import { SESSION_META_INCARNATION_COLUMN } from "../session-incarnation.js";
10
+ import { sessionMetaPlacementColumnsDdl } from "./execution-placement-sql.js";
11
+ import { SESSION_META_WORKSPACE_COLUMN } from "./session-workspace-cell.js";
10
12
  import { RUN_IDENTITY_COLUMNS } from "../identity-source.js";
11
13
  import { SESSION_RULES_COLUMN } from "./session-rules-column.js";
12
14
  import { SESSION_TITLE_MAX_CHARS } from "../session-row.js";
@@ -62,17 +64,22 @@ export const SCHEMA_STATEMENTS = [
62
64
  leaf_id VARCHAR(64) NULL,
63
65
  leaf_seq BIGINT NOT NULL DEFAULT 0,
64
66
  -- title: ONE column, two writers, one law (src/session-row.ts): the user's write (PATCH /v1/sessions/:id →
65
- -- patchSessionMeta, unconditional; null = cleared back to the automatic title) and the auto-titler (cheap-model
67
+ -- writeSessionRowCells, unconditional unless the PATCH carries expectedRowTag; null = cleared back to the automatic title) and the auto-titler (cheap-model
66
68
  -- one-liner on a submit that finds it NULL; setTitleIfNull, fills NULL only) — a user-set title is never overwritten,
67
69
  -- a cleared one is filled again. Width = SESSION_TITLE_MAX_CHARS (the wire bound).
68
70
  title VARCHAR(${SESSION_TITLE_MAX_CHARS}) NULL,
69
- -- pinned: the user's pin (PATCH /v1/sessions/:id → patchSessionMeta); every list row carries it, false by default.
71
+ -- pinned: the user's pin (PATCH /v1/sessions/:id → writeSessionRowCells); every list row carries it, false by default.
70
72
  pinned BOOLEAN NOT NULL DEFAULT FALSE,
71
73
  -- instruction_source(S-690):本会话**上一次 run** 选中的项目指令文件 = 换源通告(instructions.source_changed)
72
74
  -- 的基线。NULL = 还没有任何 run 记过(首 run 只写不发);'' = 记过、那一次没有项目指令文件;其余 = 链成员相对路径
73
75
  -- (CLAUDE.md / .claude/CLAUDE.md / AGENTS.md / .claude/AGENTS.md)。服务端元数据:不进模型上下文、不进 wire
74
76
  -- 会话面;住在会话行上 ⇒ 随会话删除 / 留存清扫一起消失,fork 出的新行为 NULL(新会话首 run 同样只写不发)。
75
77
  instruction_source VARCHAR(64) NULL,
78
+ -- workspace:本会话上一条腿**自己的**最后一帧 workspace_changed(引擎 7.58.0 的整份快照:cwd / root / entered / project / remote)。
79
+ -- NULL = 还没有任何腿记过;唯一写者 = 腿的记录座(src/session-workspace.ts),列属主 = src/plugins/session-workspace-cell.ts
80
+ -- (列定义、形读者、编解码;存量库缺这一列 ⇒ 起服列册对账拒启,拒句里给出 ALTER)。下一条腿的 TaskSpec.projectId 读它的 project.id。
81
+ -- 服务端元数据:不进 wire 会话面、不进模型上下文;住在会话行上 ⇒ 随会话删除 / 留存清扫一起消失,fork 出的新行为 NULL。
82
+ ${SESSION_META_WORKSPACE_COLUMN.tidb},
76
83
  -- #292 C18(core 5.48.0 design/323)subagent 转录持久化 —— placement 元组,**创建时一次落行、
77
84
  -- first-write 不可变**(register 的 ON DUPLICATE 臂永不重写这六列)。判别位 = placement_kind:
78
85
  -- 在场 ⇒ 该会话住在 placement 分区里(不上宿主枚举、claim 形 acquire 恒拒、release=真删除、
@@ -90,6 +97,11 @@ export const SCHEMA_STATEMENTS = [
90
97
  -- src/session-incarnation.ts 的 SESSION_META_INCARNATION_COLUMN(存量库缺这一列 ⇒ 起服列册对账拒启,拒句里的 ALTER 由本建表语句生成)。
91
98
  -- 不上 wire、不进模型上下文、日志只打前 8 位。
92
99
  ${SESSION_META_INCARNATION_COLUMN.tidb},
100
+ -- 执行放置:根会话行上**这一位住客**的一格 —— execution_role('writer' | 'mirror' | NULL = absent)、execution_lane、device 车道的设备与绑定属主、
101
+ -- 放下时刻、换绑序号;写只按住客谓词 (session_id, incarnation, owner) ∧ placement_kind IS NULL(换了住客 = 这一格不在)。fork_key = 建这一行的那次
102
+ -- fork 请求的键(只由 fork 创建点写,此后每个写携带)。规则在 src/execution-placement.ts,列定义与语句的唯一属主 = src/plugins/execution-placement-sql.ts
103
+ -- (存量库缺这些列 ⇒ 起服列册对账拒启;加列只由一次性迁移做)。
104
+ ${sessionMetaPlacementColumnsDdl("tidb")},
93
105
  PRIMARY KEY (session_id),
94
106
  KEY idx_session_meta_owner (owner),
95
107
  -- listPlaced 的检索腿(kind + 租户 + 年龄升序);普通会话这三列全 NULL ⇒ 不占索引选择性。
@@ -296,7 +308,7 @@ export const SCHEMA_STATEMENTS = [
296
308
  -- approval_card(core 7.55.0 #1468 P1 阶段二):门上的卡词**照存**(core checkpointIndexFacts(cp).approvalCard:TOOL_APPROVAL_CARDS 的词,或本引擎不认的串 ——
297
309
  -- 留着,更晚的引擎读得对);非审批门 / 无卡 / 非串 = NULL。写口与一次性迁移 M29 的存量回填同一只派生(plugins/checkpoint-index-columns.ts)。
298
310
  -- 读者:到期 facet 的生成谓词(由 core CARD_FACET_OVERRIDES + PAUSE_REGISTRY 生成;**不存 facet 本身** —— 存一份会在 core 改表那天陈旧)、运维队列行的卡。
299
- -- 本仓不发 ALTER:存量库缺这一列 ⇒ 起服列册对账拒启并给 ADD COLUMN 句(持久数据:表里是在飞的停驻,不删表重建)。
311
+ -- 本仓不发 ALTER:存量库缺这一列 ⇒ 起服列册对账拒启并给 ADD COLUMN 句(持久数据:表里是在飞的停驻,只加列)。
300
312
  -- 列型 TEXT 而不是定长 VARCHAR:引擎照存集外词,长度不由本仓定(191 字的集外词照存)。TEXT 自己的容量是 65,535 **字节**(plugins/checkpoint-index-columns.ts 的
301
313
  -- APPROVAL_CARD_COLUMN_MAX_BYTES):引擎铸的卡词是闭集短词,超出只会是手改 / 外来写者的存量行 —— M29 回填对这样的行计数 + 点名跳过,不写、不挡起服。
302
314
  approval_card TEXT NULL,