@sema-agent/server 7.141.0 → 7.143.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 (341) hide show
  1. package/MIGRATION.md +657 -0
  2. package/USAGE.md +102 -30
  3. package/dist/adoption/plan.js +22 -0
  4. package/dist/approval-ask-audit-store.d.ts +61 -43
  5. package/dist/approval-ask-audit-store.js +69 -44
  6. package/dist/approval-ask-machine.d.ts +15 -7
  7. package/dist/approval-ask-machine.js +10 -4
  8. package/dist/approval-card.d.ts +126 -27
  9. package/dist/approval-card.js +103 -23
  10. package/dist/approval-crash-convergence.d.ts +17 -7
  11. package/dist/approval-crash-convergence.js +94 -22
  12. package/dist/approval-deny-reasons.d.ts +34 -30
  13. package/dist/approval-deny-reasons.js +21 -6
  14. package/dist/approval-policy-settlement.d.ts +1 -1
  15. package/dist/approval-queue-kind.d.ts +27 -0
  16. package/dist/approval-queue-kind.js +6 -0
  17. package/dist/approval-reconciler.d.ts +41 -9
  18. package/dist/approval-reconciler.js +45 -42
  19. package/dist/audit.js +4 -2
  20. package/dist/auth-bridge.d.ts +9 -6
  21. package/dist/auth-bridge.js +9 -3
  22. package/dist/boot/adoption.js +2 -1
  23. package/dist/boot/budget-tracing.d.ts +17 -1
  24. package/dist/boot/budget-tracing.js +10 -6
  25. package/dist/boot/device-lane.d.ts +1 -1
  26. package/dist/boot/execution-env.d.ts +24 -4
  27. package/dist/boot/execution-env.js +26 -23
  28. package/dist/boot/execution-lane.js +2 -1
  29. package/dist/boot/exit-boundary.js +5 -4
  30. package/dist/boot/governance-seams.d.ts +26 -41
  31. package/dist/boot/governance-seams.js +61 -28
  32. package/dist/boot/late-binds.d.ts +7 -4
  33. package/dist/boot/late-binds.js +3 -1
  34. package/dist/boot/leader.js +2 -4
  35. package/dist/boot/memory-consolidation.d.ts +0 -8
  36. package/dist/boot/memory-consolidation.js +5 -4
  37. package/dist/boot/parked-revive-gate.js +2 -1
  38. package/dist/boot/permission-rules-audit.d.ts +0 -28
  39. package/dist/boot/permission-rules-audit.js +2 -1
  40. package/dist/boot/read-deny-anchor-audit.js +2 -1
  41. package/dist/boot/reapers.js +9 -7
  42. package/dist/boot/resolve-spec.d.ts +1 -1
  43. package/dist/boot/resolve-spec.js +19 -10
  44. package/dist/boot/retention-lane.js +5 -3
  45. package/dist/boot/runner-deps.d.ts +10 -7
  46. package/dist/boot/runner-deps.js +20 -29
  47. package/dist/boot/runtime-caps.js +3 -2
  48. package/dist/boot/session-faces.js +4 -3
  49. package/dist/boot/shutdown.d.ts +9 -0
  50. package/dist/boot/shutdown.js +19 -4
  51. package/dist/boot/stage-02-web-admin-pull.js +3 -0
  52. package/dist/boot/stage-03-store-backend.d.ts +4 -1
  53. package/dist/boot/stage-03-store-backend.js +1 -2
  54. package/dist/boot/stage-04-stores.d.ts +5 -2
  55. package/dist/boot/stage-04-stores.js +6 -7
  56. package/dist/boot/stage-05-execution-env.d.ts +6 -3
  57. package/dist/boot/stage-05-execution-env.js +8 -6
  58. package/dist/boot/stage-06-runners.d.ts +6 -3
  59. package/dist/boot/stage-06-runners.js +3 -4
  60. package/dist/boot/stage-07-capability-layer.d.ts +6 -3
  61. package/dist/boot/stage-07-capability-layer.js +7 -2
  62. package/dist/boot/stage-08-reapers.d.ts +6 -3
  63. package/dist/boot/stage-08-reapers.js +3 -2
  64. package/dist/boot/stage-09-leader.d.ts +6 -3
  65. package/dist/boot/stage-10-http-server.d.ts +6 -3
  66. package/dist/boot/stage-10-http-server.js +14 -5
  67. package/dist/boot/stores.d.ts +5 -2
  68. package/dist/boot/stores.js +10 -9
  69. package/dist/boot/web-admin.d.ts +5 -1
  70. package/dist/boot/web-admin.js +48 -36
  71. package/dist/boot/webfetch-summarize-lane.js +2 -1
  72. package/dist/boot/workflow-orchestration.js +3 -2
  73. package/dist/capabilities/builtin-tools.d.ts +2 -1
  74. package/dist/capabilities/builtin-tools.js +2 -4
  75. package/dist/capabilities/composite-members.d.ts +2 -1
  76. package/dist/capabilities/composite-members.js +2 -1
  77. package/dist/capabilities/hands-lane.d.ts +9 -39
  78. package/dist/capabilities/hands-lane.js +8 -13
  79. package/dist/capabilities/mcp-probe.d.ts +3 -2
  80. package/dist/capabilities/scenarios.d.ts +11 -1
  81. package/dist/capabilities/scenarios.js +1 -0
  82. package/dist/capabilities/skills.d.ts +1 -17
  83. package/dist/capabilities/skills.js +0 -8
  84. package/dist/capability-facts.d.ts +31 -7
  85. package/dist/capability-facts.js +27 -4
  86. package/dist/card-void.d.ts +6 -9
  87. package/dist/config-catalog.js +2 -1
  88. package/dist/config-dir-trust.js +58 -29
  89. package/dist/config-invariants.d.ts +7 -1
  90. package/dist/config-invariants.js +7 -0
  91. package/dist/config-provider.d.ts +10 -0
  92. package/dist/config-provider.js +15 -4
  93. package/dist/config-root-migration.d.ts +13 -0
  94. package/dist/config-root-migration.js +99 -0
  95. package/dist/config-types.d.ts +19 -8
  96. package/dist/config.d.ts +10 -3
  97. package/dist/config.js +18 -4
  98. package/dist/delegated-session-owner.d.ts +5 -3
  99. package/dist/delegated-session-owner.js +1 -1
  100. package/dist/device-ws-protocol.d.ts +1 -0
  101. package/dist/device-ws-protocol.js +2 -1
  102. package/dist/env-model-table-gate.d.ts +53 -0
  103. package/dist/env-model-table-gate.js +53 -0
  104. package/dist/execution-lane-caps.d.ts +2 -1
  105. package/dist/fleet/fleet-bus.d.ts +34 -55
  106. package/dist/fleet/fleet-bus.js +35 -100
  107. package/dist/fleet/fleet-reconciler.d.ts +4 -4
  108. package/dist/fleet/fleet-reconciler.js +1 -1
  109. package/dist/fleet/subagent-tail-bus.d.ts +14 -11
  110. package/dist/fleet/subagent-tail-bus.js +15 -28
  111. package/dist/hooks/hook-runner.d.ts +6 -0
  112. package/dist/hooks/hook-runner.js +58 -8
  113. package/dist/host-decision.d.ts +54 -20
  114. package/dist/host-decision.js +42 -14
  115. package/dist/http/admission.d.ts +1 -1
  116. package/dist/http/admission.js +7 -3
  117. package/dist/http/approval-sweeps.js +11 -8
  118. package/dist/http/caller-trust.d.ts +1 -1
  119. package/dist/http/dispatch.js +4 -2
  120. package/dist/http/durable-run-leg.js +2 -2
  121. package/dist/http/engine-refusal.d.ts +16 -0
  122. package/dist/http/engine-refusal.js +17 -0
  123. package/dist/http/execution-context.d.ts +2 -2
  124. package/dist/http/idempotency.d.ts +1 -1
  125. package/dist/http/memory-compliance-failure-reply.d.ts +1 -3
  126. package/dist/http/memory-compliance-failure-reply.js +4 -6
  127. package/dist/http/owner-access.d.ts +11 -0
  128. package/dist/http/owner-access.js +13 -0
  129. package/dist/http/principal-gate.d.ts +3 -1
  130. package/dist/http/principal-gate.js +17 -8
  131. package/dist/http/resume-legs.d.ts +21 -7
  132. package/dist/http/resume-legs.js +76 -58
  133. package/dist/http/resume-restatement.d.ts +39 -0
  134. package/dist/http/resume-restatement.js +25 -0
  135. package/dist/http/route-ctx.d.ts +35 -10
  136. package/dist/http/route-ctx.js +1 -1
  137. package/dist/http/route-table.js +2 -0
  138. package/dist/http/routes/approvals-assistant.d.ts +9 -1
  139. package/dist/http/routes/approvals-assistant.js +142 -31
  140. package/dist/http/routes/attachments.js +18 -0
  141. package/dist/http/routes/capabilities.js +16 -4
  142. package/dist/http/routes/memory-policy.d.ts +2 -2
  143. package/dist/http/routes/memory-policy.js +27 -7
  144. package/dist/http/routes/notify-wake.js +23 -25
  145. package/dist/http/routes/rules.js +48 -13
  146. package/dist/http/routes/runs.js +12 -11
  147. package/dist/http/routes/session-memory-sync.d.ts +91 -0
  148. package/dist/http/routes/session-memory-sync.js +174 -0
  149. package/dist/http/routes/session-sync.js +13 -20
  150. package/dist/http/routes/sessions-list.d.ts +2 -1
  151. package/dist/http/routes/sessions.d.ts +2 -2
  152. package/dist/http/routes/sessions.js +22 -23
  153. package/dist/http/routes/tasks.js +34 -54
  154. package/dist/http/run-read-face.d.ts +7 -0
  155. package/dist/http/run-read-face.js +9 -3
  156. package/dist/http/send.d.ts +5 -0
  157. package/dist/http/send.js +9 -2
  158. package/dist/http/server.d.ts +27 -15
  159. package/dist/http/server.js +4 -6
  160. package/dist/http/stored-body-replay.js +15 -4
  161. package/dist/http/submit-replay.d.ts +4 -3
  162. package/dist/http/submit-replay.js +2 -2
  163. package/dist/http/sync-submit-body.d.ts +21 -4
  164. package/dist/http/sync-submit-body.js +13 -0
  165. package/dist/http/wire-types.d.ts +17 -7
  166. package/dist/identity-source.d.ts +21 -2
  167. package/dist/identity-source.js +6 -0
  168. package/dist/leader/leader.js +5 -2
  169. package/dist/leader/wire.d.ts +7 -1
  170. package/dist/leader/wire.js +6 -5
  171. package/dist/leg-facts.d.ts +37 -0
  172. package/dist/leg-facts.js +15 -0
  173. package/dist/memory-bundle-engine.d.ts +1 -4
  174. package/dist/memory-bundle-engine.js +2 -2
  175. package/dist/memory-incident-sink.d.ts +19 -6
  176. package/dist/memory-incident-sink.js +8 -4
  177. package/dist/memory-operator-faces.d.ts +30 -66
  178. package/dist/memory-operator-faces.js +17 -88
  179. package/dist/memory-scope-stores.d.ts +38 -40
  180. package/dist/memory-scope-stores.js +51 -36
  181. package/dist/memory-scope.d.ts +0 -9
  182. package/dist/memory-scope.js +0 -9
  183. package/dist/memory-sync-client.d.ts +21 -21
  184. package/dist/memory-sync-client.js +62 -43
  185. package/dist/memory-sync-identity.d.ts +24 -0
  186. package/dist/memory-sync-identity.js +57 -0
  187. package/dist/memory-sync.d.ts +27 -5
  188. package/dist/memory-sync.js +29 -7
  189. package/dist/model-plane-face.d.ts +13 -7
  190. package/dist/model-plane-face.js +2 -1
  191. package/dist/observability/fail-open.d.ts +47 -14
  192. package/dist/observability/fail-open.js +37 -11
  193. package/dist/observability/run-terminal.d.ts +92 -34
  194. package/dist/observability/run-terminal.js +171 -162
  195. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -19
  196. package/dist/orchestration/workflow-completion-inbox.js +0 -12
  197. package/dist/orchestration/workflow-notify-journal.d.ts +3 -2
  198. package/dist/orchestration/workflow-park-decision.d.ts +8 -3
  199. package/dist/orchestration/workflow-park-decision.js +5 -3
  200. package/dist/org-delivery-gate.d.ts +11 -0
  201. package/dist/org-delivery-gate.js +19 -1
  202. package/dist/parked-decide.d.ts +13 -9
  203. package/dist/parked-decide.js +7 -7
  204. package/dist/parked-revive-surface.js +1 -1
  205. package/dist/peer-inbound-face.d.ts +127 -0
  206. package/dist/peer-inbound-face.js +158 -0
  207. package/dist/permission-rule-list.d.ts +13 -9
  208. package/dist/permission-rule-list.js +7 -3
  209. package/dist/plugins/approval-ask-store-file.d.ts +14 -4
  210. package/dist/plugins/approval-ask-store-file.js +64 -11
  211. package/dist/plugins/approval-ask-store-memory.d.ts +2 -1
  212. package/dist/plugins/approval-ask-store-memory.js +17 -14
  213. package/dist/plugins/approval-ask-store-sql.d.ts +70 -23
  214. package/dist/plugins/approval-ask-store-sql.js +79 -30
  215. package/dist/plugins/checkpoint-store-sql.d.ts +4 -4
  216. package/dist/plugins/checkpoint-store-sql.js +9 -7
  217. package/dist/plugins/file-history-store-sql.d.ts +24 -18
  218. package/dist/plugins/file-history-store-sql.js +24 -19
  219. package/dist/plugins/file-run-store.d.ts +35 -1
  220. package/dist/plugins/file-run-store.js +108 -9
  221. package/dist/plugins/imported-layer-root-retirement.d.ts +82 -0
  222. package/dist/plugins/imported-layer-root-retirement.js +96 -0
  223. package/dist/plugins/local-checkpoint-store.d.ts +1 -1
  224. package/dist/plugins/local-checkpoint-store.js +2 -2
  225. package/dist/plugins/local-session-store.d.ts +42 -9
  226. package/dist/plugins/local-session-store.js +83 -33
  227. package/dist/plugins/local-task-attachment-store.d.ts +12 -2
  228. package/dist/plugins/local-task-attachment-store.js +67 -43
  229. package/dist/plugins/mailbox-recipient-lifecycle.d.ts +9 -31
  230. package/dist/plugins/mailbox-recipient-lifecycle.js +4 -0
  231. package/dist/plugins/mailbox-store-sql.d.ts +12 -8
  232. package/dist/plugins/mailbox-store-sql.js +76 -7
  233. package/dist/plugins/memory-key-guards.d.ts +3 -3
  234. package/dist/plugins/memory-run-store.d.ts +6 -1
  235. package/dist/plugins/memory-run-store.js +11 -1
  236. package/dist/plugins/memory-sync-store-pg.d.ts +15 -0
  237. package/dist/plugins/memory-sync-store-pg.js +21 -0
  238. package/dist/plugins/memory-sync-store-tidb.d.ts +2 -0
  239. package/dist/plugins/memory-sync-store-tidb.js +13 -1
  240. package/dist/plugins/one-time-migrations.d.ts +58 -2
  241. package/dist/plugins/one-time-migrations.js +161 -4
  242. package/dist/plugins/org-delivery-quota-store.d.ts +1 -1
  243. package/dist/plugins/org-delivery-quota-store.js +2 -12
  244. package/dist/plugins/park-commit-watch.d.ts +1 -1
  245. package/dist/plugins/permission-rule-store-file.d.ts +10 -1
  246. package/dist/plugins/permission-rule-store-file.js +15 -2
  247. package/dist/plugins/permission-rule-store-sql.d.ts +31 -1
  248. package/dist/plugins/permission-rule-store-sql.js +35 -2
  249. package/dist/plugins/remote-env-device.d.ts +8 -11
  250. package/dist/plugins/remote-env-device.js +6 -3
  251. package/dist/plugins/remote-env-e2b.d.ts +23 -4
  252. package/dist/plugins/remote-env-e2b.js +111 -28
  253. package/dist/plugins/remote-env-k8s.d.ts +61 -22
  254. package/dist/plugins/remote-env-k8s.js +133 -33
  255. package/dist/plugins/remote-env-local-docker.d.ts +15 -3
  256. package/dist/plugins/remote-env-local-docker.js +40 -12
  257. package/dist/plugins/retention-store-sql.d.ts +2 -2
  258. package/dist/plugins/run-store-sql.d.ts +9 -1
  259. package/dist/plugins/run-store-sql.js +9 -1
  260. package/dist/plugins/sandbox-read-floor.d.ts +5 -7
  261. package/dist/plugins/sandbox-read-floor.js +2 -7
  262. package/dist/plugins/sql-errors.d.ts +10 -1
  263. package/dist/plugins/sql-errors.js +13 -0
  264. package/dist/plugins/store-backend.d.ts +33 -35
  265. package/dist/plugins/store-backend.js +10 -1
  266. package/dist/plugins/store-contracts.d.ts +50 -1
  267. package/dist/plugins/store-contracts.js +6 -0
  268. package/dist/plugins/task-attachment-store.d.ts +112 -14
  269. package/dist/plugins/task-attachment-store.js +207 -29
  270. package/dist/plugins/tool-result-store-sql.d.ts +9 -8
  271. package/dist/plugins/tool-result-store-sql.js +23 -2
  272. package/dist/plugins/wiring-section-codec.d.ts +32 -0
  273. package/dist/plugins/wiring-section-codec.js +55 -0
  274. package/dist/plugins/workspace-fill.d.ts +60 -0
  275. package/dist/plugins/workspace-fill.js +106 -0
  276. package/dist/project-bundle.d.ts +38 -0
  277. package/dist/project-bundle.js +609 -0
  278. package/dist/rule-entry-list.d.ts +3 -2
  279. package/dist/rule-entry-list.js +5 -1
  280. package/dist/rules-consent.d.ts +28 -5
  281. package/dist/rules-consent.js +40 -28
  282. package/dist/run-local.js +11 -13
  283. package/dist/runs.d.ts +12 -8
  284. package/dist/runs.js +6 -8
  285. package/dist/runtime-governance.d.ts +1 -1
  286. package/dist/sandbox-egress-policy.d.ts +4 -1
  287. package/dist/sandbox-egress-policy.js +3 -1
  288. package/dist/sandbox-git-plane.d.ts +31 -12
  289. package/dist/sandbox-git-plane.js +24 -4
  290. package/dist/security.d.ts +9 -0
  291. package/dist/security.js +8 -0
  292. package/dist/server-secret-env.d.ts +4 -0
  293. package/dist/session-error-wire.d.ts +36 -0
  294. package/dist/session-error-wire.js +13 -0
  295. package/dist/session-incarnation.d.ts +1 -1
  296. package/dist/session-notify-door.d.ts +106 -0
  297. package/dist/session-notify-door.js +93 -0
  298. package/dist/session-sync.d.ts +9 -14
  299. package/dist/session-sync.js +5 -5
  300. package/dist/session-workspace.d.ts +3 -13
  301. package/dist/session-workspace.js +0 -9
  302. package/dist/standing-answer-grant.d.ts +5 -0
  303. package/dist/standing-answer-grant.js +10 -0
  304. package/dist/stores-sql/session-policy-store-sql.d.ts +4 -2
  305. package/dist/stores-sql/session-policy-store-sql.js +3 -13
  306. package/dist/task-mcp.d.ts +1 -1
  307. package/dist/task-settings.d.ts +21 -10
  308. package/dist/task-settings.js +46 -13
  309. package/dist/terminal.d.ts +39 -19
  310. package/dist/terminal.js +65 -22
  311. package/dist/tool-approval.d.ts +56 -37
  312. package/dist/tool-approval.js +78 -71
  313. package/dist/trace/core-keyset-guard.d.ts +134 -14
  314. package/dist/trace/engine-notice-wire.d.ts +9 -3
  315. package/dist/trace/engine-notice-wire.js +7 -3
  316. package/dist/trace/exit-seat.d.ts +9 -2
  317. package/dist/trace/exit-seat.js +1 -0
  318. package/dist/trace/ledger-sink.d.ts +10 -6
  319. package/dist/trace/ledger-sink.js +2 -2
  320. package/dist/trace/model-segment.d.ts +55 -25
  321. package/dist/trace/model-segment.js +101 -33
  322. package/dist/trace/project.d.ts +25 -5
  323. package/dist/trace/project.js +22 -0
  324. package/dist/trace/redact.d.ts +61 -9
  325. package/dist/trace/redact.js +1490 -468
  326. package/dist/trace/self-minted-wire.d.ts +745 -24
  327. package/dist/trace/self-minted-wire.js +239 -9
  328. package/dist/trace/wire-projection-faces.d.ts +2 -2
  329. package/dist/trace/wire-projection-faces.js +2 -2
  330. package/dist/verifier-pause-card.d.ts +33 -5
  331. package/dist/verifier-pause-card.js +15 -10
  332. package/dist/web-admin-client/apply-effective.d.ts +12 -16
  333. package/dist/web-admin-client/apply-effective.js +109 -9
  334. package/dist/web-admin-client/effective-keys.js +2 -0
  335. package/dist/web-admin-client/types.d.ts +4 -0
  336. package/dist/workspace-space.d.ts +19 -3
  337. package/dist/workspace-space.js +8 -4
  338. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -1
  339. package/package.json +3 -2
  340. package/wire-changes/server@7.142.0.json +639 -0
  341. package/wire-changes/server@7.143.0.json +1167 -0
@@ -9,7 +9,7 @@ import { ensureIndexes } from "./ensure-index.js";
9
9
  import { createObjectColumnReader, decodeJsonColumn, decodeNullableJsonColumn, isJsonColumnBodyProblem, jsonColumnText, jsonColumnTextExpr, readNonNullJsonValueColumn, readOpaqueJsonValueColumn, } from "./sql-json-column.js";
10
10
  import { incarnationFromCell, sessionFaceFromRow } from "../session-incarnation.js";
11
11
  import { RUN_HOLDER_DEATH_CODES, RUN_ORPHANED_ERROR, RUN_STALLED_ERROR, StoreBootRefusalError } from "./store-contracts.js";
12
- import { PERMISSION_RULE_TABLE, purgeUndurableScopeRows } from "./permission-rule-store-sql.js";
12
+ import { PERMISSION_RULE_TABLE, SqlDurableRulePartitionProvider, isSqlBucketRefusal, isSqlBucketVanished, listRuleBucketOwners, purgeUndurableScopeRows } from "./permission-rule-store-sql.js";
13
13
  import { SESSION_RULES_COLUMN } from "./session-rules-column.js";
14
14
  import { readJsonObjectColumn } from "./sql-json-column.js";
15
15
  import { retireStoredCardWords } from "../retired-card-words.js";
@@ -17,7 +17,8 @@ import { redactSecrets } from "../trace/redact.js";
17
17
  import { CLAIM_OWNERLESS_PLACED_SQL, claimOwnerlessParams, isOrphanSessionOwner, judgeDelegatedOwner } from "../delegated-session-owner.js";
18
18
  import { MAILBOX_KEYED_TABLES, mailboxKeyDeleteSql, storedMailboxKeyless } from "./mailbox-store-sql.js";
19
19
  import { SESSION_META_PLACEMENT_COLUMNS, backfillWriterStatement, countOrphanBindingsStatement, migrateDeviceBindingsStatement } from "./execution-placement-sql.js";
20
- import { APPROVAL_ASK_EXECUTION_LANE_COLUMN, APPROVAL_ASK_TABLE } from "./approval-ask-store-sql.js";
20
+ import { APPROVAL_ASK_ENDING_CAUSE_COLUMN, APPROVAL_ASK_EXECUTION_LANE_COLUMN, APPROVAL_ASK_PARK_TRIGGER_COLUMN, APPROVAL_ASK_TABLE } from "./approval-ask-store-sql.js";
21
+ import { retireImportedLayerRootAllows } from "./imported-layer-root-retirement.js";
21
22
  import { isUnusableArchive } from "../execution-placement.js";
22
23
  const RETIRED_SESSION_RULE_BUCKETS = ["commandAllow", "commandDeny"];
23
24
  export function buildMigrationReceipts() {
@@ -60,6 +61,11 @@ export function buildMigrationReceipts() {
60
61
  localArchivesKeptStamped: [],
61
62
  localArchivesUnreadable: [],
62
63
  localArchiveSnapshotsRecovered: 0,
64
+ askCauseColumnsChanged: 0,
65
+ askParkTriggersMoved: 0,
66
+ askJournalRecordsUpgraded: 0,
67
+ importedLayerRootAllowsRetired: [],
68
+ importedLayerRootBucketsSkipped: [],
63
69
  };
64
70
  }
65
71
  function completedReport(r) {
@@ -110,7 +116,12 @@ function changedAnything(r) {
110
116
  r.localArchivesAnonymized > 0 ||
111
117
  r.localArchivesKeptStamped.length > 0 ||
112
118
  r.localArchivesUnreadable.length > 0 ||
113
- r.localArchiveSnapshotsRecovered > 0);
119
+ r.localArchiveSnapshotsRecovered > 0 ||
120
+ r.askCauseColumnsChanged > 0 ||
121
+ r.askParkTriggersMoved > 0 ||
122
+ r.askJournalRecordsUpgraded > 0 ||
123
+ r.importedLayerRootAllowsRetired.length > 0 ||
124
+ r.importedLayerRootBucketsSkipped.length > 0);
114
125
  }
115
126
  function readingsSnapshot(r) {
116
127
  return {
@@ -125,6 +136,8 @@ function readingsSnapshot(r) {
125
136
  executionPlacementSessionsUnreadable: [...r.executionPlacementSessionsUnreadable],
126
137
  localArchivesKeptStamped: [...r.localArchivesKeptStamped],
127
138
  localArchivesUnreadable: [...r.localArchivesUnreadable],
139
+ importedLayerRootAllowsRetired: [...r.importedLayerRootAllowsRetired],
140
+ importedLayerRootBucketsSkipped: [...r.importedLayerRootBucketsSkipped],
128
141
  };
129
142
  }
130
143
  function receiptSentence(r) {
@@ -135,6 +148,7 @@ function receiptSentence(r) {
135
148
  ...r.undurableScopeRuleRows.map((x) => `removed ${x.kind} in permission-rule bucket ${x.ownerKey} (${x.behavior} ${x.tool}, scope root ${JSON.stringify(x.scopeRoot)})`),
136
149
  ...r.sessionRuleRowsWithOldGrammarEntries.map((x) => `${x.policyKey} (session ${x.sessionId}): toolAllowDropped=${JSON.stringify(x.toolAllowDropped)}`),
137
150
  ...r.sessionRuleRowsWithUnreadableDeny.map((x) => `${x.policyKey} (session ${x.sessionId}): left as stored, toolDenyUnreadable=${JSON.stringify(x.toolDenyUnreadable)}${x.retiredBuckets !== undefined ? `, retiredBuckets=${JSON.stringify(x.retiredBuckets)}` : ""}`),
151
+ ...r.importedLayerRootAllowsRetired.map((x) => `retired ${x.addsRetired} imported add(s) of an allow ${x.tool} rule in permission-rule bucket ${x.ownerKey} (${x.stillLive ? "the rule stays in effect through another approval" : "the rule no longer takes effect"})`),
138
152
  ];
139
153
  return [counts.join(", "), ...rows].filter((part) => part !== "").join("; ");
140
154
  }
@@ -561,6 +575,63 @@ export const ONE_TIME_MIGRATIONS = [
561
575
  "读 / 续跑 / 拉取照常 —— 出路 = 删掉会话后重推,或升级时先过一次带本条的版本并在窗内起服;③ 行上没有建档时刻的格(同一类数据根)⇒ 列表 / 会话视图 / 交出的元数据读纪元(F 类 tag 计数)",
562
576
  reportKeys: ["localArchiveRowFactsBackfilled", "localArchivesAnonymized", "localArchivesKeptStamped", "localArchivesUnreadable", "localArchiveSnapshotsRecovered"],
563
577
  },
578
+ {
579
+ id: "M34",
580
+ introducedIn: "7.142.0",
581
+ releasedOn: "2026-10-30",
582
+ removableAfter: "2026-11-29",
583
+ removal: "删 migrateStepsOn 的 M34 步(`splitApprovalAskCauseColumns`、`RETIRED_ASK_CAUSE_COLUMN`、`renameAskCauseColumnSql`)与本文件的 `askCauseColumnsChanged` / `askParkTriggersMoved` / `askJournalRecordsUpgraded`" +
584
+ "(报告 / 收据 / `changedAnything` / `readingFields` / `NO_SQL_MIGRATIONS` 与读数行 note 里的 M34 句、头注 M34 段);`approval-ask-store-file.ts` 的 v1 读侧半场 " +
585
+ "(`translateJournalV1Record`、`JOURNAL_V1_ENVELOPE`、`V1_ENDING_STATES` / `V1_TRIGGER_STATES`、`replayRecord` 里认 v1 的那两行与 `journalV1Records`)与 local 腿 `upgradeJournalEnvelope`、" +
586
+ "`store-backend.ts` 的 `LocalBackend.runOneTimeMigrations` 里报 `askJournalRecordsUpgraded` 的那一行;`test/approval-ask-store-db-integration.test.ts` 的 M34 组与 " +
587
+ "`test/approval-ask-cause-columns.test.ts` 里标题含 ⑤ 与 local 腿读数的格。**不随本条删**:两列与它们的列定义(`APPROVAL_ASK_ENDING_CAUSE_COLUMN` / `APPROVAL_ASK_PARK_TRIGGER_COLUMN`)、" +
588
+ "两只读口(`askRowEndingCause` / `askRowParkTrigger`)、信封号 2 与「认不出的信封号 ⇒ 拒启」那一臂(常设的数据形)",
589
+ afterRemoval: "两只常设臂接住:① SQL —— 删码同版把 `approval_ask.denied_reason` 加进退役 schema 判据(`schema-consistency.ts` 的跳版库探针,先于建表)⇒ 跳过了带本条的版本直接升的库响亮拒启," +
590
+ "出路 = 先在带本条的版本起服一次让迁移走完,再升(列册对账见幽灵列 / 缺列同样拒启,但它的拒句给的是加列,不是这一出路 —— 所以退役判据要在删码同版加上);" +
591
+ "② local —— 信封号 1 的日志记录走 File 问账店「认不出的信封号」拒启(`unknown envelope version`,点名文件与出路:用写下它的那一版起服一次,或移开文件)",
592
+ reportKeys: ["askCauseColumnsChanged", "askParkTriggersMoved", "askJournalRecordsUpgraded"],
593
+ },
594
+ {
595
+ id: "M35",
596
+ introducedIn: "7.143.0",
597
+ releasedOn: "2026-10-31",
598
+ removableAfter: "2026-11-30",
599
+ removal: "删 `config-root-migration.ts` 整只文件与两个入口的调用(`boot/stage-02-web-admin-pull.ts` 目录信任判官之后、`run-local.ts` 同一位置各两行 + import);" +
600
+ "`test/env-model-table-gate.test.ts` 里标题以 `迁移` 起头的那一组;部署文档里「起服自动铸 config.env-models.json」那一句。" +
601
+ "**不随本条删**:模型面闸 `env-model-table-gate.ts`、`LocalConfigProvider` 读决定(etag 覆盖)、目录信任判官的根级文档名单(常设)",
602
+ afterRemoval: "常设的模型面闸接住:跳过了带本条的版本直接升的本地根(或运行记录在场而决定缺席的根),决定缺席读作 kept ⇒ env 模型表已声明时管模型面的本地目录被组拒 " +
603
+ "`config.catalog_replaces_env_models`,拒句点名被顶替的 env 模型与两条出路(在配置根写 `config.env-models.json` = `{\"envModels\":\"replaced\"}`,或撤掉 MODEL_ID / MODEL_CHEAP_ID)",
604
+ reportKeys: [],
605
+ },
606
+ {
607
+ id: "M36",
608
+ introducedIn: "7.143.0",
609
+ releasedOn: "2026-10-31",
610
+ removableAfter: "2026-11-30",
611
+ removal: "**删码前提**:本服务已不支持回滚到引擎 7.61.0 之前的版本(更早的引擎的导入门会重新铸出这种 add,本条要在每次起服把它们收掉)—— 窗关时仍支持这种回滚 ⇒ 本行两格顺延(改日期,并在本注里写明为什么),不删。" +
612
+ "删什么:`src/plugins/imported-layer-root-retirement.ts` 整个文件;migrateStepsOn 的 M36 步(规则表与 `grammar` 列的在场探针 + 对 `retireImportedLayerRootAllows` 的调用)与握锁连接上的 `query` 口" +
613
+ "(`LockedMigrationDb` 与 `mysqlDb` / `pgDb` 里的那一员,若届时再无别的步骤用它);`permission-rule-store-sql.ts` 的 `listRuleBucketOwners` / `isSqlBucketRefusal` / `isSqlBucketVanished` / `RuleBucketVanishedError`(取点那一句的具名类,届时若再无别的读者按类判它,收回成裸错)/ `IMPORT_RECORD_KIND`;" +
614
+ "`permission-rule-store-file.ts` 的 `FileRuleApprovalRecordStore.importRecordOwners`;`store-backend.ts` 的 `LocalBackend.runOneTimeMigrations` 里 M36 那一腿(规则目录在场判与那一处调用)与报告两键的那两行;" +
615
+ "本文件的 `createSqlImportedLayerRootSource` 与 `importedLayerRootAllowsRetired` / `importedLayerRootBucketsSkipped`(报告 / 收据 / `changedAnything` / `readingsSnapshot` / `readingFields` / `receiptSentence` / `NO_SQL_MIGRATIONS` 的排除表)、" +
616
+ "`imported_layer_root_allow_retired` / `imported_layer_root_bucket_skipped` 两行 warn 与 `importedLayerRootSkipNote`、读数行 note 里的 M36 句与头注 M36 段;" +
617
+ "`test/imported-layer-root-retirement.test.ts`、`test/imported-layer-root-retirement-db-integration.test.ts`(连同 `scripts/db-integration-files.json` 里它那一行)与 `test/helpers/imported-layer-root-fixture.ts` 整文件。" +
618
+ "**不随本条删**:`SqlDurableRulePartitionProvider` 收「方言 + 执行口」的那一形(`BucketSql`;常设,池照旧满足它)",
619
+ afterRemoval: "没有响亮臂(如实):删码之后,跳过了带本条的版本直接升的库(或回滚到引擎 7.61.0 之前、在其上又导入过的库),存量的这种全局 `/x` allow 照旧在每个项目根放行,本服务不再点名 —— " +
620
+ "出路 = 升级时先过一次带本条的版本并起服;或运维列出桶里出处为导入(`adds[].origin` = `imported-cc`)、scope 为全局、从 `/` 写起的 allow(`GET /v1/rules`)并撤掉(`DELETE /v1/rules`,撤的是这条规则的全部批准)",
621
+ reportKeys: ["importedLayerRootAllowsRetired", "importedLayerRootBucketsSkipped"],
622
+ },
623
+ {
624
+ id: "M37",
625
+ introducedIn: "7.143.0",
626
+ releasedOn: "2026-10-31",
627
+ removableAfter: "2026-11-30",
628
+ removal: "删 `approval-card.ts` 的 `renameRetiredSourceKey` 与 `readStoredCardEnvelope` 里调它的那一步(回到直接把传入的那一只交 M18 改写)、" +
629
+ "fail-open tag `server.approval-card.retired-card-key-read`(`observability/fail-open.ts` 与 `docs/FAIL-OPEN-CENSUS.md` 那一行)、" +
630
+ "`test/approval-card-source-key-m37.test.ts` 整只文件、本文件头注 M37 段",
631
+ afterRemoval: "既有坏行臂接住(响亮,不再改名):仍带旧键的存卡(跳过了带本条的版本直接升的库、窗关之后还在写的 7.142.x 副本)整卡读不出 —— 断连重放跳过 + 一次 warn、" +
632
+ "幂等重入走 park、回决口按要人",
633
+ reportKeys: [],
634
+ },
564
635
  ];
565
636
  export function migrationWindowOpen(window, today) {
566
637
  return window.removableAfter === null || today <= window.removableAfter;
@@ -606,9 +677,15 @@ export const NO_SQL_MIGRATIONS = {
606
677
  deviceBindingsMoved: 0,
607
678
  deviceBindingsOrphaned: 0,
608
679
  deviceBindingTablesDropped: 0,
680
+ askCauseColumnsChanged: 0,
681
+ askParkTriggersMoved: 0,
609
682
  };
610
683
  function mysqlDb(conn) {
611
684
  return {
685
+ async query(sql, params) {
686
+ const [r] = await conn.query(sql, params === undefined ? [] : [...params]);
687
+ return Array.isArray(r) ? { rows: r, affected: 0 } : { rows: [], affected: Number(r.affectedRows ?? 0) };
688
+ },
612
689
  async rows(sql, params) {
613
690
  const [rows] = params === undefined ? await conn.query(sql) : await conn.query(sql, [...params]);
614
691
  return rows;
@@ -621,6 +698,10 @@ function mysqlDb(conn) {
621
698
  }
622
699
  function pgDb(client) {
623
700
  return {
701
+ async query(sql, params) {
702
+ const r = await client.query(sql, params === undefined ? [] : [...params]);
703
+ return { rows: r.rows, affected: r.rowCount ?? 0 };
704
+ },
624
705
  async rows(sql, params) {
625
706
  const r = params === undefined ? await client.query(sql) : await client.query(sql, [...params]);
626
707
  return r.rows;
@@ -651,6 +732,9 @@ async function columnExists(db, dialect, table, column) {
651
732
  const sql = `SELECT 1 AS present FROM information_schema.columns WHERE table_schema = ${scope} AND table_name = '${table}' AND column_name = '${column}'`;
652
733
  return (await db.rows(sql)).length > 0;
653
734
  }
735
+ export function createSqlImportedLayerRootSource(exec) {
736
+ return { candidateOwners: () => listRuleBucketOwners(exec), durable: new SqlDurableRulePartitionProvider(exec), isWholeBucketRefusal: isSqlBucketRefusal, isVanishedBucket: isSqlBucketVanished };
737
+ }
654
738
  export async function runSqlOneTimeMigrations(dialect, pool, input) {
655
739
  return dialect === "mysql" ? withMysqlLock(pool, (db) => migrateOn(db, "mysql", input)) : withPgLock(pool, (db) => migrateOn(db, "pg", input));
656
740
  }
@@ -701,6 +785,7 @@ async function migrateStepsOn(db, dialect, receipts, input) {
701
785
  : "UPDATE task_run SET error_code = $1 WHERE status = 'failed' AND error_code IS NULL AND (error = $2 OR error LIKE $3)", [RUN_HOLDER_DEATH_CODES.orphaned, RUN_ORPHANED_ERROR, `${RUN_ORPHANED_ERROR} (%`]);
702
786
  await migrateShadowedRuleKey(db, dialect, receipts);
703
787
  await migrateRetiredCardWords(db, dialect, receipts);
788
+ await splitApprovalAskCauseColumns(db, dialect, receipts);
704
789
  await reshapeWorkflowParkIndex(db, dialect, receipts);
705
790
  await migrateSessionRuleGrammar(db, dialect, receipts);
706
791
  await repairParkedRows(db, dialect, receipts);
@@ -709,12 +794,37 @@ async function migrateStepsOn(db, dialect, receipts, input) {
709
794
  await inheritPlacedSessionOwners(db, dialect, receipts);
710
795
  if ((await tableExists(db, dialect, PERMISSION_RULE_TABLE)) && (await columnExists(db, dialect, PERMISSION_RULE_TABLE, "grammar")))
711
796
  await purgeUndurableScopeRows(db, dialect, receipts);
797
+ if ((await tableExists(db, dialect, PERMISSION_RULE_TABLE)) && (await columnExists(db, dialect, PERMISSION_RULE_TABLE, "grammar"))) {
798
+ await retireImportedLayerRootAllows(createSqlImportedLayerRootSource({ dialect, query: (sql, params) => db.query(sql, params) }), receipts);
799
+ }
712
800
  await moveWorkflowJournalScope(db, dialect, receipts);
713
801
  if (await tableExists(db, dialect, RETIRED_BREAKER_TABLE))
714
802
  await dropRetiredBreakerTable(db, dialect, receipts);
715
803
  await dropRejectedMailboxKeys(db, dialect, receipts);
716
804
  await switchExecutionPlacement(db, dialect, receipts, input);
717
805
  }
806
+ const RETIRED_ASK_CAUSE_COLUMN = "denied_reason";
807
+ function renameAskCauseColumnSql(dialect) {
808
+ return dialect === "mysql"
809
+ ? `ALTER TABLE ${APPROVAL_ASK_TABLE} CHANGE COLUMN ${RETIRED_ASK_CAUSE_COLUMN} ${APPROVAL_ASK_ENDING_CAUSE_COLUMN.mysql}`
810
+ : `ALTER TABLE ${APPROVAL_ASK_TABLE} RENAME COLUMN ${RETIRED_ASK_CAUSE_COLUMN} TO ${APPROVAL_ASK_ENDING_CAUSE_COLUMN.name}`;
811
+ }
812
+ export async function splitApprovalAskCauseColumns(db, dialect, receipts) {
813
+ if (!(await tableExists(db, dialect, APPROVAL_ASK_TABLE)))
814
+ return;
815
+ const has = (column) => columnExists(db, dialect, APPROVAL_ASK_TABLE, column);
816
+ if (await has(RETIRED_ASK_CAUSE_COLUMN)) {
817
+ await execUntil(db, renameAskCauseColumnSql(dialect), async () => !(await has(RETIRED_ASK_CAUSE_COLUMN)) && (await has(APPROVAL_ASK_ENDING_CAUSE_COLUMN.name)));
818
+ receipts.askCauseColumnsChanged += 1;
819
+ }
820
+ if (!(await has(APPROVAL_ASK_PARK_TRIGGER_COLUMN.name))) {
821
+ await execUntil(db, `ALTER TABLE ${APPROVAL_ASK_TABLE} ADD COLUMN ${APPROVAL_ASK_PARK_TRIGGER_COLUMN[dialect]}`, () => has(APPROVAL_ASK_PARK_TRIGGER_COLUMN.name));
822
+ receipts.askCauseColumnsChanged += 1;
823
+ }
824
+ receipts.askParkTriggersMoved += await db.exec(`UPDATE ${APPROVAL_ASK_TABLE} SET ${APPROVAL_ASK_PARK_TRIGGER_COLUMN.name} = ${APPROVAL_ASK_ENDING_CAUSE_COLUMN.name} ` +
825
+ `WHERE state IN ('PARKING', 'PARKED') AND ${APPROVAL_ASK_PARK_TRIGGER_COLUMN.name} IS NULL AND ${APPROVAL_ASK_ENDING_CAUSE_COLUMN.name} IS NOT NULL`);
826
+ await db.exec(`UPDATE ${APPROVAL_ASK_TABLE} SET ${APPROVAL_ASK_ENDING_CAUSE_COLUMN.name} = NULL WHERE state IN ('PARKING', 'PARKED') AND ${APPROVAL_ASK_ENDING_CAUSE_COLUMN.name} IS NOT NULL`);
827
+ }
718
828
  const RETIRED_DEVICE_BINDING_TABLE = "device_session";
719
829
  const PARKED_DEVICE_BINDING_TABLE = "device_session_retired";
720
830
  const BINDING_COLUMNS = "root_session_id, device_id, owner_tenant, owner_subject, bound_at, rev";
@@ -1637,6 +1747,11 @@ function readingFields(r) {
1637
1747
  localArchivesKeptStamped: r.localArchivesKeptStamped.length,
1638
1748
  localArchivesUnreadable: r.localArchivesUnreadable.length,
1639
1749
  localArchiveSnapshotsRecovered: r.localArchiveSnapshotsRecovered,
1750
+ askCauseColumnsChanged: r.askCauseColumnsChanged,
1751
+ askParkTriggersMoved: r.askParkTriggersMoved,
1752
+ askJournalRecordsUpgraded: r.askJournalRecordsUpgraded,
1753
+ importedLayerRootAllowsRetired: r.importedLayerRootAllowsRetired.length,
1754
+ importedLayerRootBucketsSkipped: r.importedLayerRootBucketsSkipped.length,
1640
1755
  };
1641
1756
  }
1642
1757
  export function logOneTimeMigrations(log, backend, report) {
@@ -1690,7 +1805,15 @@ export function logOneTimeMigrations(log, backend, report) {
1690
1805
  "that still carried an importer's owner stamp were rewritten as anonymous with every entry kept as it was (a snapshot of the log is written before each rewrite " +
1691
1806
  "and removed after it; an interrupted rewrite is finished from the snapshot on the next boot); an archive whose log the import check refuses, whose re-import would " +
1692
1807
  "change its content, or whose read skipped unreadable records is left byte for byte as it was and named by a local_archive_kept_stamped line, and a session whose " +
1693
- "archive header cannot be read is named by a local_archive_unreadable line). Each step " +
1808
+ "archive header cannot be read is named by a local_archive_unreadable line; " +
1809
+ "M34: the approval ask table's single cause column denied_reason — the reason a row was moved to the delivery surface while it is PARKING or PARKED, " +
1810
+ "the reason it ended without a human answer while it is VOID or DENIED, overwritten by the second when a parked row was closed — renamed ending_cause, " +
1811
+ "a column park_trigger added, and the word on PARKING and PARKED rows moved into park_trigger; on the local backend the approval ask journal is rewritten " +
1812
+ "once with record envelope 2, its envelope-1 records read with the same meaning; " +
1813
+ "M36: allow approvals that an import before engine 7.61.0 stored as global rules from a settings file's user-layer path written from `/` — which the user layer reads at its " +
1814
+ "configuration directory and a global rule reads at every task's project root — retired by tombstones naming exactly those approvals (a rule that also holds an approval a person " +
1815
+ "gave directly stays in effect; deny and ask rules are never retired), one imported_layer_root_allow_retired line per rule; a bucket that could not be read or written, or that another replica deleted while it was being written, is named by an " +
1816
+ "imported_layer_root_bucket_skipped line on every boot). Each step " +
1694
1817
  "runs on every boot until its window closes (the release that introduced it + 30 days; the per-step date is in " +
1695
1818
  "removableAfter, null while that release is not published yet) and is deleted in the first minor after that — start a " +
1696
1819
  "version that still carries a step at least once before upgrading past its deletion.",
@@ -1793,6 +1916,23 @@ function logReceiptRows(log, backend, r) {
1793
1916
  note: removedRuleRowNote(row.kind),
1794
1917
  });
1795
1918
  }
1919
+ for (const row of r.importedLayerRootAllowsRetired) {
1920
+ log.warn("imported_layer_root_allow_retired", {
1921
+ backend,
1922
+ ownerKey: row.ownerKey,
1923
+ tool: row.tool,
1924
+ addsRetired: row.addsRetired,
1925
+ stillLive: row.stillLive,
1926
+ note: "an allow rule imported from a settings file's user layer by an import before engine 7.61.0 was written from `/` — the user layer reads such a path at its own configuration " +
1927
+ "directory — and was stored as a global rule, which reads it at every task's project root: it let that path be acted on in every project without a question. A one-time migration " +
1928
+ "retired the imported approval of it (a tombstone naming exactly that approval). stillLive false = the rule no longer takes effect and an action it covered asks again; stillLive true = " +
1929
+ "the rule stays in effect through another approval of it (one a person gave directly). To carry the rule again, spell its directory out (`//<directory>/…`, or `~/<path>` under the home) " +
1930
+ "and approve it again. This line is written once, by the boot that retired it — keep it; the rule text is not on it (list the bucket's rules to see what remains)",
1931
+ });
1932
+ }
1933
+ for (const bucket of r.importedLayerRootBucketsSkipped) {
1934
+ log.warn("imported_layer_root_bucket_skipped", { backend, ownerKey: bucket.ownerKey, reason: bucket.reason, why: bucket.why, note: importedLayerRootSkipNote(bucket.reason) });
1935
+ }
1796
1936
  for (const bucket of r.undurableScopeRuleBucketsUnreadable) {
1797
1937
  log.warn("permission_rule_bucket_unreadable", {
1798
1938
  backend,
@@ -1871,6 +2011,23 @@ export async function runAndLogOneTimeMigrations(log, backend) {
1871
2011
  }
1872
2012
  logOneTimeMigrations(log, backend.kind, report);
1873
2013
  }
2014
+ function importedLayerRootSkipNote(reason) {
2015
+ const head = "the one-time retirement of allow approvals that an import before engine 7.61.0 stored as global rules from a user-layer path written from `/` (read at every project root) did not finish in this permission-rule bucket: ";
2016
+ switch (reason) {
2017
+ case "bucket_unreadable":
2018
+ return `${head}the bucket could not be read. The read path refuses such a bucket in whole as well, so none of its rules takes effect; once it is repaired, the next boot retires what it selects. Named on every boot until then`;
2019
+ case "vanished":
2020
+ return `${head}the bucket was deleted (by another replica) while this step was writing to it. Nothing is left to retire and none of its rules takes effect; not a boot refusal`;
2021
+ case "not_writable":
2022
+ return `${head}it holds approvals the retirement selects, but no tombstone could be written (the bucket is served read-only — a rule-store adoption in progress — or the store refused the tombstone), so those allow rules keep taking effect at every project root. Finish the adoption or repair the store and restart; named on every boot until then`;
2023
+ case "contended":
2024
+ return `${head}every write lost to a concurrent writer. Those allow rules keep taking effect until a later boot retires them; named on every boot until then`;
2025
+ default: {
2026
+ const unknown = reason;
2027
+ throw new Error(`M36: no log sentence for skip reason ${JSON.stringify(unknown)}`);
2028
+ }
2029
+ }
2030
+ }
1874
2031
  function removedRuleRowNote(kind) {
1875
2032
  switch (kind) {
1876
2033
  case "rule":
@@ -101,7 +101,7 @@ export declare class SqlOrgDeliveryQuotaStore implements OrgDeliveryQuotaStore {
101
101
  take(fromScope: string, toScope: string, policy: OrgDeliveryQuotaPolicy): Promise<boolean>;
102
102
  /**
103
103
  * 一个记账事务:库钟先读(池上一条值语句,不在事务里留普通 SELECT),判过 ⇒ 提交,判拒 ⇒ 回滚(零残留)。
104
- * 🔴 **引擎判了死锁 ⇒ 整事务重来**(有界,`isDeadlockError` 是全仓唯一的可重试锁故障判据):被挑中的一方已整事务回滚、锁全放,
104
+ * 🔴 **引擎判了死锁 ⇒ 整事务重来**(`retryOnDeadlock`:全仓唯一的重来写法、唯一的上界与判据):被挑中的一方已整事务回滚、锁全放,
105
105
  * 重来时读-判-写全部重做,语义等价。真 InnoDB(REPEATABLE READ)上这条路是真的:窗前进时按收件人前缀删旧对偶行要取范围 / 间隙锁,
106
106
  * 两位相邻收件人同时翻窗、各自往两人之间的间隙里插新对偶行 ⇒ 互等(TiDB 无间隙锁、PG 读已提交不取间隙锁)。谁当牺牲者是引擎的任意选择,
107
107
  * 抛给调用方等于把一次内部仲裁当成投递失败。锁等待超时**不**重来(延迟信号,照抛)。
@@ -2,7 +2,7 @@ import { join } from "node:path";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
3
  import { atomicWriteFile, ensureDir } from "@sema-agent/core";
4
4
  import { mysqlDriver, pgDriver, sqlDbNowMs } from "./sql-driver.js";
5
- import { isDeadlockError } from "./sql-errors.js";
5
+ import { retryOnDeadlock } from "./sql-errors.js";
6
6
  import { alignedWindow } from "../observability/cost-quota.js";
7
7
  export const ORG_DELIVERY_SENDER_QUOTA_TABLE = "org_delivery_sender_quota";
8
8
  export const ORG_DELIVERY_RECIPIENT_QUOTA_TABLE = "org_delivery_recipient_quota";
@@ -122,7 +122,6 @@ export class FileOrgDeliveryQuotaStore extends MemoryOrgDeliveryQuotaStore {
122
122
  atomicWriteFile(this.tmpDir, this.path, `${JSON.stringify(snapshot)}\n`);
123
123
  }
124
124
  }
125
- const DEADLOCK_RETRY_LIMIT = 3;
126
125
  export class SqlOrgDeliveryQuotaStore {
127
126
  db;
128
127
  constructor(db) {
@@ -138,16 +137,7 @@ export class SqlOrgDeliveryQuotaStore {
138
137
  return this.inTx(ORG_DELIVERY_RECIPIENT_QUOTA_TABLE, policy, (conn, at) => this.takeIn(conn, fromScope, toScope, at, policy));
139
138
  }
140
139
  async inTx(table, policy, body) {
141
- for (let tries = 0;; tries++) {
142
- try {
143
- return await this.inTxOnce(table, policy, body);
144
- }
145
- catch (err) {
146
- if (tries < DEADLOCK_RETRY_LIMIT && isDeadlockError(this.db.dialect, err))
147
- continue;
148
- throw err;
149
- }
150
- }
140
+ return retryOnDeadlock(this.db.dialect, () => this.inTxOnce(table, policy, body));
151
141
  }
152
142
  async inTxOnce(table, policy, body) {
153
143
  const at = quotaWindowStart(await sqlDbNowMs(this.db, table), policy.windowMs);
@@ -28,7 +28,7 @@ import type { CheckpointStore } from "@sema-agent/core";
28
28
  /**
29
29
  * 一只**已提交**的审批停车行说它握着哪些调用(停车行自己的陈述,原样,不是推断):
30
30
  * · `sessionId` —— 行停住的会话 `cp.sessionId`(执行这次调用的会话:委派 / 后台子代的行落在**子会话**上)。core 铸行用的是 prepare-task 的
31
- * 同一个 `sessionId`,它也是同一次调用的 ask 上的 `sourceTaskId`(`askSourceIdentity`)⇒ 订阅者按**相等**配,不做别名(车 SO:
31
+ * 同一个 `sessionId`,它也是同一次调用的 ask 上的 `sourceSessionId`(`askSourceIdentity`)⇒ 订阅者按**相等**配,不做别名(车 SO:
32
32
  * 调用 id 只在 run 内唯一,宿主会话若当子代的等价别名,宿主停车行会冒充子代的提交);
33
33
  * · `toolCallIds` —— 停在门前的那只调用(`pendingAction.toolCallId`)∪ 同一条助手消息里**尚未执行**的调用
34
34
  * (`batchToolCallIds − completedCallIds`,core d.ts「#k+1..N are still pending」):那些调用随这一行一起被持久挂起,
@@ -2,7 +2,7 @@ import { FileDurableRulePartitionProvider, type RuleApprovalRecord, type RuleApp
2
2
  import { type RuleImportTicket, type RuleTicketPurpose, type RuleTicketReclaimResult, type RuleTicketRedeemResult, type RuleTicketSnapshot } from "./permission-rule-store-sql.js";
3
3
  import type { PermissionRuleStoreBundle } from "../rules-consent.js";
4
4
  import type { Logger } from "../observability/logger.js";
5
- import { type RuleCandidate } from "@sema-agent/core";
5
+ import { type RuleCandidate, type RuleOwner } from "@sema-agent/core";
6
6
  /**
7
7
  * `RuleApprovalRecordStore` 的 File 形。
8
8
  *
@@ -26,6 +26,13 @@ export declare class FileRuleApprovalRecordStore implements RuleApprovalRecordSt
26
26
  get(id: string): Promise<RuleApprovalRecord | StaleRuleApprovalRecord | undefined>;
27
27
  create(record: RuleApprovalRecord): Promise<void>;
28
28
  cas(id: string, expectRev: number, next: RuleApprovalRecord): Promise<boolean>;
29
+ /**
30
+ * 一次性迁移 M36(窗口见 `plugins/one-time-migrations.ts` 登记表)在 File 店能叫出名字的桶:本店**导入记录**点名过的每一个属主(去重)。
31
+ * 引擎 File 分区的桶文件按属主名的单向摘要命名、分区后端没有列桶口,记录是本店唯一叫得出桶属主的地方(SQL 店直接列规则表;File 侧的缺口见
32
+ * `imported-layer-root-retirement.ts` 头注)。上一形的记录(Stale 信封)读不出种类 ⇒ 属主读得出就算上(多读一只桶零写,少读一只会漏)。
33
+ * 损坏闸合着 ⇒ 抛(读不全记录就叫不全桶;与本面其余口同一条 fail-closed)。
34
+ */
35
+ importRecordOwners(): Promise<RuleOwner[]>;
29
36
  /** 与 SQL 侧同名方法同义(超帽拒绝时收掉 `prepareCcImport` 已落盘的那条 pending 记录)。
30
37
  * `state === "pending"` 是硬的:已确认/已兑付的记录是一次真人同意的审计事实。 */
31
38
  discardPendingRecord(recordId: string): Promise<boolean>;
@@ -87,6 +94,8 @@ export declare class FileRuleImportTicketStore {
87
94
  * 那一条:过期只挡开始一次兑付,不挡做完一次已经开始的)。抢到之后**换一代认领**。 */
88
95
  reclaim(ticketId: string, principal: string, purpose: RuleTicketPurpose, minClaimAgeMs: number): Promise<RuleTicketReclaimResult>;
89
96
  }
97
+ /** 三面束在数据根下的目录名(唯一一处;束在第一次装配时建它 —— 没开过规则店的数据根下没有它)。 */
98
+ export declare const FILE_PERMISSION_RULE_DIR = "permission-rules";
90
99
  /** local 车道的三面束 + 生命周期。`dispose()` 释放 core File durable 分区 provider 的写锁(见 {@link createFilePermissionRuleStores})。 */
91
100
  export interface FilePermissionRuleStores extends PermissionRuleStoreBundle {
92
101
  durable: FileDurableRulePartitionProvider;
@@ -4,7 +4,7 @@ import { readdirSync } from "node:fs";
4
4
  import { performance } from "node:perf_hooks";
5
5
  import { FileDurableRulePartitionProvider, AppendLog, ensureDir, } from "@sema-agent/core";
6
6
  import { z } from "zod";
7
- import { DurableRuleScopeSchema, RuleOfferColumn, buildRulePayloadHash, recordOwnerOf, refusedOwnerSentence } from "./permission-rule-store-sql.js";
7
+ import { DurableRuleScopeSchema, IMPORT_RECORD_KIND, RuleOfferColumn, buildRulePayloadHash, recordOwnerOf, refusedOwnerSentence } from "./permission-rule-store-sql.js";
8
8
  import { replayJsonlGuarded } from "../observability/corrupt-read-seat.js";
9
9
  import { EDITED_RULE_BREADTH_WARNING_CODES, RULE_APPROVAL_KINDS, RULE_APPROVAL_RECORD_SCHEMA, RULE_APPROVAL_STATES, RULE_BEHAVIORS, readRuleOwner } from "@sema-agent/core";
10
10
  const RuleDotSchema = z.object({ actor: z.string().min(1), counter: z.number().int().nonnegative().safe() }).strict();
@@ -173,6 +173,18 @@ export class FileRuleApprovalRecordStore {
173
173
  this.rows.set(id, structuredClone(next));
174
174
  return true;
175
175
  }
176
+ async importRecordOwners() {
177
+ this.gate.assertUsable();
178
+ const owners = [];
179
+ for (const r of this.rows.values()) {
180
+ if (!("staleSchema" in r) && r.kind !== IMPORT_RECORD_KIND)
181
+ continue;
182
+ const owner = recordOwnerOf(r);
183
+ if (owner !== undefined)
184
+ owners.push(owner);
185
+ }
186
+ return owners;
187
+ }
176
188
  async discardPendingRecord(recordId) {
177
189
  this.gate.assertUsable();
178
190
  const cur = this.rows.get(recordId);
@@ -319,8 +331,9 @@ export class FileRuleImportTicketStore {
319
331
  return { ok: true, approvalId: row.approvalId, payloadHash: row.payloadHash, claimId };
320
332
  }
321
333
  }
334
+ export const FILE_PERMISSION_RULE_DIR = "permission-rules";
322
335
  export function createFilePermissionRuleStores(root, opts) {
323
- const dir = join(root, "permission-rules");
336
+ const dir = join(root, FILE_PERMISSION_RULE_DIR);
324
337
  ensureDir(dir);
325
338
  const durable = opts?.onError ? new FileDurableRulePartitionProvider(dir, opts.onError) : new FileDurableRulePartitionProvider(dir);
326
339
  const approvals = new FileRuleApprovalRecordStore(dir, opts?.warn);
@@ -158,6 +158,8 @@ export declare function buildRuleBucketChecksum(body: {
158
158
  /** 这只桶的行写在哪一版规则文法里(`SESSION_RULES_GRAMMAR` 的数);缺席 = 存量无戳行(列 NULL)。 */
159
159
  grammar?: number;
160
160
  }): string;
161
+ /** 一只桶要的全部 SQL 能力:方言 + 一只执行口(池,或一次性迁移握着锁的那一条连接 —— M36 在那条连接上经同一只写面落墓碑)。 */
162
+ type BucketSql = Pick<SqlDriver, "dialect" | "query">;
161
163
  /**
162
164
  * SQL 双方言的 {@link DurableRulePartitionProvider} —— [ref] 里的 **durable 分区后端**
163
165
  * (`user` + `project` 两源的 OR-Set CRDT),**不是**引擎接缝本身:
@@ -170,7 +172,7 @@ export declare function buildRuleBucketChecksum(body: {
170
172
  export declare class SqlDurableRulePartitionProvider implements DurableRulePartitionProvider {
171
173
  private readonly db;
172
174
  private readonly now;
173
- constructor(db: SqlDriver, now?: () => number);
175
+ constructor(db: BucketSql, now?: () => number);
174
176
  forPrincipal(principal: string | undefined): DurableRulePartition;
175
177
  forLocalOwner(): DurableRulePartition;
176
178
  }
@@ -182,6 +184,33 @@ export declare function recordOwnerOf(record: {
182
184
  readonly owner?: unknown;
183
185
  readonly principal?: unknown;
184
186
  }): RuleOwner | undefined;
187
+ /**
188
+ * 一次性迁移 M36(窗口见 `plugins/one-time-migrations.ts` 登记表)要读的桶:**规则表里的每一只桶**(按 `owner_key` 分页,与 M11 同一只走法)。
189
+ * 不从审批记录推:收编腿把规则桶整桶改键到新身份、而审批史按 D2 留在旧身份名下(`src/adoption/plan.ts` 的 `permission_rule#owner_key` 腿与
190
+ * `permission-rule-approval-history` 一员)—— 从记录推出的属主集在收编之后漏掉新桶。属主读法只有引擎的 `readRuleOwner`(与本店读口同一只);
191
+ * 读不出属主的行不点名任何可解析的桶(读腿同样解析不到它),不返回。`db` 是池或迁移握锁的那一条连接。
192
+ */
193
+ export declare function listRuleBucketOwners(db: Pick<SqlDriver, "dialect" | "query">): Promise<RuleOwner[]>;
194
+ /**
195
+ * 取 dot 那一步「建行之后、读行之前」这只桶被并发删掉了(另一只副本删桶恰落在这一窗里)。具名类 ⇒ 调用方按类判(M36 退役读作「无可退役」),不按文案。
196
+ */
197
+ export declare class RuleBucketVanishedError extends Error {
198
+ readonly ownerKey: string;
199
+ constructor(ownerKey: string);
200
+ }
201
+ /** 这只抛出物是不是本店的「桶在写它的那一步里没了」({@link RuleBucketVanishedError})。 */
202
+ export declare function isSqlBucketVanished(e: unknown): boolean;
203
+ /**
204
+ * 这只读桶抛出物是不是**本店读腿的整桶拒** —— 判的是读腿({@link SqlDurableRulePartition} 的 `list` / 写的读基)**真往外交的**那两类,不是判官内部的抛出形:
205
+ * · {@link BucketRefusedError} —— 判官四道(文法戳 / 指纹 / 行的 behavior / 引擎校验器)的拒;
206
+ * · {@link StoreCorruptError} —— 某只 JSON 列的体读不出(`not-json` / `malformed`),读腿在 {@link refusingUnreadableColumns} 把判官抛的列错误折成的店拒绝契约。
207
+ * 两类都是这一行的确定性判决:原样再读也读不出,读腿对它同样整桶拒(零规则),所以它什么都不放行。
208
+ * 别的抛出物**不是**这一类:连接 / 超时 / 权限(一次读失败证明不了以后也读不出);以及仍以 {@link SqlJsonColumnError} 穿出读腿的唯一一形 `not-text`
209
+ * (列没按文本投影:读腿与解码点漂了,是码缺陷不是坏数据 —— 按列错误类判它会把一次码漂读成「这只桶坏了」)。
210
+ */
211
+ export declare function isSqlBucketRefusal(e: unknown): boolean;
212
+ /** 导入记录的种类词(引擎闭集 `RULE_APPROVAL_KINDS` 的一员;`imported-cc` 出处由它派生)。 */
213
+ export declare const IMPORT_RECORD_KIND = "import";
185
214
  /** 写口拒句(两只店同一句):属主引擎读不出,记录不落。 */
186
215
  export declare function refusedOwnerSentence(recordId: string): string;
187
216
  /**
@@ -542,4 +571,5 @@ export interface PermissionRuleStores {
542
571
  reapExpired(nowMs: number): Promise<number>;
543
572
  }
544
573
  export declare function createSqlPermissionRuleStores(db: SqlDriver, now?: () => number): PermissionRuleStores;
574
+ export {};
545
575
  //# sourceMappingURL=permission-rule-store-sql.d.ts.map
@@ -6,7 +6,7 @@ import { isMissingColumnError } from "./sql-errors.js";
6
6
  import { describeProbedTableDrift } from "./schema-drift-sentence.js";
7
7
  import { canonicalJsonStringify } from "@sema-agent/core";
8
8
  import { ensureIndexes } from "./ensure-index.js";
9
- import { createZodColumnReader, decodeJsonColumn, foldJsonColumn, jsonColumnText, readJsonObjectColumn, SqlJsonColumnError } from "./sql-json-column.js";
9
+ import { createZodColumnReader, decodeJsonColumn, foldJsonColumn, jsonColumnText, readJsonObjectColumn, SqlJsonColumnError, StoreCorruptError } from "./sql-json-column.js";
10
10
  import { redactSecrets } from "../trace/redact.js";
11
11
  export function writerOfSqlRuleStore(store) {
12
12
  const w = Reflect.get(store, PERMISSION_RULE_WRITER);
@@ -508,7 +508,7 @@ class SqlDurableRulePartition {
508
508
  const { rows } = await this.db.query(this.q(`SELECT actor, counter, grammar FROM ${PERMISSION_RULE_TABLE} WHERE owner_key = ?`, `SELECT actor, counter, grammar FROM ${PERMISSION_RULE_TABLE} WHERE owner_key = $1`), [this.ownerKey]);
509
509
  const row = rows[0];
510
510
  if (row === undefined)
511
- throw new Error(`permission_rule bucket ${this.ownerKey} vanished while minting a dot`);
511
+ throw new RuleBucketVanishedError(this.ownerKey);
512
512
  readBucketStamp(this.ownerKey, row.grammar);
513
513
  const cur = Number(row.counter);
514
514
  const next = cur + 1;
@@ -593,6 +593,39 @@ export class SqlDurableRulePartitionProvider {
593
593
  export function recordOwnerOf(record) {
594
594
  return readRuleOwner(record.owner ?? { kind: "principal", principal: record.principal });
595
595
  }
596
+ export async function listRuleBucketOwners(db) {
597
+ const owners = [];
598
+ let after = "";
599
+ for (;;) {
600
+ const { rows } = await db.query(db.dialect === "mysql"
601
+ ? `SELECT owner_key, owner_kind, principal FROM ${PERMISSION_RULE_TABLE} WHERE owner_key > ? ORDER BY owner_key LIMIT ${UNDURABLE_PURGE_PAGE}`
602
+ : `SELECT owner_key, owner_kind, principal FROM ${PERMISSION_RULE_TABLE} WHERE owner_key > $1 ORDER BY owner_key LIMIT ${UNDURABLE_PURGE_PAGE}`, [after]);
603
+ for (const row of rows) {
604
+ after = String(row["owner_key"]);
605
+ const principal = row["principal"] === null || row["principal"] === undefined ? undefined : String(row["principal"]);
606
+ const owner = readRuleOwner({ kind: row["owner_kind"], ...(principal !== undefined ? { principal } : {}) });
607
+ if (owner !== undefined)
608
+ owners.push(owner);
609
+ }
610
+ if (rows.length < UNDURABLE_PURGE_PAGE)
611
+ return owners;
612
+ }
613
+ }
614
+ export class RuleBucketVanishedError extends Error {
615
+ ownerKey;
616
+ constructor(ownerKey) {
617
+ super(`permission_rule bucket ${ownerKey} vanished while minting a dot`);
618
+ this.name = "RuleBucketVanishedError";
619
+ this.ownerKey = ownerKey;
620
+ }
621
+ }
622
+ export function isSqlBucketVanished(e) {
623
+ return e instanceof RuleBucketVanishedError;
624
+ }
625
+ export function isSqlBucketRefusal(e) {
626
+ return e instanceof BucketRefusedError || e instanceof StoreCorruptError;
627
+ }
628
+ export const IMPORT_RECORD_KIND = "import";
596
629
  export function refusedOwnerSentence(recordId) {
597
630
  return `refusing to create approval record ${recordId}: its owner is not one the engine reads as an owner (a principal name must be a non-blank string; the kind must be a member of the owner set) — nothing was written`;
598
631
  }
@@ -314,21 +314,18 @@ export declare class RemoteDeviceExecutionEnv implements RemoteExecutionEnv {
314
314
  * 🔴 **工厂认设备不认住客**(读口一只 = 会话店执行席 `seat.read(root)`,与解析器 / 换绑门 / 执行放置读面 / 准入链同一只读):按 placement 根读**当前**格,
315
315
  * 与这条腿带来的设备句柄 `honor` 比,不同即 typed 拒。判序(一次行读):
316
316
  * root = ctx.placementRootSessionId // 空 ⇒ config.placement_root_invalid
317
- * honor = ctx.resumeWorkspace?.deviceId
317
+ * honor = ctx.placementTarget?.deviceId // 引擎交来的放置围栏(这条腿必须落在哪台设备上)
318
318
  * cell = seat.read(root).cell // 行不在 / 格缺 / mirror / writer(device, ∅) ⇒ device.not_bound(唯一读法 `boundDeviceOf`)
319
319
  * honor ≠ undefined ∧ 格上设备 ≠ honor ⇒ device.identity_mismatch(句点名两台,零连接)
320
320
  * readDeviceForAdmission(格上设备, 格上属主)
321
321
  * 工厂**不**读 `task_run`、不读子代分区行、不按 `ctx.taskId`、不比化身、不比属主(住客谓词只用于写与 judged 复核,不是工厂的读者)。
322
- * 续跑腿(core 7.54.0 [ref] r3 / [ref],`ExecutionEnvFactoryContext.resumeWorkspace`):行记下了设备 ⇒ 本工厂**只**在那台设备上铸 —— 根此刻绑的不是它(换绑过)
323
- * ⇒ `device.identity_mismatch`(换绑后赎回的同一个词,零新码);不连另一台、不改道(新设备没有那个工作区)。「绑的就是它但现在不在线」与新跑同形:
324
- * 构造成功、`connect()` 回 typed `device.offline`(从不连别的设备)。行没记设备(早于该字段的行)⇒ 引擎已命名的「行未记设备 ⇒ 普通路由」臂,按根读当前格。
325
- * 句柄(`workspaceHandle()`)恒报连接真到达的设备,不抄 `resumeWorkspace`。
326
- *
327
- * 🔴 **过渡期(条件句)**:新派后代腿的放置句柄(引擎在 `ExecutionEnvFactoryContext` 上的放置工作区字段,`honor` 的第二来源)**只在本仓所基的引擎版本带它时**
328
- * 进判序;本仓今天所基的引擎上下文没有这个字段 ⇒ `honor` 只取续跑腿的 `resumeWorkspace`,新派后代腿按根读当前格。本仓**不**注入任何替代见证、不造任何行
329
- * (修在源头)。敞口(在那个字段到货之前成立):活过父 turn 的后台子代,在根换绑之后新派的腿会落到根当前设备 —— 换绑半边由换绑门的子代活性判关闭(归换绑门那一车),
330
- * 删除半边另有票。字段到货后的形:`honor = ctx.resumeWorkspace?.deviceId ?? <放置句柄>.deviceId`,放置句柄的 provider 不是本车道 ⇒ 装配拒(`config.placement_root_invalid` 同级)。
331
- * 本仓任何文本不得宣称工厂「一律按住客定址」。
322
+ * 围栏(`ExecutionEnvFactoryContext.placementTarget`,引擎每条腿一个答案):派生它的那条腿的环境报过的目标 —— 委派子代 / 工作流 agent / 被唤醒的
323
+ * 保留 agent 带唤醒腿的目标,级联后续阶 / 验证腿带第一条腿的目标,续跑腿带检查点行记下的目标。围栏点名了设备 ⇒ 本工厂**只**在那台设备上铸 ——
324
+ * 根此刻绑的不是它(换绑过)⇒ `device.identity_mismatch`(换绑后赎回的同一个词,零新码);不连另一台、不改道(新设备没有那个工作区)。
325
+ * 「绑的就是它但现在不在线」与新跑同形:构造成功、`connect()` 回 typed `device.offline`(从不连别的设备)。围栏缺席(顶层首腿,或派生它的腿
326
+ * 不是远端环境)或不点名设备 ⇒ 普通路由,按根读当前格。围栏的 `provider` 不进判序(引擎的回执也只比设备)。
327
+ * 句柄(`workspaceHandle()`)恒报连接真到达的设备,不抄围栏;引擎拿这份报告复核每条带围栏的腿(不符 ⇒ 续跑腿 `resume.env_failed`、
328
+ * 其余腿 `config.placement_not_honored`,都在腿跑之前)—— 本工厂在连接之前就拒,那两个词只在报告与围栏真不一致时出现。
332
329
  */
333
330
  export declare function deviceExecutionEnvFactory(deps: DeviceEnvFactoryDeps): ExecutionEnvFactory;
334
331
  /**
@@ -17,6 +17,7 @@ export const DEVICE_OUTCOME_TO_FILE_ERROR = {
17
17
  is_directory: "is_directory",
18
18
  liveness_no_output: "unknown",
19
19
  name_too_long: "name_too_long",
20
+ symlink_loop: "symlink_loop",
20
21
  not_directory: "not_directory",
21
22
  not_found: "not_found",
22
23
  not_supported: "not_supported",
@@ -40,6 +41,7 @@ export const DEVICE_OUTCOME_TO_EXEC_ERROR = {
40
41
  is_directory: "unknown",
41
42
  liveness_no_output: "liveness_no_output",
42
43
  name_too_long: "unknown",
44
+ symlink_loop: "unknown",
43
45
  not_directory: "unknown",
44
46
  not_found: "unknown",
45
47
  not_supported: "shell_unavailable",
@@ -771,6 +773,7 @@ const OUTCOME_TO_REMOTE_ERROR = {
771
773
  is_directory: "unknown",
772
774
  liveness_no_output: "liveness_no_output",
773
775
  name_too_long: "unknown",
776
+ symlink_loop: "unknown",
774
777
  not_directory: "unknown",
775
778
  not_found: "unknown",
776
779
  not_supported: "unsupported",
@@ -806,10 +809,10 @@ export function deviceExecutionEnvFactory(deps) {
806
809
  throw deviceFactoryRefusal(DEVICE_ERROR_CODES.NOT_BOUND, `device lane: no device binding for placement root ${rootSessionId} [${DEVICE_ERROR_CODES.NOT_BOUND}] — ` +
807
810
  "the submit-time admission chain must bind the root session to a device before a run reaches the factory (v2 §4.3.2)");
808
811
  }
809
- const honor = ctx.resumeWorkspace?.deviceId;
812
+ const honor = ctx.placementTarget?.deviceId;
810
813
  if (honor !== undefined && honor !== binding.id) {
811
- throw deviceFactoryRefusal(DEVICE_ERROR_CODES.IDENTITY_MISMATCH, `device lane: this resume rebuilds the workspace on device ${honor}, but placement root ${rootSessionId} is now bound to ${binding.id} [${DEVICE_ERROR_CODES.IDENTITY_MISMATCH}] — ` +
812
- "a parked call does not follow a rebind (the new device has no copy of that workspace); finish or cancel the parked task, then start it again on the device now bound");
814
+ throw deviceFactoryRefusal(DEVICE_ERROR_CODES.IDENTITY_MISMATCH, `device lane: this leg is placed on device ${honor} (the device the work it continues ran on), but placement root ${rootSessionId} is now bound to ${binding.id} [${DEVICE_ERROR_CODES.IDENTITY_MISMATCH}] — ` +
815
+ "work already under way does not follow a rebind (the new device has no copy of that workspace); finish or cancel the running or parked task, then start it again on the device now bound");
813
816
  }
814
817
  const view = await deps.store.readDeviceForAdmission(binding.id, binding.owner);
815
818
  if (!view) {