@knpkv/herdr-work 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +95 -1
  2. package/dist/agent-binding.d.ts +4 -0
  3. package/dist/agent-binding.d.ts.map +1 -0
  4. package/dist/agent-binding.js +26 -0
  5. package/dist/agent-binding.js.map +1 -0
  6. package/dist/errors.d.ts +141 -0
  7. package/dist/errors.d.ts.map +1 -1
  8. package/dist/errors.js +97 -0
  9. package/dist/errors.js.map +1 -1
  10. package/dist/index.d.ts +4 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +4 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/internal/agent-binding-admission.d.ts +15 -0
  15. package/dist/internal/agent-binding-admission.d.ts.map +1 -0
  16. package/dist/internal/agent-binding-admission.js +92 -0
  17. package/dist/internal/agent-binding-admission.js.map +1 -0
  18. package/dist/internal/agent-binding-readback.d.ts +55 -0
  19. package/dist/internal/agent-binding-readback.d.ts.map +1 -0
  20. package/dist/internal/agent-binding-readback.js +124 -0
  21. package/dist/internal/agent-binding-readback.js.map +1 -0
  22. package/dist/internal/coordinator-authority.d.ts +62 -0
  23. package/dist/internal/coordinator-authority.d.ts.map +1 -0
  24. package/dist/internal/coordinator-authority.js +214 -0
  25. package/dist/internal/coordinator-authority.js.map +1 -0
  26. package/dist/internal/decision-handoff-migration.d.ts +126 -0
  27. package/dist/internal/decision-handoff-migration.d.ts.map +1 -0
  28. package/dist/internal/decision-handoff-migration.js +51 -0
  29. package/dist/internal/decision-handoff-migration.js.map +1 -0
  30. package/dist/internal/history-validation.d.ts +5 -0
  31. package/dist/internal/history-validation.d.ts.map +1 -0
  32. package/dist/internal/history-validation.js +33 -0
  33. package/dist/internal/history-validation.js.map +1 -0
  34. package/dist/model.d.ts +3075 -51
  35. package/dist/model.d.ts.map +1 -1
  36. package/dist/model.js +334 -12
  37. package/dist/model.js.map +1 -1
  38. package/dist/navigation.d.ts +17 -0
  39. package/dist/navigation.d.ts.map +1 -0
  40. package/dist/navigation.js +62 -0
  41. package/dist/navigation.js.map +1 -0
  42. package/dist/projection.d.ts +644 -0
  43. package/dist/projection.d.ts.map +1 -1
  44. package/dist/projection.js +65 -30
  45. package/dist/projection.js.map +1 -1
  46. package/dist/service.d.ts +1870 -3
  47. package/dist/service.d.ts.map +1 -1
  48. package/dist/service.js +138 -5
  49. package/dist/service.js.map +1 -1
  50. package/dist/sql.d.ts +30 -0
  51. package/dist/sql.d.ts.map +1 -0
  52. package/dist/sql.js +1454 -0
  53. package/dist/sql.js.map +1 -0
  54. package/dist/store.d.ts +1068 -4
  55. package/dist/store.d.ts.map +1 -1
  56. package/dist/store.js +3309 -26
  57. package/dist/store.js.map +1 -1
  58. package/dist/view.d.ts.map +1 -1
  59. package/dist/view.js +154 -8
  60. package/dist/view.js.map +1 -1
  61. package/package.json +25 -13
  62. package/src/styles.css +91 -17
package/dist/store.js CHANGED
@@ -1,46 +1,1234 @@
1
- import { fleetResponseBodyMaxBytes } from "@knpkv/herdr-fleet";
2
- import { Effect, Equal, FileSystem, Path, Schema } from "effect";
1
+ import { agentConnectTarget, fleetResponseBodyMaxBytes, workReassignActivitySummary } from "@knpkv/herdr-fleet";
2
+ import { Clock, Crypto, Effect, Equal, FileSystem, Option, Path, Schema } from "effect";
3
+ import { Hex } from "effect/encoding";
3
4
  import { DatabaseSync } from "node:sqlite";
4
- import { WorkCheckpointConflictError, WorkProjectionError, WorkStoreError } from "./errors.js";
5
+ import { makeWorkAgentBinding } from "./agent-binding.js";
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";
5
7
  import { validateGoalFamilyHistory } from "./goal-family.js";
6
- import { WorkGoalCheckpoint, workHistoryMaxEvents, workSnapshotMaxGoals } from "./model.js";
8
+ import { agentBindingAdmissionError, workAgentBindingLaneOperationMaxBytes, workAgentBindingLaneOperationMaxRecords, workAgentBindingMaximumSnapshotBytes, workAgentBindingSnapshotEnvelopeMaxBytes, workMaximumSnapshotBytesForHistory } from "./internal/agent-binding-admission.js";
9
+ import { AgentBindingGoalEventRow, AgentBindingLaneOperationRow, agentBindingReadbackError, AgentBindingRow, decodeAgentBindingGoalEvent, decodeAgentBindingRow, laneOperationReadbackError } from "./internal/agent-binding-readback.js";
10
+ import { CoordinatorCommandRow, CoordinatorLifecycleDispatchRow, CoordinatorLifecycleEventRow, coordinatorLifecycleRunningAt, CoordinatorRouteDiscriminatorRow, coordinatorRouteRequiresWorkLink, requireCoordinatorFailedLunaAuthority, requireCoordinatorLifecycleAuthority, requireCoordinatorRouteAuthority, requireCoordinatorRouteBinding } from "./internal/coordinator-authority.js";
11
+ import { currentDecisionHandoffEquivalent, CurrentMetadataWorkLink, decodePreviousDecisionHandoff, previousDecisionHandoffEquivalent, PreviousWorkDecisionHandoff, upgradePreviousDecisionHandoff, workDispatchLineageContainedBy, workDispatchLineageEquivalent } from "./internal/decision-handoff-migration.js";
12
+ import { workHistoryError } from "./internal/history-validation.js";
13
+ import { WorkAdmissionTarget, WorkAgentBinding, WorkAgentBindingRequest, WorkCoordinatorSessionId, WorkDecisionHandoff, WorkDispatchHandoff, WorkExistingGoalRecovery, WorkGoal, WorkGoalCheckpoint, WorkGoalId, WorkGoalReassigned, WorkGoalReassignment, workHistoryMaxEvents, WorkLaneClaim, WorkLaneClaimed, WorkProspectiveAdmission, WorkPullRequestLink, WorkRecoveryTarget, workSnapshotMaxGoals } from "./model.js";
7
14
  const StoredEventRow = Schema.Struct({ record: Schema.String });
8
15
  const StoredEventRows = Schema.Array(StoredEventRow);
16
+ const StoredEventWithTransactionRow = Schema.Struct({
17
+ eventId: Schema.String,
18
+ goalId: Schema.String,
19
+ occurredAt: Schema.Number,
20
+ record: Schema.String,
21
+ transactionId: Schema.NullOr(Schema.String)
22
+ });
23
+ const StoredEventWithTransactionRows = Schema.Array(StoredEventWithTransactionRow);
24
+ const TransactionRow = Schema.Struct({ record: Schema.String });
25
+ const TransactionEventIdentity = Schema.Struct({
26
+ eventId: Schema.String,
27
+ goalId: Schema.String,
28
+ occurredAt: Schema.Number
29
+ });
30
+ const LegacyCompactTransactionRecord = Schema.Struct({
31
+ events: Schema.Array(TransactionEventIdentity),
32
+ version: Schema.Literal("herdr.work.transaction.v1")
33
+ });
34
+ const CompactTransactionRecord = Schema.Struct({
35
+ digest: Schema.String.check(Schema.isPattern(/^[a-f0-9]{64}$/)),
36
+ version: Schema.Literal("herdr.work.transaction.v3")
37
+ });
38
+ const PreviousCompactTransactionRecord = Schema.Struct({
39
+ digest: Schema.String.check(Schema.isPattern(/^[a-f0-9]{64}$/)),
40
+ version: Schema.Literal("herdr.work.transaction.v2")
41
+ });
42
+ const LaneRow = Schema.Struct({
43
+ goalId: Schema.String,
44
+ laneId: Schema.String,
45
+ operationId: Schema.String,
46
+ phase: Schema.String,
47
+ record: Schema.String,
48
+ revision: Schema.Number
49
+ });
50
+ const DecisionRow = Schema.Struct({
51
+ handoffId: Schema.String,
52
+ sessionId: Schema.String,
53
+ laneId: Schema.String,
54
+ occurredAt: Schema.Number,
55
+ record: Schema.String
56
+ });
57
+ const DecisionIdentityRow = Schema.Struct({ handoffId: Schema.String });
58
+ const AgentBindingRows = Schema.Array(AgentBindingRow);
9
59
  const CountRow = Schema.Struct({ count: Schema.Number });
60
+ const DecisionLedgerTotalsRow = Schema.Struct({
61
+ decisionBytes: Schema.Number,
62
+ decisionCount: Schema.Number
63
+ });
64
+ const LedgerBytesRow = Schema.Struct({ bytes: Schema.Number });
65
+ const LaneOperationLedgerTotalsRow = Schema.Struct({
66
+ operationBytes: Schema.Number,
67
+ operationCount: Schema.Number
68
+ });
69
+ const TransactionLedgerTotalsRow = Schema.Struct({
70
+ transactionBytes: Schema.Number,
71
+ transactionCount: Schema.Number
72
+ });
73
+ const LegacyLaneStoredRow = Schema.Struct({ laneId: Schema.String, record: Schema.String, revision: Schema.Number });
74
+ const LegacyLaneRecord = Schema.Struct({
75
+ laneId: Schema.String,
76
+ worktree: Schema.String,
77
+ branch: Schema.String,
78
+ head: Schema.String,
79
+ owner: Schema.Struct({ id: Schema.String, name: Schema.String }),
80
+ parent: Schema.NullOr(Schema.String),
81
+ phase: Schema.String,
82
+ expectedRevision: Schema.Number,
83
+ revision: Schema.Number
84
+ });
85
+ const LegacyDecisionStoredRow = Schema.Struct({ handoffId: Schema.String, record: Schema.String });
86
+ const LegacyDispatchStoredRow = Schema.Struct({
87
+ dispatchRequestId: Schema.String,
88
+ handoffId: Schema.String,
89
+ laneId: Schema.String,
90
+ occurredAt: Schema.Number,
91
+ lineage: Schema.String,
92
+ record: Schema.String
93
+ });
94
+ const LaneRevisionRow = Schema.Struct({ revision: Schema.Number });
95
+ const MetadataWorkLinkRow = Schema.Struct({
96
+ dispatchRequestId: Schema.String,
97
+ route: Schema.String,
98
+ workLink: Schema.NullOr(Schema.String)
99
+ });
100
+ const MetadataHandoffIdentityRow = Schema.Struct({
101
+ dispatchRequestId: Schema.String,
102
+ hasWorkLink: Schema.Literals([0, 1]),
103
+ handoffId: Schema.NullOr(Schema.String),
104
+ route: Schema.NullOr(Schema.String)
105
+ });
106
+ const RoutedMetadataCardinalityRow = Schema.Struct({
107
+ dispatchRequestId: Schema.String,
108
+ metadataCount: Schema.Number
109
+ });
110
+ const CoordinatorMetadataRouteRow = Schema.Struct({
111
+ activityIdempotencyKey: Schema.NullOr(Schema.String),
112
+ command: Schema.NullOr(Schema.String),
113
+ dispatchRequestId: Schema.String,
114
+ hasWorkLink: Schema.Literals([0, 1]),
115
+ isRouted: Schema.NullOr(Schema.Literals([0, 1])),
116
+ route: Schema.NullOr(Schema.String),
117
+ rowId: Schema.String
118
+ });
119
+ const LegacyDecisionRecord = Schema.Struct({
120
+ version: Schema.Literal("herdr.work.decision.v1"),
121
+ id: Schema.String,
122
+ laneId: Schema.String,
123
+ goalId: Schema.String,
124
+ decision: Schema.String,
125
+ summary: Schema.String,
126
+ owner: Schema.Struct({ id: Schema.String, name: Schema.String }),
127
+ occurredAt: Schema.Number
128
+ });
129
+ const PreviousMetadataWorkLink = Schema.Struct({
130
+ handoff: PreviousWorkDecisionHandoff,
131
+ lineage: WorkDispatchHandoff.fields.lineage
132
+ });
133
+ const LegacyMetadataWorkLink = Schema.Struct({
134
+ handoff: LegacyDecisionRecord,
135
+ lineage: WorkDispatchHandoff.fields.lineage
136
+ });
137
+ const legacyDecisionEquivalent = Schema.toEquivalence(LegacyDecisionRecord);
138
+ const TransactionId = Schema.String.check(Schema.isNonEmpty(), Schema.isMaxLength(256), Schema.isPattern(/^(?:[^\uD800-\uDFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF])*$/));
10
139
  const storeError = (operation) => (cause) => new WorkStoreError({ cause, operation });
140
+ 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")
151
+ : [];
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,
155
+ 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))
191
+ });
192
+ const routeStorageAuthority = (dispatchRequestId, operation) => {
193
+ if (!coordinatorDispatchColumns.includes("is_routed")) {
194
+ return { _tag: "legacy_without_routed_discriminator" };
195
+ }
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 });
201
+ }
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 });
212
+ }
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 });
222
+ }
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
+ const bindingRows = tables.includes("work_agent_bindings")
236
+ ? Schema.decodeUnknownSync(Schema.Array(AgentBindingRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, lane_id AS laneId,
237
+ expected_revision AS expectedRevision, revision, agent_id AS agentId, host, record
238
+ FROM work_agent_bindings WHERE dispatch_request_id = ? LIMIT 2`).all(dispatchRequestId))
239
+ : [];
240
+ if (runningAt === null && bindingRows.length === 0)
241
+ return;
242
+ 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) {
260
+ throw new WorkStoreError({
261
+ cause: { binding: decoded.binding, currentLaneRows },
262
+ operation: `${operation}.lane-revision`
263
+ });
264
+ }
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` });
271
+ }
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) {
292
+ throw new WorkStoreError({
293
+ cause: { binding: decoded.binding, currentLane, currentLaneRow, currentOperationError },
294
+ operation: `${operation}.lane-revision`
295
+ });
296
+ }
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"
311
+ });
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
+ }
335
+ 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
+ });
357
+ }
358
+ }
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) {
406
+ throw new WorkStoreError({
407
+ cause: { dispatch, metadataRows },
408
+ operation: "open.migrate.legacy-metadata-authority"
409
+ });
410
+ }
411
+ if (metadata.workLink === null) {
412
+ throw new WorkStoreError({
413
+ cause: { dispatch, metadata },
414
+ operation: "open.migrate.legacy-metadata-authority"
415
+ });
416
+ }
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)) {
421
+ throw new WorkStoreError({
422
+ cause: { legacy, workLink },
423
+ operation: "open.migrate.legacy-metadata-authority"
424
+ });
425
+ }
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
+ });
575
+ }
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
+ });
591
+ }
592
+ requireCurrentAgentBindingAuthority(dispatch.dispatchRequestId, previous.value, "open.migrate.current-agent-binding");
593
+ }
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")) {
678
+ const metadata = linkedMetadata.find(({ dispatchRequestId }) => dispatchRequestId === dispatch.dispatchRequestId);
679
+ if (metadata?.workLink === null || metadata?.workLink === undefined) {
680
+ throw new WorkStoreError({
681
+ cause: { dispatch, metadata },
682
+ operation: "open.migrate.metadata-authority"
683
+ });
684
+ }
685
+ const workLink = Schema.decodeUnknownSync(PreviousMetadataWorkLink)(JSON.parse(metadata.workLink));
686
+ requireMetadataAuthority(dispatch.dispatchRequestId, metadata.route, workLink.lineage, "open.migrate.metadata-authority");
687
+ if (!previousDecisionHandoffEquivalent(workLink.handoff, previous) ||
688
+ !workDispatchLineageEquivalent(workLink.lineage, lineage)) {
689
+ throw new WorkStoreError({
690
+ cause: { previous, workLink },
691
+ operation: "open.migrate.metadata-authority"
692
+ });
693
+ }
694
+ }
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);
707
+ }
708
+ }
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) {
715
+ throw new WorkStoreError({
716
+ cause: migratedLedger,
717
+ operation: "open.migrate.handoff-capacity"
718
+ });
719
+ }
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");
760
+ }
761
+ catch (cause) {
762
+ if (Schema.is(WorkStoreError)(cause))
763
+ throw cause;
764
+ throw new WorkStoreError({ cause, operation: "open.migrate.metadata-route" });
765
+ }
766
+ }
767
+ const last = rows.at(-1);
768
+ if (rows.length < pageSize || last === undefined)
769
+ break;
770
+ cursor = last.rowId;
771
+ }
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) {
782
+ throw new WorkStoreError({
783
+ cause: invalidRoutedMetadata,
784
+ operation: "open.migrate.routed-metadata"
785
+ });
786
+ }
787
+ }
788
+ database.exec("COMMIT");
789
+ transaction = false;
790
+ }
791
+ catch (error) {
792
+ if (transaction)
793
+ database.exec("ROLLBACK");
794
+ throw error;
795
+ }
796
+ };
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
+ const readTransactionLedgerTotals = (database) => Schema.decodeUnknownSync(TransactionLedgerTotalsRow)(database.prepare(`SELECT transaction_count AS transactionCount, transaction_bytes AS transactionBytes
819
+ FROM work_goal_transaction_totals WHERE singleton = 1`).get());
820
+ const readDecisionLedgerTotals = (database) => Schema.decodeUnknownSync(DecisionLedgerTotalsRow)(database.prepare(`SELECT decision_count AS decisionCount, decision_bytes AS decisionBytes
821
+ FROM work_decision_totals WHERE singleton = 1`).get());
822
+ const readLaneOperationLedgerTotals = (database) => Schema.decodeUnknownSync(LaneOperationLedgerTotalsRow)(database.prepare(`SELECT operation_count AS operationCount, operation_bytes AS operationBytes
823
+ FROM work_lane_operation_totals WHERE singleton = 1`).get());
824
+ const ReassignmentRow = Schema.Struct({ approvalJobId: Schema.String, goalId: Schema.String, record: Schema.String });
825
+ /** Applies a reassignment's agent choice; `keep` is only reached when the goal has no target. */
826
+ const withReassignedAgent = (goal, toAgent) => {
827
+ switch (toAgent._tag) {
828
+ case "set":
829
+ return { ...goal, agentHierarchy: { agent: toAgent.agent }, connectTarget: agentConnectTarget(toAgent.agent) };
830
+ case "clear":
831
+ return { ...goal, agentHierarchy: null, connectTarget: null };
832
+ case "keep":
833
+ return goal;
834
+ }
835
+ };
11
836
  const utf8 = new TextEncoder();
12
837
  const encodedBytes = (value) => utf8.encode(JSON.stringify(value)).byteLength;
13
838
  const maximumTimestamp = 8_640_000_000_000_000;
14
- const workSnapshotEnvelopeMaxBytes = encodedBytes({
15
- observedAt: maximumTimestamp,
16
- now: { window: "now", observedAt: maximumTimestamp, asOf: maximumTimestamp, goals: [] },
17
- day: { window: "day", observedAt: maximumTimestamp, asOf: maximumTimestamp, goals: [] },
18
- week: { window: "week", observedAt: maximumTimestamp, asOf: maximumTimestamp, goals: [] },
19
- month: { window: "month", observedAt: maximumTimestamp, asOf: maximumTimestamp, goals: [] }
20
- });
21
- const maximumSnapshotBytes = (history, candidate) => {
22
- const maximumGoalBytes = new Map();
23
- for (const { goal } of [...history, candidate]) {
24
- const bytes = encodedBytes(goal);
25
- maximumGoalBytes.set(goal.id, Math.max(maximumGoalBytes.get(goal.id) ?? 0, bytes));
26
- }
27
- const encodedGoals = [...maximumGoalBytes.values()].reduce((total, bytes) => total + bytes, 0);
28
- const separators = Math.max(0, maximumGoalBytes.size - 1);
29
- return workSnapshotEnvelopeMaxBytes + 4 * (encodedGoals + separators);
30
- };
839
+ const workTransactionMaxRecords = 16_384;
840
+ const workTransactionMaxBytes = 2 * 1024 * 1024;
841
+ const workLaneMaxRecords = workSnapshotMaxGoals;
842
+ const workLaneMaxBytes = 2 * 1024 * 1024;
843
+ const workLaneOperationMaxRecords = workAgentBindingLaneOperationMaxRecords;
844
+ const workLaneOperationMaxBytes = workAgentBindingLaneOperationMaxBytes;
845
+ const workDecisionMaxRecords = 16_384;
846
+ const workDecisionMaxBytes = 2 * 1024 * 1024;
847
+ const workStoreBusyTimeoutMillis = 5_000;
848
+ const workSnapshotEnvelopeMaxBytes = workAgentBindingSnapshotEnvelopeMaxBytes;
849
+ const maximumSnapshotBytes = workAgentBindingMaximumSnapshotBytes;
850
+ const transactionContent = (events) => JSON.stringify(events);
851
+ export const __herdrWorkMaximumSnapshotBytesForTest = maximumSnapshotBytes;
852
+ export const __herdrWorkEncodedBytesForTest = encodedBytes;
853
+ export const __herdrWorkSnapshotEnvelopeMaxBytesForTest = workSnapshotEnvelopeMaxBytes;
854
+ export const __herdrWorkLaneOperationMaxBytesForTest = workLaneOperationMaxBytes;
31
855
  const decodeRow = (row) => Schema.decodeUnknownEffect(StoredEventRow)(row).pipe(Effect.mapError(storeError("decode.row")), Effect.flatMap(({ record }) => Effect.try({
32
856
  try: () => Schema.decodeUnknownSync(WorkGoalCheckpoint)(JSON.parse(record)),
33
857
  catch: storeError("decode.event")
34
858
  })));
859
+ const claimInputFromClaimed = (claim) => {
860
+ const input = {
861
+ branch: claim.branch,
862
+ expectedRevision: claim.expectedRevision,
863
+ goalId: claim.goalId,
864
+ head: claim.head,
865
+ laneId: claim.laneId,
866
+ operationId: claim.operationId,
867
+ owner: claim.owner,
868
+ parent: claim.parent,
869
+ phase: claim.phase
870
+ };
871
+ return claim.reconciliation === undefined
872
+ ? { ...input, worktree: claim.worktree }
873
+ : { ...input, reconciliation: claim.reconciliation, worktree: claim.worktree };
874
+ };
875
+ const readValidatedLaneLedger = (database, operation) => {
876
+ try {
877
+ const rows = Schema.decodeUnknownSync(Schema.Array(LaneRow))(database.prepare(`SELECT lane_id AS laneId, goal_id AS goalId, operation_id AS operationId,
878
+ phase, revision, record
879
+ FROM work_lane_claims ORDER BY lane_id ASC LIMIT ?`).all(workLaneMaxRecords + 1));
880
+ if (rows.length > workLaneMaxRecords) {
881
+ return {
882
+ _tag: "invalid",
883
+ error: new WorkStoreError({ cause: rows.length, operation: `${operation}.capacity` })
884
+ };
885
+ }
886
+ const entries = [];
887
+ for (const row of rows) {
888
+ const claim = Schema.decodeUnknownSync(WorkLaneClaimed)(JSON.parse(row.record));
889
+ if (claim.goalId !== row.goalId ||
890
+ claim.laneId !== row.laneId ||
891
+ claim.operationId !== row.operationId ||
892
+ claim.phase !== row.phase ||
893
+ claim.revision !== row.revision) {
894
+ return {
895
+ _tag: "invalid",
896
+ error: new WorkStoreError({ cause: { claim, row }, operation: `${operation}.identity-mismatch` })
897
+ };
898
+ }
899
+ entries.push({ claim, row });
900
+ }
901
+ return { _tag: "valid", entries };
902
+ }
903
+ catch (cause) {
904
+ return { _tag: "invalid", error: new WorkStoreError({ cause, operation: `${operation}.decode` }) };
905
+ }
906
+ };
907
+ const admissionConflict = (target, reason) => new WorkAdmissionConflictError({ goalId: target.goalId, laneId: target.laneId, reason });
908
+ /**
909
+ * Splits bindings into each lane's authoritative row (highest lane revision)
910
+ * and the lanes where that revision is shared by more than one row. Earlier
911
+ * rows on a lane were superseded, for example by an approved reassignment.
912
+ */
913
+ const authoritativeBindings = (bindings) => {
914
+ const maxRevision = new Map();
915
+ for (const binding of bindings) {
916
+ const laneId = binding.request.laneId;
917
+ maxRevision.set(laneId, Math.max(maxRevision.get(laneId) ?? 0, binding.lane.revision));
918
+ }
919
+ const latest = bindings.filter((binding) => binding.lane.revision === maxRevision.get(binding.request.laneId));
920
+ const ambiguousLanes = new Set(latest
921
+ .map(({ request }) => request.laneId)
922
+ .filter((laneId, index, laneIds) => laneIds.indexOf(laneId) !== index));
923
+ return { latest, ambiguousLanes };
924
+ };
925
+ /** Reads every durable identity row, including entries omitted by time-window projections. */
926
+ const admissionState = (database, target) => {
927
+ const eventRows = Schema.decodeUnknownSync(Schema.Array(AgentBindingGoalEventRow))(database.prepare(`SELECT event_id AS eventId, goal_id AS goalId, occurred_at AS occurredAt, record
928
+ FROM work_goal_events ORDER BY occurred_at ASC, event_id ASC LIMIT ?`).all(workHistoryMaxEvents + 1));
929
+ const lanes = readValidatedLaneLedger(database, "admission.inspect");
930
+ if (lanes._tag === "invalid")
931
+ throw lanes.error;
932
+ const bindingRows = Schema.decodeUnknownSync(AgentBindingRows)(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, lane_id AS laneId,
933
+ expected_revision AS expectedRevision, revision, agent_id AS agentId, host, record
934
+ FROM work_agent_bindings ORDER BY dispatch_request_id ASC LIMIT ?`).all(workLaneOperationMaxRecords + 1));
935
+ const operationRows = Schema.decodeUnknownSync(Schema.Array(AgentBindingLaneOperationRow))(database.prepare(`SELECT operation_id AS operationId, lane_id AS laneId, goal_id AS goalId,
936
+ phase, revision, record FROM work_lane_operations ORDER BY operation_id ASC LIMIT ?`).all(workLaneOperationMaxRecords + 1));
937
+ if (eventRows.length > workHistoryMaxEvents || bindingRows.length > workLaneOperationMaxRecords ||
938
+ operationRows.length > workLaneOperationMaxRecords) {
939
+ throw new WorkStoreError({ cause: target, operation: "admission.inspect.capacity" });
940
+ }
941
+ const events = eventRows.map((row) => {
942
+ const decision = decodeAgentBindingGoalEvent(row, "admission.inspect.event");
943
+ if (decision._tag === "invalid")
944
+ throw decision.error;
945
+ return decision.checkpoint;
946
+ });
947
+ const historyError = workHistoryError(events);
948
+ if (historyError !== undefined)
949
+ throw historyError;
950
+ const bindings = bindingRows.map((row) => {
951
+ const binding = Schema.decodeUnknownSync(WorkAgentBinding)(JSON.parse(row.record));
952
+ if (row.dispatchRequestId !== binding.request.dispatchRequestId ||
953
+ row.laneId !== binding.request.laneId || row.revision !== binding.lane.revision ||
954
+ row.agentId !== binding.request.worker.agentId ||
955
+ row.host.toLowerCase() !== binding.request.worker.host.toLowerCase()) {
956
+ throw new WorkStoreError({ cause: row, operation: "admission.inspect.binding-identity" });
957
+ }
958
+ const readback = agentBindingReadbackError(binding, operationRows.find(({ operationId }) => operationId === binding.lane.operationId), eventRows.find(({ eventId }) => eventId === binding.checkpoint.eventId), "admission.inspect.binding");
959
+ if (readback !== undefined)
960
+ throw readback;
961
+ return binding;
962
+ });
963
+ const latest = new Map();
964
+ for (const event of events)
965
+ latest.set(event.goal.id, event);
966
+ const goal = [...latest.values()].find(({ goal }) => goal.review?.url === target.reviewUrl);
967
+ const lane = lanes.entries.find(({ claim }) => claim.laneId === target.laneId)?.claim;
968
+ const authority = authoritativeBindings(bindings);
969
+ const binding = authority.latest.find(({ lane: bound }) => bound.laneId === target.laneId);
970
+ const foreignConflict = events.some(({ goal: entry }) => (entry.review?.url === target.reviewUrl && entry.id !== target.goalId) ||
971
+ (entry.agentHierarchy?.agent.agentId === target.worker.agentId && entry.id !== target.goalId) ||
972
+ (entry.goalFamily?.canonicalGoalId === target.goalId && entry.id !== target.goalId)) || lanes.entries.some(({ claim }) => (claim.goalId === target.goalId && claim.laneId !== target.laneId) ||
973
+ (claim.worktree === target.worktree && claim.laneId !== target.laneId)) ||
974
+ authority.latest.some(({ request }) => request.worker.agentId === target.worker.agentId && request.laneId !== target.laneId) ||
975
+ // A session id stays claimed by its lane even after a reassignment supersedes that binding.
976
+ bindings.some(({ request }) => (request.prospectiveAdmission?.sessionId === target.sessionId && request.laneId !== target.laneId) ||
977
+ (request.existingGoalRecovery?.sessionId === target.sessionId && request.laneId !== target.laneId)) || authority.ambiguousLanes.has(target.laneId);
978
+ const exact = goal !== undefined && lane !== undefined && binding !== undefined &&
979
+ goal.goal.id === target.goalId && goal.goal.state !== "completed" &&
980
+ goal.goal.state !== "deployed" && goal.goal.goalFamily?.role === "canonical" &&
981
+ goal.goal.goalFamily.canonicalGoalId === target.goalId &&
982
+ Equal.equals(goal.goal.owner, target.owner) &&
983
+ lane.goalId === target.goalId && lane.head === target.head &&
984
+ lane.worktree === target.worktree && lane.branch === target.branch &&
985
+ lane.phase !== "shipped" && Equal.equals(lane.owner, target.owner) &&
986
+ binding.lane.laneId === target.laneId &&
987
+ Equal.equals(binding.request.worker, target.worker) &&
988
+ (binding.request.prospectiveAdmission === undefined || (binding.request.prospectiveAdmission.sessionId === target.sessionId &&
989
+ binding.request.prospectiveAdmission.workAssignment === target.expectedWork &&
990
+ binding.request.prospectiveAdmission.baseHead === target.baseHead)) &&
991
+ (binding.request.existingGoalRecovery === undefined ||
992
+ (binding.request.existingGoalRecovery.sessionId === target.sessionId &&
993
+ binding.request.existingGoalRecovery.workAssignment === target.expectedWork &&
994
+ binding.request.existingGoalRecovery.baseHead === target.baseHead));
995
+ if (exact && !foreignConflict) {
996
+ return {
997
+ _tag: "existing",
998
+ target,
999
+ link: Schema.decodeUnknownSync(WorkPullRequestLink)({
1000
+ request: {
1001
+ repository: target.repository,
1002
+ pullRequest: target.pullRequest,
1003
+ goalId: target.goalId,
1004
+ laneId: target.laneId
1005
+ },
1006
+ goalEventId: goal.eventId,
1007
+ goal: goal.goal,
1008
+ lane,
1009
+ binding
1010
+ })
1011
+ };
1012
+ }
1013
+ const conflicts = events.some(({ goal: entry }) => entry.id === target.goalId || entry.review?.url === target.reviewUrl ||
1014
+ entry.goalFamily?.canonicalGoalId === target.goalId ||
1015
+ entry.agentHierarchy?.agent.agentId === target.worker.agentId) || lanes.entries.some(({ claim }) => claim.laneId === target.laneId || claim.goalId === target.goalId ||
1016
+ claim.worktree === target.worktree) || authority.latest.some(({ request }) => request.worker.agentId === target.worker.agentId) ||
1017
+ bindings.some(({ request }) => request.prospectiveAdmission?.sessionId === target.sessionId ||
1018
+ request.existingGoalRecovery?.sessionId === target.sessionId);
1019
+ if (conflicts) {
1020
+ return { _tag: "conflict", target, reason: "durable PR, goal, lane, worker, or worktree is already bound" };
1021
+ }
1022
+ const snapshot = JSON.stringify({
1023
+ target,
1024
+ eventRows,
1025
+ lanes: lanes.entries.map(({ row }) => row),
1026
+ bindingRows,
1027
+ operationRows
1028
+ });
1029
+ return { _tag: "prospective", target, snapshot };
1030
+ };
1031
+ const readAdmissionState = (database, target) => {
1032
+ database.exec("BEGIN");
1033
+ try {
1034
+ const state = admissionState(database, target);
1035
+ database.exec("ROLLBACK");
1036
+ return state;
1037
+ }
1038
+ catch (cause) {
1039
+ database.exec("ROLLBACK");
1040
+ throw cause;
1041
+ }
1042
+ };
1043
+ const recoveryTables = [
1044
+ "work_goal_events",
1045
+ "work_lane_claims",
1046
+ "work_lane_operations",
1047
+ "work_agent_bindings",
1048
+ "work_goal_transactions",
1049
+ "work_goal_transaction_totals",
1050
+ "work_lane_operation_totals",
1051
+ "work_decision_handoffs",
1052
+ "work_decision_totals"
1053
+ ];
1054
+ /** Validates every historical operation against its indexed row and current lane. */
1055
+ const readRecoveryOperations = (database) => {
1056
+ const lanes = readValidatedLaneLedger(database, "recovery.preflight");
1057
+ if (lanes._tag === "invalid")
1058
+ throw lanes.error;
1059
+ const rows = Schema.decodeUnknownSync(Schema.Array(AgentBindingLaneOperationRow))(database
1060
+ .prepare(`SELECT operation_id AS operationId, lane_id AS laneId, goal_id AS goalId,
1061
+ phase, revision, record FROM work_lane_operations ORDER BY operation_id ASC LIMIT ?`)
1062
+ .all(workLaneOperationMaxRecords + 1));
1063
+ if (rows.length > workLaneOperationMaxRecords) {
1064
+ throw new WorkStoreError({ cause: rows.length, operation: "recovery.preflight.ledger-capacity" });
1065
+ }
1066
+ const current = new Map(lanes.entries.map(({ claim, row }) => [claim.laneId, { claim, row }]));
1067
+ const revisions = new Set();
1068
+ const claims = rows.map((row) => {
1069
+ let claim;
1070
+ try {
1071
+ claim = Schema.decodeUnknownSync(WorkLaneClaimed)(JSON.parse(row.record));
1072
+ }
1073
+ catch (cause) {
1074
+ throw new WorkStoreError({ cause, operation: "recovery.preflight.operation-decode" });
1075
+ }
1076
+ if (claim.operationId !== row.operationId || claim.laneId !== row.laneId ||
1077
+ claim.goalId !== row.goalId || claim.phase !== row.phase || claim.revision !== row.revision) {
1078
+ throw new WorkStoreError({ cause: { claim, row }, operation: "recovery.preflight.operation-identity" });
1079
+ }
1080
+ const lane = current.get(claim.laneId);
1081
+ const key = `${claim.laneId}\u0000${String(claim.revision)}`;
1082
+ if (lane === undefined || lane.claim.goalId !== claim.goalId ||
1083
+ claim.revision > lane.claim.revision || revisions.has(key) ||
1084
+ (claim.revision === lane.claim.revision && row.record !== lane.row.record)) {
1085
+ throw new WorkStoreError({ cause: { claim, row, lane }, operation: "recovery.preflight.operation-companion" });
1086
+ }
1087
+ revisions.add(key);
1088
+ return claim;
1089
+ });
1090
+ const byOperation = new Map(rows.map((row) => [row.operationId, row]));
1091
+ if (lanes.entries.some(({ claim, row }) => {
1092
+ const latest = byOperation.get(claim.operationId);
1093
+ return latest === undefined || latest.revision !== claim.revision || latest.record !== row.record;
1094
+ })) {
1095
+ throw new WorkStoreError({ cause: lanes.entries, operation: "recovery.preflight.missing-latest-operation" });
1096
+ }
1097
+ return { lanes, rows, claims };
1098
+ };
1099
+ /** Reads the complete Work authority, including rows outside every projection window. */
1100
+ const recoveryState = (database, target) => {
1101
+ const inspected = admissionState(database, target);
1102
+ const operations = readRecoveryOperations(database);
1103
+ if (inspected._tag === "existing")
1104
+ return { _tag: "existing", target, link: inspected.link };
1105
+ const conflict = (reason) => ({
1106
+ _tag: "conflict",
1107
+ target,
1108
+ reason
1109
+ });
1110
+ const tables = recoveryTables.map((name) => {
1111
+ const rows = database.prepare(`SELECT * FROM ${name} ORDER BY rowid ASC LIMIT ?`).all(workHistoryMaxEvents + 1);
1112
+ if (rows.length > workHistoryMaxEvents) {
1113
+ throw new WorkStoreError({
1114
+ cause: name,
1115
+ operation: "recovery.preflight.capacity"
1116
+ });
1117
+ }
1118
+ return { name, rows };
1119
+ });
1120
+ const eventRows = Schema.decodeUnknownSync(Schema.Array(AgentBindingGoalEventRow))(database
1121
+ .prepare(`SELECT event_id AS eventId, goal_id AS goalId, occurred_at AS occurredAt, record
1122
+ FROM work_goal_events ORDER BY occurred_at ASC, event_id ASC LIMIT ?`)
1123
+ .all(workHistoryMaxEvents + 1));
1124
+ if (eventRows.length > workHistoryMaxEvents) {
1125
+ throw new WorkStoreError({
1126
+ cause: target,
1127
+ operation: "recovery.preflight.history-capacity"
1128
+ });
1129
+ }
1130
+ const history = eventRows.map((row) => {
1131
+ const decision = decodeAgentBindingGoalEvent(row, "recovery.preflight.event");
1132
+ if (decision._tag === "invalid")
1133
+ throw decision.error;
1134
+ return decision.checkpoint;
1135
+ });
1136
+ const historyError = workHistoryError(history);
1137
+ if (historyError !== undefined)
1138
+ throw historyError;
1139
+ const own = history.filter(({ goal }) => goal.id === target.goalId);
1140
+ const original = own.at(-1);
1141
+ if (original === undefined)
1142
+ return conflict("canonical goal does not exist");
1143
+ if (original.eventId !== target.expectedGoalEventId || original.goal.updatedAt !== target.expectedGoalUpdatedAt) {
1144
+ return conflict("approved goal event or revision is stale");
1145
+ }
1146
+ if (own.some(({ goal }) => goal.goalFamily?.role !== "canonical" ||
1147
+ goal.goalFamily.canonicalGoalId !== target.goalId ||
1148
+ !Equal.equals(goal.owner, target.owner) ||
1149
+ (goal.repository.repository !== target.repository &&
1150
+ (goal.repository.repository !== target.worktree || goal.repository.branch !== target.branch)) ||
1151
+ goal.connectTarget !== null ||
1152
+ (goal.agentHierarchy !== undefined && goal.agentHierarchy !== null) ||
1153
+ (goal.review?.url !== null && goal.review?.url !== undefined && goal.review.url !== target.reviewUrl) ||
1154
+ goal.state === "completed" ||
1155
+ goal.state === "deployed")) {
1156
+ return conflict("canonical goal history is linked, terminal, or belongs to another owner");
1157
+ }
1158
+ const bindingRows = Schema.decodeUnknownSync(AgentBindingRows)(database
1159
+ .prepare(`SELECT dispatch_request_id AS dispatchRequestId, lane_id AS laneId,
1160
+ expected_revision AS expectedRevision, revision, agent_id AS agentId, host, record
1161
+ FROM work_agent_bindings ORDER BY dispatch_request_id ASC LIMIT ?`)
1162
+ .all(workLaneOperationMaxRecords + 1));
1163
+ if (bindingRows.length > workLaneOperationMaxRecords) {
1164
+ throw new WorkStoreError({
1165
+ cause: target,
1166
+ operation: "recovery.preflight.ledger-capacity"
1167
+ });
1168
+ }
1169
+ const bindings = bindingRows.map((row) => Schema.decodeUnknownSync(WorkAgentBinding)(JSON.parse(row.record)));
1170
+ const decisions = Schema.decodeUnknownSync(Schema.Array(DecisionRow))(database
1171
+ .prepare(`SELECT handoff_id AS handoffId, session_id AS sessionId, lane_id AS laneId,
1172
+ occurred_at AS occurredAt, record FROM work_decision_handoffs ORDER BY handoff_id ASC LIMIT ?`)
1173
+ .all(workHistoryMaxEvents + 1));
1174
+ if (decisions.length > workHistoryMaxEvents) {
1175
+ throw new WorkStoreError({
1176
+ cause: target,
1177
+ operation: "recovery.preflight.decision-capacity"
1178
+ });
1179
+ }
1180
+ if (history.some(({ goal }) => goal.id !== target.goalId &&
1181
+ (goal.review?.url === target.reviewUrl ||
1182
+ goal.goalFamily?.canonicalGoalId === target.goalId ||
1183
+ goal.agentHierarchy?.agent.agentId === target.worker.agentId)) ||
1184
+ operations.lanes.entries.some(({ claim }) => claim.goalId === target.goalId || claim.laneId === target.laneId || claim.worktree === target.worktree) ||
1185
+ operations.claims.some(({ goalId, laneId, worktree }) => goalId === target.goalId || laneId === target.laneId || worktree === target.worktree) ||
1186
+ bindings.some(({ request }) => request.laneId === target.laneId ||
1187
+ request.worker.agentId === target.worker.agentId ||
1188
+ request.prospectiveAdmission?.sessionId === target.sessionId ||
1189
+ request.existingGoalRecovery?.sessionId === target.sessionId) ||
1190
+ decisions.some(({ record }) => {
1191
+ const handoff = Schema.decodeUnknownSync(WorkDecisionHandoff)(JSON.parse(record));
1192
+ return (handoff.goalId === target.goalId || handoff.laneId === target.laneId || handoff.sessionId === target.sessionId);
1193
+ })) {
1194
+ return conflict("historical PR, goal, lane, worker, session, or worktree linkage conflicts");
1195
+ }
1196
+ return {
1197
+ _tag: "recoverable",
1198
+ target,
1199
+ original,
1200
+ history,
1201
+ snapshot: JSON.stringify({ target, tables })
1202
+ };
1203
+ };
1204
+ const readRecoveryState = (database, target) => {
1205
+ database.exec("BEGIN");
1206
+ try {
1207
+ const state = recoveryState(database, target);
1208
+ database.exec("ROLLBACK");
1209
+ return state;
1210
+ }
1211
+ catch (cause) {
1212
+ database.exec("ROLLBACK");
1213
+ throw cause;
1214
+ }
1215
+ };
35
1216
  export class WorkStore {
36
1217
  #database;
1218
+ #cryptoService;
37
1219
  #fileSystem;
1220
+ #paths;
38
1221
  path;
39
- constructor(path, fileSystem) {
1222
+ constructor(path, fileSystem, paths, cryptoService) {
40
1223
  this.path = path;
41
1224
  this.#fileSystem = fileSystem;
1225
+ this.#paths = paths;
1226
+ this.#cryptoService = cryptoService;
42
1227
  this.#database = new DatabaseSync(path);
43
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;
44
1232
  this.#database.exec(`
45
1233
  PRAGMA journal_mode = WAL;
46
1234
  CREATE TABLE IF NOT EXISTS work_goal_events (
@@ -50,6 +1238,139 @@ export class WorkStore {
50
1238
  record TEXT NOT NULL,
51
1239
  UNIQUE (goal_id, occurred_at)
52
1240
  );
1241
+ CREATE TABLE IF NOT EXISTS work_agent_bindings (
1242
+ dispatch_request_id TEXT PRIMARY KEY,
1243
+ lane_id TEXT NOT NULL,
1244
+ expected_revision INTEGER NOT NULL,
1245
+ revision INTEGER NOT NULL,
1246
+ agent_id TEXT NOT NULL,
1247
+ host TEXT NOT NULL,
1248
+ record TEXT NOT NULL
1249
+ );
1250
+ CREATE INDEX IF NOT EXISTS work_agent_bindings_lane_revision
1251
+ ON work_agent_bindings (lane_id, revision);
1252
+ CREATE TABLE IF NOT EXISTS work_goal_transactions (
1253
+ transaction_id TEXT PRIMARY KEY,
1254
+ record TEXT NOT NULL
1255
+ );
1256
+ CREATE TABLE IF NOT EXISTS work_goal_transaction_totals (
1257
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
1258
+ transaction_count INTEGER NOT NULL CHECK (transaction_count >= 0),
1259
+ transaction_bytes INTEGER NOT NULL CHECK (transaction_bytes >= 0)
1260
+ );
1261
+ CREATE TRIGGER IF NOT EXISTS work_goal_transactions_after_insert
1262
+ AFTER INSERT ON work_goal_transactions
1263
+ BEGIN
1264
+ UPDATE work_goal_transaction_totals
1265
+ SET transaction_count = transaction_count + 1,
1266
+ transaction_bytes = transaction_bytes +
1267
+ length(CAST(NEW.transaction_id AS BLOB)) + length(CAST(NEW.record AS BLOB))
1268
+ WHERE singleton = 1;
1269
+ END;
1270
+ CREATE TABLE IF NOT EXISTS work_lane_claims (
1271
+ lane_id TEXT PRIMARY KEY,
1272
+ goal_id TEXT NOT NULL,
1273
+ operation_id TEXT NOT NULL UNIQUE,
1274
+ phase TEXT NOT NULL,
1275
+ revision INTEGER NOT NULL,
1276
+ record TEXT NOT NULL
1277
+ );
1278
+ CREATE UNIQUE INDEX IF NOT EXISTS work_lane_claims_one_active_goal
1279
+ ON work_lane_claims (goal_id)
1280
+ WHERE phase <> 'shipped';
1281
+ CREATE TABLE IF NOT EXISTS work_lane_operations (
1282
+ operation_id TEXT PRIMARY KEY,
1283
+ lane_id TEXT NOT NULL,
1284
+ goal_id TEXT NOT NULL,
1285
+ phase TEXT NOT NULL,
1286
+ revision INTEGER NOT NULL,
1287
+ record TEXT NOT NULL
1288
+ );
1289
+ CREATE INDEX IF NOT EXISTS work_lane_operations_lane_revision
1290
+ ON work_lane_operations (lane_id, revision);
1291
+ CREATE TABLE IF NOT EXISTS work_lane_operation_totals (
1292
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
1293
+ operation_count INTEGER NOT NULL CHECK (operation_count >= 0),
1294
+ operation_bytes INTEGER NOT NULL CHECK (operation_bytes >= 0)
1295
+ );
1296
+ CREATE TRIGGER IF NOT EXISTS work_lane_operations_after_insert
1297
+ AFTER INSERT ON work_lane_operations
1298
+ BEGIN
1299
+ UPDATE work_lane_operation_totals
1300
+ SET operation_count = operation_count + 1,
1301
+ operation_bytes = operation_bytes +
1302
+ length(CAST(NEW.operation_id AS BLOB)) + length(CAST(NEW.record AS BLOB))
1303
+ WHERE singleton = 1;
1304
+ END;
1305
+ -- One row per approved reassignment. Every row commits together with the
1306
+ -- goal checkpoint whose event id equals its approval job id, so the
1307
+ -- work_goal_events capacity bounds this table too.
1308
+ CREATE TABLE IF NOT EXISTS work_goal_reassignments (
1309
+ approval_job_id TEXT PRIMARY KEY,
1310
+ goal_id TEXT NOT NULL,
1311
+ record TEXT NOT NULL
1312
+ );
1313
+ CREATE TABLE IF NOT EXISTS work_decision_handoffs (
1314
+ handoff_id TEXT PRIMARY KEY,
1315
+ session_id TEXT NOT NULL UNIQUE,
1316
+ lane_id TEXT NOT NULL,
1317
+ occurred_at INTEGER NOT NULL,
1318
+ record TEXT NOT NULL
1319
+ );
1320
+ CREATE TABLE IF NOT EXISTS work_decision_totals (
1321
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
1322
+ decision_count INTEGER NOT NULL CHECK (decision_count >= 0),
1323
+ decision_bytes INTEGER NOT NULL CHECK (decision_bytes >= 0)
1324
+ );
1325
+ CREATE TRIGGER IF NOT EXISTS work_decision_handoffs_after_insert
1326
+ AFTER INSERT ON work_decision_handoffs
1327
+ BEGIN
1328
+ UPDATE work_decision_totals
1329
+ SET decision_count = decision_count + 1,
1330
+ decision_bytes = decision_bytes +
1331
+ length(CAST(NEW.handoff_id AS BLOB)) + length(CAST(NEW.record AS BLOB))
1332
+ WHERE singleton = 1;
1333
+ END;
1334
+ CREATE INDEX IF NOT EXISTS work_decision_handoffs_lane_time
1335
+ ON work_decision_handoffs (lane_id, occurred_at, handoff_id);
1336
+ CREATE INDEX IF NOT EXISTS work_decision_handoffs_session
1337
+ ON work_decision_handoffs (session_id);
1338
+ `);
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(`
1345
+ INSERT OR IGNORE INTO work_lane_operations
1346
+ (operation_id, lane_id, goal_id, phase, revision, record)
1347
+ SELECT operation_id, lane_id, goal_id, phase, revision, record
1348
+ FROM work_lane_claims
1349
+ `);
1350
+ }
1351
+ this.#database.exec(`
1352
+ INSERT OR IGNORE INTO work_lane_operation_totals
1353
+ (singleton, operation_count, operation_bytes)
1354
+ SELECT 1, COUNT(*), COALESCE(SUM(
1355
+ length(CAST(operation_id AS BLOB)) + length(CAST(record AS BLOB))
1356
+ ), 0)
1357
+ FROM work_lane_operations
1358
+ `);
1359
+ this.#database.exec(`
1360
+ INSERT OR IGNORE INTO work_goal_transaction_totals
1361
+ (singleton, transaction_count, transaction_bytes)
1362
+ SELECT 1, COUNT(*), COALESCE(SUM(
1363
+ length(CAST(transaction_id AS BLOB)) + length(CAST(record AS BLOB))
1364
+ ), 0)
1365
+ FROM work_goal_transactions
1366
+ `);
1367
+ this.#database.exec(`
1368
+ INSERT OR REPLACE INTO work_decision_totals
1369
+ (singleton, decision_count, decision_bytes)
1370
+ SELECT 1, COUNT(*), COALESCE(SUM(
1371
+ length(CAST(handoff_id AS BLOB)) + length(CAST(record AS BLOB))
1372
+ ), 0)
1373
+ FROM work_decision_handoffs
53
1374
  `);
54
1375
  }
55
1376
  catch (error) {
@@ -58,20 +1379,1013 @@ export class WorkStore {
58
1379
  }
59
1380
  }
60
1381
  static open = Effect.fn("WorkStore.open")(function* (path) {
1382
+ const cryptoService = yield* Crypto.Crypto;
61
1383
  const fileSystem = yield* FileSystem.FileSystem;
62
1384
  const paths = yield* Path.Path;
63
1385
  const directory = paths.dirname(path);
1386
+ const directoryExists = yield* fileSystem.exists(directory).pipe(Effect.mapError(storeError("open.directory.exists")));
64
1387
  yield* fileSystem.makeDirectory(directory, { recursive: true, mode: 0o700 }).pipe(Effect.mapError(storeError("open.directory")));
65
- yield* fileSystem.chmod(directory, 0o700).pipe(Effect.mapError(storeError("open.secureDirectory")));
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 });
66
1409
  const store = yield* Effect.try({
67
- try: () => new WorkStore(path, fileSystem),
1410
+ try: () => new WorkStore(path, fileSystem, paths, cryptoService),
68
1411
  catch: storeError("open.database")
69
1412
  });
70
1413
  yield* store.secureFiles();
71
1414
  return store;
72
1415
  });
1416
+ recoveryPreflight = Effect.fn("WorkStore.recoveryPreflight")(function* (target) {
1417
+ const decoded = yield* Schema.decodeUnknownEffect(WorkRecoveryTarget)(target).pipe(Effect.mapError(storeError("recovery.preflight.decode")));
1418
+ const state = yield* Effect.try({
1419
+ try: () => readRecoveryState(this.#database, decoded),
1420
+ catch: storeError("recovery.preflight.read")
1421
+ });
1422
+ if (state._tag !== "recoverable")
1423
+ return state;
1424
+ const historyToken = yield* this.#cryptoService
1425
+ .digest("SHA-256", utf8.encode(state.snapshot))
1426
+ .pipe(Effect.mapError(storeError("recovery.preflight.digest")), Effect.map(Hex.encode));
1427
+ const preflight = {
1428
+ _tag: "recoverable",
1429
+ target: decoded,
1430
+ historyToken
1431
+ };
1432
+ return preflight;
1433
+ });
1434
+ recoverExistingGoal = Effect.fn("WorkStore.recoverExistingGoal")(function* (request) {
1435
+ const decoded = yield* Schema.decodeUnknownEffect(WorkExistingGoalRecovery)(request).pipe(Effect.mapError(storeError("recovery.decode")));
1436
+ const target = Schema.decodeUnknownSync(WorkRecoveryTarget)(decoded);
1437
+ const observedAt = yield* Clock.currentTimeMillis;
1438
+ yield* this.secureFiles();
1439
+ const observed = yield* Effect.try({
1440
+ try: () => readRecoveryState(this.#database, target),
1441
+ catch: storeError("recovery.inspect")
1442
+ });
1443
+ const historyToken = observed._tag === "recoverable"
1444
+ ? yield* this.#cryptoService
1445
+ .digest("SHA-256", utf8.encode(observed.snapshot))
1446
+ .pipe(Effect.mapError(storeError("recovery.digest")), Effect.map(Hex.encode))
1447
+ : undefined;
1448
+ const decision = yield* Effect.try({
1449
+ try: () => {
1450
+ let transaction = false;
1451
+ const reject = (reason) => ({
1452
+ _tag: "rejected",
1453
+ error: admissionConflict(target, reason)
1454
+ });
1455
+ try {
1456
+ this.#database.exec("BEGIN IMMEDIATE");
1457
+ transaction = true;
1458
+ const state = recoveryState(this.#database, target);
1459
+ if (state._tag === "existing") {
1460
+ const existing = state.link;
1461
+ const provenance = existing.binding.request.existingGoalRecovery;
1462
+ this.#database.exec("ROLLBACK");
1463
+ transaction = false;
1464
+ return existing.goalEventId === existing.binding.checkpoint.eventId &&
1465
+ existing.lane.revision === 1 &&
1466
+ Equal.equals(existing.goal, existing.binding.checkpoint.goal) &&
1467
+ existing.binding.request.dispatchRequestId === decoded.operationId &&
1468
+ provenance?.expectedGoalEventId === decoded.expectedGoalEventId &&
1469
+ provenance.expectedGoalUpdatedAt === decoded.expectedGoalUpdatedAt &&
1470
+ provenance.expectedHistoryToken === decoded.expectedHistoryToken &&
1471
+ provenance.approvalJobId === decoded.approvalJobId &&
1472
+ provenance.approvalActor === decoded.approvalActor &&
1473
+ provenance.approvalApprovedBy === decoded.approvalApprovedBy &&
1474
+ provenance.approvalApprovedAt === decoded.approvalApprovedAt &&
1475
+ provenance.approvalHash === decoded.approvalHash
1476
+ ? { _tag: "linked", link: existing }
1477
+ : reject("existing linkage is not this exact approved recovery");
1478
+ }
1479
+ if (observed._tag !== "recoverable" ||
1480
+ state._tag !== "recoverable" ||
1481
+ state.snapshot !== observed.snapshot ||
1482
+ historyToken !== decoded.expectedHistoryToken) {
1483
+ this.#database.exec("ROLLBACK");
1484
+ transaction = false;
1485
+ return reject(state._tag === "conflict" ? state.reason : "stale complete-history evidence");
1486
+ }
1487
+ if (this.#database
1488
+ .prepare(`SELECT 1 FROM work_lane_operations WHERE operation_id = ?
1489
+ UNION ALL SELECT 1 FROM work_goal_events WHERE event_id = ?
1490
+ UNION ALL SELECT 1 FROM work_agent_bindings WHERE dispatch_request_id = ?
1491
+ UNION ALL SELECT 1 FROM work_goal_transactions WHERE transaction_id = ?
1492
+ UNION ALL SELECT 1 FROM work_decision_handoffs WHERE handoff_id = ? LIMIT 1`)
1493
+ .get(decoded.operationId, decoded.operationId, decoded.operationId, decoded.operationId, decoded.operationId) !== undefined) {
1494
+ this.#database.exec("ROLLBACK");
1495
+ transaction = false;
1496
+ return reject("operation ID is already used by another durable record");
1497
+ }
1498
+ const at = Math.max(observedAt, state.original.goal.updatedAt + 1);
1499
+ const previous = state.original.goal;
1500
+ const goal = Schema.decodeUnknownSync(WorkGoal)({
1501
+ ...previous,
1502
+ repository: {
1503
+ repository: decoded.repository,
1504
+ branch: decoded.branch
1505
+ },
1506
+ review: previous.review === null || previous.review === undefined
1507
+ ? {
1508
+ state: "not_requested",
1509
+ summary: null,
1510
+ updatedAt: at,
1511
+ url: decoded.reviewUrl
1512
+ }
1513
+ : { ...previous.review, url: decoded.reviewUrl },
1514
+ agentHierarchy: { agent: decoded.worker },
1515
+ connectTarget: agentConnectTarget(decoded.worker),
1516
+ activity: [
1517
+ ...(previous.activity ?? []),
1518
+ {
1519
+ id: decoded.operationId,
1520
+ kind: "status",
1521
+ summary: `Existing canonical goal linked to settled owner by approved Fleet job ${decoded.approvalJobId}`,
1522
+ occurredAt: at
1523
+ }
1524
+ ],
1525
+ updatedAt: at
1526
+ });
1527
+ const checkpoint = Schema.decodeUnknownSync(WorkGoalCheckpoint)({
1528
+ version: "herdr.work.event.v1",
1529
+ eventId: decoded.operationId,
1530
+ occurredAt: at,
1531
+ goal
1532
+ });
1533
+ const lane = Schema.decodeUnknownSync(WorkLaneClaimed)({
1534
+ operationId: decoded.operationId,
1535
+ goalId: decoded.goalId,
1536
+ laneId: decoded.laneId,
1537
+ worktree: decoded.worktree,
1538
+ branch: decoded.branch,
1539
+ head: decoded.head,
1540
+ owner: decoded.owner,
1541
+ parent: null,
1542
+ phase: "claim",
1543
+ expectedRevision: 0,
1544
+ revision: 1
1545
+ });
1546
+ const binding = Schema.decodeUnknownSync(WorkAgentBinding)({
1547
+ version: "herdr.work.agent-binding.v1",
1548
+ request: {
1549
+ version: "herdr.work.agent-binding-request.v1",
1550
+ dispatchRequestId: decoded.operationId,
1551
+ laneId: decoded.laneId,
1552
+ expectedRevision: 0,
1553
+ worker: decoded.worker,
1554
+ existingGoalRecovery: {
1555
+ sessionId: decoded.sessionId,
1556
+ workAssignment: decoded.expectedWork,
1557
+ baseHead: decoded.baseHead,
1558
+ expectedHistoryToken: decoded.expectedHistoryToken,
1559
+ expectedGoalEventId: decoded.expectedGoalEventId,
1560
+ expectedGoalUpdatedAt: decoded.expectedGoalUpdatedAt,
1561
+ approvalJobId: decoded.approvalJobId,
1562
+ approvalActor: decoded.approvalActor,
1563
+ approvalApprovedBy: decoded.approvalApprovedBy,
1564
+ approvalApprovedAt: decoded.approvalApprovedAt,
1565
+ approvalHash: decoded.approvalHash
1566
+ }
1567
+ },
1568
+ lane,
1569
+ checkpoint
1570
+ });
1571
+ const operationTotals = readLaneOperationLedgerTotals(this.#database);
1572
+ const laneRecord = JSON.stringify(lane);
1573
+ const operationBytes = utf8.encode(decoded.operationId).byteLength + utf8.encode(laneRecord).byteLength;
1574
+ const capacity = agentBindingAdmissionError({
1575
+ history: state.history,
1576
+ candidate: checkpoint,
1577
+ operationCount: operationTotals.operationCount,
1578
+ operationBytes: operationTotals.operationBytes,
1579
+ candidateOperationBytes: operationBytes
1580
+ });
1581
+ const lanes = readValidatedLaneLedger(this.#database, "recovery.capacity");
1582
+ if (lanes._tag === "invalid")
1583
+ throw lanes.error;
1584
+ const claimBytes = Schema.decodeUnknownSync(LedgerBytesRow)(this.#database
1585
+ .prepare(`SELECT COALESCE(SUM(length(CAST(lane_id AS BLOB)) + length(CAST(record AS BLOB))), 0) AS bytes
1586
+ FROM work_lane_claims`)
1587
+ .get()).bytes;
1588
+ if (capacity !== undefined ||
1589
+ lanes.entries.length >= workLaneMaxRecords ||
1590
+ claimBytes + utf8.encode(decoded.laneId).byteLength + utf8.encode(laneRecord).byteLength > workLaneMaxBytes) {
1591
+ this.#database.exec("ROLLBACK");
1592
+ transaction = false;
1593
+ return {
1594
+ _tag: "rejected",
1595
+ error: capacity ??
1596
+ new WorkProjectionError({
1597
+ cause: decoded,
1598
+ detail: "existing-goal recovery exceeds Work capacity",
1599
+ reason: "capacity_exceeded"
1600
+ })
1601
+ };
1602
+ }
1603
+ const link = Schema.decodeUnknownSync(WorkPullRequestLink)({
1604
+ request: {
1605
+ repository: decoded.repository,
1606
+ pullRequest: decoded.pullRequest,
1607
+ goalId: decoded.goalId,
1608
+ laneId: decoded.laneId
1609
+ },
1610
+ goalEventId: checkpoint.eventId,
1611
+ goal,
1612
+ lane,
1613
+ binding
1614
+ });
1615
+ this.#database
1616
+ .prepare(`INSERT INTO work_goal_events (event_id, goal_id, occurred_at, record)
1617
+ VALUES (?, ?, ?, ?)`)
1618
+ .run(checkpoint.eventId, checkpoint.goal.id, checkpoint.occurredAt, JSON.stringify(checkpoint));
1619
+ this.#database
1620
+ .prepare(`INSERT INTO work_lane_claims (lane_id, goal_id, operation_id, phase, revision, record)
1621
+ VALUES (?, ?, ?, ?, ?, ?)`)
1622
+ .run(lane.laneId, lane.goalId, lane.operationId, lane.phase, lane.revision, laneRecord);
1623
+ this.#database
1624
+ .prepare(`INSERT INTO work_lane_operations (operation_id, lane_id, goal_id, phase, revision, record)
1625
+ VALUES (?, ?, ?, ?, ?, ?)`)
1626
+ .run(lane.operationId, lane.laneId, lane.goalId, lane.phase, lane.revision, laneRecord);
1627
+ this.#database
1628
+ .prepare(`INSERT INTO work_agent_bindings
1629
+ (dispatch_request_id, lane_id, expected_revision, revision, agent_id, host, record)
1630
+ VALUES (?, ?, ?, ?, ?, ?, ?)`)
1631
+ .run(decoded.operationId, decoded.laneId, 0, 1, decoded.worker.agentId, decoded.worker.host, JSON.stringify(binding));
1632
+ this.#database.exec("COMMIT");
1633
+ transaction = false;
1634
+ return { _tag: "linked", link };
1635
+ }
1636
+ catch (cause) {
1637
+ if (transaction)
1638
+ this.#database.exec("ROLLBACK");
1639
+ throw cause;
1640
+ }
1641
+ },
1642
+ catch: storeError("recovery.transaction")
1643
+ });
1644
+ if (decision._tag === "rejected")
1645
+ return yield* decision.error;
1646
+ return decision.link;
1647
+ });
1648
+ reassign = Effect.fn("WorkStore.reassign")(function* (request) {
1649
+ const decoded = yield* Schema.decodeUnknownEffect(WorkGoalReassignment)(request).pipe(Effect.mapError(storeError("reassign.decode")));
1650
+ const jobId = decoded.approvalJobId;
1651
+ const observedAt = yield* Clock.currentTimeMillis;
1652
+ yield* this.secureFiles();
1653
+ const decision = yield* Effect.try({
1654
+ try: () => {
1655
+ let transaction = false;
1656
+ const reject = (error) => {
1657
+ this.#database.exec("ROLLBACK");
1658
+ transaction = false;
1659
+ return { _tag: "rejected", error };
1660
+ };
1661
+ const ownerMismatch = (laneId, actual) => reject(new WorkGoalOwnerMismatchError({
1662
+ goalId: decoded.goalId,
1663
+ laneId,
1664
+ expectedOwner: { id: decoded.from.id, name: decoded.from.name },
1665
+ actualOwner: { id: actual.id, name: actual.name }
1666
+ }));
1667
+ try {
1668
+ this.#database.exec("BEGIN IMMEDIATE");
1669
+ transaction = true;
1670
+ const priorRaw = this.#database
1671
+ .prepare(`SELECT approval_job_id AS approvalJobId, goal_id AS goalId, record
1672
+ FROM work_goal_reassignments WHERE approval_job_id = ?`)
1673
+ .get(jobId);
1674
+ if (priorRaw !== undefined) {
1675
+ const row = Schema.decodeUnknownSync(ReassignmentRow)(priorRaw);
1676
+ const prior = Schema.decodeUnknownSync(WorkGoalReassigned)(JSON.parse(row.record));
1677
+ if (row.approvalJobId !== prior.reassignment.approvalJobId || row.goalId !== prior.reassignment.goalId) {
1678
+ return reject(new WorkStoreError({ cause: { prior, row }, operation: "reassign.replay.identity-mismatch" }));
1679
+ }
1680
+ if (!Equal.equals(prior.reassignment, decoded)) {
1681
+ return reject(new WorkGoalReassignmentConflictError({ approvalJobId: jobId, reason: "payload_mismatch" }));
1682
+ }
1683
+ this.#database.exec("ROLLBACK");
1684
+ transaction = false;
1685
+ return { _tag: "reassigned", result: prior };
1686
+ }
1687
+ const collision = this.#database
1688
+ .prepare(`SELECT 1 FROM work_goal_events WHERE event_id = ?
1689
+ UNION ALL SELECT 1 FROM work_lane_operations WHERE operation_id = ?
1690
+ UNION ALL SELECT 1 FROM work_agent_bindings WHERE dispatch_request_id = ? LIMIT 1`)
1691
+ .get(jobId, jobId, jobId);
1692
+ if (collision !== undefined) {
1693
+ return reject(new WorkGoalReassignmentConflictError({ approvalJobId: jobId, reason: "identifier_in_use" }));
1694
+ }
1695
+ const eventRows = Schema.decodeUnknownSync(Schema.Array(AgentBindingGoalEventRow))(this.#database
1696
+ .prepare(`SELECT event_id AS eventId, goal_id AS goalId, occurred_at AS occurredAt, record
1697
+ FROM work_goal_events ORDER BY occurred_at ASC, event_id ASC LIMIT ?`)
1698
+ .all(workHistoryMaxEvents + 1));
1699
+ const history = [];
1700
+ for (const row of eventRows) {
1701
+ const event = decodeAgentBindingGoalEvent(row, "reassign.history");
1702
+ if (event._tag === "invalid")
1703
+ return reject(event.error);
1704
+ history.push(event.checkpoint);
1705
+ }
1706
+ const head = history.filter(({ goal }) => goal.id === decoded.goalId).at(-1);
1707
+ if (head === undefined ||
1708
+ head.eventId !== decoded.expectedGoalEventId ||
1709
+ head.goal.updatedAt !== decoded.expectedGoalUpdatedAt) {
1710
+ return reject(new WorkGoalRevisionConflictError({
1711
+ goalId: decoded.goalId,
1712
+ expectedEventId: decoded.expectedGoalEventId,
1713
+ expectedUpdatedAt: decoded.expectedGoalUpdatedAt,
1714
+ actualEventId: head?.eventId ?? null,
1715
+ actualUpdatedAt: head?.goal.updatedAt ?? null
1716
+ }));
1717
+ }
1718
+ if (!Equal.equals(head.goal.owner, decoded.from))
1719
+ return ownerMismatch(null, head.goal.owner);
1720
+ const ledger = readValidatedLaneLedger(this.#database, "reassign.lane");
1721
+ if (ledger._tag === "invalid")
1722
+ return reject(ledger.error);
1723
+ const active = ledger.entries.filter(({ claim }) => claim.goalId === decoded.goalId && claim.phase !== "shipped");
1724
+ if (active.length > 1) {
1725
+ return reject(new WorkStoreError({ cause: active, operation: "reassign.lane.ambiguous" }));
1726
+ }
1727
+ const current = active[0];
1728
+ if (current !== undefined && !Equal.equals(current.claim.owner, decoded.from)) {
1729
+ return ownerMismatch(current.claim.laneId, current.claim.owner);
1730
+ }
1731
+ const latestBindingRaw = current === undefined ? undefined : this.#database
1732
+ .prepare(`SELECT dispatch_request_id AS dispatchRequestId, lane_id AS laneId,
1733
+ expected_revision AS expectedRevision, revision, agent_id AS agentId, host, record
1734
+ FROM work_agent_bindings WHERE lane_id = ? ORDER BY revision DESC LIMIT 1`)
1735
+ .get(current.claim.laneId);
1736
+ const latestBindingRow = latestBindingRaw === undefined
1737
+ ? undefined
1738
+ : Schema.decodeUnknownSync(AgentBindingRow)(latestBindingRaw);
1739
+ const bindingDecision = latestBindingRow === undefined ? null : decodeAgentBindingRow(latestBindingRow, { dispatchRequestId: latestBindingRow.dispatchRequestId, laneId: latestBindingRow.laneId }, "reassign.binding");
1740
+ if (bindingDecision?._tag === "invalid")
1741
+ return reject(bindingDecision.error);
1742
+ const previousBinding = bindingDecision === null ? null : bindingDecision.binding;
1743
+ if (previousBinding !== null && decoded.toAgent._tag !== "set") {
1744
+ return reject(new WorkGoalBindingRequiresAgentError({
1745
+ goalId: decoded.goalId,
1746
+ laneId: previousBinding.request.laneId,
1747
+ dispatchRequestId: previousBinding.request.dispatchRequestId
1748
+ }));
1749
+ }
1750
+ const previous = head.goal;
1751
+ const currentAgentId = previous.agentHierarchy?.agent.agentId ?? previous.connectTarget?.agentId;
1752
+ if (decoded.toAgent._tag === "keep" && currentAgentId !== undefined) {
1753
+ return reject(new WorkGoalAgentTargetConflictError({
1754
+ goalId: decoded.goalId,
1755
+ agentId: currentAgentId,
1756
+ reason: "keep_existing_target",
1757
+ holderId: decoded.goalId
1758
+ }));
1759
+ }
1760
+ if (decoded.toAgent._tag === "set") {
1761
+ const agentId = decoded.toAgent.agent.agentId;
1762
+ const agentConflict = (reason, holderId) => reject(new WorkGoalAgentTargetConflictError({ goalId: decoded.goalId, agentId, reason, holderId }));
1763
+ const latestGoals = new Map();
1764
+ for (const { goal } of history)
1765
+ latestGoals.set(goal.id, goal);
1766
+ const goalHolder = [...latestGoals.values()].find((goal) => goal.id !== decoded.goalId &&
1767
+ (goal.agentHierarchy?.agent.agentId === agentId || goal.connectTarget?.agentId === agentId));
1768
+ if (goalHolder !== undefined)
1769
+ return agentConflict("held_by_other_goal", goalHolder.id);
1770
+ const holderRows = Schema.decodeUnknownSync(AgentBindingRows)(this.#database
1771
+ .prepare(`SELECT dispatch_request_id AS dispatchRequestId, lane_id AS laneId,
1772
+ expected_revision AS expectedRevision, revision, agent_id AS agentId, host, record
1773
+ FROM work_agent_bindings ORDER BY dispatch_request_id ASC LIMIT ?`)
1774
+ .all(workLaneOperationMaxRecords + 1));
1775
+ if (holderRows.length > workLaneOperationMaxRecords) {
1776
+ return reject(new WorkStoreError({ cause: holderRows.length, operation: "reassign.agent-holders.capacity" }));
1777
+ }
1778
+ const holders = [];
1779
+ for (const row of holderRows) {
1780
+ const holder = decodeAgentBindingRow(row, { dispatchRequestId: row.dispatchRequestId, laneId: row.laneId }, "reassign.agent-holders");
1781
+ if (holder._tag === "invalid")
1782
+ return reject(holder.error);
1783
+ holders.push(holder.binding);
1784
+ }
1785
+ const laneHolder = authoritativeBindings(holders).latest.find(({ lane, request }) => lane.goalId !== decoded.goalId && request.worker.agentId === agentId);
1786
+ if (laneHolder !== undefined)
1787
+ return agentConflict("held_by_other_lane", laneHolder.request.laneId);
1788
+ }
1789
+ // occurredAt equals updatedAt for every valid checkpoint; both bound the ordering defensively.
1790
+ const at = Math.max(observedAt, head.occurredAt + 1, head.goal.updatedAt + 1);
1791
+ const reassigned = {
1792
+ ...previous,
1793
+ owner: decoded.to,
1794
+ activity: [
1795
+ ...(previous.activity ?? []),
1796
+ {
1797
+ id: jobId,
1798
+ kind: "status",
1799
+ summary: workReassignActivitySummary(decoded, jobId, decoded.approvalHash),
1800
+ occurredAt: at
1801
+ }
1802
+ ],
1803
+ updatedAt: at
1804
+ };
1805
+ const goal = Schema.decodeUnknownSync(WorkGoal)(withReassignedAgent(reassigned, decoded.toAgent));
1806
+ const checkpoint = Schema.decodeUnknownSync(WorkGoalCheckpoint)({
1807
+ version: "herdr.work.event.v1",
1808
+ eventId: jobId,
1809
+ occurredAt: at,
1810
+ goal
1811
+ });
1812
+ const lane = current === undefined ? null : Schema.decodeUnknownSync(WorkLaneClaimed)({
1813
+ operationId: jobId,
1814
+ goalId: current.claim.goalId,
1815
+ laneId: current.claim.laneId,
1816
+ worktree: current.claim.worktree,
1817
+ branch: current.claim.branch,
1818
+ head: current.claim.head,
1819
+ owner: decoded.to,
1820
+ parent: current.claim.parent,
1821
+ phase: current.claim.phase,
1822
+ expectedRevision: current.claim.revision,
1823
+ revision: current.claim.revision + 1
1824
+ });
1825
+ const binding = previousBinding === null || lane === null || decoded.toAgent._tag !== "set"
1826
+ ? null
1827
+ : Schema.decodeUnknownSync(WorkAgentBinding)({
1828
+ version: "herdr.work.agent-binding.v1",
1829
+ request: {
1830
+ version: "herdr.work.agent-binding-request.v1",
1831
+ dispatchRequestId: jobId,
1832
+ laneId: lane.laneId,
1833
+ expectedRevision: lane.expectedRevision,
1834
+ worker: decoded.toAgent.agent,
1835
+ ownerReassignment: {
1836
+ previousDispatchRequestId: previousBinding.request.dispatchRequestId,
1837
+ approvalJobId: jobId,
1838
+ approvalActor: decoded.approvalActor,
1839
+ approvalApprovedBy: decoded.approvalApprovedBy,
1840
+ approvalApprovedAt: decoded.approvalApprovedAt,
1841
+ approvalHash: decoded.approvalHash
1842
+ }
1843
+ },
1844
+ lane,
1845
+ checkpoint
1846
+ });
1847
+ const result = Schema.decodeUnknownSync(WorkGoalReassigned)({
1848
+ reassignment: decoded,
1849
+ checkpoint,
1850
+ lane,
1851
+ binding
1852
+ });
1853
+ const familyError = validateGoalFamilyHistory([...history, checkpoint]);
1854
+ if (familyError !== undefined)
1855
+ return reject(familyError);
1856
+ const laneRecord = lane === null ? null : JSON.stringify(lane);
1857
+ // A goal without a lane appends no lane operation, so only a lane rewrite is held to that ledger.
1858
+ const operationTotals = lane === null
1859
+ ? { operationCount: 0, operationBytes: 0 }
1860
+ : readLaneOperationLedgerTotals(this.#database);
1861
+ const capacity = agentBindingAdmissionError({
1862
+ history,
1863
+ candidate: checkpoint,
1864
+ operationCount: operationTotals.operationCount,
1865
+ operationBytes: operationTotals.operationBytes,
1866
+ candidateOperationBytes: laneRecord === null
1867
+ ? 0
1868
+ : utf8.encode(jobId).byteLength + utf8.encode(laneRecord).byteLength
1869
+ });
1870
+ if (capacity !== undefined)
1871
+ return reject(capacity);
1872
+ if (current !== undefined && laneRecord !== null) {
1873
+ const claimBytes = Schema.decodeUnknownSync(LedgerBytesRow)(this.#database
1874
+ .prepare(`SELECT COALESCE(SUM(length(CAST(lane_id AS BLOB)) + length(CAST(record AS BLOB))), 0) AS bytes
1875
+ FROM work_lane_claims`)
1876
+ .get()).bytes;
1877
+ if (claimBytes - utf8.encode(current.row.record).byteLength + utf8.encode(laneRecord).byteLength >
1878
+ workLaneMaxBytes) {
1879
+ return reject(new WorkProjectionError({
1880
+ cause: decoded,
1881
+ detail: `work lane claims cannot exceed ${workLaneMaxBytes} encoded bytes`,
1882
+ reason: "capacity_exceeded"
1883
+ }));
1884
+ }
1885
+ }
1886
+ this.#database
1887
+ .prepare("INSERT INTO work_goal_events (event_id, goal_id, occurred_at, record) VALUES (?, ?, ?, ?)")
1888
+ .run(checkpoint.eventId, checkpoint.goal.id, checkpoint.occurredAt, JSON.stringify(checkpoint));
1889
+ if (lane !== null && laneRecord !== null) {
1890
+ const changes = this.#database
1891
+ .prepare(`UPDATE work_lane_claims SET operation_id = ?, revision = ?, record = ?
1892
+ WHERE lane_id = ? AND revision = ?`)
1893
+ .run(lane.operationId, lane.revision, laneRecord, lane.laneId, lane.expectedRevision).changes;
1894
+ if (changes !== 1 && changes !== 1n) {
1895
+ return reject(new WorkStoreError({ cause: lane, operation: "reassign.lane.update" }));
1896
+ }
1897
+ this.#database
1898
+ .prepare(`INSERT INTO work_lane_operations (operation_id, lane_id, goal_id, phase, revision, record)
1899
+ VALUES (?, ?, ?, ?, ?, ?)`)
1900
+ .run(lane.operationId, lane.laneId, lane.goalId, lane.phase, lane.revision, laneRecord);
1901
+ }
1902
+ if (binding !== null) {
1903
+ this.#database
1904
+ .prepare(`INSERT INTO work_agent_bindings
1905
+ (dispatch_request_id, lane_id, expected_revision, revision, agent_id, host, record)
1906
+ VALUES (?, ?, ?, ?, ?, ?, ?)`)
1907
+ .run(binding.request.dispatchRequestId, binding.request.laneId, binding.request.expectedRevision, binding.lane.revision, binding.request.worker.agentId, binding.request.worker.host, JSON.stringify(binding));
1908
+ }
1909
+ this.#database
1910
+ .prepare("INSERT INTO work_goal_reassignments (approval_job_id, goal_id, record) VALUES (?, ?, ?)")
1911
+ .run(jobId, decoded.goalId, JSON.stringify(result));
1912
+ this.#database.exec("COMMIT");
1913
+ transaction = false;
1914
+ return { _tag: "reassigned", result };
1915
+ }
1916
+ catch (cause) {
1917
+ if (transaction)
1918
+ this.#database.exec("ROLLBACK");
1919
+ throw cause;
1920
+ }
1921
+ },
1922
+ catch: storeError("reassign.transaction")
1923
+ });
1924
+ if (decision._tag === "rejected")
1925
+ return yield* decision.error;
1926
+ return decision.result;
1927
+ });
1928
+ admissionPreflight = Effect.fn("WorkStore.admissionPreflight")(function* (target) {
1929
+ const decoded = yield* Schema.decodeUnknownEffect(WorkAdmissionTarget)(target).pipe(Effect.mapError(storeError("admission.preflight.decode")));
1930
+ const state = yield* Effect.try({
1931
+ try: () => readAdmissionState(this.#database, decoded),
1932
+ catch: storeError("admission.preflight.read")
1933
+ });
1934
+ if (state._tag !== "prospective")
1935
+ return state;
1936
+ const absenceToken = yield* this.#cryptoService.digest("SHA-256", new TextEncoder().encode(state.snapshot)).pipe(Effect.mapError(storeError("admission.preflight.digest")), Effect.map(Hex.encode));
1937
+ const preflight = { _tag: "prospective", target: state.target, absenceToken };
1938
+ return preflight;
1939
+ });
1940
+ admitExistingOwner = Effect.fn("WorkStore.admitExistingOwner")(function* (request) {
1941
+ const decoded = yield* Schema.decodeUnknownEffect(WorkProspectiveAdmission)(request).pipe(Effect.mapError(storeError("admission.decode")));
1942
+ const target = Schema.decodeUnknownSync(WorkAdmissionTarget)(decoded);
1943
+ const now = yield* Clock.currentTimeMillis;
1944
+ yield* this.secureFiles();
1945
+ const observed = yield* Effect.try({
1946
+ try: () => readAdmissionState(this.#database, target),
1947
+ catch: storeError("admission.inspect")
1948
+ });
1949
+ const absenceToken = observed._tag === "prospective"
1950
+ ? yield* this.#cryptoService.digest("SHA-256", new TextEncoder().encode(observed.snapshot)).pipe(Effect.mapError(storeError("admission.digest")), Effect.map(Hex.encode))
1951
+ : undefined;
1952
+ const decision = yield* Effect.try({
1953
+ try: () => {
1954
+ let transaction = false;
1955
+ try {
1956
+ this.#database.exec("BEGIN IMMEDIATE");
1957
+ transaction = true;
1958
+ const reject = (reason) => ({
1959
+ _tag: "rejected",
1960
+ error: admissionConflict(target, reason)
1961
+ });
1962
+ const state = admissionState(this.#database, target);
1963
+ if (state._tag === "existing") {
1964
+ const existing = state.link;
1965
+ const provenance = existing.binding.request.prospectiveAdmission;
1966
+ this.#database.exec("ROLLBACK");
1967
+ transaction = false;
1968
+ return existing.binding.request.dispatchRequestId === decoded.operationId &&
1969
+ provenance?.expectedAbsenceToken === decoded.expectedAbsenceToken &&
1970
+ provenance?.approvalJobId === decoded.approvalJobId &&
1971
+ provenance.approvalActor === decoded.approvalActor &&
1972
+ existing.goal.title === decoded.title &&
1973
+ existing.goal.summary === decoded.summary &&
1974
+ existing.goal.detail === decoded.detail
1975
+ ? { _tag: "admitted", link: existing }
1976
+ : reject("existing admission is not this exact approved operation");
1977
+ }
1978
+ if (state._tag !== "prospective" || observed._tag !== "prospective" ||
1979
+ state.snapshot !== observed.snapshot || absenceToken !== decoded.expectedAbsenceToken) {
1980
+ this.#database.exec("ROLLBACK");
1981
+ transaction = false;
1982
+ return reject(state._tag === "conflict" ? state.reason : "stale absence evidence");
1983
+ }
1984
+ if (this.#database.prepare(`SELECT 1 FROM work_lane_operations WHERE operation_id = ?
1985
+ UNION ALL SELECT 1 FROM work_goal_events WHERE event_id IN (?, ?)
1986
+ UNION ALL SELECT 1 FROM work_agent_bindings WHERE dispatch_request_id = ? LIMIT 1`).get(decoded.operationId, decoded.operationId, `${decoded.operationId}.admission`, decoded.operationId) !==
1987
+ undefined) {
1988
+ this.#database.exec("ROLLBACK");
1989
+ transaction = false;
1990
+ return reject("operation ID is already used by another durable record");
1991
+ }
1992
+ if (now < 1) {
1993
+ this.#database.exec("ROLLBACK");
1994
+ transaction = false;
1995
+ return reject("admission clock cannot produce two ordered checkpoints");
1996
+ }
1997
+ const createdAt = now - 1;
1998
+ const goal = Schema.decodeUnknownSync(WorkGoal)({
1999
+ id: decoded.goalId,
2000
+ title: decoded.title,
2001
+ summary: decoded.summary,
2002
+ detail: decoded.detail,
2003
+ state: "review",
2004
+ owner: decoded.owner,
2005
+ repository: { repository: decoded.repository, branch: decoded.branch },
2006
+ spend: null,
2007
+ delivery: "pull_request",
2008
+ blocker: null,
2009
+ connectTarget: null,
2010
+ goalFamily: { canonicalGoalId: decoded.goalId, role: "canonical" },
2011
+ activity: [{
2012
+ id: `${decoded.operationId}.admission`,
2013
+ kind: "status",
2014
+ summary: `Prospective admission of an existing owner by approved Fleet job ${decoded.approvalJobId}`,
2015
+ occurredAt: createdAt
2016
+ }],
2017
+ review: { state: "requested", summary: null, updatedAt: createdAt, url: decoded.reviewUrl },
2018
+ createdAt,
2019
+ updatedAt: createdAt
2020
+ });
2021
+ const initial = Schema.decodeUnknownSync(WorkGoalCheckpoint)({
2022
+ version: "herdr.work.event.v1",
2023
+ eventId: `${decoded.operationId}.admission`,
2024
+ occurredAt: createdAt,
2025
+ goal
2026
+ });
2027
+ const lane = Schema.decodeUnknownSync(WorkLaneClaimed)({
2028
+ operationId: decoded.operationId,
2029
+ goalId: decoded.goalId,
2030
+ laneId: decoded.laneId,
2031
+ worktree: decoded.worktree,
2032
+ branch: decoded.branch,
2033
+ head: decoded.head,
2034
+ owner: decoded.owner,
2035
+ parent: null,
2036
+ phase: "review",
2037
+ expectedRevision: 0,
2038
+ revision: 1
2039
+ });
2040
+ const binding = Schema.decodeUnknownSync(WorkAgentBinding)({
2041
+ version: "herdr.work.agent-binding.v1",
2042
+ request: {
2043
+ version: "herdr.work.agent-binding-request.v1",
2044
+ dispatchRequestId: decoded.operationId,
2045
+ laneId: decoded.laneId,
2046
+ expectedRevision: 0,
2047
+ worker: decoded.worker,
2048
+ prospectiveAdmission: {
2049
+ sessionId: decoded.sessionId,
2050
+ workAssignment: decoded.expectedWork,
2051
+ baseHead: decoded.baseHead,
2052
+ expectedAbsenceToken: decoded.expectedAbsenceToken,
2053
+ approvalJobId: decoded.approvalJobId,
2054
+ approvalActor: decoded.approvalActor
2055
+ }
2056
+ },
2057
+ lane,
2058
+ checkpoint: {
2059
+ version: "herdr.work.event.v1",
2060
+ eventId: decoded.operationId,
2061
+ occurredAt: now,
2062
+ goal: {
2063
+ ...goal,
2064
+ agentHierarchy: { agent: decoded.worker },
2065
+ connectTarget: agentConnectTarget(decoded.worker),
2066
+ updatedAt: now
2067
+ }
2068
+ }
2069
+ });
2070
+ const existingRows = Schema.decodeUnknownSync(Schema.Array(AgentBindingGoalEventRow))(this.#database.prepare(`SELECT event_id AS eventId, goal_id AS goalId, occurred_at AS occurredAt, record
2071
+ FROM work_goal_events ORDER BY occurred_at ASC, event_id ASC LIMIT ?`).all(workHistoryMaxEvents + 1));
2072
+ const history = existingRows.map((row) => {
2073
+ const result = decodeAgentBindingGoalEvent(row, "admission.history");
2074
+ if (result._tag === "invalid")
2075
+ throw result.error;
2076
+ return result.checkpoint;
2077
+ });
2078
+ const operationTotals = readLaneOperationLedgerTotals(this.#database);
2079
+ const laneRecord = JSON.stringify(lane);
2080
+ const operationBytes = utf8.encode(decoded.operationId).byteLength + utf8.encode(laneRecord).byteLength;
2081
+ const claimBytes = Schema.decodeUnknownSync(LedgerBytesRow)(this.#database.prepare(`SELECT COALESCE(SUM(length(CAST(lane_id AS BLOB)) + length(CAST(record AS BLOB))), 0) AS bytes
2082
+ FROM work_lane_claims`).get()).bytes;
2083
+ const capacity = agentBindingAdmissionError({
2084
+ history: [...history, initial],
2085
+ candidate: binding.checkpoint,
2086
+ operationCount: operationTotals.operationCount,
2087
+ operationBytes: operationTotals.operationBytes,
2088
+ candidateOperationBytes: operationBytes
2089
+ });
2090
+ const lanes = readValidatedLaneLedger(this.#database, "admission.capacity");
2091
+ if (lanes._tag === "invalid")
2092
+ throw lanes.error;
2093
+ if (capacity !== undefined || lanes.entries.length >= workLaneMaxRecords ||
2094
+ new Set(history.map(({ goal: previous }) => previous.id)).size >= workSnapshotMaxGoals ||
2095
+ claimBytes + utf8.encode(decoded.laneId).byteLength + utf8.encode(laneRecord).byteLength > workLaneMaxBytes) {
2096
+ this.#database.exec("ROLLBACK");
2097
+ transaction = false;
2098
+ return {
2099
+ _tag: "rejected",
2100
+ error: capacity ?? new WorkProjectionError({
2101
+ cause: decoded,
2102
+ detail: "prospective admission exceeds Work capacity",
2103
+ reason: "capacity_exceeded"
2104
+ })
2105
+ };
2106
+ }
2107
+ const link = Schema.decodeUnknownSync(WorkPullRequestLink)({
2108
+ request: {
2109
+ repository: decoded.repository,
2110
+ pullRequest: decoded.pullRequest,
2111
+ goalId: decoded.goalId,
2112
+ laneId: decoded.laneId
2113
+ },
2114
+ goalEventId: binding.checkpoint.eventId,
2115
+ goal: binding.checkpoint.goal,
2116
+ lane,
2117
+ binding
2118
+ });
2119
+ this.#database.prepare(`INSERT INTO work_goal_events (event_id, goal_id, occurred_at, record)
2120
+ VALUES (?, ?, ?, ?)`).run(initial.eventId, initial.goal.id, initial.occurredAt, JSON.stringify(initial));
2121
+ this.#database.prepare(`INSERT INTO work_goal_events (event_id, goal_id, occurred_at, record)
2122
+ VALUES (?, ?, ?, ?)`).run(binding.checkpoint.eventId, decoded.goalId, now, JSON.stringify(binding.checkpoint));
2123
+ this.#database.prepare(`INSERT INTO work_lane_claims (lane_id, goal_id, operation_id, phase, revision, record)
2124
+ VALUES (?, ?, ?, ?, ?, ?)`).run(lane.laneId, lane.goalId, lane.operationId, lane.phase, lane.revision, laneRecord);
2125
+ this.#database.prepare(`INSERT INTO work_lane_operations (operation_id, lane_id, goal_id, phase, revision, record)
2126
+ VALUES (?, ?, ?, ?, ?, ?)`).run(lane.operationId, lane.laneId, lane.goalId, lane.phase, lane.revision, laneRecord);
2127
+ this.#database.prepare(`INSERT INTO work_agent_bindings
2128
+ (dispatch_request_id, lane_id, expected_revision, revision, agent_id, host, record)
2129
+ VALUES (?, ?, ?, ?, ?, ?, ?)`).run(decoded.operationId, decoded.laneId, 0, 1, decoded.worker.agentId, decoded.worker.host, JSON.stringify(binding));
2130
+ this.#database.exec("COMMIT");
2131
+ transaction = false;
2132
+ return { _tag: "admitted", link };
2133
+ }
2134
+ catch (cause) {
2135
+ if (transaction)
2136
+ this.#database.exec("ROLLBACK");
2137
+ throw cause;
2138
+ }
2139
+ },
2140
+ catch: storeError("admission.transaction")
2141
+ });
2142
+ if (decision._tag === "rejected")
2143
+ return yield* decision.error;
2144
+ return decision.link;
2145
+ });
2146
+ bindAgent = Effect.fn("WorkStore.bindAgent")(function* (request) {
2147
+ const decoded = yield* Schema.decodeUnknownEffect(WorkAgentBindingRequest)(request).pipe(Effect.mapError(storeError("agent-binding.decode")));
2148
+ const observedAt = yield* Clock.currentTimeMillis;
2149
+ yield* this.secureFiles();
2150
+ const decision = yield* Effect.try({
2151
+ try: () => {
2152
+ let inTransaction = false;
2153
+ try {
2154
+ this.#database.exec("BEGIN IMMEDIATE");
2155
+ inTransaction = true;
2156
+ const reject = (error) => {
2157
+ this.#database.exec("ROLLBACK");
2158
+ inTransaction = false;
2159
+ return { _tag: "rejected", error };
2160
+ };
2161
+ const existingRaw = this.#database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, lane_id AS laneId,
2162
+ expected_revision AS expectedRevision, revision, agent_id AS agentId, host, record
2163
+ FROM work_agent_bindings WHERE dispatch_request_id = ?`).get(decoded.dispatchRequestId);
2164
+ if (existingRaw !== undefined) {
2165
+ const row = Schema.decodeUnknownSync(AgentBindingRow)(existingRaw);
2166
+ const binding = Schema.decodeUnknownSync(WorkAgentBinding)(JSON.parse(row.record));
2167
+ if (row.dispatchRequestId !== binding.request.dispatchRequestId ||
2168
+ row.laneId !== binding.request.laneId ||
2169
+ row.expectedRevision !== binding.request.expectedRevision ||
2170
+ row.revision !== binding.lane.revision ||
2171
+ row.agentId !== binding.request.worker.agentId ||
2172
+ row.host.toLowerCase() !== binding.request.worker.host.toLowerCase()) {
2173
+ return reject(new WorkStoreError({
2174
+ cause: { binding, row },
2175
+ operation: "agent-binding.identity-mismatch"
2176
+ }));
2177
+ }
2178
+ const laneRow = this.#database.prepare(`SELECT operation_id AS operationId, lane_id AS laneId, goal_id AS goalId,
2179
+ phase, revision, record
2180
+ FROM work_lane_operations WHERE operation_id = ?`).get(binding.lane.operationId);
2181
+ const checkpointRow = this.#database.prepare(`SELECT event_id AS eventId, goal_id AS goalId, occurred_at AS occurredAt, record
2182
+ FROM work_goal_events WHERE event_id = ?`).get(binding.checkpoint.eventId);
2183
+ const readbackError = agentBindingReadbackError(binding, laneRow === undefined ? undefined : Schema.decodeUnknownSync(AgentBindingLaneOperationRow)(laneRow), checkpointRow === undefined
2184
+ ? undefined
2185
+ : Schema.decodeUnknownSync(AgentBindingGoalEventRow)(checkpointRow), "agent-binding.replay");
2186
+ if (readbackError !== undefined)
2187
+ return reject(readbackError);
2188
+ this.#database.exec("ROLLBACK");
2189
+ inTransaction = false;
2190
+ return Equal.equals(binding.request, decoded)
2191
+ ? { _tag: "bound", binding }
2192
+ : {
2193
+ _tag: "rejected",
2194
+ error: new WorkAgentBindingConflictError({ dispatchRequestId: decoded.dispatchRequestId })
2195
+ };
2196
+ }
2197
+ const laneLedger = readValidatedLaneLedger(this.#database, "agent-binding.authority");
2198
+ if (laneLedger._tag === "invalid")
2199
+ return reject(laneLedger.error);
2200
+ const lane = laneLedger.entries.find(({ claim }) => claim.laneId === decoded.laneId)?.claim;
2201
+ if (lane === undefined) {
2202
+ return reject(new WorkAgentBindingAuthorityError({
2203
+ actualRevision: 0,
2204
+ expectedRevision: decoded.expectedRevision,
2205
+ laneId: decoded.laneId,
2206
+ reason: "missing_lane"
2207
+ }));
2208
+ }
2209
+ if (lane.revision !== decoded.expectedRevision) {
2210
+ return reject(new WorkAgentBindingAuthorityError({
2211
+ actualRevision: lane.revision,
2212
+ expectedRevision: decoded.expectedRevision,
2213
+ laneId: decoded.laneId,
2214
+ reason: "stale_revision"
2215
+ }));
2216
+ }
2217
+ if (lane.phase === "shipped") {
2218
+ return reject(new WorkAgentBindingAuthorityError({
2219
+ actualRevision: lane.revision,
2220
+ expectedRevision: decoded.expectedRevision,
2221
+ laneId: decoded.laneId,
2222
+ reason: "shipped_lane"
2223
+ }));
2224
+ }
2225
+ const activeGoalClaims = laneLedger.entries
2226
+ .map(({ claim }) => claim)
2227
+ .filter((claim) => claim.goalId === lane.goalId && claim.phase !== "shipped");
2228
+ if (activeGoalClaims.length !== 1 || activeGoalClaims[0]?.laneId !== lane.laneId) {
2229
+ return reject(new WorkStoreError({
2230
+ cause: { activeGoalClaims, lane },
2231
+ operation: "agent-binding.goal-authority-conflict"
2232
+ }));
2233
+ }
2234
+ const currentRaw = this.#database.prepare(`SELECT event_id AS eventId, goal_id AS goalId, occurred_at AS occurredAt, record
2235
+ FROM work_goal_events
2236
+ WHERE goal_id = ? ORDER BY occurred_at DESC, event_id DESC LIMIT 1`).get(lane.goalId);
2237
+ if (currentRaw === undefined) {
2238
+ return reject(new WorkAgentBindingAuthorityError({
2239
+ actualRevision: lane.revision,
2240
+ expectedRevision: decoded.expectedRevision,
2241
+ laneId: decoded.laneId,
2242
+ reason: "missing_goal"
2243
+ }));
2244
+ }
2245
+ const currentDecision = decodeAgentBindingGoalEvent(Schema.decodeUnknownSync(AgentBindingGoalEventRow)(currentRaw), "agent-binding.current-goal");
2246
+ if (currentDecision._tag === "invalid")
2247
+ return reject(currentDecision.error);
2248
+ const current = currentDecision.checkpoint;
2249
+ if (current.goal.state === "completed") {
2250
+ return reject(new WorkAgentBindingAuthorityError({
2251
+ actualRevision: lane.revision,
2252
+ expectedRevision: decoded.expectedRevision,
2253
+ laneId: decoded.laneId,
2254
+ reason: "terminal_goal"
2255
+ }));
2256
+ }
2257
+ if (current.occurredAt > observedAt) {
2258
+ return reject(new WorkProjectionError({
2259
+ cause: { checkpoint: current, observedAt },
2260
+ detail: "work agent binding cannot advance beyond the observed clock",
2261
+ reason: "inconsistent_history"
2262
+ }));
2263
+ }
2264
+ if (current.occurredAt >= maximumTimestamp) {
2265
+ return reject(new WorkProjectionError({
2266
+ cause: current,
2267
+ detail: "work agent binding timestamp cannot advance",
2268
+ reason: "capacity_exceeded"
2269
+ }));
2270
+ }
2271
+ const occurredAt = Math.max(observedAt, current.occurredAt + 1);
2272
+ const binding = makeWorkAgentBinding(decoded, lane, current, occurredAt);
2273
+ const historyRows = Schema.decodeUnknownSync(Schema.Array(AgentBindingGoalEventRow))(this.#database.prepare(`SELECT event_id AS eventId, goal_id AS goalId, occurred_at AS occurredAt, record
2274
+ FROM work_goal_events ORDER BY occurred_at ASC, event_id ASC`).all());
2275
+ const history = [];
2276
+ for (const row of historyRows) {
2277
+ const historyDecision = decodeAgentBindingGoalEvent(row, "agent-binding.history");
2278
+ if (historyDecision._tag === "invalid")
2279
+ return reject(historyDecision.error);
2280
+ history.push(historyDecision.checkpoint);
2281
+ }
2282
+ const collision = this.#database.prepare(`SELECT record FROM work_goal_events
2283
+ WHERE event_id = ? OR (goal_id = ? AND occurred_at = ?)`).get(binding.checkpoint.eventId, binding.checkpoint.goal.id, binding.checkpoint.occurredAt);
2284
+ if (collision !== undefined) {
2285
+ return reject(new WorkStoreError({ cause: collision, operation: "agent-binding.event-collision" }));
2286
+ }
2287
+ const operationTotals = readLaneOperationLedgerTotals(this.#database);
2288
+ const encodedLane = JSON.stringify(binding.lane);
2289
+ const operationBytes = utf8.encode(binding.lane.operationId).byteLength + utf8.encode(encodedLane).byteLength;
2290
+ const admissionError = agentBindingAdmissionError({
2291
+ candidate: binding.checkpoint,
2292
+ candidateOperationBytes: operationBytes,
2293
+ history,
2294
+ operationBytes: operationTotals.operationBytes,
2295
+ operationCount: operationTotals.operationCount
2296
+ });
2297
+ if (admissionError !== undefined)
2298
+ return reject(admissionError);
2299
+ const laneChanges = this.#database.prepare(`UPDATE work_lane_claims
2300
+ SET operation_id = ?, revision = ?, record = ?
2301
+ WHERE lane_id = ? AND revision = ?`).run(binding.lane.operationId, binding.lane.revision, encodedLane, decoded.laneId, decoded.expectedRevision).changes;
2302
+ if (laneChanges !== 1 && laneChanges !== 1n) {
2303
+ return reject(new WorkAgentBindingAuthorityError({
2304
+ actualRevision: lane.revision,
2305
+ expectedRevision: decoded.expectedRevision,
2306
+ laneId: decoded.laneId,
2307
+ reason: "stale_revision"
2308
+ }));
2309
+ }
2310
+ this.#database.prepare(`INSERT INTO work_lane_operations
2311
+ (operation_id, lane_id, goal_id, phase, revision, record)
2312
+ VALUES (?, ?, ?, ?, ?, ?)`).run(binding.lane.operationId, binding.lane.laneId, binding.lane.goalId, binding.lane.phase, binding.lane.revision, encodedLane);
2313
+ this.#database.prepare(`INSERT INTO work_goal_events (event_id, goal_id, occurred_at, record)
2314
+ VALUES (?, ?, ?, ?)`).run(binding.checkpoint.eventId, binding.checkpoint.goal.id, binding.checkpoint.occurredAt, JSON.stringify(binding.checkpoint));
2315
+ this.#database.prepare(`INSERT INTO work_agent_bindings
2316
+ (dispatch_request_id, lane_id, expected_revision, revision, agent_id, host, record)
2317
+ VALUES (?, ?, ?, ?, ?, ?, ?)`).run(decoded.dispatchRequestId, decoded.laneId, decoded.expectedRevision, binding.lane.revision, decoded.worker.agentId, decoded.worker.host, JSON.stringify(binding));
2318
+ this.#database.exec("COMMIT");
2319
+ inTransaction = false;
2320
+ return { _tag: "bound", binding };
2321
+ }
2322
+ catch (error) {
2323
+ if (inTransaction)
2324
+ this.#database.exec("ROLLBACK");
2325
+ throw error;
2326
+ }
2327
+ },
2328
+ catch: storeError("agent-binding.write")
2329
+ });
2330
+ if (decision._tag === "rejected")
2331
+ return yield* decision.error;
2332
+ return decision.binding;
2333
+ });
2334
+ agentBinding = Effect.fn("WorkStore.agentBinding")(function* (dispatchRequestId) {
2335
+ const decodedId = yield* Schema.decodeUnknownEffect(WorkAgentBindingRequest.fields.dispatchRequestId)(dispatchRequestId).pipe(Effect.mapError(storeError("agent-binding.read.decode")));
2336
+ const raw = yield* Effect.try({
2337
+ try: () => this.#database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, lane_id AS laneId,
2338
+ expected_revision AS expectedRevision, revision, agent_id AS agentId, host, record
2339
+ FROM work_agent_bindings WHERE dispatch_request_id = ?`).get(decodedId),
2340
+ catch: storeError("agent-binding.read")
2341
+ });
2342
+ if (raw === undefined)
2343
+ return Option.none();
2344
+ const row = yield* Schema.decodeUnknownEffect(AgentBindingRow)(raw).pipe(Effect.mapError(storeError("agent-binding.read.row")));
2345
+ const binding = yield* Effect.try({
2346
+ try: () => Schema.decodeUnknownSync(WorkAgentBinding)(JSON.parse(row.record)),
2347
+ catch: storeError("agent-binding.read.record")
2348
+ });
2349
+ if (row.dispatchRequestId !== binding.request.dispatchRequestId ||
2350
+ row.laneId !== binding.request.laneId ||
2351
+ row.expectedRevision !== binding.request.expectedRevision ||
2352
+ row.revision !== binding.lane.revision ||
2353
+ row.agentId !== binding.request.worker.agentId ||
2354
+ row.host.toLowerCase() !== binding.request.worker.host.toLowerCase()) {
2355
+ return yield* new WorkStoreError({ cause: { binding, row }, operation: "agent-binding.read.identity-mismatch" });
2356
+ }
2357
+ const companions = yield* Effect.try({
2358
+ try: () => ({
2359
+ checkpoint: this.#database.prepare(`SELECT event_id AS eventId, goal_id AS goalId, occurred_at AS occurredAt, record
2360
+ FROM work_goal_events WHERE event_id = ?`).get(binding.checkpoint.eventId),
2361
+ lane: this.#database.prepare(`SELECT operation_id AS operationId, lane_id AS laneId, goal_id AS goalId,
2362
+ phase, revision, record
2363
+ FROM work_lane_operations WHERE operation_id = ?`).get(binding.lane.operationId)
2364
+ }),
2365
+ catch: storeError("agent-binding.read.companions")
2366
+ });
2367
+ const readbackError = agentBindingReadbackError(binding, companions.lane === undefined
2368
+ ? undefined
2369
+ : yield* Schema.decodeUnknownEffect(AgentBindingLaneOperationRow)(companions.lane).pipe(Effect.mapError(storeError("agent-binding.read.lane-companion"))), companions.checkpoint === undefined
2370
+ ? undefined
2371
+ : yield* Schema.decodeUnknownEffect(AgentBindingGoalEventRow)(companions.checkpoint).pipe(Effect.mapError(storeError("agent-binding.read.checkpoint-companion"))), "agent-binding.readback");
2372
+ if (readbackError !== undefined)
2373
+ return yield* readbackError;
2374
+ return Option.some(binding);
2375
+ });
2376
+ bindingDispatchesForLane = Effect.fn("WorkStore.bindingDispatchesForLane")(function* (laneId) {
2377
+ const decoded = yield* Schema.decodeUnknownEffect(WorkGoalId)(laneId).pipe(Effect.mapError(storeError("agent-binding.lane.decode")));
2378
+ const rows = yield* Effect.try({
2379
+ try: () => this.#database.prepare(`SELECT dispatch_request_id AS dispatchRequestId
2380
+ FROM work_agent_bindings WHERE lane_id = ? ORDER BY revision DESC`).all(decoded),
2381
+ catch: storeError("agent-binding.lane.read")
2382
+ });
2383
+ const entries = yield* Schema.decodeUnknownEffect(Schema.Array(Schema.Struct({ dispatchRequestId: WorkAgentBindingRequest.fields.dispatchRequestId })))(rows).pipe(Effect.mapError(storeError("agent-binding.lane.rows")));
2384
+ return entries.map(({ dispatchRequestId }) => dispatchRequestId);
2385
+ });
73
2386
  append = Effect.fn("WorkStore.append")(function* (event) {
74
2387
  const decoded = yield* Schema.decodeUnknownEffect(WorkGoalCheckpoint)(event).pipe(Effect.mapError(storeError("append.decode")));
2388
+ yield* this.secureFiles();
75
2389
  const decision = yield* Effect.try({
76
2390
  try: () => {
77
2391
  let transaction = false;
@@ -166,9 +2480,910 @@ export class WorkStore {
166
2480
  if (decision.changes !== 1 && decision.changes !== 1n) {
167
2481
  return yield* storeError("append.insert.count")(decision.changes);
168
2482
  }
169
- yield* this.secureFiles();
170
2483
  return decoded;
171
2484
  });
2485
+ appendMany = Effect.fn("WorkStore.appendMany")(function* (transactionId, events) {
2486
+ if (events.length > workHistoryMaxEvents) {
2487
+ return yield* new WorkProjectionError({
2488
+ cause: events.length,
2489
+ detail: `work history cannot exceed ${workHistoryMaxEvents} checkpoints`,
2490
+ reason: "capacity_exceeded"
2491
+ });
2492
+ }
2493
+ const transaction = yield* Schema.decodeUnknownEffect(TransactionId)(transactionId).pipe(Effect.mapError(storeError("appendMany.decode.transaction")));
2494
+ const decoded = yield* Effect.forEach(events, (event) => Schema.decodeUnknownEffect(WorkGoalCheckpoint)(event).pipe(Effect.mapError(storeError("appendMany.decode.event"))));
2495
+ if (decoded.length === 0) {
2496
+ return yield* new WorkProjectionError({
2497
+ cause: events,
2498
+ detail: "a checkpoint transaction must contain at least one event",
2499
+ reason: "malformed"
2500
+ });
2501
+ }
2502
+ const digest = yield* this.#cryptoService.digest("SHA-256", new TextEncoder().encode(transactionContent(decoded))).pipe(Effect.mapError(storeError("appendMany.digest")), Effect.map(Hex.encode));
2503
+ const transactionRecord = JSON.stringify({ digest, version: "herdr.work.transaction.v3" });
2504
+ const transactionEntryBytes = utf8.encode(transaction).byteLength + utf8.encode(transactionRecord).byteLength;
2505
+ yield* this.secureFiles();
2506
+ const decision = yield* Effect.try({
2507
+ try: () => {
2508
+ let inTransaction = false;
2509
+ try {
2510
+ this.#database.exec("BEGIN IMMEDIATE");
2511
+ inTransaction = true;
2512
+ const storedTransaction = this.#database.prepare("SELECT record FROM work_goal_transactions WHERE transaction_id = ?").get(transaction);
2513
+ let compactTransaction;
2514
+ let legacyCompactTransaction;
2515
+ let unsupportedCompactTransaction = false;
2516
+ let legacyTransaction;
2517
+ if (storedTransaction !== undefined) {
2518
+ const stored = Schema.decodeUnknownSync(TransactionRow)(storedTransaction);
2519
+ const previous = JSON.parse(stored.record);
2520
+ const compact = Schema.decodeUnknownResult(CompactTransactionRecord)(previous);
2521
+ if (compact._tag === "Success") {
2522
+ compactTransaction = compact.success;
2523
+ }
2524
+ else {
2525
+ const legacyCompact = Schema.decodeUnknownResult(LegacyCompactTransactionRecord)(previous);
2526
+ if (legacyCompact._tag === "Success") {
2527
+ legacyCompactTransaction = legacyCompact.success;
2528
+ unsupportedCompactTransaction = true;
2529
+ }
2530
+ else {
2531
+ const previousCompact = Schema.decodeUnknownResult(PreviousCompactTransactionRecord)(previous);
2532
+ if (previousCompact._tag === "Success") {
2533
+ unsupportedCompactTransaction = true;
2534
+ }
2535
+ else {
2536
+ legacyTransaction = Schema.decodeUnknownSync(Schema.Array(WorkGoalCheckpoint))(previous);
2537
+ }
2538
+ }
2539
+ }
2540
+ }
2541
+ const duplicateEventIds = new Set();
2542
+ const duplicateGoalTimes = new Set();
2543
+ for (const event of decoded) {
2544
+ const goalTime = `${event.goal.id}\u0000${event.occurredAt}`;
2545
+ if (duplicateEventIds.has(event.eventId) || duplicateGoalTimes.has(goalTime)) {
2546
+ this.#database.exec("ROLLBACK");
2547
+ inTransaction = false;
2548
+ return {
2549
+ _tag: "rejected",
2550
+ error: new WorkProjectionError({
2551
+ cause: event,
2552
+ detail: "a checkpoint transaction contains a duplicate event identity",
2553
+ reason: "duplicate_event"
2554
+ })
2555
+ };
2556
+ }
2557
+ duplicateEventIds.add(event.eventId);
2558
+ duplicateGoalTimes.add(goalTime);
2559
+ }
2560
+ const rows = Schema.decodeUnknownSync(StoredEventWithTransactionRows)(this.#database.prepare(`SELECT event_id AS eventId, goal_id AS goalId, occurred_at AS occurredAt,
2561
+ record, transaction_id AS transactionId
2562
+ FROM work_goal_events`).all());
2563
+ const rowsByEventId = new Map(rows.map((row) => [row.eventId, row]));
2564
+ const rowsByGoalTime = new Map(rows.map((row) => [
2565
+ JSON.stringify([row.goalId, row.occurredAt]),
2566
+ row
2567
+ ]));
2568
+ const decodedRows = rows.map((row) => ({
2569
+ event: Schema.decodeUnknownSync(WorkGoalCheckpoint)(JSON.parse(row.record)),
2570
+ row
2571
+ }));
2572
+ const decodedEventsByEventId = new Map(decodedRows.map(({ event, row }) => [row.eventId, event]));
2573
+ if (legacyTransaction !== undefined) {
2574
+ const legacyRows = legacyTransaction.map((event) => rowsByEventId.get(event.eventId));
2575
+ if (legacyRows.some((row, index) => {
2576
+ const event = legacyTransaction[index];
2577
+ return (row === undefined ||
2578
+ event === undefined ||
2579
+ row.goalId !== event.goal.id ||
2580
+ row.occurredAt !== event.occurredAt);
2581
+ })) {
2582
+ this.#database.exec("ROLLBACK");
2583
+ inTransaction = false;
2584
+ return {
2585
+ _tag: "rejected",
2586
+ error: new WorkTransactionConflictError({ transactionId: transaction })
2587
+ };
2588
+ }
2589
+ const legacyEvents = legacyRows.map((row) => row === undefined
2590
+ ? undefined
2591
+ : Schema.decodeUnknownSync(WorkGoalCheckpoint)(JSON.parse(row.record)));
2592
+ if (legacyEvents.some((event) => event === undefined) ||
2593
+ legacyEvents.some((event, index) => event !== undefined && !Equal.equals(event, legacyTransaction[index]))) {
2594
+ this.#database.exec("ROLLBACK");
2595
+ inTransaction = false;
2596
+ return {
2597
+ _tag: "rejected",
2598
+ error: new WorkTransactionConflictError({ transactionId: transaction })
2599
+ };
2600
+ }
2601
+ }
2602
+ const existing = decoded.map((event) => {
2603
+ const row = rowsByEventId.get(event.eventId) ??
2604
+ rowsByGoalTime.get(JSON.stringify([event.goal.id, event.occurredAt]));
2605
+ return row === undefined
2606
+ ? undefined
2607
+ : decodedEventsByEventId.get(row.eventId);
2608
+ });
2609
+ const conflicting = existing.find((candidate, index) => candidate !== undefined && !Equal.equals(candidate, decoded[index]));
2610
+ if (conflicting !== undefined) {
2611
+ this.#database.exec("ROLLBACK");
2612
+ inTransaction = false;
2613
+ const index = existing.findIndex((candidate) => candidate === conflicting);
2614
+ const event = decoded[index];
2615
+ if (event === undefined) {
2616
+ return {
2617
+ _tag: "rejected",
2618
+ error: new WorkStoreError({
2619
+ cause: decoded,
2620
+ operation: "appendMany.collision-candidate"
2621
+ })
2622
+ };
2623
+ }
2624
+ return {
2625
+ _tag: "rejected",
2626
+ error: new WorkCheckpointConflictError({
2627
+ eventId: event.eventId,
2628
+ goalId: event.goal.id,
2629
+ occurredAt: event.occurredAt
2630
+ })
2631
+ };
2632
+ }
2633
+ if (compactTransaction !== undefined) {
2634
+ const denormalizedMismatch = decoded.some((event) => {
2635
+ const row = rowsByEventId.get(event.eventId) ??
2636
+ rowsByGoalTime.get(JSON.stringify([event.goal.id, event.occurredAt]));
2637
+ return (row === undefined ||
2638
+ row.eventId !== event.eventId ||
2639
+ row.goalId !== event.goal.id ||
2640
+ row.occurredAt !== event.occurredAt);
2641
+ });
2642
+ if (denormalizedMismatch) {
2643
+ this.#database.exec("ROLLBACK");
2644
+ inTransaction = false;
2645
+ return {
2646
+ _tag: "rejected",
2647
+ error: new WorkTransactionConflictError({ transactionId: transaction })
2648
+ };
2649
+ }
2650
+ }
2651
+ if (legacyCompactTransaction !== undefined) {
2652
+ const denormalizedMismatch = legacyCompactTransaction.events.some((identity, index) => {
2653
+ const event = decoded[index];
2654
+ const row = rowsByEventId.get(identity.eventId);
2655
+ return (row === undefined ||
2656
+ event === undefined ||
2657
+ row.goalId !== event.goal.id ||
2658
+ row.occurredAt !== event.occurredAt);
2659
+ });
2660
+ if (denormalizedMismatch) {
2661
+ this.#database.exec("ROLLBACK");
2662
+ inTransaction = false;
2663
+ return {
2664
+ _tag: "rejected",
2665
+ error: new WorkTransactionConflictError({ transactionId: transaction })
2666
+ };
2667
+ }
2668
+ }
2669
+ const corruptedRow = decodedRows.find(({ event, row }) => row.eventId !== event.eventId || row.goalId !== event.goal.id || row.occurredAt !== event.occurredAt);
2670
+ if (corruptedRow !== undefined) {
2671
+ this.#database.exec("ROLLBACK");
2672
+ inTransaction = false;
2673
+ return {
2674
+ _tag: "rejected",
2675
+ error: new WorkTransactionConflictError({ transactionId: transaction })
2676
+ };
2677
+ }
2678
+ if (unsupportedCompactTransaction) {
2679
+ this.#database.exec("ROLLBACK");
2680
+ inTransaction = false;
2681
+ return {
2682
+ _tag: "rejected",
2683
+ error: new WorkTransactionConflictError({ transactionId: transaction })
2684
+ };
2685
+ }
2686
+ if (legacyTransaction !== undefined && !Equal.equals(legacyTransaction, decoded)) {
2687
+ this.#database.exec("ROLLBACK");
2688
+ inTransaction = false;
2689
+ return {
2690
+ _tag: "rejected",
2691
+ error: new WorkTransactionConflictError({ transactionId: transaction })
2692
+ };
2693
+ }
2694
+ const newEvents = decoded.filter((_, index) => existing[index] === undefined);
2695
+ if (compactTransaction !== undefined &&
2696
+ newEvents.length !== 0) {
2697
+ this.#database.exec("ROLLBACK");
2698
+ inTransaction = false;
2699
+ return {
2700
+ _tag: "rejected",
2701
+ error: new WorkTransactionConflictError({ transactionId: transaction })
2702
+ };
2703
+ }
2704
+ if (newEvents.length !== 0 && existing.some((candidate) => candidate !== undefined)) {
2705
+ this.#database.exec("ROLLBACK");
2706
+ inTransaction = false;
2707
+ const event = decoded.find((candidate, index) => existing[index] !== undefined);
2708
+ if (event === undefined) {
2709
+ return {
2710
+ _tag: "rejected",
2711
+ error: new WorkStoreError({
2712
+ cause: decoded,
2713
+ operation: "appendMany.partial-replay-candidate"
2714
+ })
2715
+ };
2716
+ }
2717
+ return {
2718
+ _tag: "rejected",
2719
+ error: new WorkCheckpointConflictError({
2720
+ eventId: event.eventId,
2721
+ goalId: event.goal.id,
2722
+ occurredAt: event.occurredAt
2723
+ })
2724
+ };
2725
+ }
2726
+ if (newEvents.length === 0) {
2727
+ if (compactTransaction !== undefined) {
2728
+ if (compactTransaction.digest !== digest) {
2729
+ this.#database.exec("ROLLBACK");
2730
+ inTransaction = false;
2731
+ return {
2732
+ _tag: "rejected",
2733
+ error: new WorkTransactionConflictError({ transactionId: transaction })
2734
+ };
2735
+ }
2736
+ this.#database.exec("ROLLBACK");
2737
+ inTransaction = false;
2738
+ return { _tag: "replayed", events: decoded };
2739
+ }
2740
+ if (legacyTransaction !== undefined) {
2741
+ this.#database.exec("ROLLBACK");
2742
+ inTransaction = false;
2743
+ return { _tag: "replayed", events: decoded };
2744
+ }
2745
+ const transactionLedgerTotals = readTransactionLedgerTotals(this.#database);
2746
+ if (transactionLedgerTotals.transactionCount >= workTransactionMaxRecords) {
2747
+ this.#database.exec("ROLLBACK");
2748
+ inTransaction = false;
2749
+ return {
2750
+ _tag: "rejected",
2751
+ error: new WorkProjectionError({
2752
+ cause: decoded,
2753
+ detail: `work transaction history cannot exceed ${workTransactionMaxRecords} transaction IDs`,
2754
+ reason: "capacity_exceeded"
2755
+ })
2756
+ };
2757
+ }
2758
+ if (transactionLedgerTotals.transactionBytes + transactionEntryBytes > workTransactionMaxBytes) {
2759
+ this.#database.exec("ROLLBACK");
2760
+ inTransaction = false;
2761
+ return {
2762
+ _tag: "rejected",
2763
+ error: new WorkProjectionError({
2764
+ cause: decoded,
2765
+ detail: `work transaction history cannot exceed ${workTransactionMaxBytes} encoded bytes`,
2766
+ reason: "capacity_exceeded"
2767
+ })
2768
+ };
2769
+ }
2770
+ this.#database.prepare("INSERT INTO work_goal_transactions (transaction_id, record) VALUES (?, ?)").run(transaction, transactionRecord);
2771
+ this.#database.exec("COMMIT");
2772
+ inTransaction = false;
2773
+ return { _tag: "replayed", events: decoded };
2774
+ }
2775
+ const eventCount = Schema.decodeUnknownSync(CountRow)(this.#database.prepare("SELECT COUNT(*) AS count FROM work_goal_events").get()).count;
2776
+ if (eventCount + newEvents.length > workHistoryMaxEvents) {
2777
+ this.#database.exec("ROLLBACK");
2778
+ inTransaction = false;
2779
+ return {
2780
+ _tag: "rejected",
2781
+ error: new WorkProjectionError({
2782
+ cause: decoded,
2783
+ detail: `work history cannot exceed ${workHistoryMaxEvents} checkpoints`,
2784
+ reason: "capacity_exceeded"
2785
+ })
2786
+ };
2787
+ }
2788
+ const history = decodedRows.map(({ event }) => event);
2789
+ const prospective = [...history, ...newEvents].toSorted((left, right) => left.occurredAt - right.occurredAt || left.eventId.localeCompare(right.eventId));
2790
+ const familyError = validateGoalFamilyHistory(prospective);
2791
+ if (familyError !== undefined) {
2792
+ this.#database.exec("ROLLBACK");
2793
+ inTransaction = false;
2794
+ return { _tag: "rejected", error: familyError };
2795
+ }
2796
+ if (workMaximumSnapshotBytesForHistory(prospective) > fleetResponseBodyMaxBytes) {
2797
+ this.#database.exec("ROLLBACK");
2798
+ inTransaction = false;
2799
+ return {
2800
+ _tag: "rejected",
2801
+ error: new WorkProjectionError({
2802
+ cause: decoded,
2803
+ detail: `work snapshots cannot exceed ${fleetResponseBodyMaxBytes} encoded bytes`,
2804
+ reason: "capacity_exceeded"
2805
+ })
2806
+ };
2807
+ }
2808
+ const goalIds = new Set(history.map(({ goal }) => goal.id));
2809
+ for (const event of newEvents)
2810
+ goalIds.add(event.goal.id);
2811
+ if (goalIds.size > workSnapshotMaxGoals) {
2812
+ this.#database.exec("ROLLBACK");
2813
+ inTransaction = false;
2814
+ return {
2815
+ _tag: "rejected",
2816
+ error: new WorkProjectionError({
2817
+ cause: decoded,
2818
+ detail: `work snapshots cannot exceed ${workSnapshotMaxGoals} goals`,
2819
+ reason: "capacity_exceeded"
2820
+ })
2821
+ };
2822
+ }
2823
+ const creationTimes = new Map();
2824
+ for (const event of prospective) {
2825
+ const creationTime = creationTimes.get(event.goal.id);
2826
+ if (creationTime === undefined) {
2827
+ if (event.occurredAt !== event.goal.createdAt) {
2828
+ this.#database.exec("ROLLBACK");
2829
+ inTransaction = false;
2830
+ return {
2831
+ _tag: "rejected",
2832
+ error: new WorkProjectionError({
2833
+ cause: event,
2834
+ detail: `goal ${event.goal.id} must begin at its creation timestamp`,
2835
+ reason: "inconsistent_history"
2836
+ })
2837
+ };
2838
+ }
2839
+ creationTimes.set(event.goal.id, event.goal.createdAt);
2840
+ }
2841
+ else if (creationTime !== event.goal.createdAt) {
2842
+ this.#database.exec("ROLLBACK");
2843
+ inTransaction = false;
2844
+ return {
2845
+ _tag: "rejected",
2846
+ error: new WorkProjectionError({
2847
+ cause: event,
2848
+ detail: `goal ${event.goal.id} changed its creation timestamp`,
2849
+ reason: "inconsistent_history"
2850
+ })
2851
+ };
2852
+ }
2853
+ }
2854
+ const transactionLedgerTotals = readTransactionLedgerTotals(this.#database);
2855
+ if (transactionLedgerTotals.transactionCount >= workTransactionMaxRecords) {
2856
+ this.#database.exec("ROLLBACK");
2857
+ inTransaction = false;
2858
+ return {
2859
+ _tag: "rejected",
2860
+ error: new WorkProjectionError({
2861
+ cause: decoded,
2862
+ detail: `work transaction history cannot exceed ${workTransactionMaxRecords} transaction IDs`,
2863
+ reason: "capacity_exceeded"
2864
+ })
2865
+ };
2866
+ }
2867
+ if (transactionLedgerTotals.transactionBytes + transactionEntryBytes > workTransactionMaxBytes) {
2868
+ this.#database.exec("ROLLBACK");
2869
+ inTransaction = false;
2870
+ return {
2871
+ _tag: "rejected",
2872
+ error: new WorkProjectionError({
2873
+ cause: decoded,
2874
+ detail: `work transaction history cannot exceed ${workTransactionMaxBytes} encoded bytes`,
2875
+ reason: "capacity_exceeded"
2876
+ })
2877
+ };
2878
+ }
2879
+ const insert = this.#database.prepare(`INSERT INTO work_goal_events
2880
+ (event_id, goal_id, occurred_at, record, transaction_id)
2881
+ VALUES (?, ?, ?, ?, ?)`);
2882
+ for (const event of newEvents) {
2883
+ insert.run(event.eventId, event.goal.id, event.occurredAt, JSON.stringify(event), transaction);
2884
+ }
2885
+ this.#database.prepare("INSERT INTO work_goal_transactions (transaction_id, record) VALUES (?, ?)").run(transaction, transactionRecord);
2886
+ this.#database.exec("COMMIT");
2887
+ inTransaction = false;
2888
+ return { _tag: "inserted", events: decoded };
2889
+ }
2890
+ catch (error) {
2891
+ if (inTransaction)
2892
+ this.#database.exec("ROLLBACK");
2893
+ throw error;
2894
+ }
2895
+ },
2896
+ catch: storeError("appendMany.insert")
2897
+ });
2898
+ if (decision._tag === "rejected")
2899
+ return yield* decision.error;
2900
+ return decision.events;
2901
+ });
2902
+ claim = Effect.fn("WorkStore.claim")(function* (claim) {
2903
+ const decoded = yield* Schema.decodeUnknownEffect(WorkLaneClaim)(claim).pipe(Effect.mapError(storeError("claim.decode")));
2904
+ yield* this.secureFiles();
2905
+ const decision = yield* Effect.try({
2906
+ try: () => {
2907
+ let inTransaction = false;
2908
+ try {
2909
+ this.#database.exec("BEGIN IMMEDIATE");
2910
+ inTransaction = true;
2911
+ const operationRaw = this.#database.prepare(`SELECT lane_id AS laneId, goal_id AS goalId, operation_id AS operationId,
2912
+ phase, revision, record
2913
+ FROM work_lane_operations WHERE operation_id = ?`).get(decoded.operationId);
2914
+ if (operationRaw !== undefined) {
2915
+ const prior = Schema.decodeUnknownSync(LaneRow)(operationRaw);
2916
+ const priorClaim = Schema.decodeUnknownSync(WorkLaneClaimed)(JSON.parse(prior.record));
2917
+ if (priorClaim.goalId !== prior.goalId ||
2918
+ priorClaim.laneId !== prior.laneId ||
2919
+ priorClaim.operationId !== prior.operationId ||
2920
+ priorClaim.phase !== prior.phase ||
2921
+ priorClaim.revision !== prior.revision) {
2922
+ this.#database.exec("ROLLBACK");
2923
+ inTransaction = false;
2924
+ return {
2925
+ _tag: "rejected",
2926
+ error: new WorkStoreError({
2927
+ cause: { claim: priorClaim, row: prior },
2928
+ operation: "claim.operation.identity-mismatch"
2929
+ })
2930
+ };
2931
+ }
2932
+ this.#database.exec("ROLLBACK");
2933
+ inTransaction = false;
2934
+ return Equal.equals(claimInputFromClaimed(priorClaim), decoded)
2935
+ ? { _tag: "claimed", value: priorClaim }
2936
+ : {
2937
+ _tag: "operation-conflict",
2938
+ error: new WorkLaneOperationConflictError({ operationId: decoded.operationId })
2939
+ };
2940
+ }
2941
+ const laneLedger = readValidatedLaneLedger(this.#database, "claim.write");
2942
+ if (laneLedger._tag === "invalid") {
2943
+ this.#database.exec("ROLLBACK");
2944
+ inTransaction = false;
2945
+ return {
2946
+ _tag: "rejected",
2947
+ error: laneLedger.error
2948
+ };
2949
+ }
2950
+ const existingEntry = laneLedger.entries.find(({ claim }) => claim.laneId === decoded.laneId);
2951
+ const existing = existingEntry?.row;
2952
+ const existingClaim = existingEntry?.claim;
2953
+ const actualRevision = existingClaim?.revision ?? 0;
2954
+ const reconciliation = decoded.reconciliation;
2955
+ if (reconciliation !== undefined) {
2956
+ const rejectLink = (reason) => {
2957
+ this.#database.exec("ROLLBACK");
2958
+ inTransaction = false;
2959
+ return {
2960
+ _tag: "rejected",
2961
+ error: new WorkPullRequestLinkError({
2962
+ goalId: decoded.goalId,
2963
+ laneId: decoded.laneId,
2964
+ reason
2965
+ })
2966
+ };
2967
+ };
2968
+ if (existingClaim === undefined || existingClaim.goalId !== decoded.goalId) {
2969
+ return rejectLink("missing_lane");
2970
+ }
2971
+ if (existingClaim.revision !== decoded.expectedRevision)
2972
+ return rejectLink("stale_revision");
2973
+ if (existingClaim.head !== reconciliation.expectedHead)
2974
+ return rejectLink("head_mismatch");
2975
+ if (!Equal.equals(existingClaim.owner, reconciliation.expectedOwner) ||
2976
+ !Equal.equals(decoded.owner, existingClaim.owner))
2977
+ return rejectLink("owner_mismatch");
2978
+ if (existingClaim.worktree !== decoded.worktree ||
2979
+ existingClaim.branch !== decoded.branch ||
2980
+ existingClaim.parent !== decoded.parent ||
2981
+ existingClaim.phase !== decoded.phase ||
2982
+ existingClaim.phase === "shipped")
2983
+ return rejectLink("identity_mismatch");
2984
+ const eventRaw = this.#database.prepare(`SELECT event_id AS eventId, goal_id AS goalId, occurred_at AS occurredAt, record
2985
+ FROM work_goal_events WHERE goal_id = ? ORDER BY occurred_at DESC, event_id DESC LIMIT 1`).get(decoded.goalId);
2986
+ if (eventRaw === undefined)
2987
+ return rejectLink("missing_provenance");
2988
+ const event = Schema.decodeUnknownSync(WorkGoalCheckpoint)(JSON.parse(Schema.decodeUnknownSync(AgentBindingGoalEventRow)(eventRaw).record));
2989
+ const goal = event.goal;
2990
+ if (event.eventId !== reconciliation.expectedGoalEventId ||
2991
+ goal.review?.url !==
2992
+ `https://github.com/${reconciliation.repository}/pull/${reconciliation.pullRequest}` ||
2993
+ goal.goalFamily?.role !== "canonical" ||
2994
+ goal.goalFamily.canonicalGoalId !== goal.id)
2995
+ return rejectLink("missing_provenance");
2996
+ if (goal.state === "completed" || goal.state === "deployed")
2997
+ return rejectLink("terminal_goal");
2998
+ if (!Equal.equals(goal.owner, reconciliation.expectedOwner))
2999
+ return rejectLink("owner_mismatch");
3000
+ const bindingRows = Schema.decodeUnknownSync(Schema.Array(AgentBindingRow))(this.#database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, lane_id AS laneId,
3001
+ expected_revision AS expectedRevision, revision, agent_id AS agentId, host, record
3002
+ FROM work_agent_bindings WHERE lane_id = ? ORDER BY revision DESC`).all(decoded.laneId));
3003
+ const latest = bindingRows[0];
3004
+ if (latest === undefined)
3005
+ return rejectLink("missing_binding");
3006
+ if (latest.dispatchRequestId !== reconciliation.bindingDispatchRequestId) {
3007
+ return rejectLink("ambiguous_binding");
3008
+ }
3009
+ const binding = Schema.decodeUnknownSync(WorkAgentBinding)(JSON.parse(latest.record));
3010
+ if (binding.request.laneId !== decoded.laneId ||
3011
+ binding.lane.goalId !== decoded.goalId ||
3012
+ !Equal.equals(binding.request.worker, reconciliation.worker) ||
3013
+ !Equal.equals(goal.agentHierarchy?.agent, reconciliation.worker) ||
3014
+ goal.connectTarget?.agentId !== reconciliation.worker.agentId ||
3015
+ goal.connectTarget.host.toLowerCase() !== reconciliation.worker.host.toLowerCase()) {
3016
+ return rejectLink("identity_mismatch");
3017
+ }
3018
+ }
3019
+ if (actualRevision !== decoded.expectedRevision) {
3020
+ this.#database.exec("ROLLBACK");
3021
+ inTransaction = false;
3022
+ return {
3023
+ _tag: "conflict",
3024
+ error: new WorkLaneClaimConflictError({
3025
+ actualRevision,
3026
+ expectedRevision: decoded.expectedRevision,
3027
+ laneId: decoded.laneId
3028
+ })
3029
+ };
3030
+ }
3031
+ if (existingClaim !== undefined && existingClaim.goalId !== decoded.goalId) {
3032
+ this.#database.exec("ROLLBACK");
3033
+ inTransaction = false;
3034
+ return {
3035
+ _tag: "goal-conflict",
3036
+ error: new WorkLaneGoalConflictError({
3037
+ activeLaneId: existingClaim.laneId,
3038
+ goalId: decoded.goalId,
3039
+ laneId: decoded.laneId
3040
+ })
3041
+ };
3042
+ }
3043
+ const activeGoal = laneLedger.entries.find(({ claim }) => claim.goalId === decoded.goalId && claim.phase !== "shipped" && claim.laneId !== decoded.laneId);
3044
+ if (decoded.phase !== "shipped" && activeGoal !== undefined) {
3045
+ this.#database.exec("ROLLBACK");
3046
+ inTransaction = false;
3047
+ return {
3048
+ _tag: "goal-conflict",
3049
+ error: new WorkLaneGoalConflictError({
3050
+ activeLaneId: activeGoal.claim.laneId,
3051
+ goalId: decoded.goalId,
3052
+ laneId: decoded.laneId
3053
+ })
3054
+ };
3055
+ }
3056
+ if (actualRevision >= Number.MAX_SAFE_INTEGER) {
3057
+ this.#database.exec("ROLLBACK");
3058
+ inTransaction = false;
3059
+ return {
3060
+ _tag: "rejected",
3061
+ error: new WorkProjectionError({
3062
+ cause: decoded,
3063
+ detail: "work lane claim revision cannot exceed Number.MAX_SAFE_INTEGER",
3064
+ reason: "capacity_exceeded"
3065
+ })
3066
+ };
3067
+ }
3068
+ const revision = actualRevision + 1;
3069
+ const result = { ...decoded, revision };
3070
+ const encodedRecord = JSON.stringify(result);
3071
+ const operationEntryBytes = utf8.encode(decoded.operationId).byteLength +
3072
+ utf8.encode(encodedRecord).byteLength;
3073
+ const operationTotals = readLaneOperationLedgerTotals(this.#database);
3074
+ if (operationTotals.operationCount >= workLaneOperationMaxRecords) {
3075
+ this.#database.exec("ROLLBACK");
3076
+ inTransaction = false;
3077
+ return {
3078
+ _tag: "rejected",
3079
+ error: new WorkProjectionError({
3080
+ cause: decoded,
3081
+ detail: `work lane operation history cannot exceed ${workLaneOperationMaxRecords} operation IDs`,
3082
+ reason: "capacity_exceeded"
3083
+ })
3084
+ };
3085
+ }
3086
+ if (operationTotals.operationBytes + operationEntryBytes > workLaneOperationMaxBytes) {
3087
+ this.#database.exec("ROLLBACK");
3088
+ inTransaction = false;
3089
+ return {
3090
+ _tag: "rejected",
3091
+ error: new WorkProjectionError({
3092
+ cause: decoded,
3093
+ detail: `work lane operation history cannot exceed ${workLaneOperationMaxBytes} encoded bytes`,
3094
+ reason: "capacity_exceeded"
3095
+ })
3096
+ };
3097
+ }
3098
+ const entryBytes = utf8.encode(decoded.laneId).byteLength + utf8.encode(encodedRecord).byteLength;
3099
+ const existingEntryBytes = existing === undefined
3100
+ ? 0
3101
+ : utf8.encode(decoded.laneId).byteLength + utf8.encode(existing.record).byteLength;
3102
+ const claimCount = laneLedger.entries.length;
3103
+ if (existing === undefined && claimCount >= workLaneMaxRecords) {
3104
+ this.#database.exec("ROLLBACK");
3105
+ inTransaction = false;
3106
+ return {
3107
+ _tag: "rejected",
3108
+ error: new WorkProjectionError({
3109
+ cause: decoded,
3110
+ detail: `work lane claims cannot exceed ${workLaneMaxRecords} lanes`,
3111
+ reason: "capacity_exceeded"
3112
+ })
3113
+ };
3114
+ }
3115
+ const claimBytes = Schema.decodeUnknownSync(LedgerBytesRow)(this.#database.prepare(`SELECT COALESCE(SUM(length(CAST(lane_id AS BLOB)) + length(CAST(record AS BLOB))), 0) AS bytes
3116
+ FROM work_lane_claims`).get()).bytes;
3117
+ if (claimBytes - existingEntryBytes + entryBytes > workLaneMaxBytes) {
3118
+ this.#database.exec("ROLLBACK");
3119
+ inTransaction = false;
3120
+ return {
3121
+ _tag: "rejected",
3122
+ error: new WorkProjectionError({
3123
+ cause: decoded,
3124
+ detail: `work lane claims cannot exceed ${workLaneMaxBytes} encoded bytes`,
3125
+ reason: "capacity_exceeded"
3126
+ })
3127
+ };
3128
+ }
3129
+ const changes = existing === undefined
3130
+ ? this.#database.prepare(`INSERT INTO work_lane_claims
3131
+ (lane_id, goal_id, operation_id, phase, revision, record)
3132
+ VALUES (?, ?, ?, ?, ?, ?)`).run(decoded.laneId, decoded.goalId, decoded.operationId, decoded.phase, revision, encodedRecord).changes
3133
+ : this.#database.prepare(`UPDATE work_lane_claims
3134
+ SET goal_id = ?, operation_id = ?, phase = ?, revision = ?, record = ?
3135
+ WHERE lane_id = ? AND revision = ?`).run(decoded.goalId, decoded.operationId, decoded.phase, revision, encodedRecord, decoded.laneId, decoded.expectedRevision).changes;
3136
+ if (changes !== 1 && changes !== 1n) {
3137
+ this.#database.exec("ROLLBACK");
3138
+ inTransaction = false;
3139
+ return {
3140
+ _tag: "conflict",
3141
+ error: new WorkLaneClaimConflictError({
3142
+ actualRevision,
3143
+ expectedRevision: decoded.expectedRevision,
3144
+ laneId: decoded.laneId
3145
+ })
3146
+ };
3147
+ }
3148
+ const operationChanges = this.#database.prepare(`INSERT INTO work_lane_operations
3149
+ (operation_id, lane_id, goal_id, phase, revision, record)
3150
+ VALUES (?, ?, ?, ?, ?, ?)`).run(decoded.operationId, decoded.laneId, decoded.goalId, decoded.phase, revision, encodedRecord).changes;
3151
+ if (operationChanges !== 1 && operationChanges !== 1n) {
3152
+ this.#database.exec("ROLLBACK");
3153
+ inTransaction = false;
3154
+ return {
3155
+ _tag: "rejected",
3156
+ error: new WorkStoreError({ cause: decoded, operation: "claim.operation.insert" })
3157
+ };
3158
+ }
3159
+ this.#database.exec("COMMIT");
3160
+ inTransaction = false;
3161
+ return { _tag: "claimed", value: result };
3162
+ }
3163
+ catch (error) {
3164
+ if (inTransaction)
3165
+ this.#database.exec("ROLLBACK");
3166
+ throw error;
3167
+ }
3168
+ },
3169
+ catch: storeError("claim.write")
3170
+ });
3171
+ if (decision._tag !== "claimed")
3172
+ return yield* decision.error;
3173
+ return decision.value;
3174
+ });
3175
+ decision = Effect.fn("WorkStore.decision")(function* (handoff) {
3176
+ const decoded = yield* Schema.decodeUnknownEffect(WorkDecisionHandoff)(handoff).pipe(Effect.mapError(storeError("decision.decode")));
3177
+ yield* this.secureFiles();
3178
+ const encodedHandoff = JSON.stringify(decoded);
3179
+ const handoffEntryBytes = utf8.encode(decoded.id).byteLength + utf8.encode(encodedHandoff).byteLength;
3180
+ const result = yield* Effect.try({
3181
+ try: () => {
3182
+ let inTransaction = false;
3183
+ try {
3184
+ this.#database.exec("BEGIN IMMEDIATE");
3185
+ inTransaction = true;
3186
+ const sessionRaw = this.#database.prepare(`SELECT handoff_id AS handoffId, session_id AS sessionId, lane_id AS laneId,
3187
+ occurred_at AS occurredAt, record
3188
+ FROM work_decision_handoffs WHERE session_id = ?`).get(decoded.sessionId);
3189
+ if (sessionRaw !== undefined) {
3190
+ const previous = Schema.decodeUnknownSync(DecisionRow)(sessionRaw);
3191
+ const prior = Schema.decodeUnknownSync(WorkDecisionHandoff)(JSON.parse(previous.record));
3192
+ this.#database.exec("ROLLBACK");
3193
+ inTransaction = false;
3194
+ if (previous.handoffId !== prior.id ||
3195
+ previous.sessionId !== prior.sessionId ||
3196
+ previous.laneId !== prior.laneId ||
3197
+ previous.occurredAt !== prior.occurredAt) {
3198
+ return {
3199
+ _tag: "rejected",
3200
+ error: new WorkStoreError({
3201
+ cause: { row: previous, record: prior },
3202
+ operation: "decision.decode.identity-mismatch"
3203
+ })
3204
+ };
3205
+ }
3206
+ if (Equal.equals(prior, decoded))
3207
+ return { _tag: "replayed", value: decoded };
3208
+ return {
3209
+ _tag: "coordinator-conflict",
3210
+ error: new WorkCoordinatorHandoffConflictError({ sessionId: decoded.sessionId })
3211
+ };
3212
+ }
3213
+ const handoffRaw = this.#database.prepare(`SELECT handoff_id AS handoffId, session_id AS sessionId, lane_id AS laneId,
3214
+ occurred_at AS occurredAt, record
3215
+ FROM work_decision_handoffs WHERE handoff_id = ?`).get(decoded.id);
3216
+ if (handoffRaw !== undefined) {
3217
+ this.#database.exec("ROLLBACK");
3218
+ inTransaction = false;
3219
+ return {
3220
+ _tag: "conflict",
3221
+ error: new WorkDecisionHandoffConflictError({ handoffId: decoded.id })
3222
+ };
3223
+ }
3224
+ const laneLedger = readValidatedLaneLedger(this.#database, "decision.claim");
3225
+ if (laneLedger._tag === "invalid") {
3226
+ this.#database.exec("ROLLBACK");
3227
+ inTransaction = false;
3228
+ return { _tag: "rejected", error: laneLedger.error };
3229
+ }
3230
+ const activeGoalClaims = laneLedger.entries.filter(({ claim }) => claim.goalId === decoded.goalId && claim.phase !== "shipped");
3231
+ if (activeGoalClaims.length !== 1 || activeGoalClaims[0]?.claim.laneId !== decoded.laneId) {
3232
+ this.#database.exec("ROLLBACK");
3233
+ inTransaction = false;
3234
+ return {
3235
+ _tag: "rejected",
3236
+ error: new WorkDecisionAuthorityConflictError({ goalId: decoded.goalId, laneId: decoded.laneId })
3237
+ };
3238
+ }
3239
+ const activeClaim = activeGoalClaims[0].claim;
3240
+ if (activeClaim.revision !== decoded.expectedRevision) {
3241
+ this.#database.exec("ROLLBACK");
3242
+ inTransaction = false;
3243
+ return {
3244
+ _tag: "rejected",
3245
+ error: new WorkDecisionRevisionConflictError({
3246
+ actualRevision: activeClaim.revision,
3247
+ expectedRevision: decoded.expectedRevision,
3248
+ laneId: decoded.laneId
3249
+ })
3250
+ };
3251
+ }
3252
+ const decisionLedgerTotals = readDecisionLedgerTotals(this.#database);
3253
+ if (decisionLedgerTotals.decisionCount >= workDecisionMaxRecords) {
3254
+ this.#database.exec("ROLLBACK");
3255
+ inTransaction = false;
3256
+ return {
3257
+ _tag: "rejected",
3258
+ error: new WorkProjectionError({
3259
+ cause: decoded,
3260
+ detail: `work decision history cannot exceed ${workDecisionMaxRecords} handoffs`,
3261
+ reason: "capacity_exceeded"
3262
+ })
3263
+ };
3264
+ }
3265
+ if (decisionLedgerTotals.decisionBytes + handoffEntryBytes > workDecisionMaxBytes) {
3266
+ this.#database.exec("ROLLBACK");
3267
+ inTransaction = false;
3268
+ return {
3269
+ _tag: "rejected",
3270
+ error: new WorkProjectionError({
3271
+ cause: decoded,
3272
+ detail: `work decision history cannot exceed ${workDecisionMaxBytes} encoded bytes`,
3273
+ reason: "capacity_exceeded"
3274
+ })
3275
+ };
3276
+ }
3277
+ this.#database.prepare(`INSERT INTO work_decision_handoffs
3278
+ (handoff_id, session_id, lane_id, occurred_at, record)
3279
+ VALUES (?, ?, ?, ?, ?)`).run(decoded.id, decoded.sessionId, decoded.laneId, decoded.occurredAt, encodedHandoff);
3280
+ this.#database.exec("COMMIT");
3281
+ inTransaction = false;
3282
+ return { _tag: "inserted", value: decoded };
3283
+ }
3284
+ catch (error) {
3285
+ if (inTransaction)
3286
+ this.#database.exec("ROLLBACK");
3287
+ throw error;
3288
+ }
3289
+ },
3290
+ catch: storeError("decision.write")
3291
+ });
3292
+ if (result._tag !== "inserted" && result._tag !== "replayed")
3293
+ return yield* result.error;
3294
+ return result.value;
3295
+ });
3296
+ currentClaim = Effect.fn("WorkStore.currentClaim")(function* (laneId) {
3297
+ const decodedLaneId = yield* Schema.decodeUnknownEffect(WorkGoalId)(laneId).pipe(Effect.mapError(storeError("claim.read.decode-lane-id")));
3298
+ const ledger = yield* Effect.sync(() => readValidatedLaneLedger(this.#database, "claim.read"));
3299
+ if (ledger._tag === "invalid")
3300
+ return yield* ledger.error;
3301
+ const entry = ledger.entries.find(({ claim }) => claim.laneId === decodedLaneId);
3302
+ return entry === undefined ? Option.none() : Option.some(entry.claim);
3303
+ });
3304
+ activeGoalClaim = Effect.fn("WorkStore.activeGoalClaim")(function* (goalId) {
3305
+ const decodedGoalId = yield* Schema.decodeUnknownEffect(WorkGoalId)(goalId).pipe(Effect.mapError(storeError("claim.goal.decode-goal-id")));
3306
+ const ledger = yield* Effect.sync(() => readValidatedLaneLedger(this.#database, "claim.goal"));
3307
+ if (ledger._tag === "invalid")
3308
+ return yield* ledger.error;
3309
+ const active = ledger.entries.filter(({ claim }) => claim.goalId === decodedGoalId && claim.phase !== "shipped");
3310
+ if (active.length === 0)
3311
+ return Option.none();
3312
+ if (active.length !== 1) {
3313
+ return yield* new WorkStoreError({
3314
+ cause: { goalId: decodedGoalId, lanes: active.map(({ claim }) => claim.laneId) },
3315
+ operation: "claim.goal.exclusivity-violation"
3316
+ });
3317
+ }
3318
+ const entry = active[0];
3319
+ if (entry === undefined) {
3320
+ return yield* new WorkStoreError({ cause: decodedGoalId, operation: "claim.goal.missing-row" });
3321
+ }
3322
+ return Option.some(entry.claim);
3323
+ });
3324
+ coordinatorHandoff = Effect.fn("WorkStore.coordinatorHandoff")(function* (sessionId) {
3325
+ const decodedSessionId = yield* Schema.decodeUnknownEffect(WorkCoordinatorSessionId)(sessionId).pipe(Effect.mapError(storeError("decision.session.decode-session-id")));
3326
+ const raw = yield* Effect.try({
3327
+ try: () => this.#database.prepare(`SELECT handoff_id AS handoffId, session_id AS sessionId, lane_id AS laneId,
3328
+ occurred_at AS occurredAt, record
3329
+ FROM work_decision_handoffs WHERE session_id = ?`).get(decodedSessionId),
3330
+ catch: storeError("decision.session.read")
3331
+ });
3332
+ if (raw === undefined)
3333
+ return Option.none();
3334
+ const row = yield* Schema.decodeUnknownEffect(DecisionRow)(raw).pipe(Effect.mapError(storeError("decision.session.decode-row")));
3335
+ const handoff = yield* Effect.try({
3336
+ try: () => JSON.parse(row.record),
3337
+ catch: storeError("decision.session.parse")
3338
+ }).pipe(Effect.flatMap((value) => Schema.decodeUnknownEffect(WorkDecisionHandoff)(value)), Effect.mapError(storeError("decision.session.decode")));
3339
+ if (handoff.id !== row.handoffId ||
3340
+ handoff.sessionId !== row.sessionId ||
3341
+ handoff.sessionId !== decodedSessionId ||
3342
+ handoff.laneId !== row.laneId ||
3343
+ handoff.occurredAt !== row.occurredAt) {
3344
+ return yield* new WorkStoreError({
3345
+ cause: { handoff, row },
3346
+ operation: "decision.session.identity-mismatch"
3347
+ });
3348
+ }
3349
+ return Option.some(handoff);
3350
+ });
3351
+ decisions = Effect.fn("WorkStore.decisions")(function* (laneId) {
3352
+ const decodedLaneId = yield* Schema.decodeUnknownEffect(WorkGoalId)(laneId).pipe(Effect.mapError(storeError("decisions.list.decode-lane-id")));
3353
+ const rows = yield* Effect.try({
3354
+ try: () => this.#database.prepare(`SELECT handoff_id AS handoffId, session_id AS sessionId, lane_id AS laneId,
3355
+ occurred_at AS occurredAt, record
3356
+ FROM work_decision_handoffs
3357
+ WHERE lane_id = ? ORDER BY occurred_at ASC, handoff_id ASC`).all(decodedLaneId),
3358
+ catch: storeError("decisions.list")
3359
+ });
3360
+ return yield* Effect.forEach(rows, (row) => Schema.decodeUnknownEffect(DecisionRow)(row).pipe(Effect.mapError(storeError("decisions.decode-row")), Effect.flatMap((row) => Effect.try({
3361
+ try: () => Schema.decodeUnknownSync(WorkDecisionHandoff)(JSON.parse(row.record)),
3362
+ catch: storeError("decisions.decode")
3363
+ })
3364
+ .pipe(Effect.flatMap((handoff) => handoff.laneId !== decodedLaneId
3365
+ ? Effect.fail(new WorkStoreError({
3366
+ cause: { requestedLaneId: decodedLaneId, recordLaneId: handoff.laneId },
3367
+ operation: "decisions.decode.lane-mismatch"
3368
+ }))
3369
+ : handoff.laneId !== row.laneId ||
3370
+ handoff.sessionId !== row.sessionId ||
3371
+ handoff.id !== row.handoffId ||
3372
+ handoff.occurredAt !== row.occurredAt
3373
+ ? Effect.fail(new WorkStoreError({
3374
+ cause: {
3375
+ record: handoff,
3376
+ row: {
3377
+ handoffId: row.handoffId,
3378
+ sessionId: row.sessionId,
3379
+ laneId: row.laneId,
3380
+ occurredAt: row.occurredAt
3381
+ }
3382
+ },
3383
+ operation: "decisions.decode.identity-mismatch"
3384
+ }))
3385
+ : Effect.succeed(handoff))))));
3386
+ });
172
3387
  list = Effect.fn("WorkStore.list")(function* () {
173
3388
  const rows = yield* Effect.try({
174
3389
  try: () => this.#database.prepare("SELECT record FROM work_goal_events ORDER BY occurred_at ASC, event_id ASC").all(),
@@ -176,9 +3391,77 @@ export class WorkStore {
176
3391
  });
177
3392
  return yield* Effect.forEach(rows, decodeRow);
178
3393
  });
3394
+ snapshotInput = Effect.fn("WorkStore.snapshotInput")(function* () {
3395
+ const source = yield* Effect.try({
3396
+ try: () => {
3397
+ let inTransaction = false;
3398
+ try {
3399
+ this.#database.exec("BEGIN");
3400
+ inTransaction = true;
3401
+ const events = this.#database.prepare(`SELECT event_id AS eventId, goal_id AS goalId, occurred_at AS occurredAt, record
3402
+ FROM work_goal_events ORDER BY occurred_at ASC, event_id ASC
3403
+ LIMIT ?`).all(workHistoryMaxEvents + 1);
3404
+ const bindings = this.#database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, lane_id AS laneId,
3405
+ expected_revision AS expectedRevision, revision, agent_id AS agentId, host, record
3406
+ FROM work_agent_bindings ORDER BY dispatch_request_id ASC
3407
+ LIMIT ?`).all(workLaneOperationMaxRecords + 1);
3408
+ const laneOperations = this.#database.prepare(`SELECT operation_id AS operationId, lane_id AS laneId, goal_id AS goalId,
3409
+ phase, revision, record
3410
+ FROM work_lane_operations ORDER BY operation_id ASC
3411
+ LIMIT ?`).all(workLaneOperationMaxRecords + 1);
3412
+ this.#database.exec("COMMIT");
3413
+ inTransaction = false;
3414
+ return { bindings, events, laneOperations };
3415
+ }
3416
+ catch (cause) {
3417
+ if (inTransaction)
3418
+ this.#database.exec("ROLLBACK");
3419
+ throw cause;
3420
+ }
3421
+ },
3422
+ catch: storeError("snapshot-input.read")
3423
+ });
3424
+ const eventRows = yield* Schema.decodeUnknownEffect(Schema.Array(AgentBindingGoalEventRow))(source.events).pipe(Effect.mapError(storeError("snapshot-input.decode-events")));
3425
+ const bindingRows = yield* Schema.decodeUnknownEffect(AgentBindingRows)(source.bindings).pipe(Effect.mapError(storeError("snapshot-input.decode-bindings")));
3426
+ const laneRows = yield* Schema.decodeUnknownEffect(Schema.Array(AgentBindingLaneOperationRow))(source.laneOperations).pipe(Effect.mapError(storeError("snapshot-input.decode-lane-operations")));
3427
+ if (eventRows.length > workHistoryMaxEvents ||
3428
+ bindingRows.length > workLaneOperationMaxRecords ||
3429
+ laneRows.length > workLaneOperationMaxRecords) {
3430
+ return yield* new WorkStoreError({ cause: source, operation: "snapshot-input.capacity" });
3431
+ }
3432
+ const events = yield* Effect.forEach(eventRows, (row) => {
3433
+ const decision = decodeAgentBindingGoalEvent(row, "snapshot-input.event");
3434
+ return decision._tag === "valid" ? Effect.succeed(decision.checkpoint) : Effect.fail(decision.error);
3435
+ });
3436
+ const eventById = new Map(eventRows.map((row) => [row.eventId, row]));
3437
+ const laneByOperation = new Map(laneRows.map((row) => [row.operationId, row]));
3438
+ let logicalObservedAt = null;
3439
+ for (const row of bindingRows) {
3440
+ const binding = yield* Effect.try({
3441
+ try: () => Schema.decodeUnknownSync(WorkAgentBinding)(JSON.parse(row.record)),
3442
+ catch: storeError("snapshot-input.decode-binding")
3443
+ });
3444
+ if (row.dispatchRequestId !== binding.request.dispatchRequestId ||
3445
+ row.laneId !== binding.request.laneId ||
3446
+ row.expectedRevision !== binding.request.expectedRevision ||
3447
+ row.revision !== binding.lane.revision ||
3448
+ row.agentId !== binding.request.worker.agentId ||
3449
+ row.host.toLowerCase() !== binding.request.worker.host.toLowerCase()) {
3450
+ return yield* new WorkStoreError({
3451
+ cause: { binding, row },
3452
+ operation: "snapshot-input.binding-identity-mismatch"
3453
+ });
3454
+ }
3455
+ const readbackError = agentBindingReadbackError(binding, laneByOperation.get(binding.lane.operationId), eventById.get(binding.checkpoint.eventId), "snapshot-input.binding");
3456
+ if (readbackError !== undefined)
3457
+ return yield* readbackError;
3458
+ logicalObservedAt = Math.max(logicalObservedAt ?? 0, binding.checkpoint.occurredAt);
3459
+ }
3460
+ return { events, logicalObservedAt };
3461
+ });
179
3462
  secureFiles() {
180
3463
  const files = [this.path, `${this.path}-wal`, `${this.path}-shm`];
181
- return Effect.forEach(files, (path) => this.#fileSystem.exists(path).pipe(Effect.mapError(storeError("secure.exists")), Effect.flatMap((exists) => exists
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
182
3465
  ? this.#fileSystem.chmod(path, 0o600).pipe(Effect.mapError(storeError("secure.chmod")))
183
3466
  : Effect.void)), { discard: true });
184
3467
  }