@sema-agent/server 7.148.0 → 7.149.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 (247) hide show
  1. package/MIGRATION.md +429 -0
  2. package/README.md +16 -1
  3. package/README.zh-CN.md +4 -0
  4. package/USAGE.md +89 -55
  5. package/dist/adoption/wire.d.ts +3 -3
  6. package/dist/approval-card.d.ts +32 -32
  7. package/dist/approval-leg-origin.d.ts +33 -0
  8. package/dist/approval-leg-origin.js +4 -0
  9. package/dist/auth-bridge.d.ts +9 -1
  10. package/dist/auth-bridge.js +4 -4
  11. package/dist/auth-keys.d.ts +10 -1
  12. package/dist/auth-keys.js +8 -0
  13. package/dist/boot/execution-env.js +10 -11
  14. package/dist/boot/execution-lane.d.ts +6 -4
  15. package/dist/boot/execution-lane.js +3 -1
  16. package/dist/boot/org-delivery.d.ts +3 -2
  17. package/dist/boot/resolve-spec.js +6 -4
  18. package/dist/boot/runner-deps.js +3 -1
  19. package/dist/boot/stage-01-config.d.ts +1 -0
  20. package/dist/boot/stage-01-config.js +4 -3
  21. package/dist/boot/stage-02-web-admin-pull.d.ts +1 -0
  22. package/dist/boot/stage-02-web-admin-pull.js +10 -2
  23. package/dist/boot/stage-03-store-backend.d.ts +1 -0
  24. package/dist/boot/stage-03-store-backend.js +2 -2
  25. package/dist/boot/stage-04-stores.d.ts +1 -0
  26. package/dist/boot/stage-05-execution-env.d.ts +1 -0
  27. package/dist/boot/stage-06-runners.d.ts +2 -1
  28. package/dist/boot/stage-07-capability-layer.d.ts +2 -1
  29. package/dist/boot/stage-08-reapers.d.ts +2 -1
  30. package/dist/boot/stage-09-leader.d.ts +2 -1
  31. package/dist/boot/stage-10-http-server.d.ts +2 -1
  32. package/dist/boot/stores.d.ts +3 -0
  33. package/dist/boot/stores.js +22 -19
  34. package/dist/capabilities/tool-defer.d.ts +1 -1
  35. package/dist/capability-facts.d.ts +5 -4
  36. package/dist/capability-facts.js +3 -14
  37. package/dist/card-settle-stored-scope.d.ts +56 -0
  38. package/dist/card-settle-stored-scope.js +27 -0
  39. package/dist/card-void.d.ts +7 -12
  40. package/dist/card-void.js +2 -7
  41. package/dist/config-catalog.d.ts +12 -0
  42. package/dist/config-catalog.js +36 -32
  43. package/dist/config-invariants.d.ts +5 -4
  44. package/dist/config-invariants.js +4 -4
  45. package/dist/config-types.d.ts +37 -5
  46. package/dist/config.d.ts +6 -5
  47. package/dist/config.js +35 -24
  48. package/dist/context-window-notice.d.ts +31 -0
  49. package/dist/context-window-notice.js +28 -0
  50. package/dist/deployment-shape.d.ts +20 -11
  51. package/dist/deployment-shape.js +1 -0
  52. package/dist/env-name-allowlist-knobs.d.ts +6 -5
  53. package/dist/env-name-allowlist-knobs.js +5 -0
  54. package/dist/execution-lane-caps.d.ts +20 -5
  55. package/dist/execution-lane-caps.js +18 -4
  56. package/dist/hooks/command-spawn.d.ts +7 -2
  57. package/dist/hooks/command-spawn.js +13 -6
  58. package/dist/hooks/hook-runner.d.ts +5 -0
  59. package/dist/hooks/hook-runner.js +11 -7
  60. package/dist/http/admission.js +3 -3
  61. package/dist/http/approval-sweeps.js +3 -2
  62. package/dist/http/claim-heartbeat.d.ts +3 -3
  63. package/dist/http/claim-heartbeat.js +3 -3
  64. package/dist/http/claimed-run.d.ts +16 -9
  65. package/dist/http/claimed-run.js +8 -0
  66. package/dist/http/dispatch.js +5 -3
  67. package/dist/http/durable-run-leg.d.ts +8 -1
  68. package/dist/http/durable-run-leg.js +30 -11
  69. package/dist/http/execution-context.d.ts +115 -14
  70. package/dist/http/execution-context.js +130 -40
  71. package/dist/http/owner-access.d.ts +3 -2
  72. package/dist/http/owner-access.js +2 -3
  73. package/dist/http/principal-gate.d.ts +18 -11
  74. package/dist/http/principal-gate.js +3 -1
  75. package/dist/http/resume-ctx-read.d.ts +14 -3
  76. package/dist/http/resume-ctx-read.js +8 -2
  77. package/dist/http/resume-legs.d.ts +35 -3
  78. package/dist/http/resume-legs.js +244 -92
  79. package/dist/http/route-ctx.d.ts +6 -0
  80. package/dist/http/routes/a2a-serve.js +2 -0
  81. package/dist/http/routes/approvals-assistant.js +3 -2
  82. package/dist/http/routes/capabilities.js +1 -1
  83. package/dist/http/routes/diagnostics.d.ts +3 -1
  84. package/dist/http/routes/diagnostics.js +30 -5
  85. package/dist/http/routes/principal-inbox.d.ts +3 -1
  86. package/dist/http/routes/principal-inbox.js +3 -2
  87. package/dist/http/routes/runs.js +10 -7
  88. package/dist/http/routes/session-sync.d.ts +24 -5
  89. package/dist/http/routes/session-sync.js +46 -34
  90. package/dist/http/routes/sessions-list.d.ts +1 -1
  91. package/dist/http/routes/tasks.js +127 -93
  92. package/dist/http/server.d.ts +4 -5
  93. package/dist/http/server.js +30 -5
  94. package/dist/http/submit-ctx.d.ts +1 -0
  95. package/dist/http/submit-ctx.js +2 -3
  96. package/dist/index.d.ts +3 -1
  97. package/dist/index.js +1 -0
  98. package/dist/leg-facts.d.ts +7 -2
  99. package/dist/leg-facts.js +1 -0
  100. package/dist/local-store-declaration.d.ts +54 -0
  101. package/dist/local-store-declaration.js +59 -0
  102. package/dist/mcp-assertion-header.d.ts +1 -1
  103. package/dist/mcp-assertion-header.js +1 -1
  104. package/dist/memory-project-layer.js +1 -1
  105. package/dist/memory-scope.d.ts +4 -4
  106. package/dist/memory-scope.js +4 -4
  107. package/dist/memory-sync-client.js +2 -2
  108. package/dist/model-context-window.d.ts +50 -11
  109. package/dist/model-context-window.js +20 -1
  110. package/dist/model-entry-refusal.d.ts +5 -4
  111. package/dist/model-entry-refusal.js +17 -3
  112. package/dist/model-reach-scope.d.ts +2 -1
  113. package/dist/observability/fail-open.d.ts +14 -2
  114. package/dist/observability/fail-open.js +14 -2
  115. package/dist/observability/metrics.js +1 -1
  116. package/dist/observability/run-terminal.js +1 -0
  117. package/dist/observability/secret-env-scrub.d.ts +1 -1
  118. package/dist/observability/secret-env-scrub.js +1 -1
  119. package/dist/org-delivery-gate.d.ts +31 -6
  120. package/dist/org-delivery-gate.js +7 -4
  121. package/dist/owner-faces.d.ts +3 -2
  122. package/dist/park-row-commit.d.ts +67 -0
  123. package/dist/park-row-commit.js +76 -0
  124. package/dist/parked-decide.d.ts +9 -7
  125. package/dist/parked-decide.js +25 -6
  126. package/dist/peer-directory.d.ts +1 -1
  127. package/dist/plugins/adoption-log-sql.d.ts +3 -3
  128. package/dist/plugins/adoption-log-sql.js +19 -19
  129. package/dist/plugins/approval-ask-store-sql.d.ts +1 -1
  130. package/dist/plugins/approval-ask-store-sql.js +3 -5
  131. package/dist/plugins/approval-exemption-skill-cutoff.d.ts +64 -0
  132. package/dist/plugins/approval-exemption-skill-cutoff.js +122 -0
  133. package/dist/plugins/approval-exemption-skill-m46.d.ts +0 -6
  134. package/dist/plugins/approval-exemption-skill-m46.js +6 -10
  135. package/dist/plugins/approval-exemption-store.d.ts +26 -1
  136. package/dist/plugins/approval-exemption-store.js +28 -19
  137. package/dist/plugins/ensure-schema-lock-policy.d.ts +36 -3
  138. package/dist/plugins/ensure-schema-lock-policy.js +75 -9
  139. package/dist/plugins/file-run-store.d.ts +22 -9
  140. package/dist/plugins/file-run-store.js +50 -22
  141. package/dist/plugins/local-checkpoint-store.d.ts +4 -0
  142. package/dist/plugins/local-checkpoint-store.js +2 -1
  143. package/dist/plugins/local-data-root-admission.d.ts +52 -0
  144. package/dist/plugins/local-data-root-admission.js +100 -0
  145. package/dist/plugins/local-engine-high-water.d.ts +28 -7
  146. package/dist/plugins/local-engine-high-water.js +27 -22
  147. package/dist/plugins/local-session-store.d.ts +134 -49
  148. package/dist/plugins/local-session-store.js +149 -108
  149. package/dist/plugins/memory-run-store.d.ts +14 -9
  150. package/dist/plugins/memory-run-store.js +47 -21
  151. package/dist/plugins/one-time-migration-marker.d.ts +19 -3
  152. package/dist/plugins/one-time-migration-marker.js +7 -1
  153. package/dist/plugins/one-time-migrations.d.ts +46 -20
  154. package/dist/plugins/one-time-migrations.js +102 -79
  155. package/dist/plugins/org-delivery-quota-store.d.ts +34 -11
  156. package/dist/plugins/org-delivery-quota-store.js +29 -14
  157. package/dist/plugins/permission-rule-store-sql.d.ts +1 -1
  158. package/dist/plugins/permission-rule-store-sql.js +5 -9
  159. package/dist/plugins/pg-pool.d.ts +1 -1
  160. package/dist/plugins/pg-pool.js +10 -12
  161. package/dist/plugins/remote-env-device.d.ts +3 -2
  162. package/dist/plugins/remote-env-e2b.d.ts +1 -7
  163. package/dist/plugins/remote-env-e2b.js +10 -7
  164. package/dist/plugins/remote-env-host.d.ts +50 -9
  165. package/dist/plugins/remote-env-host.js +77 -20
  166. package/dist/plugins/remote-env-k8s.js +4 -2
  167. package/dist/plugins/remote-env-local-docker.d.ts +6 -3
  168. package/dist/plugins/remote-env-local-docker.js +8 -23
  169. package/dist/plugins/run-store-sql.d.ts +26 -7
  170. package/dist/plugins/run-store-sql.js +44 -16
  171. package/dist/plugins/scenario-prefix-switch-m42.d.ts +3 -1
  172. package/dist/plugins/scenario-prefix-switch-m42.js +4 -3
  173. package/dist/plugins/schema-consistency.d.ts +89 -5
  174. package/dist/plugins/schema-consistency.js +176 -7
  175. package/dist/plugins/schema-drift-sentence.d.ts +6 -5
  176. package/dist/plugins/schema-drift-sentence.js +13 -5
  177. package/dist/plugins/session-placement.d.ts +5 -4
  178. package/dist/plugins/shell-sentinel-owner-m48.d.ts +22 -0
  179. package/dist/plugins/shell-sentinel-owner-m48.js +6 -0
  180. package/dist/plugins/stop-then-start-sentence.d.ts +16 -0
  181. package/dist/plugins/stop-then-start-sentence.js +7 -0
  182. package/dist/plugins/store-backend.d.ts +22 -8
  183. package/dist/plugins/store-backend.js +58 -24
  184. package/dist/plugins/store-contracts.d.ts +33 -3
  185. package/dist/plugins/store-contracts.js +16 -0
  186. package/dist/plugins/tidb-pool.d.ts +11 -6
  187. package/dist/plugins/tidb-pool.js +18 -12
  188. package/dist/plugins/workspace-custody.d.ts +44 -0
  189. package/dist/plugins/workspace-custody.js +47 -0
  190. package/dist/project-identity.d.ts +8 -7
  191. package/dist/project-identity.js +40 -17
  192. package/dist/project-layer-key.d.ts +13 -2
  193. package/dist/project-layer-key.js +9 -1
  194. package/dist/read-deny-floor-notice.d.ts +36 -0
  195. package/dist/read-deny-floor-notice.js +40 -0
  196. package/dist/{plain-string-record.d.ts → request-declaration-disposition.d.ts} +9 -23
  197. package/dist/request-declaration-disposition.js +39 -0
  198. package/dist/run-cancel-card.d.ts +28 -0
  199. package/dist/run-cancel-card.js +19 -0
  200. package/dist/run-cancel-context.d.ts +10 -0
  201. package/dist/run-cancel-context.js +1 -0
  202. package/dist/run-local.js +28 -13
  203. package/dist/runs.d.ts +31 -7
  204. package/dist/runs.js +34 -16
  205. package/dist/security.d.ts +2 -2
  206. package/dist/server-secret-env.d.ts +34 -20
  207. package/dist/server-secret-env.js +61 -18
  208. package/dist/session-agent.d.ts +3 -2
  209. package/dist/session-children-retire.d.ts +6 -3
  210. package/dist/session-children-retire.js +2 -5
  211. package/dist/session-workspace.d.ts +3 -3
  212. package/dist/store-admission.d.ts +33 -0
  213. package/dist/store-admission.js +49 -0
  214. package/dist/store-posture.d.ts +5 -0
  215. package/dist/store-posture.js +18 -1
  216. package/dist/stores-sql/checkpoint-store-sql.d.ts +1 -1
  217. package/dist/stores-sql/checkpoint-store-sql.js +3 -5
  218. package/dist/stores-sql/file-history-store-sql.d.ts +1 -1
  219. package/dist/stores-sql/file-history-store-sql.js +3 -5
  220. package/dist/stores-sql/tool-result-store-sql.d.ts +1 -1
  221. package/dist/stores-sql/tool-result-store-sql.js +4 -6
  222. package/dist/task-a2a.d.ts +3 -8
  223. package/dist/task-a2a.js +25 -27
  224. package/dist/task-cwd.d.ts +23 -7
  225. package/dist/task-cwd.js +3 -0
  226. package/dist/task-mcp.d.ts +3 -3
  227. package/dist/task-mcp.js +1 -1
  228. package/dist/task-settings.js +1 -0
  229. package/dist/terminal.d.ts +2 -0
  230. package/dist/terminal.js +21 -6
  231. package/dist/tool-approval.d.ts +44 -36
  232. package/dist/tool-approval.js +66 -47
  233. package/dist/trace/engine-notice-wire.d.ts +24 -21
  234. package/dist/trace/engine-notice-wire.js +7 -3
  235. package/dist/trace/ledger-sink.d.ts +3 -1
  236. package/dist/trace/project.d.ts +4 -2
  237. package/dist/trace/project.js +8 -7
  238. package/dist/trace/redact.d.ts +12 -0
  239. package/dist/trace/redact.js +5 -1
  240. package/dist/trace/self-minted-wire.d.ts +131 -52
  241. package/dist/trace/self-minted-wire.js +71 -39
  242. package/dist/web-admin-client/apply-effective.js +22 -9
  243. package/docs/error-text-lexicon.json +317 -0
  244. package/package.json +3 -1
  245. package/scripts/embed-minimal.mjs +183 -0
  246. package/wire-changes/server@7.149.0.json +372 -0
  247. package/dist/plain-string-record.js +0 -70
@@ -10,8 +10,9 @@ import { StoreBootRefusalError } from "./store-contracts.js";
10
10
  import { decodeRunResultCellM44 } from "./run-store-sql.js";
11
11
  import { persistedPlaneOf } from "../terminal.js";
12
12
  import { RUN_PREVIEW_COARSE_PREFIXES_M45, normalizedRunPreviewM45 } from "./run-preview-m45.js";
13
- import { SHELL_SENTINEL_OWNER_M48, SHELL_SENTINEL_OWNER_TABLES_M48 } from "./shell-sentinel-owner-m48.js";
14
- import { LEGACY_SKILL_EXEMPTION_MARKER_ID, LEGACY_SKILL_EXEMPTION_PAGE, LEGACY_SKILL_EXEMPTION_TOOL_NAME } from "./approval-exemption-skill-m46.js";
13
+ import { SHELL_SENTINEL_OWNER_M48, SHELL_SENTINEL_OWNER_TABLES_M48, shellSentinelOwnerWhereM48 } from "./shell-sentinel-owner-m48.js";
14
+ import { LEGACY_SKILL_EXEMPTION_PAGE } from "./approval-exemption-skill-m46.js";
15
+ import { SKILL_EXEMPTION_TOOL_NAME, readSqlSkillExemptionCutoff } from "./approval-exemption-skill-cutoff.js";
15
16
  import { PERMISSION_RULE_TABLE, SqlDurableRulePartitionProvider, isSqlBucketRefusal, isSqlBucketVanished, listRuleBucketOwners } from "./permission-rule-store-sql.js";
16
17
  import { SESSION_RULES_COLUMN } from "./session-rules-column.js";
17
18
  import { readJsonObjectColumn } from "./sql-json-column.js";
@@ -27,8 +28,9 @@ import { RENAMED_EXECUTION_LANE_M40, RETIRED_EXECUTION_LANE_WORD_M40, retiredExe
27
28
  import { retireImportedLayerRootAllows } from "./imported-layer-root-retirement.js";
28
29
  import { isUnusableArchive } from "../execution-placement.js";
29
30
  import { IDENTITY_COLUMN_NAME, SCENARIO_PREFIX_LIKE, SCENARIO_PREFIX_MARKER_ID, ScenarioPrefixSwitchRefusedError } from "./scenario-prefix-switch-m42.js";
30
- import { MIGRATION_MARKER_TABLE, migrationMarkerDdl } from "./one-time-migration-marker.js";
31
- import { expectedTableShapes } from "./schema-consistency.js";
31
+ import { MIGRATION_MARKER_TABLE, SKILL_EXEMPTION_CUTOFF_ID, markIfAbsentSql, migrationMarkerDdl } from "./one-time-migration-marker.js";
32
+ import { describeUpgradeDriftTail, expectedTableShapes } from "./schema-consistency.js";
33
+ import { stopThenStartAction } from "./stop-then-start-sentence.js";
32
34
  const RETIRED_SESSION_RULE_BUCKETS = ["commandAllow", "commandDeny"];
33
35
  export function buildMigrationReceipts() {
34
36
  return {
@@ -66,6 +68,7 @@ export function buildMigrationReceipts() {
66
68
  runErrorCodeColumnRewritten: 0,
67
69
  runObjectivePreviewRewritten: 0,
68
70
  shellSentinelOwnersCleared: 0,
71
+ shellSentinelOwnerRowsRetained: [],
69
72
  checkpointHostSessionsBackfilled: 0,
70
73
  legacySkillExemptionsDropped: 0,
71
74
  executionPlacementSessionsUnreadable: [],
@@ -127,6 +130,7 @@ function changedAnything(r) {
127
130
  r.runErrorCodeColumnRewritten > 0 ||
128
131
  r.runObjectivePreviewRewritten > 0 ||
129
132
  r.shellSentinelOwnersCleared > 0 ||
133
+ r.shellSentinelOwnerRowsRetained.length > 0 ||
130
134
  r.checkpointHostSessionsBackfilled > 0 ||
131
135
  r.legacySkillExemptionsDropped > 0 ||
132
136
  r.executionPlacementSessionsUnreadable.length > 0 ||
@@ -152,6 +156,7 @@ function readingsSnapshot(r) {
152
156
  sessionRuleRowsWithUnreadableDeny: [...r.sessionRuleRowsWithUnreadableDeny],
153
157
  parkedApprovalCardRowsUnreadable: [...r.parkedApprovalCardRowsUnreadable],
154
158
  parkedApprovalCardRowsTooLong: [...r.parkedApprovalCardRowsTooLong],
159
+ shellSentinelOwnerRowsRetained: [...r.shellSentinelOwnerRowsRetained],
155
160
  executionPlacementSessionsUnreadable: [...r.executionPlacementSessionsUnreadable],
156
161
  localArchivesKeptStamped: [...r.localArchivesKeptStamped],
157
162
  localArchivesUnreadable: [...r.localArchivesUnreadable],
@@ -180,6 +185,17 @@ export class OneTimeMigrationInterruptedError extends Error {
180
185
  this.receipts = receipts;
181
186
  }
182
187
  }
188
+ const WORKFLOW_JOURNAL_TABLE = "workflow_journal";
189
+ const WORKFLOW_JOURNAL_OWNER_TABLE = "workflow_journal_owner";
190
+ const WORKFLOW_JOURNAL_STAGING = `${WORKFLOW_JOURNAL_TABLE}_m24`;
191
+ const WORKFLOW_JOURNAL_RETIRED = `${WORKFLOW_JOURNAL_TABLE}_m24_old`;
192
+ const WORKFLOW_JOURNAL_SUPPLIED_COLUMN = "incarnation";
193
+ const RETIRED_JOURNAL_SCOPE_COLUMN = "scope";
194
+ const SESSION_POLICY_TABLE = "session_policy";
195
+ const RETIRED_SESSION_RULES_COLUMN = "rules";
196
+ const OLD_GRAMMAR_SESSION_RULES_COLUMN = "rule_entries_old_grammar";
197
+ const RETIRED_ASK_CAUSE_COLUMN = "denied_reason";
198
+ const ASK_EXECUTION_LANE_COLUMN = "execution_lane";
183
199
  export const ONE_TIME_MIGRATIONS = [
184
200
  {
185
201
  id: "M12",
@@ -250,6 +266,7 @@ export const ONE_TIME_MIGRATIONS = [
250
266
  "出路是先在一只带本条的版本上起服一次;② 行里仍带退役命令桶:SQL 店读回照旧答 `corrupt`(引擎的形读法点名那只桶,与本条在场时同一个结局),只是起服日志不再逐行点名 —— " +
251
267
  "运维在库里把那一行改写成引擎文法或删掉它(删会话即删规则);③ 跳过本条直接升的库同样没有改名那一拍的条目判读 —— 由 ① 的拒启接住(出路同 ①)",
252
268
  reportKeys: ["sessionRuleColumn", "sessionRuleRowsWithRetiredBuckets", "sessionRuleRowsWithOldGrammarEntries", "sessionRuleRowsWithUnreadableDeny"],
269
+ shapeChanges: [{ kind: "rename", table: SESSION_POLICY_TABLE, from: [RETIRED_SESSION_RULES_COLUMN, OLD_GRAMMAR_SESSION_RULES_COLUMN], to: SESSION_RULES_COLUMN.name }],
253
270
  },
254
271
  {
255
272
  id: "M18",
@@ -370,6 +387,7 @@ export const ONE_TIME_MIGRATIONS = [
370
387
  afterRemoval: "起服对账一族的列集探针接住(`schema-consistency.ts` 的 `assertSchemaMatchesCode`,常设):跳过了带本条的版本直接升的库,`workflow_journal` 上仍带 scope 列、没有 incarnation 列 = 库与代码列集不符 ⇒ " +
371
388
  "响亮拒启(`config.schema_mismatch`),拒句点名两列;出路是先在带本条的版本(7.133.x)起服一次让它走完,再升",
372
389
  reportKeys: ["workflowJournalScopeColumn", "workflowJournalOwnersBackfilled", "workflowJournalForeignScopeRowsRemoved"],
390
+ shapeChanges: [{ kind: "rebuild", table: WORKFLOW_JOURNAL_TABLE, whilePresent: RETIRED_JOURNAL_SCOPE_COLUMN, supplies: [WORKFLOW_JOURNAL_SUPPLIED_COLUMN] }],
373
391
  },
374
392
  {
375
393
  id: "M25",
@@ -497,13 +515,15 @@ export const ONE_TIME_MIGRATIONS = [
497
515
  afterRemoval: "起服对账一族的跳版库拒启臂接住:删码同版把 `device_session` / `device_session_retired` 两张表加进退役 schema 判据(`schema-consistency.ts` 的跳版库探针,先于建表)⇒ " +
498
516
  "跳过了带本条的版本直接升的库响亮拒启,出路 = 先在带本条的版本起服一次让切换走完,再升;缺执行放置列的库由列集对账缺列拒启(`config.schema_mismatch`,拒句给 ALTER),同一出路",
499
517
  reportKeys: ["executionPlacementColumnsAdded", "deviceBindingsMoved", "deviceBindingsOrphaned", "deviceBindingTablesDropped", "executionPlacementsBackfilled", "askExecutionLanesBackfilled", "executionPlacementSessionsUnreadable"],
518
+ shapeChanges: [{ kind: "add", table: APPROVAL_ASK_TABLE, column: ASK_EXECUTION_LANE_COLUMN, whilePresent: { table: "session_meta", anyOf: SESSION_EXT_MOVED_COLUMNS.map((c) => c.name) } }],
500
519
  },
501
520
  {
502
521
  id: "M33",
503
522
  introducedIn: "7.141.0",
504
523
  releasedOn: "2026-10-30",
505
524
  removableAfter: "2026-11-29",
506
- removal: "删 `LocalSessionStore.migrateLocalArchives` 与它的私有助手 `anonymizeArchiveHeld` / `fillRowFacts`、`settleArchiveSnapshots` 里除最后一句常设臂 `assertNoArchiveSnapshots(dir)` 以外的一切(逐份消化的循环与 `settleArchiveSnapshot`)、" +
525
+ removal: "删 `LocalSessionStore.migrateLocalArchives` 与它的私有助手 `anonymizeArchiveHeld` / `fillRowFacts`、`settleArchiveSnapshots` 整只(逐份消化的循环与 `settleArchiveSnapshot`;`LocalBackend.ensureSchema` 里那一步)—— " +
526
+ "常设臂 `assertNoArchiveSnapshots`(只读)搬进数据根准入 `local-data-root-admission.ts` 的 `admitLocalDataRoot`(一格,本进程第一笔写之前判)、" +
507
527
  "`archiveSnapshotsSettled` 字段、`rowFactsFilled` / `ARCHIVE_SNAPSHOT_RECORD` / `ArchiveSnapshotRecord` / `readArchiveSnapshotRecord` / `sameLog` / " +
508
528
  "`ArchivedRowFacts` / `archivedRowFactsOf`(M47 仍在树时留着,随 M47 删)/ " +
509
529
  "`ArchiveRewriteStep` / `LocalArchiveMigrationReading` / `errnoOf`、" +
@@ -532,6 +552,10 @@ export const ONE_TIME_MIGRATIONS = [
532
552
  "出路 = 先在带本条的版本起服一次让迁移走完,再升(列册对账见幽灵列 / 缺列同样拒启,但它的拒句给的是加列,不是这一出路 —— 所以退役判据要在删码同版加上);" +
533
553
  "② local —— 信封号 1 的日志记录走 File 问账店「认不出的信封号」拒启(`unknown envelope version`,点名文件与出路:用写下它的那一版起服一次,或移开文件)",
534
554
  reportKeys: ["askCauseColumnsChanged", "askParkTriggersMoved", "askJournalRecordsUpgraded"],
555
+ shapeChanges: [
556
+ { kind: "rename", table: APPROVAL_ASK_TABLE, from: [RETIRED_ASK_CAUSE_COLUMN], to: APPROVAL_ASK_ENDING_CAUSE_COLUMN.name },
557
+ { kind: "add", table: APPROVAL_ASK_TABLE, column: APPROVAL_ASK_PARK_TRIGGER_COLUMN.name },
558
+ ],
535
559
  },
536
560
  {
537
561
  id: "M35",
@@ -609,10 +633,12 @@ export const ONE_TIME_MIGRATIONS = [
609
633
  removal: "删 `src/plugins/execution-lane-rename-m40.ts` 整只文件;本文件的 M40 步(`renameRetiredExecutionLaneWordsM40` / `refuseWhileRunsHeartbeat`〔它读的 `readLiveRunHeartbeat` / `LiveRunHeartbeat` 由 M41 / M42 共读,两条也已删码时随之删〕/ `ExecutionLaneRenameRefusedError` / " +
610
634
  "`EXECUTION_LANE_RENAME_REFUSED_CODE` / `ExecutionLaneRenameInput`,`migrateStepsOn` 第一步那一行与 `SqlOneTimeMigrationInput` 里的那一半)、报告 / 收据 / 日志 / `NO_SQL_MIGRATIONS` 的 `executionLaneWordsRenamed`、头注 M40 段;" +
611
635
  "`store-backend.ts` 的 `LocalBackend.ensureSchema` 第一步与 `executionLaneWordsRenamed` 字段、两只 SQL 后端递 `liveRunWindowMs` 的那一半;" +
636
+ "`local-session-store.ts` 的内核 `renameRetiredExecutionLaneWordsInCellsM40` 与数据根准入的具名读者 `readSessionMetaSidecarInWindow`(在内存里套的那一步;准入的解码点改用 `readSessionMetaSidecar`);" +
612
637
  "`LocalSessionStore.renameRetiredExecutionLaneWordM40`、`FileApprovalAskStore.renameRetiredExecutionLaneWordM40` 与内核的 `renameExecutionLaneWord`;" +
613
638
  "`approval-ask-store-sql.ts` / `execution-placement-sql.ts` 拒句里的 `retiredExecutionLaneHintM40(…)` 补句;`test/execution-lane-rename-m40.test.ts` 整只文件与 `test/execution-placement-switch-db-integration.test.ts` 的 M40 组",
614
639
  afterRemoval: "既有读口的拒臂接住(响亮,不再改名):仍带旧词的行 —— 跳过了带本条的版本直接升的库、窗关之后还在写的更早版本副本 —— SQL 问行读口拒、会话执行格读口拒(`ExecutionPlacementRowError`)," +
615
640
  "local 会话元数据边档整只严格拒启(`config.schema_mismatch`),local 问账日志由 M32 回填的词表外拒臂拒启(M32 删码之后:那只问行原样装回)。出路 = 升级时先过一次带本条的版本并起服",
641
+ stopThenStart: "renames the retired execution-lane word \"in-process\" to \"none\" on stored approval asks and sessions; a replica of an earlier version would keep writing the old word after the rename",
616
642
  reportKeys: ["executionLaneWordsRenamed"],
617
643
  },
618
644
  {
@@ -626,8 +652,9 @@ export const ONE_TIME_MIGRATIONS = [
626
652
  "留 ③ 的旁表臂(`migrateDeviceBindingsOntoSideTableStatements`:围栏拒句的「采旧」在任何库上仍要能落到执行格);M32 先于本条删码时这些随 M32 一并删;`readLiveRunHeartbeat` 在 M40 与 M42 也已删码时随之删;`test/session-ext-move-db-integration.test.ts` 整只文件",
627
653
  afterRemoval: "跳过了带本条的版本直接升上来的库:`session_meta` 上仍带部署格列 ⇒ 起服列册对账拒启(`config.schema_mismatch`,那几列是本树之外的多余列,拒句不给 ALTER —— 出路是带本条的版本);" +
628
654
  "部署格从旁表读,旧列上的值不被任何读者读。出路 = 升级时先过一次带本条的版本并起服",
655
+ stopThenStart: "moves the session deployment cells from session_meta to session_ext and then retires the old columns; a cell an earlier replica writes between the copy and the retirement is lost, and every cell write it makes afterwards fails",
629
656
  reportKeys: ["sessionExtRowsMoved", "sessionMetaColumnsDropped"],
630
- dropsColumns: { session_meta: SESSION_EXT_MOVED_COLUMNS.map((c) => c.name) },
657
+ shapeChanges: [{ kind: "drop", table: "session_meta", columns: SESSION_EXT_MOVED_COLUMNS.map((c) => c.name) }],
631
658
  },
632
659
  {
633
660
  id: "M42",
@@ -636,9 +663,10 @@ export const ONE_TIME_MIGRATIONS = [
636
663
  removableAfter: "2026-12-08",
637
664
  removal: "删 `src/plugins/scenario-prefix-switch-m42.ts` 整只文件;本文件的 M42 步(`switchScenarioPrefixM42` / `scannedIdentityColumnsM42`,`migrateStepsOn` 第一步那一行)、报告 / 收据 / 日志的 `scenarioPrefixSourcesScanned`、头注 M42 段;" +
638
665
  "`readLiveRunHeartbeat` / `LiveRunHeartbeat` 在 M40 与 M41 也已删码时随之删;`store-backend.ts` 的 `LocalBackend.runOneTimeMigrations` 里 M42 那一腿(`switchLocalScenarioPrefixM42`)与四只 File 店的 `scenarioPrefixOwnerRowsM42`(run / 会话 / 问 / 附件);`test/scenario-prefix-switch-m42.test.ts` 与 `test/scenario-prefix-switch-db-integration.test.ts` 两只文件。" +
639
- "标记表的属主 `one-time-migration-marker.ts` 在 M43 也已删码时随之删;库里留下的标记表 / 数据根里的标记文件无人再读,不删(起服列册对账只核本树声明的表)",
666
+ "标记表 `one-time-migration-marker.ts` 不随删(常设:中央建表语句建它,`Skill` 豁免截点那一行的家);库里留下的 `M42` 标记行 / 数据根里的标记文件无人再读,不删",
640
667
  afterRemoval: "没有响亮臂(如实):跳过了带本条的版本直接升的库,存量里以 `_scn:` 开头的身份被恢复管线读作某个人的场景(拆不出合文法的场景与人的 ⇒ `owner.malformed_subject` 响亮)。" +
641
668
  "出路 = 升级时先过一次带本条的版本并起服(它扫一次、命中即拒启点名)",
669
+ stopThenStart: "scans stored identities for the reserved scenario prefix and records a marker; an earlier replica does not reserve the prefix and would keep accepting such identities after the scan",
642
670
  reportKeys: ["scenarioPrefixSourcesScanned"],
643
671
  },
644
672
  {
@@ -647,7 +675,7 @@ export const ONE_TIME_MIGRATIONS = [
647
675
  releasedOn: "2026-11-08",
648
676
  removableAfter: "2026-12-08",
649
677
  removal: "删本文件的 M43 步(`markWithdrawEnabledM43`,`migrateStepsOn` 里那一行)、报告 / 收据 / 日志 / `NO_SQL_MIGRATIONS` 的 `withdrawMarkWritten`、头注 M43 段;" +
650
- "`plugins/session-withdraw-sql.ts` 的 `WITHDRAW_MARK_ID` 与部署判据里读标记的那一句(撤回出口无条件启用);标记表 `one-time-migration-marker.ts` 在 M42 也已删码时随之删、`plugins/session-birth.ts` 的 `upgrade_pending` 词与出口计数里的同名词;" +
678
+ "`plugins/session-withdraw-sql.ts` 的 `WITHDRAW_MARK_ID` 与部署判据里读标记的那一句(撤回出口无条件启用);标记表 `one-time-migration-marker.ts` 不随删(常设)、`plugins/session-birth.ts` 的 `upgrade_pending` 词与出口计数里的同名词;" +
651
679
  "local 会话元数据格「没有 `clientWrites` 位 ⇒ upgrade_pending」那一臂;库里留下的标记行无人再读,不删(与 M42 同一张标记表)",
652
680
  afterRemoval: "窗 30 天之后更早版本的副本早已不在;标记行不再被任何读者读(留着无害)。跳过了带本条的版本直接升上来的库:撤回出口从第一次起服就启用 —— " +
653
681
  "跨两版以上的混跑本就不被支持(7.147.0 起的停-迁-起)。出路 = 升级时先过一次带本条的版本并起服",
@@ -685,9 +713,16 @@ export const ONE_TIME_MIGRATIONS = [
685
713
  removableAfter: "2026-12-08",
686
714
  removal: "删 `src/plugins/approval-exemption-skill-m46.ts` 整个文件;本文件的 M46 步(`dropLegacySkillExemptionsM46`,`migrateStepsOn` 里那一行)、报告 / 收据 / 日志 / `NO_SQL_MIGRATIONS` 的 `legacySkillExemptionsDropped`、头注 M46 段、`logReceiptRows` 里的 `legacy_skill_exemptions_dropped` 一行;" +
687
715
  "`approval-exemption-store.ts` 的 `FileApprovalExemptionStore.dropToolRowsCreatedBeforeM46` 与 `LocalBackend.runOneTimeMigrations` 里那一格;" +
688
- "`test/approval-exemption-skill-m46.test.ts` 整个文件、`test/one-time-migrations-db-integration.test.ts` 的 M46 组;标记表 `one-time-migration-marker.ts` 在 M42 / M43 也已删码时随之删(库里留下的 `M46` 标记行与数据根里的标记文件无人再读,不删)",
689
- afterRemoval: "没有响亮臂(如实):跳过了带本条的版本直接升上来的库,7.116.0 之前写下的 `(会话, \"Skill\")` 豁免行照旧代答「不绑授予」的同名调用 —— 部署自挂一只叫 `Skill` 的工具、没有技能时," +
690
- "那个人当年批的是「激活某技能」,代答的却是这只工具的调用。出路 = 升级时先过一次带本条的版本并起服(或运维手工删那些行:`DELETE FROM approval_exemption WHERE tool_name = 'Skill'` / 删会话)",
716
+ "`test/approval-exemption-skill-m46.test.ts` 整个文件、`test/one-time-migrations-db-integration.test.ts` 的 M46 组;" +
717
+ "跳版拒启臂的豁免:`store-backend.ts` 的 `withRetiredSchemaProbe` 与 `local-data-root-admission.ts` 的 `admitLocalDataRoot` 各一处 `migrationCarried(SKILL_EXEMPTION_CUTOFF_ID)` 实参、" +
718
+ "`assertSkillExemptionCutoffHeldOnPool` / `judgeSqlSkillExemptionCutoff` / `judgeLocalSkillExemptionCutoff` 的 `carried` 参数与它那一支(臂恒判;截点读不出的拒本来就不看它)。" +
719
+ "**不随本条删**(常设):`approval-exemption-skill-cutoff.ts` 整只(截点、工具名、两车道的判与落)、标记表 `one-time-migration-marker.ts` 与它的 `M46` 行(截点的家)、" +
720
+ "数据根里的 `one-time-migration-M46.marker`、`ensureSchema()` 的截点种子行、数据根准入锁内一步的落截点、`approval-exemption-store.ts` 的 `readExemptionLog`",
721
+ afterRemoval: "跳版拒启臂接住(常设,起服对账一族,先于本次起服对这只库的任何写;`approval-exemption-skill-cutoff.ts`):库里有首次放行早于 `Skill` 豁免截点 T0 的 `Skill` 行" +
722
+ "(SQL:标记表 `M46` 行 / local:`one-time-migration-M46.marker`;T0 缺席 ⇒ 任一 `Skill` 行)⇒ 拒启 `config.schema_mismatch`,零写 —— " +
723
+ "SQL 在 `ensureSchema()` 建表之前(`LegacySkillExemptionsPresentError`,与退役 schema 探针同一处),local 在数据根准入(读数种别 `skipped-required-migration`)。" +
724
+ "出路 = 先用一个仍带本条、不低于 7.148.0(local 还要不低于拒句里已记的引擎高水位)的版本对这只库完整起一次,再升;SQL 另可手工 `DELETE FROM approval_exemption WHERE tool_name = 'Skill'`。" +
725
+ "T0 由常设的一步落(SQL:`ensureSchema()` 建表之后的种子行;local:准入的锁内一步):判过、缺席才落 —— 删码之后全新的库照样有 T0,不会被自己写下的合法行拒",
691
726
  reportKeys: ["legacySkillExemptionsDropped"],
692
727
  },
693
728
  {
@@ -695,13 +730,13 @@ export const ONE_TIME_MIGRATIONS = [
695
730
  introducedIn: "7.148.0",
696
731
  releasedOn: "2026-11-08",
697
732
  removableAfter: "2026-12-08",
698
- removal: "删 `LocalSessionStore.adoptArchiveTokensM47`(含欠账起服的 `required` 分档、证明 `assertArchiveTokensAdoptedM47` / `adoptionScopeOf` / `skippedFromAdoption` 与它那一处销账)与 `LocalArchiveTokenAdoption`、`archiveTokenAdoptionCarried`;" +
733
+ removal: "删 `LocalSessionStore.adoptArchiveTokensM47`(含欠账起服的 `required` 分档、证明 `assertArchiveTokensAdoptedM47` / `adoptionScopeOf` / `skippedFromAdoption` 与它那一处销账)与 `LocalArchiveTokenAdoption`;" +
699
734
  "收令牌欠账的记账(`recordArchiveTokenAdoptionPending`);窗内长第一格的规则 `firstCellIncarnationOf` / `LocalSessionStore.firstCellIncarnationHeld`(调用点 —— 补铸、登记、执行位回填、M33 的行回填与快照恢复 —— 回到新铸);" +
700
- "`holdArchiveTokenAdoptionDuty` 的 `adoptionCarried` 参数与记账那一支、`store-backend.ts` 判前钩里传它的那一处(臂恒判);" +
735
+ "`judgeArchiveTokenAdoptionDuty` 的 `adoptionCarried` 参数与 `record` 那一支(`ArchiveTokenAdoptionDutyAction` 随之少 `record`,`settleArchiveTokenAdoptionDuty` 的那一臂同删)、`local-data-root-admission.ts` 里传它的那一处 `migrationCarried(\"M47\")`(臂恒判);" +
701
736
  "`LocalBackend.ensureSchema` 里对 `adoptArchiveTokensM47` 的调用与两只读数字段、`runOneTimeMigrations` 里报告两键那几行;报告 / 收据 / `changedAnything` / `readingFields` / `NO_SQL_MIGRATIONS` 的 `localArchiveTokensAdopted` / `localArchiveTokensMinted`、头注 M47 段;" +
702
737
  "M33 也已删码时连同 `archivedRowFactsOf` / `ArchivedRowFacts`;`test/local-root-one-row-store.test.ts` 里标题含 `P3` / `P4` 的格。" +
703
- "**不随本条删**:跳版拒启臂 `holdArchiveTokenAdoptionDuty`(不带本条那一支:标记在而无格存档为零 ⇒ 销账放行)/ `archiveIdsWithoutCellSync` / `archiveTokenAdoptionPendingPresent` / `dischargeArchiveTokenAdoptionPending` / `ARCHIVE_TOKEN_ADOPTION_SINCE`、读数种别 `skipped-required-migration` 与它按高水位分支的拒句(常设)",
704
- afterRemoval: "跳版拒启臂接住(常设,起服对账一族、数据根打开那一刻、引擎高水位抬之前):(打开之前的引擎高水位早于 7.148.0 或缺席 ∨ 数据根里的收令牌欠账标记还在 —— 带本条的版本打开过、没收完)" +
738
+ "**不随本条删**:跳版拒启臂 `judgeArchiveTokenAdoptionDuty`(不带本条那一支:标记在而无格存档为零 ⇒ `discharge`)/ `settleArchiveTokenAdoptionDuty` / `readSessionMetaCellsSync` / `archiveTokenAdoptionPendingPresent` / `dischargeArchiveTokenAdoptionPending` / `ARCHIVE_TOKEN_ADOPTION_SINCE`、读数种别 `skipped-required-migration` 与它按高水位分支的拒句(常设)",
739
+ afterRemoval: "跳版拒启臂接住(常设,数据根准入的一格:只读、本进程第一笔写之前、引擎高水位抬之前):(打开之前的引擎高水位早于 7.148.0 或缺席 ∨ 数据根里的收令牌欠账标记还在 —— 带本条的版本打开过、没收完)" +
705
740
  "∧ 根里有存档在而会话元数据格缺的会话 ⇒ 拒启 `config.schema_mismatch`(读数种别 `skipped-required-migration`,点名会话数与版本),零写、不抬高水位 —— " +
706
741
  "出路 = 先用一个仍带本条、不低于 7.148.0 且不低于已记高水位的版本(服务或 run-local)完整起一次,再升",
707
742
  reportKeys: ["localArchiveTokensAdopted", "localArchiveTokensMinted"],
@@ -711,14 +746,14 @@ export const ONE_TIME_MIGRATIONS = [
711
746
  introducedIn: "7.148.0",
712
747
  releasedOn: "2026-11-08",
713
748
  removableAfter: "2026-12-08",
714
- removal: "删 `src/plugins/shell-sentinel-owner-m48.ts` 整个文件;本文件的 M48 步(`clearShellSentinelOwnersM48`,`migrateStepsOn` 里那一行)、报告 / 收据 / 日志的 `shellSentinelOwnersCleared`、头注 M48 段;" +
749
+ removal: "删 `src/plugins/shell-sentinel-owner-m48.ts` 整个文件;本文件的 M48 步(`clearShellSentinelOwnersM48`,`migrateStepsOn` 里那一行)、报告 / 收据 / `changedAnything` / `readingFields` / `readingsSnapshot` / 日志的 `shellSentinelOwnersCleared` 与 `shellSentinelOwnerRowsRetained`(连同 `logReceiptRows` 里 `shell_sentinel_rows_retained` 那一支)、头注 M48 段;" +
715
750
  "`SqlOneTimeMigrationInput` 里 M48 那一半(`ShellSentinelOwnerInput`)与它在两只 SQL 后端 / 测试夹具上的实参(`identityBoundary`);" +
716
- "local 腿 `LocalSessionStore.clearShellSentinelOwnersM48` / `FileRunStore.clearShellSentinelOwnersM48` 与 `LocalBackend.runOneTimeMigrations` 里那一格(连同后端的 `identityBoundary` 字段);部署形表行 `identityBoundary` 不随删(dev-open 判据读它);" +
751
+ "local 腿 `LocalSessionStore.clearShellSentinelOwnersM48` / `countShellSentinelOwnersM48` 与 `FileRunStore.clearShellSentinelOwnersM48` / `countShellSentinelOwnersM48`,以及 `LocalBackend.runOneTimeMigrations` 里那两格(改写读数与留存读数)(连同后端的 `identityBoundary` 字段);部署形表行 `identityBoundary` 不随删(dev-open 判据读它);" +
717
752
  "`test/shell-sentinel-owner-m48.test.ts` 整个文件、`test/one-time-migrations-db-integration.test.ts` 的 M48 组",
718
753
  afterRemoval: "没有响亮臂(如实):没有身份边界的部署跳过了带本条的版本直接升上来,哨兵属主的存量行仍记着那个字面量,属主面表已不认它 —— 不带主体的本地单用户读不到、续不上那些会话与 run(404)," +
719
754
  "带同一个头的请求仍读得到(属主相同)。出路 = 升级时先过一次带本条的版本并起服(或运维手工 `UPDATE <表> SET owner = NULL WHERE owner = 'anon:shell-live'`,三张表见删法)。" +
720
755
  "有身份边界的部署本条本来就零改写,删码前后同答(哨兵是一位普通的具名属主)",
721
- reportKeys: ["shellSentinelOwnersCleared"],
756
+ reportKeys: ["shellSentinelOwnersCleared", "shellSentinelOwnerRowsRetained"],
722
757
  },
723
758
  {
724
759
  id: "M49",
@@ -752,8 +787,9 @@ export const ONE_TIME_MIGRATIONS = [
752
787
  removableAfter: "2026-12-08",
753
788
  removal: "删本文件的 M51 步(`switchSessionDeletingM51` / `SessionDeletingSwitchRefusedError` / `SESSION_DELETING_SWITCH_REFUSED_CODE` / `SESSION_DELETING_MARK_ID`,`migrateStepsOn` 里那一行)、" +
754
789
  "报告 / 收据 / `changedAnything` / `readingFields` / `NO_SQL_MIGRATIONS` 的 `sessionDeletingSwitchMarked`、头注 M51 段;`test/session-deleting-state-db-integration.test.ts` 的 M51 组;" +
755
- "标记表 `one-time-migration-marker.ts` 在 M42 / M43 / M46 也已删码时随之删(库里留下的 `M51` 标记行无人再读,不删)。**不随本条删**:在删态本身(列、围栏、规则 L 的在删半、终刀判令牌 —— 常设)",
790
+ "库里留下的 `M51` 标记行无人再读,不删。**不随本条删**:在删态本身(列、围栏、规则 L 的在删半、终刀判令牌 —— 常设)、标记表 `one-time-migration-marker.ts`(常设)",
756
791
  afterRemoval: "没有响亮臂(也无需):窗 30 天之后更早版本的副本早已不在;跳过了带本条的版本直接升上来的库 —— 跨两版以上的混跑本就不被支持(7.147.0 起的停-迁-起)。出路 = 升级时先过一次带本条的版本并起服",
792
+ stopThenStart: "switches session deletion to a deleting state that every replica must read; an earlier replica does not read it and would accept a submission on a session this version is deleting",
757
793
  reportKeys: ["sessionDeletingSwitchMarked"],
758
794
  },
759
795
  ];
@@ -768,21 +804,11 @@ export function migrationWindowEnd(id) {
768
804
  throw new Error(`one-time-migrations: ${id} is in the unreleased form (removableAfter: null) — there is no window end to quote yet`);
769
805
  return row.removableAfter;
770
806
  }
771
- export function columnsRemovedByPendingMigrations(registry = ONE_TIME_MIGRATIONS) {
772
- const removals = new Map();
773
- for (const row of registry) {
774
- for (const [table, columns] of Object.entries(row.dropsColumns ?? {})) {
775
- const owners = removals.get(table) ?? new Map();
776
- for (const column of columns) {
777
- const owner = owners.get(column);
778
- if (owner !== undefined)
779
- throw new Error(`one-time-migrations: ${table}.${column} is claimed as dropped by both ${owner} and ${row.id} — one column has one dropping migration`);
780
- owners.set(column, row.id);
781
- }
782
- removals.set(table, owners);
783
- }
784
- }
785
- return removals;
807
+ export function migrationCarried(id) {
808
+ return ONE_TIME_MIGRATIONS.some((w) => w.id === id);
809
+ }
810
+ export function stopThenStartMigrations(registry = ONE_TIME_MIGRATIONS) {
811
+ return registry.flatMap((row) => (row.stopThenStart === undefined ? [] : [{ id: row.id, reason: row.stopThenStart }]));
786
812
  }
787
813
  export const NO_SQL_MIGRATIONS = {
788
814
  sessionRuleColumn: "file-store",
@@ -1007,13 +1033,18 @@ async function rewriteRunObjectivePreviewM45(db, dialect, receipts) {
1007
1033
  }
1008
1034
  }
1009
1035
  export async function clearShellSentinelOwnersM48(db, dialect, receipts, input) {
1010
- if (input.identityBoundary !== false)
1011
- return;
1012
- const sentinelOwner = dialect === "mysql" ? "CAST(owner AS BINARY) = CAST(? AS BINARY)" : "owner = $1";
1036
+ const rewrite = input.identityBoundary === false;
1037
+ const sentinelOwner = shellSentinelOwnerWhereM48(dialect);
1013
1038
  for (const table of SHELL_SENTINEL_OWNER_TABLES_M48) {
1014
1039
  if (!(await tableExists(db, dialect, table)))
1015
1040
  continue;
1016
- receipts.shellSentinelOwnersCleared += await db.exec(`UPDATE ${table} SET owner = NULL WHERE ${sentinelOwner}`, [SHELL_SENTINEL_OWNER_M48]);
1041
+ if (rewrite) {
1042
+ receipts.shellSentinelOwnersCleared += await db.exec(`UPDATE ${table} SET owner = NULL WHERE ${sentinelOwner}`, [SHELL_SENTINEL_OWNER_M48]);
1043
+ continue;
1044
+ }
1045
+ const rows = Number((await db.rows(`SELECT COUNT(*) AS n FROM ${table} WHERE ${sentinelOwner}`, [SHELL_SENTINEL_OWNER_M48]))[0]?.["n"] ?? 0);
1046
+ if (rows > 0)
1047
+ receipts.shellSentinelOwnerRowsRetained.push({ surface: table, rows });
1017
1048
  }
1018
1049
  }
1019
1050
  const HOST_SESSION_BACKFILL_PAGE = 500;
@@ -1038,27 +1069,22 @@ export async function dropLegacySkillExemptionsM46(db, dialect, receipts) {
1038
1069
  if (!(await tableExists(db, dialect, MIGRATION_MARKER_TABLE)))
1039
1070
  await execUntil(db, migrationMarkerDdl(dialect), () => tableExists(db, dialect, MIGRATION_MARKER_TABLE));
1040
1071
  const ph = (i) => (dialect === "mysql" ? "?" : `$${i}`);
1041
- const readMark = async () => {
1042
- const [row] = await db.rows(`SELECT marked_at_ms FROM ${MIGRATION_MARKER_TABLE} WHERE migration_id = ${ph(1)}`, [LEGACY_SKILL_EXEMPTION_MARKER_ID]);
1043
- return row === undefined ? undefined : Number(row["marked_at_ms"]);
1044
- };
1072
+ const readMark = () => readSqlSkillExemptionCutoff((sql, params) => db.rows(sql, params), dialect);
1045
1073
  let markedAtMs = await readMark();
1046
1074
  if (markedAtMs === undefined) {
1047
- await db.exec(dialect === "mysql"
1048
- ? `INSERT INTO ${MIGRATION_MARKER_TABLE} (migration_id, marked_at_ms) VALUES (?, ?) ON DUPLICATE KEY UPDATE migration_id = migration_id`
1049
- : `INSERT INTO ${MIGRATION_MARKER_TABLE} (migration_id, marked_at_ms) VALUES ($1, $2) ON CONFLICT (migration_id) DO NOTHING`, [LEGACY_SKILL_EXEMPTION_MARKER_ID, Date.now()]);
1075
+ await db.exec(markIfAbsentSql(dialect), [SKILL_EXEMPTION_CUTOFF_ID, Date.now()]);
1050
1076
  markedAtMs = await readMark();
1051
1077
  }
1052
- if (markedAtMs === undefined || !Number.isFinite(markedAtMs))
1053
- throw new Error("one-time migration M46: the marker row is missing or unreadable right after it was written — refusing to delete rows without a recorded cutoff");
1078
+ if (markedAtMs === undefined)
1079
+ throw new Error("one-time migration M46: the marker row is missing right after it was written — refusing to delete rows without a recorded cutoff");
1054
1080
  const cutoff = new Date(markedAtMs);
1055
1081
  let after = "";
1056
1082
  for (;;) {
1057
- const page = await db.rows(`SELECT session_id FROM approval_exemption WHERE tool_name = ${ph(1)} AND created_at < ${ph(2)} AND session_id > ${ph(3)} ORDER BY session_id LIMIT ${LEGACY_SKILL_EXEMPTION_PAGE}`, [LEGACY_SKILL_EXEMPTION_TOOL_NAME, cutoff, after]);
1083
+ const page = await db.rows(`SELECT session_id FROM approval_exemption WHERE tool_name = ${ph(1)} AND created_at < ${ph(2)} AND session_id > ${ph(3)} ORDER BY session_id LIMIT ${LEGACY_SKILL_EXEMPTION_PAGE}`, [SKILL_EXEMPTION_TOOL_NAME, cutoff, after]);
1058
1084
  if (page.length === 0)
1059
1085
  return;
1060
1086
  const sessionIds = page.map((r) => String(r["session_id"]));
1061
- receipts.legacySkillExemptionsDropped += await db.exec(`DELETE FROM approval_exemption WHERE tool_name = ${ph(1)} AND created_at < ${ph(2)} AND session_id IN (${sessionIds.map((_, i) => ph(i + 3)).join(", ")})`, [LEGACY_SKILL_EXEMPTION_TOOL_NAME, cutoff, ...sessionIds]);
1087
+ receipts.legacySkillExemptionsDropped += await db.exec(`DELETE FROM approval_exemption WHERE tool_name = ${ph(1)} AND created_at < ${ph(2)} AND session_id IN (${sessionIds.map((_, i) => ph(i + 3)).join(", ")})`, [SKILL_EXEMPTION_TOOL_NAME, cutoff, ...sessionIds]);
1062
1088
  if (page.length < LEGACY_SKILL_EXEMPTION_PAGE)
1063
1089
  return;
1064
1090
  after = sessionIds[sessionIds.length - 1];
@@ -1072,21 +1098,19 @@ async function markWithdrawEnabledM43(db, dialect, receipts, input) {
1072
1098
  return;
1073
1099
  if ((await readLiveRunHeartbeat(db, dialect, input)) !== undefined)
1074
1100
  return;
1075
- receipts.withdrawMarkWritten += await db.exec(dialect === "mysql"
1076
- ? `INSERT INTO ${MIGRATION_MARKER_TABLE} (migration_id, marked_at_ms) VALUES (?, ?) ON DUPLICATE KEY UPDATE migration_id = migration_id`
1077
- : `INSERT INTO ${MIGRATION_MARKER_TABLE} (migration_id, marked_at_ms) VALUES ($1, $2) ON CONFLICT (migration_id) DO NOTHING`, [WITHDRAW_MARK_ID, Date.now()]);
1101
+ receipts.withdrawMarkWritten += await db.exec(markIfAbsentSql(dialect), [WITHDRAW_MARK_ID, Date.now()]);
1078
1102
  }
1079
1103
  export const SESSION_DELETING_MARK_ID = "M51";
1080
1104
  export const SESSION_DELETING_SWITCH_REFUSED_CODE = "config.session_deleting_switch_refused";
1081
1105
  export class SessionDeletingSwitchRefusedError extends StoreBootRefusalError {
1082
1106
  code = SESSION_DELETING_SWITCH_REFUSED_CODE;
1083
1107
  reason = "live_runs";
1084
- constructor(live) {
1108
+ constructor(live, tail = "") {
1085
1109
  super(`${SESSION_DELETING_SWITCH_REFUSED_CODE} (reason: live_runs): this version deletes a session through a deleting state that every replica must read ` +
1086
1110
  `(a submission on a session being deleted is refused), but ${live.liveRuns} running run(s) heartbeated on this database within the last ${live.windowSec}s (REAP_RUN_STALE_SEC) — ` +
1087
1111
  "a replica of an earlier version may still be serving; it does not read that state and would accept a submission on a session this version is deleting. Nothing was changed and no marker was written. " +
1088
- "Stop every replica of the earlier version, then start this version (stop, migrate, start — this release cannot be rolled across replicas of both versions). " +
1089
- `If every replica is already stopped, those are the runs they left behind: start this version again after ${live.after}`);
1112
+ stopThenStartAction(live.after) +
1113
+ tail);
1090
1114
  this.name = "SessionDeletingSwitchRefusedError";
1091
1115
  }
1092
1116
  }
@@ -1098,10 +1122,8 @@ async function switchSessionDeletingM51(db, dialect, receipts, input) {
1098
1122
  return;
1099
1123
  const heartbeat = await readLiveRunHeartbeat(db, dialect, input);
1100
1124
  if (heartbeat !== undefined)
1101
- throw new SessionDeletingSwitchRefusedError(heartbeat);
1102
- receipts.sessionDeletingSwitchMarked += await db.exec(dialect === "mysql"
1103
- ? `INSERT INTO ${MIGRATION_MARKER_TABLE} (migration_id, marked_at_ms) VALUES (?, ?) ON DUPLICATE KEY UPDATE migration_id = migration_id`
1104
- : `INSERT INTO ${MIGRATION_MARKER_TABLE} (migration_id, marked_at_ms) VALUES ($1, $2) ON CONFLICT (migration_id) DO NOTHING`, [SESSION_DELETING_MARK_ID, Date.now()]);
1125
+ throw new SessionDeletingSwitchRefusedError(heartbeat, await describeUpgradeDriftTail((sql) => db.rows(sql), dialect));
1126
+ receipts.sessionDeletingSwitchMarked += await db.exec(markIfAbsentSql(dialect), [SESSION_DELETING_MARK_ID, Date.now()]);
1105
1127
  }
1106
1128
  export async function scannedIdentityColumnsM42(db, dialect) {
1107
1129
  const scope = dialect === "mysql" ? "DATABASE()" : "current_schema()";
@@ -1133,13 +1155,11 @@ export async function switchScenarioPrefixM42(db, dialect, receipts, input) {
1133
1155
  }
1134
1156
  receipts.scenarioPrefixSourcesScanned += columns.length;
1135
1157
  if (hits.length > 0)
1136
- throw new ScenarioPrefixSwitchRefusedError("stored_prefix", hits);
1158
+ throw new ScenarioPrefixSwitchRefusedError("stored_prefix", hits, undefined, await describeUpgradeDriftTail((sql) => db.rows(sql), dialect));
1137
1159
  const live = await readLiveRunHeartbeat(db, dialect, input);
1138
1160
  if (live !== undefined)
1139
- throw new ScenarioPrefixSwitchRefusedError("live_runs", [], live);
1140
- await db.exec(dialect === "mysql"
1141
- ? `INSERT INTO ${MIGRATION_MARKER_TABLE} (migration_id, marked_at_ms) VALUES (?, ?) ON DUPLICATE KEY UPDATE migration_id = migration_id`
1142
- : `INSERT INTO ${MIGRATION_MARKER_TABLE} (migration_id, marked_at_ms) VALUES ($1, $2) ON CONFLICT (migration_id) DO NOTHING`, [SCENARIO_PREFIX_MARKER_ID, Date.now()]);
1161
+ throw new ScenarioPrefixSwitchRefusedError("live_runs", [], live, await describeUpgradeDriftTail((sql) => db.rows(sql), dialect));
1162
+ await db.exec(markIfAbsentSql(dialect), [SCENARIO_PREFIX_MARKER_ID, Date.now()]);
1143
1163
  }
1144
1164
  export const EXECUTION_LANE_RENAME_REFUSED_CODE = "config.execution_lane_rename_refused";
1145
1165
  export class ExecutionLaneRenameRefusedError extends StoreBootRefusalError {
@@ -1215,8 +1235,8 @@ async function refuseWhileRunsHeartbeat(db, dialect, pendingRows, input) {
1215
1235
  throw new ExecutionLaneRenameRefusedError(pendingRows, liveRuns, restartAfter, `${pendingRows} stored rows (approval asks / sessions) carry the execution lane word "${RETIRED_EXECUTION_LANE_WORD_M40}", the name an earlier version gave the lane with no execution environment, ` +
1216
1236
  `which this version calls "${RENAMED_EXECUTION_LANE_M40}" and does not read; this boot renames them (one-time migration M40), but ${liveRuns} running runs on this database heartbeat within the last ${windowSec}s ` +
1217
1237
  `(REAP_RUN_STALE_SEC) — a replica of an earlier version is still serving, and it would keep writing the old word after the rename. No stored row was changed. ` +
1218
- "Stop every replica of the earlier version, then start this version (stop, migrate, start — this release cannot be rolled across replicas of both versions). " +
1219
- `If every replica is already stopped, those are the runs they left behind: start this version again after ${after}`);
1238
+ stopThenStartAction(after) +
1239
+ (await describeUpgradeDriftTail((sql) => db.rows(sql), dialect)));
1220
1240
  }
1221
1241
  export const SESSION_EXT_MOVE_REFUSED_CODE = "config.session_ext_move_refused";
1222
1242
  export class SessionExtMoveRefusedError extends StoreBootRefusalError {
@@ -1252,8 +1272,8 @@ export async function refuseSessionExtMoveWhileRunsHeartbeat(db, dialect, input)
1252
1272
  throw new SessionExtMoveRefusedError("live_runs", `${columns.length} session cells (${columns.map((c) => c.name).join(", ")}) still live on session_meta; this version keeps them on the side table ${SESSION_EXT_TABLE} and moves them on this boot ` +
1253
1273
  `(one-time migration M41: copy, verify, then drop the columns), but ${heartbeat.liveRuns} running runs on this database heartbeat within the last ${heartbeat.windowSec}s (REAP_RUN_STALE_SEC) — ` +
1254
1274
  "a replica of an earlier version is still serving; a cell it writes between the copy and the drop would be lost, and every cell write it makes after the drop fails. Nothing was changed. " +
1255
- "Stop every replica of the earlier version, then start this version (stop, migrate, start — this release cannot be rolled across replicas of both versions). " +
1256
- `If every replica is already stopped, those are the runs they left behind: start this version again after ${heartbeat.after}`);
1275
+ stopThenStartAction(heartbeat.after) +
1276
+ (await describeUpgradeDriftTail((sql) => db.rows(sql), dialect)));
1257
1277
  }
1258
1278
  export async function moveSessionCellsToSideTable(db, dialect, receipts) {
1259
1279
  const columns = await deploymentColumnsOnMeta(db, dialect);
@@ -1283,7 +1303,6 @@ export async function moveSessionCellsToSideTable(db, dialect, receipts) {
1283
1303
  await execUntil(db, `ALTER TABLE session_meta ${names.map((n) => `DROP COLUMN ${n}`).join(", ")}`, async () => (await deploymentColumnsOnMeta(db, dialect)).length === 0);
1284
1304
  receipts.sessionMetaColumnsDropped += columns.length;
1285
1305
  }
1286
- const RETIRED_ASK_CAUSE_COLUMN = "denied_reason";
1287
1306
  function renameAskCauseColumnSql(dialect) {
1288
1307
  return dialect === "mysql"
1289
1308
  ? `ALTER TABLE ${APPROVAL_ASK_TABLE} CHANGE COLUMN ${RETIRED_ASK_CAUSE_COLUMN} ${APPROVAL_ASK_ENDING_CAUSE_COLUMN.mysql}`
@@ -1338,7 +1357,7 @@ async function readSwitchShape(db, dialect) {
1338
1357
  }
1339
1358
  async function askTableShape(db, dialect) {
1340
1359
  const askTable = await tableExists(db, dialect, APPROVAL_ASK_TABLE);
1341
- return { askTable, askLaneColumn: askTable && (await columnExists(db, dialect, APPROVAL_ASK_TABLE, "execution_lane")) };
1360
+ return { askTable, askLaneColumn: askTable && (await columnExists(db, dialect, APPROVAL_ASK_TABLE, ASK_EXECUTION_LANE_COLUMN)) };
1342
1361
  }
1343
1362
  function switchStarted(s) {
1344
1363
  return s.placementColumns.has("execution_role") || s.parkedTable || s.cellsOnSideTable;
@@ -1644,11 +1663,6 @@ async function dropRetiredBreakerTable(db, dialect, receipts) {
1644
1663
  await execUntil(db, `DROP TABLE IF EXISTS ${RETIRED_BREAKER_TABLE}`, async () => !(await tableExists(db, dialect, RETIRED_BREAKER_TABLE)));
1645
1664
  receipts.retiredBreakerTablesDropped += 1;
1646
1665
  }
1647
- const WORKFLOW_JOURNAL_TABLE = "workflow_journal";
1648
- const WORKFLOW_JOURNAL_OWNER_TABLE = "workflow_journal_owner";
1649
- const WORKFLOW_JOURNAL_STAGING = `${WORKFLOW_JOURNAL_TABLE}_m24`;
1650
- const WORKFLOW_JOURNAL_RETIRED = `${WORKFLOW_JOURNAL_TABLE}_m24_old`;
1651
- const RETIRED_JOURNAL_SCOPE_COLUMN = "scope";
1652
1666
  const M24_OWNER_INCARNATION = "m24-backfill";
1653
1667
  const M24_COPY_BATCH_BYTES = 32 * 1024 * 1024;
1654
1668
  const M24_COPY_SCAN_PAGE = 1000;
@@ -1662,7 +1676,7 @@ async function copyJournalInBatches(db, dialect) {
1662
1676
  const T = WORKFLOW_JOURNAL_TABLE;
1663
1677
  const bytes = dialect === "mysql" ? "LENGTH(result)" : "OCTET_LENGTH(result)";
1664
1678
  const copyHead = `INSERT INTO ${WORKFLOW_JOURNAL_STAGING} (run_id, incarnation, ordinal, call_key, result, created_at_ms) ` +
1665
- `SELECT ${T}.run_id, o.incarnation, ordinal, call_key, ${jsonColumnText(dialect, "result")}, created_at_ms FROM ${T} ` +
1679
+ `SELECT ${T}.run_id, o.${WORKFLOW_JOURNAL_SUPPLIED_COLUMN}, ordinal, call_key, ${jsonColumnText(dialect, "result")}, created_at_ms FROM ${T} ` +
1666
1680
  `JOIN ${WORKFLOW_JOURNAL_OWNER_TABLE} o ON o.run_id = ${T}.run_id AND o.scope = ${T}.${RETIRED_JOURNAL_SCOPE_COLUMN} WHERE `;
1667
1681
  let after;
1668
1682
  for (;;) {
@@ -1724,9 +1738,6 @@ export async function moveWorkflowJournalScope(db, dialect, receipts) {
1724
1738
  }
1725
1739
  receipts.workflowJournalScopeColumn = "moved";
1726
1740
  }
1727
- const SESSION_POLICY_TABLE = "session_policy";
1728
- const RETIRED_SESSION_RULES_COLUMN = "rules";
1729
- const OLD_GRAMMAR_SESSION_RULES_COLUMN = "rule_entries_old_grammar";
1730
1741
  const SESSION_RULES_SCAN_PAGE = 500;
1731
1742
  export function retiredSessionRuleBucketsOf(body) {
1732
1743
  if (body === null || typeof body !== "object" || Array.isArray(body))
@@ -2141,6 +2152,7 @@ function readingFields(r) {
2141
2152
  runErrorCodeColumnRewritten: r.runErrorCodeColumnRewritten,
2142
2153
  runObjectivePreviewRewritten: r.runObjectivePreviewRewritten,
2143
2154
  shellSentinelOwnersCleared: r.shellSentinelOwnersCleared,
2155
+ shellSentinelOwnerRowsRetained: r.shellSentinelOwnerRowsRetained.reduce((n, surface) => n + surface.rows, 0),
2144
2156
  checkpointHostSessionsBackfilled: r.checkpointHostSessionsBackfilled,
2145
2157
  legacySkillExemptionsDropped: r.legacySkillExemptionsDropped,
2146
2158
  executionPlacementSessionsUnreadable: r.executionPlacementSessionsUnreadable.length,
@@ -2242,6 +2254,17 @@ function logReceiptRows(log, backend, r) {
2242
2254
  "(the next boot then migrates it) or the session is deleted. Named on every boot until then",
2243
2255
  });
2244
2256
  }
2257
+ if (r.shellSentinelOwnerRowsRetained.length > 0) {
2258
+ log.warn("shell_sentinel_rows_retained", {
2259
+ backend,
2260
+ rows: r.shellSentinelOwnerRowsRetained.reduce((n, surface) => n + surface.rows, 0),
2261
+ surfaces: Object.fromEntries(r.shellSentinelOwnerRowsRetained.map((surface) => [surface.surface, surface.rows])),
2262
+ note: "this deployment has an identity boundary, so the one-time migration that clears the old cli shell sentinel owner (`anon:shell-live`) to no owner did not touch these rows: they keep that owner, and only a caller whose principal is exactly that value reads them as its own " +
2263
+ "(on the local backend session_meta = the session-metadata cells and task_run = the run rows). The reading comes from the configuration of THE ONE PROCESS that starts the migration, and the rewrite lands on the whole database / data root and cannot be undone: " +
2264
+ "every process that opens this same database or data root (run-local, an embedding host's boot, a one-off tool process) must carry at least one identity key (service credential, auth bridge, REQUIRE_PRINCIPAL, direct door, read-only trace credential — any one is enough; the rewrite runs only when the starting process has none of them); " +
2265
+ "a process started with no identity key at all first would clear these rows to no owner on the boundary deployment's behalf. To clear them deliberately, run the manual step in MIGRATION.md entry 86 of the 7.148.0 section. Counted on every start until then",
2266
+ });
2267
+ }
2245
2268
  if (r.legacySkillExemptionsDropped > 0) {
2246
2269
  log.warn("legacy_skill_exemptions_dropped", {
2247
2270
  backend,
@@ -18,20 +18,43 @@ export interface OrgDeliveryQuotaPolicy {
18
18
  /** 一个收件人在一个窗里能收的封数上界(全部发件人合计)。 */
19
19
  readonly perRecipient: number;
20
20
  }
21
+ /** 一次记账的判决:放行,或拒 + 到店钟当前窗结束的整秒数(头注「拒的判决带重试指引」)。 */
22
+ export type OrgDeliveryQuotaVerdict = {
23
+ readonly granted: true;
24
+ } | {
25
+ readonly granted: false;
26
+ readonly retryAfterSec: number;
27
+ };
28
+ /**
29
+ * 店读不出自己的状态(File 腿:快照坏了)⇒ **拒用**的那一只错。fail-closed:每一次记账都抛它,绝不当成「放行」。
30
+ * 两句话各有读者,所以分开存:`message` = 运维句(点名文件与坏处、出路 —— 只进运维日志);{@link ORG_DELIVERY_QUOTA_UNREADABLE_SENTENCE} = 上线的
31
+ * 固定句(不点名宿主路径、不带坏处原文 —— 调用方是组织里的任何成员,不是运维)。HTTP 兜底(`http/server.ts`)按本类映射成带因的 500
32
+ * (码字面量只写在那一处发送点,wire 码目录的扫描面看得见它)。
33
+ */
34
+ export declare const ORG_DELIVERY_QUOTA_UNREADABLE_SENTENCE = "the cross-principal delivery quota state on this server cannot be read, so every delivery is refused until an operator repairs it \u2014 nothing was delivered, and retrying will not change the answer";
35
+ export declare class OrgDeliveryQuotaUnreadableError extends Error {
36
+ constructor(operatorSentence: string);
37
+ }
21
38
  export interface OrgDeliveryQuotaStore {
22
39
  /**
23
- * 原子地把这位发件人的一次投递**尝试**算进店钟当前窗:未满 ⇒ +1、答 `true`;已满 ⇒ 什么都不记、答 `false`。
40
+ * 原子地把这位发件人的一次投递**尝试**算进店钟当前窗:未满 ⇒ +1、答放行;已满 ⇒ 什么都不记、答拒(带重试指引)。
24
41
  * 店读不出 / 写不进 ⇒ **抛**(调用方按失败处置,绝不当成「放行」)。
25
42
  */
26
- admitAttempt(fromScope: string, policy: OrgDeliveryQuotaPolicy): Promise<boolean>;
43
+ admitAttempt(fromScope: string, policy: OrgDeliveryQuotaPolicy): Promise<OrgDeliveryQuotaVerdict>;
27
44
  /**
28
- * 原子地把一封 (fromScope → toScope) 的信算进**店钟当前窗**的两只计数器:两界都未满 ⇒ 两只各 +1、答 `true`;
29
- * 任一界已满 ⇒ 什么都不记、答 `false`。店读不出 / 写不进 ⇒ **抛**。
45
+ * 原子地把一封 (fromScope → toScope) 的信算进**店钟当前窗**的两只计数器:两界都未满 ⇒ 两只各 +1、答放行;
46
+ * 任一界已满 ⇒ 什么都不记、答拒(带重试指引)。店读不出 / 写不进 ⇒ **抛**。
30
47
  */
31
- take(fromScope: string, toScope: string, policy: OrgDeliveryQuotaPolicy): Promise<boolean>;
48
+ take(fromScope: string, toScope: string, policy: OrgDeliveryQuotaPolicy): Promise<OrgDeliveryQuotaVerdict>;
32
49
  }
33
50
  /** 窗起点(一处算法,三条腿共用)。 */
34
51
  export declare function quotaWindowStart(nowMs: number, windowMs: number): number;
52
+ /**
53
+ * 拒的判决的退避秒数(三条腿共用的**唯一**算式):店钟 `nowMs` 到当前窗结束,向上取整。≥ 1 是桶公式的推论、不另设下限:`nowMs` 严格早于窗末
54
+ * (`floor(now / w)` 之下 `(桶 + 1) × w − now ∈ (0, w]`),正数向上取整至少是 1 —— 窗末前最后 1 ms 也答 1,不叫客户端「立刻重试」。
55
+ * 桶与退避秒数的公式不在这里写第二份:都读 `observability/cost-quota.ts` 的 `alignedWindow` / `retryAfterSecUntil`(成本配额、速率限制器同锚)。
56
+ */
57
+ export declare function quotaRetryAfterSec(nowMs: number, windowMs: number): number;
35
58
  /** 店里记着的那个窗(`heldStart`)的计数此刻还算不算数 —— 头注那条律的**唯一**写法:此刻的窗,或此刻的下一个窗。 */
36
59
  export declare function heldWindowCounts(heldStart: number, at: number, windowMs: number): boolean;
37
60
  /** 一位发件人的当前窗(`windowMs` = 开窗那一次的窗宽:窗何时结束是条目自己的事实,File 腿据它丢已过窗的条目)。 */
@@ -58,8 +81,8 @@ export declare class MemoryOrgDeliveryQuotaStore implements OrgDeliveryQuotaStor
58
81
  protected state: QuotaWindows;
59
82
  /** @param nowMs 店钟(单进程腿:店 = 这个进程,缺省本进程墙钟)。 */
60
83
  constructor(nowMs?: () => number);
61
- admitAttempt(fromScope: string, policy: OrgDeliveryQuotaPolicy): Promise<boolean>;
62
- take(fromScope: string, toScope: string, policy: OrgDeliveryQuotaPolicy): Promise<boolean>;
84
+ admitAttempt(fromScope: string, policy: OrgDeliveryQuotaPolicy): Promise<OrgDeliveryQuotaVerdict>;
85
+ take(fromScope: string, toScope: string, policy: OrgDeliveryQuotaPolicy): Promise<OrgDeliveryQuotaVerdict>;
63
86
  /**
64
87
  * 落新的整份状态,只留窗还没结束的条目(按条目自己的窗宽判;已结束的发件人 / 收件人整个丢掉 —— 有界:量 = 本窗活跃的主体数)。
65
88
  * 丢掉的条目按头注那条律本来就不再算数(过去的窗),所以剪枝不改任何判决;剪枝写在**这里**而不是 File 腿里,Memory 与 File
@@ -89,16 +112,16 @@ export declare class FileOrgDeliveryQuotaStore extends MemoryOrgDeliveryQuotaSto
89
112
  nowMs?: () => number;
90
113
  warn?: (event: string, fields: Record<string, unknown>) => void;
91
114
  });
92
- admitAttempt(fromScope: string, policy: OrgDeliveryQuotaPolicy): Promise<boolean>;
93
- take(fromScope: string, toScope: string, policy: OrgDeliveryQuotaPolicy): Promise<boolean>;
115
+ admitAttempt(fromScope: string, policy: OrgDeliveryQuotaPolicy): Promise<OrgDeliveryQuotaVerdict>;
116
+ take(fromScope: string, toScope: string, policy: OrgDeliveryQuotaPolicy): Promise<OrgDeliveryQuotaVerdict>;
94
117
  protected persist(next: QuotaWindows): void;
95
118
  }
96
119
  export declare class SqlOrgDeliveryQuotaStore implements OrgDeliveryQuotaStore {
97
120
  private readonly db;
98
121
  constructor(db: SqlDriver);
99
122
  private q;
100
- admitAttempt(fromScope: string, policy: OrgDeliveryQuotaPolicy): Promise<boolean>;
101
- take(fromScope: string, toScope: string, policy: OrgDeliveryQuotaPolicy): Promise<boolean>;
123
+ admitAttempt(fromScope: string, policy: OrgDeliveryQuotaPolicy): Promise<OrgDeliveryQuotaVerdict>;
124
+ take(fromScope: string, toScope: string, policy: OrgDeliveryQuotaPolicy): Promise<OrgDeliveryQuotaVerdict>;
102
125
  /**
103
126
  * 一个记账事务:库钟先读(池上一条值语句,不在事务里留普通 SELECT),判过 ⇒ 提交,判拒 ⇒ 回滚(零残留)。
104
127
  * 🔴 **引擎判了死锁 ⇒ 整事务重来**(`retryOnDeadlock`:全仓唯一的重来写法、唯一的上界与判据):被挑中的一方已整事务回滚、锁全放,