@knpkv/herdr-work 0.5.0 → 0.5.3

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.
package/dist/store.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { agentConnectTarget, fleetResponseBodyMaxBytes, workReassignActivitySummary } from "@knpkv/herdr-fleet";
2
- import { Clock, Crypto, Effect, Equal, FileSystem, Option, Path, Schema } from "effect";
2
+ import { openPrivateSqlite } from "@knpkv/herdr-fleet/sqlite";
3
+ import { Clock, Crypto, Effect, Equal, Option, Schema } from "effect";
3
4
  import { Hex } from "effect/encoding";
4
- import { DatabaseSync } from "node:sqlite";
5
5
  import { makeWorkAgentBinding } from "./agent-binding.js";
6
6
  import { WorkAdmissionConflictError, WorkAgentBindingAuthorityError, WorkAgentBindingConflictError, WorkCheckpointConflictError, WorkCoordinatorHandoffConflictError, WorkDecisionAuthorityConflictError, WorkDecisionHandoffConflictError, WorkDecisionRevisionConflictError, WorkGoalAgentTargetConflictError, WorkGoalBindingRequiresAgentError, WorkGoalOwnerMismatchError, WorkGoalReassignmentConflictError, WorkGoalRevisionConflictError, WorkLaneClaimConflictError, WorkLaneGoalConflictError, WorkLaneOperationConflictError, WorkProjectionError, WorkPullRequestLinkError, WorkStoreError, WorkTransactionConflictError } from "./errors.js";
7
7
  import { validateGoalFamilyHistory } from "./goal-family.js";
@@ -137,544 +137,437 @@ const LegacyMetadataWorkLink = Schema.Struct({
137
137
  const legacyDecisionEquivalent = Schema.toEquivalence(LegacyDecisionRecord);
138
138
  const TransactionId = Schema.String.check(Schema.isNonEmpty(), Schema.isMaxLength(256), Schema.isPattern(/^(?:[^\uD800-\uDFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF])*$/));
139
139
  const storeError = (operation) => (cause) => new WorkStoreError({ cause, operation });
140
+ const fromPrivateDatabaseError = (error) => new WorkStoreError({ cause: error.cause, operation: error.operation });
141
+ /**
142
+ * Upgrades pre-session Work tables in place. Runs inside `WorkStore.open`'s
143
+ * schema transaction, so a failure here or in a later schema step leaves the
144
+ * file as it was.
145
+ */
140
146
  const migrateLegacyAuthorityTables = (database) => {
141
- let transaction = false;
142
- try {
143
- database.exec("BEGIN IMMEDIATE");
144
- transaction = true;
145
- const columns = (table) => Schema.decodeUnknownSync(Schema.Array(Schema.Struct({ name: Schema.String })))(database.prepare(`PRAGMA table_info(${table})`).all()).map(({ name }) => name);
146
- const laneColumns = columns("work_lane_claims");
147
- const decisionColumns = columns("work_decision_handoffs");
148
- const tables = Schema.decodeUnknownSync(Schema.Array(Schema.Struct({ name: Schema.String })))(database.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all()).map(({ name }) => name);
149
- const coordinatorDispatchColumns = tables.includes("orchestrator_dispatches")
150
- ? columns("orchestrator_dispatches")
147
+ const columns = (table) => Schema.decodeUnknownSync(Schema.Array(Schema.Struct({ name: Schema.String })))(database.prepare(`PRAGMA table_info(${table})`).all()).map(({ name }) => name);
148
+ const laneColumns = columns("work_lane_claims");
149
+ const decisionColumns = columns("work_decision_handoffs");
150
+ const tables = Schema.decodeUnknownSync(Schema.Array(Schema.Struct({ name: Schema.String })))(database.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all()).map(({ name }) => name);
151
+ const coordinatorDispatchColumns = tables.includes("orchestrator_dispatches")
152
+ ? columns("orchestrator_dispatches")
153
+ : [];
154
+ const requireBindingCompanions = (binding, operation) => {
155
+ const laneInput = tables.includes("work_lane_operations")
156
+ ? database.prepare(`SELECT operation_id AS operationId, lane_id AS laneId, goal_id AS goalId,
157
+ phase, revision, record
158
+ FROM work_lane_operations WHERE operation_id = ?`).get(binding.lane.operationId)
159
+ : undefined;
160
+ const checkpointInput = tables.includes("work_goal_events")
161
+ ? database.prepare(`SELECT event_id AS eventId, goal_id AS goalId, occurred_at AS occurredAt, record
162
+ FROM work_goal_events WHERE event_id = ?`).get(binding.checkpoint.eventId)
163
+ : undefined;
164
+ const error = agentBindingReadbackError(binding, laneInput === undefined ? undefined : Schema.decodeUnknownSync(AgentBindingLaneOperationRow)(laneInput), checkpointInput === undefined
165
+ ? undefined
166
+ : Schema.decodeUnknownSync(AgentBindingGoalEventRow)(checkpointInput), operation);
167
+ if (error !== undefined)
168
+ throw error;
169
+ };
170
+ const coordinatorTablesPresent = {
171
+ dispatch: tables.includes("orchestrator_dispatches"),
172
+ event: tables.includes("orchestrator_events"),
173
+ metadata: tables.includes("orchestrator_dispatch_metadata")
174
+ };
175
+ const requireCoordinatorSchema = (operation) => {
176
+ const present = Object.values(coordinatorTablesPresent).filter(Boolean).length;
177
+ if (present !== 0 && present !== 3) {
178
+ throw new WorkStoreError({ cause: coordinatorTablesPresent, operation: `${operation}.schema` });
179
+ }
180
+ return present === 3;
181
+ };
182
+ requireCoordinatorSchema("open.migrate");
183
+ const readCoordinatorLifecycle = (dispatchRequestId) => ({
184
+ dispatchRows: Schema.decodeUnknownSync(Schema.Array(CoordinatorLifecycleDispatchRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId,
185
+ activity_idempotency_key AS activityIdempotencyKey, command,
186
+ accepted_at AS acceptedAt, status
187
+ FROM orchestrator_dispatches WHERE dispatch_request_id = ? LIMIT 2`).all(dispatchRequestId)),
188
+ eventRows: Schema.decodeUnknownSync(Schema.Array(CoordinatorLifecycleEventRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, sequence, type,
189
+ activity_idempotency_key AS activityIdempotencyKey,
190
+ occurred_at AS occurredAt, detail, result
191
+ FROM orchestrator_events WHERE dispatch_request_id = ?
192
+ ORDER BY sequence ASC LIMIT 5`).all(dispatchRequestId))
193
+ });
194
+ const routeStorageAuthority = (dispatchRequestId, operation) => {
195
+ if (!coordinatorDispatchColumns.includes("is_routed")) {
196
+ return { _tag: "legacy_without_routed_discriminator" };
197
+ }
198
+ const rows = Schema.decodeUnknownSync(Schema.Array(CoordinatorRouteDiscriminatorRow))(database.prepare(`SELECT is_routed AS isRouted FROM orchestrator_dispatches
199
+ WHERE dispatch_request_id = ? LIMIT 2`).all(dispatchRequestId));
200
+ const row = rows[0];
201
+ if (rows.length !== 1 || row === undefined) {
202
+ throw new WorkStoreError({ cause: { dispatchRequestId, rows }, operation });
203
+ }
204
+ return { _tag: "routed_discriminator", isRouted: row.isRouted };
205
+ };
206
+ const requireLinkedParentAuthority = (linkedRequestId, operation) => {
207
+ if (linkedRequestId === null)
208
+ return;
209
+ const metadataRows = Schema.decodeUnknownSync(Schema.Array(MetadataWorkLinkRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, route, work_link AS workLink
210
+ FROM orchestrator_dispatch_metadata WHERE dispatch_request_id = ? LIMIT 2`).all(linkedRequestId));
211
+ const metadata = metadataRows[0];
212
+ if (metadataRows.length !== 1 || metadata === undefined || metadata.workLink !== null) {
213
+ throw new WorkStoreError({ cause: { linkedRequestId, metadataRows }, operation });
214
+ }
215
+ const lifecycle = readCoordinatorLifecycle(linkedRequestId);
216
+ requireCoordinatorFailedLunaAuthority(lifecycle.dispatchRows, lifecycle.eventRows, metadata.route, routeStorageAuthority(linkedRequestId, operation), operation);
217
+ };
218
+ const requireMetadataAuthority = (dispatchRequestId, routeText, lineage, operation) => {
219
+ const commandRows = Schema.decodeUnknownSync(Schema.Array(CoordinatorCommandRow))(database.prepare(`SELECT activity_idempotency_key AS activityIdempotencyKey, command FROM orchestrator_dispatches
220
+ WHERE dispatch_request_id = ? LIMIT 2`).all(dispatchRequestId));
221
+ const commandRow = commandRows[0];
222
+ if (commandRows.length !== 1 || commandRow === undefined) {
223
+ throw new WorkStoreError({ cause: { commandRows, dispatchRequestId }, operation });
224
+ }
225
+ const route = requireCoordinatorRouteAuthority(commandRow.command, commandRow.activityIdempotencyKey, routeText, lineage, routeStorageAuthority(dispatchRequestId, operation), operation);
226
+ requireLinkedParentAuthority(route.linkedRequestId, `${operation}.parent`);
227
+ };
228
+ const requireLifecycleAuthority = (dispatchRequestId, expectedRunningAt, operation) => {
229
+ if (!requireCoordinatorSchema(operation))
230
+ return;
231
+ const lifecycle = readCoordinatorLifecycle(dispatchRequestId);
232
+ requireCoordinatorLifecycleAuthority(lifecycle.dispatchRows, lifecycle.eventRows, expectedRunningAt, operation);
233
+ };
234
+ const requireCurrentAgentBindingAuthority = (dispatchRequestId, handoff, operation) => {
235
+ const lifecycle = readCoordinatorLifecycle(dispatchRequestId);
236
+ const runningAt = coordinatorLifecycleRunningAt(lifecycle.dispatchRows, lifecycle.eventRows, `${operation}.lifecycle`);
237
+ const bindingRows = tables.includes("work_agent_bindings")
238
+ ? Schema.decodeUnknownSync(Schema.Array(AgentBindingRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, lane_id AS laneId,
239
+ expected_revision AS expectedRevision, revision, agent_id AS agentId, host, record
240
+ FROM work_agent_bindings WHERE dispatch_request_id = ? LIMIT 2`).all(dispatchRequestId))
151
241
  : [];
152
- const requireBindingCompanions = (binding, operation) => {
153
- const laneInput = tables.includes("work_lane_operations")
154
- ? database.prepare(`SELECT operation_id AS operationId, lane_id AS laneId, goal_id AS goalId,
242
+ if (runningAt === null && bindingRows.length === 0)
243
+ return;
244
+ const bindingRow = bindingRows[0];
245
+ if (runningAt === null || bindingRows.length !== 1 || bindingRow === undefined) {
246
+ throw new WorkStoreError({ cause: { bindingRows, lifecycle }, operation });
247
+ }
248
+ const decoded = decodeAgentBindingRow(bindingRow, { dispatchRequestId, laneId: handoff.laneId }, operation);
249
+ if (decoded._tag === "invalid")
250
+ throw decoded.error;
251
+ if (decoded.binding.request.expectedRevision !== handoff.expectedRevision ||
252
+ decoded.binding.lane.goalId !== handoff.goalId ||
253
+ decoded.binding.checkpoint.occurredAt !== runningAt) {
254
+ throw new WorkStoreError({ cause: { binding: decoded.binding, handoff, runningAt }, operation });
255
+ }
256
+ requireBindingCompanions(decoded.binding, operation);
257
+ const currentLaneRows = Schema.decodeUnknownSync(Schema.Array(LaneRow))(database.prepare(`SELECT lane_id AS laneId, goal_id AS goalId, operation_id AS operationId,
258
+ phase, revision, record
259
+ FROM work_lane_claims WHERE lane_id = ? LIMIT 2`).all(decoded.binding.lane.laneId));
260
+ const currentLaneRow = currentLaneRows[0];
261
+ if (currentLaneRows.length !== 1 || currentLaneRow === undefined) {
262
+ throw new WorkStoreError({
263
+ cause: { binding: decoded.binding, currentLaneRows },
264
+ operation: `${operation}.lane-revision`
265
+ });
266
+ }
267
+ let currentLane;
268
+ try {
269
+ currentLane = Schema.decodeUnknownSync(WorkLaneClaimed)(JSON.parse(currentLaneRow.record));
270
+ }
271
+ catch (cause) {
272
+ throw new WorkStoreError({ cause, operation: `${operation}.lane-revision` });
273
+ }
274
+ const currentLaneSchema = laneColumns.includes("goal_id");
275
+ const currentOperationRows = currentLaneSchema
276
+ ? Schema.decodeUnknownSync(Schema.Array(AgentBindingLaneOperationRow))(database.prepare(`SELECT operation_id AS operationId, lane_id AS laneId, goal_id AS goalId,
155
277
  phase, revision, record
156
- FROM work_lane_operations WHERE operation_id = ?`).get(binding.lane.operationId)
157
- : undefined;
158
- const checkpointInput = tables.includes("work_goal_events")
159
- ? database.prepare(`SELECT event_id AS eventId, goal_id AS goalId, occurred_at AS occurredAt, record
160
- FROM work_goal_events WHERE event_id = ?`).get(binding.checkpoint.eventId)
161
- : undefined;
162
- const error = agentBindingReadbackError(binding, laneInput === undefined ? undefined : Schema.decodeUnknownSync(AgentBindingLaneOperationRow)(laneInput), checkpointInput === undefined
163
- ? undefined
164
- : Schema.decodeUnknownSync(AgentBindingGoalEventRow)(checkpointInput), operation);
165
- if (error !== undefined)
166
- throw error;
167
- };
168
- const coordinatorTablesPresent = {
169
- dispatch: tables.includes("orchestrator_dispatches"),
170
- event: tables.includes("orchestrator_events"),
171
- metadata: tables.includes("orchestrator_dispatch_metadata")
172
- };
173
- const requireCoordinatorSchema = (operation) => {
174
- const present = Object.values(coordinatorTablesPresent).filter(Boolean).length;
175
- if (present !== 0 && present !== 3) {
176
- throw new WorkStoreError({ cause: coordinatorTablesPresent, operation: `${operation}.schema` });
177
- }
178
- return present === 3;
179
- };
180
- requireCoordinatorSchema("open.migrate");
181
- const readCoordinatorLifecycle = (dispatchRequestId) => ({
182
- dispatchRows: Schema.decodeUnknownSync(Schema.Array(CoordinatorLifecycleDispatchRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId,
183
- activity_idempotency_key AS activityIdempotencyKey, command,
184
- accepted_at AS acceptedAt, status
185
- FROM orchestrator_dispatches WHERE dispatch_request_id = ? LIMIT 2`).all(dispatchRequestId)),
186
- eventRows: Schema.decodeUnknownSync(Schema.Array(CoordinatorLifecycleEventRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, sequence, type,
187
- activity_idempotency_key AS activityIdempotencyKey,
188
- occurred_at AS occurredAt, detail, result
189
- FROM orchestrator_events WHERE dispatch_request_id = ?
190
- ORDER BY sequence ASC LIMIT 5`).all(dispatchRequestId))
278
+ FROM work_lane_operations WHERE operation_id = ? LIMIT 2`).all(currentLane.operationId))
279
+ : [];
280
+ const currentOperationError = currentLaneSchema
281
+ ? laneOperationReadbackError(currentLane, currentOperationRows.length === 1 ? currentOperationRows[0] : undefined, `${operation}.lane-revision`)
282
+ : undefined;
283
+ if (currentLane.laneId !== currentLaneRow.laneId ||
284
+ currentLane.goalId !== currentLaneRow.goalId ||
285
+ currentLane.operationId !== currentLaneRow.operationId ||
286
+ currentLane.phase !== currentLaneRow.phase ||
287
+ currentLane.revision !== currentLaneRow.revision ||
288
+ currentLane.laneId !== decoded.binding.lane.laneId ||
289
+ currentLane.goalId !== decoded.binding.lane.goalId ||
290
+ currentLane.revision < decoded.binding.lane.revision ||
291
+ (currentLaneSchema && currentLane.revision === decoded.binding.lane.revision &&
292
+ !Equal.equals(currentLane, decoded.binding.lane)) ||
293
+ currentOperationError !== undefined) {
294
+ throw new WorkStoreError({
295
+ cause: { binding: decoded.binding, currentLane, currentLaneRow, currentOperationError },
296
+ operation: `${operation}.lane-revision`
297
+ });
298
+ }
299
+ };
300
+ const dispatches = tables.includes("work_dispatch_handoffs")
301
+ ? Schema.decodeUnknownSync(Schema.Array(LegacyDispatchStoredRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, handoff_id AS handoffId,
302
+ lane_id AS laneId, occurred_at AS occurredAt, lineage, record
303
+ FROM work_dispatch_handoffs`).all())
304
+ : [];
305
+ const decisionIdentities = decisionColumns.length > 0
306
+ ? Schema.decodeUnknownSync(Schema.Array(DecisionIdentityRow))(database.prepare("SELECT handoff_id AS handoffId FROM work_decision_handoffs").all())
307
+ : [];
308
+ const orphanDispatch = dispatches.find(({ handoffId }) => !decisionIdentities.some((decision) => decision.handoffId === handoffId));
309
+ if (orphanDispatch !== undefined) {
310
+ throw new WorkStoreError({
311
+ cause: { decisionIdentities, dispatch: orphanDispatch },
312
+ operation: "open.migrate.dispatch-decision"
313
+ });
314
+ }
315
+ if (tables.includes("orchestrator_dispatch_metadata")) {
316
+ const linkedMetadata = Schema.decodeUnknownSync(Schema.Array(MetadataHandoffIdentityRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId,
317
+ CASE WHEN work_link IS NULL THEN 0 ELSE 1 END AS hasWorkLink,
318
+ CASE WHEN json_valid(work_link) AND json_type(work_link, '$.handoff.id') = 'text'
319
+ THEN json_extract(work_link, '$.handoff.id') END AS handoffId,
320
+ route
321
+ FROM orchestrator_dispatch_metadata AS metadata
322
+ WHERE work_link IS NOT NULL OR EXISTS (
323
+ SELECT 1 FROM orchestrator_dispatch_metadata AS linked
324
+ WHERE linked.dispatch_request_id = metadata.dispatch_request_id
325
+ AND linked.work_link IS NOT NULL
326
+ ) OR (json_valid(route) AND json_extract(route, '$.model') = 'gpt-5.6-sol')
327
+ LIMIT ${workDecisionMaxRecords + 1}`).all());
328
+ if (linkedMetadata.length > workDecisionMaxRecords) {
329
+ throw new WorkStoreError({ cause: linkedMetadata.length, operation: "open.migrate.metadata-capacity" });
330
+ }
331
+ const metadataCounts = new Map();
332
+ const dispatchCounts = new Map();
333
+ const decisionCounts = new Map();
334
+ for (const metadata of linkedMetadata) {
335
+ metadataCounts.set(metadata.dispatchRequestId, (metadataCounts.get(metadata.dispatchRequestId) ?? 0) + 1);
336
+ }
337
+ for (const dispatch of dispatches) {
338
+ const identity = JSON.stringify([dispatch.dispatchRequestId, dispatch.handoffId]);
339
+ dispatchCounts.set(identity, (dispatchCounts.get(identity) ?? 0) + 1);
340
+ }
341
+ for (const decision of decisionIdentities) {
342
+ decisionCounts.set(decision.handoffId, (decisionCounts.get(decision.handoffId) ?? 0) + 1);
343
+ }
344
+ const invalidMetadata = linkedMetadata.find((metadata) => {
345
+ if (metadata.route === null)
346
+ return true;
347
+ const requiresWorkLink = coordinatorRouteRequiresWorkLink(metadata.route, routeStorageAuthority(metadata.dispatchRequestId, "open.migrate.metadata-decision"), "open.migrate.metadata-decision");
348
+ if (!requiresWorkLink)
349
+ return metadata.hasWorkLink === 1;
350
+ const dispatchIdentity = JSON.stringify([metadata.dispatchRequestId, metadata.handoffId]);
351
+ return (metadata.handoffId === null || metadataCounts.get(metadata.dispatchRequestId) !== 1 ||
352
+ dispatchCounts.get(dispatchIdentity) !== 1 || decisionCounts.get(metadata.handoffId) !== 1);
191
353
  });
192
- const routeStorageAuthority = (dispatchRequestId, operation) => {
193
- if (!coordinatorDispatchColumns.includes("is_routed")) {
194
- return { _tag: "legacy_without_routed_discriminator" };
354
+ if (invalidMetadata !== undefined) {
355
+ throw new WorkStoreError({
356
+ cause: { decisionIdentities, dispatches, linkedMetadata, metadata: invalidMetadata },
357
+ operation: "open.migrate.metadata-decision"
358
+ });
359
+ }
360
+ }
361
+ const lanes = laneColumns.length > 0 && !laneColumns.includes("goal_id")
362
+ ? Schema.decodeUnknownSync(Schema.Array(LegacyLaneStoredRow))(database.prepare("SELECT lane_id AS laneId, revision, record FROM work_lane_claims").all()).map((row) => {
363
+ const legacy = Schema.decodeUnknownSync(LegacyLaneRecord)(JSON.parse(row.record));
364
+ if (legacy.laneId !== row.laneId || legacy.revision !== row.revision) {
365
+ throw new WorkStoreError({ cause: { legacy, row }, operation: "open.migrate.lane-identity" });
195
366
  }
196
- const rows = Schema.decodeUnknownSync(Schema.Array(CoordinatorRouteDiscriminatorRow))(database.prepare(`SELECT is_routed AS isRouted FROM orchestrator_dispatches
197
- WHERE dispatch_request_id = ? LIMIT 2`).all(dispatchRequestId));
198
- const row = rows[0];
199
- if (rows.length !== 1 || row === undefined) {
200
- throw new WorkStoreError({ cause: { dispatchRequestId, rows }, operation });
367
+ return Schema.decodeUnknownSync(WorkLaneClaimed)({
368
+ ...legacy,
369
+ goalId: legacy.laneId,
370
+ operationId: legacy.laneId
371
+ });
372
+ })
373
+ : [];
374
+ const decisions = decisionColumns.length > 0 && !decisionColumns.includes("session_id")
375
+ ? Schema.decodeUnknownSync(Schema.Array(LegacyDecisionStoredRow))(database.prepare("SELECT handoff_id AS handoffId, record FROM work_decision_handoffs").all()).map((row) => {
376
+ const legacy = Schema.decodeUnknownSync(LegacyDecisionRecord)(JSON.parse(row.record));
377
+ if (legacy.id !== row.handoffId) {
378
+ throw new WorkStoreError({ cause: { legacy, row }, operation: "open.migrate.handoff-identity" });
201
379
  }
202
- return { _tag: "routed_discriminator", isRouted: row.isRouted };
203
- };
204
- const requireLinkedParentAuthority = (linkedRequestId, operation) => {
205
- if (linkedRequestId === null)
206
- return;
207
- const metadataRows = Schema.decodeUnknownSync(Schema.Array(MetadataWorkLinkRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, route, work_link AS workLink
208
- FROM orchestrator_dispatch_metadata WHERE dispatch_request_id = ? LIMIT 2`).all(linkedRequestId));
209
- const metadata = metadataRows[0];
210
- if (metadataRows.length !== 1 || metadata === undefined || metadata.workLink !== null) {
211
- throw new WorkStoreError({ cause: { linkedRequestId, metadataRows }, operation });
380
+ const matchingDispatches = dispatches.filter(({ handoffId }) => handoffId === legacy.id);
381
+ const dispatch = matchingDispatches[0];
382
+ const lane = lanes.find(({ laneId }) => laneId === legacy.laneId);
383
+ if (lane === undefined) {
384
+ throw new WorkStoreError({ cause: legacy, operation: "open.migrate.handoff-lane" });
212
385
  }
213
- const lifecycle = readCoordinatorLifecycle(linkedRequestId);
214
- requireCoordinatorFailedLunaAuthority(lifecycle.dispatchRows, lifecycle.eventRows, metadata.route, routeStorageAuthority(linkedRequestId, operation), operation);
215
- };
216
- const requireMetadataAuthority = (dispatchRequestId, routeText, lineage, operation) => {
217
- const commandRows = Schema.decodeUnknownSync(Schema.Array(CoordinatorCommandRow))(database.prepare(`SELECT activity_idempotency_key AS activityIdempotencyKey, command FROM orchestrator_dispatches
218
- WHERE dispatch_request_id = ? LIMIT 2`).all(dispatchRequestId));
219
- const commandRow = commandRows[0];
220
- if (commandRows.length !== 1 || commandRow === undefined) {
221
- throw new WorkStoreError({ cause: { commandRows, dispatchRequestId }, operation });
386
+ const dispatchIds = dispatch === undefined
387
+ ? []
388
+ : Schema.decodeUnknownSync(WorkDispatchHandoff.fields.lineage)(JSON.parse(dispatch.lineage));
389
+ if (matchingDispatches.length !== 1 || dispatch === undefined) {
390
+ throw new WorkStoreError({
391
+ cause: { legacy, matchingDispatches },
392
+ operation: "open.migrate.legacy-dispatch-cardinality"
393
+ });
394
+ }
395
+ const dispatchHandoff = Schema.decodeUnknownSync(LegacyDecisionRecord)(JSON.parse(dispatch.record));
396
+ if (dispatch.laneId !== legacy.laneId || dispatch.occurredAt !== legacy.occurredAt ||
397
+ !legacyDecisionEquivalent(dispatchHandoff, legacy)) {
398
+ throw new WorkStoreError({
399
+ cause: { dispatch, dispatchHandoff, legacy },
400
+ operation: "open.migrate.legacy-dispatch-authority"
401
+ });
402
+ }
403
+ if (tables.includes("orchestrator_dispatch_metadata")) {
404
+ const metadataRows = Schema.decodeUnknownSync(Schema.Array(MetadataWorkLinkRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, route, work_link AS workLink
405
+ FROM orchestrator_dispatch_metadata WHERE dispatch_request_id = ? LIMIT 2`).all(dispatch.dispatchRequestId));
406
+ const metadata = metadataRows[0];
407
+ if (metadataRows.length !== 1 || metadata === undefined) {
408
+ throw new WorkStoreError({
409
+ cause: { dispatch, metadataRows },
410
+ operation: "open.migrate.legacy-metadata-authority"
411
+ });
412
+ }
413
+ if (metadata.workLink === null) {
414
+ throw new WorkStoreError({
415
+ cause: { dispatch, metadata },
416
+ operation: "open.migrate.legacy-metadata-authority"
417
+ });
418
+ }
419
+ const workLink = Schema.decodeUnknownSync(LegacyMetadataWorkLink)(JSON.parse(metadata.workLink));
420
+ requireMetadataAuthority(dispatch.dispatchRequestId, metadata.route, workLink.lineage, "open.migrate.legacy-metadata-authority");
421
+ if (!legacyDecisionEquivalent(workLink.handoff, legacy) ||
422
+ !workDispatchLineageEquivalent(workLink.lineage, dispatchIds)) {
423
+ throw new WorkStoreError({
424
+ cause: { legacy, workLink },
425
+ operation: "open.migrate.legacy-metadata-authority"
426
+ });
427
+ }
222
428
  }
223
- const route = requireCoordinatorRouteAuthority(commandRow.command, commandRow.activityIdempotencyKey, routeText, lineage, routeStorageAuthority(dispatchRequestId, operation), operation);
224
- requireLinkedParentAuthority(route.linkedRequestId, `${operation}.parent`);
225
- };
226
- const requireLifecycleAuthority = (dispatchRequestId, expectedRunningAt, operation) => {
227
- if (!requireCoordinatorSchema(operation))
228
- return;
229
- const lifecycle = readCoordinatorLifecycle(dispatchRequestId);
230
- requireCoordinatorLifecycleAuthority(lifecycle.dispatchRows, lifecycle.eventRows, expectedRunningAt, operation);
231
- };
232
- const requireCurrentAgentBindingAuthority = (dispatchRequestId, handoff, operation) => {
233
- const lifecycle = readCoordinatorLifecycle(dispatchRequestId);
234
- const runningAt = coordinatorLifecycleRunningAt(lifecycle.dispatchRows, lifecycle.eventRows, `${operation}.lifecycle`);
235
429
  const bindingRows = tables.includes("work_agent_bindings")
236
430
  ? Schema.decodeUnknownSync(Schema.Array(AgentBindingRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, lane_id AS laneId,
237
431
  expected_revision AS expectedRevision, revision, agent_id AS agentId, host, record
238
- FROM work_agent_bindings WHERE dispatch_request_id = ? LIMIT 2`).all(dispatchRequestId))
432
+ FROM work_agent_bindings
433
+ WHERE dispatch_request_id = ?`).all(dispatch.dispatchRequestId))
239
434
  : [];
240
- if (runningAt === null && bindingRows.length === 0)
241
- return;
242
435
  const bindingRow = bindingRows[0];
243
- if (runningAt === null || bindingRows.length !== 1 || bindingRow === undefined) {
244
- throw new WorkStoreError({ cause: { bindingRows, lifecycle }, operation });
245
- }
246
- const decoded = decodeAgentBindingRow(bindingRow, { dispatchRequestId, laneId: handoff.laneId }, operation);
247
- if (decoded._tag === "invalid")
248
- throw decoded.error;
249
- if (decoded.binding.request.expectedRevision !== handoff.expectedRevision ||
250
- decoded.binding.lane.goalId !== handoff.goalId ||
251
- decoded.binding.checkpoint.occurredAt !== runningAt) {
252
- throw new WorkStoreError({ cause: { binding: decoded.binding, handoff, runningAt }, operation });
253
- }
254
- requireBindingCompanions(decoded.binding, operation);
255
- const currentLaneRows = Schema.decodeUnknownSync(Schema.Array(LaneRow))(database.prepare(`SELECT lane_id AS laneId, goal_id AS goalId, operation_id AS operationId,
256
- phase, revision, record
257
- FROM work_lane_claims WHERE lane_id = ? LIMIT 2`).all(decoded.binding.lane.laneId));
258
- const currentLaneRow = currentLaneRows[0];
259
- if (currentLaneRows.length !== 1 || currentLaneRow === undefined) {
436
+ if (bindingRows.length !== 1 || bindingRow === undefined) {
260
437
  throw new WorkStoreError({
261
- cause: { binding: decoded.binding, currentLaneRows },
262
- operation: `${operation}.lane-revision`
438
+ cause: { bindingRows, dispatch, legacy },
439
+ operation: "open.migrate.legacy-handoff-revision"
263
440
  });
264
441
  }
265
- let currentLane;
266
- try {
267
- currentLane = Schema.decodeUnknownSync(WorkLaneClaimed)(JSON.parse(currentLaneRow.record));
268
- }
269
- catch (cause) {
270
- throw new WorkStoreError({ cause, operation: `${operation}.lane-revision` });
442
+ const bindingDecision = decodeAgentBindingRow(bindingRow, { dispatchRequestId: dispatch.dispatchRequestId, laneId: legacy.laneId }, "open.migrate.legacy-agent-binding");
443
+ if (bindingDecision._tag === "invalid")
444
+ throw bindingDecision.error;
445
+ if (bindingDecision.binding.lane.goalId !== legacy.goalId) {
446
+ throw new WorkStoreError({
447
+ cause: { binding: bindingDecision.binding, legacy },
448
+ operation: "open.migrate.legacy-agent-binding.goal"
449
+ });
271
450
  }
272
- const currentLaneSchema = laneColumns.includes("goal_id");
273
- const currentOperationRows = currentLaneSchema
274
- ? Schema.decodeUnknownSync(Schema.Array(AgentBindingLaneOperationRow))(database.prepare(`SELECT operation_id AS operationId, lane_id AS laneId, goal_id AS goalId,
275
- phase, revision, record
276
- FROM work_lane_operations WHERE operation_id = ? LIMIT 2`).all(currentLane.operationId))
277
- : [];
278
- const currentOperationError = currentLaneSchema
279
- ? laneOperationReadbackError(currentLane, currentOperationRows.length === 1 ? currentOperationRows[0] : undefined, `${operation}.lane-revision`)
280
- : undefined;
281
- if (currentLane.laneId !== currentLaneRow.laneId ||
282
- currentLane.goalId !== currentLaneRow.goalId ||
283
- currentLane.operationId !== currentLaneRow.operationId ||
284
- currentLane.phase !== currentLaneRow.phase ||
285
- currentLane.revision !== currentLaneRow.revision ||
286
- currentLane.laneId !== decoded.binding.lane.laneId ||
287
- currentLane.goalId !== decoded.binding.lane.goalId ||
288
- currentLane.revision < decoded.binding.lane.revision ||
289
- (currentLaneSchema && currentLane.revision === decoded.binding.lane.revision &&
290
- !Equal.equals(currentLane, decoded.binding.lane)) ||
291
- currentOperationError !== undefined) {
451
+ if (lane.revision < bindingDecision.binding.lane.revision) {
292
452
  throw new WorkStoreError({
293
- cause: { binding: decoded.binding, currentLane, currentLaneRow, currentOperationError },
294
- operation: `${operation}.lane-revision`
453
+ cause: { binding: bindingDecision.binding, lane, legacy },
454
+ operation: "open.migrate.legacy-handoff-lane-revision"
295
455
  });
296
456
  }
297
- };
298
- const dispatches = tables.includes("work_dispatch_handoffs")
299
- ? Schema.decodeUnknownSync(Schema.Array(LegacyDispatchStoredRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, handoff_id AS handoffId,
300
- lane_id AS laneId, occurred_at AS occurredAt, lineage, record
301
- FROM work_dispatch_handoffs`).all())
302
- : [];
303
- const decisionIdentities = decisionColumns.length > 0
304
- ? Schema.decodeUnknownSync(Schema.Array(DecisionIdentityRow))(database.prepare("SELECT handoff_id AS handoffId FROM work_decision_handoffs").all())
305
- : [];
306
- const orphanDispatch = dispatches.find(({ handoffId }) => !decisionIdentities.some((decision) => decision.handoffId === handoffId));
307
- if (orphanDispatch !== undefined) {
308
- throw new WorkStoreError({
309
- cause: { decisionIdentities, dispatch: orphanDispatch },
310
- operation: "open.migrate.dispatch-decision"
457
+ requireBindingCompanions(bindingDecision.binding, "open.migrate.legacy-agent-binding");
458
+ requireLifecycleAuthority(bindingDecision.binding.request.dispatchRequestId, bindingDecision.binding.checkpoint.occurredAt, "open.migrate.legacy-agent-binding.lifecycle");
459
+ return Schema.decodeUnknownSync(WorkDecisionHandoff)({
460
+ ...legacy,
461
+ contextDelta: legacy.summary,
462
+ expectedRevision: bindingDecision.binding.request.expectedRevision,
463
+ sessionId: legacy.id,
464
+ dispatchIds,
465
+ blockers: [],
466
+ evidenceRefs: [],
467
+ version: "herdr.work.decision.v2"
311
468
  });
312
- }
313
- if (tables.includes("orchestrator_dispatch_metadata")) {
314
- const linkedMetadata = Schema.decodeUnknownSync(Schema.Array(MetadataHandoffIdentityRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId,
315
- CASE WHEN work_link IS NULL THEN 0 ELSE 1 END AS hasWorkLink,
316
- CASE WHEN json_valid(work_link) AND json_type(work_link, '$.handoff.id') = 'text'
317
- THEN json_extract(work_link, '$.handoff.id') END AS handoffId,
318
- route
319
- FROM orchestrator_dispatch_metadata AS metadata
320
- WHERE work_link IS NOT NULL OR EXISTS (
321
- SELECT 1 FROM orchestrator_dispatch_metadata AS linked
322
- WHERE linked.dispatch_request_id = metadata.dispatch_request_id
323
- AND linked.work_link IS NOT NULL
324
- ) OR (json_valid(route) AND json_extract(route, '$.model') = 'gpt-5.6-sol')
325
- LIMIT ${workDecisionMaxRecords + 1}`).all());
326
- if (linkedMetadata.length > workDecisionMaxRecords) {
327
- throw new WorkStoreError({ cause: linkedMetadata.length, operation: "open.migrate.metadata-capacity" });
328
- }
329
- const metadataCounts = new Map();
330
- const dispatchCounts = new Map();
331
- const decisionCounts = new Map();
332
- for (const metadata of linkedMetadata) {
333
- metadataCounts.set(metadata.dispatchRequestId, (metadataCounts.get(metadata.dispatchRequestId) ?? 0) + 1);
334
- }
469
+ })
470
+ : [];
471
+ if (laneColumns.length > 0 && !laneColumns.includes("goal_id")) {
472
+ database.exec("ALTER TABLE work_lane_claims ADD COLUMN goal_id TEXT");
473
+ database.exec("ALTER TABLE work_lane_claims ADD COLUMN operation_id TEXT");
474
+ database.exec("ALTER TABLE work_lane_claims ADD COLUMN phase TEXT");
475
+ const update = database.prepare("UPDATE work_lane_claims SET goal_id = ?, operation_id = ?, phase = ?, record = ? WHERE lane_id = ?");
476
+ for (const lane of lanes)
477
+ update.run(lane.goalId, lane.operationId, lane.phase, JSON.stringify(lane), lane.laneId);
478
+ }
479
+ if (decisionColumns.length > 0 && !decisionColumns.includes("session_id")) {
480
+ database.exec("ALTER TABLE work_decision_handoffs ADD COLUMN session_id TEXT");
481
+ const update = database.prepare("UPDATE work_decision_handoffs SET session_id = ?, record = ? WHERE handoff_id = ?");
482
+ for (const decision of decisions)
483
+ update.run(decision.sessionId, JSON.stringify(decision), decision.id);
484
+ if (dispatches.length > 0) {
485
+ const updateDispatch = database.prepare("UPDATE work_dispatch_handoffs SET record = ? WHERE dispatch_request_id = ?");
486
+ const updateMetadata = tables.includes("orchestrator_dispatch_metadata")
487
+ ? database.prepare("UPDATE orchestrator_dispatch_metadata SET work_link = ? WHERE dispatch_request_id = ?")
488
+ : null;
335
489
  for (const dispatch of dispatches) {
336
- const identity = JSON.stringify([dispatch.dispatchRequestId, dispatch.handoffId]);
337
- dispatchCounts.set(identity, (dispatchCounts.get(identity) ?? 0) + 1);
338
- }
339
- for (const decision of decisionIdentities) {
340
- decisionCounts.set(decision.handoffId, (decisionCounts.get(decision.handoffId) ?? 0) + 1);
341
- }
342
- const invalidMetadata = linkedMetadata.find((metadata) => {
343
- if (metadata.route === null)
344
- return true;
345
- const requiresWorkLink = coordinatorRouteRequiresWorkLink(metadata.route, routeStorageAuthority(metadata.dispatchRequestId, "open.migrate.metadata-decision"), "open.migrate.metadata-decision");
346
- if (!requiresWorkLink)
347
- return metadata.hasWorkLink === 1;
348
- const dispatchIdentity = JSON.stringify([metadata.dispatchRequestId, metadata.handoffId]);
349
- return (metadata.handoffId === null || metadataCounts.get(metadata.dispatchRequestId) !== 1 ||
350
- dispatchCounts.get(dispatchIdentity) !== 1 || decisionCounts.get(metadata.handoffId) !== 1);
351
- });
352
- if (invalidMetadata !== undefined) {
353
- throw new WorkStoreError({
354
- cause: { decisionIdentities, dispatches, linkedMetadata, metadata: invalidMetadata },
355
- operation: "open.migrate.metadata-decision"
356
- });
490
+ const decision = decisions.find(({ id }) => id === dispatch.handoffId);
491
+ if (decision === undefined)
492
+ continue;
493
+ updateDispatch.run(JSON.stringify(decision), dispatch.dispatchRequestId);
494
+ updateMetadata?.run(JSON.stringify({ handoff: decision, lineage: decision.dispatchIds }), dispatch.dispatchRequestId);
357
495
  }
358
496
  }
359
- const lanes = laneColumns.length > 0 && !laneColumns.includes("goal_id")
360
- ? Schema.decodeUnknownSync(Schema.Array(LegacyLaneStoredRow))(database.prepare("SELECT lane_id AS laneId, revision, record FROM work_lane_claims").all()).map((row) => {
361
- const legacy = Schema.decodeUnknownSync(LegacyLaneRecord)(JSON.parse(row.record));
362
- if (legacy.laneId !== row.laneId || legacy.revision !== row.revision) {
363
- throw new WorkStoreError({ cause: { legacy, row }, operation: "open.migrate.lane-identity" });
364
- }
365
- return Schema.decodeUnknownSync(WorkLaneClaimed)({
366
- ...legacy,
367
- goalId: legacy.laneId,
368
- operationId: legacy.laneId
369
- });
370
- })
371
- : [];
372
- const decisions = decisionColumns.length > 0 && !decisionColumns.includes("session_id")
373
- ? Schema.decodeUnknownSync(Schema.Array(LegacyDecisionStoredRow))(database.prepare("SELECT handoff_id AS handoffId, record FROM work_decision_handoffs").all()).map((row) => {
374
- const legacy = Schema.decodeUnknownSync(LegacyDecisionRecord)(JSON.parse(row.record));
375
- if (legacy.id !== row.handoffId) {
376
- throw new WorkStoreError({ cause: { legacy, row }, operation: "open.migrate.handoff-identity" });
377
- }
378
- const matchingDispatches = dispatches.filter(({ handoffId }) => handoffId === legacy.id);
379
- const dispatch = matchingDispatches[0];
380
- const lane = lanes.find(({ laneId }) => laneId === legacy.laneId);
381
- if (lane === undefined) {
382
- throw new WorkStoreError({ cause: legacy, operation: "open.migrate.handoff-lane" });
383
- }
384
- const dispatchIds = dispatch === undefined
385
- ? []
386
- : Schema.decodeUnknownSync(WorkDispatchHandoff.fields.lineage)(JSON.parse(dispatch.lineage));
387
- if (matchingDispatches.length !== 1 || dispatch === undefined) {
388
- throw new WorkStoreError({
389
- cause: { legacy, matchingDispatches },
390
- operation: "open.migrate.legacy-dispatch-cardinality"
391
- });
392
- }
393
- const dispatchHandoff = Schema.decodeUnknownSync(LegacyDecisionRecord)(JSON.parse(dispatch.record));
394
- if (dispatch.laneId !== legacy.laneId || dispatch.occurredAt !== legacy.occurredAt ||
395
- !legacyDecisionEquivalent(dispatchHandoff, legacy)) {
396
- throw new WorkStoreError({
397
- cause: { dispatch, dispatchHandoff, legacy },
398
- operation: "open.migrate.legacy-dispatch-authority"
399
- });
400
- }
401
- if (tables.includes("orchestrator_dispatch_metadata")) {
402
- const metadataRows = Schema.decodeUnknownSync(Schema.Array(MetadataWorkLinkRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, route, work_link AS workLink
403
- FROM orchestrator_dispatch_metadata WHERE dispatch_request_id = ? LIMIT 2`).all(dispatch.dispatchRequestId));
404
- const metadata = metadataRows[0];
405
- if (metadataRows.length !== 1 || metadata === undefined) {
497
+ }
498
+ if (decisionColumns.length > 0) {
499
+ const storedDecisions = Schema.decodeUnknownSync(Schema.Array(DecisionRow))(database.prepare(`SELECT handoff_id AS handoffId, session_id AS sessionId, lane_id AS laneId,
500
+ occurred_at AS occurredAt, record
501
+ FROM work_decision_handoffs`).all());
502
+ const updateDecision = database.prepare("UPDATE work_decision_handoffs SET record = ? WHERE handoff_id = ?");
503
+ const updateDispatch = tables.includes("work_dispatch_handoffs")
504
+ ? database.prepare("UPDATE work_dispatch_handoffs SET record = ? WHERE handoff_id = ?")
505
+ : null;
506
+ const updateMetadata = tables.includes("orchestrator_dispatch_metadata")
507
+ ? database.prepare(`UPDATE orchestrator_dispatch_metadata SET work_link = ?
508
+ WHERE dispatch_request_id = ?`)
509
+ : null;
510
+ const previousDecisions = storedDecisions.flatMap((row) => {
511
+ const input = Schema.decodeUnknownSync(Schema.fromJsonString(Schema.Json))(row.record);
512
+ const previous = decodePreviousDecisionHandoff(input);
513
+ const handoffDispatches = tables.includes("work_dispatch_handoffs")
514
+ ? Schema.decodeUnknownSync(Schema.Array(LegacyDispatchStoredRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, handoff_id AS handoffId,
515
+ lane_id AS laneId, occurred_at AS occurredAt, lineage, record
516
+ FROM work_dispatch_handoffs WHERE handoff_id = ?`).all(row.handoffId))
517
+ : [];
518
+ switch (previous._tag) {
519
+ case "current": {
520
+ if (previous.value.id !== row.handoffId || previous.value.sessionId !== row.sessionId ||
521
+ previous.value.laneId !== row.laneId || previous.value.occurredAt !== row.occurredAt) {
406
522
  throw new WorkStoreError({
407
- cause: { dispatch, metadataRows },
408
- operation: "open.migrate.legacy-metadata-authority"
523
+ cause: { handoff: previous.value, row },
524
+ operation: "open.migrate.handoff-identity"
409
525
  });
410
526
  }
411
- if (metadata.workLink === null) {
527
+ if (handoffDispatches.length > 1) {
412
528
  throw new WorkStoreError({
413
- cause: { dispatch, metadata },
414
- operation: "open.migrate.legacy-metadata-authority"
529
+ cause: { handoffDispatches, row },
530
+ operation: "open.migrate.dispatch-cardinality"
415
531
  });
416
532
  }
417
- const workLink = Schema.decodeUnknownSync(LegacyMetadataWorkLink)(JSON.parse(metadata.workLink));
418
- requireMetadataAuthority(dispatch.dispatchRequestId, metadata.route, workLink.lineage, "open.migrate.legacy-metadata-authority");
419
- if (!legacyDecisionEquivalent(workLink.handoff, legacy) ||
420
- !workDispatchLineageEquivalent(workLink.lineage, dispatchIds)) {
533
+ const linkedMetadata = tables.includes("orchestrator_dispatch_metadata")
534
+ ? Schema.decodeUnknownSync(Schema.Array(MetadataWorkLinkRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, route, work_link AS workLink
535
+ FROM orchestrator_dispatch_metadata
536
+ WHERE work_link IS NOT NULL
537
+ AND CASE WHEN json_valid(work_link)
538
+ THEN json_extract(work_link, '$.handoff.id') END = ?`).all(row.handoffId))
539
+ : [];
540
+ if (tables.includes("orchestrator_dispatch_metadata") &&
541
+ (linkedMetadata.length !== handoffDispatches.length ||
542
+ linkedMetadata.some((metadata) => !handoffDispatches.some(({ dispatchRequestId }) => dispatchRequestId === metadata.dispatchRequestId)))) {
421
543
  throw new WorkStoreError({
422
- cause: { legacy, workLink },
423
- operation: "open.migrate.legacy-metadata-authority"
544
+ cause: { handoffDispatches, linkedMetadata, row },
545
+ operation: "open.migrate.metadata-authority"
424
546
  });
425
547
  }
426
- }
427
- const bindingRows = tables.includes("work_agent_bindings")
428
- ? Schema.decodeUnknownSync(Schema.Array(AgentBindingRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, lane_id AS laneId,
429
- expected_revision AS expectedRevision, revision, agent_id AS agentId, host, record
430
- FROM work_agent_bindings
431
- WHERE dispatch_request_id = ?`).all(dispatch.dispatchRequestId))
432
- : [];
433
- const bindingRow = bindingRows[0];
434
- if (bindingRows.length !== 1 || bindingRow === undefined) {
435
- throw new WorkStoreError({
436
- cause: { bindingRows, dispatch, legacy },
437
- operation: "open.migrate.legacy-handoff-revision"
438
- });
439
- }
440
- const bindingDecision = decodeAgentBindingRow(bindingRow, { dispatchRequestId: dispatch.dispatchRequestId, laneId: legacy.laneId }, "open.migrate.legacy-agent-binding");
441
- if (bindingDecision._tag === "invalid")
442
- throw bindingDecision.error;
443
- if (bindingDecision.binding.lane.goalId !== legacy.goalId) {
444
- throw new WorkStoreError({
445
- cause: { binding: bindingDecision.binding, legacy },
446
- operation: "open.migrate.legacy-agent-binding.goal"
447
- });
448
- }
449
- if (lane.revision < bindingDecision.binding.lane.revision) {
450
- throw new WorkStoreError({
451
- cause: { binding: bindingDecision.binding, lane, legacy },
452
- operation: "open.migrate.legacy-handoff-lane-revision"
453
- });
454
- }
455
- requireBindingCompanions(bindingDecision.binding, "open.migrate.legacy-agent-binding");
456
- requireLifecycleAuthority(bindingDecision.binding.request.dispatchRequestId, bindingDecision.binding.checkpoint.occurredAt, "open.migrate.legacy-agent-binding.lifecycle");
457
- return Schema.decodeUnknownSync(WorkDecisionHandoff)({
458
- ...legacy,
459
- contextDelta: legacy.summary,
460
- expectedRevision: bindingDecision.binding.request.expectedRevision,
461
- sessionId: legacy.id,
462
- dispatchIds,
463
- blockers: [],
464
- evidenceRefs: [],
465
- version: "herdr.work.decision.v2"
466
- });
467
- })
468
- : [];
469
- if (laneColumns.length > 0 && !laneColumns.includes("goal_id")) {
470
- database.exec("ALTER TABLE work_lane_claims ADD COLUMN goal_id TEXT");
471
- database.exec("ALTER TABLE work_lane_claims ADD COLUMN operation_id TEXT");
472
- database.exec("ALTER TABLE work_lane_claims ADD COLUMN phase TEXT");
473
- const update = database.prepare("UPDATE work_lane_claims SET goal_id = ?, operation_id = ?, phase = ?, record = ? WHERE lane_id = ?");
474
- for (const lane of lanes)
475
- update.run(lane.goalId, lane.operationId, lane.phase, JSON.stringify(lane), lane.laneId);
476
- }
477
- if (decisionColumns.length > 0 && !decisionColumns.includes("session_id")) {
478
- database.exec("ALTER TABLE work_decision_handoffs ADD COLUMN session_id TEXT");
479
- const update = database.prepare("UPDATE work_decision_handoffs SET session_id = ?, record = ? WHERE handoff_id = ?");
480
- for (const decision of decisions)
481
- update.run(decision.sessionId, JSON.stringify(decision), decision.id);
482
- if (dispatches.length > 0) {
483
- const updateDispatch = database.prepare("UPDATE work_dispatch_handoffs SET record = ? WHERE dispatch_request_id = ?");
484
- const updateMetadata = tables.includes("orchestrator_dispatch_metadata")
485
- ? database.prepare("UPDATE orchestrator_dispatch_metadata SET work_link = ? WHERE dispatch_request_id = ?")
486
- : null;
487
- for (const dispatch of dispatches) {
488
- const decision = decisions.find(({ id }) => id === dispatch.handoffId);
489
- if (decision === undefined)
490
- continue;
491
- updateDispatch.run(JSON.stringify(decision), dispatch.dispatchRequestId);
492
- updateMetadata?.run(JSON.stringify({ handoff: decision, lineage: decision.dispatchIds }), dispatch.dispatchRequestId);
493
- }
494
- }
495
- }
496
- if (decisionColumns.length > 0) {
497
- const storedDecisions = Schema.decodeUnknownSync(Schema.Array(DecisionRow))(database.prepare(`SELECT handoff_id AS handoffId, session_id AS sessionId, lane_id AS laneId,
498
- occurred_at AS occurredAt, record
499
- FROM work_decision_handoffs`).all());
500
- const updateDecision = database.prepare("UPDATE work_decision_handoffs SET record = ? WHERE handoff_id = ?");
501
- const updateDispatch = tables.includes("work_dispatch_handoffs")
502
- ? database.prepare("UPDATE work_dispatch_handoffs SET record = ? WHERE handoff_id = ?")
503
- : null;
504
- const updateMetadata = tables.includes("orchestrator_dispatch_metadata")
505
- ? database.prepare(`UPDATE orchestrator_dispatch_metadata SET work_link = ?
506
- WHERE dispatch_request_id = ?`)
507
- : null;
508
- const previousDecisions = storedDecisions.flatMap((row) => {
509
- const input = Schema.decodeUnknownSync(Schema.fromJsonString(Schema.Json))(row.record);
510
- const previous = decodePreviousDecisionHandoff(input);
511
- const handoffDispatches = tables.includes("work_dispatch_handoffs")
512
- ? Schema.decodeUnknownSync(Schema.Array(LegacyDispatchStoredRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, handoff_id AS handoffId,
513
- lane_id AS laneId, occurred_at AS occurredAt, lineage, record
514
- FROM work_dispatch_handoffs WHERE handoff_id = ?`).all(row.handoffId))
515
- : [];
516
- switch (previous._tag) {
517
- case "current": {
518
- if (previous.value.id !== row.handoffId || previous.value.sessionId !== row.sessionId ||
519
- previous.value.laneId !== row.laneId || previous.value.occurredAt !== row.occurredAt) {
520
- throw new WorkStoreError({
521
- cause: { handoff: previous.value, row },
522
- operation: "open.migrate.handoff-identity"
523
- });
524
- }
525
- if (handoffDispatches.length > 1) {
526
- throw new WorkStoreError({
527
- cause: { handoffDispatches, row },
528
- operation: "open.migrate.dispatch-cardinality"
529
- });
530
- }
531
- const linkedMetadata = tables.includes("orchestrator_dispatch_metadata")
532
- ? Schema.decodeUnknownSync(Schema.Array(MetadataWorkLinkRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, route, work_link AS workLink
533
- FROM orchestrator_dispatch_metadata
534
- WHERE work_link IS NOT NULL
535
- AND CASE WHEN json_valid(work_link)
536
- THEN json_extract(work_link, '$.handoff.id') END = ?`).all(row.handoffId))
537
- : [];
538
- if (tables.includes("orchestrator_dispatch_metadata") &&
539
- (linkedMetadata.length !== handoffDispatches.length ||
540
- linkedMetadata.some((metadata) => !handoffDispatches.some(({ dispatchRequestId }) => dispatchRequestId === metadata.dispatchRequestId)))) {
541
- throw new WorkStoreError({
542
- cause: { handoffDispatches, linkedMetadata, row },
543
- operation: "open.migrate.metadata-authority"
544
- });
545
- }
546
- for (const dispatch of handoffDispatches) {
547
- const dispatchAuthority = (() => {
548
- try {
549
- return {
550
- handoff: Schema.decodeUnknownSync(WorkDecisionHandoff)(JSON.parse(dispatch.record)),
551
- lineage: Schema.decodeUnknownSync(WorkDispatchHandoff.fields.lineage)(JSON.parse(dispatch.lineage))
552
- };
553
- }
554
- catch (cause) {
555
- throw new WorkStoreError({ cause, operation: "open.migrate.decode-dispatch-authority" });
556
- }
557
- })();
558
- if (dispatch.laneId !== previous.value.laneId ||
559
- dispatch.occurredAt !== previous.value.occurredAt ||
560
- !currentDecisionHandoffEquivalent(dispatchAuthority.handoff, previous.value) ||
561
- !workDispatchLineageContainedBy(dispatchAuthority.lineage, previous.value.dispatchIds)) {
562
- throw new WorkStoreError({
563
- cause: { dispatch, dispatchAuthority, handoff: previous.value },
564
- operation: "open.migrate.dispatch-authority"
565
- });
566
- }
567
- if (!tables.includes("orchestrator_dispatch_metadata"))
568
- continue;
569
- const metadata = linkedMetadata.find(({ dispatchRequestId }) => dispatchRequestId === dispatch.dispatchRequestId);
570
- if (metadata?.workLink === null || metadata?.workLink === undefined) {
571
- throw new WorkStoreError({
572
- cause: { dispatch, metadata },
573
- operation: "open.migrate.metadata-authority"
574
- });
548
+ for (const dispatch of handoffDispatches) {
549
+ const dispatchAuthority = (() => {
550
+ try {
551
+ return {
552
+ handoff: Schema.decodeUnknownSync(WorkDecisionHandoff)(JSON.parse(dispatch.record)),
553
+ lineage: Schema.decodeUnknownSync(WorkDispatchHandoff.fields.lineage)(JSON.parse(dispatch.lineage))
554
+ };
575
555
  }
576
- const workLink = (() => {
577
- try {
578
- return Schema.decodeUnknownSync(CurrentMetadataWorkLink)(JSON.parse(metadata.workLink));
579
- }
580
- catch (cause) {
581
- throw new WorkStoreError({ cause, operation: "open.migrate.decode-metadata-authority" });
582
- }
583
- })();
584
- requireMetadataAuthority(dispatch.dispatchRequestId, metadata.route, workLink.lineage, "open.migrate.metadata-authority");
585
- if (!currentDecisionHandoffEquivalent(workLink.handoff, previous.value) ||
586
- !workDispatchLineageEquivalent(workLink.lineage, dispatchAuthority.lineage)) {
587
- throw new WorkStoreError({
588
- cause: { handoff: previous.value, workLink },
589
- operation: "open.migrate.metadata-authority"
590
- });
556
+ catch (cause) {
557
+ throw new WorkStoreError({ cause, operation: "open.migrate.decode-dispatch-authority" });
591
558
  }
592
- requireCurrentAgentBindingAuthority(dispatch.dispatchRequestId, previous.value, "open.migrate.current-agent-binding");
559
+ })();
560
+ if (dispatch.laneId !== previous.value.laneId ||
561
+ dispatch.occurredAt !== previous.value.occurredAt ||
562
+ !currentDecisionHandoffEquivalent(dispatchAuthority.handoff, previous.value) ||
563
+ !workDispatchLineageContainedBy(dispatchAuthority.lineage, previous.value.dispatchIds)) {
564
+ throw new WorkStoreError({
565
+ cause: { dispatch, dispatchAuthority, handoff: previous.value },
566
+ operation: "open.migrate.dispatch-authority"
567
+ });
593
568
  }
594
- return [];
595
- }
596
- case "invalid":
597
- throw new WorkStoreError({
598
- cause: previous.cause,
599
- operation: "open.migrate.invalid-handoff"
600
- });
601
- case "previous":
602
- return [{ previous: previous.value, row }];
603
- }
604
- });
605
- for (const { previous, row } of previousDecisions) {
606
- const laneRevision = Schema.decodeUnknownSync(LaneRevisionRow)(database.prepare("SELECT revision FROM work_lane_claims WHERE lane_id = ?").get(row.laneId));
607
- const handoffDispatches = tables.includes("work_dispatch_handoffs")
608
- ? Schema.decodeUnknownSync(Schema.Array(LegacyDispatchStoredRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, handoff_id AS handoffId,
609
- lane_id AS laneId, occurred_at AS occurredAt, lineage, record
610
- FROM work_dispatch_handoffs WHERE handoff_id = ?`).all(row.handoffId))
611
- : [];
612
- if (handoffDispatches.length > 1) {
613
- throw new WorkStoreError({
614
- cause: { handoffDispatches, row },
615
- operation: "open.migrate.dispatch-cardinality"
616
- });
617
- }
618
- const linkedMetadata = tables.includes("orchestrator_dispatch_metadata")
619
- ? Schema.decodeUnknownSync(Schema.Array(MetadataWorkLinkRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, route, work_link AS workLink
620
- FROM orchestrator_dispatch_metadata
621
- WHERE work_link IS NOT NULL
622
- AND CASE WHEN json_valid(work_link)
623
- THEN json_extract(work_link, '$.handoff.id') END = ?`).all(row.handoffId))
624
- : [];
625
- if (tables.includes("orchestrator_dispatch_metadata") &&
626
- (linkedMetadata.length !== handoffDispatches.length ||
627
- linkedMetadata.some((metadata) => !handoffDispatches.some(({ dispatchRequestId }) => dispatchRequestId === metadata.dispatchRequestId)))) {
628
- throw new WorkStoreError({
629
- cause: { handoffDispatches, linkedMetadata, row },
630
- operation: "open.migrate.metadata-authority"
631
- });
632
- }
633
- const bindingRows = tables.includes("work_agent_bindings") && tables.includes("work_dispatch_handoffs")
634
- ? Schema.decodeUnknownSync(Schema.Array(AgentBindingRow))(database.prepare(`SELECT binding.dispatch_request_id AS dispatchRequestId, binding.lane_id AS laneId,
635
- binding.expected_revision AS expectedRevision, binding.revision,
636
- binding.agent_id AS agentId, binding.host, binding.record
637
- FROM work_agent_bindings binding
638
- JOIN work_dispatch_handoffs dispatch
639
- ON dispatch.dispatch_request_id = binding.dispatch_request_id
640
- WHERE dispatch.handoff_id = ?`).all(row.handoffId))
641
- : [];
642
- const bindingRow = bindingRows[0];
643
- const handoffDispatch = bindingRow === undefined
644
- ? undefined
645
- : handoffDispatches.find(({ dispatchRequestId }) => dispatchRequestId === bindingRow.dispatchRequestId);
646
- if (bindingRows.length !== 1 || bindingRow === undefined || handoffDispatch === undefined) {
647
- throw new WorkStoreError({ cause: { bindingRows, row }, operation: "open.migrate.handoff-revision" });
648
- }
649
- const bindingDecision = decodeAgentBindingRow(bindingRow, { dispatchRequestId: handoffDispatch.dispatchRequestId, laneId: previous.laneId }, "open.migrate.agent-binding");
650
- if (bindingDecision._tag === "invalid")
651
- throw bindingDecision.error;
652
- if (laneRevision.revision < bindingDecision.binding.lane.revision) {
653
- throw new WorkStoreError({
654
- cause: { binding: bindingDecision.binding, laneRevision, previous },
655
- operation: "open.migrate.handoff-lane-revision"
656
- });
657
- }
658
- if (bindingDecision.binding.lane.goalId !== previous.goalId) {
659
- throw new WorkStoreError({
660
- cause: { binding: bindingDecision.binding, previous },
661
- operation: "open.migrate.agent-binding.goal"
662
- });
663
- }
664
- requireBindingCompanions(bindingDecision.binding, "open.migrate.agent-binding");
665
- requireLifecycleAuthority(bindingDecision.binding.request.dispatchRequestId, bindingDecision.binding.checkpoint.occurredAt, "open.migrate.agent-binding.lifecycle");
666
- const verifiedDispatches = handoffDispatches.map((dispatch) => {
667
- const dispatchHandoff = Schema.decodeUnknownSync(PreviousWorkDecisionHandoff)(JSON.parse(dispatch.record));
668
- const lineage = Schema.decodeUnknownSync(WorkDispatchHandoff.fields.lineage)(JSON.parse(dispatch.lineage));
669
- if (dispatch.laneId !== previous.laneId || dispatch.occurredAt !== previous.occurredAt ||
670
- !previousDecisionHandoffEquivalent(dispatchHandoff, previous) ||
671
- !workDispatchLineageContainedBy(lineage, previous.dispatchIds)) {
672
- throw new WorkStoreError({
673
- cause: { dispatch, dispatchHandoff, previous },
674
- operation: "open.migrate.dispatch-authority"
675
- });
676
- }
677
- if (tables.includes("orchestrator_dispatch_metadata")) {
569
+ if (!tables.includes("orchestrator_dispatch_metadata"))
570
+ continue;
678
571
  const metadata = linkedMetadata.find(({ dispatchRequestId }) => dispatchRequestId === dispatch.dispatchRequestId);
679
572
  if (metadata?.workLink === null || metadata?.workLink === undefined) {
680
573
  throw new WorkStoreError({
@@ -682,139 +575,219 @@ const migrateLegacyAuthorityTables = (database) => {
682
575
  operation: "open.migrate.metadata-authority"
683
576
  });
684
577
  }
685
- const workLink = Schema.decodeUnknownSync(PreviousMetadataWorkLink)(JSON.parse(metadata.workLink));
578
+ const workLink = (() => {
579
+ try {
580
+ return Schema.decodeUnknownSync(CurrentMetadataWorkLink)(JSON.parse(metadata.workLink));
581
+ }
582
+ catch (cause) {
583
+ throw new WorkStoreError({ cause, operation: "open.migrate.decode-metadata-authority" });
584
+ }
585
+ })();
686
586
  requireMetadataAuthority(dispatch.dispatchRequestId, metadata.route, workLink.lineage, "open.migrate.metadata-authority");
687
- if (!previousDecisionHandoffEquivalent(workLink.handoff, previous) ||
688
- !workDispatchLineageEquivalent(workLink.lineage, lineage)) {
587
+ if (!currentDecisionHandoffEquivalent(workLink.handoff, previous.value) ||
588
+ !workDispatchLineageEquivalent(workLink.lineage, dispatchAuthority.lineage)) {
689
589
  throw new WorkStoreError({
690
- cause: { previous, workLink },
590
+ cause: { handoff: previous.value, workLink },
691
591
  operation: "open.migrate.metadata-authority"
692
592
  });
693
593
  }
594
+ requireCurrentAgentBindingAuthority(dispatch.dispatchRequestId, previous.value, "open.migrate.current-agent-binding");
694
595
  }
695
- return { dispatch, lineage };
696
- });
697
- const migrated = upgradePreviousDecisionHandoff(previous, bindingDecision.binding.request.expectedRevision);
698
- if (migrated.id !== row.handoffId || migrated.sessionId !== row.sessionId ||
699
- migrated.laneId !== row.laneId || migrated.occurredAt !== row.occurredAt) {
700
- throw new WorkStoreError({ cause: { migrated, row }, operation: "open.migrate.handoff-identity" });
701
- }
702
- const encoded = JSON.stringify(migrated);
703
- updateDecision.run(encoded, row.handoffId);
704
- updateDispatch?.run(encoded, row.handoffId);
705
- for (const { dispatch, lineage } of verifiedDispatches) {
706
- updateMetadata?.run(JSON.stringify({ handoff: migrated, lineage }), dispatch.dispatchRequestId);
596
+ return [];
707
597
  }
598
+ case "invalid":
599
+ throw new WorkStoreError({
600
+ cause: previous.cause,
601
+ operation: "open.migrate.invalid-handoff"
602
+ });
603
+ case "previous":
604
+ return [{ previous: previous.value, row }];
605
+ }
606
+ });
607
+ for (const { previous, row } of previousDecisions) {
608
+ const laneRevision = Schema.decodeUnknownSync(LaneRevisionRow)(database.prepare("SELECT revision FROM work_lane_claims WHERE lane_id = ?").get(row.laneId));
609
+ const handoffDispatches = tables.includes("work_dispatch_handoffs")
610
+ ? Schema.decodeUnknownSync(Schema.Array(LegacyDispatchStoredRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, handoff_id AS handoffId,
611
+ lane_id AS laneId, occurred_at AS occurredAt, lineage, record
612
+ FROM work_dispatch_handoffs WHERE handoff_id = ?`).all(row.handoffId))
613
+ : [];
614
+ if (handoffDispatches.length > 1) {
615
+ throw new WorkStoreError({
616
+ cause: { handoffDispatches, row },
617
+ operation: "open.migrate.dispatch-cardinality"
618
+ });
619
+ }
620
+ const linkedMetadata = tables.includes("orchestrator_dispatch_metadata")
621
+ ? Schema.decodeUnknownSync(Schema.Array(MetadataWorkLinkRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, route, work_link AS workLink
622
+ FROM orchestrator_dispatch_metadata
623
+ WHERE work_link IS NOT NULL
624
+ AND CASE WHEN json_valid(work_link)
625
+ THEN json_extract(work_link, '$.handoff.id') END = ?`).all(row.handoffId))
626
+ : [];
627
+ if (tables.includes("orchestrator_dispatch_metadata") &&
628
+ (linkedMetadata.length !== handoffDispatches.length ||
629
+ linkedMetadata.some((metadata) => !handoffDispatches.some(({ dispatchRequestId }) => dispatchRequestId === metadata.dispatchRequestId)))) {
630
+ throw new WorkStoreError({
631
+ cause: { handoffDispatches, linkedMetadata, row },
632
+ operation: "open.migrate.metadata-authority"
633
+ });
634
+ }
635
+ const bindingRows = tables.includes("work_agent_bindings") && tables.includes("work_dispatch_handoffs")
636
+ ? Schema.decodeUnknownSync(Schema.Array(AgentBindingRow))(database.prepare(`SELECT binding.dispatch_request_id AS dispatchRequestId, binding.lane_id AS laneId,
637
+ binding.expected_revision AS expectedRevision, binding.revision,
638
+ binding.agent_id AS agentId, binding.host, binding.record
639
+ FROM work_agent_bindings binding
640
+ JOIN work_dispatch_handoffs dispatch
641
+ ON dispatch.dispatch_request_id = binding.dispatch_request_id
642
+ WHERE dispatch.handoff_id = ?`).all(row.handoffId))
643
+ : [];
644
+ const bindingRow = bindingRows[0];
645
+ const handoffDispatch = bindingRow === undefined
646
+ ? undefined
647
+ : handoffDispatches.find(({ dispatchRequestId }) => dispatchRequestId === bindingRow.dispatchRequestId);
648
+ if (bindingRows.length !== 1 || bindingRow === undefined || handoffDispatch === undefined) {
649
+ throw new WorkStoreError({ cause: { bindingRows, row }, operation: "open.migrate.handoff-revision" });
650
+ }
651
+ const bindingDecision = decodeAgentBindingRow(bindingRow, { dispatchRequestId: handoffDispatch.dispatchRequestId, laneId: previous.laneId }, "open.migrate.agent-binding");
652
+ if (bindingDecision._tag === "invalid")
653
+ throw bindingDecision.error;
654
+ if (laneRevision.revision < bindingDecision.binding.lane.revision) {
655
+ throw new WorkStoreError({
656
+ cause: { binding: bindingDecision.binding, laneRevision, previous },
657
+ operation: "open.migrate.handoff-lane-revision"
658
+ });
659
+ }
660
+ if (bindingDecision.binding.lane.goalId !== previous.goalId) {
661
+ throw new WorkStoreError({
662
+ cause: { binding: bindingDecision.binding, previous },
663
+ operation: "open.migrate.agent-binding.goal"
664
+ });
708
665
  }
709
- if (decisions.length > 0 || previousDecisions.length > 0) {
710
- const migratedLedger = Schema.decodeUnknownSync(LedgerBytesRow)(database.prepare(`SELECT COALESCE(SUM(
711
- length(CAST(handoff_id AS BLOB)) + length(CAST(record AS BLOB))
712
- ), 0) AS bytes
713
- FROM work_decision_handoffs`).get());
714
- if (migratedLedger.bytes > workDecisionMaxBytes) {
666
+ requireBindingCompanions(bindingDecision.binding, "open.migrate.agent-binding");
667
+ requireLifecycleAuthority(bindingDecision.binding.request.dispatchRequestId, bindingDecision.binding.checkpoint.occurredAt, "open.migrate.agent-binding.lifecycle");
668
+ const verifiedDispatches = handoffDispatches.map((dispatch) => {
669
+ const dispatchHandoff = Schema.decodeUnknownSync(PreviousWorkDecisionHandoff)(JSON.parse(dispatch.record));
670
+ const lineage = Schema.decodeUnknownSync(WorkDispatchHandoff.fields.lineage)(JSON.parse(dispatch.lineage));
671
+ if (dispatch.laneId !== previous.laneId || dispatch.occurredAt !== previous.occurredAt ||
672
+ !previousDecisionHandoffEquivalent(dispatchHandoff, previous) ||
673
+ !workDispatchLineageContainedBy(lineage, previous.dispatchIds)) {
715
674
  throw new WorkStoreError({
716
- cause: migratedLedger,
717
- operation: "open.migrate.handoff-capacity"
675
+ cause: { dispatch, dispatchHandoff, previous },
676
+ operation: "open.migrate.dispatch-authority"
718
677
  });
719
678
  }
720
- }
721
- }
722
- if (tables.includes("orchestrator_dispatch_metadata") && coordinatorDispatchColumns.includes("is_routed")) {
723
- const pageSize = 512;
724
- let cursor = null;
725
- while (true) {
726
- const input = cursor === null
727
- ? database.prepare(`SELECT CAST(metadata.rowid AS TEXT) AS rowId,
728
- metadata.dispatch_request_id AS dispatchRequestId,
729
- dispatch.activity_idempotency_key AS activityIdempotencyKey,
730
- dispatch.command, dispatch.is_routed AS isRouted,
731
- CASE WHEN metadata.work_link IS NULL THEN 0 ELSE 1 END AS hasWorkLink,
732
- metadata.route
733
- FROM orchestrator_dispatch_metadata AS metadata
734
- LEFT JOIN orchestrator_dispatches AS dispatch
735
- ON dispatch.dispatch_request_id = metadata.dispatch_request_id
736
- ORDER BY metadata.rowid ASC LIMIT ?`).all(pageSize)
737
- : database.prepare(`SELECT CAST(metadata.rowid AS TEXT) AS rowId,
738
- metadata.dispatch_request_id AS dispatchRequestId,
739
- dispatch.activity_idempotency_key AS activityIdempotencyKey,
740
- dispatch.command, dispatch.is_routed AS isRouted,
741
- CASE WHEN metadata.work_link IS NULL THEN 0 ELSE 1 END AS hasWorkLink,
742
- metadata.route
743
- FROM orchestrator_dispatch_metadata AS metadata
744
- LEFT JOIN orchestrator_dispatches AS dispatch
745
- ON dispatch.dispatch_request_id = metadata.dispatch_request_id
746
- WHERE metadata.rowid > CAST(? AS INTEGER)
747
- ORDER BY metadata.rowid ASC LIMIT ?`).all(cursor, pageSize);
748
- const rows = Schema.decodeUnknownSync(Schema.Array(CoordinatorMetadataRouteRow))(input);
749
- for (const row of rows) {
750
- try {
751
- const isLegacyUnroutedOrphan = row.activityIdempotencyKey === null && row.command === null &&
752
- row.isRouted === null && row.route === null && row.hasWorkLink === 0;
753
- if (isLegacyUnroutedOrphan)
754
- continue;
755
- if (row.activityIdempotencyKey === null || row.command === null ||
756
- row.route === null || row.isRouted === null) {
757
- throw new WorkStoreError({ cause: row, operation: "open.migrate.metadata-route" });
758
- }
759
- requireCoordinatorRouteBinding(row.command, row.activityIdempotencyKey, row.route, row.hasWorkLink === 1, { _tag: "routed_discriminator", isRouted: row.isRouted }, "open.migrate.metadata-route");
679
+ if (tables.includes("orchestrator_dispatch_metadata")) {
680
+ const metadata = linkedMetadata.find(({ dispatchRequestId }) => dispatchRequestId === dispatch.dispatchRequestId);
681
+ if (metadata?.workLink === null || metadata?.workLink === undefined) {
682
+ throw new WorkStoreError({
683
+ cause: { dispatch, metadata },
684
+ operation: "open.migrate.metadata-authority"
685
+ });
760
686
  }
761
- catch (cause) {
762
- if (Schema.is(WorkStoreError)(cause))
763
- throw cause;
764
- throw new WorkStoreError({ cause, operation: "open.migrate.metadata-route" });
687
+ const workLink = Schema.decodeUnknownSync(PreviousMetadataWorkLink)(JSON.parse(metadata.workLink));
688
+ requireMetadataAuthority(dispatch.dispatchRequestId, metadata.route, workLink.lineage, "open.migrate.metadata-authority");
689
+ if (!previousDecisionHandoffEquivalent(workLink.handoff, previous) ||
690
+ !workDispatchLineageEquivalent(workLink.lineage, lineage)) {
691
+ throw new WorkStoreError({
692
+ cause: { previous, workLink },
693
+ operation: "open.migrate.metadata-authority"
694
+ });
765
695
  }
766
696
  }
767
- const last = rows.at(-1);
768
- if (rows.length < pageSize || last === undefined)
769
- break;
770
- cursor = last.rowId;
697
+ return { dispatch, lineage };
698
+ });
699
+ const migrated = upgradePreviousDecisionHandoff(previous, bindingDecision.binding.request.expectedRevision);
700
+ if (migrated.id !== row.handoffId || migrated.sessionId !== row.sessionId ||
701
+ migrated.laneId !== row.laneId || migrated.occurredAt !== row.occurredAt) {
702
+ throw new WorkStoreError({ cause: { migrated, row }, operation: "open.migrate.handoff-identity" });
703
+ }
704
+ const encoded = JSON.stringify(migrated);
705
+ updateDecision.run(encoded, row.handoffId);
706
+ updateDispatch?.run(encoded, row.handoffId);
707
+ for (const { dispatch, lineage } of verifiedDispatches) {
708
+ updateMetadata?.run(JSON.stringify({ handoff: migrated, lineage }), dispatch.dispatchRequestId);
771
709
  }
772
- const invalidRoutedMetadata = Schema.decodeUnknownSync(Schema.Array(RoutedMetadataCardinalityRow))(database.prepare(`SELECT dispatch.dispatch_request_id AS dispatchRequestId,
773
- COUNT(metadata.dispatch_request_id) AS metadataCount
774
- FROM orchestrator_dispatches AS dispatch
775
- LEFT JOIN orchestrator_dispatch_metadata AS metadata
776
- ON metadata.dispatch_request_id = dispatch.dispatch_request_id
777
- WHERE dispatch.is_routed = 1
778
- GROUP BY dispatch.dispatch_request_id
779
- HAVING COUNT(metadata.dispatch_request_id) <> 1
780
- LIMIT 1`).all())[0];
781
- if (invalidRoutedMetadata !== undefined) {
710
+ }
711
+ if (decisions.length > 0 || previousDecisions.length > 0) {
712
+ const migratedLedger = Schema.decodeUnknownSync(LedgerBytesRow)(database.prepare(`SELECT COALESCE(SUM(
713
+ length(CAST(handoff_id AS BLOB)) + length(CAST(record AS BLOB))
714
+ ), 0) AS bytes
715
+ FROM work_decision_handoffs`).get());
716
+ if (migratedLedger.bytes > workDecisionMaxBytes) {
782
717
  throw new WorkStoreError({
783
- cause: invalidRoutedMetadata,
784
- operation: "open.migrate.routed-metadata"
718
+ cause: migratedLedger,
719
+ operation: "open.migrate.handoff-capacity"
785
720
  });
786
721
  }
787
722
  }
788
- database.exec("COMMIT");
789
- transaction = false;
790
723
  }
791
- catch (error) {
792
- if (transaction)
793
- database.exec("ROLLBACK");
794
- throw error;
724
+ if (tables.includes("orchestrator_dispatch_metadata") && coordinatorDispatchColumns.includes("is_routed")) {
725
+ const pageSize = 512;
726
+ let cursor = null;
727
+ while (true) {
728
+ const input = cursor === null
729
+ ? database.prepare(`SELECT CAST(metadata.rowid AS TEXT) AS rowId,
730
+ metadata.dispatch_request_id AS dispatchRequestId,
731
+ dispatch.activity_idempotency_key AS activityIdempotencyKey,
732
+ dispatch.command, dispatch.is_routed AS isRouted,
733
+ CASE WHEN metadata.work_link IS NULL THEN 0 ELSE 1 END AS hasWorkLink,
734
+ metadata.route
735
+ FROM orchestrator_dispatch_metadata AS metadata
736
+ LEFT JOIN orchestrator_dispatches AS dispatch
737
+ ON dispatch.dispatch_request_id = metadata.dispatch_request_id
738
+ ORDER BY metadata.rowid ASC LIMIT ?`).all(pageSize)
739
+ : database.prepare(`SELECT CAST(metadata.rowid AS TEXT) AS rowId,
740
+ metadata.dispatch_request_id AS dispatchRequestId,
741
+ dispatch.activity_idempotency_key AS activityIdempotencyKey,
742
+ dispatch.command, dispatch.is_routed AS isRouted,
743
+ CASE WHEN metadata.work_link IS NULL THEN 0 ELSE 1 END AS hasWorkLink,
744
+ metadata.route
745
+ FROM orchestrator_dispatch_metadata AS metadata
746
+ LEFT JOIN orchestrator_dispatches AS dispatch
747
+ ON dispatch.dispatch_request_id = metadata.dispatch_request_id
748
+ WHERE metadata.rowid > CAST(? AS INTEGER)
749
+ ORDER BY metadata.rowid ASC LIMIT ?`).all(cursor, pageSize);
750
+ const rows = Schema.decodeUnknownSync(Schema.Array(CoordinatorMetadataRouteRow))(input);
751
+ for (const row of rows) {
752
+ try {
753
+ const isLegacyUnroutedOrphan = row.activityIdempotencyKey === null && row.command === null &&
754
+ row.isRouted === null && row.route === null && row.hasWorkLink === 0;
755
+ if (isLegacyUnroutedOrphan)
756
+ continue;
757
+ if (row.activityIdempotencyKey === null || row.command === null ||
758
+ row.route === null || row.isRouted === null) {
759
+ throw new WorkStoreError({ cause: row, operation: "open.migrate.metadata-route" });
760
+ }
761
+ requireCoordinatorRouteBinding(row.command, row.activityIdempotencyKey, row.route, row.hasWorkLink === 1, { _tag: "routed_discriminator", isRouted: row.isRouted }, "open.migrate.metadata-route");
762
+ }
763
+ catch (cause) {
764
+ if (Schema.is(WorkStoreError)(cause))
765
+ throw cause;
766
+ throw new WorkStoreError({ cause, operation: "open.migrate.metadata-route" });
767
+ }
768
+ }
769
+ const last = rows.at(-1);
770
+ if (rows.length < pageSize || last === undefined)
771
+ break;
772
+ cursor = last.rowId;
773
+ }
774
+ const invalidRoutedMetadata = Schema.decodeUnknownSync(Schema.Array(RoutedMetadataCardinalityRow))(database.prepare(`SELECT dispatch.dispatch_request_id AS dispatchRequestId,
775
+ COUNT(metadata.dispatch_request_id) AS metadataCount
776
+ FROM orchestrator_dispatches AS dispatch
777
+ LEFT JOIN orchestrator_dispatch_metadata AS metadata
778
+ ON metadata.dispatch_request_id = dispatch.dispatch_request_id
779
+ WHERE dispatch.is_routed = 1
780
+ GROUP BY dispatch.dispatch_request_id
781
+ HAVING COUNT(metadata.dispatch_request_id) <> 1
782
+ LIMIT 1`).all())[0];
783
+ if (invalidRoutedMetadata !== undefined) {
784
+ throw new WorkStoreError({
785
+ cause: invalidRoutedMetadata,
786
+ operation: "open.migrate.routed-metadata"
787
+ });
788
+ }
795
789
  }
796
790
  };
797
- const verifyPathIdentity = (path, fileSystem, paths, operation) => fileSystem.readLink(path).pipe(Effect.matchEffect({
798
- onFailure: (cause) => cause.reason._tag === "NotFound" || cause.reason._tag === "Unknown"
799
- ? Effect.void
800
- : Effect.fail(new WorkStoreError({ cause, operation: `${operation}.readlink` })),
801
- onSuccess: (target) => Effect.fail(new WorkStoreError({ cause: { path, target }, operation }))
802
- }), Effect.andThen(fileSystem.exists(path).pipe(Effect.mapError(storeError(`${operation}.exists`)))), Effect.flatMap((exists) => {
803
- if (!exists) {
804
- const parent = paths.dirname(path);
805
- return parent === path ? Effect.void : verifyPathIdentity(parent, fileSystem, paths, operation);
806
- }
807
- const parent = paths.dirname(path);
808
- return Effect.all({
809
- realPath: fileSystem.realPath(path).pipe(Effect.mapError(storeError(`${operation}.realpath`))),
810
- realParentPath: fileSystem.realPath(parent).pipe(Effect.mapError(storeError(`${operation}.parent-realpath`)))
811
- }).pipe(Effect.flatMap(({ realParentPath, realPath }) => {
812
- const expectedPath = paths.join(realParentPath, paths.basename(path));
813
- return realPath === expectedPath
814
- ? Effect.void
815
- : Effect.fail(new WorkStoreError({ cause: { expectedPath, path, realPath }, operation }));
816
- }));
817
- }));
818
791
  const readTransactionLedgerTotals = (database) => Schema.decodeUnknownSync(TransactionLedgerTotalsRow)(database.prepare(`SELECT transaction_count AS transactionCount, transaction_bytes AS transactionBytes
819
792
  FROM work_goal_transaction_totals WHERE singleton = 1`).get());
820
793
  const readDecisionLedgerTotals = (database) => Schema.decodeUnknownSync(DecisionLedgerTotalsRow)(database.prepare(`SELECT decision_count AS decisionCount, decision_bytes AS decisionBytes
@@ -1216,21 +1189,27 @@ const readRecoveryState = (database, target) => {
1216
1189
  export class WorkStore {
1217
1190
  #database;
1218
1191
  #cryptoService;
1219
- #fileSystem;
1220
- #paths;
1192
+ #secureFiles;
1221
1193
  path;
1222
- constructor(path, fileSystem, paths, cryptoService) {
1194
+ constructor(path, opened, cryptoService) {
1223
1195
  this.path = path;
1224
- this.#fileSystem = fileSystem;
1225
- this.#paths = paths;
1196
+ this.#database = opened.database;
1197
+ this.#secureFiles = opened.secureFiles.pipe(Effect.mapError(fromPrivateDatabaseError));
1226
1198
  this.#cryptoService = cryptoService;
1227
- this.#database = new DatabaseSync(path);
1228
- try {
1229
- this.#database.exec(`PRAGMA busy_timeout = ${workStoreBusyTimeoutMillis}`);
1230
- migrateLegacyAuthorityTables(this.#database);
1231
- const hadLaneOperationLedger = this.#database.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'work_lane_operations'").get() !== undefined;
1232
- this.#database.exec(`
1233
- PRAGMA journal_mode = WAL;
1199
+ }
1200
+ static open = Effect.fn("WorkStore.open")(function* (path) {
1201
+ const cryptoService = yield* Crypto.Crypto;
1202
+ const opened = yield* openPrivateSqlite(path, {
1203
+ busyTimeoutMillis: workStoreBusyTimeoutMillis,
1204
+ initialize: (database) => {
1205
+ // One transaction: legacy migration and schema creation succeed or fail
1206
+ // together, and their statements share one commit instead of syncing
1207
+ // the file once each.
1208
+ database.exec("BEGIN IMMEDIATE");
1209
+ try {
1210
+ migrateLegacyAuthorityTables(database);
1211
+ const hadLaneOperationLedger = database.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'work_lane_operations'").get() !== undefined;
1212
+ database.exec(`
1234
1213
  CREATE TABLE IF NOT EXISTS work_goal_events (
1235
1214
  event_id TEXT PRIMARY KEY,
1236
1215
  goal_id TEXT NOT NULL,
@@ -1336,19 +1315,19 @@ export class WorkStore {
1336
1315
  CREATE INDEX IF NOT EXISTS work_decision_handoffs_session
1337
1316
  ON work_decision_handoffs (session_id);
1338
1317
  `);
1339
- const columns = Schema.decodeUnknownSync(Schema.Array(Schema.Struct({ name: Schema.String })))(this.#database.prepare("PRAGMA table_info(work_goal_events)").all());
1340
- if (!columns.some(({ name }) => name === "transaction_id")) {
1341
- this.#database.exec("ALTER TABLE work_goal_events ADD COLUMN transaction_id TEXT");
1342
- }
1343
- if (!hadLaneOperationLedger) {
1344
- this.#database.exec(`
1318
+ const columns = Schema.decodeUnknownSync(Schema.Array(Schema.Struct({ name: Schema.String })))(database.prepare("PRAGMA table_info(work_goal_events)").all());
1319
+ if (!columns.some(({ name }) => name === "transaction_id")) {
1320
+ database.exec("ALTER TABLE work_goal_events ADD COLUMN transaction_id TEXT");
1321
+ }
1322
+ if (!hadLaneOperationLedger) {
1323
+ database.exec(`
1345
1324
  INSERT OR IGNORE INTO work_lane_operations
1346
1325
  (operation_id, lane_id, goal_id, phase, revision, record)
1347
1326
  SELECT operation_id, lane_id, goal_id, phase, revision, record
1348
1327
  FROM work_lane_claims
1349
1328
  `);
1350
- }
1351
- this.#database.exec(`
1329
+ }
1330
+ database.exec(`
1352
1331
  INSERT OR IGNORE INTO work_lane_operation_totals
1353
1332
  (singleton, operation_count, operation_bytes)
1354
1333
  SELECT 1, COUNT(*), COALESCE(SUM(
@@ -1356,7 +1335,7 @@ export class WorkStore {
1356
1335
  ), 0)
1357
1336
  FROM work_lane_operations
1358
1337
  `);
1359
- this.#database.exec(`
1338
+ database.exec(`
1360
1339
  INSERT OR IGNORE INTO work_goal_transaction_totals
1361
1340
  (singleton, transaction_count, transaction_bytes)
1362
1341
  SELECT 1, COUNT(*), COALESCE(SUM(
@@ -1364,7 +1343,7 @@ export class WorkStore {
1364
1343
  ), 0)
1365
1344
  FROM work_goal_transactions
1366
1345
  `);
1367
- this.#database.exec(`
1346
+ database.exec(`
1368
1347
  INSERT OR REPLACE INTO work_decision_totals
1369
1348
  (singleton, decision_count, decision_bytes)
1370
1349
  SELECT 1, COUNT(*), COALESCE(SUM(
@@ -1372,46 +1351,18 @@ export class WorkStore {
1372
1351
  ), 0)
1373
1352
  FROM work_decision_handoffs
1374
1353
  `);
1375
- }
1376
- catch (error) {
1377
- this.#database.close();
1378
- throw error;
1379
- }
1380
- }
1381
- static open = Effect.fn("WorkStore.open")(function* (path) {
1382
- const cryptoService = yield* Crypto.Crypto;
1383
- const fileSystem = yield* FileSystem.FileSystem;
1384
- const paths = yield* Path.Path;
1385
- const directory = paths.dirname(path);
1386
- const directoryExists = yield* fileSystem.exists(directory).pipe(Effect.mapError(storeError("open.directory.exists")));
1387
- yield* fileSystem.makeDirectory(directory, { recursive: true, mode: 0o700 }).pipe(Effect.mapError(storeError("open.directory")));
1388
- if (!directoryExists) {
1389
- yield* fileSystem.chmod(directory, 0o700).pipe(Effect.mapError(storeError("open.secureDirectory")));
1390
- }
1391
- const directoryInfo = yield* fileSystem.stat(directory).pipe(Effect.mapError(storeError("open.directory.stat")));
1392
- if (directoryInfo.type !== "Directory" || (paths.sep === "/" && (directoryInfo.mode & 0o022) !== 0)) {
1393
- return yield* new WorkStoreError({
1394
- cause: { directory, mode: directoryInfo.mode, type: directoryInfo.type },
1395
- operation: "open.directory.unsafe"
1396
- });
1397
- }
1398
- yield* verifyPathIdentity(directory, fileSystem, paths, "open.directory.path-identity");
1399
- yield* Effect.forEach([path, `${path}-wal`, `${path}-shm`], (file) => verifyPathIdentity(file, fileSystem, paths, "open.path-identity").pipe(Effect.andThen(fileSystem.exists(file).pipe(Effect.mapError(storeError("open.file.exists")))), Effect.flatMap((exists) => {
1400
- if (!exists)
1401
- return Effect.void;
1402
- return fileSystem.stat(file).pipe(Effect.mapError(storeError("open.file.stat")), Effect.flatMap((info) => info.type !== "File" || (paths.sep === "/" && (info.mode & 0o022) !== 0)
1403
- ? Effect.fail(new WorkStoreError({
1404
- cause: { file, mode: info.mode, type: info.type },
1405
- operation: "open.file.unsafe"
1406
- }))
1407
- : fileSystem.chmod(file, 0o600).pipe(Effect.mapError(storeError("open.file.secure")))));
1408
- })), { discard: true });
1409
- const store = yield* Effect.try({
1410
- try: () => new WorkStore(path, fileSystem, paths, cryptoService),
1411
- catch: storeError("open.database")
1412
- });
1413
- yield* store.secureFiles();
1414
- return store;
1354
+ database.exec("COMMIT");
1355
+ }
1356
+ catch (error) {
1357
+ // SQLite may already have rolled back (SQLITE_FULL, IOERR); a second
1358
+ // ROLLBACK would throw and replace the real error.
1359
+ if (database.isTransaction)
1360
+ database.exec("ROLLBACK");
1361
+ throw error;
1362
+ }
1363
+ }
1364
+ }).pipe(Effect.mapError(fromPrivateDatabaseError));
1365
+ return new WorkStore(path, opened, cryptoService);
1415
1366
  });
1416
1367
  recoveryPreflight = Effect.fn("WorkStore.recoveryPreflight")(function* (target) {
1417
1368
  const decoded = yield* Schema.decodeUnknownEffect(WorkRecoveryTarget)(target).pipe(Effect.mapError(storeError("recovery.preflight.decode")));
@@ -3460,10 +3411,7 @@ export class WorkStore {
3460
3411
  return { events, logicalObservedAt };
3461
3412
  });
3462
3413
  secureFiles() {
3463
- const files = [this.path, `${this.path}-wal`, `${this.path}-shm`];
3464
- return Effect.forEach(files, (path) => verifyPathIdentity(path, this.#fileSystem, this.#paths, "secure.path-identity").pipe(Effect.andThen(this.#fileSystem.exists(path).pipe(Effect.mapError(storeError("secure.exists")))), Effect.flatMap((exists) => exists
3465
- ? this.#fileSystem.chmod(path, 0o600).pipe(Effect.mapError(storeError("secure.chmod")))
3466
- : Effect.void)), { discard: true });
3414
+ return this.#secureFiles;
3467
3415
  }
3468
3416
  close() {
3469
3417
  this.#database.close();