@sema-agent/server 7.145.0 → 7.146.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (275) hide show
  1. package/MIGRATION.md +492 -0
  2. package/USAGE.md +218 -47
  3. package/deploy/facts/CHANGES.json +25 -0
  4. package/deploy/facts/README.md +57 -0
  5. package/deploy/facts/lanes.json +54 -0
  6. package/deploy/facts/lanes.schema.json +57 -0
  7. package/deploy/facts/sandbox-rbac.json +113 -0
  8. package/deploy/facts/sandbox-rbac.schema.json +74 -0
  9. package/deploy/facts/sealed-key.json +9 -0
  10. package/deploy/facts/sealed-key.schema.json +52 -0
  11. package/deploy/sema-up/chart/templates/_helpers.tpl +19 -0
  12. package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +47 -7
  13. package/deploy/sema-up/chart/templates/server-v4pro.yaml +4 -0
  14. package/deploy/sema-up/chart/templates/server.yaml +4 -0
  15. package/deploy/sema-up/chart/values.yaml +7 -0
  16. package/deploy/sema-up/stack/sema-stack-cluster.yaml +30 -14
  17. package/dist/adoption/import-limits.d.ts +6 -0
  18. package/dist/adoption/import-limits.js +7 -0
  19. package/dist/adoption/import.d.ts +62 -0
  20. package/dist/adoption/import.js +107 -0
  21. package/dist/adoption/wire.d.ts +52 -0
  22. package/dist/adoption/wire.js +9 -0
  23. package/dist/approval-ask-machine.d.ts +2 -2
  24. package/dist/approval-card.d.ts +3 -12
  25. package/dist/approval-card.js +0 -4
  26. package/dist/approval-gate-currency.d.ts +1 -1
  27. package/dist/approval-gate-currency.js +1 -1
  28. package/dist/approval-reconciler.d.ts +1 -1
  29. package/dist/boot/deployment-mods.d.ts +11 -1
  30. package/dist/boot/deployment-mods.js +15 -2
  31. package/dist/boot/device-lane.js +2 -0
  32. package/dist/boot/engine-lease.d.ts +193 -21
  33. package/dist/boot/engine-lease.js +146 -20
  34. package/dist/boot/execution-env.d.ts +4 -0
  35. package/dist/boot/execution-env.js +12 -0
  36. package/dist/boot/exit-boundary.d.ts +50 -0
  37. package/dist/boot/exit-boundary.js +32 -0
  38. package/dist/boot/exit-last.d.ts +20 -4
  39. package/dist/boot/exit-last.js +21 -4
  40. package/dist/boot/governance-seams.js +1 -0
  41. package/dist/boot/memory-consolidation.d.ts +29 -17
  42. package/dist/boot/memory-consolidation.js +26 -15
  43. package/dist/boot/reapers.d.ts +12 -4
  44. package/dist/boot/reapers.js +39 -15
  45. package/dist/boot/resolve-spec.d.ts +2 -0
  46. package/dist/boot/resolve-spec.js +38 -15
  47. package/dist/boot/runner-deps.js +1 -1
  48. package/dist/boot/session-faces.js +3 -1
  49. package/dist/boot/shutdown.js +27 -9
  50. package/dist/boot/stage-01-config.js +1 -1
  51. package/dist/boot/stage-03-store-backend.d.ts +1 -0
  52. package/dist/boot/stage-03-store-backend.js +2 -2
  53. package/dist/boot/stage-04-stores.d.ts +1 -0
  54. package/dist/boot/stage-05-execution-env.d.ts +3 -0
  55. package/dist/boot/stage-05-execution-env.js +3 -3
  56. package/dist/boot/stage-06-runners.d.ts +3 -0
  57. package/dist/boot/stage-06-runners.js +11 -10
  58. package/dist/boot/stage-07-capability-layer.d.ts +3 -0
  59. package/dist/boot/stage-07-capability-layer.js +7 -2
  60. package/dist/boot/stage-08-reapers.d.ts +3 -0
  61. package/dist/boot/stage-08-reapers.js +3 -3
  62. package/dist/boot/stage-09-leader.d.ts +3 -0
  63. package/dist/boot/stage-10-http-server.d.ts +3 -0
  64. package/dist/boot/stage-10-http-server.js +3 -2
  65. package/dist/boot/stores.d.ts +1 -0
  66. package/dist/boot/stores.js +35 -19
  67. package/dist/boot/workflow-orchestration.d.ts +1 -1
  68. package/dist/boot/workflow-orchestration.js +2 -1
  69. package/dist/capabilities/composite-members.d.ts +3 -1
  70. package/dist/capabilities/composite-members.js +3 -1
  71. package/dist/capabilities/scenarios.d.ts +2 -2
  72. package/dist/capability-facts.d.ts +18 -1
  73. package/dist/capability-facts.js +25 -7
  74. package/dist/checkpoint-ctx-seal-m28.d.ts +1 -1
  75. package/dist/config-catalog.js +13 -4
  76. package/dist/config-provider.d.ts +2 -2
  77. package/dist/config-types.d.ts +43 -4
  78. package/dist/config.d.ts +6 -1
  79. package/dist/config.js +67 -11
  80. package/dist/deleted-session-ids.d.ts +19 -0
  81. package/dist/deleted-session-ids.js +26 -0
  82. package/dist/deploy-facts.d.ts +155 -0
  83. package/dist/deploy-facts.js +94 -0
  84. package/dist/device-ws-hub.d.ts +48 -3
  85. package/dist/device-ws-hub.js +48 -2
  86. package/dist/error-code-family.d.ts +35 -0
  87. package/dist/error-code-family.js +26 -0
  88. package/dist/fleet/drain-entry.d.ts +39 -3
  89. package/dist/fleet/drain-entry.js +6 -0
  90. package/dist/fleet/fleet-bus.d.ts +2 -1
  91. package/dist/hooks/branch-transcript.d.ts +1 -1
  92. package/dist/hooks/branch-transcript.js +1 -0
  93. package/dist/hooks/classic-stations.d.ts +25 -8
  94. package/dist/hooks/classic-stations.js +29 -3
  95. package/dist/hooks/hook-runner.d.ts +33 -16
  96. package/dist/hooks/hook-runner.js +166 -73
  97. package/dist/hooks/hook-source.d.ts +14 -2
  98. package/dist/hooks/hook-source.js +8 -0
  99. package/dist/http/active-run-conflict.d.ts +1 -1
  100. package/dist/http/active-run-conflict.js +1 -1
  101. package/dist/http/admission.js +11 -1
  102. package/dist/http/checkpoint-ctx.d.ts +59 -71
  103. package/dist/http/checkpoint-ctx.js +34 -40
  104. package/dist/http/dispatch.js +18 -2
  105. package/dist/http/durable-run-leg.d.ts +5 -0
  106. package/dist/http/durable-run-leg.js +1 -0
  107. package/dist/http/execution-context.d.ts +4 -0
  108. package/dist/http/execution-context.js +9 -0
  109. package/dist/http/health-self-report.d.ts +6 -2
  110. package/dist/http/health-self-report.js +3 -2
  111. package/dist/http/memory-compliance-failure-reply.d.ts +1 -1
  112. package/dist/http/memory-compliance-failure-reply.js +2 -15
  113. package/dist/http/memory-control-plane-corrupt-reply.d.ts +25 -0
  114. package/dist/http/memory-control-plane-corrupt-reply.js +19 -0
  115. package/dist/http/principal-gate.d.ts +35 -12
  116. package/dist/http/principal-gate.js +16 -12
  117. package/dist/http/request-guards.d.ts +1 -1
  118. package/dist/http/request-guards.js +5 -5
  119. package/dist/http/resume-legs.d.ts +22 -0
  120. package/dist/http/resume-legs.js +26 -17
  121. package/dist/http/route-ctx.d.ts +11 -10
  122. package/dist/http/route-table.js +2 -0
  123. package/dist/http/routes/adoption.d.ts +11 -0
  124. package/dist/http/routes/adoption.js +80 -6
  125. package/dist/http/routes/agents-roster.js +1 -0
  126. package/dist/http/routes/approvals-assistant.js +2 -1
  127. package/dist/http/routes/background.js +1 -0
  128. package/dist/http/routes/capabilities.js +4 -1
  129. package/dist/http/routes/diagnostics.d.ts +9 -14
  130. package/dist/http/routes/diagnostics.js +10 -20
  131. package/dist/http/routes/engine.d.ts +31 -0
  132. package/dist/http/routes/engine.js +81 -0
  133. package/dist/http/routes/memory-bundle.js +3 -0
  134. package/dist/http/routes/memory-consolidation.js +3 -5
  135. package/dist/http/routes/memory-origin.d.ts +1 -1
  136. package/dist/http/routes/memory-origin.js +2 -4
  137. package/dist/http/routes/memory-policy.js +5 -1
  138. package/dist/http/routes/observability.js +3 -6
  139. package/dist/http/routes/runs.js +7 -3
  140. package/dist/http/routes/session-sync.js +12 -25
  141. package/dist/http/routes/sessions-list.d.ts +1 -1
  142. package/dist/http/routes/sessions.js +4 -0
  143. package/dist/http/routes/tasks.js +23 -5
  144. package/dist/http/routes/trace-usage.js +1 -1
  145. package/dist/http/run-concurrency-cap.d.ts +86 -0
  146. package/dist/http/run-concurrency-cap.js +54 -0
  147. package/dist/http/server.d.ts +15 -12
  148. package/dist/http/server.js +18 -3
  149. package/dist/http/stored-body-replay.d.ts +9 -6
  150. package/dist/http/stored-body-replay.js +19 -5
  151. package/dist/http/wire-types.d.ts +8 -3
  152. package/dist/json-body-limit.d.ts +8 -0
  153. package/dist/json-body-limit.js +2 -0
  154. package/dist/leader/diffout.d.ts +1 -1
  155. package/dist/local-data-root.d.ts +2 -2
  156. package/dist/memory-operator-faces.d.ts +9 -4
  157. package/dist/memory-scope-stores.d.ts +9 -7
  158. package/dist/memory-scope-stores.js +3 -14
  159. package/dist/memory-scope.d.ts +36 -0
  160. package/dist/memory-scope.js +32 -1
  161. package/dist/net-address.d.ts +53 -0
  162. package/dist/net-address.js +111 -0
  163. package/dist/observability/fail-open.d.ts +14 -10
  164. package/dist/observability/fail-open.js +14 -10
  165. package/dist/orchestration/workflow-notify-journal.js +2 -3
  166. package/dist/org-delivery-gate.js +1 -1
  167. package/dist/peer-directory.d.ts +6 -12
  168. package/dist/peer-directory.js +5 -1
  169. package/dist/plugins/caching-session-store.d.ts +3 -1
  170. package/dist/plugins/caching-session-store.js +3 -0
  171. package/dist/plugins/checkpoint-store-face.d.ts +426 -0
  172. package/dist/plugins/checkpoint-store-face.js +152 -0
  173. package/dist/plugins/file-run-store.d.ts +0 -6
  174. package/dist/plugins/file-run-store.js +1 -12
  175. package/dist/plugins/fork-routing-session-store.d.ts +6 -2
  176. package/dist/plugins/fork-routing-session-store.js +2 -2
  177. package/dist/plugins/imported-layer-root-retirement.d.ts +10 -8
  178. package/dist/plugins/local-checkpoint-store.d.ts +2 -2
  179. package/dist/plugins/local-checkpoint-store.js +2 -1
  180. package/dist/plugins/local-session-store.d.ts +28 -5
  181. package/dist/plugins/local-session-store.js +32 -8
  182. package/dist/plugins/memory-engine-pg.d.ts +29 -6
  183. package/dist/plugins/memory-engine-pg.js +96 -214
  184. package/dist/plugins/memory-engine-sql-apply.d.ts +121 -0
  185. package/dist/plugins/memory-engine-sql-apply.js +358 -0
  186. package/dist/plugins/memory-engine-tidb.d.ts +35 -9
  187. package/dist/plugins/memory-engine-tidb.js +97 -227
  188. package/dist/plugins/memory-key-guards.d.ts +1 -1
  189. package/dist/plugins/memory-origin-law.d.ts +3 -3
  190. package/dist/plugins/one-time-migrations.d.ts +32 -56
  191. package/dist/plugins/one-time-migrations.js +25 -117
  192. package/dist/plugins/permission-rule-store-file.d.ts +17 -3
  193. package/dist/plugins/permission-rule-store-file.js +8 -1
  194. package/dist/plugins/pg-pool.js +1 -1
  195. package/dist/plugins/pg-session-storage.d.ts +17 -4
  196. package/dist/plugins/pg-session-storage.js +8 -4
  197. package/dist/plugins/run-store-sql.js +2 -2
  198. package/dist/plugins/schema-ensure-legs.js +2 -2
  199. package/dist/plugins/session-fork.d.ts +31 -9
  200. package/dist/plugins/session-fork.js +3 -2
  201. package/dist/plugins/session-placement.d.ts +4 -2
  202. package/dist/plugins/sql-json-column.d.ts +5 -1
  203. package/dist/plugins/store-backend.d.ts +11 -8
  204. package/dist/plugins/store-backend.js +8 -7
  205. package/dist/plugins/store-contracts.d.ts +1 -11
  206. package/dist/plugins/store-contracts.js +0 -9
  207. package/dist/plugins/store-sweep.d.ts +63 -0
  208. package/dist/plugins/store-sweep.js +48 -0
  209. package/dist/plugins/tidb-pool.js +1 -1
  210. package/dist/plugins/tidb-session-store.d.ts +16 -3
  211. package/dist/plugins/tidb-session-store.js +8 -4
  212. package/dist/prompt-screen.d.ts +6 -9
  213. package/dist/prompt-screen.js +2 -5
  214. package/dist/rules-consent.d.ts +20 -27
  215. package/dist/rules-consent.js +82 -6
  216. package/dist/run-cancel-context.d.ts +4 -1
  217. package/dist/run-cancel-context.js +2 -1
  218. package/dist/runs.js +1 -1
  219. package/dist/sandbox-egress-policy.js +6 -11
  220. package/dist/sandbox-rbac-rules.d.ts +88 -0
  221. package/dist/sandbox-rbac-rules.js +80 -0
  222. package/dist/sealed-key-layout.d.ts +17 -0
  223. package/dist/sealed-key-layout.js +4 -0
  224. package/dist/sealed-key.js +5 -5
  225. package/dist/security.d.ts +38 -6
  226. package/dist/security.js +9 -2
  227. package/dist/session-notify-door.d.ts +5 -3
  228. package/dist/session-resident-background.d.ts +57 -0
  229. package/dist/session-resident-background.js +20 -0
  230. package/dist/session-sync-kernel.d.ts +53 -14
  231. package/dist/session-sync-kernel.js +70 -1
  232. package/dist/session-sync.d.ts +5 -3
  233. package/dist/session-workspace.d.ts +1 -0
  234. package/dist/session-workspace.js +1 -0
  235. package/dist/sighup-idle.d.ts +9 -7
  236. package/dist/sighup-idle.js +4 -5
  237. package/dist/spec-fields.d.ts +16 -0
  238. package/dist/spec-fields.js +7 -0
  239. package/dist/stored-request-env-credentials-m38.d.ts +3 -0
  240. package/dist/stored-request-env-credentials-m38.js +8 -0
  241. package/dist/stored-request-prompt-profile-m30.d.ts +1 -1
  242. package/dist/{plugins → stores-sql}/checkpoint-store-sql.d.ts +13 -427
  243. package/dist/{plugins → stores-sql}/checkpoint-store-sql.js +33 -194
  244. package/dist/{plugins → stores-sql}/mailbox-store-sql.d.ts +2 -2
  245. package/dist/{plugins → stores-sql}/mailbox-store-sql.js +4 -4
  246. package/dist/{plugins → stores-sql}/roster-store-sql.d.ts +2 -2
  247. package/dist/{plugins → stores-sql}/roster-store-sql.js +1 -1
  248. package/dist/{plugins → stores-sql}/tool-result-store-sql.d.ts +2 -2
  249. package/dist/{plugins → stores-sql}/tool-result-store-sql.js +6 -6
  250. package/dist/{plugins → stores-sql}/workflow-journal-store-sql.d.ts +7 -12
  251. package/dist/{plugins → stores-sql}/workflow-journal-store-sql.js +3 -8
  252. package/dist/task-settings.d.ts +59 -4
  253. package/dist/task-settings.js +50 -4
  254. package/dist/tool-approval.d.ts +2 -2
  255. package/dist/trace/core-keyset-guard.d.ts +8 -7
  256. package/dist/trace/engine-notice-wire.d.ts +1 -1
  257. package/dist/trace/engine-notice-wire.js +2 -0
  258. package/dist/trace/exit-seat.d.ts +6 -0
  259. package/dist/trace/exit-seat.js +1 -0
  260. package/dist/trace/project.js +9 -0
  261. package/dist/trace/redact.d.ts +13 -2
  262. package/dist/trace/redact.js +305 -24
  263. package/dist/trace/self-minted-wire.d.ts +326 -8
  264. package/dist/trace/self-minted-wire.js +125 -7
  265. package/dist/trace/wire-projection-faces.d.ts +1 -1
  266. package/dist/trace/wire-projection-faces.js +1 -1
  267. package/dist/usage-analytics.d.ts +4 -0
  268. package/package.json +12 -4
  269. package/scripts/drain-check-local.mjs +8 -29
  270. package/scripts/drain-check.mysql.sql +3 -24
  271. package/scripts/drain-check.pg.sql +3 -24
  272. package/skills/update-config.md +1 -1
  273. package/wire-changes/server@7.146.0.json +3221 -0
  274. package/dist/approval-batch-census.d.ts +0 -27
  275. package/dist/approval-batch-census.js +0 -27
@@ -0,0 +1,152 @@
1
+ import { isGateKind, judgeCheckpointRow, MAX_RULE_TEXT_CHARS, } from "@sema-agent/core";
2
+ import { redactDeep, redactSecrets } from "../trace/redact.js";
3
+ import { MAX_RULE_OFFERS, MAX_RULE_OFFER_BATCH_MEMBERS, readRuleOfferList } from "../approval-card.js";
4
+ import { StoreCorruptError } from "./sql-json-column.js";
5
+ import { recordFailOpen } from "../observability/fail-open.js";
6
+ const MAX_TOOL_INPUT_CHARS = 8192;
7
+ export const TERMINAL_BACKSTOP_MS = Math.max(60_000, (Number.isFinite(Number(process.env.APPROVAL_TERMINAL_BACKSTOP_MS)) ? Number(process.env.APPROVAL_TERMINAL_BACKSTOP_MS) : 0) || 30 * 86_400_000);
8
+ export const TERMINAL_GRACE_MS = 3_600_000;
9
+ const GATE_KIND_IS_LIVE_RUN_PARK = {
10
+ human: true,
11
+ irreversible_ask: true,
12
+ resource_limit: true,
13
+ needs_review: true,
14
+ plan_review: true,
15
+ task_done: false,
16
+ };
17
+ export const LIVE_RUN_GATE_KINDS = Object.keys(GATE_KIND_IS_LIVE_RUN_PARK).filter((k) => GATE_KIND_IS_LIVE_RUN_PARK[k]);
18
+ export const TERMINAL_PARK_GATE_KINDS = Object.keys(GATE_KIND_IS_LIVE_RUN_PARK).filter((k) => !GATE_KIND_IS_LIVE_RUN_PARK[k]);
19
+ export function pendingRowPrecedes(a, b, pick) {
20
+ if (pick === "oldest")
21
+ return a.createdAt < b.createdAt || (a.createdAt === b.createdAt && a.token < b.token);
22
+ return a.createdAt > b.createdAt || (a.createdAt === b.createdAt && a.token > b.token);
23
+ }
24
+ const MAX_RULE_OFFER_SCAN = 32;
25
+ function preboundBatchMembers(item) {
26
+ if (typeof item !== "object" || item === null)
27
+ return item;
28
+ const candidate = item;
29
+ if (candidate.kind !== "batch" || !Array.isArray(candidate.rules) || candidate.rules.length <= MAX_RULE_OFFER_BATCH_MEMBERS)
30
+ return item;
31
+ return { ...candidate, rules: candidate.rules.slice(0, MAX_RULE_OFFER_BATCH_MEMBERS) };
32
+ }
33
+ function boundedReaderInput(raw) {
34
+ return Array.isArray(raw) ? raw.slice(0, MAX_RULE_OFFER_SCAN).map(preboundBatchMembers) : raw;
35
+ }
36
+ export function boundedRuleOffers(raw, opts = {}) {
37
+ const observe = opts.countRedactions ?? true;
38
+ const scrub = (text) => redactSecrets(text, { observe }).slice(0, MAX_RULE_TEXT_CHARS);
39
+ const read = readRuleOfferList(boundedReaderInput(raw));
40
+ const out = [];
41
+ for (const { offer } of read) {
42
+ if (out.length >= MAX_RULE_OFFERS)
43
+ break;
44
+ switch (offer.kind) {
45
+ case "single":
46
+ out.push({ kind: "single", rule: scrub(offer.rule), match: offer.match, command: scrub(offer.command) });
47
+ break;
48
+ case "batch":
49
+ out.push({
50
+ kind: "batch",
51
+ rules: offer.rules.slice(0, MAX_RULE_OFFER_BATCH_MEMBERS).map((r) => {
52
+ switch (r.kind) {
53
+ case "command":
54
+ return { kind: "command", rule: scrub(r.rule), match: r.match, command: scrub(r.command), segment: scrub(r.segment) };
55
+ case "directoryRead":
56
+ return { kind: "directoryRead", rule: scrub(r.rule), directory: scrub(r.directory), segment: scrub(r.segment) };
57
+ default:
58
+ throw new Error(`unknown rule-offer batch member kind: ${JSON.stringify(r)}`);
59
+ }
60
+ }),
61
+ uncoveredSegments: offer.uncoveredSegments,
62
+ ...(offer.uncoveredDetail !== undefined ? { uncoveredDetail: offer.uncoveredDetail.map((d) => ({ segment: scrub(d.segment), reason: d.reason })) } : {}),
63
+ });
64
+ break;
65
+ default:
66
+ throw new Error(`unknown rule-offer kind: ${JSON.stringify(offer)}`);
67
+ }
68
+ }
69
+ return out.length > 0 ? out : undefined;
70
+ }
71
+ export class UnreadableCheckpointRowError extends StoreCorruptError {
72
+ rowVerdict;
73
+ constructor(corrupt, rowVerdict) {
74
+ super(corrupt.cause, corrupt.message);
75
+ this.rowVerdict = rowVerdict;
76
+ }
77
+ }
78
+ export function queueRowGateKind(kind) {
79
+ if (isGateKind(kind))
80
+ return kind;
81
+ recordFailOpen("server.approvals.queue-row-gate-kind-missing", `gate_kind=${kind === null || kind === undefined ? "NULL" : redactSecrets(String(kind)).slice(0, 40)}`);
82
+ return undefined;
83
+ }
84
+ export function boundedToolInput(args) {
85
+ if (args === undefined)
86
+ return null;
87
+ const redacted = redactDeep(args);
88
+ const json = JSON.stringify(redacted);
89
+ if (json === undefined)
90
+ return null;
91
+ if (json.length > MAX_TOOL_INPUT_CHARS)
92
+ return { truncated: true, bytes: json.length };
93
+ return redacted;
94
+ }
95
+ export const DECIDED_REPLAY_SCAN_CAP = 8;
96
+ export function approvalDecisionOfWinner(winner, boundCallId) {
97
+ if (winner === undefined)
98
+ return null;
99
+ if (winner.boundCallId !== boundCallId)
100
+ return null;
101
+ switch (winner.decision) {
102
+ case "allow":
103
+ return "approve";
104
+ case "deny":
105
+ return "deny";
106
+ case "approve":
107
+ case "reject":
108
+ case "edit":
109
+ return null;
110
+ default: {
111
+ const unhandled = winner.decision;
112
+ void unhandled;
113
+ return null;
114
+ }
115
+ }
116
+ }
117
+ function pendingActionToolCallId(body) {
118
+ if (typeof body !== "object" || body === null)
119
+ return { readable: false };
120
+ if ("sourceTaskId" in body)
121
+ return { readable: false };
122
+ const rawSource = "sourceSessionId" in body ? body.sourceSessionId : null;
123
+ if (rawSource !== null && rawSource !== undefined && typeof rawSource !== "string")
124
+ return { readable: false };
125
+ const sourceSessionId = typeof rawSource === "string" ? rawSource : null;
126
+ if (!("pendingAction" in body))
127
+ return { readable: true, toolCallId: null, boundInputHash: null, kind: null, sourceSessionId };
128
+ const pa = body.pendingAction;
129
+ if (typeof pa !== "object" || pa === null)
130
+ return { readable: false };
131
+ const kind = "kind" in pa ? pa.kind : null;
132
+ if (kind !== null && typeof kind !== "string")
133
+ return { readable: false };
134
+ if (!("toolCallId" in pa))
135
+ return { readable: true, toolCallId: null, boundInputHash: null, kind, sourceSessionId };
136
+ const id = pa.toolCallId;
137
+ if (typeof id !== "string")
138
+ return { readable: false };
139
+ if (kind !== "tool_approval")
140
+ return { readable: false };
141
+ const bih = "boundInputHash" in pa ? pa.boundInputHash : null;
142
+ if (bih !== null && typeof bih !== "string")
143
+ return { readable: false };
144
+ return { readable: true, toolCallId: id, boundInputHash: bih, kind, sourceSessionId };
145
+ }
146
+ export function candidateIdentityOf(cp) {
147
+ if (judgeCheckpointRow(cp).kind === "lacking")
148
+ return { readable: false };
149
+ return pendingActionToolCallId(cp);
150
+ }
151
+ export const CTX_CLAIM_REWRITES = ["written", "claim_released", "context_absent"];
152
+ //# sourceMappingURL=checkpoint-store-face.js.map
@@ -372,12 +372,6 @@ export declare class FileRunStore implements InProcessClaimFence {
372
372
  private releaseClaim;
373
373
  /** Drop a run entirely (registry + event log + open fd + on-disk dir) — used by deleteBySession. */
374
374
  private dropRun;
375
- /**
376
- * 一次性迁移 **M6 的 local 腿**(窗、删法与删后谁接住:窗口见 `plugins/one-time-migrations.ts` 登记表):把 ≤7.115.x 写下的死亡判决行
377
- * (`failed` ∧ 码列空 ∧ 句子是那两句,判据 = `readLegacyHolderDeathCode`)补上机读码,盘上(`run.json` 整份原子替换,同 `persist`)与内存同改。
378
- * 起服时调(`LocalBackend.runOneTimeMigrations`,任何读者之前);数据根独占 ⇒ 本车道没有滚动升级窗,不需要读侧半场。幂等:第二跑零命中零写盘。
379
- */
380
- migrateLegacyHolderDeathCodes(): number;
381
375
  /**
382
376
  * 一次性迁移 **M32 的 local 腿**(执行放置回填;窗、删法与删后谁接住:窗口见 `plugins/one-time-migrations.ts` 登记表):有 run 行的会话 id(去重)。
383
377
  * 起服时调(`LocalBackend.runOneTimeMigrations`,任何读者之前);读内存索引(构造时已从盘上 hydrate),零写。
@@ -8,7 +8,7 @@ import { AppendLog, atomicWriteFile, ensureDir, isProcessLive, processFingerprin
8
8
  import { recordFailOpen } from "../observability/fail-open.js";
9
9
  import { replayJsonlSkipping } from "../observability/corrupt-read-seat.js";
10
10
  import { joinFromStore, splitForStore } from "./wiring-section-codec.js";
11
- import { RUN_HOLDER_DEATH_CODES, RUN_ORPHANED_ERROR, RUN_STALLED_ERROR, StoreBootRefusalError, buildRunOrphanedError, readLegacyHolderDeathCode, isTerminalRunStatus, settledLegObservationOf } from "./store-contracts.js";
11
+ import { RUN_HOLDER_DEATH_CODES, RUN_ORPHANED_ERROR, RUN_STALLED_ERROR, StoreBootRefusalError, buildRunOrphanedError, isTerminalRunStatus, settledLegObservationOf } from "./store-contracts.js";
12
12
  import { notifyRunTerminal } from "../observability/run-terminal.js";
13
13
  import { readRunIdentityCells, runIdentityCells } from "../identity-source.js";
14
14
  import { createInProcessSpaceCommitTable, fenceSpaceCommitAtTerminal, SPACE_COMMIT_RECORD_SCHEMA } from "../workspace-space-commit.js";
@@ -1107,17 +1107,6 @@ export class FileRunStore {
1107
1107
  }
1108
1108
  }
1109
1109
  }
1110
- migrateLegacyHolderDeathCodes() {
1111
- let migrated = 0;
1112
- for (const r of this.runs.values()) {
1113
- const code = readLegacyHolderDeathCode(r);
1114
- if (code === null || r.errorCode === code)
1115
- continue;
1116
- this.persist({ ...r, errorCode: code });
1117
- migrated += 1;
1118
- }
1119
- return migrated;
1120
- }
1121
1110
  sessionIdsWithRuns() {
1122
1111
  return new Set([...this.runs.values()].map((r) => r.sessionId));
1123
1112
  }
@@ -34,7 +34,7 @@
34
34
  */
35
35
  import { type SessionStore, type AcquiredSession, type SessionHead, type SessionStoreSummary, type SessionPlacement, type PlacedSessionRow } from "@sema-agent/core";
36
36
  import type { ExecutionPlacementSeat } from "../execution-placement.js";
37
- import type { ForkToFn } from "./session-fork.js";
37
+ import type { ForkPlacement, ForkToFn } from "./session-fork.js";
38
38
  /** acquire 选项形(core 5.48 SessionStore 契约):[ref] placement=受信 spawner 的分区建会话声明。 */
39
39
  type AcquireOpts = {
40
40
  requireExisting?: boolean;
@@ -86,7 +86,11 @@ export declare class ForkRoutingSessionStore implements SessionStore {
86
86
  unpin(sessionId: string): void | Promise<void>;
87
87
  noteTaskRun(sessionId: string, taskId: string, runId?: string): void | Promise<void>;
88
88
  list(): Promise<SessionStoreSummary[]>;
89
- fork(sourceId: string, owner?: string | null): Promise<string | null>;
89
+ /** `opts.placement`(后台 fork 子代的转录进委派分区)原样交给做这次 fork 的那只店 —— 落不落由那只店的声明判(host 声明了分区就落;瞬态店不声明 ⇒ 忽略),
90
+ * 本类不判第二次(声明面从 host 逐字转发,引擎据它决定交不交元组)。 */
91
+ fork(sourceId: string, owner?: string | null, opts?: {
92
+ placement?: ForkPlacement;
93
+ }): Promise<string | null>;
90
94
  get size(): number;
91
95
  dispose(): void | Promise<void>;
92
96
  }
@@ -191,10 +191,10 @@ export class ForkRoutingSessionStore {
191
191
  list() {
192
192
  return this.transient.list?.() ?? Promise.resolve([]);
193
193
  }
194
- async fork(sourceId, owner) {
194
+ async fork(sourceId, owner, opts) {
195
195
  const routed = this.route.get(sourceId);
196
196
  const tryFork = async (store, where) => {
197
- const forked = (await store.fork?.(sourceId, owner)) ?? null;
197
+ const forked = (await store.fork?.(sourceId, owner, opts)) ?? null;
198
198
  if (forked !== null) {
199
199
  this.remember(sourceId, where);
200
200
  this.remember(forked, where);
@@ -16,9 +16,10 @@
16
16
  * 一律问**店**:它能叫出名字的每一只桶,判据在桶里判(不在桶外按「谁可能有」预筛)。
17
17
  * · SQL:规则表里的每一只桶(`listRuleBucketOwners`,按 `owner_key` 分页)。不从审批记录推 —— 收编把规则桶整桶改键到新身份、审批史留在旧身份名下,
18
18
  * 从记录推出的属主集在收编之后漏掉新桶。
19
- * · File:引擎的 File 分区后端**没有**列桶口(桶文件按属主名的单向摘要命名),本步能叫出名字的只有本店导入记录点名过的属主
20
- * (`FileRuleApprovalRecordStore.importRecordOwners`;`imported-cc` 只由导入记录派生,记录是只增日志)。**已知缺口(如实)**:引擎的本地数据根收编
21
- * 把一只桶改名到一个没有任何导入记录点名的身份之后,那只桶本步叫不出、不读 —— 等引擎给 File 分区后端一个列桶口再收。
19
+ * · File:本店导入记录点名过的属主(`FileRuleApprovalRecordStore.importRecordOwners`;`imported-cc` 只由导入记录派生,记录是只增日志)∪ 引擎 File 分区后端
20
+ * 自己列得出名字的属主(`FileDurableRulePartitionProvider.listBucketOwners().owners`:本机属主那只桶,与收编标记点名的身份 —— 收编把桶改名到一个没有任何
21
+ * 导入记录点名的身份之后,那只桶照样被叫出来、被读)。**已知缺口(如实)**:桶文件按属主名的单向摘要命名,记录与收编标记都叫不出属主的桶本步不读;
22
+ * 引擎给的「叫不出名字的桶数」(`unnamedBuckets`)只认本机属主那只桶与收编标记、把导入记录点名过的桶也算进去,不是本步的缺数 —— 不报它(报了会把读过的桶说成没读)。
22
23
  *
23
24
  * ## 写在哪里(一处)
24
25
  * 墓碑经**这只桶自己的写面**落(`writerOf(partition).apply({ kind: "tighten-delete" })`,引擎的写面契约)—— SQL 两孪生走 `SqlDurableRulePartition` 的那只
@@ -30,10 +31,11 @@
30
31
  * 列桶 / 解析桶 / 读桶 / 取点 / 写面**抛** ⇒ 原样抛:起服拒,收据账里已提交的墓碑随拒因交出(与本段其余各步同一条路)。例外两条,都由店的判官按抛出物的**类**判(不按文案):
31
32
  * 取点时店说这只桶**在写它的那一步里没了**(`source.isVanishedBucket`:另一只副本删桶恰落在这一窗)⇒ 无可退役、点名跳过 `vanished`(桶都没了,它什么都不放行);
32
33
  * 另一条是解析桶(属主 → 分区)或读桶时**店的唯一判官整桶拒**了
33
- * 这只桶(解析与读同一条律、同一只判官;`source.isWholeBucketRefusal`:确定性判决,引擎读侧对它同样整桶拒、它什么都不放行;SQL = 读腿真往外交的那两类 —— 判官四道的拒与某只 JSON 列的体读不出)
34
- * ⇒ 点名跳过 `bucket_unreadable`,接着读下一只桶(一只读不出的桶不挡别的桶,也不挡起服);一次读失败(连接 / 超时)
35
- * 证明不了以后也读不出,所以它**不**归这一类 —— 跳过它就是让那几行在读恢复之后照旧放行。File 店没有这种判决证据(引擎的 File 分区对损坏与 I/O 失败抛同一种错,
36
- * 解析时读收编标记也是:一次 EIO 与一份坏标记同一种错)⇒ 那一侧解析 / 读桶抛一律起服拒。另两种店**答了**却做不了的(没有写面或写面拒 / 并发争不过)⇒ 点名跳过,下一次起服再试,其间那几行照旧放行,每次起服都点名。
34
+ * 这只桶(解析与读同一条律、同一只判官;`source.isWholeBucketRefusal`:确定性判决,引擎读侧对它同样整桶拒、它什么都不放行;SQL = 读腿真往外交的那两类 —— 判官四道的拒与某只 JSON 列的体读不出;
35
+ * File = 引擎 File 分区的拒码 `permission_rules.bucket_damaged`〔同一份字节每次读都同样拒〕)
36
+ * ⇒ 点名跳过 `bucket_unreadable`,接着读下一只桶(一只读不出的桶不挡别的桶,也不挡起服);一次读失败(连接 / 超时;File 的拒码 `permission_rules.bucket_unreadable`,
37
+ * 带系统错名 `errno`)证明不了以后也读不出,所以它**不**归这一类 —— 跳过它就是让那几行在读恢复之后照旧放行。File 解析时读收编标记的拒(标记自己的拒,不带桶拒码)同样不归这一类 ⇒ 起服拒。
38
+ * 另两种店**答了**却做不了的(没有写面或写面拒 / 并发争不过)⇒ 点名跳过,下一次起服再试,其间那几行照旧放行,每次起服都点名。
37
39
  */
38
40
  import { type DurableRulePartitionProvider, type PersistedRule, type RuleAdd, type RuleOwner } from "@sema-agent/core";
39
41
  /** 一只被退役的导入 add 所在的行(一行一条读数)。**不带规则正文**(与 M11 同一条纪律:规则正文不出店)—— 桶键 + 工具名足够让运维去列举面对上它。 */
@@ -77,6 +79,6 @@ export interface ImportedLayerRootRetirementSource {
77
79
  }
78
80
  /** 判据(引擎 §5.14 那一句,逐字):这一行里该退役的 add;不选 ⇒ 空。 */
79
81
  export declare function importedLayerRootAddsOf(row: PersistedRule): readonly RuleAdd[];
80
- /** 跑一遍:读记录点名的每只桶(按桶键去重),退役判据选中的 add。读数随每次提交记进 `into`。 */
82
+ /** 跑一遍:读店叫得出名字的每只桶(`candidateOwners()`,见头注「读哪些桶」;两源有重 ⇒ 按桶键去重),退役判据选中的 add。读数随每次提交记进 `into`。 */
81
83
  export declare function retireImportedLayerRootAllows(source: ImportedLayerRootRetirementSource, into: ImportedLayerRootReadings): Promise<void>;
82
84
  //# sourceMappingURL=imported-layer-root-retirement.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { type Checkpoint, type CheckpointSummary, type CheckpointToken, type PendingSteerInput, type ReopenReason, type ResolveExpectation, type ExecutionOutcomeRecordWord, type GateOutcome, type ResumeOutcome, type StoreDurability, type StoreFidelity } from "@sema-agent/core";
2
- import { type CheckpointAskCandidate, type CtxClaimRewrite, type CtxRowRewrite, type CtxRowsRewrite, type DecidedApprovalRecord, type PendingCheckpoint, type PendingRowBinding } from "./checkpoint-store-sql.js";
2
+ import { type CheckpointAskCandidate, type CtxClaimRewrite, type CtxRowRewrite, type CtxRowsRewrite, type DecidedApprovalRecord, type PendingCheckpoint, type PendingRowBinding } from "./checkpoint-store-face.js";
3
3
  import { type FencedRun, type InProcessClaimFence } from "./store-contracts.js";
4
4
  import type { SealedCheckpointCtx } from "../http/checkpoint-ctx.js";
5
5
  export interface LocalCheckpointStoreOptions {
@@ -221,7 +221,7 @@ export declare class LocalCheckpointStore {
221
221
  */
222
222
  rewriteCtxWhileClaimed(sessionId: string, taskId: string, ctx: SealedCheckpointCtx, legHolds: (run: FencedRun) => boolean): Promise<CtxClaimRewrite>;
223
223
  /**
224
- * 🔴 **一次性迁移的 ctx 行改写店腿**(local 孪生;`checkpoint-store-sql.ts` `CheckpointCtxRowsRewriter`,判词与 SQL 孪生 `rewriteCtxRows` 逐条同):
224
+ * 🔴 **一次性迁移的 ctx 行改写店腿**(local 孪生;`checkpoint-store-face.ts` `CheckpointCtxRowsRewriter`,判词与 SQL 孪生 `rewriteCtxRows` 逐条同):
225
225
  * 逐档读(信封经读腿**同一只**判形 —— 读不出 ⇒ 计数、跳过、**不删**),`rewrite` 交回新体 ⇒ 信封原样(`sessionId` / `updatedAt` 不动:收割按 `updatedAt` 判)
226
226
  * 只换 `ctx`,经写腿同一只形闸、同一只原子写落回原档。封签绑的是信封里的 `sessionId`(与 `getCtx` 认行的那一格同一个)。
227
227
  * 数据根独占 + 起服在任何读写者之前跑 ⇒ 本车道没有并发写者,不需要 CAS;没有一行装不下这回事(`tooLarge` 恒 0)。
@@ -6,7 +6,8 @@ import { createHash } from "node:crypto";
6
6
  import { existsSync, readFileSync, readdirSync, unlinkSync, mkdirSync } from "node:fs";
7
7
  import { join } from "node:path";
8
8
  import { CHECKPOINT_LIST_LIMITS, FileCheckpointStore, atomicWriteFile, executionVerdict, sanitizePathComponent, } from "@sema-agent/core";
9
- import { approvalDecisionOfWinner, approvalPayloadFingerprint, boundedRuleOffers, boundedToolInput, candidateIdentityOf, pendingRowPrecedes, queueRowGateKind, TERMINAL_BACKSTOP_MS, TERMINAL_GRACE_MS } from "./checkpoint-store-sql.js";
9
+ import { approvalPayloadFingerprint } from "./approval-payload-fingerprint.js";
10
+ import { approvalDecisionOfWinner, boundedRuleOffers, boundedToolInput, candidateIdentityOf, pendingRowPrecedes, queueRowGateKind, TERMINAL_BACKSTOP_MS, TERMINAL_GRACE_MS } from "./checkpoint-store-face.js";
10
11
  import { quarantineUnreadableSidecar } from "./local-sidecar-quarantine.js";
11
12
  import { createZodColumnReader, decodeJsonColumn, foldJsonColumn, readStringArrayColumn } from "./sql-json-column.js";
12
13
  import { UNMANAGED_RETENTION } from "./retention-store-sql.js";
@@ -2,13 +2,13 @@ import { FileSessionRepo, type AcquiredSession, type PlacedSessionRow, type Sess
2
2
  import { type PlacementAcquireOpts } from "./session-placement.js";
3
3
  import { z } from "zod";
4
4
  import { StoreBootRefusalError, type SessionRunClaimSource, type SessionRunRollupSource, type SessionSummary } from "./store-contracts.js";
5
- import { type ClientSessionRowWrite, type ReplaySessionRowWrite, type ReplaySessionRowWriteOutcome, type SessionRowPatch, type SessionRowWriteOutcome } from "../session-row.js";
5
+ import { type ClientSessionRowWrite, type ReplaySessionRowWrite, type ReplaySessionRowWriteOutcome, type SessionRowAnswer, type SessionRowPatch, type SessionRowWriteOutcome } from "../session-row.js";
6
6
  import { type LocalArchiveKeptStamped, type LocalArchiveUnreadable } from "./one-time-migrations.js";
7
7
  import { type Logger } from "../observability/logger.js";
8
8
  import { type SessionFace } from "../session-incarnation.js";
9
9
  import { type ExecutionLane } from "../execution-lane-caps.js";
10
10
  import { type ExecutionPlacementSeat, type LocalPlacementBackfillSeat } from "../execution-placement.js";
11
- import { type ForkTarget, type ForkToOutcome } from "./session-fork.js";
11
+ import { type ForkPlacement, type ForkTarget, type ForkToOutcome } from "./session-fork.js";
12
12
  import { type InstructionSourceBaseline } from "../instruction-source-notice.js";
13
13
  import type { SessionWorkspace } from "../session-workspace.js";
14
14
  import { type StagedCommitOptions, type StagingHandle, type SyncRelation } from "../session-sync-kernel.js";
@@ -584,6 +584,17 @@ export declare class LocalSessionStore implements SessionStore {
584
584
  limit: number;
585
585
  q?: string;
586
586
  }): Promise<SessionSummary[]>;
587
+ /** {@link listSessions} 带每行的住客(属主 + 化身那一格,与行同一刻读)—— `OwnerAwareSessionStore.listSessionRows`;化身不进行本身。 */
588
+ listSessionRows(opts: {
589
+ owner?: string;
590
+ includeUnowned?: boolean;
591
+ cursor?: {
592
+ lastActivityAt: string;
593
+ sessionId: string;
594
+ };
595
+ limit: number;
596
+ q?: string;
597
+ }): Promise<Array<SessionRowAnswer<SessionSummary>>>;
587
598
  /** 列表行要读的边档一次装齐(lastActivity / 标题 / 置顶 / 会话元数据);各自记忆化,只有第一次真读盘。 */
588
599
  private loadRowSidecars;
589
600
  /** 列表行上的属主:那一格的属主(没有格 / 属主从没被记下 ⇒ null —— 与 `headOf(id)?.owner` 同答;列表不补铸缺的格,`headOf` 在第一次读时补)。 */
@@ -611,8 +622,11 @@ export declare class LocalSessionStore implements SessionStore {
611
622
  * 修前把参数原样写进新格:`fork(owned, null)` 铸出一份匿名的有主历史。
612
623
  * 拷**已提交**的叶(契约):在飞的 assistant 轮不进孩子 —— 分叉点只有引擎那一只判官(core 7.55.0 起包根导出的 `settledForkOptions`),本店把唤醒出来的源会话交给它、
613
624
  * 把它的答案原样交给引擎自己的 `repo.fork`(与 core 内存店 `TtlSessionStore.fork` 同一条调用链);两只 SQL 孪生同读它(`session-fork.ts`)。钉:
614
- * core 店契约盒的 `[fork]` 格(`test/session-store-contract-kit.test.ts`)。 */
615
- fork(sourceId: string, owner?: string | null): Promise<string | null>;
625
+ * core 店契约盒的 `[fork]` 格(`test/session-store-contract-kit.test.ts`)。
626
+ * `opts.placement`(后台 fork 子代的转录)= fork 进委派分区:见 {@link forkToHeld} 的建行点。 */
627
+ fork(sourceId: string, owner?: string | null, opts?: {
628
+ placement?: ForkPlacement;
629
+ }): Promise<string | null>;
616
630
  /**
617
631
  * **fork 续写的店契约**(local 一形;规则与结果闭集在 `session-fork.ts`,SQL 孪生同义):
618
632
  * · **两把写锁**:按 id 字典序依次取源与目标的写锁(锁不可重入;同 id 只取一把)—— 同 id 的每个写者(删除 / 登记 / 导入 / 按 id 首建 / 别的 fork)都在目标那把锁里,
@@ -622,11 +636,20 @@ export declare class LocalSessionStore implements SessionStore {
622
636
  * (不交属主 —— 引擎仓匿名;子存档的属主格抄自源存档,源匿名 ⇒ 子匿名)。
623
637
  * 崩在两步之间 = fork 创建的格而存档不在 = `forking`:普通取用一律 {@link SessionForkInProgressError};同键重试在两把锁内补拷存档、答 `replayed`。
624
638
  * 拷贝抛错(不是崩溃)⇒ 撤掉刚写的目标格再上抛(撤不掉 ⇒ 留下的就是 `forking`,与崩在中间同形);
625
- * · **撞已在** ⇒ 按目标格的属主与 fork 键分类({@link classifyForkTarget})。
639
+ * · **fork 进委派分区**(`target.placement`,只在声明了分区的店落;没声明 ⇒ 忽略、照常 fork):元组随引擎 `repo.fork` 落进子存档行头 —— 与带 placement 的建行
640
+ * ({@link createPlacedHeld})同一处、同一形(存档行头,first-write 不可变,`placedAt` 由 repo 铸);行的属主仍是上面那一读(元组不带属主)。建后读回准入同
641
+ * `createPlacedHeld`(镜像方向:repo 交回的行没带这一组元组 ⇒ `placement_refused`):拒了就删掉刚拷的存档、再撤目标格(**存档先删、格后撤** —— 崩在中间留下的是
642
+ * 「格在而存档不在」的惰性残格;反过来会留下一份没有属主格的有主历史拷贝),删不掉 ⇒ 两只故障同句点名、出路随句;
643
+ * · **撞已在** ⇒ 按目标格的属主与 fork 键分类({@link classifyForkTarget});placed 目标永不带键(型面互斥)⇒ 永不 `replayed`。
626
644
  */
627
645
  forkTo(sourceId: string, owner: string | null, target: ForkTarget): Promise<ForkToOutcome | null>;
628
646
  /** {@link forkTo} 的锁内一形(调用方持源与目标两把写锁)。 */
629
647
  private forkToHeld;
648
+ /**
649
+ * fork 进委派分区的建后读回准入(锁内,调用方持目标的写锁、刚拷完存档):判据 = 引擎那一只 `admitSessionPlacement`(镜像方向,读 repo 交回的那一份行头元组,零额外读),
650
+ * 与 {@link createPlacedHeld} 的读回同一判。拒 ⇒ 存档先删、目标格后撤(见 {@link forkToHeld} 注),再原样上抛那只 `placement_refused`;删不掉 ⇒ 同码、两只故障同句点名。
651
+ */
652
+ private admitForkedPlacement;
630
653
  /** 2c session-sync — EXPORT a session's full durable entry log (core `SessionRepo.exportEntries`; the
631
654
  * in-memory/file repo holds the whole log so there is no floor to bypass). null if the source is unknown / the
632
655
  * underlying repo lacks the optional seam. The owner is NOT exported — import re-stamps it. */
@@ -1106,6 +1106,9 @@ export class LocalSessionStore {
1106
1106
  });
1107
1107
  }
1108
1108
  async listSessions(opts) {
1109
+ return (await this.listSessionRows(opts)).map((a) => a.row);
1110
+ }
1111
+ async listSessionRows(opts) {
1109
1112
  await this.loadRowSidecars();
1110
1113
  const rows = [];
1111
1114
  for (const header of await this.rowHeaders())
@@ -1116,16 +1119,18 @@ export class LocalSessionStore {
1116
1119
  const owner = this.ownerCellOf(row.sessionId);
1117
1120
  if (opts.owner !== undefined && owner !== opts.owner && !(opts.includeUnowned === true && owner === null))
1118
1121
  continue;
1119
- all.push(await this.summaryOf(row, rollups));
1122
+ const meta = this.metaCellOf(row.sessionId);
1123
+ const incarnation = meta === undefined ? undefined : incarnationFromCell(row.sessionId, meta.incarnation, "absent");
1124
+ all.push({ row: await this.summaryOf(row, rollups), occupant: { owner, incarnation } });
1120
1125
  }
1121
1126
  const qlc = opts.q?.toLowerCase();
1122
- const filtered = qlc ? all.filter((r) => r.objectivePreview?.toLowerCase().includes(qlc)) : all;
1123
- filtered.sort((a, b) => a.lastActivityAt !== b.lastActivityAt
1127
+ const filtered = qlc ? all.filter(({ row: r }) => r.objectivePreview?.toLowerCase().includes(qlc)) : all;
1128
+ filtered.sort(({ row: a }, { row: b }) => a.lastActivityAt !== b.lastActivityAt
1124
1129
  ? (a.lastActivityAt < b.lastActivityAt ? 1 : -1)
1125
1130
  : (a.sessionId < b.sessionId ? 1 : -1));
1126
1131
  const c = opts.cursor;
1127
1132
  const after = c
1128
- ? filtered.filter((r) => r.lastActivityAt < c.lastActivityAt || (r.lastActivityAt === c.lastActivityAt && r.sessionId < c.sessionId))
1133
+ ? filtered.filter(({ row: r }) => r.lastActivityAt < c.lastActivityAt || (r.lastActivityAt === c.lastActivityAt && r.sessionId < c.sessionId))
1129
1134
  : filtered;
1130
1135
  return after.slice(0, opts.limit);
1131
1136
  }
@@ -1189,8 +1194,8 @@ export class LocalSessionStore {
1189
1194
  return null;
1190
1195
  }
1191
1196
  }
1192
- async fork(sourceId, owner) {
1193
- return forkByFreshId((s, o, t) => this.forkTo(s, o, t), sourceId, owner);
1197
+ async fork(sourceId, owner, opts) {
1198
+ return forkByFreshId((s, o, t) => this.forkTo(s, o, t), sourceId, owner, opts);
1194
1199
  }
1195
1200
  async forkTo(sourceId, owner, target) {
1196
1201
  await this.meta.ensureLoaded();
@@ -1215,20 +1220,24 @@ export class LocalSessionStore {
1215
1220
  const forkOwner = forkOwnerOf(owner, this.metaCellOf(sourceId)?.owner);
1216
1221
  const existing = this.metaCellOf(target.id);
1217
1222
  const targetArchived = await this.existsInRepo(target.id);
1218
- const copy = () => this.repo.fork(sourceMeta, { ...forkOptions, id: target.id });
1223
+ const placement = this.placements !== undefined ? target.placement : undefined;
1224
+ const copy = () => this.repo.fork(sourceMeta, { ...forkOptions, id: target.id, ...(placement !== undefined ? { placement } : {}) });
1219
1225
  if (existing === undefined && !targetArchived) {
1220
1226
  const created = await this.writeMeta(target.id, (before) => before !== undefined
1221
1227
  ? undefined
1222
1228
  : { incarnation: mintSessionIncarnation(), ...(forkOwner !== undefined ? { owner: forkOwner } : {}), ...(target.key !== undefined ? { forkKey: target.key } : {}), createdAt: rowBornNow(), forkedFrom: sourceId });
1223
1229
  if (!created)
1224
1230
  throw new SessionError("storage", `LocalSessionStore: fork target ${target.id} gained a session-meta cell under its own write lock — refusing to guess`);
1231
+ let forked;
1225
1232
  try {
1226
- await copy();
1233
+ forked = await copy();
1227
1234
  }
1228
1235
  catch (err) {
1229
1236
  await this.withdrawCreatedCell(target.id, err, (cause, drop) => `LocalSessionStore: fork into ${target.id} failed while copying (${cause}) and its target cell could not be withdrawn (${drop}) — the target stays in the fork-in-progress state; retry the same fork or delete the session`);
1230
1237
  throw err;
1231
1238
  }
1239
+ if (placement !== undefined)
1240
+ await this.admitForkedPlacement(target.id, forked, placement);
1232
1241
  return { outcome: "created", id: target.id };
1233
1242
  }
1234
1243
  const out = classifyForkTarget(target, { owner: existing?.owner, forkKey: existing?.forkKey }, forkOwner);
@@ -1236,6 +1245,21 @@ export class LocalSessionStore {
1236
1245
  await copy();
1237
1246
  return out;
1238
1247
  }
1248
+ async admitForkedPlacement(sessionId, forked, placement) {
1249
+ try {
1250
+ admitSessionPlacement(sessionId, (await forked.getMetadata()).placement, { placement }, placementCapabilityOf(this));
1251
+ }
1252
+ catch (refusal) {
1253
+ try {
1254
+ await this.repo.delete({ id: sessionId, createdAt: "" });
1255
+ }
1256
+ catch (left) {
1257
+ throw new SessionError("placement_refused", `${refusal instanceof Error ? refusal.message : String(refusal)} — and the copy ${sessionId} this fork created could not be removed (${left instanceof Error ? left.message : String(left)}); delete the session by id`, refusal instanceof Error ? refusal : undefined);
1258
+ }
1259
+ await this.withdrawCreatedCell(sessionId, refusal, (cause, drop) => `LocalSessionStore: a placed fork into ${sessionId} was refused (${cause}); its copy was removed but its session-meta cell could not be withdrawn (${drop}) — the cell stays on disk, inert; delete the session to clear it`);
1260
+ throw refusal;
1261
+ }
1262
+ }
1239
1263
  async exportEntries(sessionId) {
1240
1264
  if (!this.repo.exportEntries)
1241
1265
  return null;
@@ -1,5 +1,6 @@
1
1
  import type { MemoryHistoryAppendRow } from "./memory-sync-store-pg.js";
2
2
  import type { PgEmbedder, PgQueryFn } from "./pg-query.js";
3
+ import { type SqlApplyOpts } from "./memory-engine-sql-apply.js";
3
4
  import type { MemoryBackend, MemoryEntry, MemoryEntryHeader, NotePatch, PatchReport, ScoredMemoryEntry } from "@sema-agent/core";
4
5
  import { type IndexSpec } from "./ensure-index.js";
5
6
  /** Table names (single source). Deliberately DISJOINT from the legacy `agent_memory*` tables —
@@ -54,6 +55,8 @@ export declare class PgMemoryEngineBackend implements MemoryBackend {
54
55
  private readonly history?;
55
56
  /** Cumulative lost-row count — audit-line breakage must itself be auditable. */
56
57
  private historyRowsLost;
58
+ /** 交给共享驱动器的读口 / 写口 / history 冲刷(构造末尾铸一次)。 */
59
+ private readonly leg;
57
60
  /** [ref] rung indicator — same semantics as PgMemoryStore.vectorMode. */
58
61
  get vectorMode(): "native" | "portable" | "lexical";
59
62
  constructor(query: PgQueryFn, opts?: PgMemoryEngineBackendOptions);
@@ -70,12 +73,32 @@ export declare class PgMemoryEngineBackend implements MemoryBackend {
70
73
  limit?: number;
71
74
  exposureBands?: boolean;
72
75
  }): Promise<ScoredMemoryEntry[]>;
73
- applyPatches(patches: readonly NotePatch[]): Promise<PatchReport>;
74
- private applyOne;
75
- /** When the conditional write returns zero rows (a write landed inside the
76
- * SELECT→write window), the report's `currentRev` must be the rev NOW — re-reporting the pre-race
77
- * SELECT value could claim `baseRev === currentRev` alongside a "rev mismatch" reason. */
78
- private freshRev;
76
+ /** core 落点问询协议的能力位:`applyPatches` 兑现 `opts.admit` / `opts.confirm`(读法见 `memory-engine-sql-apply.ts` 头注)。 */
77
+ readonly admitsLandings: true;
78
+ /** 判与驱动器只在 `memory-engine-sql-apply.ts` 一处(两只 SQL 腿共用);本腿只供读口、PG 方言写口(写语句绑定计划的投影)与 history 冲刷。
79
+ * `opts.anchoredRoot` 是带目录的后端的事,本腿没有目录 ⇒ 不读(core 契约原文:a backend without a directory ignores it)。 */
80
+ applyPatches(patches: readonly NotePatch[], opts?: SqlApplyOpts): Promise<PatchReport>;
81
+ private rowOf;
82
+ private idsAt;
83
+ /** 一个 patch 第一次要写之前算一次它的向量参数(撞名 / 被挪重试复用,不重调 embedder —— 改前 add 也只在后缀循环外算一次)。
84
+ * haystack 用调用方给的原形(add 的原名,不是撞名后缀),与改前同。 */
85
+ private prepareWrite;
86
+ /** 写不落之后的现读:这个 id 的行此刻的 rev 与投影(没了 ⇒ undefined)。 */
87
+ private freshRow;
88
+ /**
89
+ * add:`INSERT … ON CONFLICT (id)`,冲突靶 = 仅主键。计划时这个 id 已有行 ⇒ `DO UPDATE … WHERE` 行仍在计划的投影上(同 scope re-add 的幂等
90
+ * 覆盖;行被挪走就不覆盖 —— 被挪去的那个拼写没问过);计划时没有 ⇒ `DO NOTHING`(同 id 的行在判与写之间被别的副本插入就不覆盖)。
91
+ * 两种「不覆盖」都让 `RETURNING` 交零行 ⇒ 现读确认后答 `moved`(驱动器按现状重判)。剩下能撞的唯一键只有 `(scope, slug)` ⇒ `slug-taken`。
92
+ */
93
+ private writeAdd;
94
+ /** update:条件 = 判过的 rev **且**判过的投影(rev 是内容哈希、不含 scope / slug)。零行 ⇒ 现读:内容未变而投影变了 = `moved`;其余 =
95
+ * CAS 冲突,currentRev 报现值(行没了 ⇒ 判时的值)。 */
96
+ private writeUpdate;
97
+ /** delete:条件同 update。 */
98
+ private writeDelete;
99
+ /** BEST-EFFORT history append(见 PgMemoryEngineBackendOptions.history 的如实取舍):entry 写已提交,审计行追加失败不许让报告失败 ——
100
+ * 吞 + warn,永不抛;appendHistory 按 (id, rev) 幂等,调用方重试安全。 */
101
+ private appendHistory;
79
102
  getConsolidationCursor(scope: string): Promise<string | undefined>;
80
103
  setConsolidationCursor(scope: string, cursor: string): Promise<void>;
81
104
  }