@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.d.ts +2 -2
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +629 -681
- package/dist/store.js.map +1 -1
- package/package.json +3 -3
package/dist/store.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { agentConnectTarget, fleetResponseBodyMaxBytes, workReassignActivitySummary } from "@knpkv/herdr-fleet";
|
|
2
|
-
import {
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
?
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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 =
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if (
|
|
206
|
-
|
|
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
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
|
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 (
|
|
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: {
|
|
262
|
-
operation:
|
|
438
|
+
cause: { bindingRows, dispatch, legacy },
|
|
439
|
+
operation: "open.migrate.legacy-handoff-revision"
|
|
263
440
|
});
|
|
264
441
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
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:
|
|
294
|
-
operation:
|
|
453
|
+
cause: { binding: bindingDecision.binding, lane, legacy },
|
|
454
|
+
operation: "open.migrate.legacy-handoff-lane-revision"
|
|
295
455
|
});
|
|
296
456
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
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
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
:
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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: {
|
|
408
|
-
operation: "open.migrate.
|
|
523
|
+
cause: { handoff: previous.value, row },
|
|
524
|
+
operation: "open.migrate.handoff-identity"
|
|
409
525
|
});
|
|
410
526
|
}
|
|
411
|
-
if (
|
|
527
|
+
if (handoffDispatches.length > 1) {
|
|
412
528
|
throw new WorkStoreError({
|
|
413
|
-
cause: {
|
|
414
|
-
operation: "open.migrate.
|
|
529
|
+
cause: { handoffDispatches, row },
|
|
530
|
+
operation: "open.migrate.dispatch-cardinality"
|
|
415
531
|
});
|
|
416
532
|
}
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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: {
|
|
423
|
-
operation: "open.migrate.
|
|
544
|
+
cause: { handoffDispatches, linkedMetadata, row },
|
|
545
|
+
operation: "open.migrate.metadata-authority"
|
|
424
546
|
});
|
|
425
547
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|
-
|
|
577
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 (!
|
|
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
|
|
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
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
if (
|
|
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:
|
|
717
|
-
operation: "open.migrate.
|
|
675
|
+
cause: { dispatch, dispatchHandoff, previous },
|
|
676
|
+
operation: "open.migrate.dispatch-authority"
|
|
718
677
|
});
|
|
719
678
|
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
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
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
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
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
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
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
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:
|
|
784
|
-
operation: "open.migrate.
|
|
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
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
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
|
-
#
|
|
1220
|
-
#paths;
|
|
1192
|
+
#secureFiles;
|
|
1221
1193
|
path;
|
|
1222
|
-
constructor(path,
|
|
1194
|
+
constructor(path, opened, cryptoService) {
|
|
1223
1195
|
this.path = path;
|
|
1224
|
-
this.#
|
|
1225
|
-
this.#
|
|
1196
|
+
this.#database = opened.database;
|
|
1197
|
+
this.#secureFiles = opened.secureFiles.pipe(Effect.mapError(fromPrivateDatabaseError));
|
|
1226
1198
|
this.#cryptoService = cryptoService;
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
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
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
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
|
-
|
|
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();
|