@sema-agent/server 7.138.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 (215) hide show
  1. package/MIGRATION.md +276 -7
  2. package/README.md +7 -4
  3. package/README.zh-CN.md +6 -4
  4. package/USAGE.md +73 -29
  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 +20 -20
  9. package/dist/bench/s1/arms.d.ts +1 -1
  10. package/dist/bench/s1/arms.js +1 -3
  11. package/dist/bench/s1/run-firm.js +2 -2
  12. package/dist/boot/device-lane.d.ts +8 -3
  13. package/dist/boot/device-lane.js +7 -2
  14. package/dist/boot/execution-env.d.ts +4 -2
  15. package/dist/boot/execution-env.js +16 -24
  16. package/dist/boot/execution-lane.d.ts +1 -1
  17. package/dist/boot/governance-seams.d.ts +3 -2
  18. package/dist/boot/governance-seams.js +9 -11
  19. package/dist/boot/memory-consolidation.js +2 -2
  20. package/dist/boot/resolve-spec.d.ts +1 -1
  21. package/dist/boot/resolve-spec.js +8 -9
  22. package/dist/boot/retention-lane.d.ts +5 -5
  23. package/dist/boot/retention-lane.js +0 -6
  24. package/dist/boot/runner-deps.d.ts +3 -1
  25. package/dist/boot/runner-deps.js +2 -1
  26. package/dist/boot/session-faces.d.ts +0 -3
  27. package/dist/boot/session-faces.js +15 -8
  28. package/dist/boot/stage-02-web-admin-pull.js +2 -2
  29. package/dist/boot/stage-05-execution-env.js +2 -2
  30. package/dist/boot/stage-06-runners.js +0 -1
  31. package/dist/boot/stage-09-leader.js +1 -2
  32. package/dist/boot/stage-10-http-server.js +2 -2
  33. package/dist/boot/stores.js +4 -4
  34. package/dist/boot/web-admin.js +2 -2
  35. package/dist/capabilities/mcp-probe.js +1 -1
  36. package/dist/capabilities/scenarios.d.ts +1 -1
  37. package/dist/capability-facts.d.ts +15 -7
  38. package/dist/capability-facts.js +8 -3
  39. package/dist/config-catalog.js +7 -7
  40. package/dist/config-invariants.d.ts +23 -3
  41. package/dist/config-invariants.js +42 -0
  42. package/dist/config-provider.d.ts +19 -24
  43. package/dist/config-provider.js +27 -12
  44. package/dist/config-types.d.ts +10 -5
  45. package/dist/config.d.ts +33 -0
  46. package/dist/config.js +44 -15
  47. package/dist/delegated-session-owner.d.ts +2 -2
  48. package/dist/deployment-shape.d.ts +1 -1
  49. package/dist/device-enrollment.d.ts +41 -2
  50. package/dist/device-enrollment.js +27 -12
  51. package/dist/device-store.d.ts +58 -85
  52. package/dist/device-store.js +19 -49
  53. package/dist/device-ws-hub.js +2 -1
  54. package/dist/env-name-allowlist-knobs.d.ts +5 -4
  55. package/dist/exec-cut.d.ts +60 -0
  56. package/dist/exec-cut.js +36 -0
  57. package/dist/execution-lane-caps.d.ts +19 -0
  58. package/dist/execution-lane-caps.js +10 -0
  59. package/dist/execution-placement.d.ts +259 -0
  60. package/dist/execution-placement.js +199 -0
  61. package/dist/field-conflict.d.ts +3 -2
  62. package/dist/field-conflict.js +1 -0
  63. package/dist/hosted-posture.d.ts +2 -2
  64. package/dist/http/admission.js +8 -19
  65. package/dist/http/checkpoint-ctx.d.ts +21 -12
  66. package/dist/http/checkpoint-ctx.js +4 -2
  67. package/dist/http/claimed-run.d.ts +1 -1
  68. package/dist/http/durable-run-leg.js +9 -5
  69. package/dist/http/execution-context.d.ts +218 -0
  70. package/dist/http/execution-context.js +330 -0
  71. package/dist/http/resume-legs.js +50 -20
  72. package/dist/http/route-ctx.d.ts +14 -8
  73. package/dist/http/routes/background.d.ts +29 -1
  74. package/dist/http/routes/background.js +29 -2
  75. package/dist/http/routes/devices.d.ts +13 -19
  76. package/dist/http/routes/devices.js +21 -26
  77. package/dist/http/routes/session-sync.js +110 -15
  78. package/dist/http/routes/sessions.d.ts +6 -0
  79. package/dist/http/routes/sessions.js +115 -11
  80. package/dist/http/routes/side-query.d.ts +12 -4
  81. package/dist/http/routes/side-query.js +4 -5
  82. package/dist/http/routes/tasks.js +37 -31
  83. package/dist/http/server.d.ts +35 -19
  84. package/dist/http/server.js +11 -0
  85. package/dist/http/session-inherit-env.d.ts +3 -4
  86. package/dist/http/session-inherit-env.js +1 -1
  87. package/dist/http/wire-types.d.ts +8 -12
  88. package/dist/index.d.ts +1 -0
  89. package/dist/memory-sync-client.d.ts +53 -14
  90. package/dist/memory-sync-client.js +196 -116
  91. package/dist/memory-sync.d.ts +1 -1
  92. package/dist/memory-sync.js +3 -2
  93. package/dist/observability/cost-taxonomy.d.ts +2 -2
  94. package/dist/observability/fail-open.d.ts +12 -0
  95. package/dist/observability/fail-open.js +12 -0
  96. package/dist/observability/run-terminal.js +0 -1
  97. package/dist/orchestration/workflow-notify-journal.js +4 -4
  98. package/dist/org-memory-admission.d.ts +4 -3
  99. package/dist/org-memory-admission.js +6 -6
  100. package/dist/parked-decide.d.ts +34 -9
  101. package/dist/parked-decide.js +12 -19
  102. package/dist/parked-revive-surface.d.ts +12 -15
  103. package/dist/parked-revive-surface.js +3 -6
  104. package/dist/peer-seat.d.ts +13 -7
  105. package/dist/peer-seat.js +37 -3
  106. package/dist/plugins/approval-ask-store-file.d.ts +7 -0
  107. package/dist/plugins/approval-ask-store-file.js +11 -0
  108. package/dist/plugins/approval-ask-store-memory.d.ts +6 -0
  109. package/dist/plugins/approval-ask-store-memory.js +12 -0
  110. package/dist/plugins/approval-ask-store-sql.d.ts +22 -5
  111. package/dist/plugins/approval-ask-store-sql.js +30 -12
  112. package/dist/plugins/caching-session-store.d.ts +6 -2
  113. package/dist/plugins/caching-session-store.js +9 -3
  114. package/dist/plugins/checkpoint-store-sql.d.ts +6 -5
  115. package/dist/plugins/checkpoint-store-sql.js +5 -7
  116. package/dist/plugins/device-store-sql.d.ts +25 -51
  117. package/dist/plugins/device-store-sql.js +49 -103
  118. package/dist/plugins/execution-placement-sql.d.ts +151 -0
  119. package/dist/plugins/execution-placement-sql.js +223 -0
  120. package/dist/plugins/file-history-store-sql.d.ts +7 -6
  121. package/dist/plugins/file-history-store-sql.js +5 -5
  122. package/dist/plugins/file-run-store.d.ts +6 -1
  123. package/dist/plugins/file-run-store.js +4 -0
  124. package/dist/plugins/fork-routing-session-store.d.ts +6 -0
  125. package/dist/plugins/fork-routing-session-store.js +16 -0
  126. package/dist/plugins/local-session-store.d.ts +149 -50
  127. package/dist/plugins/local-session-store.js +382 -107
  128. package/dist/plugins/memory-key-guards.d.ts +4 -0
  129. package/dist/plugins/memory-key-guards.js +1 -0
  130. package/dist/plugins/memory-run-store.d.ts +1 -1
  131. package/dist/plugins/one-time-migrations.d.ts +111 -3
  132. package/dist/plugins/one-time-migrations.js +380 -10
  133. package/dist/plugins/outcome-ledger-sql.d.ts +2 -2
  134. package/dist/plugins/permission-rule-store-sql.d.ts +13 -15
  135. package/dist/plugins/permission-rule-store-sql.js +13 -16
  136. package/dist/plugins/pg-pool.d.ts +7 -3
  137. package/dist/plugins/pg-session-storage.d.ts +31 -20
  138. package/dist/plugins/pg-session-storage.js +120 -52
  139. package/dist/plugins/remote-env-adb.d.ts +20 -4
  140. package/dist/plugins/remote-env-adb.js +70 -62
  141. package/dist/plugins/remote-env-device.d.ts +30 -12
  142. package/dist/plugins/remote-env-device.js +18 -17
  143. package/dist/plugins/remote-env-e2b.d.ts +18 -4
  144. package/dist/plugins/remote-env-e2b.js +41 -42
  145. package/dist/plugins/remote-env-file-error.d.ts +2 -0
  146. package/dist/plugins/remote-env-file-error.js +1 -1
  147. package/dist/plugins/remote-env-host.d.ts +21 -8
  148. package/dist/plugins/remote-env-host.js +66 -70
  149. package/dist/plugins/remote-env-k8s.d.ts +11 -16
  150. package/dist/plugins/remote-env-k8s.js +61 -54
  151. package/dist/plugins/remote-env-local-docker.d.ts +29 -6
  152. package/dist/plugins/remote-env-local-docker.js +46 -59
  153. package/dist/plugins/remote-env-ssh.d.ts +16 -4
  154. package/dist/plugins/remote-env-ssh.js +72 -56
  155. package/dist/plugins/remote-shell.d.ts +100 -3
  156. package/dist/plugins/remote-shell.js +166 -2
  157. package/dist/plugins/retention-store-sql.d.ts +9 -6
  158. package/dist/plugins/retention-store-sql.js +7 -1
  159. package/dist/plugins/run-store-sql.d.ts +13 -3
  160. package/dist/plugins/run-store-sql.js +5 -0
  161. package/dist/plugins/schema-consistency.d.ts +15 -5
  162. package/dist/plugins/schema-consistency.js +14 -8
  163. package/dist/plugins/schema-drift-sentence.d.ts +27 -0
  164. package/dist/plugins/schema-drift-sentence.js +38 -0
  165. package/dist/plugins/session-fork.d.ts +54 -0
  166. package/dist/plugins/session-fork.js +38 -1
  167. package/dist/plugins/session-row-sql.d.ts +22 -0
  168. package/dist/plugins/session-row-sql.js +81 -0
  169. package/dist/plugins/session-store.d.ts +1 -1
  170. package/dist/plugins/store-backend.d.ts +1 -1
  171. package/dist/plugins/store-backend.js +19 -7
  172. package/dist/plugins/store-contracts.d.ts +25 -5
  173. package/dist/plugins/tidb-pool.d.ts +12 -7
  174. package/dist/plugins/tidb-pool.js +9 -3
  175. package/dist/plugins/tidb-session-store.d.ts +57 -26
  176. package/dist/plugins/tidb-session-store.js +118 -50
  177. package/dist/plugins/tool-result-store-sql.d.ts +3 -2
  178. package/dist/plugins/tool-result-store-sql.js +18 -10
  179. package/dist/project-memory.d.ts +9 -15
  180. package/dist/project-memory.js +5 -4
  181. package/dist/request-key-closure.js +1 -1
  182. package/dist/request-value-closure.d.ts +6 -7
  183. package/dist/request-value-closure.js +1 -3
  184. package/dist/run-local.d.ts +6 -0
  185. package/dist/run-local.js +24 -20
  186. package/dist/runs.d.ts +2 -1
  187. package/dist/runs.js +3 -1
  188. package/dist/security.d.ts +38 -12
  189. package/dist/security.js +8 -2
  190. package/dist/server-secret-env.d.ts +9 -5
  191. package/dist/session-children-retire.d.ts +27 -9
  192. package/dist/session-children-retire.js +9 -5
  193. package/dist/session-incarnation.d.ts +19 -9
  194. package/dist/session-incarnation.js +4 -2
  195. package/dist/session-row.d.ts +114 -5
  196. package/dist/session-row.js +55 -5
  197. package/dist/session-titler.d.ts +2 -2
  198. package/dist/task-a2a.d.ts +1 -1
  199. package/dist/task-cwd.d.ts +6 -1
  200. package/dist/task-mcp.d.ts +3 -3
  201. package/dist/tool-approval.d.ts +6 -0
  202. package/dist/tool-approval.js +8 -1
  203. package/dist/trace/project.d.ts +1 -0
  204. package/dist/trace/project.js +1 -0
  205. package/dist/trace/self-minted-wire.d.ts +111 -2
  206. package/dist/trace/self-minted-wire.js +38 -1
  207. package/dist/url-refusal.d.ts +1 -1
  208. package/dist/web-admin-client/apply-effective.d.ts +17 -3
  209. package/dist/web-admin-client/apply-effective.js +20 -10
  210. package/dist/web-admin-client/skills-mcp.js +3 -2
  211. package/dist/web-admin-client/types.d.ts +6 -0
  212. package/package.json +3 -3
  213. package/wire-changes/server@7.139.0.json +348 -0
  214. package/dist/http/device-placement.d.ts +0 -175
  215. package/dist/http/device-placement.js +0 -145
@@ -123,9 +123,14 @@ export function assertRevokeShape(input) {
123
123
  assertBoundedText("revokedBy", input.revokedBy, DEVICE_TEXT_LIMITS.actor);
124
124
  assertBoundedText("revokeReason", input.reason ?? "", DEVICE_TEXT_LIMITS.revokeReason);
125
125
  }
126
+ export function assertPlaceDeviceShape(input) {
127
+ assertRootSessionIdShape(input.root);
128
+ assertDeviceIdShape(input.deviceId);
129
+ assertDeviceOwnerShape(input.owner);
130
+ }
126
131
  export function assertRebindShape(input) {
127
- assertRootSessionIdShape(input.rootSessionId);
128
- assertDeviceIdShape(input.toDeviceId);
132
+ assertRootSessionIdShape(input.root);
133
+ assertDeviceIdShape(input.targetDeviceId);
129
134
  assertDeviceOwnerShape(input.owner);
130
135
  if (!Number.isInteger(input.expectedRev) || input.expectedRev < 0) {
131
136
  throw new Error(`device-store: expectedRev must be a non-negative integer (got ${input.expectedRev}) — it enters a byte-exact BIGINT equality predicate`);
@@ -194,13 +199,14 @@ function cloneOwner(o) {
194
199
  }
195
200
  export class InMemoryDeviceStore {
196
201
  devices = new Map();
197
- sessions = new Map();
198
202
  tokens = new Map();
199
203
  audit = [];
200
204
  auditSeq = 0;
201
205
  now;
206
+ seat;
202
207
  constructor(opts) {
203
- this.now = opts?.now ?? (() => Date.now());
208
+ this.seat = opts.seat;
209
+ this.now = opts.now ?? (() => Date.now());
204
210
  }
205
211
  async issueEnrollToken(input) {
206
212
  assertIssueTokenShape(input);
@@ -361,57 +367,24 @@ export class InMemoryDeviceStore {
361
367
  row.rev += 1;
362
368
  return true;
363
369
  }
364
- async bindSession(input) {
365
- assertRootSessionIdShape(input.rootSessionId);
366
- assertDeviceIdShape(input.deviceId);
367
- assertDeviceOwnerShape(input.owner);
370
+ async placeDeviceExecution(input) {
371
+ assertPlaceDeviceShape(input);
368
372
  const device = this.devices.get(input.deviceId);
369
373
  if (!device || !sameOwner(device.owner, input.owner))
370
374
  return { outcome: "device_not_found" };
371
375
  if (device.status !== "active")
372
376
  return { outcome: "device_revoked" };
373
- const existing = this.sessions.get(input.rootSessionId);
374
- if (existing) {
375
- const same = existing.deviceId === input.deviceId && sameOwner(existing.owner, input.owner);
376
- return { outcome: same ? "idempotent" : "conflict", row: this.snapshotSession(existing) };
377
- }
378
- const row = {
379
- rootSessionId: input.rootSessionId,
380
- deviceId: input.deviceId,
381
- owner: cloneOwner(input.owner),
382
- boundAtMs: this.now(),
383
- rev: 0,
384
- };
385
- this.sessions.set(row.rootSessionId, row);
386
- return { outcome: "bound", row: this.snapshotSession(row) };
377
+ return this.seat.bindDevice(input.root, input.occupant, { id: input.deviceId, owner: cloneOwner(input.owner) }, new Date(this.now()).toISOString());
387
378
  }
388
- async rebindSession(input) {
379
+ async rebindDeviceExecution(input) {
389
380
  assertRebindShape(input);
390
- const device = this.devices.get(input.toDeviceId);
381
+ const device = this.devices.get(input.targetDeviceId);
391
382
  if (!device || !sameOwner(device.owner, input.owner) || device.status !== "active")
392
383
  return { outcome: "device_not_found" };
393
- const row = this.sessions.get(input.rootSessionId);
394
- if (!row || !sameOwner(row.owner, input.owner))
395
- return { outcome: "binding_not_found" };
396
- if (row.deviceId === input.toDeviceId && row.rev === input.expectedRev)
397
- return { outcome: "idempotent", row: this.snapshotSession(row) };
398
- if (row.rev !== input.expectedRev)
399
- return { outcome: "rev_conflict", row: this.snapshotSession(row) };
400
- const from = row.deviceId;
401
- row.deviceId = input.toDeviceId;
402
- row.boundAtMs = this.now();
403
- row.rev += 1;
404
- this.pushAudit({ event: "session_rebound", deviceId: input.toDeviceId, owner: row.owner, detail: { from, to: input.toDeviceId, rev: row.rev } });
405
- return { outcome: "rebound", row: this.snapshotSession(row) };
406
- }
407
- async getSessionBinding(rootSessionId) {
408
- assertRootSessionIdShape(rootSessionId);
409
- const row = this.sessions.get(rootSessionId);
410
- return row ? this.snapshotSession(row) : null;
411
- }
412
- async deleteByRootSession(rootSessionId) {
413
- assertRootSessionIdShape(rootSessionId);
414
- return this.sessions.delete(rootSessionId) ? 1 : 0;
384
+ const out = this.seat.rebindDevice(input.root, input.occupant, cloneOwner(input.owner), input.expectedRev, input.targetDeviceId, new Date(this.now()).toISOString());
385
+ if (out.outcome === "rebound")
386
+ this.pushAudit({ event: "session_rebound", deviceId: input.targetDeviceId, owner: input.owner, detail: { from: out.from, to: input.targetDeviceId, rev: out.device.rev } });
387
+ return out;
415
388
  }
416
389
  async appendAudit(input) {
417
390
  this.pushAudit(input);
@@ -440,9 +413,6 @@ export class InMemoryDeviceStore {
440
413
  snapshotDevice(row) {
441
414
  return { ...row, owner: cloneOwner(row.owner) };
442
415
  }
443
- snapshotSession(row) {
444
- return { ...row, owner: cloneOwner(row.owner) };
445
- }
446
416
  }
447
417
  export { sameOwner as sameDeviceOwner, cloneOwner as cloneDeviceOwner };
448
418
  //# sourceMappingURL=device-store.js.map
@@ -832,7 +832,8 @@ export function createDeviceWsHub(options) {
832
832
  return { kind: "rejected", reject: rejection(DEVICE_DRAINING_CODE, "this device hub is closed for shutdown — the instruction was never dispatched (safe to retry once the device reconnects on a replacement replica)", DRAIN_RETRY_AFTER_SEC) };
833
833
  if (draining)
834
834
  return drainingRejectResult();
835
- const admitted = await enrollment.admitSession({ rootSessionId: input.rootSessionId, requester: input.requester });
835
+ const placement = await enrollment.readPlacement(input.rootSessionId);
836
+ const admitted = await enrollment.admitSession({ rootSessionId: input.rootSessionId, placement, requester: input.requester });
836
837
  if (!admitted.ok)
837
838
  return { kind: "rejected", reject: admitted.reject };
838
839
  if (abortedNow())
@@ -10,12 +10,13 @@
10
10
  * 带外凭据(e2b / docker / k8s 车道本来就是多租户 fleet 的主形),与 LSP 那条「本机子进程」的形不同;多租户下起服说一句。
11
11
  * · `BAKE_ENV_ALLOW`([ref] 残余,7.103.0)由 **bake-runner 进程**读(构建宿主守护进程:没有主体、没有租户 ⇒ 没有姿态列,
12
12
  * 不进 {@link describeAllowlistPostures} 的姿态句)。它与服务侧四只旋钮同一只解析器、同一只「点名放行」谓词、同一条「原文按凭据类」律。
13
- * · `channel` = 这张名单**交给谁、在谁的减法之上**(闭集两词,解析器按它定「不许点哪些名」):
13
+ * · `channel` = 这张名单**交给谁、在谁的减法之上**(闭集两词)。它只定拒句里「交到哪里」那半句与出路的主语,**不定拒不拒**:「不许点哪些名」对五只一律
14
+ * = 名单型旋钮名 ∪ 本服务自有凭据名(引擎此刻的声明:起服解析时判一次,声明增长时由换代声明对活配置里每只服务侧名单此刻交出去的名重判一次;判官 = 引擎
15
+ * `inheritEnvPolicyProblem`,与引擎拒嵌入方白名单同句),规则全文在解析器头注 ②。
14
16
  * - `host-put-back`(`LSP_ENV_ALLOW` / `BAKE_ENV_ALLOW`)—— 剥后放回形:宿主子进程的 env 先经唯一组合(引擎扣掉本服务自有凭据 + 形状剥),
15
- * 名单点到的键再从本进程环境**原样**取回叠上去。点到本服务自有凭据名 = 把引擎刚扣下的那枚交回去 ⇒ 起服拒,判官 = 引擎同一只
16
- * (`inheritEnvPolicyProblem`,读起服声明;与引擎拒嵌入方白名单同句)。
17
+ * 名单点到的键再从本进程环境**原样**取回叠上去;点到本服务自有凭据名 = 把引擎刚扣下的那枚交回那只子进程。
17
18
  * - `sandbox-grant`(三只沙箱名单)—— 沙箱 env 的唯一来源(沙箱不从本进程继承任何东西),部署方显式交出的带外凭据([ref] 照转发);
18
- * 本列不在「自有凭据」律的射程里,只拒点名名单型旋钮(原文按凭据类)。
19
+ * 点到本服务自有凭据名 = 把它装进模型读得到的沙箱 env。沙箱要凭据 ⇒ 部署方给沙箱另铸一把(名与值都不同于本服务的任何凭据)。
19
20
  * 表是 `Record<旋钮, …>`:闭集加一只旋钮而不补行 = 编译红;行形随读它的进程定(服务进程 ⇒ 必须写姿态列)。
20
21
  *
21
22
  * 🔴 **姿态列只在这里写一次**([ref];7.101.0 合并复审 O9):凡要对人说「多租户下哪只旋钮忽略、哪只照转发」的句子,一律取
@@ -0,0 +1,60 @@
1
+ /**
2
+ * 一条**跑过**才被切的命令,执行错误怎么造、切断那一刻输出往哪去的**唯一一条律**(七只 adapter —— host〔管道 / spool 两形〕/ adb / e2b / ssh / k8s /
3
+ * local-docker / device —— 同读这一只):
4
+ *
5
+ * 命令已经起跑、之后被切 —— 墙钟(`timeout`)、静默界(`liveness_no_output`)、调用方中止(`aborted`)、宿主侧输出回调失败(`callback_error`)—— 它在被切之前
6
+ * 写出的 stdout / stderr 是真实的工作成果,必须原样带在 `ExecutionError.partialStdout` / `partialStderr` 上(core 合同:参考行为是超时保留**全部**已捕获的输出,
7
+ * 只追加一句「超时了」;丢掉 600 s 的构建日志会逼模型盲重跑)。引擎只从这两个字段读输出拼工具结果,从 `message` 读不到 ——
8
+ * 缺席就报「(no output was produced before the cutoff)」,模型对着一个其实已写了进度的命令说「什么也没产出」。
9
+ *
10
+ * **切断即落定**:切断时本层手里还没交出的输出(远端 shell 腿 cwd 过滤器压着的 stderr 尾巴、host spool 还没泵的字节)只**记账**(尾窗 / detach tee),
11
+ * 随错误交回,不经调用方的输出回调**外发**。**落定先于构造,构造只记账**:构造结局会做事的腿(ssh / adb / k8s / host spool —— 冲刷压着的尾巴、泵 spool、
12
+ * 杀进程)的 `finish` 收一只构造结局的函数,先置落定,再构造、收尾、解决 —— 构造期间不调调用方的代码,重入的 detach / abort / close 读到的都是「已落定」。
13
+ * 正常结束同一顺序:压着的尾巴先记账、解决,**之后**才交给回调(那是流本身的结尾,不是切断)—— 回调里发生什么(重入、抛错)都改不了、也挂不住结局。
14
+ * 构造只读尾窗 / SDK 句柄字段的腿(host 管道 / local-docker / e2b)造错误即读,没有可重入的东西;device 的错误在投递结算之后才造。
15
+ *
16
+ * 与 `exec-bounds.ts` 的分工:那一只定「什么界在何时切、切断报哪个码、哪句话、几秒」(`buildLivenessCut` 等);这一只定「切断的错误带什么」。静默切断的
17
+ * 数据(`buildLivenessCut` 的返回)直接就是这里的 {@link ExecCut};墙钟与中止切断各 adapter 自己说句子(它们的成因词各车道不同),错误一律经 {@link createCutError} 造;
18
+ * 码是数据的腿(device:码由对端说,经映射表落到本侧)经 {@link createExecFailure} 按同一判别造。
19
+ *
20
+ * 输出的口径 = 同一条命令正常收口时 `exec` 会交出的那一份,切断不另设一道界、不二次切片:自带有界滚动尾窗(`RollingTailBuffer`)的腿读尾窗的当前文本,被丢掉的字节照旧附
21
+ * `markTruncated` 的记号({@link buildCapturedOutput});收口交出的是 SDK 整串的腿(e2b)读 SDK 句柄自己的记录;device 读它自己收齐的两路串。远端 shell 腿(ssh / adb / k8s)
22
+ * 的 stderr 在读之前先把 cwd 过滤器压着的尾巴记进账,与正常收口同一步。`message` 只陈述事实(哪一界、多少毫秒),不再夹输出。
23
+ *
24
+ * 不在本律里:命令**没起跑**就失败的码(`spawn_error` / `shell_unavailable` / 起跑前的 `aborted` / device 投递门之前的拒)按定义没有输出;结果未知
25
+ * (`transport_lost` / `outcome_unknown`)与未分类(`unknown`)的码引擎不读它的 partial —— 判别只有 {@link isRanThenCutCode} 一处。
26
+ */
27
+ import { ExecutionError, type ExecutionErrorCode, type RollingTailBuffer } from "@sema-agent/core";
28
+ /** 命令已起跑、随后被切的四个码(core `ExecutionError` 注:`partialStdout` / `partialStderr` 为这四个码而设)。 */
29
+ export type RanThenCutCode = Extract<ExecutionErrorCode, "timeout" | "liveness_no_output" | "aborted" | "callback_error">;
30
+ /**
31
+ * 一次切断的数据:码 + 句。`noOutputSec` 只属静默切断(静默窗口的整秒数,core `ExecutionError` 第五构造参)—— 与 `exec-bounds.ts` 的
32
+ * `buildLivenessCut` 返回值同形,可直接传入。
33
+ */
34
+ export type ExecCut = {
35
+ readonly code: Exclude<RanThenCutCode, "liveness_no_output">;
36
+ readonly message: string;
37
+ } | {
38
+ readonly code: "liveness_no_output";
39
+ readonly message: string;
40
+ readonly noOutputSec?: number;
41
+ };
42
+ /** 切断那一刻命令已写出的输出(纯数据;两路都是字符串,没写 = 空串)。 */
43
+ export interface CapturedOutput {
44
+ readonly stdout: string;
45
+ readonly stderr: string;
46
+ }
47
+ /**
48
+ * 切断那一刻两路滚动尾窗里的内容 = 该命令正常收口时 `exec` 交出的那一份(滚出窗外的字节照旧附 {@link markTruncated} 的记号)。
49
+ * `stdout` 缺席 = 这一次往返把 stdout 整份留作**数据载荷**(字节精确的文件读取:adb / local-docker 的 `binary`、k8s 的非 `boundOutput`)—— 载荷不是
50
+ * 命令输出,被切的数据读取没有「已写出的输出」可交给读者,stdout 记空串;stderr 恒是命令输出。
51
+ */
52
+ export declare function buildCapturedOutput(stdout: RollingTailBuffer | undefined, stderr: RollingTailBuffer): CapturedOutput;
53
+ /** 一条起跑后被切的命令的执行错误 —— 唯一造法(见文件头注):码 / 句取自 `cut`,输出取自 `captured`(逐字,不截)。 */
54
+ export declare function createCutError(cut: ExecCut, captured: CapturedOutput): ExecutionError;
55
+ /**
56
+ * 码是**数据**时的执行错误(device:对端说的词经映射表落到本侧,这一刻才知道是哪个码)—— 同一条律:命令起跑过(`ran` = 它已写出的输出)且码是起跑后被切的
57
+ * 四个之一 ⇒ 经 {@link createCutError} 带输出;其余(没起跑 = `ran` 缺席 / 结果未知 / 未分类)按律不带。码在调用处就是字面量的腿直接用 {@link createCutError}。
58
+ */
59
+ export declare function createExecFailure(code: ExecutionErrorCode, message: string, ran?: CapturedOutput): ExecutionError;
60
+ //# sourceMappingURL=exec-cut.d.ts.map
@@ -0,0 +1,36 @@
1
+ import { ExecutionError, markTruncated } from "@sema-agent/core";
2
+ function isRanThenCutCode(code) {
3
+ switch (code) {
4
+ case "timeout":
5
+ case "liveness_no_output":
6
+ case "aborted":
7
+ case "callback_error":
8
+ return true;
9
+ case "shell_unavailable":
10
+ case "spawn_error":
11
+ case "transport_lost":
12
+ case "suspended":
13
+ case "auth_failed":
14
+ case "outcome_unknown":
15
+ case "target_unavailable":
16
+ case "unknown":
17
+ return false;
18
+ }
19
+ }
20
+ export function buildCapturedOutput(stdout, stderr) {
21
+ const err = stderr.result();
22
+ const out = stdout?.result();
23
+ return {
24
+ stdout: out === undefined ? "" : markTruncated(out.text, out.droppedBytes),
25
+ stderr: markTruncated(err.text, err.droppedBytes),
26
+ };
27
+ }
28
+ export function createCutError(cut, captured) {
29
+ return new ExecutionError(cut.code, cut.message, undefined, { stdout: captured.stdout, stderr: captured.stderr }, cut.code === "liveness_no_output" ? cut.noOutputSec : undefined);
30
+ }
31
+ export function createExecFailure(code, message, ran) {
32
+ if (ran !== undefined && isRanThenCutCode(code))
33
+ return createCutError({ code, message }, ran);
34
+ return new ExecutionError(code, message);
35
+ }
36
+ //# sourceMappingURL=exec-cut.js.map
@@ -139,6 +139,20 @@ export declare function executionLaneRow(lane: RemoteExecProvider): ExecutionLan
139
139
  * device 没有必需 env(设备是运行期连进来的;它的 boot 前提是跨键的,在跨域不变量区拒启)。
140
140
  */
141
141
  export declare function laneRequiredEnv(lane: RemoteExecProvider): readonly string[];
142
+ /**
143
+ * 一个必需 env 的值**算不算缺**的唯一判官 —— 两半各有归属:
144
+ * · **空白半边**(空串、只含空白)归契约包:包根导出的 `isBlankCoordinate` 是这件事唯一的实现,契约包的写闸(车道声明保存时)问的是同一只函数,本仓不另抄一份;
145
+ * · **未设半边**(`undefined`)归本仓:契约包的判官只收字符串,「这一键根本没设」由调用方先判,所以这一臂留在这里。
146
+ * 合起来:`undefined`、空串、只含空白一律按缺 —— 引擎把空值读成未设,纯空白拼进坐标(镜像名、命名空间、主机名)也只会在第一次用的时候才失败。
147
+ * 所有车道、所有必需键同判一只,没有「某个键另有一套缺席 / 空白判法」。
148
+ * 只判「是不是空白」:不 trim 值本身、不管值的格式(`" sandboxes "` 照过);可选键不走它(个别可选键的空串有语义,如 `K8S_RUNTIME_CLASS` 的空串 = 不用 Kata)。
149
+ */
150
+ export declare function isBlankRequiredEnvValue(value: string | undefined): boolean;
151
+ /**
152
+ * 一条车道此刻**缺**的必需 env 键名(次序同 {@link laneRequiredEnv};「缺」的判官是 {@link isBlankRequiredEnvValue})。
153
+ * 读者:`config.ts` 的 [ref] 拒启门(非空 ⇒ 拒启并点名缺哪几键);判的是调用方交来的 env 快照,本函数不读 `process.env`。
154
+ */
155
+ export declare function laneMissingEnv(lane: RemoteExecProvider, env: Readonly<Record<string, string | undefined>>): readonly string[];
142
156
  /**
143
157
  * [ref] 件③ / [ref] —— **「`REQUIRE_EXECUTION_LANE=true` ∧ 没有任何车道被点名 ⇒ 拒启」的判据与拒因句的唯一属主**(`modelProviderRefusal` /
144
158
  * `modelRouteEndpointRefusal` 同形:属主在叶模块,执法点各自调它,登记在 `config-invariants.ts`)。
@@ -175,6 +189,11 @@ export declare function executionLaneRequiredRefusal(deployment: {
175
189
  */
176
190
  export declare function executionLaneOf(provider: RemoteExecProvider | undefined): ExecutionLane;
177
191
  export declare function executionLaneOf(provider: string | undefined): ExecutionLane | undefined;
192
+ /**
193
+ * 本部署声明的**有序执行车道表**(首项 = 缺省 = 服务车道)的唯一读法。本版 `REMOTE_EXEC` 单值 ⇒ 一元表 `[executionLaneOf(provider)]`;
194
+ * 车道表配置面落地时只改这一只。读者:执行放置切换的回填(一次性迁移 M32 与它的 DDL 前探针 —— 回填只在一元表下有唯一答案)。
195
+ */
196
+ export declare function deployedExecutionLanes(config: Pick<ServiceConfigFlat, "remoteExec">): readonly ExecutionLane[];
178
197
  /**
179
198
  * [ref] —— 「这条车道的**手**就在本 worker 的盘上吗」。**两位合取,都读表**(不手抄):
180
199
  * · `coreRemoteExecutionEnv` —— core 拿到了执行环境 = 手带真挂上(`in-process` 答 NO:core 落 `StubExecutionEnv`);
@@ -1,5 +1,6 @@
1
1
  import { homedir } from "node:os";
2
2
  import { EXECUTION_LANE_WORDS as DECLARABLE_LANE_WORDS, EXECUTION_LANES } from "@sema-agent/settings-schema/execution-lanes";
3
+ import { isBlankCoordinate } from "@sema-agent/settings-schema";
3
4
  import { DECLARE_HOME_REMEDY, buildHomeAbsentNote } from "./lane-home-probe.js";
4
5
  const NO = "unsupported";
5
6
  const YES = "supported";
@@ -180,6 +181,12 @@ export function laneRequiredEnv(lane) {
180
181
  const row = executionLaneRow(lane);
181
182
  return [...row.requiredEnv, ...row.requiredSecretEnv];
182
183
  }
184
+ export function isBlankRequiredEnvValue(value) {
185
+ return value === undefined || isBlankCoordinate(value);
186
+ }
187
+ export function laneMissingEnv(lane, env) {
188
+ return laneRequiredEnv(lane).filter((name) => isBlankRequiredEnvValue(env[name]));
189
+ }
183
190
  export function executionLaneRequiredRefusal(deployment) {
184
191
  if (!deployment.requireExecutionLane || executionLaneOf(deployment.remoteExec?.provider) !== "in-process")
185
192
  return undefined;
@@ -199,6 +206,9 @@ export function executionLaneOf(provider) {
199
206
  return "in-process";
200
207
  return isExecutionLane(provider) ? provider : undefined;
201
208
  }
209
+ export function deployedExecutionLanes(config) {
210
+ return [executionLaneOf(config.remoteExec?.provider)];
211
+ }
202
212
  export function handsOnWorkerFilePlane(lane) {
203
213
  const caps = executionLaneCaps(lane);
204
214
  return caps.hostFilePlane === "supported" && caps.coreRemoteExecutionEnv === "supported";
@@ -0,0 +1,259 @@
1
+ /**
2
+ * **执行放置** —— 根会话行上「这一位住客」的一格(叶模块:型 + 纯函数 + 测试走廊的内存参照实现;运行期只依赖 core 的住客判官)。
3
+ *
4
+ * ## 规则(全仓唯一处写)
5
+ * · **一格四态**:`absent`(这一位住客还没有放置)| `writer(L[, D])`(本引擎声明在车道 L 上写;device 车道带设备 D,D 缺 = 迁移回填的
6
+ * 「有 run、设备未知」)| `mirror`(写者在别处,本处只读)| `forking`(只 local:fork 的目标格已写、存档未落 —— 崩溃中间态,SQL 单事务不可达)。
7
+ * 载体:SQL `session_meta` 的 `execution_*` 七列(列定义与语句的唯一属主 = `plugins/execution-placement-sql.ts`);local = 会话元数据边档那一格的
8
+ * `execution` 位(`plugins/local-session-store.ts`)。处处 `execution` 限定,与同行 `placement_*`(委派分区)不同义;执行格只在 `placement_kind IS NULL` 的行上 ——
9
+ * 委派分区行读作「不在」。
10
+ * · **读只有一只**:会话店的执行席 {@link ExecutionPlacementSeat.read}(一次行读:住客 + 格;行不在 / 委派分区行 ⇒ `undefined`;读故障抛,不吞成 `absent`)。
11
+ * · **写只按住客谓词** `(root, incarnation, owner) ∧ placement_kind IS NULL`:住客换了(删后重建,哪怕同一属主;local M10 窗内的认领)= 这一格不在 ⇒ 落空;
12
+ * 0 行一律按住客复读分类({@link settledPlaceOutcome} / {@link settledMirrorOutcome} / {@link settledDeviceBindOutcome}),**永不对现住客重试**。
13
+ * · **没有的转换**:writer ↔ mirror、writer(L) → writer(L')(device 车道的换绑 writer(device, D) → writer(device, D') 除外,它有自己的语句与门)。
14
+ * · **放置存在 ⇒ 住客存在**:放置是行的列 / 格,删会话即删放置;建行恒 absent(SQL 建行 INSERT 列名显式;local 新格整格新铸),导入新建的行同锁落 mirror。
15
+ *
16
+ * ## 测试走廊的会话行(不是第二只生产店)
17
+ * {@link InMemoryExecutionPlacementSeat} 是**语义参照实现**(与 `InMemoryDeviceStore` 同一身份):持「会话行」(root → 住客 + 格),给只用 `Map` 会话店的
18
+ * 测试走廊与 `InMemoryDeviceStore` 共用。**生产装配零引用** —— `src/` 非测试路径里只许 `device-store.ts` 与本模块提到它。
19
+ */
20
+ import { type SessionHead } from "@sema-agent/core";
21
+ import type { DeviceOwner } from "./device-store.js";
22
+ import type { ExecutionLane } from "./execution-lane-caps.js";
23
+ import type { SessionFace } from "./session-incarnation.js";
24
+ /** writer(device, D) 的 D:设备、绑定属主(= 设备属主)、换绑序号、放下时刻(ISO)。 */
25
+ export interface ExecutionDeviceCell {
26
+ readonly id: string;
27
+ readonly owner: DeviceOwner;
28
+ readonly rev: number;
29
+ readonly placedAt: string;
30
+ }
31
+ /** `lane = "device"` ∧ `device` 缺 = writer(device, ∅)(只来自迁移回填:有 run 而设备未知)。 */
32
+ export type ExecutionWriterCell = {
33
+ readonly role: "writer";
34
+ readonly lane: ExecutionLane;
35
+ readonly device?: ExecutionDeviceCell;
36
+ };
37
+ export type ExecutionPlacementCell = {
38
+ readonly role: "absent";
39
+ } | ExecutionWriterCell | {
40
+ readonly role: "mirror";
41
+ } | {
42
+ readonly role: "forking";
43
+ };
44
+ /** 一次行读的答案:这一位住客 + 它的格(同一行、同一次读)。 */
45
+ export interface ExecutionPlacementReading {
46
+ readonly occupant: SessionFace;
47
+ readonly cell: ExecutionPlacementCell;
48
+ }
49
+ /** place 的结果(0 行按住客复读分类;`gone` = 行不在或住客不是判过的那一位)。 */
50
+ export type PlaceOutcome = {
51
+ readonly outcome: "placed";
52
+ } | {
53
+ readonly outcome: "same";
54
+ } | {
55
+ readonly outcome: "tree_bound";
56
+ readonly boundLane: ExecutionLane;
57
+ } | {
58
+ readonly outcome: "mirror";
59
+ } | {
60
+ readonly outcome: "gone";
61
+ };
62
+ /** identical 臂补 mirror 的结果(`active` = 写事务内(SQL)/ 写锁内(local)读到认领,带那只认领的 task id;判官的答案先于它,只拦「absent → mirror」这一笔写)。 */
63
+ export type MarkMirrorOutcome = {
64
+ readonly outcome: "mirrored";
65
+ } | {
66
+ readonly outcome: "writer";
67
+ } | {
68
+ readonly outcome: "gone";
69
+ } | {
70
+ readonly outcome: "active";
71
+ readonly activeTaskId: string;
72
+ };
73
+ /**
74
+ * 「谁带执行席」的唯一一句(要求会话店带席的装配拒句引它,不另抄):三店都带(SQL 两孪生 + local,即 `StoreBackend` 交出的会话店),两只包装原样转发;
75
+ * 只用 `Map` 会话店的测试走廊 / 内存嵌入给参照实现。
76
+ */
77
+ export declare const EXECUTION_PLACEMENT_SEAT_CARRIERS = "every built-in session store carries one (the MySQL-protocol and PostgreSQL stores and the local store, i.e. the session store a StoreBackend hands out; the caching and fork-routing wrappers forward it), and an in-memory test or embedding passes InMemoryExecutionPlacementSeat";
78
+ /** 会话店的执行席(三店 + 两只包装原样转发)。 */
79
+ export interface ExecutionPlacementSeat {
80
+ /** 一次行读:行不在 / 委派分区行 ⇒ `undefined`;读故障抛(不吞成 absent)。 */
81
+ read(root: string): Promise<ExecutionPlacementReading | undefined>;
82
+ /** absent → writer(L)(非 device 车道;device 车道的首绑走设备店,同一只 SQL 语句模块)。传 device 车道 ⇒ 响亮抛(装配缝)。 */
83
+ place(root: string, occupant: SessionFace, cell: ExecutionWriterCell): Promise<PlaceOutcome>;
84
+ /** identical 臂补 mirror:WHERE 住客谓词 ∧ 不是 writer;写事务内先锁根行读认领(SQL),local 在这只会话的写锁里读同一个认领。 */
85
+ markMirror(root: string, occupant: SessionFace): Promise<MarkMirrorOutcome>;
86
+ }
87
+ /**
88
+ * 读不出的存档的故障词(闭集;一次性迁移 M32 的 local 腿点名用 —— 随 M32 · 窗口见 `plugins/one-time-migrations.ts` 登记表,窗关后随迁移码删):
89
+ * · `corrupt_session` —— 存档在,会话头读不出 / 截断;`invalid_session` —— 读得出而日志结构走不通(两个都是 core 的 `SessionError` 词,原样);
90
+ * · `storage` —— 读的时候抛了**别的**故障(I/O 级:权限 / 磁盘 I/O / 超大 —— core 的 `readJsonlRecords` 只吞 ENOENT,其余 errno 原样抛,不是 `SessionError`),
91
+ * 说不出在不在也说不出内容;{@link UnusableArchive.errno} 带那只故障的 `code`。
92
+ */
93
+ export type UnusableArchiveWord = "corrupt_session" | "invalid_session" | "storage";
94
+ /**
95
+ * local 的会话存档**读不出**(会话头读不出 / 日志结构走不通 / 读的时候抛了别的故障):盘上有这只会话的存档,只是读不出它 —— 与「不在」是两件事。
96
+ * (本型与 {@link isUnusableArchive} 随 M32 · 窗口见 `plugins/one-time-migrations.ts` 登记表。)
97
+ * local 店对存档的**唯一探针**答三态(不在 · 在而读得出 · 本型),要对存档动手的读者看到本型就把 {@link UnusableArchive.fault}(抛出来的那只故障,**原样**)上抛,
98
+ * 只问「有没有」的读者不上抛(见 `plugins/local-session-store.ts` 的 `probeArchive`)。
99
+ */
100
+ export interface UnusableArchive {
101
+ readonly state: "unusable";
102
+ readonly word: UnusableArchiveWord;
103
+ /** `word === "storage"` 且那只故障带字符串 `code` 时:该 `code`(`EACCES` / `EIO` / `ERR_STRING_TOO_LONG` ……);其余缺席。盘上路径与故障句不进这一格。 */
104
+ readonly errno?: string;
105
+ /** 探针抛出来的那只故障,原样(上抛的读者抛的就是它)。 */
106
+ readonly fault: unknown;
107
+ }
108
+ export declare function isUnusableArchive(x: object | undefined): x is UnusableArchive;
109
+ /**
110
+ * 一次性迁移 M32 **local 腿**的席(随 M32 · 窗口见 `plugins/one-time-migrations.ts` 登记表;只 local 有:SQL 的行就是会话,没有「会话元数据格缺 ∧ 存档读不出」这一形)。`read` 与 {@link ExecutionPlacementSeat.read} 是
111
+ * **同一读**(同一只实现的本体),只差一点:这只会话的存档读不出(格缺而探针读不出,或 fork 创建的格而探针读不出)⇒ **答 {@link UnusableArchive} 而不抛** —— 开机回填不动存档、
112
+ * 只补一格放置,读不出的会话由它点名跳过(该会话的请求照旧答存档的故障);席的 `read` 对同一形照旧抛。`place` 就是席的 `place`。
113
+ */
114
+ export interface LocalPlacementBackfillSeat extends Pick<ExecutionPlacementSeat, "place"> {
115
+ read(root: string): Promise<ExecutionPlacementReading | UnusableArchive | undefined>;
116
+ }
117
+ /** wire 投影(新读面 + 受理回声共用)。 */
118
+ export type ExecutionPlacementView = {
119
+ readonly role: "absent";
120
+ } | {
121
+ readonly role: "writer";
122
+ readonly lane: ExecutionLane;
123
+ readonly device?: {
124
+ readonly deviceId: string;
125
+ readonly rev: number;
126
+ };
127
+ } | {
128
+ readonly role: "mirror";
129
+ } | {
130
+ readonly role: "forking";
131
+ };
132
+ /** 格 → wire 投影(唯一一只;设备只投 id 与 rev,属主与时刻不上 wire)。 */
133
+ export declare function executionPlacementView(cell: ExecutionPlacementCell): ExecutionPlacementView;
134
+ /**
135
+ * 住客谓词的 JS 读法(SQL `incarnation = ? AND owner <=> ?` 的对位)= core 的行期望判官 `sessionRowMatches`(会话行格写原语锁内判的也是它,
136
+ * `session-row.ts` 的 `judgeSessionRowWrite`):属主与化身两样都没变才是判过的那一位 —— 同一化身的属主会变(local M10 认领、identical 重戳、
137
+ * SQL 导入提交点改属主)。两侧收「属主 + 化身」那一读:活住客(`SessionFace`)与店的头(`SessionHead`,门判过的那一读);调用方一侧恒是执行席的读数
138
+ * (住客经 `sessionFaceFromRow` 解码,令牌非空),所以「两侧都没有令牌」这一形在这里不可达。写口的判官与执行上下文解析器步 ① 的 `judged` 复核都经它。
139
+ */
140
+ export declare function sameOccupant(a: Pick<SessionHead, "owner" | "incarnation">, b: Pick<SessionHead, "owner" | "incarnation">): boolean;
141
+ /**
142
+ * 写口撞上本处写者 —— 三个导入写口(staged 提交点 / 整段 `importEntries` / `replaceEntries`)在写事务内(local:写锁内、写格之前)复读到 `writer`
143
+ * ⇒ 回滚、目的端零字节、抛它。具名类型:会话同步路由按类型把它答 409 `conflict.session_writer`;不是 core `SessionError`(那张闭集的 `conflict` 在导入面另有固定答复)。
144
+ */
145
+ export declare class SessionWriterPresentError extends Error {
146
+ readonly sessionId: string;
147
+ readonly lane: ExecutionLane;
148
+ readonly code = "conflict.session_writer";
149
+ constructor(sessionId: string, lane: ExecutionLane);
150
+ }
151
+ /**
152
+ * local 的 `forking` 中间态(fork 创建的格 —— 带 fork 键、执行位没人写过 —— 而存档不在)上的普通取用 —— 引擎 acquire 的缺档新建臂、登记、三个导入写口、执行席的写一律抛它;
153
+ * 只有同键的 `forkTo` 重试在两把写锁内补拷存档。具名类型:路由层按类型答 409 `conflict.fork_in_progress`。
154
+ */
155
+ export declare class SessionForkInProgressError extends Error {
156
+ readonly sessionId: string;
157
+ readonly code = "conflict.fork_in_progress";
158
+ constructor(sessionId: string);
159
+ }
160
+ /**
161
+ * 写事务内的认领读命中(读写序律,`plugins/execution-placement-sql.ts` 的 `lockRootAndReadClaim`):改根住客 / 放置的写者在自己的事务里先锁根
162
+ * `session_meta` 行、再锁定读 `task_active(root)`,读到认领 ⇒ 回滚、一个字节不写、抛它。只 SQL 后端(local 不立此律:没有 device 工厂)。
163
+ * 具名类型、**不带 wire 码**:各门按自己的既有码答(导入 409 `session_active`、删除 409 `conflict.session_active_run`),体带 `activeTaskId`。
164
+ */
165
+ export declare class SessionActiveRunError extends Error {
166
+ readonly sessionId: string;
167
+ readonly activeTaskId: string;
168
+ constructor(sessionId: string, activeTaskId: string);
169
+ }
170
+ /** 0 行分类的「这一格此刻可写」答案(只给锁内读写的实现用:local 与内存参照实现写它;SQL 在 0 行之后读到它 = 不变量破,响亮抛)。 */
171
+ export declare const WRITABLE: "writable";
172
+ /**
173
+ * place 的唯一判官(三店 + 内存参照实现同调):对一次按根的读数判「判过的住客 + 想要的格」此刻是什么结果。
174
+ * 行不在 / 住客不同 ⇒ `gone`;writer 同车道 ⇒ `same`;writer 异车道 ⇒ `tree_bound`;mirror ⇒ `mirror`;absent ⇒ {@link WRITABLE};forking ⇒ 抛 {@link SessionForkInProgressError}。
175
+ * 想要的格只认非 device 车道(device 首绑的判官是 {@link settledDeviceBindOutcome})。
176
+ */
177
+ export declare function settledPlaceOutcome(root: string, reading: ExecutionPlacementReading | undefined, occupant: SessionFace, wanted: ExecutionWriterCell): PlaceOutcome | typeof WRITABLE;
178
+ /** markMirror 的唯一判官(认领那一半在本判官之后,SQL 与 local 各在自己的写事务 / 写锁内判):行不在 / 住客不同 ⇒ `gone`;writer ⇒ `writer`;mirror ⇒ `mirrored`(幂等,零写);absent ⇒ {@link WRITABLE};forking ⇒ 抛。 */
179
+ export declare function settledMirrorOutcome(root: string, reading: ExecutionPlacementReading | undefined, occupant: SessionFace): MarkMirrorOutcome | typeof WRITABLE;
180
+ /** device 车道首绑(absent → writer(device, D) 或 writer(device, ∅) → writer(device, D))的结果(设备店的事务 / 内存参照实现同调)。 */
181
+ export type DeviceBindOutcome = {
182
+ readonly outcome: "bound";
183
+ readonly device: ExecutionDeviceCell;
184
+ } | {
185
+ readonly outcome: "same";
186
+ readonly device: ExecutionDeviceCell;
187
+ } | {
188
+ readonly outcome: "device_conflict";
189
+ readonly device: ExecutionDeviceCell;
190
+ } | {
191
+ readonly outcome: "tree_bound";
192
+ readonly boundLane: ExecutionLane;
193
+ } | {
194
+ readonly outcome: "mirror";
195
+ } | {
196
+ readonly outcome: "gone";
197
+ };
198
+ /**
199
+ * device 首绑的唯一判官:行不在 / 住客不同 ⇒ `gone`;absent 或 writer(device, ∅) ⇒ {@link WRITABLE};writer(device, D):D = 想要的设备 ⇒ `same`、否则 `device_conflict`;
200
+ * writer 异车道 ⇒ `tree_bound`;mirror ⇒ `mirror`;forking ⇒ 抛。
201
+ */
202
+ export declare function settledDeviceBindOutcome(root: string, reading: ExecutionPlacementReading | undefined, occupant: SessionFace, deviceId: string): Exclude<DeviceBindOutcome, {
203
+ outcome: "bound";
204
+ }> | typeof WRITABLE;
205
+ /**
206
+ * device 换绑 writer(device, D) → writer(device, D') 的结果(按住客 ∧ 两属主列 ∧ rev 定址;0 行按住客复读分 `rev_conflict` / `gone`)。
207
+ * `rebound` 带被换下的那台(`from`,审计行 `session_rebound` 的 detail 要它);`idempotent` = 目标就是格上那台且前态 rev 对得上 ⇒ 零写、rev 不加、不写审计。
208
+ */
209
+ export type DeviceRebindOutcome = {
210
+ readonly outcome: "rebound";
211
+ readonly device: ExecutionDeviceCell;
212
+ readonly from: string;
213
+ } | {
214
+ readonly outcome: "idempotent";
215
+ readonly device: ExecutionDeviceCell;
216
+ } | {
217
+ readonly outcome: "rev_conflict";
218
+ readonly device: ExecutionDeviceCell;
219
+ } | {
220
+ readonly outcome: "gone";
221
+ };
222
+ /**
223
+ * device 换绑的唯一判官(设备店的事务 / 内存参照实现同调,在锁住的那一读上判):行不在 / 住客不同 / 不是 writer(device, D) / 绑定属主不是判过的那一位 ⇒ `gone`;
224
+ * rev 不等 ⇒ `rev_conflict`;rev 对得上而目标就是格上那台 ⇒ `idempotent`(必须先于写判 —— `execution_rev + 1` 恒变列会把一次无操作也推进一格);否则 {@link WRITABLE}。
225
+ */
226
+ export declare function settledDeviceRebindOutcome(reading: ExecutionPlacementReading | undefined, occupant: SessionFace, owner: DeviceOwner, expectedRev: number, targetDeviceId: string): Exclude<DeviceRebindOutcome, {
227
+ outcome: "rebound";
228
+ }> | typeof WRITABLE;
229
+ /**
230
+ * 「这一格把这棵树放在哪台设备上」的**唯一读法**(准入链、device 工厂、换绑门与上面的换绑判官同调):writer(device, D) ⇒ D;其余一律 `undefined` ——
231
+ * absent(还没放)、mirror(写者在别处)、forking(local 中间态)、writer(device, ∅)(迁移回填的「有 run、设备未知」)、writer(别的车道)。
232
+ * 读者把 `undefined` 答成各自的「没有设备绑定」(`device.not_bound` / 设备 404);穷举 switch,新角色 = 编译红。
233
+ */
234
+ export declare function boundDeviceOf(cell: ExecutionPlacementCell): ExecutionDeviceCell | undefined;
235
+ /** seat.place 只认非 device 车道(device 首绑走设备店);传 device ⇒ 响亮抛 —— 这是装配缝,不是一个答案。 */
236
+ export declare function assertPlaceableLane(root: string, cell: ExecutionWriterCell): void;
237
+ /**
238
+ * **测试走廊的会话行**(语义参照实现,与 `InMemoryDeviceStore` 同一身份 —— 生产装配零引用,见头注):root → 住客 + 格。
239
+ * 只用 `Map` 会话店的测试走廊经 {@link seatRow} / {@link dropRow} 模拟会话行的生灭(= SQL 店的建行 / 删行),`InMemoryDeviceStore` 在它上面做设备首绑 / 换绑。
240
+ * 语义与 SQL 语句模块逐格同:同一组判官({@link settledPlaceOutcome} 等),写只按住客谓词。
241
+ */
242
+ export declare class InMemoryExecutionPlacementSeat implements ExecutionPlacementSeat {
243
+ private readonly rows;
244
+ /** 建行(会话店建行的对位):行不在 ⇒ 建一只 absent 行;在 ⇒ 不动(与 SQL `ON CONFLICT DO NOTHING` 同一条 first-write 律)。 */
245
+ seatRow(root: string, occupant: SessionFace): void;
246
+ /** 删行(删会话的对位):放置随行消失。 */
247
+ dropRow(root: string): void;
248
+ read(root: string): Promise<ExecutionPlacementReading | undefined>;
249
+ place(root: string, occupant: SessionFace, cell: ExecutionWriterCell): Promise<PlaceOutcome>;
250
+ markMirror(root: string, occupant: SessionFace): Promise<MarkMirrorOutcome>;
251
+ /** device 首绑(SQL `placeDeviceStatement` / `bindDeviceStatement` 的对位):absent 或 writer(device, ∅) ⇒ writer(device, D)、rev 0。 */
252
+ bindDevice(root: string, occupant: SessionFace, device: {
253
+ readonly id: string;
254
+ readonly owner: DeviceOwner;
255
+ }, placedAt: string): DeviceBindOutcome;
256
+ /** device 换绑(SQL `rebindDeviceStatement` 的对位):按住客 ∧ 两属主列 ∧ rev 定址,rev + 1;目标就是格上那台 ⇒ `idempotent` 零写。 */
257
+ rebindDevice(root: string, occupant: SessionFace, owner: DeviceOwner, expectedRev: number, targetDeviceId: string, placedAt: string): DeviceRebindOutcome;
258
+ }
259
+ //# sourceMappingURL=execution-placement.d.ts.map