@zq-silk/yui 0.13.9 → 0.14.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 (229) hide show
  1. package/ARCHITECTURE.md +69 -428
  2. package/README.md +108 -298
  3. package/dist/agent/managedRuntimeEnvironment.js +2 -2
  4. package/dist/cli/commandCatalog.js +39 -77
  5. package/dist/cli/interactionCandidates.js +4 -4
  6. package/dist/cli/interactionPolicy.js +7 -10
  7. package/dist/cli/invocationRouter.js +1 -1
  8. package/dist/cli/updateCommand.js +6 -27
  9. package/dist/cli/updateOrchestrator.js +63 -484
  10. package/dist/cli/updatePorts.js +15 -333
  11. package/dist/cli/upgradeCommand.js +8 -65
  12. package/dist/cli.js +57 -242
  13. package/dist/commands/configCommands.js +11 -62
  14. package/dist/commands/executionAuditCommands.js +23 -23
  15. package/dist/commands/globalRoleCommands.js +2 -7
  16. package/dist/commands/jobCommands.js +1 -4
  17. package/dist/commands/projectCommands.js +5 -5
  18. package/dist/commands/releaseCommands.js +3 -10
  19. package/dist/commands/resourcesCommands.js +2 -2
  20. package/dist/commands/sessionCommands.js +2 -2
  21. package/dist/commands/taskActor.js +31 -39
  22. package/dist/commands/taskChangeSetCommands.js +5 -12
  23. package/dist/commands/taskCommands.js +464 -1249
  24. package/dist/commands/taskContextCommand.js +35 -36
  25. package/dist/commands/taskExecutionCommands.js +33 -46
  26. package/dist/commands/taskInputCommands.js +20 -42
  27. package/dist/commands/taskNextActionCommand.js +9 -9
  28. package/dist/commands/taskOverviewCommand.js +56 -95
  29. package/dist/commands/taskRoleRuntimeStatus.js +77 -107
  30. package/dist/commands/taskUpstreamCommands.js +3 -3
  31. package/dist/commands/taskWorkspaceCommands.js +7 -79
  32. package/dist/commands/telemetryCommands.js +32 -124
  33. package/dist/commands/workflowCommands.js +3 -3
  34. package/dist/completion/fileCompletionManager.js +1 -1
  35. package/dist/config/configCatalog.js +4 -6
  36. package/dist/config/yuiConfig.js +6 -43
  37. package/dist/context/dispatchContext.js +14 -30
  38. package/dist/context/roleSessionContext.js +2 -2
  39. package/dist/context/sessionBootstrapManifest.js +2 -2
  40. package/dist/context/{runContextPack.js → turnContextPack.js} +58 -73
  41. package/dist/context/turnInputContract.js +204 -0
  42. package/dist/context/wakeNotification.js +2 -2
  43. package/dist/controller/agentRuntimeObserver.js +18 -18
  44. package/dist/controller/clientRuntime.js +13 -20
  45. package/dist/controller/controller.js +89 -116
  46. package/dist/controller/fileSchedulerStoreAdapter.js +511 -742
  47. package/dist/controller/jobControl.js +17 -17
  48. package/dist/controller/resourceInventory.js +6 -6
  49. package/dist/controller/resourceInventoryLinux.js +38 -59
  50. package/dist/controller/resourceInventoryRpc.js +3 -3
  51. package/dist/controller/resourceInventoryWorker.js +1 -1
  52. package/dist/controller/runtime.js +46 -53
  53. package/dist/controller/runtimeEventInbox.js +23 -31
  54. package/dist/controller/runtimeEventProcessor.js +15 -15
  55. package/dist/controller/{runtimeHookRunFence.js → runtimeHookTurnFence.js} +87 -81
  56. package/dist/controller/runtimeLaunchCoordinator.js +3 -3
  57. package/dist/controller/runtimeObservationHook.js +11 -8
  58. package/dist/controller/sessionNotify.js +11 -11
  59. package/dist/controller/structuredProviderObservation.js +89 -16
  60. package/dist/coordination/workMailbox.js +309 -579
  61. package/dist/coordination/workMailboxQueue.js +5 -6
  62. package/dist/core/controllerClient.js +1 -91
  63. package/dist/core/controllerServer.js +3 -6
  64. package/dist/doctor/doctor.js +51 -314
  65. package/dist/execution/candidateConvergence.js +3 -3
  66. package/dist/execution/executionGroup.js +32 -33
  67. package/dist/execution/executionHealth.js +36 -38
  68. package/dist/execution/resourceBroker.js +12 -12
  69. package/dist/executor/agentExecutor.js +30 -265
  70. package/dist/executor/effectiveLaunch.js +9 -19
  71. package/dist/executor/executorRegistry.js +43 -17
  72. package/dist/executor/fileRoleLaunchPlanner.js +56 -56
  73. package/dist/executor/launchPlan.js +3 -3
  74. package/dist/executor/workspacePreflightClassification.js +16 -16
  75. package/dist/grant/capabilityGrant.js +20 -25
  76. package/dist/input/inputRequest.js +12 -12
  77. package/dist/integration/changeSet.js +9 -13
  78. package/dist/integration/changeSetManifest.js +1 -1
  79. package/dist/integration/gitIntegrationService.js +2 -2
  80. package/dist/integration/integrationQueueService.js +5 -21
  81. package/dist/integration/overlapDiagnostics.js +2 -2
  82. package/dist/interaction/operatorPresentation.js +1 -1
  83. package/dist/job/jobRunner.js +1 -1
  84. package/dist/lifecycle/canonicalLifecycleEvent.js +25 -25
  85. package/dist/lifecycle/{exactRunTerminalization.js → exactTurnTerminalization.js} +116 -232
  86. package/dist/message/message.js +6 -6
  87. package/dist/observability/executionAudit.js +74 -86
  88. package/dist/observability/faultClassification.js +7 -24
  89. package/dist/observability/orchestrationMetrics.js +5 -5
  90. package/dist/observability/runtimeIdentity.js +34 -101
  91. package/dist/profile/agentProfile.js +1 -1
  92. package/dist/release/cliHomeReleaseFence.js +3 -3
  93. package/dist/release/releaseWorkflowEngine.js +6 -10
  94. package/dist/release/releaseWorkflowPorts.js +2 -2
  95. package/dist/release/workflowFileLock.js +1 -1
  96. package/dist/repository/checkoutSwap.js +1 -1
  97. package/dist/repository/gitWorkspace.js +0 -13
  98. package/dist/repository/project.js +1 -1
  99. package/dist/repository/projectMaintenanceLock.js +1 -2
  100. package/dist/repository/taskBaseFreshness.js +13 -13
  101. package/dist/repository/taskWorkspaceCoordinator.js +21 -25
  102. package/dist/repository/taskWorkspaceIdentity.js +8 -21
  103. package/dist/repository/taskWorkspacePreparer.js +63 -620
  104. package/dist/resources/autoResourceGc.js +3 -3
  105. package/dist/resources/liveReferences.js +1 -1
  106. package/dist/resources/resourceGc.js +1 -1
  107. package/dist/resources/sqliteResourceRegistry.js +1 -1
  108. package/dist/review/reviewDecision.js +7 -7
  109. package/dist/review/reviewFindingLedger.js +1 -1
  110. package/dist/review/reviewOutcomeClassifier.js +35 -50
  111. package/dist/review/reviewRound.js +14 -25
  112. package/dist/review/reviewerAvailability.js +4 -4
  113. package/dist/review/taskFinalReviewContractResolution.js +41 -0
  114. package/dist/role/role.js +6 -3
  115. package/dist/runtime/agentDriver.js +4 -0
  116. package/dist/runtime/agentHost.js +151 -36
  117. package/dist/runtime/builtinAgentDrivers.js +79 -2
  118. package/dist/runtime/codexAppServerRuntime.js +122 -0
  119. package/dist/runtime/continuationManager.js +3 -5
  120. package/dist/runtime/exactControlPlane.js +17 -42
  121. package/dist/runtime/firstProgressAdvisory.js +1 -1
  122. package/dist/runtime/index.js +2 -2
  123. package/dist/runtime/launchDiagnostics.js +2 -2
  124. package/dist/runtime/lifecycleReservation.js +4 -4
  125. package/dist/runtime/processExitObservation.js +1 -1
  126. package/dist/runtime/processExitOutbox.js +0 -32
  127. package/dist/runtime/promptEnvelope.js +9 -9
  128. package/dist/runtime/providerContinuation.js +2 -2
  129. package/dist/runtime/providerContinuationReconciliationService.js +2 -2
  130. package/dist/runtime/providerRuntimeIdentity.js +76 -24
  131. package/dist/runtime/recentTurnIds.js +38 -0
  132. package/dist/runtime/runtimeContinuationProjection.js +2 -2
  133. package/dist/runtime/runtimeHealthPolicy.js +1 -1
  134. package/dist/runtime/runtimeObservation.js +58 -21
  135. package/dist/runtime/runtimeProjection.js +19 -27
  136. package/dist/runtime/sessionLaunchRequest.js +2 -2
  137. package/dist/runtime/structuredProviderHost.js +177 -12
  138. package/dist/runtime/tmuxAdapters.js +46 -11
  139. package/dist/scheduler/activeRoleTurnDelivery.js +245 -0
  140. package/dist/scheduler/leaderWakeupProcessor.js +147 -445
  141. package/dist/scheduler/operatorEvent.js +4 -6
  142. package/dist/scheduler/resourceQueueProjection.js +71 -0
  143. package/dist/scheduler/{roleRunLiveness.js → roleTurnLiveness.js} +24 -28
  144. package/dist/scheduler/{roleRunStall.js → roleTurnStall.js} +162 -247
  145. package/dist/scheduler/taskExecutionProjection.js +99 -99
  146. package/dist/scheduler/taskObservabilityProjection.js +10 -10
  147. package/dist/scheduler/taskWake.js +4 -4
  148. package/dist/scheduler/wakeReason.js +6 -10
  149. package/dist/scheduler/wakeupQueue.js +0 -9
  150. package/dist/setup/setupCommand.js +2 -2
  151. package/dist/storage/currentTaskStore.js +46 -0
  152. package/dist/storage/persistenceWorker.js +1 -1
  153. package/dist/storage/sqliteSchema.js +81 -485
  154. package/dist/storage/sqliteStore.js +206 -365
  155. package/dist/storage/storageSchema.js +1 -15
  156. package/dist/storage/storageVersions.js +8 -9
  157. package/dist/storage/storeRpc.js +10 -34
  158. package/dist/storage/taskStore.js +63 -3415
  159. package/dist/storage/upgrade/recordVersions.js +55 -149
  160. package/dist/storage/upgrade/upgradeOrchestrator.js +89 -1752
  161. package/dist/task/completionReadiness.js +5 -5
  162. package/dist/task/deliveryGuard.js +10 -10
  163. package/dist/task/nextAction.js +74 -77
  164. package/dist/task/task.js +2 -4
  165. package/dist/task/taskRecordReference.js +3 -3
  166. package/dist/task/taskRecordRetirement.js +1 -1
  167. package/dist/telemetry/sqliteTelemetryStore.js +53 -53
  168. package/dist/telemetry/telemetryConfig.js +14 -14
  169. package/dist/telemetry/telemetryWiring.js +8 -14
  170. package/dist/turn/turn.js +356 -0
  171. package/dist/{run/runIdentity.js → turn/turnIdentity.js} +4 -4
  172. package/dist/verification/gateArtifactStore.js +2 -2
  173. package/dist/web/assets/client/app.js +1 -1
  174. package/dist/web/assets/client/components.js +29 -54
  175. package/dist/web/assets/client/i18n.js +17 -37
  176. package/dist/web/assets/client/view.js +37 -37
  177. package/dist/web/assets/shell.js +1 -1
  178. package/dist/web/assets/styles/cards.js +5 -5
  179. package/dist/web/assets/styles/widgets.js +1 -2
  180. package/dist/web/webSnapshot.js +65 -60
  181. package/dist/workItem/workItem.js +11 -11
  182. package/dist/workspace/workItemChangeSetManager.js +1 -1
  183. package/docs/task-local-identity.md +5 -5
  184. package/i18n/README.zh-CN.md +41 -54
  185. package/package.json +1 -1
  186. package/skills/yui-leader/SKILL.md +85 -83
  187. package/skills/yui-operator/SKILL.md +16 -16
  188. package/skills/yui-reviewer/SKILL.md +10 -10
  189. package/skills/yui-runtime/SKILL.md +17 -18
  190. package/skills/yui-worker/SKILL.md +16 -18
  191. package/dist/context/runContextContract.js +0 -162
  192. package/dist/executor/turnCompletion.js +0 -126
  193. package/dist/review/taskFinalReviewContractEvent.js +0 -1
  194. package/dist/review/taskFinalReviewContractRebind.js +0 -367
  195. package/dist/run/agentRun.js +0 -356
  196. package/dist/run/rejectedYieldAttempt.js +0 -221
  197. package/dist/run/runControlRequest.js +0 -50
  198. package/dist/run/yieldReceipt.js +0 -65
  199. package/dist/runtime/turnCompletion.js +0 -3
  200. package/dist/scheduler/actionability.js +0 -323
  201. package/dist/scheduler/activeRoleRunDelivery.js +0 -868
  202. package/dist/scheduler/activeTaskProgress.js +0 -126
  203. package/dist/storage/compatibleTaskStore.js +0 -248
  204. package/dist/storage/migration/baseline.js +0 -78
  205. package/dist/storage/migration/classifier.js +0 -51
  206. package/dist/storage/migration/compatibleCodec.js +0 -53
  207. package/dist/storage/migration/engine.js +0 -147
  208. package/dist/storage/migration/index.js +0 -33
  209. package/dist/storage/migration/planner.js +0 -154
  210. package/dist/storage/migration/productionRegistry.js +0 -3931
  211. package/dist/storage/migration/registry.js +0 -169
  212. package/dist/storage/migration/report.js +0 -54
  213. package/dist/storage/migration/types.js +0 -31
  214. package/dist/storage/upgrade/homeClassification.js +0 -353
  215. package/dist/storage/upgrade/homeMigrationTarget.js +0 -595
  216. package/dist/storage/upgrade/migrationReceipt.js +0 -67
  217. package/dist/storage/upgrade/offlineUpgradeInventory.js +0 -453
  218. package/dist/storage/upgrade/productionMigrationRegistry.js +0 -6
  219. package/dist/storage/upgrade/pseudoLayoutRepair.js +0 -241
  220. package/dist/storage/upgrade/recordVersionScan.js +0 -176
  221. package/dist/storage/upgrade/sqliteFileSet.js +0 -50
  222. package/dist/storage/upgrade/sqliteMigrationTarget.js +0 -375
  223. package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +0 -305
  224. package/dist/storage/upgrade/sqliteStateMigration.js +0 -918
  225. package/dist/storage/upgrade/switchProgress.js +0 -80
  226. package/dist/storage/upgrade/upgradeReceipt.js +0 -161
  227. package/dist/storage/upgradeCoordination.js +0 -187
  228. package/dist/storage/upgradeFence.js +0 -366
  229. package/dist/telemetry/telemetryCompaction.js +0 -252
@@ -18,7 +18,6 @@
18
18
  * - Crash recovery .............. WAL rollback of uncommitted transactions;
19
19
  * outbox replay of committed-but-unacked effects.
20
20
  * - Record family versioning .... full record (incl. schemaVersion) in payload.
21
- * - Upgrade fence ............... assertHomeWritable at the write boundary.
22
21
  * - Evidence retention .......... events/review_rounds/change_sets/
23
22
  * integration_attempts are never pruned.
24
23
  *
@@ -38,9 +37,8 @@ import { existsSync, mkdirSync } from "node:fs";
38
37
  import { join } from "node:path";
39
38
  import { isDeepStrictEqual } from "node:util";
40
39
  import Database from "better-sqlite3";
41
- import { consumePendingBatch, mailboxTargetKey, pendingLane, validateWorkMailbox } from "../coordination/workMailbox.js";
40
+ import { consumePendingBatch, mailboxTargetKey, validateWorkMailbox } from "../coordination/workMailbox.js";
42
41
  import { validateContextSnapshot } from "../context/contextSnapshot.js";
43
- import { TASK_FINAL_REVIEW_CONTRACT_REBOUND_EVENT } from "../review/taskFinalReviewContractEvent.js";
44
42
  import { compareRuntimeSessionCandidates, projectRuntimeSessionCandidate } from "../runtime/runtimeSessionCandidate.js";
45
43
  import { validateReviewFinding } from "../review/reviewFinding.js";
46
44
  import { reviewFindingLedgerMode } from "../review/reviewFindingLedger.js";
@@ -51,12 +49,10 @@ import { validateTaskWake } from "../scheduler/taskWake.js";
51
49
  import { operationalTaskRecords, TASK_RECORD_RETIRED_EVENT } from "../task/taskRecordRetirement.js";
52
50
  import { TASK_RECORD_ID_PREFIXES } from "../task/taskRecordReference.js";
53
51
  import { managedWorkspaceKey } from "../worktree/managedWorkspace.js";
54
- import { assertHomeWritable } from "./upgradeFence.js";
55
- import { CURRENT_CONFIG_SCHEMA_VERSION, CURRENT_PENDING_WAKEUP_SCHEMA_VERSION, CURRENT_WORK_MAILBOX_SCHEMA_VERSION, executionLaneActiveRunKey, executionLaneActiveRunKeyParts, StorageConflictError, StorageCancelledError, StorageRecordError, FileTaskStore, storedCapabilityGrant, storedPublicationReference, storedReleaseWorkflow, isValidCapabilityGrantTransition, isValidReleaseWorkflowTransition, validateYuiConfig } from "./taskStore.js";
52
+ import { CURRENT_CONFIG_SCHEMA_VERSION, CURRENT_WORK_MAILBOX_SCHEMA_VERSION, executionLaneActiveTurnKey, executionLaneActiveTurnKeyParts, StorageConflictError, StorageCancelledError, StorageRecordError, storedCapabilityGrant, storedPublicationReference, storedReleaseWorkflow, isValidCapabilityGrantTransition, isValidReleaseWorkflowTransition, pendingWakeupProjection, validateYuiConfig } from "./taskStore.js";
56
53
  import { publicationExternalKey } from "../task/publicationReference.js";
57
54
  import { gateArtifactKey, validateGateArtifact } from "../verification/gateArtifact.js";
58
- import { inspectSqliteSchemaMigrations, migrateSqliteSchema, SqliteSchemaMigrationError, SQLITE_AGGREGATE_VERSION, SQLITE_LAYOUT_VERSION, TELEMETRY_KEEP_PER_GENERATION, TELEMETRY_RUN_CAP } from "./sqliteSchema.js";
59
- import { inspectStorageSchema } from "./storageSchema.js";
55
+ import { inspectSqliteSchemaMigrations, migrateSqliteSchema, SqliteSchemaMigrationError, SQLITE_AGGREGATE_VERSION, SQLITE_LAYOUT_VERSION, TELEMETRY_KEEP_PER_GENERATION, TELEMETRY_TURN_CAP } from "./sqliteSchema.js";
60
56
  /** Read the immutable Home identity without opening a writable Store connection. */
61
57
  export function readSqliteHomeIdentity(rootDir, databaseFilename = "yui.db") {
62
58
  const database = new Database(join(rootDir, databaseFilename), {
@@ -116,39 +112,18 @@ function isUniqueConstraint(error) {
116
112
  && error.code === "SQLITE_CONSTRAINT_PRIMARYKEY"
117
113
  || error?.code === "SQLITE_CONSTRAINT_UNIQUE";
118
114
  }
119
- /** Project a leader-role work mailbox's pending batch to a PendingWakeup (mirrors taskStore.ts). */
120
- function pendingWakeupProjection(mailbox) {
121
- const pending = mailbox === null ? null : pendingLane(mailbox, "normal");
122
- if (mailbox === null || mailbox.target.kind !== "role" || mailbox.target.roleName !== "leader"
123
- || pending === null) {
124
- return null;
125
- }
126
- return {
127
- schemaVersion: CURRENT_PENDING_WAKEUP_SCHEMA_VERSION,
128
- taskId: mailbox.target.taskId,
129
- reasons: [...pending.reasons],
130
- requestCount: pending.requestCount,
131
- firstRequestedAt: pending.firstQueuedAt,
132
- lastRequestedAt: pending.lastQueuedAt
133
- };
134
- }
135
115
  export class SqliteTaskStore {
136
116
  #db;
137
117
  #rootDir;
138
- #migration;
139
118
  #openedSchemaHead;
140
119
  #inTransaction = false;
141
120
  #dirty = false;
142
121
  constructor(rootDir, _options = {}) {
143
122
  this.#rootDir = rootDir;
144
- this.#migration = _options.migration ?? false;
145
123
  mkdirSync(rootDir, { recursive: true, mode: 0o700 });
146
124
  const filename = _options.databaseFilename ?? "yui.db";
147
125
  const databasePath = join(rootDir, filename);
148
126
  const databaseExisted = existsSync(databasePath);
149
- if (this.#migration && filename === "yui.db") {
150
- throw new SqliteSchemaMigrationError("offline migration mode cannot open the authoritative yui.db", "admission");
151
- }
152
127
  this.#db = new Database(databasePath);
153
128
  try {
154
129
  // §4.1 / §9: WAL, no fsync weakening, FKs on, busy timeout for CLI contention.
@@ -158,7 +133,7 @@ export class SqliteTaskStore {
158
133
  this.#db.pragma("busy_timeout = 5000");
159
134
  this.#db.pragma("wal_autocheckpoint = 1000");
160
135
  migrateSqliteSchema(this.#db, {
161
- mode: this.#migration || !databaseExisted ? "apply" : "validate"
136
+ mode: !databaseExisted ? "apply" : "validate"
162
137
  });
163
138
  const schema = inspectSqliteSchemaMigrations(this.#db);
164
139
  this.#openedSchemaHead = {
@@ -213,14 +188,8 @@ export class SqliteTaskStore {
213
188
  this.#inTransaction = false;
214
189
  this.#dirty = false;
215
190
  }
216
- /** The upgrade-admission fence, honored at the single write moment (§9). */
191
+ /** Verify the SQLite schema head at the single write boundary. */
217
192
  #prepareWrite() {
218
- // The staged migration populates the sidecar database while the upgrade
219
- // fence is active (the migration IS the upgrade), so it bypasses the
220
- // per-write admission check. Production stores never set migration mode.
221
- if (this.#migration)
222
- return;
223
- assertHomeWritable(this.#rootDir);
224
193
  const current = this.#db.prepare("SELECT version, checksum FROM schema_migrations ORDER BY version DESC LIMIT 1").get();
225
194
  if (current?.version !== this.#openedSchemaHead.version
226
195
  || current.checksum !== this.#openedSchemaHead.checksum) {
@@ -245,14 +214,9 @@ export class SqliteTaskStore {
245
214
  }
246
215
  this.#begin();
247
216
  try {
248
- // Acquire SQLite's write reservation before checking the upgrade fence.
249
- // A writer that started first commits before the upgrader's final
250
- // transactional inventory; a writer that starts later sees the fence.
251
217
  this.#prepareWrite();
252
218
  const result = fn();
253
- if (!this.#migration) {
254
- this.#bumpRevision();
255
- }
219
+ this.#bumpRevision();
256
220
  this.#commit();
257
221
  return result;
258
222
  }
@@ -268,19 +232,15 @@ export class SqliteTaskStore {
268
232
  this.#begin();
269
233
  try {
270
234
  // Pin the Store's validated schema generation before user code runs.
271
- // This prevents a long-lived pre-upgrade Store from executing old SQL
272
- // after an in-place migration has committed.
235
+ // This prevents a long-lived Store from executing SQL after the database
236
+ // schema head has changed underneath it.
273
237
  this.#prepareWrite();
274
238
  const result = run(this);
275
239
  if (this.#dirty) {
276
240
  if (options?.requestId !== undefined) {
277
241
  this.#insertOutbox(options.requestId, options.outboxCommand ?? null);
278
242
  }
279
- // The staged migration sets the revision explicitly via
280
- // migrationSetHomeMeta; the commit must not bump it.
281
- if (!this.#migration) {
282
- this.#bumpRevision();
283
- }
243
+ this.#bumpRevision();
284
244
  }
285
245
  this.#commit();
286
246
  return result;
@@ -292,8 +252,8 @@ export class SqliteTaskStore {
292
252
  }
293
253
  /** Async transaction seam used by queue operations that must inspect Git
294
254
  * before committing the durable queue state. The SQLite write transaction
295
- * remains open across the awaited callback, matching FileTaskStore's
296
- * transactionAsync semantics and preserving the single-writer boundary. */
255
+ * remains open across the awaited callback, preserving the single-writer
256
+ * boundary. */
297
257
  async transactionAsync(execute) {
298
258
  if (this.#inTransaction)
299
259
  return execute(this);
@@ -335,11 +295,7 @@ export class SqliteTaskStore {
335
295
  if (options?.requestId !== undefined) {
336
296
  this.#insertOutbox(options.requestId, options.outboxCommand ?? null);
337
297
  }
338
- // The staged migration sets the revision explicitly via
339
- // migrationSetHomeMeta; the commit must not bump it.
340
- if (!this.#migration) {
341
- this.#bumpRevision();
342
- }
298
+ this.#bumpRevision();
343
299
  }
344
300
  this.#commit();
345
301
  return result;
@@ -405,9 +361,7 @@ export class SqliteTaskStore {
405
361
  if (options.requestId !== undefined) {
406
362
  this.#insertOutbox(options.requestId, options.outboxCommand ?? commands);
407
363
  }
408
- if (!this.#migration) {
409
- this.#bumpRevision();
410
- }
364
+ this.#bumpRevision();
411
365
  }
412
366
  this.#commit();
413
367
  return results;
@@ -510,49 +464,6 @@ export class SqliteTaskStore {
510
464
  if (row === undefined)
511
465
  throw new StorageRecordError(`Task not found: ${taskId}`);
512
466
  }
513
- // -- migration bulk-load helpers (state.json -> SQLite, task-21 §8) ----------
514
- // These are used only by the staged offline migration, which runs with the
515
- // `migration` option (fence bypass). They seed infrastructure tables that the
516
- // document owns (home identity/revision, global and per-task ID high-water
517
- // marks) so the opened store continues from the same counters.
518
- /** Preserve the document's Home identity and revision in `home_meta`. */
519
- migrationSetHomeMeta(identity, revision) {
520
- this.#mutate(() => {
521
- this.#db.prepare(`UPDATE home_meta SET home_identity = ?, revision = ?, updated_at = ? WHERE id = 1`).run(this.#json(identity), revision, this.#now());
522
- });
523
- }
524
- /** Seed a global ID high-water mark (task/project) at least `highWater`. */
525
- migrationSeedGlobalSequence(name, highWater) {
526
- this.#mutate(() => {
527
- this.#db.prepare(`INSERT INTO global_sequences (name, high_water) VALUES (?, ?)
528
- ON CONFLICT(name) DO UPDATE SET high_water = MAX(high_water, ?)`).run(name, highWater, highWater);
529
- });
530
- }
531
- /** Seed a per-task ID high-water mark at least `highWater`. */
532
- migrationSeedIdSequence(taskId, kind, highWater) {
533
- this.#mutate(() => {
534
- this.#db.prepare(`INSERT INTO id_sequences (task_id, kind, high_water) VALUES (?, ?, ?)
535
- ON CONFLICT(task_id, kind) DO UPDATE SET high_water = MAX(high_water, ?)`).run(taskId, kind, highWater, highWater);
536
- });
537
- }
538
- /** Restore one validated historical grant into a disposable migration output. */
539
- migrationRestoreCapabilityGrant(taskId, grant) {
540
- if (!this.#migration) {
541
- throw new StorageRecordError("Historical capability grants may only be restored into a migration sidecar.");
542
- }
543
- const stored = storedCapabilityGrant(grant);
544
- if (stored.taskId !== taskId) {
545
- throw new StorageRecordError(`Capability grant belongs to another Task: ${stored.taskId}`);
546
- }
547
- this.#requireTask(taskId);
548
- this.#mutate(() => {
549
- const existing = this.#getPayload("capability_grants", "task_id = ? AND grant_id = ?", [taskId, stored.id]);
550
- if (existing !== null) {
551
- throw new StorageRecordError(`Migration cannot overwrite capability grant: ${taskId}/${stored.id}`);
552
- }
553
- this.#db.prepare("INSERT INTO capability_grants (task_id, grant_id, payload, updated_at) VALUES (?, ?, ?, ?)").run(taskId, stored.id, this.#json(stored), this.#now());
554
- });
555
- }
556
467
  // -- generic payload helpers ------------------------------------------------
557
468
  #getPayload(table, where, params) {
558
469
  const row = this.#db.prepare(`SELECT payload FROM ${table} WHERE ${where}`).get(...params);
@@ -657,7 +568,7 @@ export class SqliteTaskStore {
657
568
  const boundTasks = this.listTasks().filter((task) => task.projectBindings.some((binding) => binding.projectId === projectId));
658
569
  const activeTasks = boundTasks.filter((task) => task.status === "active");
659
570
  const unresolvedWorkItemRefs = [];
660
- const activeRunRefs = [];
571
+ const activeTurnRefs = [];
661
572
  const unresolvedIntegrationRefs = [];
662
573
  for (const task of activeTasks) {
663
574
  for (const workItem of this.listWorkItems(task.id)) {
@@ -665,9 +576,9 @@ export class SqliteTaskStore {
665
576
  unresolvedWorkItemRefs.push(`${task.id}/${workItem.id}`);
666
577
  }
667
578
  }
668
- for (const run of this.listAgentRuns(task.id)) {
579
+ for (const run of this.listTurns(task.id)) {
669
580
  if (run.status === "active")
670
- activeRunRefs.push(`${task.id}/${run.id}`);
581
+ activeTurnRefs.push(`${task.id}/${run.id}`);
671
582
  }
672
583
  for (const attempt of this.listIntegrationAttempts(task.id)) {
673
584
  if (attempt.status === "running" || attempt.status === "blocked") {
@@ -680,7 +591,7 @@ export class SqliteTaskStore {
680
591
  boundTaskIds: boundTasks.map(({ id }) => id),
681
592
  activeTaskIds: activeTasks.map(({ id }) => id),
682
593
  unresolvedWorkItemRefs,
683
- activeRunRefs,
594
+ activeTurnRefs,
684
595
  unresolvedIntegrationRefs
685
596
  };
686
597
  }
@@ -794,15 +705,14 @@ export class SqliteTaskStore {
794
705
  const task = this.#getPayload("task_records", "task_id = ?", [taskId]);
795
706
  if (task === null)
796
707
  return null;
797
- // One indexed query (idx_agent_runs_role_status) covers both the active
798
- // Runs the projection waits on and the Leader Runs the budget consumes.
799
- const events = this.#sortById(this.#listPayload("events", "task_id = ? AND type IN (?, ?, ?)", [
708
+ // One indexed query (idx_turns_role_status) covers both the active
709
+ // Turns the projection waits on and the Leader Turns the budget consumes.
710
+ const events = this.#sortById(this.#listPayload("events", "task_id = ? AND type IN (?, ?)", [
800
711
  taskId,
801
- TASK_FINAL_REVIEW_CONTRACT_REBOUND_EVENT,
802
712
  TASK_RECORD_RETIRED_EVENT,
803
713
  "review.completed"
804
714
  ]), (event) => event.id);
805
- const runs = operationalTaskRecords(this.#sortById(this.#listPayload("agent_runs", "task_id = ? AND (status = 'active' OR role_name = 'leader')", [taskId]), (run) => run.id), events, "agent-run");
715
+ const runs = operationalTaskRecords(this.#sortById(this.#listPayload("turns", "task_id = ? AND (status = 'active' OR role_name = 'leader')", [taskId]), (run) => run.id), events, "turn");
806
716
  return {
807
717
  task: {
808
718
  id: task.id,
@@ -816,14 +726,12 @@ export class SqliteTaskStore {
816
726
  integrations: this.#sortById(this.#listPayload("integration_attempts", "task_id = ?", [taskId]), (attempt) => attempt.id),
817
727
  integrationQueueEntries: this.#sortById(this.#listPayload("integration_queue", "task_id = ?", [taskId]), (entry) => entry.id),
818
728
  reviewRounds: this.#sortById(this.#listPayload("review_rounds", "task_id = ?", [taskId]), (round) => round.id),
819
- taskFinalReviewContractEvents: events
820
- .filter((event) => event.type === TASK_FINAL_REVIEW_CONTRACT_REBOUND_EVENT),
821
729
  reviewConfig: this.getReviewConfig(),
822
730
  openInputRequests: this.#sortById(this.#listPayload("input_requests", "task_id = ? AND status = 'open'", [taskId]), (request) => request.id),
823
- activeRuns: runs.filter((run) => run.status === "active"),
824
- leaderRuns: runs.filter((run) => run.roleName === "leader"),
731
+ activeTurns: runs.filter((run) => run.status === "active"),
732
+ leaderTurns: runs.filter((run) => run.roleName === "leader"),
825
733
  reviewOutcomeEvidence: {
826
- agentRuns: this.#sortById(this.#listPayload("agent_runs", "task_id = ?", [taskId]).filter((run) => run.purpose === "review"), (run) => run.id),
734
+ turns: this.#sortById(this.#listPayload("turns", "task_id = ?", [taskId]).filter((run) => run.purpose === "review"), (run) => run.id),
827
735
  reviewFindings: this.listReviewFindings(taskId),
828
736
  events: events.filter((event) => event.type === "review.completed")
829
737
  }
@@ -835,7 +743,7 @@ export class SqliteTaskStore {
835
743
  return null;
836
744
  return {
837
745
  ...base,
838
- agentRuns: operationalTaskRecords(this.listAgentRuns(taskId), this.listEvents(taskId), "agent-run"),
746
+ turns: operationalTaskRecords(this.listTurns(taskId), this.listEvents(taskId), "turn"),
839
747
  managedWorkspaces: this.#sortById(this.#listPayload("managed_workspaces", "task_id = ?", [taskId]), (workspace) => managedWorkspaceKey(workspace.owner)),
840
748
  durableJobs: this.#sortById(this.#listPayload("durable_jobs", "task_id = ?", [taskId]), (job) => job.id),
841
749
  integrationQueueEntries: this.#sortById(this.#listPayload("integration_queue", "task_id = ?", [taskId]), (entry) => entry.id),
@@ -1552,35 +1460,35 @@ export class SqliteTaskStore {
1552
1460
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(stored.taskId, stored.id, stored.scope, stored.scopeRef ?? null, stored.sequence, stored.digest, this.#json(stored), stored.frozenAt);
1553
1461
  });
1554
1462
  }
1555
- // -- agent runs -------------------------------------------------------------
1556
- nextAgentRunId(taskId) { return this.#nextTaskRecordId(taskId, "agentRun"); }
1557
- peekNextAgentRunId(taskId) { return this.#peekTaskRecordId(taskId, "agentRun"); }
1558
- getAgentRun(taskId, runId) {
1559
- return this.#getPayload("agent_runs", "task_id = ? AND run_id = ?", [taskId, runId]);
1463
+ // -- turns -----------------------------------------------------------------
1464
+ nextTurnId(taskId) { return this.#nextTaskRecordId(taskId, "turn"); }
1465
+ peekNextTurnId(taskId) { return this.#peekTaskRecordId(taskId, "turn"); }
1466
+ getTurn(taskId, turnId) {
1467
+ return this.#getPayload("turns", "task_id = ? AND turn_id = ?", [taskId, turnId]);
1560
1468
  }
1561
- listAgentRuns(taskId) {
1562
- return this.#sortById(this.#listPayload("agent_runs", "task_id = ?", [taskId]), (run) => run.id);
1469
+ listTurns(taskId) {
1470
+ return this.#sortById(this.#listPayload("turns", "task_id = ?", [taskId]), (turn) => turn.id);
1563
1471
  }
1564
- saveAgentRun(run) {
1565
- if (run.taskId !== undefined)
1566
- this.#requireTask(run.taskId);
1567
- if (run.reviewRoundId !== undefined) {
1568
- const round = this.getReviewRound(run.taskId, run.reviewRoundId);
1472
+ saveTurn(turn) {
1473
+ if (turn.taskId !== undefined)
1474
+ this.#requireTask(turn.taskId);
1475
+ if (turn.reviewRoundId !== undefined) {
1476
+ const round = this.getReviewRound(turn.taskId, turn.reviewRoundId);
1569
1477
  if (round === null) {
1570
- throw new StorageRecordError(`Agent run ReviewRound not found: ${run.reviewRoundId}.`);
1478
+ throw new StorageRecordError(`Turn ReviewRound not found: ${turn.reviewRoundId}.`);
1571
1479
  }
1572
- const roundWorkItemId = round.workItemId ?? round.legacyAnchor?.workItemId;
1480
+ const roundWorkItemId = round.workItemId;
1573
1481
  const laneRole = round.executionGroup?.lanes
1574
- .find(({ id }) => id === run.executionLaneId)?.roleName;
1575
- if (roundWorkItemId !== run.workItemId
1576
- || (round.reviewerRoleName !== run.roleName && laneRole !== run.roleName)) {
1577
- throw new StorageRecordError(`Agent run does not match ReviewRound: ${run.id}.`);
1482
+ .find(({ id }) => id === turn.executionLaneId)?.roleName;
1483
+ if (roundWorkItemId !== turn.workItemId
1484
+ || (round.reviewerRoleName !== turn.roleName && laneRole !== turn.roleName)) {
1485
+ throw new StorageRecordError(`Turn does not match ReviewRound: ${turn.id}.`);
1578
1486
  }
1579
1487
  }
1580
1488
  this.#mutate(() => {
1581
- this.#db.prepare(`INSERT INTO agent_runs (task_id, run_id, role_name, status, payload, updated_at) VALUES (?, ?, ?, ?, ?, ?)
1582
- ON CONFLICT(task_id, run_id) DO UPDATE SET role_name = excluded.role_name, status = excluded.status,
1583
- payload = excluded.payload, updated_at = excluded.updated_at`).run(run.taskId, run.id, run.roleName, run.status, this.#json(run), this.#now());
1489
+ this.#db.prepare(`INSERT INTO turns (task_id, turn_id, role_name, status, payload, updated_at) VALUES (?, ?, ?, ?, ?, ?)
1490
+ ON CONFLICT(task_id, turn_id) DO UPDATE SET role_name = excluded.role_name, status = excluded.status,
1491
+ payload = excluded.payload, updated_at = excluded.updated_at`).run(turn.taskId, turn.id, turn.roleName, turn.status, this.#json(turn), this.#now());
1584
1492
  });
1585
1493
  }
1586
1494
  // -- review rounds ----------------------------------------------------------
@@ -1622,190 +1530,181 @@ export class SqliteTaskStore {
1622
1530
  severity = excluded.severity, payload = excluded.payload, updated_at = excluded.updated_at`).run(taskId, finding.id, finding.stableKey, finding.severity, this.#json(finding), this.#now());
1623
1531
  });
1624
1532
  }
1625
- // -- active runs ------------------------------------------------------------
1626
- #saveActiveRun(taskId, pointer, runId) {
1533
+ // -- active turns ----------------------------------------------------------
1534
+ #saveActiveTurn(taskId, pointer, turnId) {
1627
1535
  this.#mutate(() => {
1628
- const payload = this.#json({ schemaVersion: 3, runId });
1629
- this.#db.prepare(`INSERT INTO active_runs (task_id, pointer, run_id, payload, updated_at) VALUES (?, ?, ?, ?, ?)
1630
- ON CONFLICT(task_id, pointer) DO UPDATE SET run_id = excluded.run_id, payload = excluded.payload, updated_at = excluded.updated_at`).run(taskId, pointer, runId, payload, this.#now());
1536
+ const payload = this.#json({ schemaVersion: 3, turnId });
1537
+ this.#db.prepare(`INSERT INTO active_turns (task_id, pointer, turn_id, payload, updated_at) VALUES (?, ?, ?, ?, ?)
1538
+ ON CONFLICT(task_id, pointer) DO UPDATE SET turn_id = excluded.turn_id, payload = excluded.payload, updated_at = excluded.updated_at`).run(taskId, pointer, turnId, payload, this.#now());
1631
1539
  });
1632
1540
  }
1633
- #readActiveRunPointer(taskId, pointer) {
1634
- const row = this.#db.prepare("SELECT run_id FROM active_runs WHERE task_id = ? AND pointer = ?").get(taskId, pointer);
1541
+ #readActiveTurnPointer(taskId, pointer) {
1542
+ const row = this.#db.prepare("SELECT turn_id FROM active_turns WHERE task_id = ? AND pointer = ?").get(taskId, pointer);
1635
1543
  if (row === undefined)
1636
1544
  return null;
1637
1545
  return {
1638
- runId: row.run_id,
1639
- run: this.getAgentRun(taskId, row.run_id)
1546
+ turnId: row.turn_id,
1547
+ turn: this.getTurn(taskId, row.turn_id)
1640
1548
  };
1641
1549
  }
1642
- #assertActiveRunForWrite(run, lane) {
1643
- if (run.status !== "active") {
1644
- throw new StorageRecordError(`Active Agent run must have active status: ${run.id}`);
1550
+ #assertActiveTurnForWrite(turn, lane) {
1551
+ if (turn.status !== "active") {
1552
+ throw new StorageRecordError(`Active Turn must have active status: ${turn.id}`);
1645
1553
  }
1646
- const hasGroup = run.executionGroupId !== undefined;
1647
- const hasLane = run.executionLaneId !== undefined;
1554
+ const hasGroup = turn.executionGroupId !== undefined;
1555
+ const hasLane = turn.executionLaneId !== undefined;
1648
1556
  if (hasGroup !== hasLane) {
1649
- throw new StorageRecordError(`Agent Run execution lineage is incomplete: ${run.id}.`);
1557
+ throw new StorageRecordError(`Turn execution lineage is incomplete: ${turn.id}.`);
1650
1558
  }
1651
1559
  if (lane !== undefined
1652
- && (run.executionGroupId !== lane.executionGroupId
1653
- || run.executionLaneId !== lane.executionLaneId)) {
1654
- throw new StorageRecordError(`Active Agent run execution lineage does not match its Lane: ${run.id}`);
1560
+ && (turn.executionGroupId !== lane.executionGroupId
1561
+ || turn.executionLaneId !== lane.executionLaneId)) {
1562
+ throw new StorageRecordError(`Active Turn execution lineage does not match its Lane: ${turn.id}`);
1655
1563
  }
1656
1564
  }
1657
- #getActiveRoleRun(taskId, roleName) {
1658
- const pointer = this.#readActiveRunPointer(taskId, roleName);
1565
+ #getActiveRoleTurn(taskId, roleName) {
1566
+ const pointer = this.#readActiveTurnPointer(taskId, roleName);
1659
1567
  if (pointer === null)
1660
1568
  return null;
1661
1569
  const task = this.getTask(taskId);
1662
1570
  if (task === null) {
1663
- throw new StorageRecordError(`Active Agent run Task is missing: ${taskId}/${roleName}`);
1571
+ throw new StorageRecordError(`Active Turn Task is missing: ${taskId}/${roleName}`);
1664
1572
  }
1665
- if (pointer.run === null) {
1573
+ if (pointer.turn === null) {
1666
1574
  // Retired Tasks are an explicit historical isolation boundary. Their
1667
- // retained pointer rows may intentionally reference a missing Run.
1575
+ // retained pointer rows may intentionally reference a missing Turn.
1668
1576
  if (task.status === "retired")
1669
1577
  return null;
1670
- throw new StorageRecordError(`Active Agent run pointer is dangling: ${taskId}/${roleName}`);
1578
+ throw new StorageRecordError(`Active Turn pointer is dangling: ${taskId}/${roleName}`);
1671
1579
  }
1672
- const run = pointer.run;
1580
+ const turn = pointer.turn;
1673
1581
  if (task.status === "retired")
1674
- return run;
1675
- if (run.id !== pointer.runId
1676
- || run.taskId !== taskId
1677
- || run.roleName !== roleName
1678
- || run.status !== "active"
1679
- || (run.executionGroupId === undefined) !== (run.executionLaneId === undefined)) {
1680
- throw new StorageRecordError(`Active Agent run pointer is invalid: ${taskId}/${roleName}`);
1681
- }
1682
- return run;
1683
- }
1684
- #getActiveLaneRun(taskId, executionGroupId, executionLaneId) {
1685
- const pointerKey = executionLaneActiveRunKey(executionGroupId, executionLaneId);
1686
- const pointer = this.#readActiveRunPointer(taskId, pointerKey);
1582
+ return turn;
1583
+ if (turn.id !== pointer.turnId
1584
+ || turn.taskId !== taskId
1585
+ || turn.roleName !== roleName
1586
+ || turn.status !== "active"
1587
+ || (turn.executionGroupId === undefined) !== (turn.executionLaneId === undefined)) {
1588
+ throw new StorageRecordError(`Active Turn pointer is invalid: ${taskId}/${roleName}`);
1589
+ }
1590
+ return turn;
1591
+ }
1592
+ #getActiveLaneTurn(taskId, executionGroupId, executionLaneId) {
1593
+ const pointerKey = executionLaneActiveTurnKey(executionGroupId, executionLaneId);
1594
+ const pointer = this.#readActiveTurnPointer(taskId, pointerKey);
1687
1595
  if (pointer === null)
1688
1596
  return null;
1689
1597
  const task = this.getTask(taskId);
1690
1598
  if (task === null) {
1691
- throw new StorageRecordError(`Active Agent run Task is missing: ${taskId}/${pointerKey}`);
1599
+ throw new StorageRecordError(`Active Turn Task is missing: ${taskId}/${pointerKey}`);
1692
1600
  }
1693
- if (pointer.run === null) {
1601
+ if (pointer.turn === null) {
1694
1602
  if (task.status === "retired")
1695
1603
  return null;
1696
- throw new StorageRecordError(`Active Agent run pointer is dangling: ${taskId}/${pointerKey}`);
1604
+ throw new StorageRecordError(`Active Turn pointer is dangling: ${taskId}/${pointerKey}`);
1697
1605
  }
1698
- const run = pointer.run;
1606
+ const turn = pointer.turn;
1699
1607
  if (task.status === "retired")
1700
- return run;
1701
- if (run.id !== pointer.runId
1702
- || run.taskId !== taskId
1703
- || run.status !== "active"
1704
- || run.executionGroupId !== executionGroupId
1705
- || run.executionLaneId !== executionLaneId) {
1706
- throw new StorageRecordError(`Active Agent run pointer is invalid: ${taskId}/${pointerKey}`);
1608
+ return turn;
1609
+ if (turn.id !== pointer.turnId
1610
+ || turn.taskId !== taskId
1611
+ || turn.status !== "active"
1612
+ || turn.executionGroupId !== executionGroupId
1613
+ || turn.executionLaneId !== executionLaneId) {
1614
+ throw new StorageRecordError(`Active Turn pointer is invalid: ${taskId}/${pointerKey}`);
1707
1615
  }
1708
- return run;
1616
+ return turn;
1709
1617
  }
1710
- #clearActiveRun(taskId, pointer) {
1618
+ #clearActiveTurn(taskId, pointer) {
1711
1619
  this.#mutate(() => {
1712
- this.#db.prepare("DELETE FROM active_runs WHERE task_id = ? AND pointer = ?").run(taskId, pointer);
1620
+ this.#db.prepare("DELETE FROM active_turns WHERE task_id = ? AND pointer = ?").run(taskId, pointer);
1713
1621
  });
1714
1622
  }
1715
- getActiveAgentRun(taskId, roleName) {
1716
- return this.#getActiveRoleRun(taskId, roleName);
1623
+ getActiveTurn(taskId, roleName) {
1624
+ return this.#getActiveRoleTurn(taskId, roleName);
1717
1625
  }
1718
- saveActiveAgentRun(run) {
1719
- if (run.executionGroupId !== undefined || run.executionLaneId !== undefined) {
1720
- this.saveActiveExecutionLaneRun(run);
1626
+ saveActiveTurn(turn) {
1627
+ if (turn.executionGroupId !== undefined || turn.executionLaneId !== undefined) {
1628
+ this.saveActiveExecutionLaneTurn(turn);
1721
1629
  return;
1722
1630
  }
1723
1631
  this.transaction((store) => {
1724
- const task = store.getTask(run.taskId);
1632
+ const task = store.getTask(turn.taskId);
1725
1633
  if (task === null || task.status !== "active" || task.executionGate.state !== "enabled") {
1726
- throw new StorageRecordError(`Task execution is not enabled: ${run.taskId}.`);
1634
+ throw new StorageRecordError(`Task execution is not enabled: ${turn.taskId}.`);
1727
1635
  }
1728
- this.#assertActiveRunForWrite(run);
1729
- const current = store.getActiveAgentRun(run.taskId, run.roleName);
1730
- if (current !== null && current.id !== run.id) {
1731
- throw new StorageRecordError(`Role already has an active Agent run: ${run.taskId}/${run.roleName}`);
1636
+ this.#assertActiveTurnForWrite(turn);
1637
+ const current = store.getActiveTurn(turn.taskId, turn.roleName);
1638
+ if (current !== null && current.id !== turn.id) {
1639
+ throw new StorageRecordError(`Role already has an active Turn: ${turn.taskId}/${turn.roleName}`);
1732
1640
  }
1733
- const sessions = store.getTaskRoleSessionSet(run.taskId, run.roleName);
1734
- if (sessions !== null && sessions.inFlight !== null && sessions.inFlight.runId !== run.id) {
1735
- throw new StorageRecordError(`Role still has an in-flight Turn: ${run.taskId}/${run.roleName}/${sessions.inFlight.runId}`);
1736
- }
1737
- // Keep the Run row and its active pointer in the same transaction. The
1641
+ // Keep the Turn row and its active pointer in the same transaction. The
1738
1642
  // adapter may have already written the row; this upsert remains
1739
1643
  // intentionally idempotent for that backend-neutral path.
1740
- store.saveAgentRun(run);
1741
- this.#saveActiveRun(run.taskId, run.roleName, run.id);
1644
+ store.saveTurn(turn);
1645
+ this.#saveActiveTurn(turn.taskId, turn.roleName, turn.id);
1742
1646
  });
1743
1647
  }
1744
- clearActiveAgentRun(taskId, roleName) {
1745
- // Older Controller paths only know the Role key. When that key points
1746
- // at a lane-backed Run, remove the matching lane pointer too; preserve
1747
- // every other lane for the same Role in a multi-lane group.
1648
+ clearActiveTurn(taskId, roleName) {
1649
+ // A Role key can point at a lane-backed Turn. Remove the matching lane
1650
+ // pointer too while preserving every other lane for the same Role.
1748
1651
  this.transaction(() => {
1749
- const rolePointer = this.#readActiveRunPointer(taskId, roleName);
1750
- this.#clearActiveRun(taskId, roleName);
1652
+ const rolePointer = this.#readActiveTurnPointer(taskId, roleName);
1653
+ this.#clearActiveTurn(taskId, roleName);
1751
1654
  if (rolePointer === null)
1752
1655
  return;
1753
- const laneRows = this.#db.prepare("SELECT pointer, run_id FROM active_runs WHERE task_id = ?").all(taskId);
1656
+ const laneRows = this.#db.prepare("SELECT pointer, turn_id FROM active_turns WHERE task_id = ?").all(taskId);
1754
1657
  for (const row of laneRows) {
1755
- if (executionLaneActiveRunKeyParts(row.pointer) !== null
1756
- && row.run_id === rolePointer.runId) {
1757
- this.#clearActiveRun(taskId, row.pointer);
1658
+ if (executionLaneActiveTurnKeyParts(row.pointer) !== null
1659
+ && row.turn_id === rolePointer.turnId) {
1660
+ this.#clearActiveTurn(taskId, row.pointer);
1758
1661
  }
1759
1662
  }
1760
1663
  });
1761
1664
  }
1762
- getActiveExecutionLaneRun(taskId, executionGroupId, executionLaneId) {
1763
- return this.#getActiveLaneRun(taskId, executionGroupId, executionLaneId);
1665
+ getActiveExecutionLaneTurn(taskId, executionGroupId, executionLaneId) {
1666
+ return this.#getActiveLaneTurn(taskId, executionGroupId, executionLaneId);
1764
1667
  }
1765
- saveActiveExecutionLaneRun(run) {
1766
- if (run.executionGroupId === undefined || run.executionLaneId === undefined) {
1767
- throw new StorageRecordError(`Active execution-lane run requires group and lane ids: ${run.id}`);
1668
+ saveActiveExecutionLaneTurn(turn) {
1669
+ if (turn.executionGroupId === undefined || turn.executionLaneId === undefined) {
1670
+ throw new StorageRecordError(`Active execution-lane Turn requires group and lane ids: ${turn.id}`);
1768
1671
  }
1769
1672
  this.transaction((store) => {
1770
- const task = store.getTask(run.taskId);
1673
+ const task = store.getTask(turn.taskId);
1771
1674
  if (task === null || task.status !== "active" || task.executionGate.state !== "enabled") {
1772
- throw new StorageRecordError(`Task execution is not enabled: ${run.taskId}.`);
1675
+ throw new StorageRecordError(`Task execution is not enabled: ${turn.taskId}.`);
1773
1676
  }
1774
- const key = executionLaneActiveRunKey(run.executionGroupId, run.executionLaneId);
1775
- this.#assertActiveRunForWrite(run, {
1776
- executionGroupId: run.executionGroupId,
1777
- executionLaneId: run.executionLaneId
1677
+ const key = executionLaneActiveTurnKey(turn.executionGroupId, turn.executionLaneId);
1678
+ this.#assertActiveTurnForWrite(turn, {
1679
+ executionGroupId: turn.executionGroupId,
1680
+ executionLaneId: turn.executionLaneId
1778
1681
  });
1779
- const current = store.getActiveExecutionLaneRun(run.taskId, run.executionGroupId, run.executionLaneId);
1780
- if (current !== null && current.id !== run.id) {
1781
- throw new StorageRecordError(`Execution Lane already has an active Agent run: ${run.taskId}/${key}`);
1782
- }
1783
- const sessions = store.getTaskRoleSessionSet(run.taskId, run.roleName);
1784
- if (sessions !== null && sessions.inFlight !== null && sessions.inFlight.runId !== run.id) {
1785
- throw new StorageRecordError(`Role still has an in-flight Turn: ${run.taskId}/${run.roleName}/${sessions.inFlight.runId}`);
1682
+ const current = store.getActiveExecutionLaneTurn(turn.taskId, turn.executionGroupId, turn.executionLaneId);
1683
+ if (current !== null && current.id !== turn.id) {
1684
+ throw new StorageRecordError(`Execution Lane already has an active Turn: ${turn.taskId}/${key}`);
1786
1685
  }
1787
- const rolePointer = this.#readActiveRunPointer(run.taskId, run.roleName);
1788
- store.saveAgentRun(run);
1789
- this.#saveActiveRun(run.taskId, key, run.id);
1790
- // Keep the legacy role pointer for the single-lane delivery path, but
1791
- // never replace it when another Lane already owns that Role.
1686
+ const rolePointer = this.#readActiveTurnPointer(turn.taskId, turn.roleName);
1687
+ store.saveTurn(turn);
1688
+ this.#saveActiveTurn(turn.taskId, key, turn.id);
1689
+ // Keep the Role pointer for the single-lane delivery path, but never
1690
+ // replace it when another Lane already owns that Role.
1792
1691
  if (rolePointer === null) {
1793
- this.#saveActiveRun(run.taskId, run.roleName, run.id);
1692
+ this.#saveActiveTurn(turn.taskId, turn.roleName, turn.id);
1794
1693
  }
1795
1694
  });
1796
1695
  }
1797
- clearActiveExecutionLaneRun(taskId, executionGroupId, executionLaneId) {
1696
+ clearActiveExecutionLaneTurn(taskId, executionGroupId, executionLaneId) {
1798
1697
  this.transaction(() => {
1799
- const key = executionLaneActiveRunKey(executionGroupId, executionLaneId);
1800
- const lanePointer = this.#readActiveRunPointer(taskId, key);
1801
- this.#clearActiveRun(taskId, key);
1698
+ const key = executionLaneActiveTurnKey(executionGroupId, executionLaneId);
1699
+ const lanePointer = this.#readActiveTurnPointer(taskId, key);
1700
+ this.#clearActiveTurn(taskId, key);
1802
1701
  if (lanePointer === null)
1803
1702
  return;
1804
- const roleRows = this.#db.prepare("SELECT pointer, run_id FROM active_runs WHERE task_id = ?").all(taskId);
1703
+ const roleRows = this.#db.prepare("SELECT pointer, turn_id FROM active_turns WHERE task_id = ?").all(taskId);
1805
1704
  for (const row of roleRows) {
1806
- if (executionLaneActiveRunKeyParts(row.pointer) === null
1807
- && row.run_id === lanePointer.runId) {
1808
- this.#clearActiveRun(taskId, row.pointer);
1705
+ if (executionLaneActiveTurnKeyParts(row.pointer) === null
1706
+ && row.turn_id === lanePointer.turnId) {
1707
+ this.#clearActiveTurn(taskId, row.pointer);
1809
1708
  }
1810
1709
  }
1811
1710
  });
@@ -1930,14 +1829,14 @@ export class SqliteTaskStore {
1930
1829
  this.#mutate(() => {
1931
1830
  const seq = this.#idSequence(wake.id, "taskWake");
1932
1831
  this.#db.prepare(`INSERT INTO task_wakes
1933
- (task_id, wake_id, seq, status, run_id, from_cursor, to_cursor, reasons, payload, created_at, consumed_at)
1832
+ (task_id, wake_id, seq, status, turn_id, from_cursor, to_cursor, reasons, payload, created_at, consumed_at)
1934
1833
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
1935
1834
  + ` ON CONFLICT(task_id, wake_id) DO UPDATE SET
1936
1835
  status = excluded.status,
1937
- run_id = excluded.run_id,
1836
+ turn_id = excluded.turn_id,
1938
1837
  reasons = excluded.reasons,
1939
1838
  payload = excluded.payload,
1940
- consumed_at = excluded.consumed_at`).run(taskId, wake.id, wake.seq, wake.status, wake.runId ?? null, wake.fromCursor, wake.toCursor, this.#json([...wake.reasons]), this.#json(wake), wake.createdAt, wake.consumedAt ?? null);
1839
+ consumed_at = excluded.consumed_at`).run(taskId, wake.id, wake.seq, wake.status, wake.turnId ?? null, wake.fromCursor, wake.toCursor, this.#json([...wake.reasons]), this.#json(wake), wake.createdAt, wake.consumedAt ?? null);
1941
1840
  this.#observeHighWater(taskId, "taskWake", seq);
1942
1841
  });
1943
1842
  }
@@ -1982,7 +1881,7 @@ export class SqliteTaskStore {
1982
1881
  nextSequence: row.next_sequence,
1983
1882
  processing: row.processing === null ? null : this.#parse(row.processing),
1984
1883
  pending: this.#parse(row.pending),
1985
- inputDelivery: row.input_delivery === null ? null : this.#parse(row.input_delivery)
1884
+ recentDedupeKeys: this.#parse(row.recent_dedupe_keys)
1986
1885
  });
1987
1886
  }
1988
1887
  #targetFromCols(kind, taskId, roleName) {
@@ -1997,20 +1896,18 @@ export class SqliteTaskStore {
1997
1896
  }
1998
1897
  getWorkMailbox(target) {
1999
1898
  const cols = this.#mailboxCols(target);
2000
- const row = this.#db.prepare("SELECT target_kind, task_id, role_name, next_sequence, processing, pending, input_delivery FROM mailboxes WHERE target_key = ?").get(cols.targetKey);
1899
+ const row = this.#db.prepare("SELECT target_kind, task_id, role_name, next_sequence, processing, pending, recent_dedupe_keys FROM mailboxes WHERE target_key = ?").get(cols.targetKey);
2001
1900
  return row === undefined ? null : this.#rowToMailbox(row);
2002
1901
  }
2003
1902
  listWorkMailboxes() {
2004
- const rows = this.#db.prepare("SELECT target_kind, task_id, role_name, next_sequence, processing, pending, input_delivery FROM mailboxes ORDER BY target_key").all();
1903
+ const rows = this.#db.prepare("SELECT target_kind, task_id, role_name, next_sequence, processing, pending, recent_dedupe_keys FROM mailboxes ORDER BY target_key").all();
2005
1904
  return rows.map((row) => this.#rowToMailbox(row));
2006
1905
  }
2007
1906
  listReadyWorkMailboxes() {
2008
- const rows = this.#db.prepare(`SELECT target_kind, task_id, role_name, next_sequence, processing, pending, input_delivery
1907
+ const rows = this.#db.prepare(`SELECT target_kind, task_id, role_name, next_sequence, processing, pending, recent_dedupe_keys
2009
1908
  FROM mailboxes
2010
1909
  WHERE processing IS NOT NULL
2011
- OR input_delivery IS NOT NULL
2012
- OR json_type(pending, '$.normal') <> 'null'
2013
- OR json_type(pending, '$.userCorrection') <> 'null'
1910
+ OR json_type(pending) <> 'null'
2014
1911
  ORDER BY target_key`).all();
2015
1912
  return rows.map((row) => this.#rowToMailbox(row));
2016
1913
  }
@@ -2024,11 +1921,11 @@ export class SqliteTaskStore {
2024
1921
  }
2025
1922
  const cols = this.#mailboxCols(validated.target);
2026
1923
  this.#mutate(() => {
2027
- this.#db.prepare(`INSERT INTO mailboxes (target_kind, task_id, role_name, target_key, next_sequence, processing, pending, input_delivery)
1924
+ this.#db.prepare(`INSERT INTO mailboxes (target_kind, task_id, role_name, target_key, next_sequence, processing, pending, recent_dedupe_keys)
2028
1925
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)
2029
1926
  ON CONFLICT(target_key) DO UPDATE SET next_sequence = excluded.next_sequence,
2030
1927
  processing = excluded.processing, pending = excluded.pending,
2031
- input_delivery = excluded.input_delivery`).run(cols.targetKind, cols.taskId, cols.roleName, cols.targetKey, validated.nextSequence, validated.processing === null ? null : this.#json(validated.processing), this.#json(validated.pending), validated.inputDelivery === null ? null : this.#json(validated.inputDelivery));
1928
+ recent_dedupe_keys = excluded.recent_dedupe_keys`).run(cols.targetKind, cols.taskId, cols.roleName, cols.targetKey, validated.nextSequence, validated.processing === null ? null : this.#json(validated.processing), this.#json(validated.pending), this.#json(validated.recentDedupeKeys));
2032
1929
  });
2033
1930
  }
2034
1931
  removeWorkMailbox(target) {
@@ -2089,7 +1986,7 @@ export class SqliteTaskStore {
2089
1986
  const target = { kind: "role", taskId: value.taskId, roleName: "leader" };
2090
1987
  this.transaction((store) => {
2091
1988
  const existing = store.getWorkMailbox(target);
2092
- const existingPending = existing === null ? null : pendingLane(existing, "normal");
1989
+ const existingPending = existing?.pending ?? null;
2093
1990
  if (existingPending !== null
2094
1991
  && value.requestCount <= existingPending.requestCount) {
2095
1992
  throw new StorageRecordError(`Pending wakeup is stale: ${value.taskId}`);
@@ -2101,60 +1998,54 @@ export class SqliteTaskStore {
2101
1998
  target,
2102
1999
  nextSequence: Math.max(existing?.nextSequence ?? 1, toSequence + 1),
2103
2000
  processing: existing?.processing ?? null,
2104
- inputDelivery: existing?.inputDelivery ?? null,
2105
2001
  pending: {
2106
- normal: {
2107
- fromSequence,
2108
- toSequence,
2109
- reasons: [...value.reasons],
2110
- refs: existingPending?.refs ?? [],
2111
- requestCount: value.requestCount,
2112
- firstQueuedAt: value.firstRequestedAt,
2113
- lastQueuedAt: value.lastRequestedAt,
2114
- sources: existingPending?.sources ?? ["pending-wakeup-projection"],
2115
- dedupeKeys: existingPending?.dedupeKeys ?? [
2116
- `pending-wakeup:${value.taskId}:${fromSequence}-${toSequence}`
2117
- ],
2118
- deliveryModes: existingPending?.deliveryModes ?? ["followup"]
2119
- },
2120
- userCorrection: existing?.pending.userCorrection ?? null,
2121
- cursors: existing?.pending.cursors ?? { normal: 0, userCorrection: 0 },
2122
- recentDedupeKeys: existing?.pending.recentDedupeKeys ?? []
2123
- }
2002
+ fromSequence,
2003
+ toSequence,
2004
+ reasons: [...value.reasons],
2005
+ refs: existingPending?.refs ?? [],
2006
+ requestCount: value.requestCount,
2007
+ firstQueuedAt: value.firstRequestedAt,
2008
+ lastQueuedAt: value.lastRequestedAt,
2009
+ sources: existingPending?.sources ?? ["pending-wakeup-projection"],
2010
+ dedupeKeys: existingPending?.dedupeKeys ?? [
2011
+ `pending-wakeup:${value.taskId}:${fromSequence}-${toSequence}`
2012
+ ]
2013
+ },
2014
+ recentDedupeKeys: existing?.recentDedupeKeys ?? []
2124
2015
  });
2125
2016
  });
2126
2017
  }
2127
2018
  clearPendingWakeup(taskId) {
2128
2019
  const target = { kind: "role", taskId, roleName: "leader" };
2129
2020
  const mailbox = this.getWorkMailbox(target);
2130
- if (mailbox === null || mailbox.pending.normal === null)
2021
+ if (mailbox === null || mailbox.pending === null)
2131
2022
  return;
2132
- this.saveWorkMailbox(consumePendingBatch(mailbox, "normal"));
2023
+ this.saveWorkMailbox(consumePendingBatch(mailbox));
2133
2024
  }
2134
2025
  // -- telemetry (§4.4) -----------------------------------------------------------
2135
2026
  /**
2136
- * Upsert one progress row. The PK is (task_id, role_name, run_id, generation,
2027
+ * Upsert one progress row. The PK is (task_id, role_name, turn_id, generation,
2137
2028
  * progress_id): a repeated progress id updates in place, so a high-frequency
2138
2029
  * runtime telemetry observation is a single-row write that never
2139
2030
  * rewrites global state or another Task's rows.
2140
2031
  */
2141
2032
  upsertTelemetryProgress(entry) {
2142
2033
  this.#mutate(() => {
2143
- this.#db.prepare(`INSERT INTO telemetry (task_id, role_name, run_id, generation, progress_id, sequence, payload, received_at)
2034
+ this.#db.prepare(`INSERT INTO telemetry (task_id, role_name, turn_id, generation, progress_id, sequence, payload, received_at)
2144
2035
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)
2145
- ON CONFLICT(task_id, role_name, run_id, generation, progress_id)
2146
- DO UPDATE SET sequence = excluded.sequence, payload = excluded.payload, received_at = excluded.received_at`).run(entry.taskId, entry.roleName, entry.runId, entry.generation, entry.progressId, entry.sequence ?? null, this.#json(entry.payload), entry.receivedAt);
2036
+ ON CONFLICT(task_id, role_name, turn_id, generation, progress_id)
2037
+ DO UPDATE SET sequence = excluded.sequence, payload = excluded.payload, received_at = excluded.received_at`).run(entry.taskId, entry.roleName, entry.turnId, entry.generation, entry.progressId, entry.sequence ?? null, this.#json(entry.payload), entry.receivedAt);
2147
2038
  });
2148
2039
  }
2149
- listTelemetry(taskId, runId) {
2150
- const rows = runId === undefined
2151
- ? this.#db.prepare("SELECT task_id, role_name, run_id, generation, progress_id, sequence, payload, received_at FROM telemetry WHERE task_id = ? ORDER BY received_at").all(taskId)
2152
- : this.#db.prepare("SELECT task_id, role_name, run_id, generation, progress_id, sequence, payload, received_at FROM telemetry WHERE task_id = ? AND run_id = ? ORDER BY received_at").all(taskId, runId);
2040
+ listTelemetry(taskId, turnId) {
2041
+ const rows = turnId === undefined
2042
+ ? this.#db.prepare("SELECT task_id, role_name, turn_id, generation, progress_id, sequence, payload, received_at FROM telemetry WHERE task_id = ? ORDER BY received_at").all(taskId)
2043
+ : this.#db.prepare("SELECT task_id, role_name, turn_id, generation, progress_id, sequence, payload, received_at FROM telemetry WHERE task_id = ? AND turn_id = ? ORDER BY received_at").all(taskId, turnId);
2153
2044
  return rows
2154
2045
  .map((row) => ({
2155
2046
  taskId: row.task_id,
2156
2047
  roleName: row.role_name,
2157
- runId: row.run_id,
2048
+ turnId: row.turn_id,
2158
2049
  generation: row.generation,
2159
2050
  progressId: row.progress_id,
2160
2051
  sequence: row.sequence ?? undefined,
@@ -2162,10 +2053,10 @@ export class SqliteTaskStore {
2162
2053
  receivedAt: row.received_at
2163
2054
  }));
2164
2055
  }
2165
- countTelemetry(taskId, runId) {
2166
- const row = runId === undefined
2056
+ countTelemetry(taskId, turnId) {
2057
+ const row = turnId === undefined
2167
2058
  ? this.#db.prepare("SELECT COUNT(*) AS n FROM telemetry WHERE task_id = ?").get(taskId)
2168
- : this.#db.prepare("SELECT COUNT(*) AS n FROM telemetry WHERE task_id = ? AND run_id = ?").get(taskId, runId);
2059
+ : this.#db.prepare("SELECT COUNT(*) AS n FROM telemetry WHERE task_id = ? AND turn_id = ?").get(taskId, turnId);
2169
2060
  return row.n;
2170
2061
  }
2171
2062
  /**
@@ -2174,35 +2065,35 @@ export class SqliteTaskStore {
2174
2065
  * by task_id; it never rewrites global rows or other Tasks. Returns the number
2175
2066
  * of rows deleted. Terminal/semantic events go to `events` and are never pruned.
2176
2067
  */
2177
- pruneTelemetry(taskId, roleName, runId, generation, keep = TELEMETRY_KEEP_PER_GENERATION) {
2068
+ pruneTelemetry(taskId, roleName, turnId, generation, keep = TELEMETRY_KEEP_PER_GENERATION) {
2178
2069
  return this.#mutate(() => {
2179
2070
  const result = this.#db.prepare(`DELETE FROM telemetry
2180
- WHERE task_id = ? AND role_name = ? AND run_id = ? AND generation = ?
2181
- AND (task_id, role_name, run_id, generation, progress_id) NOT IN (
2182
- SELECT task_id, role_name, run_id, generation, progress_id
2071
+ WHERE task_id = ? AND role_name = ? AND turn_id = ? AND generation = ?
2072
+ AND (task_id, role_name, turn_id, generation, progress_id) NOT IN (
2073
+ SELECT task_id, role_name, turn_id, generation, progress_id
2183
2074
  FROM telemetry
2184
- WHERE task_id = ? AND role_name = ? AND run_id = ? AND generation = ?
2075
+ WHERE task_id = ? AND role_name = ? AND turn_id = ? AND generation = ?
2185
2076
  ORDER BY COALESCE(sequence, -1) DESC, received_at DESC, progress_id ASC
2186
2077
  LIMIT ?
2187
- )`).run(taskId, roleName, runId, generation, taskId, roleName, runId, generation, keep);
2078
+ )`).run(taskId, roleName, turnId, generation, taskId, roleName, turnId, generation, keep);
2188
2079
  return result.changes;
2189
2080
  });
2190
2081
  }
2191
2082
  /**
2192
- * Hard cap for an active run (§4.4): trim oldest rows across the run beyond
2083
+ * Hard cap for an active Turn (§4.4): trim oldest rows across the Turn beyond
2193
2084
  * `cap` (default 50k). Returns the number of rows deleted.
2194
2085
  */
2195
- capTelemetryRun(taskId, runId, cap = TELEMETRY_RUN_CAP) {
2086
+ capTelemetryTurn(taskId, turnId, cap = TELEMETRY_TURN_CAP) {
2196
2087
  return this.#mutate(() => {
2197
2088
  const result = this.#db.prepare(`DELETE FROM telemetry
2198
- WHERE task_id = ? AND run_id = ?
2199
- AND (task_id, role_name, run_id, generation, progress_id) NOT IN (
2200
- SELECT task_id, role_name, run_id, generation, progress_id
2089
+ WHERE task_id = ? AND turn_id = ?
2090
+ AND (task_id, role_name, turn_id, generation, progress_id) NOT IN (
2091
+ SELECT task_id, role_name, turn_id, generation, progress_id
2201
2092
  FROM telemetry
2202
- WHERE task_id = ? AND run_id = ?
2093
+ WHERE task_id = ? AND turn_id = ?
2203
2094
  ORDER BY COALESCE(sequence, -1) DESC, received_at DESC, progress_id ASC
2204
2095
  LIMIT ?
2205
- )`).run(taskId, runId, taskId, runId, cap);
2096
+ )`).run(taskId, turnId, taskId, turnId, cap);
2206
2097
  return result.changes;
2207
2098
  });
2208
2099
  }
@@ -2227,63 +2118,13 @@ function validIntegrationQueueTransition(before, after) {
2227
2118
  };
2228
2119
  return allowed[before.status].includes(after.status);
2229
2120
  }
2230
- /**
2231
- * Open a {@link TaskStore} for the given backend. `file` returns the existing
2232
- * {@link FileTaskStore}; `sqlite` returns the in-process {@link SqliteTaskStore}.
2233
- * Backend selection is explicit (design §6); the environment switch lives in
2234
- * {@link resolveTaskStoreBackend}. The file store is not removed — rollback is
2235
- * a config flip.
2236
- */
2237
- export function openTaskStore(home, backend, options) {
2238
- if (backend === "sqlite") {
2239
- return new SqliteTaskStore(home, options);
2240
- }
2241
- return new FileTaskStore(home);
2242
- }
2243
- /**
2244
- * Resolve the storage backend from `YUI_STORE_BACKEND` (default `file`,
2245
- * design §6). Only the exact value `sqlite` selects the SQLite store; any
2246
- * other value (including unset) keeps the file store.
2247
- */
2248
- export function resolveTaskStoreBackend(env = process.env) {
2249
- return env.YUI_STORE_BACKEND?.toLowerCase() === "sqlite" ? "sqlite" : "file";
2250
- }
2251
- /**
2252
- * Resolve the storage backend from the Home's verified manifest (Issue 01).
2253
- *
2254
- * The Home decides: a layout-7 Home's authoritative backend is SQLite WAL, so
2255
- * ordinary CLI/Controller startup opens SQLite without requiring
2256
- * `YUI_STORE_BACKEND=sqlite`. An explicit `YUI_STORE_BACKEND` env value still
2257
- * wins — it is reserved for tests and explicit recovery commands — and any
2258
- * other value (including unset) defers to the Home. A Home whose manifest
2259
- * cannot be read falls back to the file store; the classifier/doctor surfaces
2260
- * the manifest problem separately.
2261
- */
2262
- export function resolveTaskStoreBackendForHome(home, env = process.env) {
2263
- const explicit = env.YUI_STORE_BACKEND?.toLowerCase();
2264
- if (explicit === "sqlite" || explicit === "file")
2265
- return explicit;
2266
- const schema = inspectStorageSchema(home);
2267
- const layout = schema.status === "current" || schema.status === "unsupported"
2268
- ? schema.currentLayoutVersion
2269
- : 0;
2270
- if (layout < 7)
2271
- return "file";
2272
- // Issue 01: a layout-7 Home's authoritative backend is SQLite WAL, but only
2273
- // when yui.db actually exists. A pseudo-layout-7 Home (manifest 7, no
2274
- // yui.db) is classified NEEDS_STORAGE_REPAIR; until repair runs, the file
2275
- // store remains the readable fallback. This keeps the Controller's backend
2276
- // resolution consistent with openCompatibleFileTaskStore's physical check.
2277
- // Uses the literal "yui.db" (not COMMITTED_DATABASE_FILENAME) to avoid a
2278
- // circular import: sqliteStateMigration.ts imports SqliteTaskStore from here.
2279
- return existsSync(join(home, "yui.db")) ? "sqlite" : "file";
2121
+ /** The product storage backend is one current SQLite contract. */
2122
+ export function resolveTaskStoreBackendForHome(_home, _env = process.env) {
2123
+ return "sqlite";
2280
2124
  }
2281
2125
  /**
2282
- * Convenience: open the store for the backend resolved from the environment
2283
- * and the Home's verified manifest (see {@link resolveTaskStoreBackendForHome}).
2284
- * CLI/controller entry points call this instead of {@link openTaskStore}
2285
- * directly so a layout-7 Home opens SQLite without an env opt-in.
2126
+ * Convenience for seams which cannot receive an already-open current store.
2286
2127
  */
2287
- export function openConfiguredTaskStore(home, options, env = process.env) {
2288
- return openTaskStore(home, resolveTaskStoreBackendForHome(home, env), options);
2128
+ export function openConfiguredTaskStore(home, options) {
2129
+ return new SqliteTaskStore(home, options);
2289
2130
  }