@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
@@ -15,15 +15,18 @@ import { StoreBootRefusalError } from "./store-contracts.js";
15
15
  import { databaseAnswerCodesOf, isDeploymentFaultError } from "./sql-errors.js";
16
16
  import { buildStoreOpenRefusalTail } from "./store-open-refusal.js";
17
17
  import { acquireLocalBackendWithWait } from "./store-lock-wait.js";
18
- import { NO_SQL_MIGRATIONS, assertExecutionPlacementSwitchAdmissibleOnPool, backfillLocalAskExecutionLanes, backfillLocalExecutionPlacements, runSqlOneTimeMigrations } from "./one-time-migrations.js";
18
+ import { NO_SQL_MIGRATIONS, migrationCarried, assertExecutionPlacementSwitchAdmissibleOnPool, backfillLocalAskExecutionLanes, backfillLocalExecutionPlacements, runSqlOneTimeMigrations } from "./one-time-migrations.js";
19
19
  import { deployedExecutionLanes } from "../execution-lane-caps.js";
20
20
  import { DEPLOYMENT_SHAPE_PREDICATES } from "../deployment-shape.js";
21
21
  import { LOCAL_MARKER_FILE, switchLocalScenarioPrefixM42 } from "./scenario-prefix-switch-m42.js";
22
- import { LOCAL_LEGACY_SKILL_EXEMPTION_MARKER_FILE, dropLocalLegacySkillExemptionsM46 } from "./approval-exemption-skill-m46.js";
22
+ import { dropLocalLegacySkillExemptionsM46 } from "./approval-exemption-skill-m46.js";
23
+ import { LOCAL_SKILL_EXEMPTION_CUTOFF_FILE, assertSkillExemptionCutoffHeldOnPool } from "./approval-exemption-skill-cutoff.js";
24
+ import { SKILL_EXEMPTION_CUTOFF_ID } from "./one-time-migration-marker.js";
23
25
  import { LocalTaskAttachmentStore } from "./local-task-attachment-store.js";
24
26
  import { assertRetiredSchemaAbsentOnPool } from "./schema-consistency.js";
25
- import { LocalSessionStore, archiveTokenAdoptionCarried, holdArchiveTokenAdoptionDuty, createLocalSessionRepo } from "./local-session-store.js";
26
- import { SESSION_DURABILITY, SESSION_PLACEMENTS } from "./session-placement.js";
27
+ import { LocalSessionStore, createLocalSessionRepo } from "./local-session-store.js";
28
+ import { localStoreDeclarationOf } from "../local-store-declaration.js";
29
+ import { SESSION_PLACEMENTS } from "./session-placement.js";
27
30
  import { LocalCheckpointStore, dropLocalCheckpointCtxStrict } from "./local-checkpoint-store.js";
28
31
  import { FileResumeAnchorStore } from "./file-resume-anchor-store.js";
29
32
  import { TiDBApprovalExemptionStore, PgApprovalExemptionStore, FileApprovalExemptionStore } from "./approval-exemption-store.js";
@@ -76,8 +79,7 @@ import { createSqlPermissionRuleStores } from "./permission-rule-store-sql.js";
76
79
  import { FILE_PERMISSION_RULE_DIR, createFilePermissionRuleStores, fileRuleBucketOwners, isFileBucketDamaged } from "./permission-rule-store-file.js";
77
80
  import { retireImportedLayerRootAllows } from "./imported-layer-root-retirement.js";
78
81
  import { buildDataDirLayout, tightenLocalDataDirModes } from "./local-data-dir-modes-m39.js";
79
- import { holdEngineHighWater } from "./local-engine-high-water.js";
80
- import { serviceVersion } from "../service-version.js";
82
+ import { admitLocalDataRoot, holdLocalDataRootAdmission } from "./local-data-root-admission.js";
81
83
  import { mysqlDriver, pgDriver } from "./sql-driver.js";
82
84
  import { localDataRootHoldsData, storeDegradedReasonFor } from "../store-posture.js";
83
85
  import { openWithinWait } from "./store-open-wait.js";
@@ -377,10 +379,12 @@ class LocalBackend {
377
379
  parkDecisionLedgerRoot;
378
380
  executionLanes;
379
381
  identityBoundary;
382
+ storeDeclaration;
380
383
  dataDirLayout;
381
- constructor(fileBackend, config, discloseWarn, onWarn, onError, observers) {
384
+ constructor(fileBackend, config, discloseWarn, onWarn, onError, observers, facts) {
382
385
  this.executionLanes = deployedExecutionLanes(config);
383
386
  this.identityBoundary = DEPLOYMENT_SHAPE_PREDICATES.identityBoundary.holds(config);
387
+ this.storeDeclaration = localStoreDeclarationOf(config, facts?.dataRootMedium ?? "unknown");
384
388
  this.onWarn = onWarn;
385
389
  this.discloseWarn = discloseWarn;
386
390
  this.onError = onError;
@@ -397,7 +401,7 @@ class LocalBackend {
397
401
  const physicalRoot = this.fileBackend.root;
398
402
  this.sessionStore = new LocalSessionStore(createLocalSessionRepo(physicalRoot), this.runStore, physicalRoot, {
399
403
  log: createLogger(config.logLevel),
400
- durability: SESSION_DURABILITY,
404
+ durability: this.storeDeclaration.durability,
401
405
  placements: SESSION_PLACEMENTS,
402
406
  withdrawCheckpointCtx: (sessionId) => dropLocalCheckpointCtxStrict(physicalRoot, sessionId),
403
407
  });
@@ -420,7 +424,6 @@ class LocalBackend {
420
424
  }
421
425
  async ensureSchema() {
422
426
  this.executionLaneWordsRenamed += await this.sessionStore.renameRetiredExecutionLaneWordM40();
423
- await this.sessionStore.assertStrictSidecarsReadable();
424
427
  await this.sessionStore.finishPendingDrops();
425
428
  await this.sessionStore.settleArchiveSnapshots();
426
429
  const adoption = await this.sessionStore.adoptArchiveTokensM47();
@@ -443,11 +446,24 @@ class LocalBackend {
443
446
  });
444
447
  const runErrorCodeColumnRewritten = this.runStore.migrateRunErrorCodeColumnM44();
445
448
  const runObjectivePreviewRewritten = this.runStore.migrateRunObjectivePreviewM45();
446
- const shellSentinelOwnersCleared = this.identityBoundary ? 0 : (await this.sessionStore.clearShellSentinelOwnersM48()) + this.runStore.clearShellSentinelOwnersM48();
449
+ const shellSentinelOwnerRowsRetained = [];
450
+ let shellSentinelOwnersCleared = 0;
451
+ if (this.identityBoundary) {
452
+ for (const [surface, rows] of [
453
+ ["session_meta", await this.sessionStore.countShellSentinelOwnersM48()],
454
+ ["task_run", this.runStore.countShellSentinelOwnersM48()],
455
+ ]) {
456
+ if (rows > 0)
457
+ shellSentinelOwnerRowsRetained.push({ surface, rows });
458
+ }
459
+ }
460
+ else {
461
+ shellSentinelOwnersCleared = (await this.sessionStore.clearShellSentinelOwnersM48()) + this.runStore.clearShellSentinelOwnersM48();
462
+ }
447
463
  const checkpointHostSessionsBackfilled = existsSync(join(this.fileBackend.root, "checkpoint-session-tokens.json")) || existsSync(join(this.fileBackend.root, "checkpoint-scopes.json"))
448
464
  ? await this.checkpointStoreOf().backfillHostSessionsM49()
449
465
  : 0;
450
- const legacySkillExemptionsDropped = dropLocalLegacySkillExemptionsM46(join(this.fileBackend.root, LOCAL_LEGACY_SKILL_EXEMPTION_MARKER_FILE), this.approvalExemptionStore);
466
+ const legacySkillExemptionsDropped = dropLocalLegacySkillExemptionsM46(join(this.fileBackend.root, LOCAL_SKILL_EXEMPTION_CUTOFF_FILE), this.approvalExemptionStore);
451
467
  const archives = await this.sessionStore.migrateLocalArchives();
452
468
  const placements = await backfillLocalExecutionPlacements(this.runStore.sessionIdsWithRuns(), this.sessionStore.executionPlacementBackfill, { lanes: this.executionLanes });
453
469
  const importedLayerRoot = { importedLayerRootAllowsRetired: [], importedLayerRootBucketsSkipped: [], importedLayerRootBucketsUnnamed: 0 };
@@ -467,6 +483,7 @@ class LocalBackend {
467
483
  runErrorCodeColumnRewritten,
468
484
  runObjectivePreviewRewritten,
469
485
  shellSentinelOwnersCleared,
486
+ shellSentinelOwnerRowsRetained,
470
487
  checkpointHostSessionsBackfilled,
471
488
  legacySkillExemptionsDropped,
472
489
  executionPlacementsBackfilled: placements.backfilled,
@@ -524,6 +541,7 @@ class LocalBackend {
524
541
  userInputLedger() { return this.userInputLedgerStore; }
525
542
  checkpointStoreOf() {
526
543
  return (this.checkpointStoreInst ??= new LocalCheckpointStore(this.fileBackend.root, {
544
+ durability: this.storeDeclaration.durability,
527
545
  getActiveTaskId: (sessionId) => this.runStore.getActiveTaskId(sessionId),
528
546
  readSessionOwner: async (sessionId) => (await this.sessionStore.headOf(sessionId))?.owner,
529
547
  }));
@@ -544,6 +562,7 @@ class LocalBackend {
544
562
  checkpointStoreInst;
545
563
  sessionPolicy() { return (this.sessionPolicyInst ??= createClientCountedPolicyStore(this.fileBackend.sessionPolicyStore, this.sessionStore)); }
546
564
  sessionPolicyInst;
565
+ localStoreDeclaration() { return this.storeDeclaration; }
547
566
  fileUsageWindowStore() { return this.fileBackend.usageWindowStore; }
548
567
  fileHistory() { return this.fileBackend.fileHistoryStore; }
549
568
  memoryOptOutGrant() { return undefined; }
@@ -568,7 +587,7 @@ const LOCAL_OPEN_REFUSAL_HEAD = {
568
587
  "run-local": (root) => `cannot open local data dir ${root}`,
569
588
  };
570
589
  export function openLocalBackend(config, opts) {
571
- const { lane, onWarn, onError, observers } = opts;
590
+ const { lane, admission, onWarn, onError, observers, facts } = opts;
572
591
  const root = localBackendRootOf(config);
573
592
  const discloseWarn = onWarn ?? createLogger().warn;
574
593
  let fileBackend;
@@ -587,25 +606,24 @@ export function openLocalBackend(config, opts) {
587
606
  (tail === undefined ? "" : ` ${tail}`));
588
607
  }
589
608
  try {
590
- const physicalRoot = fileBackend.root;
591
- holdEngineHighWater(physicalRoot, serviceVersion(), (prior) => holdArchiveTokenAdoptionDuty(physicalRoot, prior, archiveTokenAdoptionCarried()));
609
+ holdLocalDataRootAdmission(fileBackend.root, admission);
592
610
  }
593
611
  catch (refusal) {
594
- return new RefusedLocalBackend(fileBackend, refusal instanceof Error ? refusal : new Error(String(refusal)));
612
+ return new RefusedLocalBackend(() => fileBackend.dispose(), refusal instanceof Error ? refusal : new Error(String(refusal)));
595
613
  }
596
614
  try {
597
- return new LocalBackend(fileBackend, config, discloseWarn, onWarn, onError, observers);
615
+ return new LocalBackend(fileBackend, config, discloseWarn, onWarn, onError, observers, facts);
598
616
  }
599
617
  catch (failure) {
600
- return new RefusedLocalBackend(fileBackend, failure instanceof Error ? failure : new Error(String(failure)));
618
+ return new RefusedLocalBackend(() => fileBackend.dispose(), failure instanceof Error ? failure : new Error(String(failure)));
601
619
  }
602
620
  }
603
621
  class RefusedLocalBackend {
604
- fileBackend;
622
+ release;
605
623
  refusal;
606
624
  kind = "local";
607
- constructor(fileBackend, refusal) {
608
- this.fileBackend = fileBackend;
625
+ constructor(release, refusal) {
626
+ this.release = release;
609
627
  this.refusal = refusal;
610
628
  }
611
629
  refused() {
@@ -618,7 +636,7 @@ class RefusedLocalBackend {
618
636
  return this.refused();
619
637
  }
620
638
  async close() {
621
- await this.fileBackend.dispose();
639
+ await this.release();
622
640
  }
623
641
  run() { return this.refused(); }
624
642
  resumeAnchor() { return this.refused(); }
@@ -640,12 +658,13 @@ class RefusedLocalBackend {
640
658
  engineFileStores() { return this.refused(); }
641
659
  toolResult() { return this.refused(); }
642
660
  fileUsageWindowStore() { return this.refused(); }
661
+ localStoreDeclaration() { return this.refused(); }
643
662
  mysqlPool() { return undefined; }
644
663
  pgPool() { return undefined; }
645
664
  }
646
665
  export function createStoreBackend(config, onWarn, onError, observers) {
647
666
  if (config.dbBackend === "local")
648
- return openLocalBackend(config, { lane: "http", onWarn, onError, observers });
667
+ return openAdmittedLocalBackend(config, undefined, { onWarn, onError, observers });
649
668
  if (config.dbBackend === "pg") {
650
669
  if (!config.pg)
651
670
  return undefined;
@@ -657,11 +676,24 @@ export function createStoreBackend(config, onWarn, onError, observers) {
657
676
  const pool = createTidbPool(config);
658
677
  return withRetiredSchemaProbe(new TiDBBackend(pool, config, observers), { dialect: "mysql", pool }, { lanes: deployedExecutionLanes(config) });
659
678
  }
679
+ function openAdmittedLocalBackend(config, admission, sinks) {
680
+ let admitted = admission;
681
+ if (admitted === undefined) {
682
+ try {
683
+ admitted = admitLocalDataRoot(localBackendRootOf(config));
684
+ }
685
+ catch (refusal) {
686
+ return new RefusedLocalBackend(async () => undefined, refusal instanceof Error ? refusal : new Error(String(refusal)));
687
+ }
688
+ }
689
+ return openLocalBackend(config, { lane: "http", admission: admitted, ...sinks });
690
+ }
660
691
  function withRetiredSchemaProbe(inner, target, executionPlacementSwitch) {
661
692
  const guarded = Object.create(inner);
662
693
  guarded.ensureSchema = async () => {
663
694
  await assertRetiredSchemaAbsentOnPool(target);
664
695
  await assertExecutionPlacementSwitchAdmissibleOnPool(target.dialect, target.pool, executionPlacementSwitch);
696
+ await assertSkillExemptionCutoffHeldOnPool(target, migrationCarried(SKILL_EXEMPTION_CUTOFF_ID));
665
697
  await inner.ensureSchema();
666
698
  };
667
699
  return guarded;
@@ -669,7 +701,7 @@ function withRetiredSchemaProbe(inner, target, executionPlacementSwitch) {
669
701
  export function localBackendRootOf(config) {
670
702
  return config.localDataRoot ?? join(homedir(), ".ai-agent");
671
703
  }
672
- export async function openStoreBackendWithFallback(config, logger, observers) {
704
+ export async function openStoreBackendWithFallback(config, logger, observers, localAdmission, facts) {
673
705
  const sqlEngine = config.dbBackend === "mysql" || config.dbBackend === "pg" ? config.dbBackend : undefined;
674
706
  const mayFallback = config.dbBackend === "local" && !config.dbBackendExplicit && !localDataRootHoldsData(localBackendRootOf(config));
675
707
  let backend;
@@ -678,7 +710,9 @@ export async function openStoreBackendWithFallback(config, logger, observers) {
678
710
  const attempt = async () => {
679
711
  let built;
680
712
  try {
681
- built = createStoreBackend(config, (msg, meta) => logger.warn(msg, meta), logger.error === undefined ? undefined : (msg, meta) => logger.error(msg, meta), observers);
713
+ const onWarn = (msg, meta) => logger.warn(msg, meta);
714
+ const onError = logger.error === undefined ? undefined : (msg, meta) => logger.error(msg, meta);
715
+ built = config.dbBackend === "local" ? openAdmittedLocalBackend(config, localAdmission, { onWarn, onError, observers, ...(facts !== undefined ? { facts } : {}) }) : createStoreBackend(config, onWarn, onError, observers);
682
716
  inFlight = built;
683
717
  if (built)
684
718
  await built.ensureSchema();
@@ -21,6 +21,7 @@
21
21
  import type { PeerSessionTempo, TaskResult, TaskStatus } from "@sema-agent/core";
22
22
  import type { RunIdentity } from "../identity-source.js";
23
23
  import type { SessionUnreadable } from "../session-error-wire.js";
24
+ import type { ReadDenyFloorSummary } from "../read-deny-floor-notice.js";
24
25
  /**
25
26
  * **File 店族的 boot 期拒启基类**([ref];把 `RunStoreStrictHydrateError` 的那条论证升成一条族规)。
26
27
  *
@@ -50,6 +51,19 @@ export declare class StoreBootRefusalError extends Error {
50
51
  * 会话部署格旁表建不出(`session-ext-table.ts`)、local 数据根的严格边档对账(`local-session-store.ts`)。码住在族基类旁边:读它的几只模块彼此不必互相 import。
51
52
  */
52
53
  export declare const SCHEMA_MISMATCH_ERROR_CODE = "config.schema_mismatch";
54
+ /**
55
+ * **SQL 库的列形不是本构建读得动的形 ⇒ 起服拒**的唯一出口:整库列册对账的三条拒(列集不符 / information_schema 读不出 / 整库零行,`schema-consistency.ts`)与六只单表探针的拒启
56
+ * (`checkpoint` · `tool_result` · `file_history_*` · `approval_ask` · `permission_rule_approval` · `permission_rule_ticket`)都经这一只类抛 —— 运维按码(`code` 字段 + 消息首词,同一个
57
+ * {@link SCHEMA_MISMATCH_ERROR_CODE})判「要跑 ALTER」,不必先猜是哪一只判官拒的。两只判官答同一问(这张表缺不缺列),出口只有一个。
58
+ *
59
+ * 属 {@link StoreBootRefusalError} 族:这是一句**确定**的拒启表态(再等也等不来另一个答案)—— 判官万一挪进 `ensureSchema()` 的开库等待窗,它不被重试、不被改写成「库打不开」。
60
+ * 消息**必须**以码领头:调用方交整句(对账的 `describeSchemaMismatch`、探针的 `probedMissingColumnRefusal` 都是码领头的整句),构造期核一次 —— 缺码 = 编程错,当场响,不是静默补一个。
61
+ */
62
+ export declare class SchemaMismatchRefusedError extends StoreBootRefusalError {
63
+ /** 机读码(与退役 schema / 旁表建不出 / local 数据根的拒启同一个)。 */
64
+ readonly code = "config.schema_mismatch";
65
+ constructor(message: string, options?: ErrorOptions);
66
+ }
53
67
  /**
54
68
  * 取消便签里的**大脑相位**快照 —— core `BrainStatus` 帧经 `brainStatusEventData` 投影后的五键子集。
55
69
  *
@@ -142,6 +156,14 @@ export type AbortCause = (typeof ABORT_CAUSES)[number];
142
156
  export interface PersistedResultSideKeys {
143
157
  cancelContext?: RunCancelContext;
144
158
  abortCause?: AbortCause;
159
+ /**
160
+ * 这条 run **照跑了**,而它的读拒行不按字节守:读拒表有行、执行环境不守读拒字节地板、部署没要求地板(`READ_DENY_FLOOR_REQUIRED` 未设)。
161
+ * `{ code: "config.read_deny_floor_unkept", message }` —— 与这条 run 事件流上那一帧 `engine_notice` 同码同句(句子点名 `REMOTE_EXEC=<车道>` 与出路
162
+ * `READ_DENY_FLOOR_REQUIRED=true`)。判官是引擎(每 run 准备期一次,结果按 `runId` 接上);说法的唯一属主 `read-deny-floor-notice.ts`,挂点 = 出口封口
163
+ * (`terminal.ts` 的 `sealResultForExit`)。拒起的 run 不带它(那一句在失败终局的句子里)。为什么属于 run 账本而不属于 core 的任务结果:引擎只发通告、
164
+ * 不在结果上留这件事,而句子里的执行环境与旋钮名是本仓的部署事实。
165
+ */
166
+ readDenyFloor?: ReadDenyFloorSummary;
145
167
  }
146
168
  /**
147
169
  * 落库/上 wire 的终局结果 = core 的 `TaskResult` **加上** server 自铸的可缺席侧记键({@link PersistedResultSideKeys})。
@@ -226,11 +248,15 @@ export interface RunRecord {
226
248
  * MY replica" (consult the in-process inflight/steerable map authoritatively) from "held elsewhere" (fall
227
249
  * back to the durable cross-replica flag). null for pre-column rows. */
228
250
  instanceId: string | null;
251
+ /** **持有腿**(`task_run.leg_id`):此刻持有这条行的那一条腿的身份 —— 认领铸它({@link mintRunLegId}:建行 = 首腿、续跑认领 = 新腿),交回给认领者;
252
+ * 停驻写的 CAS 只许持有腿落下(`setSuspended` / `setNeedsReview` 的 `legId`)。与 {@link instanceId} 是两件事:那一列答「活腿在哪台副本上」(路由),
253
+ * 这一列答「是不是这一条腿」。`null` = 没有持有腿的记录(列之前的行;更早版本的副本建 / 认领的行)—— 本版的腿从不持有它。不上 wire。 */
254
+ legId: string | null;
229
255
  /** 🔴 [ref] P-45 —— 这条 run 上**挂没挂着取消请求**(耐久 `cancel_requested` 列的投影,跨副本)。
230
256
  *
231
257
  * 语义就是那一列的字节:`POST /v1/runs/:id/cancel` 的 running 臂 CAS 成功即置位,持有副本的心跳拍
232
- * poll 到它就 abort;`markResuming`(续跑认领)把它清零 —— 一次撞上 suspend 的取消不许在恢复之后
233
- * 再杀一遍(那条 reset 的理由逐字在 run-store-sql `markResuming` 上)。
258
+ * poll 到它就 abort;旗是对**持有腿**的请求 —— 停驻写不落在挂着的旗上(停驻 CAS 认它,停驻提交读到旗就以取消收场),
259
+ * `markResuming`(续跑认领 = 换持有腿)把它清零,旗随旧腿去。
234
260
  *
235
261
  * 为什么是**行上的一位**而不是读面再查一次:列早就在(三个后端同名),只是行投影没带它 —— 正是
236
262
  * 本仓 L1 键集门盯的那族「投影剥键」。读面每次 poll 再发一条 `isCancelRequested` 查询,是给热路径
@@ -275,9 +301,12 @@ export interface NewRunMeta {
275
301
  objectivePreview?: string | null;
276
302
  idem?: RunIdemBinding | null;
277
303
  }
304
+ /** 铸一只持有腿({@link RunRecord.legId})—— 三只 run 店的两个认领口(`createRun` / `markResuming`)都调它,别处不铸。唯一值而非计数:同一 `taskId` 的行被删后重建,
305
+ * 新行的持有腿与旧腿的永不相等(旧腿迟到的停驻写不会落到新行上);认领者随写拿到它,不必读回。 */
306
+ export declare function mintRunLegId(): string;
278
307
  /**
279
308
  * `RunStore.createRun` 的结局 —— 四形,一形一句话:
280
- * · `{ ok: true }` —— 新行已建、会话 claim 已拿;
309
+ * · `{ ok: true, legId }` —— 新行已建、会话 claim 已拿;`legId` = 这次认领铸下的持有腿({@link RunRecord.legId}),首腿的停驻写凭它;
281
310
  * · `{ ok: false, existing }` —— 这次提交的一把**幂等钥匙**已经指向一条行:`idem_key`(`Idempotency-Key`)或
282
311
  * `task_id`(调用方自铸的 `body.taskId`)—— 两把钥匙是**同一条律**(资源行上的唯一键),先认 `idem_key` 再认 `task_id`。
283
312
  * 这是「原子赢或观察」的**观察**臂:行原样交回,没建新行、没动会话;回执由提交面的唯一回执函数判。
@@ -288,6 +317,7 @@ export interface NewRunMeta {
288
317
  */
289
318
  export type CreateRunOutcome = {
290
319
  ok: true;
320
+ legId: string;
291
321
  existing?: undefined;
292
322
  activeTaskId?: undefined;
293
323
  sessionAbsent?: undefined;
@@ -1,7 +1,20 @@
1
1
  import { RUN_STALE_SEC_DEFAULT } from "../run-stale-default.js";
2
+ import { randomUUID } from "node:crypto";
2
3
  export class StoreBootRefusalError extends Error {
3
4
  }
4
5
  export const SCHEMA_MISMATCH_ERROR_CODE = "config.schema_mismatch";
6
+ export class SchemaMismatchRefusedError extends StoreBootRefusalError {
7
+ code = SCHEMA_MISMATCH_ERROR_CODE;
8
+ constructor(message, options) {
9
+ super(codeLed(message), options);
10
+ this.name = "SchemaMismatchRefusedError";
11
+ }
12
+ }
13
+ function codeLed(message) {
14
+ if (!message.startsWith(`${SCHEMA_MISMATCH_ERROR_CODE}: `))
15
+ throw new TypeError(`a ${SCHEMA_MISMATCH_ERROR_CODE} refusal must lead with its code`);
16
+ return message;
17
+ }
5
18
  export const ABORT_CAUSES = ["client_disconnected", "ask_park_grace_exhausted", "cancel_verb", "stall_verdict"];
6
19
  export const RUN_STALLED_ERROR = "run stalled (instance lost?)";
7
20
  export const RUN_ORPHANED_ERROR = "run orphaned by engine restart";
@@ -13,6 +26,9 @@ const RUN_HOLDER_DEATH_CODE_WORDS = new Set(Object.values(RUN_HOLDER_DEATH_CODES
13
26
  export function isHolderDeathVerdict(run) {
14
27
  return run.status === "failed" && run.errorCode !== null && RUN_HOLDER_DEATH_CODE_WORDS.has(run.errorCode);
15
28
  }
29
+ export function mintRunLegId() {
30
+ return randomUUID();
31
+ }
16
32
  export const FENCE_LANDED = Symbol("in-process-claim-fence.landed");
17
33
  export const RUN_STATUS_CLASS = {
18
34
  running: "live",
@@ -51,7 +51,7 @@ export declare function createTidbPool(config: ServiceConfig): Pool;
51
51
  * 3. 生成的全集 SQL 基线导出到 `docs/schema/baseline-mysql.sql`(由本数组机器生成;它是**产物**,不是真源
52
52
  * —— 冲突时以本文件为准并重新生成)。
53
53
  *
54
- * 注意:`ensureSchema` 仍然保留(建表 + advisory lock + image_bake_admit 种子行),语句数组是纯 CREATE。数组之外只有一只只读探针:会话部署格旁表
54
+ * 注意:`ensureSchema` 仍然保留(建表 + advisory lock + 两只种子行:image_bake_admit 与 `Skill` 豁免截点),语句数组是纯 CREATE。数组之外只有一只只读探针:会话部署格旁表
55
55
  * `session_ext` 的外键列要抄被引列 `session_meta.session_id` 的字符集 / 排序规则(MySQL 外键两侧必须相同,而存量库上它随建库年代不同),建它之前读一次
56
56
  * `information_schema.COLUMNS`(`session-ext-table.ts` 的 `ensureSessionExtTableMysql`;探针只读、不导出任何 ALTER)。
57
57
  */
@@ -59,17 +59,22 @@ export declare const SCHEMA_STATEMENTS: readonly string[];
59
59
  /** Named MySQL/TiDB advisory lock that serializes the whole `ensureSchema` DDL run (see below). */
60
60
  export declare const ENSURE_SCHEMA_LOCK = "sema_ensure_schema";
61
61
  /** Acquire {@link ENSURE_SCHEMA_LOCK} on `conn` (the lock is CONNECTION-level, so the caller MUST then run all the
62
- * serialized work on this SAME connection and release with {@link releaseEnsureSchemaLock}). Returns whether it was
63
- * acquired. Exported so test setup that does ad-hoc DDL (e.g. a DROP-then-recreate) can serialize against
62
+ * serialized work on this SAME connection and release with {@link releaseEnsureSchemaLock}). Resolves once the lock is
63
+ * held. Exported so test setup that does ad-hoc DDL (e.g. a DROP-then-recreate) can serialize against
64
64
  * ensureSchema runs using the IDENTICAL lock.
65
65
  *
66
66
  * Under heavy concurrency (the integration suite fans ~20 callers at the SAME named lock; or a fleet boots N
67
67
  * replicas at once) a BLOCKING GET_LOCK(name, 30) would park ~19 sessions in a pessimistic WAIT on TiDB's advisory-
68
68
  * lock row — which both exhausts TiDB's "pessimistic lock retry limit" (surfacing as a thrown error) AND starves the
69
- * app's OWN `SELECT … FOR UPDATE` paths (image-bake admission/lease) of pessimistic-lock budget. So we acquire
70
- * NON-BLOCKING (GET_LOCK(name, 0) returns 0 immediately if held) and POLL with jittered backoff: one holder, the
69
+ * app's OWN `SELECT … FOR UPDATE` paths (image-bake admission/lease) of pessimistic-lock budget. So the lock is taken
70
+ * NON-BLOCKING (GET_LOCK(name, 0) returns 0 immediately if held) and POLLED with jittered backoff: one holder, the
71
71
  * rest sleeping (not blocked in-DB). Polls until acquired (7.83.0: never degrades to unserialized — the [ref]
72
- * `ADD INDEX` leg is not race-safe); a connection whose probe keeps throwing surfaces as a boot failure. */
72
+ * `ADD INDEX` leg is not race-safe); a connection whose probe keeps throwing surfaces as a boot failure.
73
+ *
74
+ * The probe statement, the reading of its answer, the engine's "pessimistic lock retry limit reached" sentence (read as
75
+ * "somebody else is on the lock row" — the same fact as `got = 0`, NOT a broken connection) and the polling law all
76
+ * live in ONE place, `ensure-schema-lock-policy.ts`; every other lock in this repo (the adoption arc lock) is taken
77
+ * through the same acquirer. */
73
78
  export declare function acquireEnsureSchemaLock(conn: PoolConnection, tag?: string): Promise<void>;
74
79
  /** Release {@link ENSURE_SCHEMA_LOCK} on `conn` (best-effort — the lock also auto-frees when the connection closes). */
75
80
  export declare function releaseEnsureSchemaLock(conn: PoolConnection, tag?: string): Promise<void>;
@@ -1,4 +1,3 @@
1
- import { writeLogText } from "../observability/logger.js";
2
1
  import mysql from "mysql2/promise";
3
2
  import { TIDB_APPROVAL_ASK_STATEMENTS } from "./approval-ask-store-sql.js";
4
3
  import { TIDB_MEMORY_OPTOUT_GRANT_STATEMENTS } from "./memory-optout-grant-store-sql.js";
@@ -30,7 +29,8 @@ import { PERMISSION_RULE_INDEXES } from "./permission-rule-store-sql.js";
30
29
  import { RETENTION_TOMBSTONE_INDEXES } from "../stores-sql/retention-store-sql.js";
31
30
  import { RETENTION_LANE_INDEXES } from "./retention-lane-store-sql.js";
32
31
  import { LEADER_RUN_INDEXES } from "./leader-run-store-sql.js";
33
- import { pollUntilLocked } from "./ensure-schema-lock-policy.js";
32
+ import { acquireAdvisoryLock, releaseAdvisoryLock } from "./ensure-schema-lock-policy.js";
33
+ import { SKILL_EXEMPTION_CUTOFF_ID, markIfAbsentSql, migrationMarkerDdl } from "./one-time-migration-marker.js";
34
34
  import { createCloseAwarePool } from "./sql-driver.js";
35
35
  export function tidbPoolOptions(tidb) {
36
36
  const { host, port, user, password, database, connectionLimit } = tidb;
@@ -149,6 +149,11 @@ export const SCHEMA_STATEMENTS = [
149
149
  -- deliberately-absent idx_error_code note below.
150
150
  error_code VARCHAR(64) NULL,
151
151
  instance_id VARCHAR(64) NULL,
152
+ -- leg_id: the HOLDING LEG — minted at every claim (createRun = first leg, markResuming = the resuming leg) and handed to the claimer; a park
153
+ -- write lands only for the leg that holds the row (setSuspended / setNeedsReview CAS on it). instance_id says WHERE the live leg is (routing),
154
+ -- leg_id says WHICH leg it is. NULL = no holding leg on record (a row written before the column existed, or created by an earlier-version
155
+ -- replica; an earlier-version replica's resume claim leaves the column as it found it).
156
+ leg_id VARCHAR(36) NULL,
152
157
  -- job_id (work-view correlation substrate): groups the runs of one logical task across BOTH client doors.
153
158
  job_id VARCHAR(64) NULL,
154
159
  -- source (credential-derived system identity, clay 2026-06-12): which door submitted the run
@@ -774,6 +779,7 @@ export const SCHEMA_STATEMENTS = [
774
779
  pool VARCHAR(32) NOT NULL,
775
780
  PRIMARY KEY (pool)
776
781
  ) COLLATE utf8mb4_bin`,
782
+ migrationMarkerDdl("mysql"),
777
783
  ...TIDB_APPROVAL_ASK_STATEMENTS,
778
784
  ...TIDB_MEMORY_OPTOUT_GRANT_STATEMENTS,
779
785
  ...TIDB_SESSION_CAPTURE_OPTOUT_STATEMENTS,
@@ -817,19 +823,18 @@ export const SCHEMA_STATEMENTS = [
817
823
  ...TIDB_RETENTION_TOMBSTONE_STATEMENTS,
818
824
  ];
819
825
  export const ENSURE_SCHEMA_LOCK = "sema_ensure_schema";
826
+ const ENSURE_SCHEMA_LOCK_ID = { dialect: "mysql", name: ENSURE_SCHEMA_LOCK };
827
+ const lockConnOf = (conn) => ({
828
+ query: async (sql, params) => {
829
+ const [rows] = await conn.query(sql, params);
830
+ return { rows };
831
+ },
832
+ });
820
833
  export async function acquireEnsureSchemaLock(conn, tag = "ensureSchema") {
821
- await pollUntilLocked(async () => {
822
- const [rows] = await conn.query("SELECT GET_LOCK(?, 0) AS got", [ENSURE_SCHEMA_LOCK]);
823
- return Number(rows[0]?.got) === 1;
824
- }, { tag, label: `GET_LOCK('${ENSURE_SCHEMA_LOCK}')` });
834
+ await acquireAdvisoryLock(lockConnOf(conn), ENSURE_SCHEMA_LOCK_ID, tag);
825
835
  }
826
836
  export async function releaseEnsureSchemaLock(conn, tag = "ensureSchema") {
827
- try {
828
- await conn.query("SELECT RELEASE_LOCK(?)", [ENSURE_SCHEMA_LOCK]);
829
- }
830
- catch (err) {
831
- writeLogText("stderr", `[${tag}] RELEASE_LOCK('${ENSURE_SCHEMA_LOCK}') failed (lock auto-frees on connection close):`, err);
832
- }
837
+ await releaseAdvisoryLock(lockConnOf(conn), ENSURE_SCHEMA_LOCK_ID, tag);
833
838
  }
834
839
  export const SCHEMA_INDEXES = [
835
840
  ...CENTRAL_SCHEMA_INDEXES,
@@ -861,6 +866,7 @@ export async function ensureSchema(pool) {
861
866
  return Array.isArray(rows) ? rows : [];
862
867
  });
863
868
  await conn.query("INSERT IGNORE INTO image_bake_admit (pool) VALUES ('standard')");
869
+ await conn.query(markIfAbsentSql("mysql"), [SKILL_EXEMPTION_CUTOFF_ID, Date.now()]);
864
870
  await ensureIndexes((text, params) => conn.query(text, params), "mysql", SCHEMA_INDEXES);
865
871
  }
866
872
  finally {
@@ -0,0 +1,44 @@
1
+ /**
2
+ * 执行环境工厂的工作区托管表 —— 「这只工作区现在归谁持有 / 能不能删」的唯一答者。local-docker(容器)与 host(本服务铸的临时目录)两条车道的工厂都经它铸环境、读它判删。
3
+ *
4
+ * ## 律
5
+ * 引擎 park-only 契约:`suspendable:false` 的远端环境断言工作区活过环境对象 —— 续跑由工厂重连到**同一只**工作区;引擎在续跑赢下之后丢掉停驻腿的环境、不销毁它
6
+ * (销毁了就是拆掉续跑腿正在用的工作区)。所以:
7
+ * · **一只身份一只工作区**(身份 = 工厂上下文的 `taskId ?? sessionId`,与引擎交给工具的宿主任务号同一读法):同一身份还有一只没开始收场的环境 ⇒ 交回那一只
8
+ * (停驻腿的环境引擎还拿着,续跑腿拿到的就是它);那一只正在收场 ⇒ 等它的收场落定再铸,它欠着的账交给新的这只;没有 ⇒ 铸一只入表。
9
+ * 同一身份同一时刻只有一条腿(会话锁 + run 认领),交回同一只不会让两条腿同时用它。
10
+ * · **工作区的生命归持有它的环境,不归铸它的那一只**:表里每一只成员自己说它此刻持有哪只工作区;一只工作区能不能删 = 表里除问者之外还有没有成员持有它
11
+ * ({@link WorkspaceCustody.holder})—— 没有才删,有就把删交给最后放手的那一只。
12
+ *
13
+ * ## 两条车道怎么读同一张表
14
+ * · local-docker:工作区 = 身份派生名的那只容器;一只身份同一时刻只有一只没收场的成员(上面的交回律),容器名又由身份派生 ⇒ 不可能有第二个持有者,成员收场即删
15
+ * (读 {@link WorkspaceCustody.envOf};欠账 = 删除没证实的那只容器,交给同一身份的下一只)。
16
+ * · host:工作区 = 本服务铸的临时根;委派子代按父的工作目录落根 ⇒ 子代的环境与父的共持那只根(铸时问 {@link WorkspaceCustody.holder}「谁持有包含这一处的根」),
17
+ * 收场放手时问同一只「还有谁持有它」—— 父收场时子代还停驻在里面 ⇒ 父不删,子代最后一条腿收场时删。
18
+ *
19
+ * ## 出表
20
+ * 成员收场落定、什么都不欠、什么都不持有({@link CustodyMember.settled})⇒ 出表;没收场的成员(停驻腿的环境)一直在表里(停驻中被店层取消 / 过期的,引擎不销毁它,它持有的工作区
21
+ * 活到进程退出 —— local-docker 的容器由回收器按 run 行收,host 的目录由退出边界收)。
22
+ */
23
+ /** 表里的一只成员(一只执行环境)向表说的四件事。 */
24
+ export interface CustodyMember<Leftover> {
25
+ /** 收场的进度:没开始 ⇒ `undefined`(可以交回给同一身份的下一条腿);开始了 ⇒ 那一次收场(落定之后同一身份才铸新的)。 */
26
+ readonly releasing: Promise<void> | undefined;
27
+ /** 收场已落定。 */
28
+ readonly released: boolean;
29
+ /** 收场之后仍欠着、要交给同一身份下一只环境的账(local-docker:删除没证实的那只容器);不欠 ⇒ `undefined`。 */
30
+ readonly leftover: Leftover | undefined;
31
+ /** 什么都不再欠、不再持有 —— 出表。 */
32
+ readonly settled: boolean;
33
+ }
34
+ export interface WorkspaceCustody<E extends CustodyMember<L>, L> {
35
+ /**
36
+ * 这只身份的环境:还有一只没开始收场的 ⇒ 交回它;正在收场的 ⇒ 等它落定(再看一遍);都落定了 ⇒ 拿走欠账、铸一只入表。
37
+ * `mint` 抛 ⇒ 什么都不入表,原样抛。
38
+ */
39
+ envOf(identity: string, mint: (leftover: L | undefined) => E): Promise<E>;
40
+ /** 表里一只满足 `holds` 的成员(没有 ⇒ `undefined`)—— 「这只工作区现在还有谁持有」的唯一读法:问者把自己排除在 `holds` 里。 */
41
+ holder(holds: (member: E) => boolean): E | undefined;
42
+ }
43
+ export declare function createWorkspaceCustody<E extends CustodyMember<L>, L = never>(): WorkspaceCustody<E, L>;
44
+ //# sourceMappingURL=workspace-custody.d.ts.map
@@ -0,0 +1,47 @@
1
+ export function createWorkspaceCustody() {
2
+ const members = new Map();
3
+ const sweep = () => {
4
+ for (const member of members.keys())
5
+ if (member.settled)
6
+ members.delete(member);
7
+ };
8
+ return {
9
+ async envOf(identity, mint) {
10
+ for (;;) {
11
+ sweep();
12
+ const closing = [];
13
+ const owing = [];
14
+ for (const [member, id] of members) {
15
+ if (id !== identity)
16
+ continue;
17
+ if (member.releasing === undefined)
18
+ return member;
19
+ if (!member.released)
20
+ closing.push(member.releasing);
21
+ else if (member.leftover !== undefined)
22
+ owing.push(member);
23
+ }
24
+ if (closing.length > 0) {
25
+ await Promise.allSettled(closing);
26
+ continue;
27
+ }
28
+ let leftover;
29
+ for (const member of owing) {
30
+ leftover = member.leftover;
31
+ members.delete(member);
32
+ }
33
+ const env = mint(leftover);
34
+ members.set(env, identity);
35
+ return env;
36
+ }
37
+ },
38
+ holder(holds) {
39
+ sweep();
40
+ for (const member of members.keys())
41
+ if (holds(member))
42
+ return member;
43
+ return undefined;
44
+ },
45
+ };
46
+ }
47
+ //# sourceMappingURL=workspace-custody.js.map
@@ -25,16 +25,17 @@ export declare function assertProjectLayerOwnable(scope: string, envName: string
25
25
  */
26
26
  export declare function normalizeGitRemoteUrl(url: string): string | undefined;
27
27
  /**
28
- * 任务根 → 项目层身份([ref]):当前档(`r` 优先,没有 origin 退 `p`)+ 本 checkout 的低档(`r` 档时 = 它的 `p` 档)。
29
- * 缺席形 = 找根的缺席词(`bare` / `unreadable`);git 配置在而读不了 ⇒ `unreadable`。
28
+ * 任务根 → 项目层身份(文件头「项目身份一条律」):按档序(`project-layer-key.ts` `PROJECT_IDENTITY_TIERS`)取本检出有的各档 ——
29
+ * 每一档 = 它的身份文本的 {@link digest24}(`m`:记号里的项目 id;`r`:归一后的网络 origin;`p`:canonical 项目根,恒有)。
30
+ * 链首 = 当前档,其余 = `lowerTiers`(折叠来源)。缺席 = 找根的缺席词(`bare` / `unreadable`);git 配置在而读不了、或记号读不出 ⇒ `unreadable`。
30
31
  */
31
- export declare function projectLayerIdentityOf(taskRoot: string): {
32
+ export declare function projectLayerIdentityOf(taskRoot: string): Promise<{
32
33
  readonly root: string;
33
34
  readonly identity: ProjectLayerIdentity;
34
35
  readonly lowerTiers: readonly ProjectLayerIdentity[];
35
36
  } | {
36
37
  readonly absent: ProjectIdentityAbsentReason;
37
- };
38
+ }>;
38
39
  /** 一个任务的项目层:core 声明席 + 本 checkout 要折进来的低档 / 退役层(按序,`memory-project-layer.ts` 折)。 */
39
40
  export interface ProjectLayerOfTask {
40
41
  readonly key: MemoryProjectKey;
@@ -43,11 +44,11 @@ export interface ProjectLayerOfTask {
43
44
  /**
44
45
  * 任务根 → 项目层([ref]:`userrepo:<属主段>/<档>.<摘要>`,属主 = `owner`,调用方由 `projectLayerOwnerOf(全局层)` 求得)。
45
46
  * `foldFrom` = 同一 checkout 能推出的旧层:退役 `repo:<同一路径哈希>`(一次性迁移,窗见 `plugins/one-time-migrations.ts` 登记表 M25)
46
- * + 当前档是 `r` 时它的 `p` 档(常设律)。
47
+ * + 当前档之下的每一档(常设律:`m` 档折 `r` / `p`,`r` 档折 `p`)。
47
48
  * `taskRoot === undefined` = 调用方**此刻不知道**任务根在本机哪里(根在沙箱/设备盘上;或 host 车道会话没登记 cwd、手落在 factory 现铸的
48
49
  * 临时目录)⇒ `unreadable` —— 「我看不到」,不是「这里没有项目」。
49
50
  */
50
- export declare function projectLayerForTaskRoot(taskRoot: string | undefined, owner: string): ProjectLayerOfTask;
51
+ export declare function projectLayerForTaskRoot(taskRoot: string | undefined, owner: string): Promise<ProjectLayerOfTask>;
51
52
  /** {@link projectLayerForTaskRoot} 的声明席投影(core `TaskSpec.memory.projectKey`)。 */
52
- export declare function projectKeyForTaskRoot(taskRoot: string | undefined, owner: string): MemoryProjectKey;
53
+ export declare function projectKeyForTaskRoot(taskRoot: string | undefined, owner: string): Promise<MemoryProjectKey>;
53
54
  //# sourceMappingURL=project-identity.d.ts.map