@knpkv/herdr-work 0.4.0 → 0.5.1

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 +3275 -51
  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,48 +1,1217 @@
1
- import { fleetResponseBodyMaxBytes } from "@knpkv/herdr-fleet";
2
- import { Effect, Equal, FileSystem, Path, Schema } from "effect";
3
- import { DatabaseSync } from "node:sqlite";
4
- import { WorkCheckpointConflictError, WorkProjectionError, WorkStoreError } from "./errors.js";
1
+ import { agentConnectTarget, fleetResponseBodyMaxBytes, workReassignActivitySummary } from "@knpkv/herdr-fleet";
2
+ import { openPrivateSqlite } from "@knpkv/herdr-fleet/sqlite";
3
+ import { Clock, Crypto, Effect, Equal, Option, Schema } from "effect";
4
+ import { Hex } from "effect/encoding";
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 fromPrivateDatabaseError = (error) => new WorkStoreError({ cause: error.cause, operation: error.operation });
141
+ const migrateLegacyAuthorityTables = (database) => {
142
+ let transaction = false;
143
+ try {
144
+ database.exec("BEGIN IMMEDIATE");
145
+ transaction = true;
146
+ const columns = (table) => Schema.decodeUnknownSync(Schema.Array(Schema.Struct({ name: Schema.String })))(database.prepare(`PRAGMA table_info(${table})`).all()).map(({ name }) => name);
147
+ const laneColumns = columns("work_lane_claims");
148
+ const decisionColumns = columns("work_decision_handoffs");
149
+ 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);
150
+ const coordinatorDispatchColumns = tables.includes("orchestrator_dispatches")
151
+ ? columns("orchestrator_dispatches")
152
+ : [];
153
+ const requireBindingCompanions = (binding, operation) => {
154
+ const laneInput = tables.includes("work_lane_operations")
155
+ ? database.prepare(`SELECT operation_id AS operationId, lane_id AS laneId, goal_id AS goalId,
156
+ phase, revision, record
157
+ FROM work_lane_operations WHERE operation_id = ?`).get(binding.lane.operationId)
158
+ : undefined;
159
+ const checkpointInput = tables.includes("work_goal_events")
160
+ ? database.prepare(`SELECT event_id AS eventId, goal_id AS goalId, occurred_at AS occurredAt, record
161
+ FROM work_goal_events WHERE event_id = ?`).get(binding.checkpoint.eventId)
162
+ : undefined;
163
+ const error = agentBindingReadbackError(binding, laneInput === undefined ? undefined : Schema.decodeUnknownSync(AgentBindingLaneOperationRow)(laneInput), checkpointInput === undefined
164
+ ? undefined
165
+ : Schema.decodeUnknownSync(AgentBindingGoalEventRow)(checkpointInput), operation);
166
+ if (error !== undefined)
167
+ throw error;
168
+ };
169
+ const coordinatorTablesPresent = {
170
+ dispatch: tables.includes("orchestrator_dispatches"),
171
+ event: tables.includes("orchestrator_events"),
172
+ metadata: tables.includes("orchestrator_dispatch_metadata")
173
+ };
174
+ const requireCoordinatorSchema = (operation) => {
175
+ const present = Object.values(coordinatorTablesPresent).filter(Boolean).length;
176
+ if (present !== 0 && present !== 3) {
177
+ throw new WorkStoreError({ cause: coordinatorTablesPresent, operation: `${operation}.schema` });
178
+ }
179
+ return present === 3;
180
+ };
181
+ requireCoordinatorSchema("open.migrate");
182
+ const readCoordinatorLifecycle = (dispatchRequestId) => ({
183
+ dispatchRows: Schema.decodeUnknownSync(Schema.Array(CoordinatorLifecycleDispatchRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId,
184
+ activity_idempotency_key AS activityIdempotencyKey, command,
185
+ accepted_at AS acceptedAt, status
186
+ FROM orchestrator_dispatches WHERE dispatch_request_id = ? LIMIT 2`).all(dispatchRequestId)),
187
+ eventRows: Schema.decodeUnknownSync(Schema.Array(CoordinatorLifecycleEventRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, sequence, type,
188
+ activity_idempotency_key AS activityIdempotencyKey,
189
+ occurred_at AS occurredAt, detail, result
190
+ FROM orchestrator_events WHERE dispatch_request_id = ?
191
+ ORDER BY sequence ASC LIMIT 5`).all(dispatchRequestId))
192
+ });
193
+ const routeStorageAuthority = (dispatchRequestId, operation) => {
194
+ if (!coordinatorDispatchColumns.includes("is_routed")) {
195
+ return { _tag: "legacy_without_routed_discriminator" };
196
+ }
197
+ const rows = Schema.decodeUnknownSync(Schema.Array(CoordinatorRouteDiscriminatorRow))(database.prepare(`SELECT is_routed AS isRouted FROM orchestrator_dispatches
198
+ WHERE dispatch_request_id = ? LIMIT 2`).all(dispatchRequestId));
199
+ const row = rows[0];
200
+ if (rows.length !== 1 || row === undefined) {
201
+ throw new WorkStoreError({ cause: { dispatchRequestId, rows }, operation });
202
+ }
203
+ return { _tag: "routed_discriminator", isRouted: row.isRouted };
204
+ };
205
+ const requireLinkedParentAuthority = (linkedRequestId, operation) => {
206
+ if (linkedRequestId === null)
207
+ return;
208
+ const metadataRows = Schema.decodeUnknownSync(Schema.Array(MetadataWorkLinkRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, route, work_link AS workLink
209
+ FROM orchestrator_dispatch_metadata WHERE dispatch_request_id = ? LIMIT 2`).all(linkedRequestId));
210
+ const metadata = metadataRows[0];
211
+ if (metadataRows.length !== 1 || metadata === undefined || metadata.workLink !== null) {
212
+ throw new WorkStoreError({ cause: { linkedRequestId, metadataRows }, operation });
213
+ }
214
+ const lifecycle = readCoordinatorLifecycle(linkedRequestId);
215
+ requireCoordinatorFailedLunaAuthority(lifecycle.dispatchRows, lifecycle.eventRows, metadata.route, routeStorageAuthority(linkedRequestId, operation), operation);
216
+ };
217
+ const requireMetadataAuthority = (dispatchRequestId, routeText, lineage, operation) => {
218
+ const commandRows = Schema.decodeUnknownSync(Schema.Array(CoordinatorCommandRow))(database.prepare(`SELECT activity_idempotency_key AS activityIdempotencyKey, command FROM orchestrator_dispatches
219
+ WHERE dispatch_request_id = ? LIMIT 2`).all(dispatchRequestId));
220
+ const commandRow = commandRows[0];
221
+ if (commandRows.length !== 1 || commandRow === undefined) {
222
+ throw new WorkStoreError({ cause: { commandRows, dispatchRequestId }, operation });
223
+ }
224
+ const route = requireCoordinatorRouteAuthority(commandRow.command, commandRow.activityIdempotencyKey, routeText, lineage, routeStorageAuthority(dispatchRequestId, operation), operation);
225
+ requireLinkedParentAuthority(route.linkedRequestId, `${operation}.parent`);
226
+ };
227
+ const requireLifecycleAuthority = (dispatchRequestId, expectedRunningAt, operation) => {
228
+ if (!requireCoordinatorSchema(operation))
229
+ return;
230
+ const lifecycle = readCoordinatorLifecycle(dispatchRequestId);
231
+ requireCoordinatorLifecycleAuthority(lifecycle.dispatchRows, lifecycle.eventRows, expectedRunningAt, operation);
232
+ };
233
+ const requireCurrentAgentBindingAuthority = (dispatchRequestId, handoff, operation) => {
234
+ const lifecycle = readCoordinatorLifecycle(dispatchRequestId);
235
+ const runningAt = coordinatorLifecycleRunningAt(lifecycle.dispatchRows, lifecycle.eventRows, `${operation}.lifecycle`);
236
+ const bindingRows = tables.includes("work_agent_bindings")
237
+ ? Schema.decodeUnknownSync(Schema.Array(AgentBindingRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, lane_id AS laneId,
238
+ expected_revision AS expectedRevision, revision, agent_id AS agentId, host, record
239
+ FROM work_agent_bindings WHERE dispatch_request_id = ? LIMIT 2`).all(dispatchRequestId))
240
+ : [];
241
+ if (runningAt === null && bindingRows.length === 0)
242
+ return;
243
+ const bindingRow = bindingRows[0];
244
+ if (runningAt === null || bindingRows.length !== 1 || bindingRow === undefined) {
245
+ throw new WorkStoreError({ cause: { bindingRows, lifecycle }, operation });
246
+ }
247
+ const decoded = decodeAgentBindingRow(bindingRow, { dispatchRequestId, laneId: handoff.laneId }, operation);
248
+ if (decoded._tag === "invalid")
249
+ throw decoded.error;
250
+ if (decoded.binding.request.expectedRevision !== handoff.expectedRevision ||
251
+ decoded.binding.lane.goalId !== handoff.goalId ||
252
+ decoded.binding.checkpoint.occurredAt !== runningAt) {
253
+ throw new WorkStoreError({ cause: { binding: decoded.binding, handoff, runningAt }, operation });
254
+ }
255
+ requireBindingCompanions(decoded.binding, operation);
256
+ const currentLaneRows = Schema.decodeUnknownSync(Schema.Array(LaneRow))(database.prepare(`SELECT lane_id AS laneId, goal_id AS goalId, operation_id AS operationId,
257
+ phase, revision, record
258
+ FROM work_lane_claims WHERE lane_id = ? LIMIT 2`).all(decoded.binding.lane.laneId));
259
+ const currentLaneRow = currentLaneRows[0];
260
+ if (currentLaneRows.length !== 1 || currentLaneRow === undefined) {
261
+ throw new WorkStoreError({
262
+ cause: { binding: decoded.binding, currentLaneRows },
263
+ operation: `${operation}.lane-revision`
264
+ });
265
+ }
266
+ let currentLane;
267
+ try {
268
+ currentLane = Schema.decodeUnknownSync(WorkLaneClaimed)(JSON.parse(currentLaneRow.record));
269
+ }
270
+ catch (cause) {
271
+ throw new WorkStoreError({ cause, operation: `${operation}.lane-revision` });
272
+ }
273
+ const currentLaneSchema = laneColumns.includes("goal_id");
274
+ const currentOperationRows = currentLaneSchema
275
+ ? Schema.decodeUnknownSync(Schema.Array(AgentBindingLaneOperationRow))(database.prepare(`SELECT operation_id AS operationId, lane_id AS laneId, goal_id AS goalId,
276
+ phase, revision, record
277
+ FROM work_lane_operations WHERE operation_id = ? LIMIT 2`).all(currentLane.operationId))
278
+ : [];
279
+ const currentOperationError = currentLaneSchema
280
+ ? laneOperationReadbackError(currentLane, currentOperationRows.length === 1 ? currentOperationRows[0] : undefined, `${operation}.lane-revision`)
281
+ : undefined;
282
+ if (currentLane.laneId !== currentLaneRow.laneId ||
283
+ currentLane.goalId !== currentLaneRow.goalId ||
284
+ currentLane.operationId !== currentLaneRow.operationId ||
285
+ currentLane.phase !== currentLaneRow.phase ||
286
+ currentLane.revision !== currentLaneRow.revision ||
287
+ currentLane.laneId !== decoded.binding.lane.laneId ||
288
+ currentLane.goalId !== decoded.binding.lane.goalId ||
289
+ currentLane.revision < decoded.binding.lane.revision ||
290
+ (currentLaneSchema && currentLane.revision === decoded.binding.lane.revision &&
291
+ !Equal.equals(currentLane, decoded.binding.lane)) ||
292
+ currentOperationError !== undefined) {
293
+ throw new WorkStoreError({
294
+ cause: { binding: decoded.binding, currentLane, currentLaneRow, currentOperationError },
295
+ operation: `${operation}.lane-revision`
296
+ });
297
+ }
298
+ };
299
+ const dispatches = tables.includes("work_dispatch_handoffs")
300
+ ? Schema.decodeUnknownSync(Schema.Array(LegacyDispatchStoredRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, handoff_id AS handoffId,
301
+ lane_id AS laneId, occurred_at AS occurredAt, lineage, record
302
+ FROM work_dispatch_handoffs`).all())
303
+ : [];
304
+ const decisionIdentities = decisionColumns.length > 0
305
+ ? Schema.decodeUnknownSync(Schema.Array(DecisionIdentityRow))(database.prepare("SELECT handoff_id AS handoffId FROM work_decision_handoffs").all())
306
+ : [];
307
+ const orphanDispatch = dispatches.find(({ handoffId }) => !decisionIdentities.some((decision) => decision.handoffId === handoffId));
308
+ if (orphanDispatch !== undefined) {
309
+ throw new WorkStoreError({
310
+ cause: { decisionIdentities, dispatch: orphanDispatch },
311
+ operation: "open.migrate.dispatch-decision"
312
+ });
313
+ }
314
+ if (tables.includes("orchestrator_dispatch_metadata")) {
315
+ const linkedMetadata = Schema.decodeUnknownSync(Schema.Array(MetadataHandoffIdentityRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId,
316
+ CASE WHEN work_link IS NULL THEN 0 ELSE 1 END AS hasWorkLink,
317
+ CASE WHEN json_valid(work_link) AND json_type(work_link, '$.handoff.id') = 'text'
318
+ THEN json_extract(work_link, '$.handoff.id') END AS handoffId,
319
+ route
320
+ FROM orchestrator_dispatch_metadata AS metadata
321
+ WHERE work_link IS NOT NULL OR EXISTS (
322
+ SELECT 1 FROM orchestrator_dispatch_metadata AS linked
323
+ WHERE linked.dispatch_request_id = metadata.dispatch_request_id
324
+ AND linked.work_link IS NOT NULL
325
+ ) OR (json_valid(route) AND json_extract(route, '$.model') = 'gpt-5.6-sol')
326
+ LIMIT ${workDecisionMaxRecords + 1}`).all());
327
+ if (linkedMetadata.length > workDecisionMaxRecords) {
328
+ throw new WorkStoreError({ cause: linkedMetadata.length, operation: "open.migrate.metadata-capacity" });
329
+ }
330
+ const metadataCounts = new Map();
331
+ const dispatchCounts = new Map();
332
+ const decisionCounts = new Map();
333
+ for (const metadata of linkedMetadata) {
334
+ metadataCounts.set(metadata.dispatchRequestId, (metadataCounts.get(metadata.dispatchRequestId) ?? 0) + 1);
335
+ }
336
+ for (const dispatch of dispatches) {
337
+ const identity = JSON.stringify([dispatch.dispatchRequestId, dispatch.handoffId]);
338
+ dispatchCounts.set(identity, (dispatchCounts.get(identity) ?? 0) + 1);
339
+ }
340
+ for (const decision of decisionIdentities) {
341
+ decisionCounts.set(decision.handoffId, (decisionCounts.get(decision.handoffId) ?? 0) + 1);
342
+ }
343
+ const invalidMetadata = linkedMetadata.find((metadata) => {
344
+ if (metadata.route === null)
345
+ return true;
346
+ const requiresWorkLink = coordinatorRouteRequiresWorkLink(metadata.route, routeStorageAuthority(metadata.dispatchRequestId, "open.migrate.metadata-decision"), "open.migrate.metadata-decision");
347
+ if (!requiresWorkLink)
348
+ return metadata.hasWorkLink === 1;
349
+ const dispatchIdentity = JSON.stringify([metadata.dispatchRequestId, metadata.handoffId]);
350
+ return (metadata.handoffId === null || metadataCounts.get(metadata.dispatchRequestId) !== 1 ||
351
+ dispatchCounts.get(dispatchIdentity) !== 1 || decisionCounts.get(metadata.handoffId) !== 1);
352
+ });
353
+ if (invalidMetadata !== undefined) {
354
+ throw new WorkStoreError({
355
+ cause: { decisionIdentities, dispatches, linkedMetadata, metadata: invalidMetadata },
356
+ operation: "open.migrate.metadata-decision"
357
+ });
358
+ }
359
+ }
360
+ const lanes = laneColumns.length > 0 && !laneColumns.includes("goal_id")
361
+ ? Schema.decodeUnknownSync(Schema.Array(LegacyLaneStoredRow))(database.prepare("SELECT lane_id AS laneId, revision, record FROM work_lane_claims").all()).map((row) => {
362
+ const legacy = Schema.decodeUnknownSync(LegacyLaneRecord)(JSON.parse(row.record));
363
+ if (legacy.laneId !== row.laneId || legacy.revision !== row.revision) {
364
+ throw new WorkStoreError({ cause: { legacy, row }, operation: "open.migrate.lane-identity" });
365
+ }
366
+ return Schema.decodeUnknownSync(WorkLaneClaimed)({
367
+ ...legacy,
368
+ goalId: legacy.laneId,
369
+ operationId: legacy.laneId
370
+ });
371
+ })
372
+ : [];
373
+ const decisions = decisionColumns.length > 0 && !decisionColumns.includes("session_id")
374
+ ? Schema.decodeUnknownSync(Schema.Array(LegacyDecisionStoredRow))(database.prepare("SELECT handoff_id AS handoffId, record FROM work_decision_handoffs").all()).map((row) => {
375
+ const legacy = Schema.decodeUnknownSync(LegacyDecisionRecord)(JSON.parse(row.record));
376
+ if (legacy.id !== row.handoffId) {
377
+ throw new WorkStoreError({ cause: { legacy, row }, operation: "open.migrate.handoff-identity" });
378
+ }
379
+ const matchingDispatches = dispatches.filter(({ handoffId }) => handoffId === legacy.id);
380
+ const dispatch = matchingDispatches[0];
381
+ const lane = lanes.find(({ laneId }) => laneId === legacy.laneId);
382
+ if (lane === undefined) {
383
+ throw new WorkStoreError({ cause: legacy, operation: "open.migrate.handoff-lane" });
384
+ }
385
+ const dispatchIds = dispatch === undefined
386
+ ? []
387
+ : Schema.decodeUnknownSync(WorkDispatchHandoff.fields.lineage)(JSON.parse(dispatch.lineage));
388
+ if (matchingDispatches.length !== 1 || dispatch === undefined) {
389
+ throw new WorkStoreError({
390
+ cause: { legacy, matchingDispatches },
391
+ operation: "open.migrate.legacy-dispatch-cardinality"
392
+ });
393
+ }
394
+ const dispatchHandoff = Schema.decodeUnknownSync(LegacyDecisionRecord)(JSON.parse(dispatch.record));
395
+ if (dispatch.laneId !== legacy.laneId || dispatch.occurredAt !== legacy.occurredAt ||
396
+ !legacyDecisionEquivalent(dispatchHandoff, legacy)) {
397
+ throw new WorkStoreError({
398
+ cause: { dispatch, dispatchHandoff, legacy },
399
+ operation: "open.migrate.legacy-dispatch-authority"
400
+ });
401
+ }
402
+ if (tables.includes("orchestrator_dispatch_metadata")) {
403
+ const metadataRows = Schema.decodeUnknownSync(Schema.Array(MetadataWorkLinkRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, route, work_link AS workLink
404
+ FROM orchestrator_dispatch_metadata WHERE dispatch_request_id = ? LIMIT 2`).all(dispatch.dispatchRequestId));
405
+ const metadata = metadataRows[0];
406
+ if (metadataRows.length !== 1 || metadata === undefined) {
407
+ throw new WorkStoreError({
408
+ cause: { dispatch, metadataRows },
409
+ operation: "open.migrate.legacy-metadata-authority"
410
+ });
411
+ }
412
+ if (metadata.workLink === null) {
413
+ throw new WorkStoreError({
414
+ cause: { dispatch, metadata },
415
+ operation: "open.migrate.legacy-metadata-authority"
416
+ });
417
+ }
418
+ const workLink = Schema.decodeUnknownSync(LegacyMetadataWorkLink)(JSON.parse(metadata.workLink));
419
+ requireMetadataAuthority(dispatch.dispatchRequestId, metadata.route, workLink.lineage, "open.migrate.legacy-metadata-authority");
420
+ if (!legacyDecisionEquivalent(workLink.handoff, legacy) ||
421
+ !workDispatchLineageEquivalent(workLink.lineage, dispatchIds)) {
422
+ throw new WorkStoreError({
423
+ cause: { legacy, workLink },
424
+ operation: "open.migrate.legacy-metadata-authority"
425
+ });
426
+ }
427
+ }
428
+ const bindingRows = tables.includes("work_agent_bindings")
429
+ ? Schema.decodeUnknownSync(Schema.Array(AgentBindingRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, lane_id AS laneId,
430
+ expected_revision AS expectedRevision, revision, agent_id AS agentId, host, record
431
+ FROM work_agent_bindings
432
+ WHERE dispatch_request_id = ?`).all(dispatch.dispatchRequestId))
433
+ : [];
434
+ const bindingRow = bindingRows[0];
435
+ if (bindingRows.length !== 1 || bindingRow === undefined) {
436
+ throw new WorkStoreError({
437
+ cause: { bindingRows, dispatch, legacy },
438
+ operation: "open.migrate.legacy-handoff-revision"
439
+ });
440
+ }
441
+ const bindingDecision = decodeAgentBindingRow(bindingRow, { dispatchRequestId: dispatch.dispatchRequestId, laneId: legacy.laneId }, "open.migrate.legacy-agent-binding");
442
+ if (bindingDecision._tag === "invalid")
443
+ throw bindingDecision.error;
444
+ if (bindingDecision.binding.lane.goalId !== legacy.goalId) {
445
+ throw new WorkStoreError({
446
+ cause: { binding: bindingDecision.binding, legacy },
447
+ operation: "open.migrate.legacy-agent-binding.goal"
448
+ });
449
+ }
450
+ if (lane.revision < bindingDecision.binding.lane.revision) {
451
+ throw new WorkStoreError({
452
+ cause: { binding: bindingDecision.binding, lane, legacy },
453
+ operation: "open.migrate.legacy-handoff-lane-revision"
454
+ });
455
+ }
456
+ requireBindingCompanions(bindingDecision.binding, "open.migrate.legacy-agent-binding");
457
+ requireLifecycleAuthority(bindingDecision.binding.request.dispatchRequestId, bindingDecision.binding.checkpoint.occurredAt, "open.migrate.legacy-agent-binding.lifecycle");
458
+ return Schema.decodeUnknownSync(WorkDecisionHandoff)({
459
+ ...legacy,
460
+ contextDelta: legacy.summary,
461
+ expectedRevision: bindingDecision.binding.request.expectedRevision,
462
+ sessionId: legacy.id,
463
+ dispatchIds,
464
+ blockers: [],
465
+ evidenceRefs: [],
466
+ version: "herdr.work.decision.v2"
467
+ });
468
+ })
469
+ : [];
470
+ if (laneColumns.length > 0 && !laneColumns.includes("goal_id")) {
471
+ database.exec("ALTER TABLE work_lane_claims ADD COLUMN goal_id TEXT");
472
+ database.exec("ALTER TABLE work_lane_claims ADD COLUMN operation_id TEXT");
473
+ database.exec("ALTER TABLE work_lane_claims ADD COLUMN phase TEXT");
474
+ const update = database.prepare("UPDATE work_lane_claims SET goal_id = ?, operation_id = ?, phase = ?, record = ? WHERE lane_id = ?");
475
+ for (const lane of lanes)
476
+ update.run(lane.goalId, lane.operationId, lane.phase, JSON.stringify(lane), lane.laneId);
477
+ }
478
+ if (decisionColumns.length > 0 && !decisionColumns.includes("session_id")) {
479
+ database.exec("ALTER TABLE work_decision_handoffs ADD COLUMN session_id TEXT");
480
+ const update = database.prepare("UPDATE work_decision_handoffs SET session_id = ?, record = ? WHERE handoff_id = ?");
481
+ for (const decision of decisions)
482
+ update.run(decision.sessionId, JSON.stringify(decision), decision.id);
483
+ if (dispatches.length > 0) {
484
+ const updateDispatch = database.prepare("UPDATE work_dispatch_handoffs SET record = ? WHERE dispatch_request_id = ?");
485
+ const updateMetadata = tables.includes("orchestrator_dispatch_metadata")
486
+ ? database.prepare("UPDATE orchestrator_dispatch_metadata SET work_link = ? WHERE dispatch_request_id = ?")
487
+ : null;
488
+ for (const dispatch of dispatches) {
489
+ const decision = decisions.find(({ id }) => id === dispatch.handoffId);
490
+ if (decision === undefined)
491
+ continue;
492
+ updateDispatch.run(JSON.stringify(decision), dispatch.dispatchRequestId);
493
+ updateMetadata?.run(JSON.stringify({ handoff: decision, lineage: decision.dispatchIds }), dispatch.dispatchRequestId);
494
+ }
495
+ }
496
+ }
497
+ if (decisionColumns.length > 0) {
498
+ const storedDecisions = Schema.decodeUnknownSync(Schema.Array(DecisionRow))(database.prepare(`SELECT handoff_id AS handoffId, session_id AS sessionId, lane_id AS laneId,
499
+ occurred_at AS occurredAt, record
500
+ FROM work_decision_handoffs`).all());
501
+ const updateDecision = database.prepare("UPDATE work_decision_handoffs SET record = ? WHERE handoff_id = ?");
502
+ const updateDispatch = tables.includes("work_dispatch_handoffs")
503
+ ? database.prepare("UPDATE work_dispatch_handoffs SET record = ? WHERE handoff_id = ?")
504
+ : null;
505
+ const updateMetadata = tables.includes("orchestrator_dispatch_metadata")
506
+ ? database.prepare(`UPDATE orchestrator_dispatch_metadata SET work_link = ?
507
+ WHERE dispatch_request_id = ?`)
508
+ : null;
509
+ const previousDecisions = storedDecisions.flatMap((row) => {
510
+ const input = Schema.decodeUnknownSync(Schema.fromJsonString(Schema.Json))(row.record);
511
+ const previous = decodePreviousDecisionHandoff(input);
512
+ const handoffDispatches = tables.includes("work_dispatch_handoffs")
513
+ ? Schema.decodeUnknownSync(Schema.Array(LegacyDispatchStoredRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, handoff_id AS handoffId,
514
+ lane_id AS laneId, occurred_at AS occurredAt, lineage, record
515
+ FROM work_dispatch_handoffs WHERE handoff_id = ?`).all(row.handoffId))
516
+ : [];
517
+ switch (previous._tag) {
518
+ case "current": {
519
+ if (previous.value.id !== row.handoffId || previous.value.sessionId !== row.sessionId ||
520
+ previous.value.laneId !== row.laneId || previous.value.occurredAt !== row.occurredAt) {
521
+ throw new WorkStoreError({
522
+ cause: { handoff: previous.value, row },
523
+ operation: "open.migrate.handoff-identity"
524
+ });
525
+ }
526
+ if (handoffDispatches.length > 1) {
527
+ throw new WorkStoreError({
528
+ cause: { handoffDispatches, row },
529
+ operation: "open.migrate.dispatch-cardinality"
530
+ });
531
+ }
532
+ const linkedMetadata = tables.includes("orchestrator_dispatch_metadata")
533
+ ? Schema.decodeUnknownSync(Schema.Array(MetadataWorkLinkRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, route, work_link AS workLink
534
+ FROM orchestrator_dispatch_metadata
535
+ WHERE work_link IS NOT NULL
536
+ AND CASE WHEN json_valid(work_link)
537
+ THEN json_extract(work_link, '$.handoff.id') END = ?`).all(row.handoffId))
538
+ : [];
539
+ if (tables.includes("orchestrator_dispatch_metadata") &&
540
+ (linkedMetadata.length !== handoffDispatches.length ||
541
+ linkedMetadata.some((metadata) => !handoffDispatches.some(({ dispatchRequestId }) => dispatchRequestId === metadata.dispatchRequestId)))) {
542
+ throw new WorkStoreError({
543
+ cause: { handoffDispatches, linkedMetadata, row },
544
+ operation: "open.migrate.metadata-authority"
545
+ });
546
+ }
547
+ for (const dispatch of handoffDispatches) {
548
+ const dispatchAuthority = (() => {
549
+ try {
550
+ return {
551
+ handoff: Schema.decodeUnknownSync(WorkDecisionHandoff)(JSON.parse(dispatch.record)),
552
+ lineage: Schema.decodeUnknownSync(WorkDispatchHandoff.fields.lineage)(JSON.parse(dispatch.lineage))
553
+ };
554
+ }
555
+ catch (cause) {
556
+ throw new WorkStoreError({ cause, operation: "open.migrate.decode-dispatch-authority" });
557
+ }
558
+ })();
559
+ if (dispatch.laneId !== previous.value.laneId ||
560
+ dispatch.occurredAt !== previous.value.occurredAt ||
561
+ !currentDecisionHandoffEquivalent(dispatchAuthority.handoff, previous.value) ||
562
+ !workDispatchLineageContainedBy(dispatchAuthority.lineage, previous.value.dispatchIds)) {
563
+ throw new WorkStoreError({
564
+ cause: { dispatch, dispatchAuthority, handoff: previous.value },
565
+ operation: "open.migrate.dispatch-authority"
566
+ });
567
+ }
568
+ if (!tables.includes("orchestrator_dispatch_metadata"))
569
+ continue;
570
+ const metadata = linkedMetadata.find(({ dispatchRequestId }) => dispatchRequestId === dispatch.dispatchRequestId);
571
+ if (metadata?.workLink === null || metadata?.workLink === undefined) {
572
+ throw new WorkStoreError({
573
+ cause: { dispatch, metadata },
574
+ operation: "open.migrate.metadata-authority"
575
+ });
576
+ }
577
+ const workLink = (() => {
578
+ try {
579
+ return Schema.decodeUnknownSync(CurrentMetadataWorkLink)(JSON.parse(metadata.workLink));
580
+ }
581
+ catch (cause) {
582
+ throw new WorkStoreError({ cause, operation: "open.migrate.decode-metadata-authority" });
583
+ }
584
+ })();
585
+ requireMetadataAuthority(dispatch.dispatchRequestId, metadata.route, workLink.lineage, "open.migrate.metadata-authority");
586
+ if (!currentDecisionHandoffEquivalent(workLink.handoff, previous.value) ||
587
+ !workDispatchLineageEquivalent(workLink.lineage, dispatchAuthority.lineage)) {
588
+ throw new WorkStoreError({
589
+ cause: { handoff: previous.value, workLink },
590
+ operation: "open.migrate.metadata-authority"
591
+ });
592
+ }
593
+ requireCurrentAgentBindingAuthority(dispatch.dispatchRequestId, previous.value, "open.migrate.current-agent-binding");
594
+ }
595
+ return [];
596
+ }
597
+ case "invalid":
598
+ throw new WorkStoreError({
599
+ cause: previous.cause,
600
+ operation: "open.migrate.invalid-handoff"
601
+ });
602
+ case "previous":
603
+ return [{ previous: previous.value, row }];
604
+ }
605
+ });
606
+ for (const { previous, row } of previousDecisions) {
607
+ const laneRevision = Schema.decodeUnknownSync(LaneRevisionRow)(database.prepare("SELECT revision FROM work_lane_claims WHERE lane_id = ?").get(row.laneId));
608
+ const handoffDispatches = tables.includes("work_dispatch_handoffs")
609
+ ? Schema.decodeUnknownSync(Schema.Array(LegacyDispatchStoredRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, handoff_id AS handoffId,
610
+ lane_id AS laneId, occurred_at AS occurredAt, lineage, record
611
+ FROM work_dispatch_handoffs WHERE handoff_id = ?`).all(row.handoffId))
612
+ : [];
613
+ if (handoffDispatches.length > 1) {
614
+ throw new WorkStoreError({
615
+ cause: { handoffDispatches, row },
616
+ operation: "open.migrate.dispatch-cardinality"
617
+ });
618
+ }
619
+ const linkedMetadata = tables.includes("orchestrator_dispatch_metadata")
620
+ ? Schema.decodeUnknownSync(Schema.Array(MetadataWorkLinkRow))(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, route, work_link AS workLink
621
+ FROM orchestrator_dispatch_metadata
622
+ WHERE work_link IS NOT NULL
623
+ AND CASE WHEN json_valid(work_link)
624
+ THEN json_extract(work_link, '$.handoff.id') END = ?`).all(row.handoffId))
625
+ : [];
626
+ if (tables.includes("orchestrator_dispatch_metadata") &&
627
+ (linkedMetadata.length !== handoffDispatches.length ||
628
+ linkedMetadata.some((metadata) => !handoffDispatches.some(({ dispatchRequestId }) => dispatchRequestId === metadata.dispatchRequestId)))) {
629
+ throw new WorkStoreError({
630
+ cause: { handoffDispatches, linkedMetadata, row },
631
+ operation: "open.migrate.metadata-authority"
632
+ });
633
+ }
634
+ const bindingRows = tables.includes("work_agent_bindings") && tables.includes("work_dispatch_handoffs")
635
+ ? Schema.decodeUnknownSync(Schema.Array(AgentBindingRow))(database.prepare(`SELECT binding.dispatch_request_id AS dispatchRequestId, binding.lane_id AS laneId,
636
+ binding.expected_revision AS expectedRevision, binding.revision,
637
+ binding.agent_id AS agentId, binding.host, binding.record
638
+ FROM work_agent_bindings binding
639
+ JOIN work_dispatch_handoffs dispatch
640
+ ON dispatch.dispatch_request_id = binding.dispatch_request_id
641
+ WHERE dispatch.handoff_id = ?`).all(row.handoffId))
642
+ : [];
643
+ const bindingRow = bindingRows[0];
644
+ const handoffDispatch = bindingRow === undefined
645
+ ? undefined
646
+ : handoffDispatches.find(({ dispatchRequestId }) => dispatchRequestId === bindingRow.dispatchRequestId);
647
+ if (bindingRows.length !== 1 || bindingRow === undefined || handoffDispatch === undefined) {
648
+ throw new WorkStoreError({ cause: { bindingRows, row }, operation: "open.migrate.handoff-revision" });
649
+ }
650
+ const bindingDecision = decodeAgentBindingRow(bindingRow, { dispatchRequestId: handoffDispatch.dispatchRequestId, laneId: previous.laneId }, "open.migrate.agent-binding");
651
+ if (bindingDecision._tag === "invalid")
652
+ throw bindingDecision.error;
653
+ if (laneRevision.revision < bindingDecision.binding.lane.revision) {
654
+ throw new WorkStoreError({
655
+ cause: { binding: bindingDecision.binding, laneRevision, previous },
656
+ operation: "open.migrate.handoff-lane-revision"
657
+ });
658
+ }
659
+ if (bindingDecision.binding.lane.goalId !== previous.goalId) {
660
+ throw new WorkStoreError({
661
+ cause: { binding: bindingDecision.binding, previous },
662
+ operation: "open.migrate.agent-binding.goal"
663
+ });
664
+ }
665
+ requireBindingCompanions(bindingDecision.binding, "open.migrate.agent-binding");
666
+ requireLifecycleAuthority(bindingDecision.binding.request.dispatchRequestId, bindingDecision.binding.checkpoint.occurredAt, "open.migrate.agent-binding.lifecycle");
667
+ const verifiedDispatches = handoffDispatches.map((dispatch) => {
668
+ const dispatchHandoff = Schema.decodeUnknownSync(PreviousWorkDecisionHandoff)(JSON.parse(dispatch.record));
669
+ const lineage = Schema.decodeUnknownSync(WorkDispatchHandoff.fields.lineage)(JSON.parse(dispatch.lineage));
670
+ if (dispatch.laneId !== previous.laneId || dispatch.occurredAt !== previous.occurredAt ||
671
+ !previousDecisionHandoffEquivalent(dispatchHandoff, previous) ||
672
+ !workDispatchLineageContainedBy(lineage, previous.dispatchIds)) {
673
+ throw new WorkStoreError({
674
+ cause: { dispatch, dispatchHandoff, previous },
675
+ operation: "open.migrate.dispatch-authority"
676
+ });
677
+ }
678
+ if (tables.includes("orchestrator_dispatch_metadata")) {
679
+ const metadata = linkedMetadata.find(({ dispatchRequestId }) => dispatchRequestId === dispatch.dispatchRequestId);
680
+ if (metadata?.workLink === null || metadata?.workLink === undefined) {
681
+ throw new WorkStoreError({
682
+ cause: { dispatch, metadata },
683
+ operation: "open.migrate.metadata-authority"
684
+ });
685
+ }
686
+ const workLink = Schema.decodeUnknownSync(PreviousMetadataWorkLink)(JSON.parse(metadata.workLink));
687
+ requireMetadataAuthority(dispatch.dispatchRequestId, metadata.route, workLink.lineage, "open.migrate.metadata-authority");
688
+ if (!previousDecisionHandoffEquivalent(workLink.handoff, previous) ||
689
+ !workDispatchLineageEquivalent(workLink.lineage, lineage)) {
690
+ throw new WorkStoreError({
691
+ cause: { previous, workLink },
692
+ operation: "open.migrate.metadata-authority"
693
+ });
694
+ }
695
+ }
696
+ return { dispatch, lineage };
697
+ });
698
+ const migrated = upgradePreviousDecisionHandoff(previous, bindingDecision.binding.request.expectedRevision);
699
+ if (migrated.id !== row.handoffId || migrated.sessionId !== row.sessionId ||
700
+ migrated.laneId !== row.laneId || migrated.occurredAt !== row.occurredAt) {
701
+ throw new WorkStoreError({ cause: { migrated, row }, operation: "open.migrate.handoff-identity" });
702
+ }
703
+ const encoded = JSON.stringify(migrated);
704
+ updateDecision.run(encoded, row.handoffId);
705
+ updateDispatch?.run(encoded, row.handoffId);
706
+ for (const { dispatch, lineage } of verifiedDispatches) {
707
+ updateMetadata?.run(JSON.stringify({ handoff: migrated, lineage }), dispatch.dispatchRequestId);
708
+ }
709
+ }
710
+ if (decisions.length > 0 || previousDecisions.length > 0) {
711
+ const migratedLedger = Schema.decodeUnknownSync(LedgerBytesRow)(database.prepare(`SELECT COALESCE(SUM(
712
+ length(CAST(handoff_id AS BLOB)) + length(CAST(record AS BLOB))
713
+ ), 0) AS bytes
714
+ FROM work_decision_handoffs`).get());
715
+ if (migratedLedger.bytes > workDecisionMaxBytes) {
716
+ throw new WorkStoreError({
717
+ cause: migratedLedger,
718
+ operation: "open.migrate.handoff-capacity"
719
+ });
720
+ }
721
+ }
722
+ }
723
+ if (tables.includes("orchestrator_dispatch_metadata") && coordinatorDispatchColumns.includes("is_routed")) {
724
+ const pageSize = 512;
725
+ let cursor = null;
726
+ while (true) {
727
+ const input = cursor === null
728
+ ? database.prepare(`SELECT CAST(metadata.rowid AS TEXT) AS rowId,
729
+ metadata.dispatch_request_id AS dispatchRequestId,
730
+ dispatch.activity_idempotency_key AS activityIdempotencyKey,
731
+ dispatch.command, dispatch.is_routed AS isRouted,
732
+ CASE WHEN metadata.work_link IS NULL THEN 0 ELSE 1 END AS hasWorkLink,
733
+ metadata.route
734
+ FROM orchestrator_dispatch_metadata AS metadata
735
+ LEFT JOIN orchestrator_dispatches AS dispatch
736
+ ON dispatch.dispatch_request_id = metadata.dispatch_request_id
737
+ ORDER BY metadata.rowid ASC LIMIT ?`).all(pageSize)
738
+ : database.prepare(`SELECT CAST(metadata.rowid AS TEXT) AS rowId,
739
+ metadata.dispatch_request_id AS dispatchRequestId,
740
+ dispatch.activity_idempotency_key AS activityIdempotencyKey,
741
+ dispatch.command, dispatch.is_routed AS isRouted,
742
+ CASE WHEN metadata.work_link IS NULL THEN 0 ELSE 1 END AS hasWorkLink,
743
+ metadata.route
744
+ FROM orchestrator_dispatch_metadata AS metadata
745
+ LEFT JOIN orchestrator_dispatches AS dispatch
746
+ ON dispatch.dispatch_request_id = metadata.dispatch_request_id
747
+ WHERE metadata.rowid > CAST(? AS INTEGER)
748
+ ORDER BY metadata.rowid ASC LIMIT ?`).all(cursor, pageSize);
749
+ const rows = Schema.decodeUnknownSync(Schema.Array(CoordinatorMetadataRouteRow))(input);
750
+ for (const row of rows) {
751
+ try {
752
+ const isLegacyUnroutedOrphan = row.activityIdempotencyKey === null && row.command === null &&
753
+ row.isRouted === null && row.route === null && row.hasWorkLink === 0;
754
+ if (isLegacyUnroutedOrphan)
755
+ continue;
756
+ if (row.activityIdempotencyKey === null || row.command === null ||
757
+ row.route === null || row.isRouted === null) {
758
+ throw new WorkStoreError({ cause: row, operation: "open.migrate.metadata-route" });
759
+ }
760
+ requireCoordinatorRouteBinding(row.command, row.activityIdempotencyKey, row.route, row.hasWorkLink === 1, { _tag: "routed_discriminator", isRouted: row.isRouted }, "open.migrate.metadata-route");
761
+ }
762
+ catch (cause) {
763
+ if (Schema.is(WorkStoreError)(cause))
764
+ throw cause;
765
+ throw new WorkStoreError({ cause, operation: "open.migrate.metadata-route" });
766
+ }
767
+ }
768
+ const last = rows.at(-1);
769
+ if (rows.length < pageSize || last === undefined)
770
+ break;
771
+ cursor = last.rowId;
772
+ }
773
+ const invalidRoutedMetadata = Schema.decodeUnknownSync(Schema.Array(RoutedMetadataCardinalityRow))(database.prepare(`SELECT dispatch.dispatch_request_id AS dispatchRequestId,
774
+ COUNT(metadata.dispatch_request_id) AS metadataCount
775
+ FROM orchestrator_dispatches AS dispatch
776
+ LEFT JOIN orchestrator_dispatch_metadata AS metadata
777
+ ON metadata.dispatch_request_id = dispatch.dispatch_request_id
778
+ WHERE dispatch.is_routed = 1
779
+ GROUP BY dispatch.dispatch_request_id
780
+ HAVING COUNT(metadata.dispatch_request_id) <> 1
781
+ LIMIT 1`).all())[0];
782
+ if (invalidRoutedMetadata !== undefined) {
783
+ throw new WorkStoreError({
784
+ cause: invalidRoutedMetadata,
785
+ operation: "open.migrate.routed-metadata"
786
+ });
787
+ }
788
+ }
789
+ database.exec("COMMIT");
790
+ transaction = false;
791
+ }
792
+ catch (error) {
793
+ if (transaction)
794
+ database.exec("ROLLBACK");
795
+ throw error;
796
+ }
797
+ };
798
+ const readTransactionLedgerTotals = (database) => Schema.decodeUnknownSync(TransactionLedgerTotalsRow)(database.prepare(`SELECT transaction_count AS transactionCount, transaction_bytes AS transactionBytes
799
+ FROM work_goal_transaction_totals WHERE singleton = 1`).get());
800
+ const readDecisionLedgerTotals = (database) => Schema.decodeUnknownSync(DecisionLedgerTotalsRow)(database.prepare(`SELECT decision_count AS decisionCount, decision_bytes AS decisionBytes
801
+ FROM work_decision_totals WHERE singleton = 1`).get());
802
+ const readLaneOperationLedgerTotals = (database) => Schema.decodeUnknownSync(LaneOperationLedgerTotalsRow)(database.prepare(`SELECT operation_count AS operationCount, operation_bytes AS operationBytes
803
+ FROM work_lane_operation_totals WHERE singleton = 1`).get());
804
+ const ReassignmentRow = Schema.Struct({ approvalJobId: Schema.String, goalId: Schema.String, record: Schema.String });
805
+ /** Applies a reassignment's agent choice; `keep` is only reached when the goal has no target. */
806
+ const withReassignedAgent = (goal, toAgent) => {
807
+ switch (toAgent._tag) {
808
+ case "set":
809
+ return { ...goal, agentHierarchy: { agent: toAgent.agent }, connectTarget: agentConnectTarget(toAgent.agent) };
810
+ case "clear":
811
+ return { ...goal, agentHierarchy: null, connectTarget: null };
812
+ case "keep":
813
+ return goal;
814
+ }
815
+ };
11
816
  const utf8 = new TextEncoder();
12
817
  const encodedBytes = (value) => utf8.encode(JSON.stringify(value)).byteLength;
13
818
  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
- };
819
+ const workTransactionMaxRecords = 16_384;
820
+ const workTransactionMaxBytes = 2 * 1024 * 1024;
821
+ const workLaneMaxRecords = workSnapshotMaxGoals;
822
+ const workLaneMaxBytes = 2 * 1024 * 1024;
823
+ const workLaneOperationMaxRecords = workAgentBindingLaneOperationMaxRecords;
824
+ const workLaneOperationMaxBytes = workAgentBindingLaneOperationMaxBytes;
825
+ const workDecisionMaxRecords = 16_384;
826
+ const workDecisionMaxBytes = 2 * 1024 * 1024;
827
+ const workStoreBusyTimeoutMillis = 5_000;
828
+ const workSnapshotEnvelopeMaxBytes = workAgentBindingSnapshotEnvelopeMaxBytes;
829
+ const maximumSnapshotBytes = workAgentBindingMaximumSnapshotBytes;
830
+ const transactionContent = (events) => JSON.stringify(events);
831
+ export const __herdrWorkMaximumSnapshotBytesForTest = maximumSnapshotBytes;
832
+ export const __herdrWorkEncodedBytesForTest = encodedBytes;
833
+ export const __herdrWorkSnapshotEnvelopeMaxBytesForTest = workSnapshotEnvelopeMaxBytes;
834
+ export const __herdrWorkLaneOperationMaxBytesForTest = workLaneOperationMaxBytes;
31
835
  const decodeRow = (row) => Schema.decodeUnknownEffect(StoredEventRow)(row).pipe(Effect.mapError(storeError("decode.row")), Effect.flatMap(({ record }) => Effect.try({
32
836
  try: () => Schema.decodeUnknownSync(WorkGoalCheckpoint)(JSON.parse(record)),
33
837
  catch: storeError("decode.event")
34
838
  })));
839
+ const claimInputFromClaimed = (claim) => {
840
+ const input = {
841
+ branch: claim.branch,
842
+ expectedRevision: claim.expectedRevision,
843
+ goalId: claim.goalId,
844
+ head: claim.head,
845
+ laneId: claim.laneId,
846
+ operationId: claim.operationId,
847
+ owner: claim.owner,
848
+ parent: claim.parent,
849
+ phase: claim.phase
850
+ };
851
+ return claim.reconciliation === undefined
852
+ ? { ...input, worktree: claim.worktree }
853
+ : { ...input, reconciliation: claim.reconciliation, worktree: claim.worktree };
854
+ };
855
+ const readValidatedLaneLedger = (database, operation) => {
856
+ try {
857
+ const rows = Schema.decodeUnknownSync(Schema.Array(LaneRow))(database.prepare(`SELECT lane_id AS laneId, goal_id AS goalId, operation_id AS operationId,
858
+ phase, revision, record
859
+ FROM work_lane_claims ORDER BY lane_id ASC LIMIT ?`).all(workLaneMaxRecords + 1));
860
+ if (rows.length > workLaneMaxRecords) {
861
+ return {
862
+ _tag: "invalid",
863
+ error: new WorkStoreError({ cause: rows.length, operation: `${operation}.capacity` })
864
+ };
865
+ }
866
+ const entries = [];
867
+ for (const row of rows) {
868
+ const claim = Schema.decodeUnknownSync(WorkLaneClaimed)(JSON.parse(row.record));
869
+ if (claim.goalId !== row.goalId ||
870
+ claim.laneId !== row.laneId ||
871
+ claim.operationId !== row.operationId ||
872
+ claim.phase !== row.phase ||
873
+ claim.revision !== row.revision) {
874
+ return {
875
+ _tag: "invalid",
876
+ error: new WorkStoreError({ cause: { claim, row }, operation: `${operation}.identity-mismatch` })
877
+ };
878
+ }
879
+ entries.push({ claim, row });
880
+ }
881
+ return { _tag: "valid", entries };
882
+ }
883
+ catch (cause) {
884
+ return { _tag: "invalid", error: new WorkStoreError({ cause, operation: `${operation}.decode` }) };
885
+ }
886
+ };
887
+ const admissionConflict = (target, reason) => new WorkAdmissionConflictError({ goalId: target.goalId, laneId: target.laneId, reason });
888
+ /**
889
+ * Splits bindings into each lane's authoritative row (highest lane revision)
890
+ * and the lanes where that revision is shared by more than one row. Earlier
891
+ * rows on a lane were superseded, for example by an approved reassignment.
892
+ */
893
+ const authoritativeBindings = (bindings) => {
894
+ const maxRevision = new Map();
895
+ for (const binding of bindings) {
896
+ const laneId = binding.request.laneId;
897
+ maxRevision.set(laneId, Math.max(maxRevision.get(laneId) ?? 0, binding.lane.revision));
898
+ }
899
+ const latest = bindings.filter((binding) => binding.lane.revision === maxRevision.get(binding.request.laneId));
900
+ const ambiguousLanes = new Set(latest
901
+ .map(({ request }) => request.laneId)
902
+ .filter((laneId, index, laneIds) => laneIds.indexOf(laneId) !== index));
903
+ return { latest, ambiguousLanes };
904
+ };
905
+ /** Reads every durable identity row, including entries omitted by time-window projections. */
906
+ const admissionState = (database, target) => {
907
+ const eventRows = Schema.decodeUnknownSync(Schema.Array(AgentBindingGoalEventRow))(database.prepare(`SELECT event_id AS eventId, goal_id AS goalId, occurred_at AS occurredAt, record
908
+ FROM work_goal_events ORDER BY occurred_at ASC, event_id ASC LIMIT ?`).all(workHistoryMaxEvents + 1));
909
+ const lanes = readValidatedLaneLedger(database, "admission.inspect");
910
+ if (lanes._tag === "invalid")
911
+ throw lanes.error;
912
+ const bindingRows = Schema.decodeUnknownSync(AgentBindingRows)(database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, lane_id AS laneId,
913
+ expected_revision AS expectedRevision, revision, agent_id AS agentId, host, record
914
+ FROM work_agent_bindings ORDER BY dispatch_request_id ASC LIMIT ?`).all(workLaneOperationMaxRecords + 1));
915
+ const operationRows = Schema.decodeUnknownSync(Schema.Array(AgentBindingLaneOperationRow))(database.prepare(`SELECT operation_id AS operationId, lane_id AS laneId, goal_id AS goalId,
916
+ phase, revision, record FROM work_lane_operations ORDER BY operation_id ASC LIMIT ?`).all(workLaneOperationMaxRecords + 1));
917
+ if (eventRows.length > workHistoryMaxEvents || bindingRows.length > workLaneOperationMaxRecords ||
918
+ operationRows.length > workLaneOperationMaxRecords) {
919
+ throw new WorkStoreError({ cause: target, operation: "admission.inspect.capacity" });
920
+ }
921
+ const events = eventRows.map((row) => {
922
+ const decision = decodeAgentBindingGoalEvent(row, "admission.inspect.event");
923
+ if (decision._tag === "invalid")
924
+ throw decision.error;
925
+ return decision.checkpoint;
926
+ });
927
+ const historyError = workHistoryError(events);
928
+ if (historyError !== undefined)
929
+ throw historyError;
930
+ const bindings = bindingRows.map((row) => {
931
+ const binding = Schema.decodeUnknownSync(WorkAgentBinding)(JSON.parse(row.record));
932
+ if (row.dispatchRequestId !== binding.request.dispatchRequestId ||
933
+ row.laneId !== binding.request.laneId || row.revision !== binding.lane.revision ||
934
+ row.agentId !== binding.request.worker.agentId ||
935
+ row.host.toLowerCase() !== binding.request.worker.host.toLowerCase()) {
936
+ throw new WorkStoreError({ cause: row, operation: "admission.inspect.binding-identity" });
937
+ }
938
+ const readback = agentBindingReadbackError(binding, operationRows.find(({ operationId }) => operationId === binding.lane.operationId), eventRows.find(({ eventId }) => eventId === binding.checkpoint.eventId), "admission.inspect.binding");
939
+ if (readback !== undefined)
940
+ throw readback;
941
+ return binding;
942
+ });
943
+ const latest = new Map();
944
+ for (const event of events)
945
+ latest.set(event.goal.id, event);
946
+ const goal = [...latest.values()].find(({ goal }) => goal.review?.url === target.reviewUrl);
947
+ const lane = lanes.entries.find(({ claim }) => claim.laneId === target.laneId)?.claim;
948
+ const authority = authoritativeBindings(bindings);
949
+ const binding = authority.latest.find(({ lane: bound }) => bound.laneId === target.laneId);
950
+ const foreignConflict = events.some(({ goal: entry }) => (entry.review?.url === target.reviewUrl && entry.id !== target.goalId) ||
951
+ (entry.agentHierarchy?.agent.agentId === target.worker.agentId && entry.id !== target.goalId) ||
952
+ (entry.goalFamily?.canonicalGoalId === target.goalId && entry.id !== target.goalId)) || lanes.entries.some(({ claim }) => (claim.goalId === target.goalId && claim.laneId !== target.laneId) ||
953
+ (claim.worktree === target.worktree && claim.laneId !== target.laneId)) ||
954
+ authority.latest.some(({ request }) => request.worker.agentId === target.worker.agentId && request.laneId !== target.laneId) ||
955
+ // A session id stays claimed by its lane even after a reassignment supersedes that binding.
956
+ bindings.some(({ request }) => (request.prospectiveAdmission?.sessionId === target.sessionId && request.laneId !== target.laneId) ||
957
+ (request.existingGoalRecovery?.sessionId === target.sessionId && request.laneId !== target.laneId)) || authority.ambiguousLanes.has(target.laneId);
958
+ const exact = goal !== undefined && lane !== undefined && binding !== undefined &&
959
+ goal.goal.id === target.goalId && goal.goal.state !== "completed" &&
960
+ goal.goal.state !== "deployed" && goal.goal.goalFamily?.role === "canonical" &&
961
+ goal.goal.goalFamily.canonicalGoalId === target.goalId &&
962
+ Equal.equals(goal.goal.owner, target.owner) &&
963
+ lane.goalId === target.goalId && lane.head === target.head &&
964
+ lane.worktree === target.worktree && lane.branch === target.branch &&
965
+ lane.phase !== "shipped" && Equal.equals(lane.owner, target.owner) &&
966
+ binding.lane.laneId === target.laneId &&
967
+ Equal.equals(binding.request.worker, target.worker) &&
968
+ (binding.request.prospectiveAdmission === undefined || (binding.request.prospectiveAdmission.sessionId === target.sessionId &&
969
+ binding.request.prospectiveAdmission.workAssignment === target.expectedWork &&
970
+ binding.request.prospectiveAdmission.baseHead === target.baseHead)) &&
971
+ (binding.request.existingGoalRecovery === undefined ||
972
+ (binding.request.existingGoalRecovery.sessionId === target.sessionId &&
973
+ binding.request.existingGoalRecovery.workAssignment === target.expectedWork &&
974
+ binding.request.existingGoalRecovery.baseHead === target.baseHead));
975
+ if (exact && !foreignConflict) {
976
+ return {
977
+ _tag: "existing",
978
+ target,
979
+ link: Schema.decodeUnknownSync(WorkPullRequestLink)({
980
+ request: {
981
+ repository: target.repository,
982
+ pullRequest: target.pullRequest,
983
+ goalId: target.goalId,
984
+ laneId: target.laneId
985
+ },
986
+ goalEventId: goal.eventId,
987
+ goal: goal.goal,
988
+ lane,
989
+ binding
990
+ })
991
+ };
992
+ }
993
+ const conflicts = events.some(({ goal: entry }) => entry.id === target.goalId || entry.review?.url === target.reviewUrl ||
994
+ entry.goalFamily?.canonicalGoalId === target.goalId ||
995
+ entry.agentHierarchy?.agent.agentId === target.worker.agentId) || lanes.entries.some(({ claim }) => claim.laneId === target.laneId || claim.goalId === target.goalId ||
996
+ claim.worktree === target.worktree) || authority.latest.some(({ request }) => request.worker.agentId === target.worker.agentId) ||
997
+ bindings.some(({ request }) => request.prospectiveAdmission?.sessionId === target.sessionId ||
998
+ request.existingGoalRecovery?.sessionId === target.sessionId);
999
+ if (conflicts) {
1000
+ return { _tag: "conflict", target, reason: "durable PR, goal, lane, worker, or worktree is already bound" };
1001
+ }
1002
+ const snapshot = JSON.stringify({
1003
+ target,
1004
+ eventRows,
1005
+ lanes: lanes.entries.map(({ row }) => row),
1006
+ bindingRows,
1007
+ operationRows
1008
+ });
1009
+ return { _tag: "prospective", target, snapshot };
1010
+ };
1011
+ const readAdmissionState = (database, target) => {
1012
+ database.exec("BEGIN");
1013
+ try {
1014
+ const state = admissionState(database, target);
1015
+ database.exec("ROLLBACK");
1016
+ return state;
1017
+ }
1018
+ catch (cause) {
1019
+ database.exec("ROLLBACK");
1020
+ throw cause;
1021
+ }
1022
+ };
1023
+ const recoveryTables = [
1024
+ "work_goal_events",
1025
+ "work_lane_claims",
1026
+ "work_lane_operations",
1027
+ "work_agent_bindings",
1028
+ "work_goal_transactions",
1029
+ "work_goal_transaction_totals",
1030
+ "work_lane_operation_totals",
1031
+ "work_decision_handoffs",
1032
+ "work_decision_totals"
1033
+ ];
1034
+ /** Validates every historical operation against its indexed row and current lane. */
1035
+ const readRecoveryOperations = (database) => {
1036
+ const lanes = readValidatedLaneLedger(database, "recovery.preflight");
1037
+ if (lanes._tag === "invalid")
1038
+ throw lanes.error;
1039
+ const rows = Schema.decodeUnknownSync(Schema.Array(AgentBindingLaneOperationRow))(database
1040
+ .prepare(`SELECT operation_id AS operationId, lane_id AS laneId, goal_id AS goalId,
1041
+ phase, revision, record FROM work_lane_operations ORDER BY operation_id ASC LIMIT ?`)
1042
+ .all(workLaneOperationMaxRecords + 1));
1043
+ if (rows.length > workLaneOperationMaxRecords) {
1044
+ throw new WorkStoreError({ cause: rows.length, operation: "recovery.preflight.ledger-capacity" });
1045
+ }
1046
+ const current = new Map(lanes.entries.map(({ claim, row }) => [claim.laneId, { claim, row }]));
1047
+ const revisions = new Set();
1048
+ const claims = rows.map((row) => {
1049
+ let claim;
1050
+ try {
1051
+ claim = Schema.decodeUnknownSync(WorkLaneClaimed)(JSON.parse(row.record));
1052
+ }
1053
+ catch (cause) {
1054
+ throw new WorkStoreError({ cause, operation: "recovery.preflight.operation-decode" });
1055
+ }
1056
+ if (claim.operationId !== row.operationId || claim.laneId !== row.laneId ||
1057
+ claim.goalId !== row.goalId || claim.phase !== row.phase || claim.revision !== row.revision) {
1058
+ throw new WorkStoreError({ cause: { claim, row }, operation: "recovery.preflight.operation-identity" });
1059
+ }
1060
+ const lane = current.get(claim.laneId);
1061
+ const key = `${claim.laneId}\u0000${String(claim.revision)}`;
1062
+ if (lane === undefined || lane.claim.goalId !== claim.goalId ||
1063
+ claim.revision > lane.claim.revision || revisions.has(key) ||
1064
+ (claim.revision === lane.claim.revision && row.record !== lane.row.record)) {
1065
+ throw new WorkStoreError({ cause: { claim, row, lane }, operation: "recovery.preflight.operation-companion" });
1066
+ }
1067
+ revisions.add(key);
1068
+ return claim;
1069
+ });
1070
+ const byOperation = new Map(rows.map((row) => [row.operationId, row]));
1071
+ if (lanes.entries.some(({ claim, row }) => {
1072
+ const latest = byOperation.get(claim.operationId);
1073
+ return latest === undefined || latest.revision !== claim.revision || latest.record !== row.record;
1074
+ })) {
1075
+ throw new WorkStoreError({ cause: lanes.entries, operation: "recovery.preflight.missing-latest-operation" });
1076
+ }
1077
+ return { lanes, rows, claims };
1078
+ };
1079
+ /** Reads the complete Work authority, including rows outside every projection window. */
1080
+ const recoveryState = (database, target) => {
1081
+ const inspected = admissionState(database, target);
1082
+ const operations = readRecoveryOperations(database);
1083
+ if (inspected._tag === "existing")
1084
+ return { _tag: "existing", target, link: inspected.link };
1085
+ const conflict = (reason) => ({
1086
+ _tag: "conflict",
1087
+ target,
1088
+ reason
1089
+ });
1090
+ const tables = recoveryTables.map((name) => {
1091
+ const rows = database.prepare(`SELECT * FROM ${name} ORDER BY rowid ASC LIMIT ?`).all(workHistoryMaxEvents + 1);
1092
+ if (rows.length > workHistoryMaxEvents) {
1093
+ throw new WorkStoreError({
1094
+ cause: name,
1095
+ operation: "recovery.preflight.capacity"
1096
+ });
1097
+ }
1098
+ return { name, rows };
1099
+ });
1100
+ const eventRows = Schema.decodeUnknownSync(Schema.Array(AgentBindingGoalEventRow))(database
1101
+ .prepare(`SELECT event_id AS eventId, goal_id AS goalId, occurred_at AS occurredAt, record
1102
+ FROM work_goal_events ORDER BY occurred_at ASC, event_id ASC LIMIT ?`)
1103
+ .all(workHistoryMaxEvents + 1));
1104
+ if (eventRows.length > workHistoryMaxEvents) {
1105
+ throw new WorkStoreError({
1106
+ cause: target,
1107
+ operation: "recovery.preflight.history-capacity"
1108
+ });
1109
+ }
1110
+ const history = eventRows.map((row) => {
1111
+ const decision = decodeAgentBindingGoalEvent(row, "recovery.preflight.event");
1112
+ if (decision._tag === "invalid")
1113
+ throw decision.error;
1114
+ return decision.checkpoint;
1115
+ });
1116
+ const historyError = workHistoryError(history);
1117
+ if (historyError !== undefined)
1118
+ throw historyError;
1119
+ const own = history.filter(({ goal }) => goal.id === target.goalId);
1120
+ const original = own.at(-1);
1121
+ if (original === undefined)
1122
+ return conflict("canonical goal does not exist");
1123
+ if (original.eventId !== target.expectedGoalEventId || original.goal.updatedAt !== target.expectedGoalUpdatedAt) {
1124
+ return conflict("approved goal event or revision is stale");
1125
+ }
1126
+ if (own.some(({ goal }) => goal.goalFamily?.role !== "canonical" ||
1127
+ goal.goalFamily.canonicalGoalId !== target.goalId ||
1128
+ !Equal.equals(goal.owner, target.owner) ||
1129
+ (goal.repository.repository !== target.repository &&
1130
+ (goal.repository.repository !== target.worktree || goal.repository.branch !== target.branch)) ||
1131
+ goal.connectTarget !== null ||
1132
+ (goal.agentHierarchy !== undefined && goal.agentHierarchy !== null) ||
1133
+ (goal.review?.url !== null && goal.review?.url !== undefined && goal.review.url !== target.reviewUrl) ||
1134
+ goal.state === "completed" ||
1135
+ goal.state === "deployed")) {
1136
+ return conflict("canonical goal history is linked, terminal, or belongs to another owner");
1137
+ }
1138
+ const bindingRows = Schema.decodeUnknownSync(AgentBindingRows)(database
1139
+ .prepare(`SELECT dispatch_request_id AS dispatchRequestId, lane_id AS laneId,
1140
+ expected_revision AS expectedRevision, revision, agent_id AS agentId, host, record
1141
+ FROM work_agent_bindings ORDER BY dispatch_request_id ASC LIMIT ?`)
1142
+ .all(workLaneOperationMaxRecords + 1));
1143
+ if (bindingRows.length > workLaneOperationMaxRecords) {
1144
+ throw new WorkStoreError({
1145
+ cause: target,
1146
+ operation: "recovery.preflight.ledger-capacity"
1147
+ });
1148
+ }
1149
+ const bindings = bindingRows.map((row) => Schema.decodeUnknownSync(WorkAgentBinding)(JSON.parse(row.record)));
1150
+ const decisions = Schema.decodeUnknownSync(Schema.Array(DecisionRow))(database
1151
+ .prepare(`SELECT handoff_id AS handoffId, session_id AS sessionId, lane_id AS laneId,
1152
+ occurred_at AS occurredAt, record FROM work_decision_handoffs ORDER BY handoff_id ASC LIMIT ?`)
1153
+ .all(workHistoryMaxEvents + 1));
1154
+ if (decisions.length > workHistoryMaxEvents) {
1155
+ throw new WorkStoreError({
1156
+ cause: target,
1157
+ operation: "recovery.preflight.decision-capacity"
1158
+ });
1159
+ }
1160
+ if (history.some(({ goal }) => goal.id !== target.goalId &&
1161
+ (goal.review?.url === target.reviewUrl ||
1162
+ goal.goalFamily?.canonicalGoalId === target.goalId ||
1163
+ goal.agentHierarchy?.agent.agentId === target.worker.agentId)) ||
1164
+ operations.lanes.entries.some(({ claim }) => claim.goalId === target.goalId || claim.laneId === target.laneId || claim.worktree === target.worktree) ||
1165
+ operations.claims.some(({ goalId, laneId, worktree }) => goalId === target.goalId || laneId === target.laneId || worktree === target.worktree) ||
1166
+ bindings.some(({ request }) => request.laneId === target.laneId ||
1167
+ request.worker.agentId === target.worker.agentId ||
1168
+ request.prospectiveAdmission?.sessionId === target.sessionId ||
1169
+ request.existingGoalRecovery?.sessionId === target.sessionId) ||
1170
+ decisions.some(({ record }) => {
1171
+ const handoff = Schema.decodeUnknownSync(WorkDecisionHandoff)(JSON.parse(record));
1172
+ return (handoff.goalId === target.goalId || handoff.laneId === target.laneId || handoff.sessionId === target.sessionId);
1173
+ })) {
1174
+ return conflict("historical PR, goal, lane, worker, session, or worktree linkage conflicts");
1175
+ }
1176
+ return {
1177
+ _tag: "recoverable",
1178
+ target,
1179
+ original,
1180
+ history,
1181
+ snapshot: JSON.stringify({ target, tables })
1182
+ };
1183
+ };
1184
+ const readRecoveryState = (database, target) => {
1185
+ database.exec("BEGIN");
1186
+ try {
1187
+ const state = recoveryState(database, target);
1188
+ database.exec("ROLLBACK");
1189
+ return state;
1190
+ }
1191
+ catch (cause) {
1192
+ database.exec("ROLLBACK");
1193
+ throw cause;
1194
+ }
1195
+ };
35
1196
  export class WorkStore {
36
1197
  #database;
37
- #fileSystem;
1198
+ #cryptoService;
1199
+ #secureFiles;
38
1200
  path;
39
- constructor(path, fileSystem) {
1201
+ constructor(path, opened, cryptoService) {
40
1202
  this.path = path;
41
- this.#fileSystem = fileSystem;
42
- this.#database = new DatabaseSync(path);
43
- try {
44
- this.#database.exec(`
45
- PRAGMA journal_mode = WAL;
1203
+ this.#database = opened.database;
1204
+ this.#secureFiles = opened.secureFiles.pipe(Effect.mapError(fromPrivateDatabaseError));
1205
+ this.#cryptoService = cryptoService;
1206
+ }
1207
+ static open = Effect.fn("WorkStore.open")(function* (path) {
1208
+ const cryptoService = yield* Crypto.Crypto;
1209
+ const opened = yield* openPrivateSqlite(path, {
1210
+ busyTimeoutMillis: workStoreBusyTimeoutMillis,
1211
+ initialize: (database) => {
1212
+ migrateLegacyAuthorityTables(database);
1213
+ const hadLaneOperationLedger = database.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'work_lane_operations'").get() !== undefined;
1214
+ database.exec(`
46
1215
  CREATE TABLE IF NOT EXISTS work_goal_events (
47
1216
  event_id TEXT PRIMARY KEY,
48
1217
  goal_id TEXT NOT NULL,
@@ -50,28 +1219,1117 @@ export class WorkStore {
50
1219
  record TEXT NOT NULL,
51
1220
  UNIQUE (goal_id, occurred_at)
52
1221
  );
1222
+ CREATE TABLE IF NOT EXISTS work_agent_bindings (
1223
+ dispatch_request_id TEXT PRIMARY KEY,
1224
+ lane_id TEXT NOT NULL,
1225
+ expected_revision INTEGER NOT NULL,
1226
+ revision INTEGER NOT NULL,
1227
+ agent_id TEXT NOT NULL,
1228
+ host TEXT NOT NULL,
1229
+ record TEXT NOT NULL
1230
+ );
1231
+ CREATE INDEX IF NOT EXISTS work_agent_bindings_lane_revision
1232
+ ON work_agent_bindings (lane_id, revision);
1233
+ CREATE TABLE IF NOT EXISTS work_goal_transactions (
1234
+ transaction_id TEXT PRIMARY KEY,
1235
+ record TEXT NOT NULL
1236
+ );
1237
+ CREATE TABLE IF NOT EXISTS work_goal_transaction_totals (
1238
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
1239
+ transaction_count INTEGER NOT NULL CHECK (transaction_count >= 0),
1240
+ transaction_bytes INTEGER NOT NULL CHECK (transaction_bytes >= 0)
1241
+ );
1242
+ CREATE TRIGGER IF NOT EXISTS work_goal_transactions_after_insert
1243
+ AFTER INSERT ON work_goal_transactions
1244
+ BEGIN
1245
+ UPDATE work_goal_transaction_totals
1246
+ SET transaction_count = transaction_count + 1,
1247
+ transaction_bytes = transaction_bytes +
1248
+ length(CAST(NEW.transaction_id AS BLOB)) + length(CAST(NEW.record AS BLOB))
1249
+ WHERE singleton = 1;
1250
+ END;
1251
+ CREATE TABLE IF NOT EXISTS work_lane_claims (
1252
+ lane_id TEXT PRIMARY KEY,
1253
+ goal_id TEXT NOT NULL,
1254
+ operation_id TEXT NOT NULL UNIQUE,
1255
+ phase TEXT NOT NULL,
1256
+ revision INTEGER NOT NULL,
1257
+ record TEXT NOT NULL
1258
+ );
1259
+ CREATE UNIQUE INDEX IF NOT EXISTS work_lane_claims_one_active_goal
1260
+ ON work_lane_claims (goal_id)
1261
+ WHERE phase <> 'shipped';
1262
+ CREATE TABLE IF NOT EXISTS work_lane_operations (
1263
+ operation_id TEXT PRIMARY KEY,
1264
+ lane_id TEXT NOT NULL,
1265
+ goal_id TEXT NOT NULL,
1266
+ phase TEXT NOT NULL,
1267
+ revision INTEGER NOT NULL,
1268
+ record TEXT NOT NULL
1269
+ );
1270
+ CREATE INDEX IF NOT EXISTS work_lane_operations_lane_revision
1271
+ ON work_lane_operations (lane_id, revision);
1272
+ CREATE TABLE IF NOT EXISTS work_lane_operation_totals (
1273
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
1274
+ operation_count INTEGER NOT NULL CHECK (operation_count >= 0),
1275
+ operation_bytes INTEGER NOT NULL CHECK (operation_bytes >= 0)
1276
+ );
1277
+ CREATE TRIGGER IF NOT EXISTS work_lane_operations_after_insert
1278
+ AFTER INSERT ON work_lane_operations
1279
+ BEGIN
1280
+ UPDATE work_lane_operation_totals
1281
+ SET operation_count = operation_count + 1,
1282
+ operation_bytes = operation_bytes +
1283
+ length(CAST(NEW.operation_id AS BLOB)) + length(CAST(NEW.record AS BLOB))
1284
+ WHERE singleton = 1;
1285
+ END;
1286
+ -- One row per approved reassignment. Every row commits together with the
1287
+ -- goal checkpoint whose event id equals its approval job id, so the
1288
+ -- work_goal_events capacity bounds this table too.
1289
+ CREATE TABLE IF NOT EXISTS work_goal_reassignments (
1290
+ approval_job_id TEXT PRIMARY KEY,
1291
+ goal_id TEXT NOT NULL,
1292
+ record TEXT NOT NULL
1293
+ );
1294
+ CREATE TABLE IF NOT EXISTS work_decision_handoffs (
1295
+ handoff_id TEXT PRIMARY KEY,
1296
+ session_id TEXT NOT NULL UNIQUE,
1297
+ lane_id TEXT NOT NULL,
1298
+ occurred_at INTEGER NOT NULL,
1299
+ record TEXT NOT NULL
1300
+ );
1301
+ CREATE TABLE IF NOT EXISTS work_decision_totals (
1302
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
1303
+ decision_count INTEGER NOT NULL CHECK (decision_count >= 0),
1304
+ decision_bytes INTEGER NOT NULL CHECK (decision_bytes >= 0)
1305
+ );
1306
+ CREATE TRIGGER IF NOT EXISTS work_decision_handoffs_after_insert
1307
+ AFTER INSERT ON work_decision_handoffs
1308
+ BEGIN
1309
+ UPDATE work_decision_totals
1310
+ SET decision_count = decision_count + 1,
1311
+ decision_bytes = decision_bytes +
1312
+ length(CAST(NEW.handoff_id AS BLOB)) + length(CAST(NEW.record AS BLOB))
1313
+ WHERE singleton = 1;
1314
+ END;
1315
+ CREATE INDEX IF NOT EXISTS work_decision_handoffs_lane_time
1316
+ ON work_decision_handoffs (lane_id, occurred_at, handoff_id);
1317
+ CREATE INDEX IF NOT EXISTS work_decision_handoffs_session
1318
+ ON work_decision_handoffs (session_id);
53
1319
  `);
1320
+ const columns = Schema.decodeUnknownSync(Schema.Array(Schema.Struct({ name: Schema.String })))(database.prepare("PRAGMA table_info(work_goal_events)").all());
1321
+ if (!columns.some(({ name }) => name === "transaction_id")) {
1322
+ database.exec("ALTER TABLE work_goal_events ADD COLUMN transaction_id TEXT");
1323
+ }
1324
+ if (!hadLaneOperationLedger) {
1325
+ database.exec(`
1326
+ INSERT OR IGNORE INTO work_lane_operations
1327
+ (operation_id, lane_id, goal_id, phase, revision, record)
1328
+ SELECT operation_id, lane_id, goal_id, phase, revision, record
1329
+ FROM work_lane_claims
1330
+ `);
1331
+ }
1332
+ database.exec(`
1333
+ INSERT OR IGNORE INTO work_lane_operation_totals
1334
+ (singleton, operation_count, operation_bytes)
1335
+ SELECT 1, COUNT(*), COALESCE(SUM(
1336
+ length(CAST(operation_id AS BLOB)) + length(CAST(record AS BLOB))
1337
+ ), 0)
1338
+ FROM work_lane_operations
1339
+ `);
1340
+ database.exec(`
1341
+ INSERT OR IGNORE INTO work_goal_transaction_totals
1342
+ (singleton, transaction_count, transaction_bytes)
1343
+ SELECT 1, COUNT(*), COALESCE(SUM(
1344
+ length(CAST(transaction_id AS BLOB)) + length(CAST(record AS BLOB))
1345
+ ), 0)
1346
+ FROM work_goal_transactions
1347
+ `);
1348
+ database.exec(`
1349
+ INSERT OR REPLACE INTO work_decision_totals
1350
+ (singleton, decision_count, decision_bytes)
1351
+ SELECT 1, COUNT(*), COALESCE(SUM(
1352
+ length(CAST(handoff_id AS BLOB)) + length(CAST(record AS BLOB))
1353
+ ), 0)
1354
+ FROM work_decision_handoffs
1355
+ `);
1356
+ }
1357
+ }).pipe(Effect.mapError(fromPrivateDatabaseError));
1358
+ return new WorkStore(path, opened, cryptoService);
1359
+ });
1360
+ recoveryPreflight = Effect.fn("WorkStore.recoveryPreflight")(function* (target) {
1361
+ const decoded = yield* Schema.decodeUnknownEffect(WorkRecoveryTarget)(target).pipe(Effect.mapError(storeError("recovery.preflight.decode")));
1362
+ const state = yield* Effect.try({
1363
+ try: () => readRecoveryState(this.#database, decoded),
1364
+ catch: storeError("recovery.preflight.read")
1365
+ });
1366
+ if (state._tag !== "recoverable")
1367
+ return state;
1368
+ const historyToken = yield* this.#cryptoService
1369
+ .digest("SHA-256", utf8.encode(state.snapshot))
1370
+ .pipe(Effect.mapError(storeError("recovery.preflight.digest")), Effect.map(Hex.encode));
1371
+ const preflight = {
1372
+ _tag: "recoverable",
1373
+ target: decoded,
1374
+ historyToken
1375
+ };
1376
+ return preflight;
1377
+ });
1378
+ recoverExistingGoal = Effect.fn("WorkStore.recoverExistingGoal")(function* (request) {
1379
+ const decoded = yield* Schema.decodeUnknownEffect(WorkExistingGoalRecovery)(request).pipe(Effect.mapError(storeError("recovery.decode")));
1380
+ const target = Schema.decodeUnknownSync(WorkRecoveryTarget)(decoded);
1381
+ const observedAt = yield* Clock.currentTimeMillis;
1382
+ yield* this.secureFiles();
1383
+ const observed = yield* Effect.try({
1384
+ try: () => readRecoveryState(this.#database, target),
1385
+ catch: storeError("recovery.inspect")
1386
+ });
1387
+ const historyToken = observed._tag === "recoverable"
1388
+ ? yield* this.#cryptoService
1389
+ .digest("SHA-256", utf8.encode(observed.snapshot))
1390
+ .pipe(Effect.mapError(storeError("recovery.digest")), Effect.map(Hex.encode))
1391
+ : undefined;
1392
+ const decision = yield* Effect.try({
1393
+ try: () => {
1394
+ let transaction = false;
1395
+ const reject = (reason) => ({
1396
+ _tag: "rejected",
1397
+ error: admissionConflict(target, reason)
1398
+ });
1399
+ try {
1400
+ this.#database.exec("BEGIN IMMEDIATE");
1401
+ transaction = true;
1402
+ const state = recoveryState(this.#database, target);
1403
+ if (state._tag === "existing") {
1404
+ const existing = state.link;
1405
+ const provenance = existing.binding.request.existingGoalRecovery;
1406
+ this.#database.exec("ROLLBACK");
1407
+ transaction = false;
1408
+ return existing.goalEventId === existing.binding.checkpoint.eventId &&
1409
+ existing.lane.revision === 1 &&
1410
+ Equal.equals(existing.goal, existing.binding.checkpoint.goal) &&
1411
+ existing.binding.request.dispatchRequestId === decoded.operationId &&
1412
+ provenance?.expectedGoalEventId === decoded.expectedGoalEventId &&
1413
+ provenance.expectedGoalUpdatedAt === decoded.expectedGoalUpdatedAt &&
1414
+ provenance.expectedHistoryToken === decoded.expectedHistoryToken &&
1415
+ provenance.approvalJobId === decoded.approvalJobId &&
1416
+ provenance.approvalActor === decoded.approvalActor &&
1417
+ provenance.approvalApprovedBy === decoded.approvalApprovedBy &&
1418
+ provenance.approvalApprovedAt === decoded.approvalApprovedAt &&
1419
+ provenance.approvalHash === decoded.approvalHash
1420
+ ? { _tag: "linked", link: existing }
1421
+ : reject("existing linkage is not this exact approved recovery");
1422
+ }
1423
+ if (observed._tag !== "recoverable" ||
1424
+ state._tag !== "recoverable" ||
1425
+ state.snapshot !== observed.snapshot ||
1426
+ historyToken !== decoded.expectedHistoryToken) {
1427
+ this.#database.exec("ROLLBACK");
1428
+ transaction = false;
1429
+ return reject(state._tag === "conflict" ? state.reason : "stale complete-history evidence");
1430
+ }
1431
+ if (this.#database
1432
+ .prepare(`SELECT 1 FROM work_lane_operations WHERE operation_id = ?
1433
+ UNION ALL SELECT 1 FROM work_goal_events WHERE event_id = ?
1434
+ UNION ALL SELECT 1 FROM work_agent_bindings WHERE dispatch_request_id = ?
1435
+ UNION ALL SELECT 1 FROM work_goal_transactions WHERE transaction_id = ?
1436
+ UNION ALL SELECT 1 FROM work_decision_handoffs WHERE handoff_id = ? LIMIT 1`)
1437
+ .get(decoded.operationId, decoded.operationId, decoded.operationId, decoded.operationId, decoded.operationId) !== undefined) {
1438
+ this.#database.exec("ROLLBACK");
1439
+ transaction = false;
1440
+ return reject("operation ID is already used by another durable record");
1441
+ }
1442
+ const at = Math.max(observedAt, state.original.goal.updatedAt + 1);
1443
+ const previous = state.original.goal;
1444
+ const goal = Schema.decodeUnknownSync(WorkGoal)({
1445
+ ...previous,
1446
+ repository: {
1447
+ repository: decoded.repository,
1448
+ branch: decoded.branch
1449
+ },
1450
+ review: previous.review === null || previous.review === undefined
1451
+ ? {
1452
+ state: "not_requested",
1453
+ summary: null,
1454
+ updatedAt: at,
1455
+ url: decoded.reviewUrl
1456
+ }
1457
+ : { ...previous.review, url: decoded.reviewUrl },
1458
+ agentHierarchy: { agent: decoded.worker },
1459
+ connectTarget: agentConnectTarget(decoded.worker),
1460
+ activity: [
1461
+ ...(previous.activity ?? []),
1462
+ {
1463
+ id: decoded.operationId,
1464
+ kind: "status",
1465
+ summary: `Existing canonical goal linked to settled owner by approved Fleet job ${decoded.approvalJobId}`,
1466
+ occurredAt: at
1467
+ }
1468
+ ],
1469
+ updatedAt: at
1470
+ });
1471
+ const checkpoint = Schema.decodeUnknownSync(WorkGoalCheckpoint)({
1472
+ version: "herdr.work.event.v1",
1473
+ eventId: decoded.operationId,
1474
+ occurredAt: at,
1475
+ goal
1476
+ });
1477
+ const lane = Schema.decodeUnknownSync(WorkLaneClaimed)({
1478
+ operationId: decoded.operationId,
1479
+ goalId: decoded.goalId,
1480
+ laneId: decoded.laneId,
1481
+ worktree: decoded.worktree,
1482
+ branch: decoded.branch,
1483
+ head: decoded.head,
1484
+ owner: decoded.owner,
1485
+ parent: null,
1486
+ phase: "claim",
1487
+ expectedRevision: 0,
1488
+ revision: 1
1489
+ });
1490
+ const binding = Schema.decodeUnknownSync(WorkAgentBinding)({
1491
+ version: "herdr.work.agent-binding.v1",
1492
+ request: {
1493
+ version: "herdr.work.agent-binding-request.v1",
1494
+ dispatchRequestId: decoded.operationId,
1495
+ laneId: decoded.laneId,
1496
+ expectedRevision: 0,
1497
+ worker: decoded.worker,
1498
+ existingGoalRecovery: {
1499
+ sessionId: decoded.sessionId,
1500
+ workAssignment: decoded.expectedWork,
1501
+ baseHead: decoded.baseHead,
1502
+ expectedHistoryToken: decoded.expectedHistoryToken,
1503
+ expectedGoalEventId: decoded.expectedGoalEventId,
1504
+ expectedGoalUpdatedAt: decoded.expectedGoalUpdatedAt,
1505
+ approvalJobId: decoded.approvalJobId,
1506
+ approvalActor: decoded.approvalActor,
1507
+ approvalApprovedBy: decoded.approvalApprovedBy,
1508
+ approvalApprovedAt: decoded.approvalApprovedAt,
1509
+ approvalHash: decoded.approvalHash
1510
+ }
1511
+ },
1512
+ lane,
1513
+ checkpoint
1514
+ });
1515
+ const operationTotals = readLaneOperationLedgerTotals(this.#database);
1516
+ const laneRecord = JSON.stringify(lane);
1517
+ const operationBytes = utf8.encode(decoded.operationId).byteLength + utf8.encode(laneRecord).byteLength;
1518
+ const capacity = agentBindingAdmissionError({
1519
+ history: state.history,
1520
+ candidate: checkpoint,
1521
+ operationCount: operationTotals.operationCount,
1522
+ operationBytes: operationTotals.operationBytes,
1523
+ candidateOperationBytes: operationBytes
1524
+ });
1525
+ const lanes = readValidatedLaneLedger(this.#database, "recovery.capacity");
1526
+ if (lanes._tag === "invalid")
1527
+ throw lanes.error;
1528
+ const claimBytes = Schema.decodeUnknownSync(LedgerBytesRow)(this.#database
1529
+ .prepare(`SELECT COALESCE(SUM(length(CAST(lane_id AS BLOB)) + length(CAST(record AS BLOB))), 0) AS bytes
1530
+ FROM work_lane_claims`)
1531
+ .get()).bytes;
1532
+ if (capacity !== undefined ||
1533
+ lanes.entries.length >= workLaneMaxRecords ||
1534
+ claimBytes + utf8.encode(decoded.laneId).byteLength + utf8.encode(laneRecord).byteLength > workLaneMaxBytes) {
1535
+ this.#database.exec("ROLLBACK");
1536
+ transaction = false;
1537
+ return {
1538
+ _tag: "rejected",
1539
+ error: capacity ??
1540
+ new WorkProjectionError({
1541
+ cause: decoded,
1542
+ detail: "existing-goal recovery exceeds Work capacity",
1543
+ reason: "capacity_exceeded"
1544
+ })
1545
+ };
1546
+ }
1547
+ const link = Schema.decodeUnknownSync(WorkPullRequestLink)({
1548
+ request: {
1549
+ repository: decoded.repository,
1550
+ pullRequest: decoded.pullRequest,
1551
+ goalId: decoded.goalId,
1552
+ laneId: decoded.laneId
1553
+ },
1554
+ goalEventId: checkpoint.eventId,
1555
+ goal,
1556
+ lane,
1557
+ binding
1558
+ });
1559
+ this.#database
1560
+ .prepare(`INSERT INTO work_goal_events (event_id, goal_id, occurred_at, record)
1561
+ VALUES (?, ?, ?, ?)`)
1562
+ .run(checkpoint.eventId, checkpoint.goal.id, checkpoint.occurredAt, JSON.stringify(checkpoint));
1563
+ this.#database
1564
+ .prepare(`INSERT INTO work_lane_claims (lane_id, goal_id, operation_id, phase, revision, record)
1565
+ VALUES (?, ?, ?, ?, ?, ?)`)
1566
+ .run(lane.laneId, lane.goalId, lane.operationId, lane.phase, lane.revision, laneRecord);
1567
+ this.#database
1568
+ .prepare(`INSERT INTO work_lane_operations (operation_id, lane_id, goal_id, phase, revision, record)
1569
+ VALUES (?, ?, ?, ?, ?, ?)`)
1570
+ .run(lane.operationId, lane.laneId, lane.goalId, lane.phase, lane.revision, laneRecord);
1571
+ this.#database
1572
+ .prepare(`INSERT INTO work_agent_bindings
1573
+ (dispatch_request_id, lane_id, expected_revision, revision, agent_id, host, record)
1574
+ VALUES (?, ?, ?, ?, ?, ?, ?)`)
1575
+ .run(decoded.operationId, decoded.laneId, 0, 1, decoded.worker.agentId, decoded.worker.host, JSON.stringify(binding));
1576
+ this.#database.exec("COMMIT");
1577
+ transaction = false;
1578
+ return { _tag: "linked", link };
1579
+ }
1580
+ catch (cause) {
1581
+ if (transaction)
1582
+ this.#database.exec("ROLLBACK");
1583
+ throw cause;
1584
+ }
1585
+ },
1586
+ catch: storeError("recovery.transaction")
1587
+ });
1588
+ if (decision._tag === "rejected")
1589
+ return yield* decision.error;
1590
+ return decision.link;
1591
+ });
1592
+ reassign = Effect.fn("WorkStore.reassign")(function* (request) {
1593
+ const decoded = yield* Schema.decodeUnknownEffect(WorkGoalReassignment)(request).pipe(Effect.mapError(storeError("reassign.decode")));
1594
+ const jobId = decoded.approvalJobId;
1595
+ const observedAt = yield* Clock.currentTimeMillis;
1596
+ yield* this.secureFiles();
1597
+ const decision = yield* Effect.try({
1598
+ try: () => {
1599
+ let transaction = false;
1600
+ const reject = (error) => {
1601
+ this.#database.exec("ROLLBACK");
1602
+ transaction = false;
1603
+ return { _tag: "rejected", error };
1604
+ };
1605
+ const ownerMismatch = (laneId, actual) => reject(new WorkGoalOwnerMismatchError({
1606
+ goalId: decoded.goalId,
1607
+ laneId,
1608
+ expectedOwner: { id: decoded.from.id, name: decoded.from.name },
1609
+ actualOwner: { id: actual.id, name: actual.name }
1610
+ }));
1611
+ try {
1612
+ this.#database.exec("BEGIN IMMEDIATE");
1613
+ transaction = true;
1614
+ const priorRaw = this.#database
1615
+ .prepare(`SELECT approval_job_id AS approvalJobId, goal_id AS goalId, record
1616
+ FROM work_goal_reassignments WHERE approval_job_id = ?`)
1617
+ .get(jobId);
1618
+ if (priorRaw !== undefined) {
1619
+ const row = Schema.decodeUnknownSync(ReassignmentRow)(priorRaw);
1620
+ const prior = Schema.decodeUnknownSync(WorkGoalReassigned)(JSON.parse(row.record));
1621
+ if (row.approvalJobId !== prior.reassignment.approvalJobId || row.goalId !== prior.reassignment.goalId) {
1622
+ return reject(new WorkStoreError({ cause: { prior, row }, operation: "reassign.replay.identity-mismatch" }));
1623
+ }
1624
+ if (!Equal.equals(prior.reassignment, decoded)) {
1625
+ return reject(new WorkGoalReassignmentConflictError({ approvalJobId: jobId, reason: "payload_mismatch" }));
1626
+ }
1627
+ this.#database.exec("ROLLBACK");
1628
+ transaction = false;
1629
+ return { _tag: "reassigned", result: prior };
1630
+ }
1631
+ const collision = this.#database
1632
+ .prepare(`SELECT 1 FROM work_goal_events WHERE event_id = ?
1633
+ UNION ALL SELECT 1 FROM work_lane_operations WHERE operation_id = ?
1634
+ UNION ALL SELECT 1 FROM work_agent_bindings WHERE dispatch_request_id = ? LIMIT 1`)
1635
+ .get(jobId, jobId, jobId);
1636
+ if (collision !== undefined) {
1637
+ return reject(new WorkGoalReassignmentConflictError({ approvalJobId: jobId, reason: "identifier_in_use" }));
1638
+ }
1639
+ const eventRows = Schema.decodeUnknownSync(Schema.Array(AgentBindingGoalEventRow))(this.#database
1640
+ .prepare(`SELECT event_id AS eventId, goal_id AS goalId, occurred_at AS occurredAt, record
1641
+ FROM work_goal_events ORDER BY occurred_at ASC, event_id ASC LIMIT ?`)
1642
+ .all(workHistoryMaxEvents + 1));
1643
+ const history = [];
1644
+ for (const row of eventRows) {
1645
+ const event = decodeAgentBindingGoalEvent(row, "reassign.history");
1646
+ if (event._tag === "invalid")
1647
+ return reject(event.error);
1648
+ history.push(event.checkpoint);
1649
+ }
1650
+ const head = history.filter(({ goal }) => goal.id === decoded.goalId).at(-1);
1651
+ if (head === undefined ||
1652
+ head.eventId !== decoded.expectedGoalEventId ||
1653
+ head.goal.updatedAt !== decoded.expectedGoalUpdatedAt) {
1654
+ return reject(new WorkGoalRevisionConflictError({
1655
+ goalId: decoded.goalId,
1656
+ expectedEventId: decoded.expectedGoalEventId,
1657
+ expectedUpdatedAt: decoded.expectedGoalUpdatedAt,
1658
+ actualEventId: head?.eventId ?? null,
1659
+ actualUpdatedAt: head?.goal.updatedAt ?? null
1660
+ }));
1661
+ }
1662
+ if (!Equal.equals(head.goal.owner, decoded.from))
1663
+ return ownerMismatch(null, head.goal.owner);
1664
+ const ledger = readValidatedLaneLedger(this.#database, "reassign.lane");
1665
+ if (ledger._tag === "invalid")
1666
+ return reject(ledger.error);
1667
+ const active = ledger.entries.filter(({ claim }) => claim.goalId === decoded.goalId && claim.phase !== "shipped");
1668
+ if (active.length > 1) {
1669
+ return reject(new WorkStoreError({ cause: active, operation: "reassign.lane.ambiguous" }));
1670
+ }
1671
+ const current = active[0];
1672
+ if (current !== undefined && !Equal.equals(current.claim.owner, decoded.from)) {
1673
+ return ownerMismatch(current.claim.laneId, current.claim.owner);
1674
+ }
1675
+ const latestBindingRaw = current === undefined ? undefined : this.#database
1676
+ .prepare(`SELECT dispatch_request_id AS dispatchRequestId, lane_id AS laneId,
1677
+ expected_revision AS expectedRevision, revision, agent_id AS agentId, host, record
1678
+ FROM work_agent_bindings WHERE lane_id = ? ORDER BY revision DESC LIMIT 1`)
1679
+ .get(current.claim.laneId);
1680
+ const latestBindingRow = latestBindingRaw === undefined
1681
+ ? undefined
1682
+ : Schema.decodeUnknownSync(AgentBindingRow)(latestBindingRaw);
1683
+ const bindingDecision = latestBindingRow === undefined ? null : decodeAgentBindingRow(latestBindingRow, { dispatchRequestId: latestBindingRow.dispatchRequestId, laneId: latestBindingRow.laneId }, "reassign.binding");
1684
+ if (bindingDecision?._tag === "invalid")
1685
+ return reject(bindingDecision.error);
1686
+ const previousBinding = bindingDecision === null ? null : bindingDecision.binding;
1687
+ if (previousBinding !== null && decoded.toAgent._tag !== "set") {
1688
+ return reject(new WorkGoalBindingRequiresAgentError({
1689
+ goalId: decoded.goalId,
1690
+ laneId: previousBinding.request.laneId,
1691
+ dispatchRequestId: previousBinding.request.dispatchRequestId
1692
+ }));
1693
+ }
1694
+ const previous = head.goal;
1695
+ const currentAgentId = previous.agentHierarchy?.agent.agentId ?? previous.connectTarget?.agentId;
1696
+ if (decoded.toAgent._tag === "keep" && currentAgentId !== undefined) {
1697
+ return reject(new WorkGoalAgentTargetConflictError({
1698
+ goalId: decoded.goalId,
1699
+ agentId: currentAgentId,
1700
+ reason: "keep_existing_target",
1701
+ holderId: decoded.goalId
1702
+ }));
1703
+ }
1704
+ if (decoded.toAgent._tag === "set") {
1705
+ const agentId = decoded.toAgent.agent.agentId;
1706
+ const agentConflict = (reason, holderId) => reject(new WorkGoalAgentTargetConflictError({ goalId: decoded.goalId, agentId, reason, holderId }));
1707
+ const latestGoals = new Map();
1708
+ for (const { goal } of history)
1709
+ latestGoals.set(goal.id, goal);
1710
+ const goalHolder = [...latestGoals.values()].find((goal) => goal.id !== decoded.goalId &&
1711
+ (goal.agentHierarchy?.agent.agentId === agentId || goal.connectTarget?.agentId === agentId));
1712
+ if (goalHolder !== undefined)
1713
+ return agentConflict("held_by_other_goal", goalHolder.id);
1714
+ const holderRows = Schema.decodeUnknownSync(AgentBindingRows)(this.#database
1715
+ .prepare(`SELECT dispatch_request_id AS dispatchRequestId, lane_id AS laneId,
1716
+ expected_revision AS expectedRevision, revision, agent_id AS agentId, host, record
1717
+ FROM work_agent_bindings ORDER BY dispatch_request_id ASC LIMIT ?`)
1718
+ .all(workLaneOperationMaxRecords + 1));
1719
+ if (holderRows.length > workLaneOperationMaxRecords) {
1720
+ return reject(new WorkStoreError({ cause: holderRows.length, operation: "reassign.agent-holders.capacity" }));
1721
+ }
1722
+ const holders = [];
1723
+ for (const row of holderRows) {
1724
+ const holder = decodeAgentBindingRow(row, { dispatchRequestId: row.dispatchRequestId, laneId: row.laneId }, "reassign.agent-holders");
1725
+ if (holder._tag === "invalid")
1726
+ return reject(holder.error);
1727
+ holders.push(holder.binding);
1728
+ }
1729
+ const laneHolder = authoritativeBindings(holders).latest.find(({ lane, request }) => lane.goalId !== decoded.goalId && request.worker.agentId === agentId);
1730
+ if (laneHolder !== undefined)
1731
+ return agentConflict("held_by_other_lane", laneHolder.request.laneId);
1732
+ }
1733
+ // occurredAt equals updatedAt for every valid checkpoint; both bound the ordering defensively.
1734
+ const at = Math.max(observedAt, head.occurredAt + 1, head.goal.updatedAt + 1);
1735
+ const reassigned = {
1736
+ ...previous,
1737
+ owner: decoded.to,
1738
+ activity: [
1739
+ ...(previous.activity ?? []),
1740
+ {
1741
+ id: jobId,
1742
+ kind: "status",
1743
+ summary: workReassignActivitySummary(decoded, jobId, decoded.approvalHash),
1744
+ occurredAt: at
1745
+ }
1746
+ ],
1747
+ updatedAt: at
1748
+ };
1749
+ const goal = Schema.decodeUnknownSync(WorkGoal)(withReassignedAgent(reassigned, decoded.toAgent));
1750
+ const checkpoint = Schema.decodeUnknownSync(WorkGoalCheckpoint)({
1751
+ version: "herdr.work.event.v1",
1752
+ eventId: jobId,
1753
+ occurredAt: at,
1754
+ goal
1755
+ });
1756
+ const lane = current === undefined ? null : Schema.decodeUnknownSync(WorkLaneClaimed)({
1757
+ operationId: jobId,
1758
+ goalId: current.claim.goalId,
1759
+ laneId: current.claim.laneId,
1760
+ worktree: current.claim.worktree,
1761
+ branch: current.claim.branch,
1762
+ head: current.claim.head,
1763
+ owner: decoded.to,
1764
+ parent: current.claim.parent,
1765
+ phase: current.claim.phase,
1766
+ expectedRevision: current.claim.revision,
1767
+ revision: current.claim.revision + 1
1768
+ });
1769
+ const binding = previousBinding === null || lane === null || decoded.toAgent._tag !== "set"
1770
+ ? null
1771
+ : Schema.decodeUnknownSync(WorkAgentBinding)({
1772
+ version: "herdr.work.agent-binding.v1",
1773
+ request: {
1774
+ version: "herdr.work.agent-binding-request.v1",
1775
+ dispatchRequestId: jobId,
1776
+ laneId: lane.laneId,
1777
+ expectedRevision: lane.expectedRevision,
1778
+ worker: decoded.toAgent.agent,
1779
+ ownerReassignment: {
1780
+ previousDispatchRequestId: previousBinding.request.dispatchRequestId,
1781
+ approvalJobId: jobId,
1782
+ approvalActor: decoded.approvalActor,
1783
+ approvalApprovedBy: decoded.approvalApprovedBy,
1784
+ approvalApprovedAt: decoded.approvalApprovedAt,
1785
+ approvalHash: decoded.approvalHash
1786
+ }
1787
+ },
1788
+ lane,
1789
+ checkpoint
1790
+ });
1791
+ const result = Schema.decodeUnknownSync(WorkGoalReassigned)({
1792
+ reassignment: decoded,
1793
+ checkpoint,
1794
+ lane,
1795
+ binding
1796
+ });
1797
+ const familyError = validateGoalFamilyHistory([...history, checkpoint]);
1798
+ if (familyError !== undefined)
1799
+ return reject(familyError);
1800
+ const laneRecord = lane === null ? null : JSON.stringify(lane);
1801
+ // A goal without a lane appends no lane operation, so only a lane rewrite is held to that ledger.
1802
+ const operationTotals = lane === null
1803
+ ? { operationCount: 0, operationBytes: 0 }
1804
+ : readLaneOperationLedgerTotals(this.#database);
1805
+ const capacity = agentBindingAdmissionError({
1806
+ history,
1807
+ candidate: checkpoint,
1808
+ operationCount: operationTotals.operationCount,
1809
+ operationBytes: operationTotals.operationBytes,
1810
+ candidateOperationBytes: laneRecord === null
1811
+ ? 0
1812
+ : utf8.encode(jobId).byteLength + utf8.encode(laneRecord).byteLength
1813
+ });
1814
+ if (capacity !== undefined)
1815
+ return reject(capacity);
1816
+ if (current !== undefined && laneRecord !== null) {
1817
+ const claimBytes = Schema.decodeUnknownSync(LedgerBytesRow)(this.#database
1818
+ .prepare(`SELECT COALESCE(SUM(length(CAST(lane_id AS BLOB)) + length(CAST(record AS BLOB))), 0) AS bytes
1819
+ FROM work_lane_claims`)
1820
+ .get()).bytes;
1821
+ if (claimBytes - utf8.encode(current.row.record).byteLength + utf8.encode(laneRecord).byteLength >
1822
+ workLaneMaxBytes) {
1823
+ return reject(new WorkProjectionError({
1824
+ cause: decoded,
1825
+ detail: `work lane claims cannot exceed ${workLaneMaxBytes} encoded bytes`,
1826
+ reason: "capacity_exceeded"
1827
+ }));
1828
+ }
1829
+ }
1830
+ this.#database
1831
+ .prepare("INSERT INTO work_goal_events (event_id, goal_id, occurred_at, record) VALUES (?, ?, ?, ?)")
1832
+ .run(checkpoint.eventId, checkpoint.goal.id, checkpoint.occurredAt, JSON.stringify(checkpoint));
1833
+ if (lane !== null && laneRecord !== null) {
1834
+ const changes = this.#database
1835
+ .prepare(`UPDATE work_lane_claims SET operation_id = ?, revision = ?, record = ?
1836
+ WHERE lane_id = ? AND revision = ?`)
1837
+ .run(lane.operationId, lane.revision, laneRecord, lane.laneId, lane.expectedRevision).changes;
1838
+ if (changes !== 1 && changes !== 1n) {
1839
+ return reject(new WorkStoreError({ cause: lane, operation: "reassign.lane.update" }));
1840
+ }
1841
+ this.#database
1842
+ .prepare(`INSERT INTO work_lane_operations (operation_id, lane_id, goal_id, phase, revision, record)
1843
+ VALUES (?, ?, ?, ?, ?, ?)`)
1844
+ .run(lane.operationId, lane.laneId, lane.goalId, lane.phase, lane.revision, laneRecord);
1845
+ }
1846
+ if (binding !== null) {
1847
+ this.#database
1848
+ .prepare(`INSERT INTO work_agent_bindings
1849
+ (dispatch_request_id, lane_id, expected_revision, revision, agent_id, host, record)
1850
+ VALUES (?, ?, ?, ?, ?, ?, ?)`)
1851
+ .run(binding.request.dispatchRequestId, binding.request.laneId, binding.request.expectedRevision, binding.lane.revision, binding.request.worker.agentId, binding.request.worker.host, JSON.stringify(binding));
1852
+ }
1853
+ this.#database
1854
+ .prepare("INSERT INTO work_goal_reassignments (approval_job_id, goal_id, record) VALUES (?, ?, ?)")
1855
+ .run(jobId, decoded.goalId, JSON.stringify(result));
1856
+ this.#database.exec("COMMIT");
1857
+ transaction = false;
1858
+ return { _tag: "reassigned", result };
1859
+ }
1860
+ catch (cause) {
1861
+ if (transaction)
1862
+ this.#database.exec("ROLLBACK");
1863
+ throw cause;
1864
+ }
1865
+ },
1866
+ catch: storeError("reassign.transaction")
1867
+ });
1868
+ if (decision._tag === "rejected")
1869
+ return yield* decision.error;
1870
+ return decision.result;
1871
+ });
1872
+ admissionPreflight = Effect.fn("WorkStore.admissionPreflight")(function* (target) {
1873
+ const decoded = yield* Schema.decodeUnknownEffect(WorkAdmissionTarget)(target).pipe(Effect.mapError(storeError("admission.preflight.decode")));
1874
+ const state = yield* Effect.try({
1875
+ try: () => readAdmissionState(this.#database, decoded),
1876
+ catch: storeError("admission.preflight.read")
1877
+ });
1878
+ if (state._tag !== "prospective")
1879
+ return state;
1880
+ const absenceToken = yield* this.#cryptoService.digest("SHA-256", new TextEncoder().encode(state.snapshot)).pipe(Effect.mapError(storeError("admission.preflight.digest")), Effect.map(Hex.encode));
1881
+ const preflight = { _tag: "prospective", target: state.target, absenceToken };
1882
+ return preflight;
1883
+ });
1884
+ admitExistingOwner = Effect.fn("WorkStore.admitExistingOwner")(function* (request) {
1885
+ const decoded = yield* Schema.decodeUnknownEffect(WorkProspectiveAdmission)(request).pipe(Effect.mapError(storeError("admission.decode")));
1886
+ const target = Schema.decodeUnknownSync(WorkAdmissionTarget)(decoded);
1887
+ const now = yield* Clock.currentTimeMillis;
1888
+ yield* this.secureFiles();
1889
+ const observed = yield* Effect.try({
1890
+ try: () => readAdmissionState(this.#database, target),
1891
+ catch: storeError("admission.inspect")
1892
+ });
1893
+ const absenceToken = observed._tag === "prospective"
1894
+ ? yield* this.#cryptoService.digest("SHA-256", new TextEncoder().encode(observed.snapshot)).pipe(Effect.mapError(storeError("admission.digest")), Effect.map(Hex.encode))
1895
+ : undefined;
1896
+ const decision = yield* Effect.try({
1897
+ try: () => {
1898
+ let transaction = false;
1899
+ try {
1900
+ this.#database.exec("BEGIN IMMEDIATE");
1901
+ transaction = true;
1902
+ const reject = (reason) => ({
1903
+ _tag: "rejected",
1904
+ error: admissionConflict(target, reason)
1905
+ });
1906
+ const state = admissionState(this.#database, target);
1907
+ if (state._tag === "existing") {
1908
+ const existing = state.link;
1909
+ const provenance = existing.binding.request.prospectiveAdmission;
1910
+ this.#database.exec("ROLLBACK");
1911
+ transaction = false;
1912
+ return existing.binding.request.dispatchRequestId === decoded.operationId &&
1913
+ provenance?.expectedAbsenceToken === decoded.expectedAbsenceToken &&
1914
+ provenance?.approvalJobId === decoded.approvalJobId &&
1915
+ provenance.approvalActor === decoded.approvalActor &&
1916
+ existing.goal.title === decoded.title &&
1917
+ existing.goal.summary === decoded.summary &&
1918
+ existing.goal.detail === decoded.detail
1919
+ ? { _tag: "admitted", link: existing }
1920
+ : reject("existing admission is not this exact approved operation");
1921
+ }
1922
+ if (state._tag !== "prospective" || observed._tag !== "prospective" ||
1923
+ state.snapshot !== observed.snapshot || absenceToken !== decoded.expectedAbsenceToken) {
1924
+ this.#database.exec("ROLLBACK");
1925
+ transaction = false;
1926
+ return reject(state._tag === "conflict" ? state.reason : "stale absence evidence");
1927
+ }
1928
+ if (this.#database.prepare(`SELECT 1 FROM work_lane_operations WHERE operation_id = ?
1929
+ UNION ALL SELECT 1 FROM work_goal_events WHERE event_id IN (?, ?)
1930
+ UNION ALL SELECT 1 FROM work_agent_bindings WHERE dispatch_request_id = ? LIMIT 1`).get(decoded.operationId, decoded.operationId, `${decoded.operationId}.admission`, decoded.operationId) !==
1931
+ undefined) {
1932
+ this.#database.exec("ROLLBACK");
1933
+ transaction = false;
1934
+ return reject("operation ID is already used by another durable record");
1935
+ }
1936
+ if (now < 1) {
1937
+ this.#database.exec("ROLLBACK");
1938
+ transaction = false;
1939
+ return reject("admission clock cannot produce two ordered checkpoints");
1940
+ }
1941
+ const createdAt = now - 1;
1942
+ const goal = Schema.decodeUnknownSync(WorkGoal)({
1943
+ id: decoded.goalId,
1944
+ title: decoded.title,
1945
+ summary: decoded.summary,
1946
+ detail: decoded.detail,
1947
+ state: "review",
1948
+ owner: decoded.owner,
1949
+ repository: { repository: decoded.repository, branch: decoded.branch },
1950
+ spend: null,
1951
+ delivery: "pull_request",
1952
+ blocker: null,
1953
+ connectTarget: null,
1954
+ goalFamily: { canonicalGoalId: decoded.goalId, role: "canonical" },
1955
+ activity: [{
1956
+ id: `${decoded.operationId}.admission`,
1957
+ kind: "status",
1958
+ summary: `Prospective admission of an existing owner by approved Fleet job ${decoded.approvalJobId}`,
1959
+ occurredAt: createdAt
1960
+ }],
1961
+ review: { state: "requested", summary: null, updatedAt: createdAt, url: decoded.reviewUrl },
1962
+ createdAt,
1963
+ updatedAt: createdAt
1964
+ });
1965
+ const initial = Schema.decodeUnknownSync(WorkGoalCheckpoint)({
1966
+ version: "herdr.work.event.v1",
1967
+ eventId: `${decoded.operationId}.admission`,
1968
+ occurredAt: createdAt,
1969
+ goal
1970
+ });
1971
+ const lane = Schema.decodeUnknownSync(WorkLaneClaimed)({
1972
+ operationId: decoded.operationId,
1973
+ goalId: decoded.goalId,
1974
+ laneId: decoded.laneId,
1975
+ worktree: decoded.worktree,
1976
+ branch: decoded.branch,
1977
+ head: decoded.head,
1978
+ owner: decoded.owner,
1979
+ parent: null,
1980
+ phase: "review",
1981
+ expectedRevision: 0,
1982
+ revision: 1
1983
+ });
1984
+ const binding = Schema.decodeUnknownSync(WorkAgentBinding)({
1985
+ version: "herdr.work.agent-binding.v1",
1986
+ request: {
1987
+ version: "herdr.work.agent-binding-request.v1",
1988
+ dispatchRequestId: decoded.operationId,
1989
+ laneId: decoded.laneId,
1990
+ expectedRevision: 0,
1991
+ worker: decoded.worker,
1992
+ prospectiveAdmission: {
1993
+ sessionId: decoded.sessionId,
1994
+ workAssignment: decoded.expectedWork,
1995
+ baseHead: decoded.baseHead,
1996
+ expectedAbsenceToken: decoded.expectedAbsenceToken,
1997
+ approvalJobId: decoded.approvalJobId,
1998
+ approvalActor: decoded.approvalActor
1999
+ }
2000
+ },
2001
+ lane,
2002
+ checkpoint: {
2003
+ version: "herdr.work.event.v1",
2004
+ eventId: decoded.operationId,
2005
+ occurredAt: now,
2006
+ goal: {
2007
+ ...goal,
2008
+ agentHierarchy: { agent: decoded.worker },
2009
+ connectTarget: agentConnectTarget(decoded.worker),
2010
+ updatedAt: now
2011
+ }
2012
+ }
2013
+ });
2014
+ const existingRows = Schema.decodeUnknownSync(Schema.Array(AgentBindingGoalEventRow))(this.#database.prepare(`SELECT event_id AS eventId, goal_id AS goalId, occurred_at AS occurredAt, record
2015
+ FROM work_goal_events ORDER BY occurred_at ASC, event_id ASC LIMIT ?`).all(workHistoryMaxEvents + 1));
2016
+ const history = existingRows.map((row) => {
2017
+ const result = decodeAgentBindingGoalEvent(row, "admission.history");
2018
+ if (result._tag === "invalid")
2019
+ throw result.error;
2020
+ return result.checkpoint;
2021
+ });
2022
+ const operationTotals = readLaneOperationLedgerTotals(this.#database);
2023
+ const laneRecord = JSON.stringify(lane);
2024
+ const operationBytes = utf8.encode(decoded.operationId).byteLength + utf8.encode(laneRecord).byteLength;
2025
+ 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
2026
+ FROM work_lane_claims`).get()).bytes;
2027
+ const capacity = agentBindingAdmissionError({
2028
+ history: [...history, initial],
2029
+ candidate: binding.checkpoint,
2030
+ operationCount: operationTotals.operationCount,
2031
+ operationBytes: operationTotals.operationBytes,
2032
+ candidateOperationBytes: operationBytes
2033
+ });
2034
+ const lanes = readValidatedLaneLedger(this.#database, "admission.capacity");
2035
+ if (lanes._tag === "invalid")
2036
+ throw lanes.error;
2037
+ if (capacity !== undefined || lanes.entries.length >= workLaneMaxRecords ||
2038
+ new Set(history.map(({ goal: previous }) => previous.id)).size >= workSnapshotMaxGoals ||
2039
+ claimBytes + utf8.encode(decoded.laneId).byteLength + utf8.encode(laneRecord).byteLength > workLaneMaxBytes) {
2040
+ this.#database.exec("ROLLBACK");
2041
+ transaction = false;
2042
+ return {
2043
+ _tag: "rejected",
2044
+ error: capacity ?? new WorkProjectionError({
2045
+ cause: decoded,
2046
+ detail: "prospective admission exceeds Work capacity",
2047
+ reason: "capacity_exceeded"
2048
+ })
2049
+ };
2050
+ }
2051
+ const link = Schema.decodeUnknownSync(WorkPullRequestLink)({
2052
+ request: {
2053
+ repository: decoded.repository,
2054
+ pullRequest: decoded.pullRequest,
2055
+ goalId: decoded.goalId,
2056
+ laneId: decoded.laneId
2057
+ },
2058
+ goalEventId: binding.checkpoint.eventId,
2059
+ goal: binding.checkpoint.goal,
2060
+ lane,
2061
+ binding
2062
+ });
2063
+ this.#database.prepare(`INSERT INTO work_goal_events (event_id, goal_id, occurred_at, record)
2064
+ VALUES (?, ?, ?, ?)`).run(initial.eventId, initial.goal.id, initial.occurredAt, JSON.stringify(initial));
2065
+ this.#database.prepare(`INSERT INTO work_goal_events (event_id, goal_id, occurred_at, record)
2066
+ VALUES (?, ?, ?, ?)`).run(binding.checkpoint.eventId, decoded.goalId, now, JSON.stringify(binding.checkpoint));
2067
+ this.#database.prepare(`INSERT INTO work_lane_claims (lane_id, goal_id, operation_id, phase, revision, record)
2068
+ VALUES (?, ?, ?, ?, ?, ?)`).run(lane.laneId, lane.goalId, lane.operationId, lane.phase, lane.revision, laneRecord);
2069
+ this.#database.prepare(`INSERT INTO work_lane_operations (operation_id, lane_id, goal_id, phase, revision, record)
2070
+ VALUES (?, ?, ?, ?, ?, ?)`).run(lane.operationId, lane.laneId, lane.goalId, lane.phase, lane.revision, laneRecord);
2071
+ this.#database.prepare(`INSERT INTO work_agent_bindings
2072
+ (dispatch_request_id, lane_id, expected_revision, revision, agent_id, host, record)
2073
+ VALUES (?, ?, ?, ?, ?, ?, ?)`).run(decoded.operationId, decoded.laneId, 0, 1, decoded.worker.agentId, decoded.worker.host, JSON.stringify(binding));
2074
+ this.#database.exec("COMMIT");
2075
+ transaction = false;
2076
+ return { _tag: "admitted", link };
2077
+ }
2078
+ catch (cause) {
2079
+ if (transaction)
2080
+ this.#database.exec("ROLLBACK");
2081
+ throw cause;
2082
+ }
2083
+ },
2084
+ catch: storeError("admission.transaction")
2085
+ });
2086
+ if (decision._tag === "rejected")
2087
+ return yield* decision.error;
2088
+ return decision.link;
2089
+ });
2090
+ bindAgent = Effect.fn("WorkStore.bindAgent")(function* (request) {
2091
+ const decoded = yield* Schema.decodeUnknownEffect(WorkAgentBindingRequest)(request).pipe(Effect.mapError(storeError("agent-binding.decode")));
2092
+ const observedAt = yield* Clock.currentTimeMillis;
2093
+ yield* this.secureFiles();
2094
+ const decision = yield* Effect.try({
2095
+ try: () => {
2096
+ let inTransaction = false;
2097
+ try {
2098
+ this.#database.exec("BEGIN IMMEDIATE");
2099
+ inTransaction = true;
2100
+ const reject = (error) => {
2101
+ this.#database.exec("ROLLBACK");
2102
+ inTransaction = false;
2103
+ return { _tag: "rejected", error };
2104
+ };
2105
+ const existingRaw = this.#database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, lane_id AS laneId,
2106
+ expected_revision AS expectedRevision, revision, agent_id AS agentId, host, record
2107
+ FROM work_agent_bindings WHERE dispatch_request_id = ?`).get(decoded.dispatchRequestId);
2108
+ if (existingRaw !== undefined) {
2109
+ const row = Schema.decodeUnknownSync(AgentBindingRow)(existingRaw);
2110
+ const binding = Schema.decodeUnknownSync(WorkAgentBinding)(JSON.parse(row.record));
2111
+ if (row.dispatchRequestId !== binding.request.dispatchRequestId ||
2112
+ row.laneId !== binding.request.laneId ||
2113
+ row.expectedRevision !== binding.request.expectedRevision ||
2114
+ row.revision !== binding.lane.revision ||
2115
+ row.agentId !== binding.request.worker.agentId ||
2116
+ row.host.toLowerCase() !== binding.request.worker.host.toLowerCase()) {
2117
+ return reject(new WorkStoreError({
2118
+ cause: { binding, row },
2119
+ operation: "agent-binding.identity-mismatch"
2120
+ }));
2121
+ }
2122
+ const laneRow = this.#database.prepare(`SELECT operation_id AS operationId, lane_id AS laneId, goal_id AS goalId,
2123
+ phase, revision, record
2124
+ FROM work_lane_operations WHERE operation_id = ?`).get(binding.lane.operationId);
2125
+ const checkpointRow = this.#database.prepare(`SELECT event_id AS eventId, goal_id AS goalId, occurred_at AS occurredAt, record
2126
+ FROM work_goal_events WHERE event_id = ?`).get(binding.checkpoint.eventId);
2127
+ const readbackError = agentBindingReadbackError(binding, laneRow === undefined ? undefined : Schema.decodeUnknownSync(AgentBindingLaneOperationRow)(laneRow), checkpointRow === undefined
2128
+ ? undefined
2129
+ : Schema.decodeUnknownSync(AgentBindingGoalEventRow)(checkpointRow), "agent-binding.replay");
2130
+ if (readbackError !== undefined)
2131
+ return reject(readbackError);
2132
+ this.#database.exec("ROLLBACK");
2133
+ inTransaction = false;
2134
+ return Equal.equals(binding.request, decoded)
2135
+ ? { _tag: "bound", binding }
2136
+ : {
2137
+ _tag: "rejected",
2138
+ error: new WorkAgentBindingConflictError({ dispatchRequestId: decoded.dispatchRequestId })
2139
+ };
2140
+ }
2141
+ const laneLedger = readValidatedLaneLedger(this.#database, "agent-binding.authority");
2142
+ if (laneLedger._tag === "invalid")
2143
+ return reject(laneLedger.error);
2144
+ const lane = laneLedger.entries.find(({ claim }) => claim.laneId === decoded.laneId)?.claim;
2145
+ if (lane === undefined) {
2146
+ return reject(new WorkAgentBindingAuthorityError({
2147
+ actualRevision: 0,
2148
+ expectedRevision: decoded.expectedRevision,
2149
+ laneId: decoded.laneId,
2150
+ reason: "missing_lane"
2151
+ }));
2152
+ }
2153
+ if (lane.revision !== decoded.expectedRevision) {
2154
+ return reject(new WorkAgentBindingAuthorityError({
2155
+ actualRevision: lane.revision,
2156
+ expectedRevision: decoded.expectedRevision,
2157
+ laneId: decoded.laneId,
2158
+ reason: "stale_revision"
2159
+ }));
2160
+ }
2161
+ if (lane.phase === "shipped") {
2162
+ return reject(new WorkAgentBindingAuthorityError({
2163
+ actualRevision: lane.revision,
2164
+ expectedRevision: decoded.expectedRevision,
2165
+ laneId: decoded.laneId,
2166
+ reason: "shipped_lane"
2167
+ }));
2168
+ }
2169
+ const activeGoalClaims = laneLedger.entries
2170
+ .map(({ claim }) => claim)
2171
+ .filter((claim) => claim.goalId === lane.goalId && claim.phase !== "shipped");
2172
+ if (activeGoalClaims.length !== 1 || activeGoalClaims[0]?.laneId !== lane.laneId) {
2173
+ return reject(new WorkStoreError({
2174
+ cause: { activeGoalClaims, lane },
2175
+ operation: "agent-binding.goal-authority-conflict"
2176
+ }));
2177
+ }
2178
+ const currentRaw = this.#database.prepare(`SELECT event_id AS eventId, goal_id AS goalId, occurred_at AS occurredAt, record
2179
+ FROM work_goal_events
2180
+ WHERE goal_id = ? ORDER BY occurred_at DESC, event_id DESC LIMIT 1`).get(lane.goalId);
2181
+ if (currentRaw === undefined) {
2182
+ return reject(new WorkAgentBindingAuthorityError({
2183
+ actualRevision: lane.revision,
2184
+ expectedRevision: decoded.expectedRevision,
2185
+ laneId: decoded.laneId,
2186
+ reason: "missing_goal"
2187
+ }));
2188
+ }
2189
+ const currentDecision = decodeAgentBindingGoalEvent(Schema.decodeUnknownSync(AgentBindingGoalEventRow)(currentRaw), "agent-binding.current-goal");
2190
+ if (currentDecision._tag === "invalid")
2191
+ return reject(currentDecision.error);
2192
+ const current = currentDecision.checkpoint;
2193
+ if (current.goal.state === "completed") {
2194
+ return reject(new WorkAgentBindingAuthorityError({
2195
+ actualRevision: lane.revision,
2196
+ expectedRevision: decoded.expectedRevision,
2197
+ laneId: decoded.laneId,
2198
+ reason: "terminal_goal"
2199
+ }));
2200
+ }
2201
+ if (current.occurredAt > observedAt) {
2202
+ return reject(new WorkProjectionError({
2203
+ cause: { checkpoint: current, observedAt },
2204
+ detail: "work agent binding cannot advance beyond the observed clock",
2205
+ reason: "inconsistent_history"
2206
+ }));
2207
+ }
2208
+ if (current.occurredAt >= maximumTimestamp) {
2209
+ return reject(new WorkProjectionError({
2210
+ cause: current,
2211
+ detail: "work agent binding timestamp cannot advance",
2212
+ reason: "capacity_exceeded"
2213
+ }));
2214
+ }
2215
+ const occurredAt = Math.max(observedAt, current.occurredAt + 1);
2216
+ const binding = makeWorkAgentBinding(decoded, lane, current, occurredAt);
2217
+ const historyRows = Schema.decodeUnknownSync(Schema.Array(AgentBindingGoalEventRow))(this.#database.prepare(`SELECT event_id AS eventId, goal_id AS goalId, occurred_at AS occurredAt, record
2218
+ FROM work_goal_events ORDER BY occurred_at ASC, event_id ASC`).all());
2219
+ const history = [];
2220
+ for (const row of historyRows) {
2221
+ const historyDecision = decodeAgentBindingGoalEvent(row, "agent-binding.history");
2222
+ if (historyDecision._tag === "invalid")
2223
+ return reject(historyDecision.error);
2224
+ history.push(historyDecision.checkpoint);
2225
+ }
2226
+ const collision = this.#database.prepare(`SELECT record FROM work_goal_events
2227
+ WHERE event_id = ? OR (goal_id = ? AND occurred_at = ?)`).get(binding.checkpoint.eventId, binding.checkpoint.goal.id, binding.checkpoint.occurredAt);
2228
+ if (collision !== undefined) {
2229
+ return reject(new WorkStoreError({ cause: collision, operation: "agent-binding.event-collision" }));
2230
+ }
2231
+ const operationTotals = readLaneOperationLedgerTotals(this.#database);
2232
+ const encodedLane = JSON.stringify(binding.lane);
2233
+ const operationBytes = utf8.encode(binding.lane.operationId).byteLength + utf8.encode(encodedLane).byteLength;
2234
+ const admissionError = agentBindingAdmissionError({
2235
+ candidate: binding.checkpoint,
2236
+ candidateOperationBytes: operationBytes,
2237
+ history,
2238
+ operationBytes: operationTotals.operationBytes,
2239
+ operationCount: operationTotals.operationCount
2240
+ });
2241
+ if (admissionError !== undefined)
2242
+ return reject(admissionError);
2243
+ const laneChanges = this.#database.prepare(`UPDATE work_lane_claims
2244
+ SET operation_id = ?, revision = ?, record = ?
2245
+ WHERE lane_id = ? AND revision = ?`).run(binding.lane.operationId, binding.lane.revision, encodedLane, decoded.laneId, decoded.expectedRevision).changes;
2246
+ if (laneChanges !== 1 && laneChanges !== 1n) {
2247
+ return reject(new WorkAgentBindingAuthorityError({
2248
+ actualRevision: lane.revision,
2249
+ expectedRevision: decoded.expectedRevision,
2250
+ laneId: decoded.laneId,
2251
+ reason: "stale_revision"
2252
+ }));
2253
+ }
2254
+ this.#database.prepare(`INSERT INTO work_lane_operations
2255
+ (operation_id, lane_id, goal_id, phase, revision, record)
2256
+ VALUES (?, ?, ?, ?, ?, ?)`).run(binding.lane.operationId, binding.lane.laneId, binding.lane.goalId, binding.lane.phase, binding.lane.revision, encodedLane);
2257
+ this.#database.prepare(`INSERT INTO work_goal_events (event_id, goal_id, occurred_at, record)
2258
+ VALUES (?, ?, ?, ?)`).run(binding.checkpoint.eventId, binding.checkpoint.goal.id, binding.checkpoint.occurredAt, JSON.stringify(binding.checkpoint));
2259
+ this.#database.prepare(`INSERT INTO work_agent_bindings
2260
+ (dispatch_request_id, lane_id, expected_revision, revision, agent_id, host, record)
2261
+ VALUES (?, ?, ?, ?, ?, ?, ?)`).run(decoded.dispatchRequestId, decoded.laneId, decoded.expectedRevision, binding.lane.revision, decoded.worker.agentId, decoded.worker.host, JSON.stringify(binding));
2262
+ this.#database.exec("COMMIT");
2263
+ inTransaction = false;
2264
+ return { _tag: "bound", binding };
2265
+ }
2266
+ catch (error) {
2267
+ if (inTransaction)
2268
+ this.#database.exec("ROLLBACK");
2269
+ throw error;
2270
+ }
2271
+ },
2272
+ catch: storeError("agent-binding.write")
2273
+ });
2274
+ if (decision._tag === "rejected")
2275
+ return yield* decision.error;
2276
+ return decision.binding;
2277
+ });
2278
+ agentBinding = Effect.fn("WorkStore.agentBinding")(function* (dispatchRequestId) {
2279
+ const decodedId = yield* Schema.decodeUnknownEffect(WorkAgentBindingRequest.fields.dispatchRequestId)(dispatchRequestId).pipe(Effect.mapError(storeError("agent-binding.read.decode")));
2280
+ const raw = yield* Effect.try({
2281
+ try: () => this.#database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, lane_id AS laneId,
2282
+ expected_revision AS expectedRevision, revision, agent_id AS agentId, host, record
2283
+ FROM work_agent_bindings WHERE dispatch_request_id = ?`).get(decodedId),
2284
+ catch: storeError("agent-binding.read")
2285
+ });
2286
+ if (raw === undefined)
2287
+ return Option.none();
2288
+ const row = yield* Schema.decodeUnknownEffect(AgentBindingRow)(raw).pipe(Effect.mapError(storeError("agent-binding.read.row")));
2289
+ const binding = yield* Effect.try({
2290
+ try: () => Schema.decodeUnknownSync(WorkAgentBinding)(JSON.parse(row.record)),
2291
+ catch: storeError("agent-binding.read.record")
2292
+ });
2293
+ if (row.dispatchRequestId !== binding.request.dispatchRequestId ||
2294
+ row.laneId !== binding.request.laneId ||
2295
+ row.expectedRevision !== binding.request.expectedRevision ||
2296
+ row.revision !== binding.lane.revision ||
2297
+ row.agentId !== binding.request.worker.agentId ||
2298
+ row.host.toLowerCase() !== binding.request.worker.host.toLowerCase()) {
2299
+ return yield* new WorkStoreError({ cause: { binding, row }, operation: "agent-binding.read.identity-mismatch" });
54
2300
  }
55
- catch (error) {
56
- this.#database.close();
57
- throw error;
58
- }
59
- }
60
- static open = Effect.fn("WorkStore.open")(function* (path) {
61
- const fileSystem = yield* FileSystem.FileSystem;
62
- const paths = yield* Path.Path;
63
- const directory = paths.dirname(path);
64
- 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")));
66
- const store = yield* Effect.try({
67
- try: () => new WorkStore(path, fileSystem),
68
- catch: storeError("open.database")
69
- });
70
- yield* store.secureFiles();
71
- return store;
2301
+ const companions = yield* Effect.try({
2302
+ try: () => ({
2303
+ checkpoint: this.#database.prepare(`SELECT event_id AS eventId, goal_id AS goalId, occurred_at AS occurredAt, record
2304
+ FROM work_goal_events WHERE event_id = ?`).get(binding.checkpoint.eventId),
2305
+ lane: this.#database.prepare(`SELECT operation_id AS operationId, lane_id AS laneId, goal_id AS goalId,
2306
+ phase, revision, record
2307
+ FROM work_lane_operations WHERE operation_id = ?`).get(binding.lane.operationId)
2308
+ }),
2309
+ catch: storeError("agent-binding.read.companions")
2310
+ });
2311
+ const readbackError = agentBindingReadbackError(binding, companions.lane === undefined
2312
+ ? undefined
2313
+ : yield* Schema.decodeUnknownEffect(AgentBindingLaneOperationRow)(companions.lane).pipe(Effect.mapError(storeError("agent-binding.read.lane-companion"))), companions.checkpoint === undefined
2314
+ ? undefined
2315
+ : yield* Schema.decodeUnknownEffect(AgentBindingGoalEventRow)(companions.checkpoint).pipe(Effect.mapError(storeError("agent-binding.read.checkpoint-companion"))), "agent-binding.readback");
2316
+ if (readbackError !== undefined)
2317
+ return yield* readbackError;
2318
+ return Option.some(binding);
2319
+ });
2320
+ bindingDispatchesForLane = Effect.fn("WorkStore.bindingDispatchesForLane")(function* (laneId) {
2321
+ const decoded = yield* Schema.decodeUnknownEffect(WorkGoalId)(laneId).pipe(Effect.mapError(storeError("agent-binding.lane.decode")));
2322
+ const rows = yield* Effect.try({
2323
+ try: () => this.#database.prepare(`SELECT dispatch_request_id AS dispatchRequestId
2324
+ FROM work_agent_bindings WHERE lane_id = ? ORDER BY revision DESC`).all(decoded),
2325
+ catch: storeError("agent-binding.lane.read")
2326
+ });
2327
+ const entries = yield* Schema.decodeUnknownEffect(Schema.Array(Schema.Struct({ dispatchRequestId: WorkAgentBindingRequest.fields.dispatchRequestId })))(rows).pipe(Effect.mapError(storeError("agent-binding.lane.rows")));
2328
+ return entries.map(({ dispatchRequestId }) => dispatchRequestId);
72
2329
  });
73
2330
  append = Effect.fn("WorkStore.append")(function* (event) {
74
2331
  const decoded = yield* Schema.decodeUnknownEffect(WorkGoalCheckpoint)(event).pipe(Effect.mapError(storeError("append.decode")));
2332
+ yield* this.secureFiles();
75
2333
  const decision = yield* Effect.try({
76
2334
  try: () => {
77
2335
  let transaction = false;
@@ -166,9 +2424,910 @@ export class WorkStore {
166
2424
  if (decision.changes !== 1 && decision.changes !== 1n) {
167
2425
  return yield* storeError("append.insert.count")(decision.changes);
168
2426
  }
169
- yield* this.secureFiles();
170
2427
  return decoded;
171
2428
  });
2429
+ appendMany = Effect.fn("WorkStore.appendMany")(function* (transactionId, events) {
2430
+ if (events.length > workHistoryMaxEvents) {
2431
+ return yield* new WorkProjectionError({
2432
+ cause: events.length,
2433
+ detail: `work history cannot exceed ${workHistoryMaxEvents} checkpoints`,
2434
+ reason: "capacity_exceeded"
2435
+ });
2436
+ }
2437
+ const transaction = yield* Schema.decodeUnknownEffect(TransactionId)(transactionId).pipe(Effect.mapError(storeError("appendMany.decode.transaction")));
2438
+ const decoded = yield* Effect.forEach(events, (event) => Schema.decodeUnknownEffect(WorkGoalCheckpoint)(event).pipe(Effect.mapError(storeError("appendMany.decode.event"))));
2439
+ if (decoded.length === 0) {
2440
+ return yield* new WorkProjectionError({
2441
+ cause: events,
2442
+ detail: "a checkpoint transaction must contain at least one event",
2443
+ reason: "malformed"
2444
+ });
2445
+ }
2446
+ const digest = yield* this.#cryptoService.digest("SHA-256", new TextEncoder().encode(transactionContent(decoded))).pipe(Effect.mapError(storeError("appendMany.digest")), Effect.map(Hex.encode));
2447
+ const transactionRecord = JSON.stringify({ digest, version: "herdr.work.transaction.v3" });
2448
+ const transactionEntryBytes = utf8.encode(transaction).byteLength + utf8.encode(transactionRecord).byteLength;
2449
+ yield* this.secureFiles();
2450
+ const decision = yield* Effect.try({
2451
+ try: () => {
2452
+ let inTransaction = false;
2453
+ try {
2454
+ this.#database.exec("BEGIN IMMEDIATE");
2455
+ inTransaction = true;
2456
+ const storedTransaction = this.#database.prepare("SELECT record FROM work_goal_transactions WHERE transaction_id = ?").get(transaction);
2457
+ let compactTransaction;
2458
+ let legacyCompactTransaction;
2459
+ let unsupportedCompactTransaction = false;
2460
+ let legacyTransaction;
2461
+ if (storedTransaction !== undefined) {
2462
+ const stored = Schema.decodeUnknownSync(TransactionRow)(storedTransaction);
2463
+ const previous = JSON.parse(stored.record);
2464
+ const compact = Schema.decodeUnknownResult(CompactTransactionRecord)(previous);
2465
+ if (compact._tag === "Success") {
2466
+ compactTransaction = compact.success;
2467
+ }
2468
+ else {
2469
+ const legacyCompact = Schema.decodeUnknownResult(LegacyCompactTransactionRecord)(previous);
2470
+ if (legacyCompact._tag === "Success") {
2471
+ legacyCompactTransaction = legacyCompact.success;
2472
+ unsupportedCompactTransaction = true;
2473
+ }
2474
+ else {
2475
+ const previousCompact = Schema.decodeUnknownResult(PreviousCompactTransactionRecord)(previous);
2476
+ if (previousCompact._tag === "Success") {
2477
+ unsupportedCompactTransaction = true;
2478
+ }
2479
+ else {
2480
+ legacyTransaction = Schema.decodeUnknownSync(Schema.Array(WorkGoalCheckpoint))(previous);
2481
+ }
2482
+ }
2483
+ }
2484
+ }
2485
+ const duplicateEventIds = new Set();
2486
+ const duplicateGoalTimes = new Set();
2487
+ for (const event of decoded) {
2488
+ const goalTime = `${event.goal.id}\u0000${event.occurredAt}`;
2489
+ if (duplicateEventIds.has(event.eventId) || duplicateGoalTimes.has(goalTime)) {
2490
+ this.#database.exec("ROLLBACK");
2491
+ inTransaction = false;
2492
+ return {
2493
+ _tag: "rejected",
2494
+ error: new WorkProjectionError({
2495
+ cause: event,
2496
+ detail: "a checkpoint transaction contains a duplicate event identity",
2497
+ reason: "duplicate_event"
2498
+ })
2499
+ };
2500
+ }
2501
+ duplicateEventIds.add(event.eventId);
2502
+ duplicateGoalTimes.add(goalTime);
2503
+ }
2504
+ const rows = Schema.decodeUnknownSync(StoredEventWithTransactionRows)(this.#database.prepare(`SELECT event_id AS eventId, goal_id AS goalId, occurred_at AS occurredAt,
2505
+ record, transaction_id AS transactionId
2506
+ FROM work_goal_events`).all());
2507
+ const rowsByEventId = new Map(rows.map((row) => [row.eventId, row]));
2508
+ const rowsByGoalTime = new Map(rows.map((row) => [
2509
+ JSON.stringify([row.goalId, row.occurredAt]),
2510
+ row
2511
+ ]));
2512
+ const decodedRows = rows.map((row) => ({
2513
+ event: Schema.decodeUnknownSync(WorkGoalCheckpoint)(JSON.parse(row.record)),
2514
+ row
2515
+ }));
2516
+ const decodedEventsByEventId = new Map(decodedRows.map(({ event, row }) => [row.eventId, event]));
2517
+ if (legacyTransaction !== undefined) {
2518
+ const legacyRows = legacyTransaction.map((event) => rowsByEventId.get(event.eventId));
2519
+ if (legacyRows.some((row, index) => {
2520
+ const event = legacyTransaction[index];
2521
+ return (row === undefined ||
2522
+ event === undefined ||
2523
+ row.goalId !== event.goal.id ||
2524
+ row.occurredAt !== event.occurredAt);
2525
+ })) {
2526
+ this.#database.exec("ROLLBACK");
2527
+ inTransaction = false;
2528
+ return {
2529
+ _tag: "rejected",
2530
+ error: new WorkTransactionConflictError({ transactionId: transaction })
2531
+ };
2532
+ }
2533
+ const legacyEvents = legacyRows.map((row) => row === undefined
2534
+ ? undefined
2535
+ : Schema.decodeUnknownSync(WorkGoalCheckpoint)(JSON.parse(row.record)));
2536
+ if (legacyEvents.some((event) => event === undefined) ||
2537
+ legacyEvents.some((event, index) => event !== undefined && !Equal.equals(event, legacyTransaction[index]))) {
2538
+ this.#database.exec("ROLLBACK");
2539
+ inTransaction = false;
2540
+ return {
2541
+ _tag: "rejected",
2542
+ error: new WorkTransactionConflictError({ transactionId: transaction })
2543
+ };
2544
+ }
2545
+ }
2546
+ const existing = decoded.map((event) => {
2547
+ const row = rowsByEventId.get(event.eventId) ??
2548
+ rowsByGoalTime.get(JSON.stringify([event.goal.id, event.occurredAt]));
2549
+ return row === undefined
2550
+ ? undefined
2551
+ : decodedEventsByEventId.get(row.eventId);
2552
+ });
2553
+ const conflicting = existing.find((candidate, index) => candidate !== undefined && !Equal.equals(candidate, decoded[index]));
2554
+ if (conflicting !== undefined) {
2555
+ this.#database.exec("ROLLBACK");
2556
+ inTransaction = false;
2557
+ const index = existing.findIndex((candidate) => candidate === conflicting);
2558
+ const event = decoded[index];
2559
+ if (event === undefined) {
2560
+ return {
2561
+ _tag: "rejected",
2562
+ error: new WorkStoreError({
2563
+ cause: decoded,
2564
+ operation: "appendMany.collision-candidate"
2565
+ })
2566
+ };
2567
+ }
2568
+ return {
2569
+ _tag: "rejected",
2570
+ error: new WorkCheckpointConflictError({
2571
+ eventId: event.eventId,
2572
+ goalId: event.goal.id,
2573
+ occurredAt: event.occurredAt
2574
+ })
2575
+ };
2576
+ }
2577
+ if (compactTransaction !== undefined) {
2578
+ const denormalizedMismatch = decoded.some((event) => {
2579
+ const row = rowsByEventId.get(event.eventId) ??
2580
+ rowsByGoalTime.get(JSON.stringify([event.goal.id, event.occurredAt]));
2581
+ return (row === undefined ||
2582
+ row.eventId !== event.eventId ||
2583
+ row.goalId !== event.goal.id ||
2584
+ row.occurredAt !== event.occurredAt);
2585
+ });
2586
+ if (denormalizedMismatch) {
2587
+ this.#database.exec("ROLLBACK");
2588
+ inTransaction = false;
2589
+ return {
2590
+ _tag: "rejected",
2591
+ error: new WorkTransactionConflictError({ transactionId: transaction })
2592
+ };
2593
+ }
2594
+ }
2595
+ if (legacyCompactTransaction !== undefined) {
2596
+ const denormalizedMismatch = legacyCompactTransaction.events.some((identity, index) => {
2597
+ const event = decoded[index];
2598
+ const row = rowsByEventId.get(identity.eventId);
2599
+ return (row === undefined ||
2600
+ event === undefined ||
2601
+ row.goalId !== event.goal.id ||
2602
+ row.occurredAt !== event.occurredAt);
2603
+ });
2604
+ if (denormalizedMismatch) {
2605
+ this.#database.exec("ROLLBACK");
2606
+ inTransaction = false;
2607
+ return {
2608
+ _tag: "rejected",
2609
+ error: new WorkTransactionConflictError({ transactionId: transaction })
2610
+ };
2611
+ }
2612
+ }
2613
+ const corruptedRow = decodedRows.find(({ event, row }) => row.eventId !== event.eventId || row.goalId !== event.goal.id || row.occurredAt !== event.occurredAt);
2614
+ if (corruptedRow !== undefined) {
2615
+ this.#database.exec("ROLLBACK");
2616
+ inTransaction = false;
2617
+ return {
2618
+ _tag: "rejected",
2619
+ error: new WorkTransactionConflictError({ transactionId: transaction })
2620
+ };
2621
+ }
2622
+ if (unsupportedCompactTransaction) {
2623
+ this.#database.exec("ROLLBACK");
2624
+ inTransaction = false;
2625
+ return {
2626
+ _tag: "rejected",
2627
+ error: new WorkTransactionConflictError({ transactionId: transaction })
2628
+ };
2629
+ }
2630
+ if (legacyTransaction !== undefined && !Equal.equals(legacyTransaction, decoded)) {
2631
+ this.#database.exec("ROLLBACK");
2632
+ inTransaction = false;
2633
+ return {
2634
+ _tag: "rejected",
2635
+ error: new WorkTransactionConflictError({ transactionId: transaction })
2636
+ };
2637
+ }
2638
+ const newEvents = decoded.filter((_, index) => existing[index] === undefined);
2639
+ if (compactTransaction !== undefined &&
2640
+ newEvents.length !== 0) {
2641
+ this.#database.exec("ROLLBACK");
2642
+ inTransaction = false;
2643
+ return {
2644
+ _tag: "rejected",
2645
+ error: new WorkTransactionConflictError({ transactionId: transaction })
2646
+ };
2647
+ }
2648
+ if (newEvents.length !== 0 && existing.some((candidate) => candidate !== undefined)) {
2649
+ this.#database.exec("ROLLBACK");
2650
+ inTransaction = false;
2651
+ const event = decoded.find((candidate, index) => existing[index] !== undefined);
2652
+ if (event === undefined) {
2653
+ return {
2654
+ _tag: "rejected",
2655
+ error: new WorkStoreError({
2656
+ cause: decoded,
2657
+ operation: "appendMany.partial-replay-candidate"
2658
+ })
2659
+ };
2660
+ }
2661
+ return {
2662
+ _tag: "rejected",
2663
+ error: new WorkCheckpointConflictError({
2664
+ eventId: event.eventId,
2665
+ goalId: event.goal.id,
2666
+ occurredAt: event.occurredAt
2667
+ })
2668
+ };
2669
+ }
2670
+ if (newEvents.length === 0) {
2671
+ if (compactTransaction !== undefined) {
2672
+ if (compactTransaction.digest !== digest) {
2673
+ this.#database.exec("ROLLBACK");
2674
+ inTransaction = false;
2675
+ return {
2676
+ _tag: "rejected",
2677
+ error: new WorkTransactionConflictError({ transactionId: transaction })
2678
+ };
2679
+ }
2680
+ this.#database.exec("ROLLBACK");
2681
+ inTransaction = false;
2682
+ return { _tag: "replayed", events: decoded };
2683
+ }
2684
+ if (legacyTransaction !== undefined) {
2685
+ this.#database.exec("ROLLBACK");
2686
+ inTransaction = false;
2687
+ return { _tag: "replayed", events: decoded };
2688
+ }
2689
+ const transactionLedgerTotals = readTransactionLedgerTotals(this.#database);
2690
+ if (transactionLedgerTotals.transactionCount >= workTransactionMaxRecords) {
2691
+ this.#database.exec("ROLLBACK");
2692
+ inTransaction = false;
2693
+ return {
2694
+ _tag: "rejected",
2695
+ error: new WorkProjectionError({
2696
+ cause: decoded,
2697
+ detail: `work transaction history cannot exceed ${workTransactionMaxRecords} transaction IDs`,
2698
+ reason: "capacity_exceeded"
2699
+ })
2700
+ };
2701
+ }
2702
+ if (transactionLedgerTotals.transactionBytes + transactionEntryBytes > workTransactionMaxBytes) {
2703
+ this.#database.exec("ROLLBACK");
2704
+ inTransaction = false;
2705
+ return {
2706
+ _tag: "rejected",
2707
+ error: new WorkProjectionError({
2708
+ cause: decoded,
2709
+ detail: `work transaction history cannot exceed ${workTransactionMaxBytes} encoded bytes`,
2710
+ reason: "capacity_exceeded"
2711
+ })
2712
+ };
2713
+ }
2714
+ this.#database.prepare("INSERT INTO work_goal_transactions (transaction_id, record) VALUES (?, ?)").run(transaction, transactionRecord);
2715
+ this.#database.exec("COMMIT");
2716
+ inTransaction = false;
2717
+ return { _tag: "replayed", events: decoded };
2718
+ }
2719
+ const eventCount = Schema.decodeUnknownSync(CountRow)(this.#database.prepare("SELECT COUNT(*) AS count FROM work_goal_events").get()).count;
2720
+ if (eventCount + newEvents.length > workHistoryMaxEvents) {
2721
+ this.#database.exec("ROLLBACK");
2722
+ inTransaction = false;
2723
+ return {
2724
+ _tag: "rejected",
2725
+ error: new WorkProjectionError({
2726
+ cause: decoded,
2727
+ detail: `work history cannot exceed ${workHistoryMaxEvents} checkpoints`,
2728
+ reason: "capacity_exceeded"
2729
+ })
2730
+ };
2731
+ }
2732
+ const history = decodedRows.map(({ event }) => event);
2733
+ const prospective = [...history, ...newEvents].toSorted((left, right) => left.occurredAt - right.occurredAt || left.eventId.localeCompare(right.eventId));
2734
+ const familyError = validateGoalFamilyHistory(prospective);
2735
+ if (familyError !== undefined) {
2736
+ this.#database.exec("ROLLBACK");
2737
+ inTransaction = false;
2738
+ return { _tag: "rejected", error: familyError };
2739
+ }
2740
+ if (workMaximumSnapshotBytesForHistory(prospective) > fleetResponseBodyMaxBytes) {
2741
+ this.#database.exec("ROLLBACK");
2742
+ inTransaction = false;
2743
+ return {
2744
+ _tag: "rejected",
2745
+ error: new WorkProjectionError({
2746
+ cause: decoded,
2747
+ detail: `work snapshots cannot exceed ${fleetResponseBodyMaxBytes} encoded bytes`,
2748
+ reason: "capacity_exceeded"
2749
+ })
2750
+ };
2751
+ }
2752
+ const goalIds = new Set(history.map(({ goal }) => goal.id));
2753
+ for (const event of newEvents)
2754
+ goalIds.add(event.goal.id);
2755
+ if (goalIds.size > workSnapshotMaxGoals) {
2756
+ this.#database.exec("ROLLBACK");
2757
+ inTransaction = false;
2758
+ return {
2759
+ _tag: "rejected",
2760
+ error: new WorkProjectionError({
2761
+ cause: decoded,
2762
+ detail: `work snapshots cannot exceed ${workSnapshotMaxGoals} goals`,
2763
+ reason: "capacity_exceeded"
2764
+ })
2765
+ };
2766
+ }
2767
+ const creationTimes = new Map();
2768
+ for (const event of prospective) {
2769
+ const creationTime = creationTimes.get(event.goal.id);
2770
+ if (creationTime === undefined) {
2771
+ if (event.occurredAt !== event.goal.createdAt) {
2772
+ this.#database.exec("ROLLBACK");
2773
+ inTransaction = false;
2774
+ return {
2775
+ _tag: "rejected",
2776
+ error: new WorkProjectionError({
2777
+ cause: event,
2778
+ detail: `goal ${event.goal.id} must begin at its creation timestamp`,
2779
+ reason: "inconsistent_history"
2780
+ })
2781
+ };
2782
+ }
2783
+ creationTimes.set(event.goal.id, event.goal.createdAt);
2784
+ }
2785
+ else if (creationTime !== event.goal.createdAt) {
2786
+ this.#database.exec("ROLLBACK");
2787
+ inTransaction = false;
2788
+ return {
2789
+ _tag: "rejected",
2790
+ error: new WorkProjectionError({
2791
+ cause: event,
2792
+ detail: `goal ${event.goal.id} changed its creation timestamp`,
2793
+ reason: "inconsistent_history"
2794
+ })
2795
+ };
2796
+ }
2797
+ }
2798
+ const transactionLedgerTotals = readTransactionLedgerTotals(this.#database);
2799
+ if (transactionLedgerTotals.transactionCount >= workTransactionMaxRecords) {
2800
+ this.#database.exec("ROLLBACK");
2801
+ inTransaction = false;
2802
+ return {
2803
+ _tag: "rejected",
2804
+ error: new WorkProjectionError({
2805
+ cause: decoded,
2806
+ detail: `work transaction history cannot exceed ${workTransactionMaxRecords} transaction IDs`,
2807
+ reason: "capacity_exceeded"
2808
+ })
2809
+ };
2810
+ }
2811
+ if (transactionLedgerTotals.transactionBytes + transactionEntryBytes > workTransactionMaxBytes) {
2812
+ this.#database.exec("ROLLBACK");
2813
+ inTransaction = false;
2814
+ return {
2815
+ _tag: "rejected",
2816
+ error: new WorkProjectionError({
2817
+ cause: decoded,
2818
+ detail: `work transaction history cannot exceed ${workTransactionMaxBytes} encoded bytes`,
2819
+ reason: "capacity_exceeded"
2820
+ })
2821
+ };
2822
+ }
2823
+ const insert = this.#database.prepare(`INSERT INTO work_goal_events
2824
+ (event_id, goal_id, occurred_at, record, transaction_id)
2825
+ VALUES (?, ?, ?, ?, ?)`);
2826
+ for (const event of newEvents) {
2827
+ insert.run(event.eventId, event.goal.id, event.occurredAt, JSON.stringify(event), transaction);
2828
+ }
2829
+ this.#database.prepare("INSERT INTO work_goal_transactions (transaction_id, record) VALUES (?, ?)").run(transaction, transactionRecord);
2830
+ this.#database.exec("COMMIT");
2831
+ inTransaction = false;
2832
+ return { _tag: "inserted", events: decoded };
2833
+ }
2834
+ catch (error) {
2835
+ if (inTransaction)
2836
+ this.#database.exec("ROLLBACK");
2837
+ throw error;
2838
+ }
2839
+ },
2840
+ catch: storeError("appendMany.insert")
2841
+ });
2842
+ if (decision._tag === "rejected")
2843
+ return yield* decision.error;
2844
+ return decision.events;
2845
+ });
2846
+ claim = Effect.fn("WorkStore.claim")(function* (claim) {
2847
+ const decoded = yield* Schema.decodeUnknownEffect(WorkLaneClaim)(claim).pipe(Effect.mapError(storeError("claim.decode")));
2848
+ yield* this.secureFiles();
2849
+ const decision = yield* Effect.try({
2850
+ try: () => {
2851
+ let inTransaction = false;
2852
+ try {
2853
+ this.#database.exec("BEGIN IMMEDIATE");
2854
+ inTransaction = true;
2855
+ const operationRaw = this.#database.prepare(`SELECT lane_id AS laneId, goal_id AS goalId, operation_id AS operationId,
2856
+ phase, revision, record
2857
+ FROM work_lane_operations WHERE operation_id = ?`).get(decoded.operationId);
2858
+ if (operationRaw !== undefined) {
2859
+ const prior = Schema.decodeUnknownSync(LaneRow)(operationRaw);
2860
+ const priorClaim = Schema.decodeUnknownSync(WorkLaneClaimed)(JSON.parse(prior.record));
2861
+ if (priorClaim.goalId !== prior.goalId ||
2862
+ priorClaim.laneId !== prior.laneId ||
2863
+ priorClaim.operationId !== prior.operationId ||
2864
+ priorClaim.phase !== prior.phase ||
2865
+ priorClaim.revision !== prior.revision) {
2866
+ this.#database.exec("ROLLBACK");
2867
+ inTransaction = false;
2868
+ return {
2869
+ _tag: "rejected",
2870
+ error: new WorkStoreError({
2871
+ cause: { claim: priorClaim, row: prior },
2872
+ operation: "claim.operation.identity-mismatch"
2873
+ })
2874
+ };
2875
+ }
2876
+ this.#database.exec("ROLLBACK");
2877
+ inTransaction = false;
2878
+ return Equal.equals(claimInputFromClaimed(priorClaim), decoded)
2879
+ ? { _tag: "claimed", value: priorClaim }
2880
+ : {
2881
+ _tag: "operation-conflict",
2882
+ error: new WorkLaneOperationConflictError({ operationId: decoded.operationId })
2883
+ };
2884
+ }
2885
+ const laneLedger = readValidatedLaneLedger(this.#database, "claim.write");
2886
+ if (laneLedger._tag === "invalid") {
2887
+ this.#database.exec("ROLLBACK");
2888
+ inTransaction = false;
2889
+ return {
2890
+ _tag: "rejected",
2891
+ error: laneLedger.error
2892
+ };
2893
+ }
2894
+ const existingEntry = laneLedger.entries.find(({ claim }) => claim.laneId === decoded.laneId);
2895
+ const existing = existingEntry?.row;
2896
+ const existingClaim = existingEntry?.claim;
2897
+ const actualRevision = existingClaim?.revision ?? 0;
2898
+ const reconciliation = decoded.reconciliation;
2899
+ if (reconciliation !== undefined) {
2900
+ const rejectLink = (reason) => {
2901
+ this.#database.exec("ROLLBACK");
2902
+ inTransaction = false;
2903
+ return {
2904
+ _tag: "rejected",
2905
+ error: new WorkPullRequestLinkError({
2906
+ goalId: decoded.goalId,
2907
+ laneId: decoded.laneId,
2908
+ reason
2909
+ })
2910
+ };
2911
+ };
2912
+ if (existingClaim === undefined || existingClaim.goalId !== decoded.goalId) {
2913
+ return rejectLink("missing_lane");
2914
+ }
2915
+ if (existingClaim.revision !== decoded.expectedRevision)
2916
+ return rejectLink("stale_revision");
2917
+ if (existingClaim.head !== reconciliation.expectedHead)
2918
+ return rejectLink("head_mismatch");
2919
+ if (!Equal.equals(existingClaim.owner, reconciliation.expectedOwner) ||
2920
+ !Equal.equals(decoded.owner, existingClaim.owner))
2921
+ return rejectLink("owner_mismatch");
2922
+ if (existingClaim.worktree !== decoded.worktree ||
2923
+ existingClaim.branch !== decoded.branch ||
2924
+ existingClaim.parent !== decoded.parent ||
2925
+ existingClaim.phase !== decoded.phase ||
2926
+ existingClaim.phase === "shipped")
2927
+ return rejectLink("identity_mismatch");
2928
+ const eventRaw = this.#database.prepare(`SELECT event_id AS eventId, goal_id AS goalId, occurred_at AS occurredAt, record
2929
+ FROM work_goal_events WHERE goal_id = ? ORDER BY occurred_at DESC, event_id DESC LIMIT 1`).get(decoded.goalId);
2930
+ if (eventRaw === undefined)
2931
+ return rejectLink("missing_provenance");
2932
+ const event = Schema.decodeUnknownSync(WorkGoalCheckpoint)(JSON.parse(Schema.decodeUnknownSync(AgentBindingGoalEventRow)(eventRaw).record));
2933
+ const goal = event.goal;
2934
+ if (event.eventId !== reconciliation.expectedGoalEventId ||
2935
+ goal.review?.url !==
2936
+ `https://github.com/${reconciliation.repository}/pull/${reconciliation.pullRequest}` ||
2937
+ goal.goalFamily?.role !== "canonical" ||
2938
+ goal.goalFamily.canonicalGoalId !== goal.id)
2939
+ return rejectLink("missing_provenance");
2940
+ if (goal.state === "completed" || goal.state === "deployed")
2941
+ return rejectLink("terminal_goal");
2942
+ if (!Equal.equals(goal.owner, reconciliation.expectedOwner))
2943
+ return rejectLink("owner_mismatch");
2944
+ const bindingRows = Schema.decodeUnknownSync(Schema.Array(AgentBindingRow))(this.#database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, lane_id AS laneId,
2945
+ expected_revision AS expectedRevision, revision, agent_id AS agentId, host, record
2946
+ FROM work_agent_bindings WHERE lane_id = ? ORDER BY revision DESC`).all(decoded.laneId));
2947
+ const latest = bindingRows[0];
2948
+ if (latest === undefined)
2949
+ return rejectLink("missing_binding");
2950
+ if (latest.dispatchRequestId !== reconciliation.bindingDispatchRequestId) {
2951
+ return rejectLink("ambiguous_binding");
2952
+ }
2953
+ const binding = Schema.decodeUnknownSync(WorkAgentBinding)(JSON.parse(latest.record));
2954
+ if (binding.request.laneId !== decoded.laneId ||
2955
+ binding.lane.goalId !== decoded.goalId ||
2956
+ !Equal.equals(binding.request.worker, reconciliation.worker) ||
2957
+ !Equal.equals(goal.agentHierarchy?.agent, reconciliation.worker) ||
2958
+ goal.connectTarget?.agentId !== reconciliation.worker.agentId ||
2959
+ goal.connectTarget.host.toLowerCase() !== reconciliation.worker.host.toLowerCase()) {
2960
+ return rejectLink("identity_mismatch");
2961
+ }
2962
+ }
2963
+ if (actualRevision !== decoded.expectedRevision) {
2964
+ this.#database.exec("ROLLBACK");
2965
+ inTransaction = false;
2966
+ return {
2967
+ _tag: "conflict",
2968
+ error: new WorkLaneClaimConflictError({
2969
+ actualRevision,
2970
+ expectedRevision: decoded.expectedRevision,
2971
+ laneId: decoded.laneId
2972
+ })
2973
+ };
2974
+ }
2975
+ if (existingClaim !== undefined && existingClaim.goalId !== decoded.goalId) {
2976
+ this.#database.exec("ROLLBACK");
2977
+ inTransaction = false;
2978
+ return {
2979
+ _tag: "goal-conflict",
2980
+ error: new WorkLaneGoalConflictError({
2981
+ activeLaneId: existingClaim.laneId,
2982
+ goalId: decoded.goalId,
2983
+ laneId: decoded.laneId
2984
+ })
2985
+ };
2986
+ }
2987
+ const activeGoal = laneLedger.entries.find(({ claim }) => claim.goalId === decoded.goalId && claim.phase !== "shipped" && claim.laneId !== decoded.laneId);
2988
+ if (decoded.phase !== "shipped" && activeGoal !== undefined) {
2989
+ this.#database.exec("ROLLBACK");
2990
+ inTransaction = false;
2991
+ return {
2992
+ _tag: "goal-conflict",
2993
+ error: new WorkLaneGoalConflictError({
2994
+ activeLaneId: activeGoal.claim.laneId,
2995
+ goalId: decoded.goalId,
2996
+ laneId: decoded.laneId
2997
+ })
2998
+ };
2999
+ }
3000
+ if (actualRevision >= Number.MAX_SAFE_INTEGER) {
3001
+ this.#database.exec("ROLLBACK");
3002
+ inTransaction = false;
3003
+ return {
3004
+ _tag: "rejected",
3005
+ error: new WorkProjectionError({
3006
+ cause: decoded,
3007
+ detail: "work lane claim revision cannot exceed Number.MAX_SAFE_INTEGER",
3008
+ reason: "capacity_exceeded"
3009
+ })
3010
+ };
3011
+ }
3012
+ const revision = actualRevision + 1;
3013
+ const result = { ...decoded, revision };
3014
+ const encodedRecord = JSON.stringify(result);
3015
+ const operationEntryBytes = utf8.encode(decoded.operationId).byteLength +
3016
+ utf8.encode(encodedRecord).byteLength;
3017
+ const operationTotals = readLaneOperationLedgerTotals(this.#database);
3018
+ if (operationTotals.operationCount >= workLaneOperationMaxRecords) {
3019
+ this.#database.exec("ROLLBACK");
3020
+ inTransaction = false;
3021
+ return {
3022
+ _tag: "rejected",
3023
+ error: new WorkProjectionError({
3024
+ cause: decoded,
3025
+ detail: `work lane operation history cannot exceed ${workLaneOperationMaxRecords} operation IDs`,
3026
+ reason: "capacity_exceeded"
3027
+ })
3028
+ };
3029
+ }
3030
+ if (operationTotals.operationBytes + operationEntryBytes > workLaneOperationMaxBytes) {
3031
+ this.#database.exec("ROLLBACK");
3032
+ inTransaction = false;
3033
+ return {
3034
+ _tag: "rejected",
3035
+ error: new WorkProjectionError({
3036
+ cause: decoded,
3037
+ detail: `work lane operation history cannot exceed ${workLaneOperationMaxBytes} encoded bytes`,
3038
+ reason: "capacity_exceeded"
3039
+ })
3040
+ };
3041
+ }
3042
+ const entryBytes = utf8.encode(decoded.laneId).byteLength + utf8.encode(encodedRecord).byteLength;
3043
+ const existingEntryBytes = existing === undefined
3044
+ ? 0
3045
+ : utf8.encode(decoded.laneId).byteLength + utf8.encode(existing.record).byteLength;
3046
+ const claimCount = laneLedger.entries.length;
3047
+ if (existing === undefined && claimCount >= workLaneMaxRecords) {
3048
+ this.#database.exec("ROLLBACK");
3049
+ inTransaction = false;
3050
+ return {
3051
+ _tag: "rejected",
3052
+ error: new WorkProjectionError({
3053
+ cause: decoded,
3054
+ detail: `work lane claims cannot exceed ${workLaneMaxRecords} lanes`,
3055
+ reason: "capacity_exceeded"
3056
+ })
3057
+ };
3058
+ }
3059
+ 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
3060
+ FROM work_lane_claims`).get()).bytes;
3061
+ if (claimBytes - existingEntryBytes + entryBytes > workLaneMaxBytes) {
3062
+ this.#database.exec("ROLLBACK");
3063
+ inTransaction = false;
3064
+ return {
3065
+ _tag: "rejected",
3066
+ error: new WorkProjectionError({
3067
+ cause: decoded,
3068
+ detail: `work lane claims cannot exceed ${workLaneMaxBytes} encoded bytes`,
3069
+ reason: "capacity_exceeded"
3070
+ })
3071
+ };
3072
+ }
3073
+ const changes = existing === undefined
3074
+ ? this.#database.prepare(`INSERT INTO work_lane_claims
3075
+ (lane_id, goal_id, operation_id, phase, revision, record)
3076
+ VALUES (?, ?, ?, ?, ?, ?)`).run(decoded.laneId, decoded.goalId, decoded.operationId, decoded.phase, revision, encodedRecord).changes
3077
+ : this.#database.prepare(`UPDATE work_lane_claims
3078
+ SET goal_id = ?, operation_id = ?, phase = ?, revision = ?, record = ?
3079
+ WHERE lane_id = ? AND revision = ?`).run(decoded.goalId, decoded.operationId, decoded.phase, revision, encodedRecord, decoded.laneId, decoded.expectedRevision).changes;
3080
+ if (changes !== 1 && changes !== 1n) {
3081
+ this.#database.exec("ROLLBACK");
3082
+ inTransaction = false;
3083
+ return {
3084
+ _tag: "conflict",
3085
+ error: new WorkLaneClaimConflictError({
3086
+ actualRevision,
3087
+ expectedRevision: decoded.expectedRevision,
3088
+ laneId: decoded.laneId
3089
+ })
3090
+ };
3091
+ }
3092
+ const operationChanges = this.#database.prepare(`INSERT INTO work_lane_operations
3093
+ (operation_id, lane_id, goal_id, phase, revision, record)
3094
+ VALUES (?, ?, ?, ?, ?, ?)`).run(decoded.operationId, decoded.laneId, decoded.goalId, decoded.phase, revision, encodedRecord).changes;
3095
+ if (operationChanges !== 1 && operationChanges !== 1n) {
3096
+ this.#database.exec("ROLLBACK");
3097
+ inTransaction = false;
3098
+ return {
3099
+ _tag: "rejected",
3100
+ error: new WorkStoreError({ cause: decoded, operation: "claim.operation.insert" })
3101
+ };
3102
+ }
3103
+ this.#database.exec("COMMIT");
3104
+ inTransaction = false;
3105
+ return { _tag: "claimed", value: result };
3106
+ }
3107
+ catch (error) {
3108
+ if (inTransaction)
3109
+ this.#database.exec("ROLLBACK");
3110
+ throw error;
3111
+ }
3112
+ },
3113
+ catch: storeError("claim.write")
3114
+ });
3115
+ if (decision._tag !== "claimed")
3116
+ return yield* decision.error;
3117
+ return decision.value;
3118
+ });
3119
+ decision = Effect.fn("WorkStore.decision")(function* (handoff) {
3120
+ const decoded = yield* Schema.decodeUnknownEffect(WorkDecisionHandoff)(handoff).pipe(Effect.mapError(storeError("decision.decode")));
3121
+ yield* this.secureFiles();
3122
+ const encodedHandoff = JSON.stringify(decoded);
3123
+ const handoffEntryBytes = utf8.encode(decoded.id).byteLength + utf8.encode(encodedHandoff).byteLength;
3124
+ const result = yield* Effect.try({
3125
+ try: () => {
3126
+ let inTransaction = false;
3127
+ try {
3128
+ this.#database.exec("BEGIN IMMEDIATE");
3129
+ inTransaction = true;
3130
+ const sessionRaw = this.#database.prepare(`SELECT handoff_id AS handoffId, session_id AS sessionId, lane_id AS laneId,
3131
+ occurred_at AS occurredAt, record
3132
+ FROM work_decision_handoffs WHERE session_id = ?`).get(decoded.sessionId);
3133
+ if (sessionRaw !== undefined) {
3134
+ const previous = Schema.decodeUnknownSync(DecisionRow)(sessionRaw);
3135
+ const prior = Schema.decodeUnknownSync(WorkDecisionHandoff)(JSON.parse(previous.record));
3136
+ this.#database.exec("ROLLBACK");
3137
+ inTransaction = false;
3138
+ if (previous.handoffId !== prior.id ||
3139
+ previous.sessionId !== prior.sessionId ||
3140
+ previous.laneId !== prior.laneId ||
3141
+ previous.occurredAt !== prior.occurredAt) {
3142
+ return {
3143
+ _tag: "rejected",
3144
+ error: new WorkStoreError({
3145
+ cause: { row: previous, record: prior },
3146
+ operation: "decision.decode.identity-mismatch"
3147
+ })
3148
+ };
3149
+ }
3150
+ if (Equal.equals(prior, decoded))
3151
+ return { _tag: "replayed", value: decoded };
3152
+ return {
3153
+ _tag: "coordinator-conflict",
3154
+ error: new WorkCoordinatorHandoffConflictError({ sessionId: decoded.sessionId })
3155
+ };
3156
+ }
3157
+ const handoffRaw = this.#database.prepare(`SELECT handoff_id AS handoffId, session_id AS sessionId, lane_id AS laneId,
3158
+ occurred_at AS occurredAt, record
3159
+ FROM work_decision_handoffs WHERE handoff_id = ?`).get(decoded.id);
3160
+ if (handoffRaw !== undefined) {
3161
+ this.#database.exec("ROLLBACK");
3162
+ inTransaction = false;
3163
+ return {
3164
+ _tag: "conflict",
3165
+ error: new WorkDecisionHandoffConflictError({ handoffId: decoded.id })
3166
+ };
3167
+ }
3168
+ const laneLedger = readValidatedLaneLedger(this.#database, "decision.claim");
3169
+ if (laneLedger._tag === "invalid") {
3170
+ this.#database.exec("ROLLBACK");
3171
+ inTransaction = false;
3172
+ return { _tag: "rejected", error: laneLedger.error };
3173
+ }
3174
+ const activeGoalClaims = laneLedger.entries.filter(({ claim }) => claim.goalId === decoded.goalId && claim.phase !== "shipped");
3175
+ if (activeGoalClaims.length !== 1 || activeGoalClaims[0]?.claim.laneId !== decoded.laneId) {
3176
+ this.#database.exec("ROLLBACK");
3177
+ inTransaction = false;
3178
+ return {
3179
+ _tag: "rejected",
3180
+ error: new WorkDecisionAuthorityConflictError({ goalId: decoded.goalId, laneId: decoded.laneId })
3181
+ };
3182
+ }
3183
+ const activeClaim = activeGoalClaims[0].claim;
3184
+ if (activeClaim.revision !== decoded.expectedRevision) {
3185
+ this.#database.exec("ROLLBACK");
3186
+ inTransaction = false;
3187
+ return {
3188
+ _tag: "rejected",
3189
+ error: new WorkDecisionRevisionConflictError({
3190
+ actualRevision: activeClaim.revision,
3191
+ expectedRevision: decoded.expectedRevision,
3192
+ laneId: decoded.laneId
3193
+ })
3194
+ };
3195
+ }
3196
+ const decisionLedgerTotals = readDecisionLedgerTotals(this.#database);
3197
+ if (decisionLedgerTotals.decisionCount >= workDecisionMaxRecords) {
3198
+ this.#database.exec("ROLLBACK");
3199
+ inTransaction = false;
3200
+ return {
3201
+ _tag: "rejected",
3202
+ error: new WorkProjectionError({
3203
+ cause: decoded,
3204
+ detail: `work decision history cannot exceed ${workDecisionMaxRecords} handoffs`,
3205
+ reason: "capacity_exceeded"
3206
+ })
3207
+ };
3208
+ }
3209
+ if (decisionLedgerTotals.decisionBytes + handoffEntryBytes > workDecisionMaxBytes) {
3210
+ this.#database.exec("ROLLBACK");
3211
+ inTransaction = false;
3212
+ return {
3213
+ _tag: "rejected",
3214
+ error: new WorkProjectionError({
3215
+ cause: decoded,
3216
+ detail: `work decision history cannot exceed ${workDecisionMaxBytes} encoded bytes`,
3217
+ reason: "capacity_exceeded"
3218
+ })
3219
+ };
3220
+ }
3221
+ this.#database.prepare(`INSERT INTO work_decision_handoffs
3222
+ (handoff_id, session_id, lane_id, occurred_at, record)
3223
+ VALUES (?, ?, ?, ?, ?)`).run(decoded.id, decoded.sessionId, decoded.laneId, decoded.occurredAt, encodedHandoff);
3224
+ this.#database.exec("COMMIT");
3225
+ inTransaction = false;
3226
+ return { _tag: "inserted", value: decoded };
3227
+ }
3228
+ catch (error) {
3229
+ if (inTransaction)
3230
+ this.#database.exec("ROLLBACK");
3231
+ throw error;
3232
+ }
3233
+ },
3234
+ catch: storeError("decision.write")
3235
+ });
3236
+ if (result._tag !== "inserted" && result._tag !== "replayed")
3237
+ return yield* result.error;
3238
+ return result.value;
3239
+ });
3240
+ currentClaim = Effect.fn("WorkStore.currentClaim")(function* (laneId) {
3241
+ const decodedLaneId = yield* Schema.decodeUnknownEffect(WorkGoalId)(laneId).pipe(Effect.mapError(storeError("claim.read.decode-lane-id")));
3242
+ const ledger = yield* Effect.sync(() => readValidatedLaneLedger(this.#database, "claim.read"));
3243
+ if (ledger._tag === "invalid")
3244
+ return yield* ledger.error;
3245
+ const entry = ledger.entries.find(({ claim }) => claim.laneId === decodedLaneId);
3246
+ return entry === undefined ? Option.none() : Option.some(entry.claim);
3247
+ });
3248
+ activeGoalClaim = Effect.fn("WorkStore.activeGoalClaim")(function* (goalId) {
3249
+ const decodedGoalId = yield* Schema.decodeUnknownEffect(WorkGoalId)(goalId).pipe(Effect.mapError(storeError("claim.goal.decode-goal-id")));
3250
+ const ledger = yield* Effect.sync(() => readValidatedLaneLedger(this.#database, "claim.goal"));
3251
+ if (ledger._tag === "invalid")
3252
+ return yield* ledger.error;
3253
+ const active = ledger.entries.filter(({ claim }) => claim.goalId === decodedGoalId && claim.phase !== "shipped");
3254
+ if (active.length === 0)
3255
+ return Option.none();
3256
+ if (active.length !== 1) {
3257
+ return yield* new WorkStoreError({
3258
+ cause: { goalId: decodedGoalId, lanes: active.map(({ claim }) => claim.laneId) },
3259
+ operation: "claim.goal.exclusivity-violation"
3260
+ });
3261
+ }
3262
+ const entry = active[0];
3263
+ if (entry === undefined) {
3264
+ return yield* new WorkStoreError({ cause: decodedGoalId, operation: "claim.goal.missing-row" });
3265
+ }
3266
+ return Option.some(entry.claim);
3267
+ });
3268
+ coordinatorHandoff = Effect.fn("WorkStore.coordinatorHandoff")(function* (sessionId) {
3269
+ const decodedSessionId = yield* Schema.decodeUnknownEffect(WorkCoordinatorSessionId)(sessionId).pipe(Effect.mapError(storeError("decision.session.decode-session-id")));
3270
+ const raw = yield* Effect.try({
3271
+ try: () => this.#database.prepare(`SELECT handoff_id AS handoffId, session_id AS sessionId, lane_id AS laneId,
3272
+ occurred_at AS occurredAt, record
3273
+ FROM work_decision_handoffs WHERE session_id = ?`).get(decodedSessionId),
3274
+ catch: storeError("decision.session.read")
3275
+ });
3276
+ if (raw === undefined)
3277
+ return Option.none();
3278
+ const row = yield* Schema.decodeUnknownEffect(DecisionRow)(raw).pipe(Effect.mapError(storeError("decision.session.decode-row")));
3279
+ const handoff = yield* Effect.try({
3280
+ try: () => JSON.parse(row.record),
3281
+ catch: storeError("decision.session.parse")
3282
+ }).pipe(Effect.flatMap((value) => Schema.decodeUnknownEffect(WorkDecisionHandoff)(value)), Effect.mapError(storeError("decision.session.decode")));
3283
+ if (handoff.id !== row.handoffId ||
3284
+ handoff.sessionId !== row.sessionId ||
3285
+ handoff.sessionId !== decodedSessionId ||
3286
+ handoff.laneId !== row.laneId ||
3287
+ handoff.occurredAt !== row.occurredAt) {
3288
+ return yield* new WorkStoreError({
3289
+ cause: { handoff, row },
3290
+ operation: "decision.session.identity-mismatch"
3291
+ });
3292
+ }
3293
+ return Option.some(handoff);
3294
+ });
3295
+ decisions = Effect.fn("WorkStore.decisions")(function* (laneId) {
3296
+ const decodedLaneId = yield* Schema.decodeUnknownEffect(WorkGoalId)(laneId).pipe(Effect.mapError(storeError("decisions.list.decode-lane-id")));
3297
+ const rows = yield* Effect.try({
3298
+ try: () => this.#database.prepare(`SELECT handoff_id AS handoffId, session_id AS sessionId, lane_id AS laneId,
3299
+ occurred_at AS occurredAt, record
3300
+ FROM work_decision_handoffs
3301
+ WHERE lane_id = ? ORDER BY occurred_at ASC, handoff_id ASC`).all(decodedLaneId),
3302
+ catch: storeError("decisions.list")
3303
+ });
3304
+ return yield* Effect.forEach(rows, (row) => Schema.decodeUnknownEffect(DecisionRow)(row).pipe(Effect.mapError(storeError("decisions.decode-row")), Effect.flatMap((row) => Effect.try({
3305
+ try: () => Schema.decodeUnknownSync(WorkDecisionHandoff)(JSON.parse(row.record)),
3306
+ catch: storeError("decisions.decode")
3307
+ })
3308
+ .pipe(Effect.flatMap((handoff) => handoff.laneId !== decodedLaneId
3309
+ ? Effect.fail(new WorkStoreError({
3310
+ cause: { requestedLaneId: decodedLaneId, recordLaneId: handoff.laneId },
3311
+ operation: "decisions.decode.lane-mismatch"
3312
+ }))
3313
+ : handoff.laneId !== row.laneId ||
3314
+ handoff.sessionId !== row.sessionId ||
3315
+ handoff.id !== row.handoffId ||
3316
+ handoff.occurredAt !== row.occurredAt
3317
+ ? Effect.fail(new WorkStoreError({
3318
+ cause: {
3319
+ record: handoff,
3320
+ row: {
3321
+ handoffId: row.handoffId,
3322
+ sessionId: row.sessionId,
3323
+ laneId: row.laneId,
3324
+ occurredAt: row.occurredAt
3325
+ }
3326
+ },
3327
+ operation: "decisions.decode.identity-mismatch"
3328
+ }))
3329
+ : Effect.succeed(handoff))))));
3330
+ });
172
3331
  list = Effect.fn("WorkStore.list")(function* () {
173
3332
  const rows = yield* Effect.try({
174
3333
  try: () => this.#database.prepare("SELECT record FROM work_goal_events ORDER BY occurred_at ASC, event_id ASC").all(),
@@ -176,11 +3335,76 @@ export class WorkStore {
176
3335
  });
177
3336
  return yield* Effect.forEach(rows, decodeRow);
178
3337
  });
3338
+ snapshotInput = Effect.fn("WorkStore.snapshotInput")(function* () {
3339
+ const source = yield* Effect.try({
3340
+ try: () => {
3341
+ let inTransaction = false;
3342
+ try {
3343
+ this.#database.exec("BEGIN");
3344
+ inTransaction = true;
3345
+ const events = this.#database.prepare(`SELECT event_id AS eventId, goal_id AS goalId, occurred_at AS occurredAt, record
3346
+ FROM work_goal_events ORDER BY occurred_at ASC, event_id ASC
3347
+ LIMIT ?`).all(workHistoryMaxEvents + 1);
3348
+ const bindings = this.#database.prepare(`SELECT dispatch_request_id AS dispatchRequestId, lane_id AS laneId,
3349
+ expected_revision AS expectedRevision, revision, agent_id AS agentId, host, record
3350
+ FROM work_agent_bindings ORDER BY dispatch_request_id ASC
3351
+ LIMIT ?`).all(workLaneOperationMaxRecords + 1);
3352
+ const laneOperations = this.#database.prepare(`SELECT operation_id AS operationId, lane_id AS laneId, goal_id AS goalId,
3353
+ phase, revision, record
3354
+ FROM work_lane_operations ORDER BY operation_id ASC
3355
+ LIMIT ?`).all(workLaneOperationMaxRecords + 1);
3356
+ this.#database.exec("COMMIT");
3357
+ inTransaction = false;
3358
+ return { bindings, events, laneOperations };
3359
+ }
3360
+ catch (cause) {
3361
+ if (inTransaction)
3362
+ this.#database.exec("ROLLBACK");
3363
+ throw cause;
3364
+ }
3365
+ },
3366
+ catch: storeError("snapshot-input.read")
3367
+ });
3368
+ const eventRows = yield* Schema.decodeUnknownEffect(Schema.Array(AgentBindingGoalEventRow))(source.events).pipe(Effect.mapError(storeError("snapshot-input.decode-events")));
3369
+ const bindingRows = yield* Schema.decodeUnknownEffect(AgentBindingRows)(source.bindings).pipe(Effect.mapError(storeError("snapshot-input.decode-bindings")));
3370
+ const laneRows = yield* Schema.decodeUnknownEffect(Schema.Array(AgentBindingLaneOperationRow))(source.laneOperations).pipe(Effect.mapError(storeError("snapshot-input.decode-lane-operations")));
3371
+ if (eventRows.length > workHistoryMaxEvents ||
3372
+ bindingRows.length > workLaneOperationMaxRecords ||
3373
+ laneRows.length > workLaneOperationMaxRecords) {
3374
+ return yield* new WorkStoreError({ cause: source, operation: "snapshot-input.capacity" });
3375
+ }
3376
+ const events = yield* Effect.forEach(eventRows, (row) => {
3377
+ const decision = decodeAgentBindingGoalEvent(row, "snapshot-input.event");
3378
+ return decision._tag === "valid" ? Effect.succeed(decision.checkpoint) : Effect.fail(decision.error);
3379
+ });
3380
+ const eventById = new Map(eventRows.map((row) => [row.eventId, row]));
3381
+ const laneByOperation = new Map(laneRows.map((row) => [row.operationId, row]));
3382
+ let logicalObservedAt = null;
3383
+ for (const row of bindingRows) {
3384
+ const binding = yield* Effect.try({
3385
+ try: () => Schema.decodeUnknownSync(WorkAgentBinding)(JSON.parse(row.record)),
3386
+ catch: storeError("snapshot-input.decode-binding")
3387
+ });
3388
+ if (row.dispatchRequestId !== binding.request.dispatchRequestId ||
3389
+ row.laneId !== binding.request.laneId ||
3390
+ row.expectedRevision !== binding.request.expectedRevision ||
3391
+ row.revision !== binding.lane.revision ||
3392
+ row.agentId !== binding.request.worker.agentId ||
3393
+ row.host.toLowerCase() !== binding.request.worker.host.toLowerCase()) {
3394
+ return yield* new WorkStoreError({
3395
+ cause: { binding, row },
3396
+ operation: "snapshot-input.binding-identity-mismatch"
3397
+ });
3398
+ }
3399
+ const readbackError = agentBindingReadbackError(binding, laneByOperation.get(binding.lane.operationId), eventById.get(binding.checkpoint.eventId), "snapshot-input.binding");
3400
+ if (readbackError !== undefined)
3401
+ return yield* readbackError;
3402
+ logicalObservedAt = Math.max(logicalObservedAt ?? 0, binding.checkpoint.occurredAt);
3403
+ }
3404
+ return { events, logicalObservedAt };
3405
+ });
179
3406
  secureFiles() {
180
- 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
182
- ? this.#fileSystem.chmod(path, 0o600).pipe(Effect.mapError(storeError("secure.chmod")))
183
- : Effect.void)), { discard: true });
3407
+ return this.#secureFiles;
184
3408
  }
185
3409
  close() {
186
3410
  this.#database.close();