@sema-agent/server 7.148.0 → 7.150.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 (360) hide show
  1. package/MIGRATION.md +551 -9
  2. package/README.md +17 -2
  3. package/README.zh-CN.md +5 -1
  4. package/USAGE.md +126 -68
  5. package/dist/adoption/wire.d.ts +3 -3
  6. package/dist/approval-card.d.ts +33 -33
  7. package/dist/approval-gate-currency.d.ts +1 -1
  8. package/dist/approval-leg-origin.d.ts +33 -0
  9. package/dist/approval-leg-origin.js +4 -0
  10. package/dist/audit.d.ts +1 -1
  11. package/dist/audit.js +2 -2
  12. package/dist/auth-bridge.d.ts +9 -1
  13. package/dist/auth-bridge.js +4 -4
  14. package/dist/auth-keys.d.ts +10 -1
  15. package/dist/auth-keys.js +8 -0
  16. package/dist/background-session-reap.d.ts +1 -1
  17. package/dist/boot/deferred-sandbox-path-env.d.ts +5 -3
  18. package/dist/boot/deferred-sandbox-path-env.js +1 -2
  19. package/dist/boot/execution-env.js +10 -11
  20. package/dist/boot/execution-lane.d.ts +6 -4
  21. package/dist/boot/execution-lane.js +3 -1
  22. package/dist/boot/org-delivery.d.ts +3 -2
  23. package/dist/boot/parked-revive-gate.d.ts +5 -5
  24. package/dist/boot/parked-revive-gate.js +2 -3
  25. package/dist/boot/resolve-spec.d.ts +4 -5
  26. package/dist/boot/resolve-spec.js +66 -28
  27. package/dist/boot/retention-lane.d.ts +1 -1
  28. package/dist/boot/runner-deps.js +3 -1
  29. package/dist/boot/session-faces.js +21 -21
  30. package/dist/boot/stage-01-config.d.ts +1 -0
  31. package/dist/boot/stage-01-config.js +4 -3
  32. package/dist/boot/stage-02-web-admin-pull.d.ts +1 -0
  33. package/dist/boot/stage-02-web-admin-pull.js +10 -2
  34. package/dist/boot/stage-03-store-backend.d.ts +1 -0
  35. package/dist/boot/stage-03-store-backend.js +4 -4
  36. package/dist/boot/stage-04-stores.d.ts +1 -0
  37. package/dist/boot/stage-05-execution-env.d.ts +1 -0
  38. package/dist/boot/stage-06-runners.d.ts +2 -1
  39. package/dist/boot/stage-06-runners.js +1 -2
  40. package/dist/boot/stage-07-capability-layer.d.ts +2 -1
  41. package/dist/boot/stage-07-capability-layer.js +1 -1
  42. package/dist/boot/stage-08-reapers.d.ts +2 -1
  43. package/dist/boot/stage-09-leader.d.ts +2 -1
  44. package/dist/boot/stage-10-http-server.d.ts +35 -2
  45. package/dist/boot/stage-10-http-server.js +9 -4
  46. package/dist/boot/stores.d.ts +3 -0
  47. package/dist/boot/stores.js +22 -19
  48. package/dist/boot/web-admin.d.ts +1 -1
  49. package/dist/capabilities/tool-defer.d.ts +1 -1
  50. package/dist/capability-facts.d.ts +70 -60
  51. package/dist/capability-facts.js +45 -49
  52. package/dist/card-settle-stored-scope.d.ts +56 -0
  53. package/dist/card-settle-stored-scope.js +27 -0
  54. package/dist/card-void.d.ts +7 -12
  55. package/dist/card-void.js +2 -7
  56. package/dist/config-catalog.d.ts +12 -0
  57. package/dist/config-catalog.js +37 -33
  58. package/dist/config-invariants.d.ts +5 -4
  59. package/dist/config-invariants.js +4 -4
  60. package/dist/config-types.d.ts +49 -16
  61. package/dist/config.d.ts +6 -5
  62. package/dist/config.js +35 -24
  63. package/dist/context-window-notice.d.ts +31 -0
  64. package/dist/context-window-notice.js +28 -0
  65. package/dist/deployment-governance.d.ts +7 -28
  66. package/dist/deployment-governance.js +1 -14
  67. package/dist/deployment-shape.d.ts +20 -11
  68. package/dist/deployment-shape.js +1 -0
  69. package/dist/device-ws-hub.d.ts +1 -0
  70. package/dist/device-ws-hub.js +64 -28
  71. package/dist/device-ws-protocol.d.ts +20 -6
  72. package/dist/device-ws-protocol.js +9 -2
  73. package/dist/engine-identity.d.ts +7 -1
  74. package/dist/engine-identity.js +5 -0
  75. package/dist/env-name-allowlist-knobs.d.ts +6 -5
  76. package/dist/env-name-allowlist-knobs.js +5 -0
  77. package/dist/execution-lane-caps.d.ts +20 -5
  78. package/dist/execution-lane-caps.js +18 -4
  79. package/dist/field-conflict.d.ts +2 -2
  80. package/dist/fleet/engine-signal.d.ts +14 -1
  81. package/dist/fleet/engine-signal.js +5 -1
  82. package/dist/fleet/fleet-reconciler.d.ts +1 -1
  83. package/dist/fleet/serving-lease.d.ts +18 -3
  84. package/dist/fleet/serving-lease.js +81 -35
  85. package/dist/fleet-client.d.ts +13 -11
  86. package/dist/fleet-client.js +53 -33
  87. package/dist/history-anchor.js +1 -1
  88. package/dist/hitl-delivery.d.ts +1 -3
  89. package/dist/hitl-delivery.js +1 -1
  90. package/dist/hooks/command-spawn.d.ts +7 -2
  91. package/dist/hooks/command-spawn.js +13 -6
  92. package/dist/hooks/hook-runner.d.ts +5 -0
  93. package/dist/hooks/hook-runner.js +11 -7
  94. package/dist/http/active-run-conflict.d.ts +3 -7
  95. package/dist/http/active-run-conflict.js +2 -2
  96. package/dist/http/admission.js +5 -5
  97. package/dist/http/approval-sweeps.js +5 -3
  98. package/dist/http/caller-trust.d.ts +2 -2
  99. package/dist/http/caller-trust.js +3 -3
  100. package/dist/http/checkpoint-ctx.d.ts +18 -63
  101. package/dist/http/checkpoint-ctx.js +1 -24
  102. package/dist/http/claim-heartbeat.d.ts +6 -6
  103. package/dist/http/claim-heartbeat.js +6 -6
  104. package/dist/http/claimed-run.d.ts +16 -9
  105. package/dist/http/claimed-run.js +8 -0
  106. package/dist/http/dispatch.js +7 -5
  107. package/dist/http/durable-run-leg.d.ts +8 -1
  108. package/dist/http/durable-run-leg.js +34 -15
  109. package/dist/http/execution-context.d.ts +115 -14
  110. package/dist/http/execution-context.js +130 -40
  111. package/dist/http/owner-access.d.ts +246 -21
  112. package/dist/http/owner-access.js +186 -8
  113. package/dist/http/principal-gate.d.ts +18 -11
  114. package/dist/http/principal-gate.js +3 -1
  115. package/dist/http/readiness.d.ts +4 -2
  116. package/dist/http/resume-ctx-read.d.ts +14 -3
  117. package/dist/http/resume-ctx-read.js +24 -3
  118. package/dist/http/resume-legs.d.ts +35 -3
  119. package/dist/http/resume-legs.js +262 -106
  120. package/dist/http/route-ctx.d.ts +29 -11
  121. package/dist/http/routes/a2a-serve.js +17 -14
  122. package/dist/http/routes/approvals-assistant.d.ts +3 -2
  123. package/dist/http/routes/approvals-assistant.js +11 -11
  124. package/dist/http/routes/capabilities.js +5 -4
  125. package/dist/http/routes/config-catalog.d.ts +1 -1
  126. package/dist/http/routes/diagnostics.d.ts +3 -1
  127. package/dist/http/routes/diagnostics.js +30 -5
  128. package/dist/http/routes/mcp-reconnect.js +2 -2
  129. package/dist/http/routes/memory-origin.d.ts +1 -1
  130. package/dist/http/routes/notify-wake.js +2 -3
  131. package/dist/http/routes/principal-inbox.d.ts +3 -1
  132. package/dist/http/routes/principal-inbox.js +3 -2
  133. package/dist/http/routes/projects.d.ts +1 -0
  134. package/dist/http/routes/projects.js +5 -3
  135. package/dist/http/routes/retention-ops.d.ts +1 -1
  136. package/dist/http/routes/run-permission-mode.d.ts +1 -1
  137. package/dist/http/routes/run-permission-mode.js +2 -2
  138. package/dist/http/routes/runs.d.ts +1 -1
  139. package/dist/http/routes/runs.js +33 -37
  140. package/dist/http/routes/session-memory-sync.js +4 -5
  141. package/dist/http/routes/session-sync.d.ts +24 -5
  142. package/dist/http/routes/session-sync.js +45 -37
  143. package/dist/http/routes/sessions-list.d.ts +1 -1
  144. package/dist/http/routes/sessions.d.ts +1 -1
  145. package/dist/http/routes/sessions.js +18 -44
  146. package/dist/http/routes/tasks.js +136 -101
  147. package/dist/http/routes/trace-usage.js +14 -14
  148. package/dist/http/routes/workflows.d.ts +1 -0
  149. package/dist/http/routes/workflows.js +20 -38
  150. package/dist/http/server.d.ts +12 -9
  151. package/dist/http/server.js +35 -6
  152. package/dist/http/stored-body-replay.d.ts +3 -24
  153. package/dist/http/stored-body-replay.js +2 -12
  154. package/dist/http/submit-ctx.d.ts +2 -2
  155. package/dist/http/submit-ctx.js +5 -5
  156. package/dist/http/submit-replay.d.ts +15 -2
  157. package/dist/http/submit-replay.js +10 -4
  158. package/dist/http/wire-types.d.ts +6 -3
  159. package/dist/index.d.ts +3 -1
  160. package/dist/index.js +1 -0
  161. package/dist/leader/endpoint.js +9 -8
  162. package/dist/leg-facts.d.ts +13 -2
  163. package/dist/leg-facts.js +2 -0
  164. package/dist/local-store-declaration.d.ts +54 -0
  165. package/dist/local-store-declaration.js +59 -0
  166. package/dist/mcp-assertion-header.d.ts +1 -1
  167. package/dist/mcp-assertion-header.js +1 -1
  168. package/dist/mcp-authorization-face.d.ts +18 -14
  169. package/dist/mcp-authorization-face.js +3 -2
  170. package/dist/memory-project-layer.js +1 -1
  171. package/dist/memory-scope.d.ts +4 -4
  172. package/dist/memory-scope.js +4 -4
  173. package/dist/memory-store-refusal.d.ts +1 -1
  174. package/dist/memory-sync-client.js +2 -2
  175. package/dist/model-context-window.d.ts +50 -11
  176. package/dist/model-context-window.js +20 -1
  177. package/dist/model-entry-refusal.d.ts +5 -4
  178. package/dist/model-entry-refusal.js +17 -3
  179. package/dist/model-reach-scope.d.ts +2 -1
  180. package/dist/observability/corrupt-read-seat.d.ts +1 -1
  181. package/dist/observability/fail-open.d.ts +19 -7
  182. package/dist/observability/fail-open.js +19 -7
  183. package/dist/observability/logger.d.ts +7 -0
  184. package/dist/observability/logger.js +3 -1
  185. package/dist/observability/metrics.js +1 -1
  186. package/dist/observability/run-terminal.d.ts +1 -1
  187. package/dist/observability/run-terminal.js +1 -0
  188. package/dist/observability/secret-env-scrub.d.ts +1 -1
  189. package/dist/observability/secret-env-scrub.js +1 -1
  190. package/dist/org-delivery-gate.d.ts +31 -6
  191. package/dist/org-delivery-gate.js +7 -4
  192. package/dist/owner-faces.d.ts +30 -5
  193. package/dist/owner-faces.js +2 -0
  194. package/dist/park-row-commit.d.ts +67 -0
  195. package/dist/park-row-commit.js +76 -0
  196. package/dist/parked-decide.d.ts +9 -7
  197. package/dist/parked-decide.js +25 -6
  198. package/dist/peer-directory.d.ts +1 -1
  199. package/dist/peer-seat.d.ts +1 -1
  200. package/dist/peer-seat.js +52 -59
  201. package/dist/plugins/adoption-log-sql.d.ts +3 -3
  202. package/dist/plugins/adoption-log-sql.js +19 -19
  203. package/dist/plugins/approval-ask-store-sql.d.ts +1 -1
  204. package/dist/plugins/approval-ask-store-sql.js +3 -5
  205. package/dist/plugins/approval-exemption-skill-cutoff.d.ts +64 -0
  206. package/dist/plugins/approval-exemption-skill-cutoff.js +122 -0
  207. package/dist/plugins/approval-exemption-skill-m46.d.ts +0 -6
  208. package/dist/plugins/approval-exemption-skill-m46.js +6 -10
  209. package/dist/plugins/approval-exemption-store.d.ts +26 -1
  210. package/dist/plugins/approval-exemption-store.js +28 -19
  211. package/dist/plugins/ensure-schema-lock-policy.d.ts +36 -3
  212. package/dist/plugins/ensure-schema-lock-policy.js +75 -9
  213. package/dist/plugins/exec-path-law.d.ts +34 -0
  214. package/dist/plugins/exec-path-law.js +13 -0
  215. package/dist/plugins/file-run-store.d.ts +22 -9
  216. package/dist/plugins/file-run-store.js +51 -23
  217. package/dist/plugins/local-checkpoint-store.d.ts +8 -1
  218. package/dist/plugins/local-checkpoint-store.js +5 -1
  219. package/dist/plugins/local-data-root-admission.d.ts +52 -0
  220. package/dist/plugins/local-data-root-admission.js +100 -0
  221. package/dist/plugins/local-engine-high-water.d.ts +28 -7
  222. package/dist/plugins/local-engine-high-water.js +27 -22
  223. package/dist/plugins/local-session-store.d.ts +134 -49
  224. package/dist/plugins/local-session-store.js +149 -108
  225. package/dist/plugins/memory-run-store.d.ts +14 -9
  226. package/dist/plugins/memory-run-store.js +48 -22
  227. package/dist/plugins/one-time-migration-marker.d.ts +19 -3
  228. package/dist/plugins/one-time-migration-marker.js +7 -1
  229. package/dist/plugins/one-time-migrations.d.ts +110 -59
  230. package/dist/plugins/one-time-migrations.js +224 -136
  231. package/dist/plugins/org-delivery-quota-store.d.ts +34 -11
  232. package/dist/plugins/org-delivery-quota-store.js +29 -14
  233. package/dist/plugins/permission-rule-store-sql.d.ts +1 -1
  234. package/dist/plugins/permission-rule-store-sql.js +5 -9
  235. package/dist/plugins/pg-pool.d.ts +1 -1
  236. package/dist/plugins/pg-pool.js +14 -12
  237. package/dist/plugins/posix-shell-fs.js +4 -2
  238. package/dist/plugins/remote-env-adb.js +3 -2
  239. package/dist/plugins/remote-env-device.d.ts +3 -2
  240. package/dist/plugins/remote-env-device.js +8 -6
  241. package/dist/plugins/remote-env-e2b.d.ts +4 -8
  242. package/dist/plugins/remote-env-e2b.js +21 -15
  243. package/dist/plugins/remote-env-host.d.ts +51 -10
  244. package/dist/plugins/remote-env-host.js +97 -31
  245. package/dist/plugins/remote-env-k8s.js +7 -4
  246. package/dist/plugins/remote-env-local-docker.d.ts +6 -3
  247. package/dist/plugins/remote-env-local-docker.js +11 -25
  248. package/dist/plugins/remote-env-ssh.js +4 -3
  249. package/dist/plugins/remote-shell.d.ts +3 -1
  250. package/dist/plugins/remote-shell.js +3 -3
  251. package/dist/plugins/replica-registration.d.ts +35 -0
  252. package/dist/plugins/replica-registration.js +48 -0
  253. package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
  254. package/dist/plugins/run-store-sql.d.ts +27 -8
  255. package/dist/plugins/run-store-sql.js +46 -18
  256. package/dist/plugins/scenario-prefix-switch-m42.d.ts +8 -12
  257. package/dist/plugins/scenario-prefix-switch-m42.js +9 -6
  258. package/dist/plugins/schema-consistency.d.ts +91 -5
  259. package/dist/plugins/schema-consistency.js +189 -7
  260. package/dist/plugins/schema-drift-sentence.d.ts +6 -5
  261. package/dist/plugins/schema-drift-sentence.js +13 -5
  262. package/dist/plugins/serving-requirement.d.ts +49 -0
  263. package/dist/plugins/serving-requirement.js +53 -0
  264. package/dist/plugins/session-placement.d.ts +5 -4
  265. package/dist/plugins/shell-sentinel-owner-m48.d.ts +22 -0
  266. package/dist/plugins/shell-sentinel-owner-m48.js +6 -0
  267. package/dist/plugins/stop-then-start-sentence.d.ts +55 -0
  268. package/dist/plugins/stop-then-start-sentence.js +51 -0
  269. package/dist/plugins/store-backend.d.ts +23 -9
  270. package/dist/plugins/store-backend.js +58 -24
  271. package/dist/plugins/store-contracts.d.ts +45 -5
  272. package/dist/plugins/store-contracts.js +19 -0
  273. package/dist/plugins/tidb-pool.d.ts +11 -6
  274. package/dist/plugins/tidb-pool.js +22 -12
  275. package/dist/plugins/workspace-custody.d.ts +44 -0
  276. package/dist/plugins/workspace-custody.js +47 -0
  277. package/dist/process-instance.d.ts +3 -0
  278. package/dist/process-instance.js +7 -0
  279. package/dist/project-context.d.ts +66 -0
  280. package/dist/project-context.js +70 -0
  281. package/dist/project-identity.d.ts +8 -7
  282. package/dist/project-identity.js +40 -17
  283. package/dist/project-layer-key.d.ts +13 -2
  284. package/dist/project-layer-key.js +9 -1
  285. package/dist/question.d.ts +9 -21
  286. package/dist/question.js +28 -19
  287. package/dist/read-deny-floor-notice.d.ts +36 -0
  288. package/dist/read-deny-floor-notice.js +40 -0
  289. package/dist/{plain-string-record.d.ts → request-declaration-disposition.d.ts} +9 -23
  290. package/dist/request-declaration-disposition.js +39 -0
  291. package/dist/run-cancel-card.d.ts +28 -0
  292. package/dist/run-cancel-card.js +20 -0
  293. package/dist/run-cancel-context.d.ts +10 -0
  294. package/dist/run-cancel-context.js +1 -0
  295. package/dist/run-local.js +28 -13
  296. package/dist/runs.d.ts +31 -7
  297. package/dist/runs.js +34 -16
  298. package/dist/security.d.ts +3 -3
  299. package/dist/server-secret-env.d.ts +34 -20
  300. package/dist/server-secret-env.js +61 -18
  301. package/dist/session-agent.d.ts +4 -12
  302. package/dist/session-agent.js +0 -6
  303. package/dist/session-children-retire.d.ts +6 -3
  304. package/dist/session-children-retire.js +2 -5
  305. package/dist/session-incarnation.d.ts +18 -4
  306. package/dist/session-incarnation.js +4 -1
  307. package/dist/session-notify-door.d.ts +4 -4
  308. package/dist/session-notify-door.js +5 -6
  309. package/dist/session-rules-seat.d.ts +4 -3
  310. package/dist/session-rules-seat.js +3 -11
  311. package/dist/session-sync.js +3 -3
  312. package/dist/session-workspace.d.ts +3 -3
  313. package/dist/store-admission.d.ts +33 -0
  314. package/dist/store-admission.js +49 -0
  315. package/dist/store-posture.d.ts +5 -0
  316. package/dist/store-posture.js +18 -1
  317. package/dist/stores-sql/checkpoint-store-sql.d.ts +22 -3
  318. package/dist/stores-sql/checkpoint-store-sql.js +109 -23
  319. package/dist/stores-sql/file-history-store-sql.d.ts +1 -1
  320. package/dist/stores-sql/file-history-store-sql.js +3 -5
  321. package/dist/stores-sql/retention-store-sql.d.ts +1 -1
  322. package/dist/stores-sql/tool-result-store-sql.d.ts +1 -1
  323. package/dist/stores-sql/tool-result-store-sql.js +4 -6
  324. package/dist/task-a2a.d.ts +3 -8
  325. package/dist/task-a2a.js +25 -27
  326. package/dist/task-cwd.d.ts +27 -10
  327. package/dist/task-cwd.js +3 -0
  328. package/dist/task-mcp.d.ts +3 -3
  329. package/dist/task-mcp.js +1 -1
  330. package/dist/task-settings.d.ts +13 -0
  331. package/dist/task-settings.js +14 -7
  332. package/dist/terminal.d.ts +2 -0
  333. package/dist/terminal.js +21 -6
  334. package/dist/tool-approval.d.ts +50 -41
  335. package/dist/tool-approval.js +96 -73
  336. package/dist/trace/engine-notice-wire.d.ts +38 -21
  337. package/dist/trace/engine-notice-wire.js +25 -4
  338. package/dist/trace/ledger-sink.d.ts +3 -1
  339. package/dist/trace/project.d.ts +8 -5
  340. package/dist/trace/project.js +11 -9
  341. package/dist/trace/projection-table.d.ts +33 -2
  342. package/dist/trace/redact.d.ts +12 -0
  343. package/dist/trace/redact.js +5 -1
  344. package/dist/trace/self-minted-wire.d.ts +268 -56
  345. package/dist/trace/self-minted-wire.js +110 -40
  346. package/dist/trace/tool-card.d.ts +3 -0
  347. package/dist/trace/tool-card.js +24 -0
  348. package/dist/trace/tool-output-media.d.ts +2 -1
  349. package/dist/unattended-permission-mode.d.ts +1 -1
  350. package/dist/web-admin-client/apply-effective.d.ts +1 -1
  351. package/dist/web-admin-client/apply-effective.js +22 -9
  352. package/dist/web-admin-client/effective-keys.d.ts +1 -1
  353. package/dist/web-admin-client/skills-mcp.d.ts +1 -1
  354. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +57 -29
  355. package/docs/error-text-lexicon.json +317 -0
  356. package/package.json +4 -2
  357. package/scripts/embed-minimal.mjs +183 -0
  358. package/wire-changes/server@7.149.0.json +372 -0
  359. package/wire-changes/server@7.150.0.json +335 -0
  360. package/dist/plain-string-record.js +0 -70
@@ -1,7 +1,8 @@
1
1
  import { recordEnsureStatements } from "./schema-ensure-legs.js";
2
2
  import { readLiveColumns } from "./schema-live-columns.js";
3
3
  import { SCHEMA_MISMATCH_WAYS_OUT } from "./schema-drift-sentence.js";
4
- import { SCHEMA_MISMATCH_ERROR_CODE, StoreBootRefusalError } from "./store-contracts.js";
4
+ import { SCHEMA_MISMATCH_ERROR_CODE, SchemaMismatchRefusedError, StoreBootRefusalError } from "./store-contracts.js";
5
+ import { stopThenStartAction } from "./stop-then-start-sentence.js";
5
6
  const QUOTES = new Set(["'", '"', "`"]);
6
7
  function stripComments(sql) {
7
8
  let out = "";
@@ -263,6 +264,117 @@ export function diffSchema(expected, live) {
263
264
  }
264
265
  return out;
265
266
  }
267
+ function claimedColumnsOf(change) {
268
+ switch (change.kind) {
269
+ case "drop":
270
+ return change.columns;
271
+ case "rename":
272
+ return [...change.from, change.to];
273
+ case "add":
274
+ return [change.column];
275
+ case "rebuild":
276
+ return undefined;
277
+ default: {
278
+ const unhandled = change;
279
+ throw new Error(`unknown pending shape change ${JSON.stringify(unhandled)}`);
280
+ }
281
+ }
282
+ }
283
+ const WHOLE_TABLE = "*";
284
+ function assertSingleOwnerPerShape(sources) {
285
+ const owners = new Map();
286
+ for (const s of sources) {
287
+ for (const change of s.shapeChanges ?? []) {
288
+ const table = owners.get(change.table) ?? new Map();
289
+ owners.set(change.table, table);
290
+ const columns = claimedColumnsOf(change);
291
+ const clashes = columns === undefined ? [...table.values()] : [table.get(WHOLE_TABLE), ...columns.map((c) => table.get(c))];
292
+ const clash = clashes.find((id) => id !== undefined && id !== s.id);
293
+ if (clash !== undefined)
294
+ throw new Error(`pending shape: table ${change.table} is changed by both ${clash} and ${s.id} on the same ${columns === undefined ? "table" : "column"} — one shape change has one migration`);
295
+ for (const c of columns ?? [WHOLE_TABLE])
296
+ table.set(c, s.id);
297
+ }
298
+ }
299
+ }
300
+ function renamedColumn(dialect, old, declared) {
301
+ switch (dialect) {
302
+ case "mysql":
303
+ return declared();
304
+ case "pg":
305
+ return old;
306
+ default: {
307
+ const unhandled = dialect;
308
+ throw new Error(`unknown SQL dialect ${String(unhandled)}`);
309
+ }
310
+ }
311
+ }
312
+ export function projectPendingShape(expected, live, sources, dialect) {
313
+ assertSingleOwnerPerShape(sources);
314
+ const out = new Map([...live].map(([t, cols]) => [t, new Map(cols)]));
315
+ const declaredNullable = (table, column) => {
316
+ const col = expected.get(table)?.columns.find((c) => c.name === column);
317
+ if (col === undefined)
318
+ throw new Error(`pending shape: ${table}.${column} is not declared by this build's schema — a migration can only bring a column the build declares`);
319
+ return { nullable: !declaresNotNull(col) };
320
+ };
321
+ const applied = new Set();
322
+ for (const s of sources) {
323
+ for (const change of s.shapeChanges ?? []) {
324
+ const before = live.get(change.table);
325
+ const after = out.get(change.table);
326
+ if (before === undefined || after === undefined)
327
+ continue;
328
+ switch (change.kind) {
329
+ case "drop": {
330
+ const present = change.columns.filter((c) => before.has(c));
331
+ for (const c of present)
332
+ after.delete(c);
333
+ if (present.length > 0)
334
+ applied.add(s.id);
335
+ break;
336
+ }
337
+ case "rename": {
338
+ const present = change.from.filter((c) => before.has(c));
339
+ if (before.has(change.to) || present.length !== 1)
340
+ break;
341
+ const [from] = present;
342
+ after.set(change.to, renamedColumn(dialect, before.get(from), () => declaredNullable(change.table, change.to)));
343
+ after.delete(from);
344
+ applied.add(s.id);
345
+ break;
346
+ }
347
+ case "add": {
348
+ if (before.has(change.column))
349
+ break;
350
+ const gate = change.whilePresent;
351
+ if (gate !== undefined && !gate.anyOf.some((c) => live.get(gate.table)?.has(c) === true))
352
+ break;
353
+ after.set(change.column, declaredNullable(change.table, change.column));
354
+ applied.add(s.id);
355
+ break;
356
+ }
357
+ case "rebuild": {
358
+ if (!before.has(change.whilePresent))
359
+ break;
360
+ const shape = expected.get(change.table);
361
+ if (shape === undefined)
362
+ throw new Error(`pending shape: ${change.table} is rebuilt by ${s.id} but this build declares no such table`);
363
+ if (shape.columns.some((c) => !change.supplies.includes(c.name) && !before.has(c.name)))
364
+ break;
365
+ out.set(change.table, new Map(shape.columns.map((c) => [c.name, { nullable: !declaresNotNull(c) }])));
366
+ applied.add(s.id);
367
+ break;
368
+ }
369
+ default: {
370
+ const unhandled = change;
371
+ throw new Error(`unknown pending shape change ${JSON.stringify(unhandled)}`);
372
+ }
373
+ }
374
+ }
375
+ }
376
+ return { projected: out, appliedBy: sources.filter((s) => applied.has(s.id)).map((s) => s.id) };
377
+ }
266
378
  function tightenLines(dialect, table, c) {
267
379
  switch (dialect) {
268
380
  case "pg":
@@ -337,34 +449,104 @@ export function describeMismatchedTables(dialect, mismatches) {
337
449
  lines.push("", ...SCHEMA_MISMATCH_WAYS_OUT);
338
450
  return lines;
339
451
  }
340
- export function describeSchemaMismatch(dialect, mismatches) {
452
+ function needsStatements(mismatches) {
453
+ return mismatches.some((m) => m.missing.length > 0 || m.tightened.length > 0);
454
+ }
455
+ export async function describeUpgradeChecklist(read, dialect, mismatches) {
456
+ if (!needsStatements(mismatches))
457
+ return [];
458
+ const { stopThenStartMigrations } = await import("./one-time-migrations.js");
459
+ const owed = [];
460
+ for (const m of stopThenStartMigrations()) {
461
+ let owes;
462
+ try {
463
+ owes = await m.owedOn(read, dialect);
464
+ }
465
+ catch (err) {
466
+ owed.push(` - ${m.id}: ${m.reason} (whether this database still owes it could not be read — ${err instanceof Error ? err.message : String(err)} — so it is listed)`);
467
+ continue;
468
+ }
469
+ if (owes)
470
+ owed.push(` - ${m.id}: ${m.reason}`);
471
+ }
472
+ const steps = [["Run every statement printed below, each recipe in the order it is printed (a printed comment line marks a value only you can choose) — all tables at once, before anything else (this build never alters a database for you)."]];
473
+ if (owed.length > 0) {
474
+ steps.push([
475
+ "Stop-then-start one-time migrations this database still owes: this build runs them itself on its first boot (no statement from you), and each refuses to start — writing nothing — while a run on this database has",
476
+ "heartbeated within REAP_RUN_STALE_SEC held by a replica not registered as knowing that migration (a replica of a release that predates replica registration never is).",
477
+ ...owed,
478
+ stopThenStartAction(),
479
+ ]);
480
+ }
481
+ steps.push([owed.length > 0 ? "Start this version; the stop-then-start migrations above run on that boot." : "Start this version."]);
482
+ return [
483
+ "Upgrade checklist — everything this build needs from you on this database, in this order (one start lists all of it; do not fix one table and retry):",
484
+ ...steps.flatMap((lines, i) => lines.map((l, k) => (k === 0 ? ` ${i + 1}. ${l}` : ` ${l}`))),
485
+ ];
486
+ }
487
+ export function describeSchemaMismatch(dialect, mismatches, checklist = []) {
341
488
  const lines = [
342
489
  `${SCHEMA_MISMATCH_ERROR_CODE}: the ${dialect === "pg" ? "PostgreSQL" : "MySQL-protocol"} database does not match this build's schema — refusing to start.`,
343
490
  "Schema policy: the code is the single source of truth, and the column check that produced this refusal never alters a database —",
344
491
  "so a drifted database is a deployment-order problem, not something the process may quietly work around.",
345
492
  "",
493
+ ...(checklist.length > 0 ? [...checklist, ""] : []),
346
494
  ...describeMismatchedTables(dialect, mismatches),
347
495
  ];
348
496
  return lines.join("\n");
349
497
  }
350
- export async function assertSchemaMatchesCode(read, dialect) {
498
+ export async function statementBearing(expected, live, dialect) {
499
+ const { ONE_TIME_MIGRATIONS } = await import("./one-time-migrations.js");
500
+ return diffSchema(expected, projectPendingShape(expected, live, ONE_TIME_MIGRATIONS, dialect).projected)
501
+ .map((m) => ({ ...m, ghost: [] }))
502
+ .filter((m) => m.missing.length > 0 || m.tightened.length > 0);
503
+ }
504
+ export async function describeUpgradeBody(read, dialect, mismatches) {
505
+ const checklist = await describeUpgradeChecklist(read, dialect, mismatches);
506
+ return [...(checklist.length > 0 ? [...checklist, ""] : []), ...describeMismatchedTables(dialect, mismatches)];
507
+ }
508
+ export const DRIFT_TAIL_DEADLINE_MS = 10_000;
509
+ export async function describeUpgradeDriftTail(read, dialect, deadlineMs = DRIFT_TAIL_DEADLINE_MS) {
510
+ let timer;
511
+ try {
512
+ const work = (async () => {
513
+ const mismatches = await statementBearing(await expectedTableShapes(dialect), await readLiveColumns(read, dialect), dialect);
514
+ if (mismatches.length === 0)
515
+ return "";
516
+ return `\n\nThis database also does not match this build's schema — the statements below are the ones the column check prints at boot:\n${(await describeUpgradeBody(read, dialect, mismatches)).join("\n")}`;
517
+ })();
518
+ work.catch(() => undefined);
519
+ const expired = new Promise((_, reject) => {
520
+ timer = setTimeout(() => reject(new Error(`no answer within ${deadlineMs} ms`)), deadlineMs);
521
+ timer.unref?.();
522
+ });
523
+ return await Promise.race([work, expired]);
524
+ }
525
+ catch (err) {
526
+ return `\n\nThe column check could not be read here (${err instanceof Error ? err.message : String(err)}), so any ALTER this database still owes is not listed in this refusal — the column check at boot lists them.`;
527
+ }
528
+ finally {
529
+ clearTimeout(timer);
530
+ }
531
+ }
532
+ export async function assertSchemaMatchesCode(read, dialect, pending) {
351
533
  const expected = await expectedTableShapes(dialect);
352
534
  let live;
353
535
  try {
354
536
  live = await readLiveColumns(read, dialect);
355
537
  }
356
538
  catch (err) {
357
- throw new Error(`${SCHEMA_MISMATCH_ERROR_CODE}: could not read information_schema.columns to verify the ${dialect} schema — refusing to start ` +
539
+ throw new SchemaMismatchRefusedError(`${SCHEMA_MISMATCH_ERROR_CODE}: could not read information_schema.columns to verify the ${dialect} schema — refusing to start ` +
358
540
  `(an unverifiable schema is treated as a mismatch, not as a pass): ${err instanceof Error ? err.message : String(err)}`, { cause: err });
359
541
  }
360
542
  if (live.size === 0) {
361
- throw new Error(`${SCHEMA_MISMATCH_ERROR_CODE}: information_schema.columns returned no rows anywhere on this ${dialect} connection's schema path — refusing to start. ` +
543
+ throw new SchemaMismatchRefusedError(`${SCHEMA_MISMATCH_ERROR_CODE}: information_schema.columns returned no rows anywhere on this ${dialect} connection's schema path — refusing to start. ` +
362
544
  `Either the connection points at an empty/foreign schema, or the account cannot see its own catalog; both make the ` +
363
545
  `"database matches the code" check unprovable, and an unprovable protective check never passes.`);
364
546
  }
365
- const mismatches = diffSchema(expected, live);
547
+ const mismatches = diffSchema(expected, projectPendingShape(expected, live, pending, dialect).projected);
366
548
  if (mismatches.length > 0)
367
- throw new Error(describeSchemaMismatch(dialect, mismatches));
549
+ throw new SchemaMismatchRefusedError(describeSchemaMismatch(dialect, mismatches, await describeUpgradeChecklist(read, dialect, mismatches)));
368
550
  }
369
551
  const RETIRED_SNAPSHOT_TABLE = "snapshot_manifest";
370
552
  const RETIRED_STEER_SEAT = { table: "checkpoint", column: "pending_steer" };
@@ -1,4 +1,5 @@
1
1
  import type { SqlDialect } from "./sql-driver.js";
2
+ import { SchemaMismatchRefusedError } from "./store-contracts.js";
2
3
  /** 「库比构建旧」那一条出路的两个可变件:旧在哪(括注)、部署者要自己做的那件事。其余字一个不变。 */
3
4
  export interface OlderWayOut {
4
5
  /** 括注:库旧在哪(例:`missing columns`)。 */
@@ -20,11 +21,11 @@ export declare const SCHEMA_MISMATCH_WAYS_OUT: readonly string[];
20
21
  export type ProbeQuery = (sql: string) => Promise<{
21
22
  rows: Record<string, unknown>[];
22
23
  }>;
24
+ /** 单表探针的拒启(缺列、键宽不够)的出口:`body` 是码之后的整句,码在这里领头 —— 各探针不自己拼码。 */
25
+ export declare function schemaMismatchRefusal(body: string): SchemaMismatchRefusedError;
23
26
  /**
24
- * 单表探针抓到可证缺列后,拒因里「哪几列缺、该跑哪几条 ALTER」+ 闭集出路这一整段(多行文本,不含探针自己的首句与底层错误)。
25
- *
26
- * 读不出库的列册(权限 / 超时)或对账看不到探针点名的表有缺列(探针与建表语句互相矛盾)⇒ **响亮**地说「ALTER 没能打印」及原因,
27
- * 出路仍是同一份闭集(旧一条改成「按建表语句自己加」)—— 绝不退回销毁性指路,也不悄悄吞掉这一段。
27
+ * 单表探针抓到可证缺列后的**整条拒启**(唯一铸点;`throw await probedMissingColumnRefusal(…)`):探针自己的首句 `lead`(这张表缺什么、缺了会在哪里坏;不带码、不带换行收尾)
28
+ * + 底层驱动错误 `underlying`(运维要看见是哪条探针读撞的)+ {@link describeProbedTableDrift} 的逐表段与闭集出路,码领头({@link schemaMismatchRefusal})。
28
29
  */
29
- export declare function describeProbedTableDrift(query: ProbeQuery, dialect: SqlDialect, tables: readonly string[]): Promise<string>;
30
+ export declare function probedMissingColumnRefusal(query: ProbeQuery, dialect: SqlDialect, tables: readonly string[], lead: string, underlying: unknown): Promise<SchemaMismatchRefusedError>;
30
31
  //# sourceMappingURL=schema-drift-sentence.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import { readLiveColumns } from "./schema-live-columns.js";
2
+ import { SCHEMA_MISMATCH_ERROR_CODE, SchemaMismatchRefusedError } from "./store-contracts.js";
2
3
  export function schemaWaysOut(older) {
3
4
  return [
4
5
  " Ways out (these three and no others):",
@@ -11,16 +12,23 @@ export const SCHEMA_MISMATCH_WAYS_OUT = schemaWaysOut({
11
12
  when: "missing columns, or columns that allow NULL where this build declares NOT NULL",
12
13
  action: "run the statements printed above yourself (where a column needs a value this build cannot choose for rows already in the table, the printed comment says so and that value is yours; this refusal stays until every printed statement has taken effect)",
13
14
  });
14
- export async function describeProbedTableDrift(query, dialect, tables) {
15
+ export function schemaMismatchRefusal(body) {
16
+ return new SchemaMismatchRefusedError(`${SCHEMA_MISMATCH_ERROR_CODE}: ${body}`);
17
+ }
18
+ export async function probedMissingColumnRefusal(query, dialect, tables, lead, underlying) {
19
+ return schemaMismatchRefusal(`${lead}\n` +
20
+ `Underlying probe error: ${underlying instanceof Error ? underlying.message : String(underlying)}\n\n` +
21
+ (await describeProbedTableDrift(query, dialect, tables)));
22
+ }
23
+ async function describeProbedTableDrift(query, dialect, tables) {
15
24
  const d = dialect === "pg" ? "pg" : "mysql";
16
25
  const read = async (sql) => (await query(sql)).rows;
17
26
  let why;
18
27
  try {
19
28
  const sc = await import("./schema-consistency.js");
20
- const diff = sc.diffSchema(await sc.expectedTableShapes(d), await readLiveColumns(read, d));
21
- const missingOnly = diff.filter((m) => tables.includes(m.table) && m.missing.length > 0).map((m) => ({ ...m, ghost: [], tightened: [] }));
22
- if (missingOnly.length > 0)
23
- return sc.describeMismatchedTables(d, missingOnly).join("\n");
29
+ const diff = await sc.statementBearing(await sc.expectedTableShapes(d), await readLiveColumns(read, d), d);
30
+ if (diff.some((m) => tables.includes(m.table) && m.missing.length > 0))
31
+ return (await sc.describeUpgradeBody(read, d, diff)).join("\n");
24
32
  why = "this build's table definitions list no column that the database lacks on that table, so the probe and the definitions disagree";
25
33
  }
26
34
  catch (err) {
@@ -0,0 +1,49 @@
1
+ /**
2
+ * **起服准入**(SQL 两方言;常设)—— 「这只库要求每一只服务者有哪些能力」一张表,与本构建的能力名单比:库上记着本构建没有的能力 ⇒ 拒启,本构建不迁移、不服务。
3
+ *
4
+ * ## 律
5
+ * · 一行一能力:`migration_id` = 能力名(= 一次性迁移名,`replica-registration.ts` 的 {@link REPLICA_CAPABILITIES} 同一词表),`state` 两词闭集
6
+ * {@link SERVING_REQUIREMENT_STATES}:`pending`(要求方已认领、还没做任何不兼容写)/ `in_force`(不兼容写已开始或已完成)。两态都要求;读出词表外的词 ⇒ 当作 `in_force`,拒句点名那一行。
7
+ * · 判据 = `knows(本构建, M)`(`knowsCapability`,与停-迁-起证据读法同一只谓词);发布线不进判据(同版本串的构建可以能力不同)。
8
+ * · 两次读,同一判据:SQL 一次性迁移段头、取迁移锁之前读一次(锁外预读);取到迁移锁之后、第一步迁移之前在锁连接上再读一次(锁内复核)——
9
+ * 在本进程等锁时握着锁的那一只若写下了要求,锁内复核读得到。拒 ⇒ 本构建不迁移;库上只留本进程的登记行(段头第一步)。
10
+ * · 表不在(跳过了 `ensureSchema()` 的嵌入方)⇒ 拒:证不出「没有要求」不是「没有要求」。
11
+ * · 本构建不写本表(没有写者;写者随之后的停-迁-起迁移来)—— 表空 ⇒ 准入恒过。装早一版:从本版起的每一只构建都读它,之后写下的要求挡得住它们重启开服。
12
+ *
13
+ * ## 射程(如实)
14
+ * 挡的是**起服者**:已在服务的副本不再读本表;准入与开服之间的停顿也不在它的射程里。早于本表那一版的构建不读本表。
15
+ */
16
+ import { StoreBootRefusalError } from "./store-contracts.js";
17
+ import { type ReplicaRegistration } from "./replica-registration.js";
18
+ import { type UnmetServingRequirement } from "./stop-then-start-sentence.js";
19
+ import type { SqlDialect, SqlExec } from "./sql-driver.js";
20
+ /** 表名。 */
21
+ export declare const SERVING_REQUIREMENT_TABLE = "serving_requirement";
22
+ /** 要求行的状态词(闭集)。 */
23
+ export declare const SERVING_REQUIREMENT_STATES: readonly ["pending", "in_force"];
24
+ export type ServingRequirementState = (typeof SERVING_REQUIREMENT_STATES)[number];
25
+ /** 拒启码(确定的拒启表态;继承 {@link StoreBootRefusalError}:开库等待窗不重试、降级臂不吞)。 */
26
+ export declare const SERVING_REQUIREMENT_UNMET_CODE = "config.serving_requirement_unmet";
27
+ /** 拒因闭集:库要求的能力本构建没有 / 准入读的表不在库里(证不出)。 */
28
+ export type ServingRequirementRefusalReason = "capability_missing" | "table_absent";
29
+ /** 建表句(两方言;幂等)。中央建表语句读它(`tidb-pool.ts` / `pg-pool.ts`)。 */
30
+ export declare function servingRequirementDdl(dialect: SqlDialect): string;
31
+ /** 起服准入拒。 */
32
+ export declare class ServingRequirementUnmetError extends StoreBootRefusalError {
33
+ readonly reason: ServingRequirementRefusalReason;
34
+ /** `capability_missing`:本构建没有的那几行(按能力名排序);`table_absent`:空。 */
35
+ readonly unmet: readonly UnmetServingRequirement[];
36
+ /** `table_absent`:不在库里的那几张表;`capability_missing`:空。 */
37
+ readonly absentTables: readonly string[];
38
+ readonly code = "config.serving_requirement_unmet";
39
+ constructor(reason: ServingRequirementRefusalReason,
40
+ /** `capability_missing`:本构建没有的那几行(按能力名排序);`table_absent`:空。 */
41
+ unmet: readonly UnmetServingRequirement[],
42
+ /** `table_absent`:不在库里的那几张表;`capability_missing`:空。 */
43
+ absentTables: readonly string[], releaseLine: string);
44
+ }
45
+ /** 准入读的两张表(本表与段头写的登记表)都在库里;不在 ⇒ 拒(`table_absent`),点名公开序列。 */
46
+ export declare function assertReplicaTablesPresent(db: SqlExec, dialect: SqlDialect, me: ReplicaRegistration): Promise<void>;
47
+ /** 准入:库上每一行要求的能力本构建都有 ⇒ 过;否则拒(`capability_missing`,点名每一行)。纯读;两方言同一句(无占位符)。 */
48
+ export declare function assertServingRequirementsMet(db: SqlExec, me: ReplicaRegistration): Promise<void>;
49
+ //# sourceMappingURL=serving-requirement.d.ts.map
@@ -0,0 +1,53 @@
1
+ import { StoreBootRefusalError } from "./store-contracts.js";
2
+ import { REPLICA_REGISTRATION_TABLE, knowsCapability } from "./replica-registration.js";
3
+ import { servingRequirementUnmetSentence } from "./stop-then-start-sentence.js";
4
+ export const SERVING_REQUIREMENT_TABLE = "serving_requirement";
5
+ export const SERVING_REQUIREMENT_STATES = ["pending", "in_force"];
6
+ export const SERVING_REQUIREMENT_UNMET_CODE = "config.serving_requirement_unmet";
7
+ export function servingRequirementDdl(dialect) {
8
+ return dialect === "mysql"
9
+ ? `CREATE TABLE IF NOT EXISTS ${SERVING_REQUIREMENT_TABLE} (migration_id VARCHAR(16) NOT NULL PRIMARY KEY, state VARCHAR(16) NOT NULL, required_at_ms BIGINT NOT NULL) COLLATE utf8mb4_bin`
10
+ : `CREATE TABLE IF NOT EXISTS ${SERVING_REQUIREMENT_TABLE} (migration_id VARCHAR(16) COLLATE "C" NOT NULL PRIMARY KEY, state VARCHAR(16) COLLATE "C" NOT NULL, required_at_ms BIGINT NOT NULL)`;
11
+ }
12
+ export class ServingRequirementUnmetError extends StoreBootRefusalError {
13
+ reason;
14
+ unmet;
15
+ absentTables;
16
+ code = SERVING_REQUIREMENT_UNMET_CODE;
17
+ constructor(reason, unmet, absentTables, releaseLine) {
18
+ super(servingRequirementUnmetSentence(SERVING_REQUIREMENT_UNMET_CODE, reason, unmet, absentTables, releaseLine));
19
+ this.reason = reason;
20
+ this.unmet = unmet;
21
+ this.absentTables = absentTables;
22
+ this.name = "ServingRequirementUnmetError";
23
+ }
24
+ }
25
+ export async function assertReplicaTablesPresent(db, dialect, me) {
26
+ const scope = dialect === "mysql" ? "DATABASE()" : "current_schema()";
27
+ const absent = [];
28
+ for (const table of [REPLICA_REGISTRATION_TABLE, SERVING_REQUIREMENT_TABLE]) {
29
+ const { rows } = await db.query(`SELECT 1 AS present FROM information_schema.tables WHERE table_schema = ${scope} AND table_name = '${table}'`);
30
+ if (rows.length === 0)
31
+ absent.push(table);
32
+ }
33
+ if (absent.length > 0)
34
+ throw new ServingRequirementUnmetError("table_absent", [], absent, me.releaseLine);
35
+ }
36
+ function stateOf(cell) {
37
+ const word = String(cell);
38
+ return SERVING_REQUIREMENT_STATES.includes(word) ? { state: word, word } : { state: "in_force", word };
39
+ }
40
+ export async function assertServingRequirementsMet(db, me) {
41
+ const { rows } = await db.query(`SELECT migration_id, state FROM ${SERVING_REQUIREMENT_TABLE} ORDER BY migration_id`);
42
+ const unmet = [];
43
+ for (const r of rows) {
44
+ const migration = String(r["migration_id"]);
45
+ if (knowsCapability(me.capabilities, migration))
46
+ continue;
47
+ const { state, word } = stateOf(r["state"]);
48
+ unmet.push({ migration, state, word });
49
+ }
50
+ if (unmet.length > 0)
51
+ throw new ServingRequirementUnmetError("capability_missing", unmet, [], me.releaseLine);
52
+ }
53
+ //# sourceMappingURL=serving-requirement.js.map
@@ -37,8 +37,9 @@ import { type PlacedSessionRow, type SessionPlacement, type SessionPlacementReco
37
37
  import type { ForkPlacement } from "./session-fork.js";
38
38
  /**
39
39
  * 三只持久会话店(SQL 两孪生 + local 后端)的**会话耐久声明**(core `SessionStore.durability`:未 placed 的会话活不活过进程重启)——
40
- * **单源**:三处声明(两孪生类上的顶层 `durability`、`LocalBackend` 交给店的顶层 `durability`)与委派分区的 {@link SESSION_PLACEMENTS}
41
- * 引用的都是这一只常量,源码里不再有第二份 `"durable"` 字面(`test/session-store-durability-declaration.test.ts` 的 AST 钉)。
40
+ * **单源**:两孪生类上的顶层 `durability` 与委派分区的 {@link SESSION_PLACEMENTS} 引用的都是这一只常量;local 后端交给两半店(会话店顶层、
41
+ * 检查点店)的声明由 `local-store-declaration.ts` 的读者定,它的「店型的设计声明」一臂就是这一只常量(运维声明易失 / 店落在调用方自己那块易失盘上 ⇒ `process-local`)。
42
+ * 源码里不再有第二份 `"durable"` 字面(`test/session-store-durability-declaration.test.ts` 的 AST 钉)。
42
43
  *
43
44
  * 为什么必须声明:core 的静态装配对**缺席**折 `"process-local"`(它不能替没表态的店猜),接线清单 `session.store` 与起服自检读的就是它 ——
44
45
  * 不声明 ⇒ 一个真持久的 SQL 部署读成「重启即丢」,没有任何一处报错。依据同下面分区声明那一段:两孪生的会话史落在 `session_meta` + `session_event` 两张真表,
@@ -47,8 +48,8 @@ import type { ForkPlacement } from "./session-fork.js";
47
48
  */
48
49
  export declare const SESSION_DURABILITY: "durable";
49
50
  /**
50
- * 三只持久会话店的 placement **声明**(core `SessionStore.placements`):两支 SQL 孪生(类上的字段)与 local 后端
51
- * (`LocalBackend` 装配 `LocalSessionStore` 时交进去 —— 值与 core `FileStorageBackend` 对同一种文件 repo 的声明同)。
51
+ * 两支 SQL 孪生的 placement **声明**(core `SessionStore.placements`,类上的字段)。local 后端装配 `LocalSessionStore` 时交进去的分区声明与它同形,
52
+ * 耐久取 local 店的那一份声明(`local-store-declaration.ts`;设计声明一臂与这里同值,与 core `FileStorageBackend` 对同一种文件 repo 的声明同)。
52
53
  *
53
54
  * 🔴 声明制,不是鸭子类型:core 的铸造门判「row store ∧ session 店声明」,不声明的店**不收** placed
54
55
  * 语义(合法降级:placement 参数被忽略,会话就是普通会话)。所以这一格只有在上面六条义务**全部**在
@@ -1,3 +1,4 @@
1
+ import type { SqlDialect } from "./sql-driver.js";
1
2
  /**
2
3
  * 一次性迁移 **M48** 的唯一属主(窗、删法与删后谁接住:窗口见 `plugins/one-time-migrations.ts` 登记表;随 M48 删整个文件)。
3
4
  *
@@ -13,8 +14,29 @@
13
14
  * 改写面 = 曾放那一格的读面读属主的那几处:SQL `session_meta` / `task_run` / `leader_run` 的 `owner` 列;local 会话元数据边档那一格的 `owner`、
14
15
  * `FileRunStore` 的 run 行 `owner`(local 没有 leader run 店)。不改的(如实):规则行(存在哨兵键下的规则此前也不对无主体任务生效,改键 = 放宽,不做)、
15
16
  * 收件箱条目 / 问账行 / 检查点 scope(这些读面从没放过那一格)。
17
+ *
18
+ * **有身份边界的部署不改写,但不零信号**:部署形读数取自**起服的那一个进程**的配置,而改写落在整只库 / 数据根上、不可逆 ⇒ 同一只库 / 根被身份配置不同的进程打开
19
+ * (`run-local` 不带服务凭据 / 桥、嵌入方先于验签器装配的起服序列、连着共享库的临时进程)时,一样身份键都不带的那个进程先起,会替有边界的服务进程把行改成无主(边界是任一身份来源在的并 —— 改写只在一样都没有时发生)。
20
+ * 有边界的进程自己只看见「零改写」,不数一数就不知道库里还留着多少行 ⇒ 那一臂对**同一批改写面**用**同一个比较**数一遍(不写,不改),读数 {@link ShellSentinelOwnerRowsRetained}
21
+ * 逐面列非零的计数,起服日志一行 warn(`shell_sentinel_rows_retained`)。比较只有一份:SQL 的 {@link shellSentinelOwnerWhereM48}(改写的 `UPDATE` 与计数的 `SELECT COUNT(*)` 共用),
22
+ * local 的 {@link isShellSentinelOwnerM48}(会话元数据格与 run 行的改写和计数共用)。
16
23
  */
17
24
  export declare const SHELL_SENTINEL_OWNER_M48 = "anon:shell-live";
18
25
  /** SQL 腿改写 `owner` 列的表(曾放那一格的读面读属主的表;local 腿的对应面见文件头)。 */
19
26
  export declare const SHELL_SENTINEL_OWNER_TABLES_M48: readonly ["session_meta", "task_run", "leader_run"];
27
+ /** 改写面的词(留存读数与日志的逐面计数键):SQL = 表名;local 腿借同一组词 —— `session_meta` = 会话元数据格、`task_run` = run 行(local 没有 leader run 店 ⇒ 不出 `leader_run`)。 */
28
+ export type ShellSentinelOwnerSurfaceM48 = (typeof SHELL_SENTINEL_OWNER_TABLES_M48)[number];
29
+ /** 留存读数的一只:有身份边界的部署上,这一面还留着多少行哨兵属主(只列非零的面)。 */
30
+ export interface ShellSentinelOwnerRowsRetained {
31
+ readonly surface: ShellSentinelOwnerSurfaceM48;
32
+ readonly rows: number;
33
+ }
34
+ /**
35
+ * SQL 腿「这一行是哨兵属主」的比较(**唯一一份**;改写的 `UPDATE … SET owner = NULL WHERE <此>` 与留存计数的 `SELECT COUNT(*) … WHERE <此>` 共用,绑定参数同为 {@link SHELL_SENTINEL_OWNER_M48})。
36
+ * 值**逐字节**比:MySQL 协议的 `utf8mb4_bin` 是 PAD SPACE(`'a' = 'a '`),普通等值会把只差尾空格的另一位属主(合法主体名)也当哨兵 ——
37
+ * 与 M17 / M18 的 CAS 同一写法 `CAST(… AS BINARY)`;PG `text` 等值本就逐字节。
38
+ */
39
+ export declare function shellSentinelOwnerWhereM48(dialect: SqlDialect): string;
40
+ /** local 腿「这一行 / 格的属主是哨兵」的比较(**唯一一份**;会话元数据格与 run 行的改写、留存计数共用 —— 严格相等,与 SQL 腿的逐字节比同义)。 */
41
+ export declare function isShellSentinelOwnerM48(owner: string | null | undefined): boolean;
20
42
  //# sourceMappingURL=shell-sentinel-owner-m48.d.ts.map
@@ -1,3 +1,9 @@
1
1
  export const SHELL_SENTINEL_OWNER_M48 = "anon:shell-live";
2
2
  export const SHELL_SENTINEL_OWNER_TABLES_M48 = ["session_meta", "task_run", "leader_run"];
3
+ export function shellSentinelOwnerWhereM48(dialect) {
4
+ return dialect === "mysql" ? "CAST(owner AS BINARY) = CAST(? AS BINARY)" : "owner = $1";
5
+ }
6
+ export function isShellSentinelOwnerM48(owner) {
7
+ return owner === SHELL_SENTINEL_OWNER_M48;
8
+ }
3
9
  //# sourceMappingURL=shell-sentinel-owner-m48.js.map
@@ -0,0 +1,55 @@
1
+ /**
2
+ * 「停-迁-起」那一段的**唯一属主**。
3
+ *
4
+ * 一次性迁移里有几条(登记表 `stopThenStart` 一格点名的)改的是更早版本的副本还会继续写的形:改完之后它再写一笔,这一笔读不出 / 丢掉。它们没有运行期开关,
5
+ * 起服时由守卫判 —— 库上有「不知道这条迁移的副本还在服务」的证据 ⇒ 拒启、一字节不写。证据只有一种读法(`one-time-migrations.ts` 的 `readOlderReplicaEvidence`):
6
+ * 判活窗内(`REAP_RUN_STALE_SEC`,收割器判 run 持有者还活着的同一个窗)心跳过的 `running` run,持有者没有登记为知道这条迁移(`replica-registration.ts`)。
7
+ * 四只守卫(M40 · M41 · M42 · M51)的拒句和起服列册对账的升级清单**说同一件事**:先停光更早版本的副本、等满判活窗、再起本版。
8
+ * 这件事的句子只在这里写一遍:证据一段({@link olderReplicaEvidenceSentence})与动作一段({@link stopThenStartAction});各守卫只讲「为什么这一条要停」。
9
+ * 起服准入(`serving-requirement.ts`:库上记着本构建没有的能力 ⇒ 拒启)的拒句同属这一族,也在这里({@link servingRequirementUnmetSentence})。
10
+ *
11
+ * 叶子模块:零 import(守卫所在的一次性迁移文件、M42 的拒句文件、起服准入与列册对账都静态 import 它,不成环)。
12
+ */
13
+ /** 证据里一只登记过、却没有这条迁移能力的持有者(归因用:点名它是谁)。 */
14
+ export interface OlderReplicaHolder {
15
+ readonly instanceId: string;
16
+ readonly releaseLine: string;
17
+ readonly hostName: string;
18
+ readonly pid: number;
19
+ /** 它持有的、判活窗内心跳过的 `running` run 数。 */
20
+ readonly runs: number;
21
+ }
22
+ /**
23
+ * 停-迁-起守卫的证据(纯数据):判活窗内心跳过、持有者没有登记为知道 {@link migration} 的 `running` run。
24
+ * 登记为知道它的持有者的 run 不在这里(它们不是旧形写者);登记只作归因,不判活。
25
+ */
26
+ export interface OlderReplicaEvidence {
27
+ readonly migration: string;
28
+ /** 算进证据的 run 总数(= {@link unregisteredRuns} + 各 {@link registeredWithout} 的 `runs`)。 */
29
+ readonly liveRuns: number;
30
+ readonly windowSec: number;
31
+ /** 全停之后可以再起的最早时刻(ISO;= 最新心跳 + 判活窗;读不出最新心跳 ⇒ 现在 + 窗,上界)。 */
32
+ readonly restartAfter: string;
33
+ /** 同一时刻的人读形(带「离现在还有几秒」)。 */
34
+ readonly after: string;
35
+ /** 持有者没有登记行(或没有持有者)的 run 数。 */
36
+ readonly unregisteredRuns: number;
37
+ /** 登记了而能力名单里没有这条迁移的持有者,逐只点名。 */
38
+ readonly registeredWithout: readonly OlderReplicaHolder[];
39
+ }
40
+ /** 证据一段(各守卫拒句与 M43 的读数共用;句子不含时刻 —— 时刻在动作段)。 */
41
+ export declare function olderReplicaEvidenceSentence(e: OlderReplicaEvidence): string;
42
+ /**
43
+ * 动作段:停光 → 等窗 → 再起。`after` = 全停之后可以再起的最早时刻(守卫读到的最新心跳 + 判活窗,人读形);
44
+ * 缺席 ⇒ 读不到具体心跳(列册对账的清单:此刻没有守卫在判),只说「等满判活窗」。
45
+ */
46
+ export declare function stopThenStartAction(after?: string): string;
47
+ /** 起服准入里一行本构建没有的要求(`state` = 判读的态,词表外的词读作 `in_force`;`word` = 库里存的原词)。 */
48
+ export interface UnmetServingRequirement {
49
+ readonly migration: string;
50
+ readonly state: "pending" | "in_force";
51
+ readonly word: string;
52
+ }
53
+ /** 起服准入的拒句(`reason` 闭集两词;码由调用方交,本模块零 import)。 */
54
+ export declare function servingRequirementUnmetSentence(code: string, reason: "capability_missing" | "table_absent", unmet: readonly UnmetServingRequirement[], absentTables: readonly string[], releaseLine: string): string;
55
+ //# sourceMappingURL=stop-then-start-sentence.d.ts.map
@@ -0,0 +1,51 @@
1
+ export function olderReplicaEvidenceSentence(e) {
2
+ const parts = [];
3
+ if (e.unregisteredRuns > 0)
4
+ parts.push(`${e.unregisteredRuns} by replicas with no registration on this database (a release that predates replica registration, or a process that skipped the start sequence)`);
5
+ for (const h of e.registeredWithout)
6
+ parts.push(`${h.runs} by instance ${h.instanceId} (release ${h.releaseLine}, host ${h.hostName}, pid ${h.pid}), registered without it`);
7
+ return (`${e.liveRuns} running run(s) on this database heartbeated within the last ${e.windowSec}s (REAP_RUN_STALE_SEC), held by replicas not registered as knowing one-time migration ${e.migration}` +
8
+ ` (${parts.join("; ")})`);
9
+ }
10
+ export function stopThenStartAction(after) {
11
+ return ("Stop every replica of the earlier version, then start this version (stop, migrate, start — this release cannot be rolled across replicas of both versions). " +
12
+ (after === undefined
13
+ ? "Wait out the heartbeat window (REAP_RUN_STALE_SEC) after the last replica stops, then start this version."
14
+ : `If every replica is already stopped, those are the runs they left behind: start this version again after ${after}`));
15
+ }
16
+ function requirementClause(r) {
17
+ switch (r.state) {
18
+ case "pending":
19
+ return `${r.migration} (pending: a newer build recorded it and has not changed any data for it yet)`;
20
+ case "in_force":
21
+ return r.word === "in_force"
22
+ ? `${r.migration} (in force: data has been changed for it)`
23
+ : `${r.migration} (state "${r.word}", which this build does not know — read as in force)`;
24
+ default: {
25
+ const unhandled = r.state;
26
+ throw new Error(`stop-then-start-sentence: unknown requirement state ${String(unhandled)}`);
27
+ }
28
+ }
29
+ }
30
+ export function servingRequirementUnmetSentence(code, reason, unmet, absentTables, releaseLine) {
31
+ switch (reason) {
32
+ case "capability_missing": {
33
+ const inForce = unmet.filter((r) => r.state === "in_force").map((r) => r.migration);
34
+ return (`${code} (reason: capability_missing): this database requires every serving replica to have ${unmet.map(requirementClause).join(", ")}; this build (release ${releaseLine}) does not. ` +
35
+ "No one-time migration ran and no stored data was changed (the section head only registered this replica and removed registrations of other replicas older than 90 days). " +
36
+ `Start a build that has ${unmet.map((r) => r.migration).join(", ")}.` +
37
+ (inForce.length === 0
38
+ ? ""
39
+ : ` Only to roll back on purpose, after handling the data of that migration: ${inForce.map((m) => `DELETE FROM serving_requirement WHERE migration_id = '${m}';`).join(" ")}`));
40
+ }
41
+ case "table_absent":
42
+ return (`${code} (reason: table_absent): this database has no ${absentTables.join(" / ")} table — the serving requirements recorded there cannot be read, ` +
43
+ 'and an unreadable requirement is not "no requirement", so this build refuses to start. Nothing was written. ' +
44
+ "Open the store with the public sequence createStoreBackend() → ensureSchema() → runOneTimeMigrations(): ensureSchema() creates these tables.");
45
+ default: {
46
+ const unhandled = reason;
47
+ throw new Error(`stop-then-start-sentence: unknown refusal reason ${String(unhandled)}`);
48
+ }
49
+ }
50
+ }
51
+ //# sourceMappingURL=stop-then-start-sentence.js.map