@knpkv/herdr-work 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -1
- package/dist/agent-binding.d.ts +4 -0
- package/dist/agent-binding.d.ts.map +1 -0
- package/dist/agent-binding.js +26 -0
- package/dist/agent-binding.js.map +1 -0
- package/dist/errors.d.ts +141 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +97 -0
- package/dist/errors.js.map +1 -1
- package/dist/goal-family.d.ts +4 -0
- package/dist/goal-family.d.ts.map +1 -0
- package/dist/goal-family.js +32 -0
- package/dist/goal-family.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/agent-binding-admission.d.ts +15 -0
- package/dist/internal/agent-binding-admission.d.ts.map +1 -0
- package/dist/internal/agent-binding-admission.js +92 -0
- package/dist/internal/agent-binding-admission.js.map +1 -0
- package/dist/internal/agent-binding-readback.d.ts +55 -0
- package/dist/internal/agent-binding-readback.d.ts.map +1 -0
- package/dist/internal/agent-binding-readback.js +124 -0
- package/dist/internal/agent-binding-readback.js.map +1 -0
- package/dist/internal/coordinator-authority.d.ts +62 -0
- package/dist/internal/coordinator-authority.d.ts.map +1 -0
- package/dist/internal/coordinator-authority.js +214 -0
- package/dist/internal/coordinator-authority.js.map +1 -0
- package/dist/internal/decision-handoff-migration.d.ts +126 -0
- package/dist/internal/decision-handoff-migration.d.ts.map +1 -0
- package/dist/internal/decision-handoff-migration.js +51 -0
- package/dist/internal/decision-handoff-migration.js.map +1 -0
- package/dist/internal/history-validation.d.ts +5 -0
- package/dist/internal/history-validation.d.ts.map +1 -0
- package/dist/internal/history-validation.js +33 -0
- package/dist/internal/history-validation.js.map +1 -0
- package/dist/model.d.ts +3109 -51
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +340 -11
- package/dist/model.js.map +1 -1
- package/dist/navigation.d.ts +17 -0
- package/dist/navigation.d.ts.map +1 -0
- package/dist/navigation.js +62 -0
- package/dist/navigation.js.map +1 -0
- package/dist/projection.d.ts +660 -0
- package/dist/projection.d.ts.map +1 -1
- package/dist/projection.js +65 -25
- package/dist/projection.js.map +1 -1
- package/dist/service.d.ts +1886 -3
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +138 -5
- package/dist/service.js.map +1 -1
- package/dist/sql.d.ts +30 -0
- package/dist/sql.d.ts.map +1 -0
- package/dist/sql.js +1454 -0
- package/dist/sql.js.map +1 -0
- package/dist/store.d.ts +1072 -4
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +3328 -43
- package/dist/store.js.map +1 -1
- package/dist/view.d.ts +9 -2
- package/dist/view.d.ts.map +1 -1
- package/dist/view.js +156 -8
- package/dist/view.js.map +1 -1
- package/package.json +25 -13
- package/src/styles.css +91 -17
package/dist/sql.js
ADDED
|
@@ -0,0 +1,1454 @@
|
|
|
1
|
+
import { Effect, Equal, Schema } from "effect";
|
|
2
|
+
import { makeWorkAgentBinding } from "./agent-binding.js";
|
|
3
|
+
import { WorkAgentBindingAuthorityError, WorkAgentBindingConflictError, WorkCoordinatorHandoffConflictError, WorkDecisionAuthorityConflictError, WorkDecisionHandoffConflictError, WorkDecisionRevisionConflictError, WorkDispatchHandoffConflictError, WorkProjectionError, WorkStoreError } from "./errors.js";
|
|
4
|
+
import { agentBindingAdmissionError } from "./internal/agent-binding-admission.js";
|
|
5
|
+
import { AgentBindingGoalEventRow, AgentBindingLaneOperationRow, agentBindingReadbackError, AgentBindingRow, decodeAgentBindingGoalEvent, decodeAgentBindingRow, laneOperationReadbackError } from "./internal/agent-binding-readback.js";
|
|
6
|
+
import { CoordinatorCommandRow, CoordinatorLifecycleDispatchRow, CoordinatorLifecycleEventRow, coordinatorLifecycleRunningAt, CoordinatorRouteDiscriminatorRow, coordinatorRouteRequiresWorkLink, requireCoordinatorFailedLunaAuthority, requireCoordinatorLifecycleAuthority, requireCoordinatorRouteAuthority, requireCoordinatorRouteBinding } from "./internal/coordinator-authority.js";
|
|
7
|
+
import { currentDecisionHandoffEquivalent, CurrentMetadataWorkLink, decodePreviousDecisionHandoff, previousDecisionHandoffEquivalent, PreviousWorkDecisionHandoff, upgradePreviousDecisionHandoff, workDispatchLineageContainedBy, workDispatchLineageEquivalent } from "./internal/decision-handoff-migration.js";
|
|
8
|
+
import { WorkAgentBinding, WorkAgentBindingRequest, WorkDecisionHandoff, WorkDispatchHandoff, WorkGoalCheckpoint, workHistoryMaxEvents, WorkLaneClaimed, workSnapshotMaxGoals } from "./model.js";
|
|
9
|
+
const DecisionRow = Schema.Struct({
|
|
10
|
+
handoffId: Schema.String,
|
|
11
|
+
sessionId: Schema.String,
|
|
12
|
+
laneId: Schema.String,
|
|
13
|
+
occurredAt: Schema.Number,
|
|
14
|
+
record: Schema.String
|
|
15
|
+
});
|
|
16
|
+
const DispatchHandoffRow = Schema.Struct({
|
|
17
|
+
dispatchRequestId: Schema.String,
|
|
18
|
+
handoffId: Schema.String,
|
|
19
|
+
laneId: Schema.String,
|
|
20
|
+
occurredAt: Schema.Number,
|
|
21
|
+
lineage: Schema.String,
|
|
22
|
+
record: Schema.String
|
|
23
|
+
});
|
|
24
|
+
const LaneOperationLedgerTotalsRow = Schema.Struct({
|
|
25
|
+
operationBytes: Schema.Number,
|
|
26
|
+
operationCount: Schema.Number
|
|
27
|
+
});
|
|
28
|
+
const DecisionLedgerTotalsRow = Schema.Struct({
|
|
29
|
+
decisionBytes: Schema.Number,
|
|
30
|
+
decisionCount: Schema.Number
|
|
31
|
+
});
|
|
32
|
+
const LaneRow = Schema.Struct({
|
|
33
|
+
goalId: Schema.String,
|
|
34
|
+
laneId: Schema.String,
|
|
35
|
+
operationId: Schema.String,
|
|
36
|
+
phase: Schema.String,
|
|
37
|
+
record: Schema.String,
|
|
38
|
+
revision: Schema.Number
|
|
39
|
+
});
|
|
40
|
+
const SqliteColumnRow = Schema.Struct({ name: Schema.String });
|
|
41
|
+
const LegacyDecisionRow = Schema.Struct({
|
|
42
|
+
handoffId: Schema.String,
|
|
43
|
+
laneId: Schema.String,
|
|
44
|
+
occurredAt: Schema.Number,
|
|
45
|
+
record: Schema.String
|
|
46
|
+
});
|
|
47
|
+
const LegacyDecisionRecord = Schema.Struct({
|
|
48
|
+
version: Schema.Literal("herdr.work.decision.v1"),
|
|
49
|
+
id: Schema.String,
|
|
50
|
+
laneId: Schema.String,
|
|
51
|
+
goalId: Schema.String,
|
|
52
|
+
decision: Schema.String,
|
|
53
|
+
summary: Schema.String,
|
|
54
|
+
owner: Schema.Struct({ id: Schema.String, name: Schema.String }),
|
|
55
|
+
occurredAt: Schema.Number
|
|
56
|
+
});
|
|
57
|
+
const LegacyLaneRow = Schema.Struct({ laneId: Schema.String, revision: Schema.Number, record: Schema.String });
|
|
58
|
+
const LegacyLaneRecord = Schema.Struct({
|
|
59
|
+
laneId: Schema.String,
|
|
60
|
+
worktree: Schema.String,
|
|
61
|
+
branch: Schema.String,
|
|
62
|
+
head: Schema.String,
|
|
63
|
+
owner: Schema.Struct({ id: Schema.String, name: Schema.String }),
|
|
64
|
+
parent: Schema.NullOr(Schema.String),
|
|
65
|
+
phase: Schema.String,
|
|
66
|
+
expectedRevision: Schema.Number,
|
|
67
|
+
revision: Schema.Number
|
|
68
|
+
});
|
|
69
|
+
const LegacyDispatchRow = Schema.Struct({
|
|
70
|
+
dispatchRequestId: Schema.String,
|
|
71
|
+
handoffId: Schema.String,
|
|
72
|
+
laneId: Schema.String,
|
|
73
|
+
occurredAt: Schema.Number,
|
|
74
|
+
lineage: Schema.String,
|
|
75
|
+
record: Schema.String
|
|
76
|
+
});
|
|
77
|
+
const SqliteTableRow = Schema.Struct({ name: Schema.String });
|
|
78
|
+
const LaneRevisionRow = Schema.Struct({ revision: Schema.Number });
|
|
79
|
+
const MetadataWorkLinkRow = Schema.Struct({
|
|
80
|
+
dispatchRequestId: Schema.String,
|
|
81
|
+
route: Schema.String,
|
|
82
|
+
workLink: Schema.NullOr(Schema.String)
|
|
83
|
+
});
|
|
84
|
+
const MetadataHandoffIdentityRow = Schema.Struct({
|
|
85
|
+
dispatchRequestId: Schema.String,
|
|
86
|
+
hasWorkLink: Schema.Literals([0, 1]),
|
|
87
|
+
handoffId: Schema.NullOr(Schema.String),
|
|
88
|
+
route: Schema.NullOr(Schema.String)
|
|
89
|
+
});
|
|
90
|
+
const RoutedMetadataCardinalityRow = Schema.Struct({
|
|
91
|
+
dispatchRequestId: Schema.String,
|
|
92
|
+
metadataCount: Schema.Number
|
|
93
|
+
});
|
|
94
|
+
const CoordinatorMetadataRouteRow = Schema.Struct({
|
|
95
|
+
activityIdempotencyKey: Schema.NullOr(Schema.String),
|
|
96
|
+
command: Schema.NullOr(Schema.String),
|
|
97
|
+
dispatchRequestId: Schema.String,
|
|
98
|
+
hasWorkLink: Schema.Literals([0, 1]),
|
|
99
|
+
isRouted: Schema.NullOr(Schema.Literals([0, 1])),
|
|
100
|
+
route: Schema.NullOr(Schema.String),
|
|
101
|
+
rowId: Schema.String
|
|
102
|
+
});
|
|
103
|
+
const PreviousMetadataWorkLink = Schema.Struct({
|
|
104
|
+
handoff: PreviousWorkDecisionHandoff,
|
|
105
|
+
lineage: WorkDispatchHandoff.fields.lineage
|
|
106
|
+
});
|
|
107
|
+
const LegacyMetadataWorkLink = Schema.Struct({
|
|
108
|
+
handoff: LegacyDecisionRecord,
|
|
109
|
+
lineage: WorkDispatchHandoff.fields.lineage
|
|
110
|
+
});
|
|
111
|
+
const legacyDecisionEquivalent = Schema.toEquivalence(LegacyDecisionRecord);
|
|
112
|
+
const workDecisionMaxRecords = 16_384;
|
|
113
|
+
const workDecisionMaxBytes = 2 * 1024 * 1024;
|
|
114
|
+
const utf8 = new TextEncoder();
|
|
115
|
+
const storeError = (operation) => (cause) => new WorkStoreError({ cause, operation });
|
|
116
|
+
const encodedBytes = (value) => utf8.encode(value).byteLength;
|
|
117
|
+
const decodeLegacyLane = (row) => Effect.try({
|
|
118
|
+
try: () => {
|
|
119
|
+
const legacy = Schema.decodeUnknownSync(LegacyLaneRecord)(JSON.parse(row.record));
|
|
120
|
+
if (legacy.laneId !== row.laneId || legacy.revision !== row.revision) {
|
|
121
|
+
throw new WorkStoreError({ cause: { legacy, row }, operation: "sql-work.migrate.lane-identity" });
|
|
122
|
+
}
|
|
123
|
+
return Schema.decodeUnknownSync(WorkLaneClaimed)({
|
|
124
|
+
...legacy,
|
|
125
|
+
goalId: legacy.laneId,
|
|
126
|
+
operationId: legacy.laneId
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
catch: storeError("sql-work.initialize.decode-legacy-lane")
|
|
130
|
+
});
|
|
131
|
+
const decodeLegacyDecision = (bindingRows, bindingLaneRows, bindingCheckpointRows, dispatches, lanes, metadataTablePresent, metadataRows, row) => Effect.try({
|
|
132
|
+
try: () => {
|
|
133
|
+
const legacy = Schema.decodeUnknownSync(LegacyDecisionRecord)(JSON.parse(row.record));
|
|
134
|
+
if (legacy.id !== row.handoffId || legacy.laneId !== row.laneId || legacy.occurredAt !== row.occurredAt) {
|
|
135
|
+
throw new WorkStoreError({ cause: { legacy, row }, operation: "sql-work.migrate.identity" });
|
|
136
|
+
}
|
|
137
|
+
const matchingDispatches = dispatches.filter(({ handoffId }) => handoffId === legacy.id);
|
|
138
|
+
const dispatch = matchingDispatches[0];
|
|
139
|
+
const dispatchIds = dispatch === undefined
|
|
140
|
+
? []
|
|
141
|
+
: Schema.decodeUnknownSync(WorkDispatchHandoff.fields.lineage)(JSON.parse(dispatch.lineage));
|
|
142
|
+
const lane = lanes.find(({ laneId }) => laneId === legacy.laneId);
|
|
143
|
+
if (lane === undefined) {
|
|
144
|
+
throw new WorkStoreError({ cause: legacy, operation: "sql-work.migrate.handoff-lane" });
|
|
145
|
+
}
|
|
146
|
+
if (matchingDispatches.length !== 1 || dispatch === undefined) {
|
|
147
|
+
throw new WorkStoreError({
|
|
148
|
+
cause: { legacy, matchingDispatches },
|
|
149
|
+
operation: "sql-work.migrate.legacy-dispatch-cardinality"
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
const dispatchHandoff = Schema.decodeUnknownSync(LegacyDecisionRecord)(JSON.parse(dispatch.record));
|
|
153
|
+
if (dispatch.laneId !== legacy.laneId || dispatch.occurredAt !== legacy.occurredAt ||
|
|
154
|
+
!legacyDecisionEquivalent(dispatchHandoff, legacy)) {
|
|
155
|
+
throw new WorkStoreError({
|
|
156
|
+
cause: { dispatch, dispatchHandoff, legacy },
|
|
157
|
+
operation: "sql-work.migrate.legacy-dispatch-authority"
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
if (metadataTablePresent) {
|
|
161
|
+
const matchingMetadata = metadataRows.filter(({ dispatchRequestId }) => dispatchRequestId === dispatch.dispatchRequestId);
|
|
162
|
+
const metadata = matchingMetadata[0];
|
|
163
|
+
if (matchingMetadata.length !== 1 || metadata?.workLink === null || metadata?.workLink === undefined) {
|
|
164
|
+
throw new WorkStoreError({
|
|
165
|
+
cause: { dispatch, matchingMetadata },
|
|
166
|
+
operation: "sql-work.migrate.legacy-metadata-authority"
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
const workLink = Schema.decodeUnknownSync(LegacyMetadataWorkLink)(JSON.parse(metadata.workLink));
|
|
170
|
+
if (!legacyDecisionEquivalent(workLink.handoff, legacy) ||
|
|
171
|
+
!workDispatchLineageEquivalent(workLink.lineage, dispatchIds)) {
|
|
172
|
+
throw new WorkStoreError({
|
|
173
|
+
cause: { legacy, workLink },
|
|
174
|
+
operation: "sql-work.migrate.legacy-metadata-authority"
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const matchingBindings = bindingRows.filter(({ dispatchRequestId }) => dispatchRequestId === dispatch.dispatchRequestId);
|
|
179
|
+
const bindingRow = matchingBindings[0];
|
|
180
|
+
if (matchingBindings.length !== 1 || bindingRow === undefined) {
|
|
181
|
+
throw new WorkStoreError({
|
|
182
|
+
cause: { dispatch, legacy, matchingBindings },
|
|
183
|
+
operation: "sql-work.migrate.legacy-handoff-revision"
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
const bindingDecision = decodeAgentBindingRow(bindingRow, { dispatchRequestId: dispatch.dispatchRequestId, laneId: legacy.laneId }, "sql-work.migrate.legacy-agent-binding");
|
|
187
|
+
if (bindingDecision._tag === "invalid")
|
|
188
|
+
throw bindingDecision.error;
|
|
189
|
+
if (bindingDecision.binding.lane.goalId !== legacy.goalId) {
|
|
190
|
+
throw new WorkStoreError({
|
|
191
|
+
cause: { binding: bindingDecision.binding, legacy },
|
|
192
|
+
operation: "sql-work.migrate.legacy-agent-binding.goal"
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
if (lane.revision < bindingDecision.binding.lane.revision) {
|
|
196
|
+
throw new WorkStoreError({
|
|
197
|
+
cause: { binding: bindingDecision.binding, lane, legacy },
|
|
198
|
+
operation: "sql-work.migrate.legacy-handoff-lane-revision"
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
const readbackError = agentBindingReadbackError(bindingDecision.binding, bindingLaneRows.find(({ operationId }) => operationId === bindingDecision.binding.lane.operationId), bindingCheckpointRows.find(({ eventId }) => eventId === bindingDecision.binding.checkpoint.eventId), "sql-work.migrate.legacy-agent-binding");
|
|
202
|
+
if (readbackError !== undefined)
|
|
203
|
+
throw readbackError;
|
|
204
|
+
return Schema.decodeUnknownSync(WorkDecisionHandoff)({
|
|
205
|
+
...legacy,
|
|
206
|
+
contextDelta: legacy.summary,
|
|
207
|
+
expectedRevision: bindingDecision.binding.request.expectedRevision,
|
|
208
|
+
sessionId: legacy.id,
|
|
209
|
+
dispatchIds,
|
|
210
|
+
blockers: [],
|
|
211
|
+
evidenceRefs: [],
|
|
212
|
+
version: "herdr.work.decision.v2"
|
|
213
|
+
});
|
|
214
|
+
},
|
|
215
|
+
catch: (cause) => Schema.is(WorkStoreError)(cause)
|
|
216
|
+
? cause
|
|
217
|
+
: new WorkStoreError({ cause, operation: "sql-work.initialize.decode-legacy-handoff" })
|
|
218
|
+
});
|
|
219
|
+
const decodeHandoff = (row) => Effect.try({
|
|
220
|
+
try: () => Schema.decodeUnknownSync(WorkDecisionHandoff)(JSON.parse(row.record)),
|
|
221
|
+
catch: storeError("sql-work.decode-handoff")
|
|
222
|
+
}).pipe(Effect.flatMap((handoff) => row.handoffId !== handoff.id ||
|
|
223
|
+
row.sessionId !== handoff.sessionId ||
|
|
224
|
+
row.laneId !== handoff.laneId ||
|
|
225
|
+
row.occurredAt !== handoff.occurredAt
|
|
226
|
+
? Effect.fail(new WorkStoreError({
|
|
227
|
+
cause: { handoff, row },
|
|
228
|
+
operation: "sql-work.decode-handoff.identity-mismatch"
|
|
229
|
+
}))
|
|
230
|
+
: Effect.succeed(handoff)));
|
|
231
|
+
const decodeDispatchHandoff = (row) => Effect.try({
|
|
232
|
+
try: () => Schema.decodeUnknownSync(WorkDispatchHandoff)({
|
|
233
|
+
dispatchRequestId: row.dispatchRequestId,
|
|
234
|
+
handoff: JSON.parse(row.record),
|
|
235
|
+
lineage: JSON.parse(row.lineage)
|
|
236
|
+
}),
|
|
237
|
+
catch: storeError("sql-work.decode-dispatch-handoff")
|
|
238
|
+
}).pipe(Effect.flatMap((binding) => row.handoffId !== binding.handoff.id ||
|
|
239
|
+
row.laneId !== binding.handoff.laneId ||
|
|
240
|
+
row.occurredAt !== binding.handoff.occurredAt
|
|
241
|
+
? Effect.fail(new WorkStoreError({
|
|
242
|
+
cause: { binding, row },
|
|
243
|
+
operation: "sql-work.decode-dispatch-handoff.identity-mismatch"
|
|
244
|
+
}))
|
|
245
|
+
: Effect.succeed(binding)));
|
|
246
|
+
/**
|
|
247
|
+
* SQLite-backed Work operations designed to run inside a coordinator transaction.
|
|
248
|
+
* It does not begin or commit a transaction itself, so dispatch acceptance and
|
|
249
|
+
* Work lineage either commit together or roll back together.
|
|
250
|
+
*/
|
|
251
|
+
export const makeSqliteWorkBridge = (sql) => {
|
|
252
|
+
const initialize = Effect.gen(function* () {
|
|
253
|
+
const initialTables = yield* sql `SELECT name FROM sqlite_master WHERE type = 'table'`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(SqliteTableRow))), Effect.mapError(storeError("sql-work.initialize.preflight-tables")));
|
|
254
|
+
const initialCoordinatorTables = {
|
|
255
|
+
dispatch: initialTables.some(({ name }) => name === "orchestrator_dispatches"),
|
|
256
|
+
event: initialTables.some(({ name }) => name === "orchestrator_events"),
|
|
257
|
+
metadata: initialTables.some(({ name }) => name === "orchestrator_dispatch_metadata")
|
|
258
|
+
};
|
|
259
|
+
const initialCoordinatorCount = Object.values(initialCoordinatorTables).filter(Boolean).length;
|
|
260
|
+
if (initialCoordinatorCount !== 0 && initialCoordinatorCount !== 3) {
|
|
261
|
+
return yield* new WorkStoreError({
|
|
262
|
+
cause: initialCoordinatorTables,
|
|
263
|
+
operation: "sql-work.initialize.schema"
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
yield* sql.withTransaction(Effect.gen(function* () {
|
|
267
|
+
yield* sql `
|
|
268
|
+
CREATE TABLE IF NOT EXISTS work_goal_events (
|
|
269
|
+
event_id TEXT PRIMARY KEY,
|
|
270
|
+
goal_id TEXT NOT NULL,
|
|
271
|
+
occurred_at INTEGER NOT NULL,
|
|
272
|
+
record TEXT NOT NULL,
|
|
273
|
+
transaction_id TEXT,
|
|
274
|
+
UNIQUE (goal_id, occurred_at)
|
|
275
|
+
)
|
|
276
|
+
`.pipe(Effect.mapError(storeError("sql-work.initialize.goal-events")));
|
|
277
|
+
yield* sql `
|
|
278
|
+
CREATE TABLE IF NOT EXISTS work_agent_bindings (
|
|
279
|
+
dispatch_request_id TEXT PRIMARY KEY,
|
|
280
|
+
lane_id TEXT NOT NULL,
|
|
281
|
+
expected_revision INTEGER NOT NULL,
|
|
282
|
+
revision INTEGER NOT NULL,
|
|
283
|
+
agent_id TEXT NOT NULL,
|
|
284
|
+
host TEXT NOT NULL,
|
|
285
|
+
record TEXT NOT NULL
|
|
286
|
+
)
|
|
287
|
+
`.pipe(Effect.mapError(storeError("sql-work.initialize.agent-bindings")));
|
|
288
|
+
yield* sql `
|
|
289
|
+
CREATE TABLE IF NOT EXISTS work_lane_claims (
|
|
290
|
+
lane_id TEXT PRIMARY KEY,
|
|
291
|
+
goal_id TEXT NOT NULL,
|
|
292
|
+
operation_id TEXT NOT NULL UNIQUE,
|
|
293
|
+
phase TEXT NOT NULL,
|
|
294
|
+
revision INTEGER NOT NULL,
|
|
295
|
+
record TEXT NOT NULL
|
|
296
|
+
)
|
|
297
|
+
`.pipe(Effect.mapError(storeError("sql-work.initialize.lane-claims")));
|
|
298
|
+
yield* sql `
|
|
299
|
+
CREATE TABLE IF NOT EXISTS work_lane_operations (
|
|
300
|
+
operation_id TEXT PRIMARY KEY,
|
|
301
|
+
lane_id TEXT NOT NULL,
|
|
302
|
+
goal_id TEXT NOT NULL,
|
|
303
|
+
phase TEXT NOT NULL,
|
|
304
|
+
revision INTEGER NOT NULL,
|
|
305
|
+
record TEXT NOT NULL
|
|
306
|
+
)
|
|
307
|
+
`.pipe(Effect.mapError(storeError("sql-work.initialize.lane-operations")));
|
|
308
|
+
yield* sql `
|
|
309
|
+
CREATE TABLE IF NOT EXISTS work_lane_operation_totals (
|
|
310
|
+
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
311
|
+
operation_count INTEGER NOT NULL CHECK (operation_count >= 0),
|
|
312
|
+
operation_bytes INTEGER NOT NULL CHECK (operation_bytes >= 0)
|
|
313
|
+
)
|
|
314
|
+
`.pipe(Effect.mapError(storeError("sql-work.initialize.lane-operation-totals")));
|
|
315
|
+
yield* sql `
|
|
316
|
+
CREATE TABLE IF NOT EXISTS work_decision_handoffs (
|
|
317
|
+
handoff_id TEXT PRIMARY KEY,
|
|
318
|
+
session_id TEXT NOT NULL UNIQUE,
|
|
319
|
+
lane_id TEXT NOT NULL,
|
|
320
|
+
occurred_at INTEGER NOT NULL,
|
|
321
|
+
record TEXT NOT NULL
|
|
322
|
+
)
|
|
323
|
+
`.pipe(Effect.mapError(storeError("sql-work.initialize.handoffs")));
|
|
324
|
+
yield* sql `
|
|
325
|
+
CREATE TABLE IF NOT EXISTS work_dispatch_handoffs (
|
|
326
|
+
dispatch_request_id TEXT PRIMARY KEY,
|
|
327
|
+
handoff_id TEXT NOT NULL UNIQUE,
|
|
328
|
+
lane_id TEXT NOT NULL,
|
|
329
|
+
occurred_at INTEGER NOT NULL,
|
|
330
|
+
lineage TEXT NOT NULL,
|
|
331
|
+
record TEXT NOT NULL,
|
|
332
|
+
FOREIGN KEY (handoff_id) REFERENCES work_decision_handoffs(handoff_id)
|
|
333
|
+
)
|
|
334
|
+
`.pipe(Effect.mapError(storeError("sql-work.initialize.dispatch-links")));
|
|
335
|
+
yield* sql `
|
|
336
|
+
CREATE TABLE IF NOT EXISTS work_decision_totals (
|
|
337
|
+
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
338
|
+
decision_count INTEGER NOT NULL CHECK (decision_count >= 0),
|
|
339
|
+
decision_bytes INTEGER NOT NULL CHECK (decision_bytes >= 0)
|
|
340
|
+
)
|
|
341
|
+
`.pipe(Effect.mapError(storeError("sql-work.initialize.totals")));
|
|
342
|
+
const laneColumns = yield* sql `PRAGMA table_info(work_lane_claims)`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(SqliteColumnRow))), Effect.mapError(storeError("sql-work.initialize.lane-columns")));
|
|
343
|
+
const decisionColumns = yield* sql `PRAGMA table_info(work_decision_handoffs)`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(SqliteColumnRow))), Effect.mapError(storeError("sql-work.initialize.handoff-columns")));
|
|
344
|
+
const tables = yield* sql `SELECT name FROM sqlite_master WHERE type = 'table'`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(SqliteTableRow))), Effect.mapError(storeError("sql-work.initialize.tables")));
|
|
345
|
+
const coordinatorTablesPresent = {
|
|
346
|
+
dispatch: tables.some(({ name }) => name === "orchestrator_dispatches"),
|
|
347
|
+
event: tables.some(({ name }) => name === "orchestrator_events"),
|
|
348
|
+
metadata: tables.some(({ name }) => name === "orchestrator_dispatch_metadata")
|
|
349
|
+
};
|
|
350
|
+
const coordinatorDispatchColumns = coordinatorTablesPresent.dispatch
|
|
351
|
+
? yield* sql `PRAGMA table_info(orchestrator_dispatches)`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(SqliteColumnRow))), Effect.mapError(storeError("sql-work.initialize.coordinator-dispatch-columns")))
|
|
352
|
+
: [];
|
|
353
|
+
const requireCoordinatorSchema = Effect.fn("SqlWork.requireCoordinatorSchema")(function* (operation) {
|
|
354
|
+
const present = Object.values(coordinatorTablesPresent).filter(Boolean).length;
|
|
355
|
+
if (present !== 0 && present !== 3) {
|
|
356
|
+
return yield* new WorkStoreError({
|
|
357
|
+
cause: coordinatorTablesPresent,
|
|
358
|
+
operation: `${operation}.schema`
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
return present === 3;
|
|
362
|
+
});
|
|
363
|
+
const readCoordinatorLifecycle = Effect.fn("SqlWork.readCoordinatorLifecycle")(function* (dispatchRequestId, operation) {
|
|
364
|
+
const dispatchRows = yield* sql `
|
|
365
|
+
SELECT dispatch_request_id AS "dispatchRequestId",
|
|
366
|
+
activity_idempotency_key AS "activityIdempotencyKey", command,
|
|
367
|
+
accepted_at AS "acceptedAt", status
|
|
368
|
+
FROM orchestrator_dispatches WHERE dispatch_request_id = ${dispatchRequestId} LIMIT 2
|
|
369
|
+
`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(CoordinatorLifecycleDispatchRow))), Effect.mapError(storeError(`${operation}.read-dispatch`)));
|
|
370
|
+
const eventRows = yield* sql `
|
|
371
|
+
SELECT dispatch_request_id AS "dispatchRequestId", sequence, type,
|
|
372
|
+
activity_idempotency_key AS "activityIdempotencyKey",
|
|
373
|
+
occurred_at AS "occurredAt", detail, result
|
|
374
|
+
FROM orchestrator_events WHERE dispatch_request_id = ${dispatchRequestId}
|
|
375
|
+
ORDER BY sequence ASC LIMIT 5
|
|
376
|
+
`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(CoordinatorLifecycleEventRow))), Effect.mapError(storeError(`${operation}.read-events`)));
|
|
377
|
+
return { dispatchRows, eventRows };
|
|
378
|
+
});
|
|
379
|
+
const routeStorageAuthority = Effect.fn("SqlWork.routeStorageAuthority")(function* (dispatchRequestId, operation) {
|
|
380
|
+
if (!coordinatorDispatchColumns.some(({ name }) => name === "is_routed")) {
|
|
381
|
+
return {
|
|
382
|
+
_tag: "legacy_without_routed_discriminator"
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
const rows = yield* sql `
|
|
386
|
+
SELECT is_routed AS "isRouted" FROM orchestrator_dispatches
|
|
387
|
+
WHERE dispatch_request_id = ${dispatchRequestId} LIMIT 2
|
|
388
|
+
`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(CoordinatorRouteDiscriminatorRow))), Effect.mapError(storeError(operation)));
|
|
389
|
+
const row = rows[0];
|
|
390
|
+
if (rows.length !== 1 || row === undefined) {
|
|
391
|
+
return yield* new WorkStoreError({ cause: { dispatchRequestId, rows }, operation });
|
|
392
|
+
}
|
|
393
|
+
return {
|
|
394
|
+
_tag: "routed_discriminator",
|
|
395
|
+
isRouted: row.isRouted
|
|
396
|
+
};
|
|
397
|
+
});
|
|
398
|
+
const requireLinkedParentAuthority = Effect.fn("SqlWork.requireLinkedParentAuthority")(function* (linkedRequestId, operation) {
|
|
399
|
+
if (linkedRequestId === null)
|
|
400
|
+
return;
|
|
401
|
+
const metadataRows = yield* sql `
|
|
402
|
+
SELECT dispatch_request_id AS "dispatchRequestId", route, work_link AS "workLink"
|
|
403
|
+
FROM orchestrator_dispatch_metadata WHERE dispatch_request_id = ${linkedRequestId} LIMIT 2
|
|
404
|
+
`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(MetadataWorkLinkRow))), Effect.mapError(storeError(operation)));
|
|
405
|
+
const metadata = metadataRows[0];
|
|
406
|
+
if (metadataRows.length !== 1 || metadata === undefined || metadata.workLink !== null) {
|
|
407
|
+
return yield* new WorkStoreError({ cause: { linkedRequestId, metadataRows }, operation });
|
|
408
|
+
}
|
|
409
|
+
const lifecycle = yield* readCoordinatorLifecycle(linkedRequestId, operation);
|
|
410
|
+
const storageAuthority = yield* routeStorageAuthority(linkedRequestId, operation);
|
|
411
|
+
yield* Effect.try({
|
|
412
|
+
try: () => requireCoordinatorFailedLunaAuthority(lifecycle.dispatchRows, lifecycle.eventRows, metadata.route, storageAuthority, operation),
|
|
413
|
+
catch: (cause) => Schema.is(WorkStoreError)(cause) ? cause : new WorkStoreError({ cause, operation })
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
const requireMetadataAuthority = Effect.fn("SqlWork.requireMetadataAuthority")(function* (dispatchRequestId, routeText, lineage, operation) {
|
|
417
|
+
const commandRows = yield* sql `
|
|
418
|
+
SELECT activity_idempotency_key AS "activityIdempotencyKey", command FROM orchestrator_dispatches
|
|
419
|
+
WHERE dispatch_request_id = ${dispatchRequestId} LIMIT 2
|
|
420
|
+
`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(CoordinatorCommandRow))), Effect.mapError(storeError(operation)));
|
|
421
|
+
const commandRow = commandRows[0];
|
|
422
|
+
if (commandRows.length !== 1 || commandRow === undefined) {
|
|
423
|
+
return yield* new WorkStoreError({ cause: { commandRows, dispatchRequestId }, operation });
|
|
424
|
+
}
|
|
425
|
+
const storageAuthority = yield* routeStorageAuthority(dispatchRequestId, operation);
|
|
426
|
+
const route = yield* Effect.try({
|
|
427
|
+
try: () => requireCoordinatorRouteAuthority(commandRow.command, commandRow.activityIdempotencyKey, routeText, lineage, storageAuthority, operation),
|
|
428
|
+
catch: (cause) => Schema.is(WorkStoreError)(cause) ? cause : new WorkStoreError({ cause, operation })
|
|
429
|
+
});
|
|
430
|
+
yield* requireLinkedParentAuthority(route.linkedRequestId, `${operation}.parent`);
|
|
431
|
+
});
|
|
432
|
+
const requireLifecycleAuthority = Effect.fn("SqlWork.requireLifecycleAuthority")(function* (dispatchRequestId, expectedRunningAt, operation) {
|
|
433
|
+
if (!(yield* requireCoordinatorSchema(operation)))
|
|
434
|
+
return;
|
|
435
|
+
const lifecycle = yield* readCoordinatorLifecycle(dispatchRequestId, operation);
|
|
436
|
+
yield* Effect.try({
|
|
437
|
+
try: () => requireCoordinatorLifecycleAuthority(lifecycle.dispatchRows, lifecycle.eventRows, expectedRunningAt, operation),
|
|
438
|
+
catch: (cause) => Schema.is(WorkStoreError)(cause) ? cause : new WorkStoreError({ cause, operation })
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
const requireCurrentAgentBindingAuthority = Effect.fn("SqlWork.requireCurrentAgentBindingAuthority")(function* (dispatchRequestId, handoff, operation) {
|
|
442
|
+
const lifecycle = yield* readCoordinatorLifecycle(dispatchRequestId, `${operation}.lifecycle`);
|
|
443
|
+
const runningAt = yield* Effect.try({
|
|
444
|
+
try: () => coordinatorLifecycleRunningAt(lifecycle.dispatchRows, lifecycle.eventRows, `${operation}.lifecycle`),
|
|
445
|
+
catch: (cause) => Schema.is(WorkStoreError)(cause) ? cause : new WorkStoreError({ cause, operation })
|
|
446
|
+
});
|
|
447
|
+
const bindingRows = tables.some(({ name }) => name === "work_agent_bindings")
|
|
448
|
+
? yield* sql `
|
|
449
|
+
SELECT dispatch_request_id AS "dispatchRequestId", lane_id AS "laneId",
|
|
450
|
+
expected_revision AS "expectedRevision", revision, agent_id AS "agentId", host, record
|
|
451
|
+
FROM work_agent_bindings WHERE dispatch_request_id = ${dispatchRequestId} LIMIT 2
|
|
452
|
+
`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(AgentBindingRow))), Effect.mapError(storeError(operation)))
|
|
453
|
+
: [];
|
|
454
|
+
if (runningAt === null && bindingRows.length === 0)
|
|
455
|
+
return;
|
|
456
|
+
const bindingRow = bindingRows[0];
|
|
457
|
+
if (runningAt === null || bindingRows.length !== 1 || bindingRow === undefined) {
|
|
458
|
+
return yield* new WorkStoreError({ cause: { bindingRows, lifecycle }, operation });
|
|
459
|
+
}
|
|
460
|
+
const decoded = decodeAgentBindingRow(bindingRow, { dispatchRequestId, laneId: handoff.laneId }, operation);
|
|
461
|
+
if (decoded._tag === "invalid")
|
|
462
|
+
return yield* decoded.error;
|
|
463
|
+
if (decoded.binding.request.expectedRevision !== handoff.expectedRevision ||
|
|
464
|
+
decoded.binding.lane.goalId !== handoff.goalId ||
|
|
465
|
+
decoded.binding.checkpoint.occurredAt !== runningAt) {
|
|
466
|
+
return yield* new WorkStoreError({ cause: { binding: decoded.binding, handoff, runningAt }, operation });
|
|
467
|
+
}
|
|
468
|
+
const companions = yield* Effect.all({
|
|
469
|
+
checkpointRows: sql `
|
|
470
|
+
SELECT event_id AS "eventId", goal_id AS "goalId", occurred_at AS "occurredAt", record
|
|
471
|
+
FROM work_goal_events WHERE event_id = ${decoded.binding.checkpoint.eventId}
|
|
472
|
+
`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(AgentBindingGoalEventRow))), Effect.mapError(storeError(`${operation}.checkpoint-companion`))),
|
|
473
|
+
laneRows: sql `
|
|
474
|
+
SELECT operation_id AS "operationId", lane_id AS "laneId", goal_id AS "goalId",
|
|
475
|
+
phase, revision, record
|
|
476
|
+
FROM work_lane_operations WHERE operation_id = ${decoded.binding.lane.operationId}
|
|
477
|
+
`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(AgentBindingLaneOperationRow))), Effect.mapError(storeError(`${operation}.lane-companion`)))
|
|
478
|
+
});
|
|
479
|
+
const readbackError = agentBindingReadbackError(decoded.binding, companions.laneRows[0], companions.checkpointRows[0], operation);
|
|
480
|
+
if (readbackError !== undefined)
|
|
481
|
+
return yield* readbackError;
|
|
482
|
+
const currentLaneRows = yield* sql `
|
|
483
|
+
SELECT lane_id AS "laneId", goal_id AS "goalId", operation_id AS "operationId",
|
|
484
|
+
phase, revision, record
|
|
485
|
+
FROM work_lane_claims WHERE lane_id = ${decoded.binding.lane.laneId} LIMIT 2
|
|
486
|
+
`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(LaneRow))), Effect.mapError(storeError(`${operation}.lane-revision`)));
|
|
487
|
+
const currentLaneRow = currentLaneRows[0];
|
|
488
|
+
if (currentLaneRows.length !== 1 || currentLaneRow === undefined) {
|
|
489
|
+
return yield* new WorkStoreError({
|
|
490
|
+
cause: { binding: decoded.binding, currentLaneRows },
|
|
491
|
+
operation: `${operation}.lane-revision`
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
const currentLane = yield* Effect.try({
|
|
495
|
+
try: () => Schema.decodeUnknownSync(WorkLaneClaimed)(JSON.parse(currentLaneRow.record)),
|
|
496
|
+
catch: storeError(`${operation}.lane-revision`)
|
|
497
|
+
});
|
|
498
|
+
const currentLaneSchema = laneColumns.some(({ name }) => name === "goal_id");
|
|
499
|
+
const currentOperationRows = currentLaneSchema
|
|
500
|
+
? yield* sql `
|
|
501
|
+
SELECT operation_id AS "operationId", lane_id AS "laneId", goal_id AS "goalId",
|
|
502
|
+
phase, revision, record
|
|
503
|
+
FROM work_lane_operations WHERE operation_id = ${currentLane.operationId} LIMIT 2
|
|
504
|
+
`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(AgentBindingLaneOperationRow))), Effect.mapError(storeError(`${operation}.lane-revision`)))
|
|
505
|
+
: [];
|
|
506
|
+
const currentOperationError = currentLaneSchema
|
|
507
|
+
? laneOperationReadbackError(currentLane, currentOperationRows.length === 1 ? currentOperationRows[0] : undefined, `${operation}.lane-revision`)
|
|
508
|
+
: undefined;
|
|
509
|
+
if (currentLane.laneId !== currentLaneRow.laneId ||
|
|
510
|
+
currentLane.goalId !== currentLaneRow.goalId ||
|
|
511
|
+
currentLane.operationId !== currentLaneRow.operationId ||
|
|
512
|
+
currentLane.phase !== currentLaneRow.phase ||
|
|
513
|
+
currentLane.revision !== currentLaneRow.revision ||
|
|
514
|
+
currentLane.laneId !== decoded.binding.lane.laneId ||
|
|
515
|
+
currentLane.goalId !== decoded.binding.lane.goalId ||
|
|
516
|
+
currentLane.revision < decoded.binding.lane.revision ||
|
|
517
|
+
(currentLaneSchema && currentLane.revision === decoded.binding.lane.revision &&
|
|
518
|
+
!Equal.equals(currentLane, decoded.binding.lane)) ||
|
|
519
|
+
currentOperationError !== undefined) {
|
|
520
|
+
return yield* new WorkStoreError({
|
|
521
|
+
cause: { binding: decoded.binding, currentLane, currentLaneRow, currentOperationError },
|
|
522
|
+
operation: `${operation}.lane-revision`
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
const hasLegacyDecisionTable = !decisionColumns.some(({ name }) => name === "session_id");
|
|
527
|
+
const legacyLanes = !laneColumns.some(({ name }) => name === "goal_id")
|
|
528
|
+
? yield* sql `SELECT lane_id AS "laneId", revision, record FROM work_lane_claims`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(LegacyLaneRow))), Effect.flatMap((rows) => Effect.forEach(rows, decodeLegacyLane)), Effect.mapError(storeError("sql-work.initialize.legacy-lanes")))
|
|
529
|
+
: [];
|
|
530
|
+
const legacyDispatches = yield* sql `
|
|
531
|
+
SELECT dispatch_request_id AS "dispatchRequestId", handoff_id AS "handoffId",
|
|
532
|
+
lane_id AS "laneId", occurred_at AS "occurredAt", lineage, record
|
|
533
|
+
FROM work_dispatch_handoffs
|
|
534
|
+
`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(LegacyDispatchRow))), Effect.mapError(storeError("sql-work.initialize.legacy-dispatches")));
|
|
535
|
+
const legacyDispatchRequestIds = Array.from(new Set(legacyDispatches.map(({ dispatchRequestId }) => dispatchRequestId)));
|
|
536
|
+
const legacyBindingRows = hasLegacyDecisionTable &&
|
|
537
|
+
tables.some(({ name }) => name === "work_agent_bindings") &&
|
|
538
|
+
legacyDispatchRequestIds.length > 0
|
|
539
|
+
? yield* sql `
|
|
540
|
+
SELECT dispatch_request_id AS "dispatchRequestId", lane_id AS "laneId",
|
|
541
|
+
expected_revision AS "expectedRevision", revision, agent_id AS "agentId", host, record
|
|
542
|
+
FROM work_agent_bindings
|
|
543
|
+
WHERE dispatch_request_id IN ${sql.in(legacyDispatchRequestIds)}
|
|
544
|
+
`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(AgentBindingRow))), Effect.mapError(storeError("sql-work.initialize.legacy-binding-rows")))
|
|
545
|
+
: [];
|
|
546
|
+
const legacyBindings = yield* Effect.forEach(hasLegacyDecisionTable ? legacyBindingRows : [], (row) => Effect.gen(function* () {
|
|
547
|
+
const decision = decodeAgentBindingRow(row, { dispatchRequestId: row.dispatchRequestId, laneId: row.laneId }, "sql-work.initialize.legacy-agent-binding");
|
|
548
|
+
return decision._tag === "invalid" ? yield* decision.error : decision.binding;
|
|
549
|
+
}));
|
|
550
|
+
const legacyBindingOperationIds = Array.from(new Set(legacyBindings.map(({ lane }) => lane.operationId)));
|
|
551
|
+
const legacyBindingEventIds = Array.from(new Set(legacyBindings.map(({ checkpoint }) => checkpoint.eventId)));
|
|
552
|
+
const legacyBindingLaneRows = tables.some(({ name }) => name === "work_lane_operations") && legacyBindingOperationIds.length > 0
|
|
553
|
+
? yield* sql `
|
|
554
|
+
SELECT operation_id AS "operationId", lane_id AS "laneId", goal_id AS "goalId",
|
|
555
|
+
phase, revision, record
|
|
556
|
+
FROM work_lane_operations
|
|
557
|
+
WHERE operation_id IN ${sql.in(legacyBindingOperationIds)}
|
|
558
|
+
`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(AgentBindingLaneOperationRow))), Effect.mapError(storeError("sql-work.initialize.legacy-binding-lane-rows")))
|
|
559
|
+
: [];
|
|
560
|
+
const legacyBindingCheckpointRows = tables.some(({ name }) => name === "work_goal_events") && legacyBindingEventIds.length > 0
|
|
561
|
+
? yield* sql `
|
|
562
|
+
SELECT event_id AS "eventId", goal_id AS "goalId", occurred_at AS "occurredAt", record
|
|
563
|
+
FROM work_goal_events
|
|
564
|
+
WHERE event_id IN ${sql.in(legacyBindingEventIds)}
|
|
565
|
+
`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(AgentBindingGoalEventRow))), Effect.mapError(storeError("sql-work.initialize.legacy-binding-checkpoint-rows")))
|
|
566
|
+
: [];
|
|
567
|
+
const legacyMetadata = hasLegacyDecisionTable &&
|
|
568
|
+
tables.some(({ name }) => name === "orchestrator_dispatch_metadata") &&
|
|
569
|
+
legacyDispatchRequestIds.length > 0
|
|
570
|
+
? yield* sql `
|
|
571
|
+
SELECT dispatch_request_id AS "dispatchRequestId", route, work_link AS "workLink"
|
|
572
|
+
FROM orchestrator_dispatch_metadata
|
|
573
|
+
WHERE dispatch_request_id IN ${sql.in(legacyDispatchRequestIds)}
|
|
574
|
+
`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(MetadataWorkLinkRow))), Effect.mapError(storeError("sql-work.initialize.legacy-metadata")))
|
|
575
|
+
: [];
|
|
576
|
+
const metadataTablePresent = tables.some(({ name }) => name === "orchestrator_dispatch_metadata");
|
|
577
|
+
const legacyDecisions = hasLegacyDecisionTable
|
|
578
|
+
? yield* sql `
|
|
579
|
+
SELECT handoff_id AS "handoffId", lane_id AS "laneId", occurred_at AS "occurredAt", record
|
|
580
|
+
FROM work_decision_handoffs
|
|
581
|
+
`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(LegacyDecisionRow))), Effect.mapError(storeError("sql-work.initialize.legacy-handoff-rows")), Effect.flatMap((rows) => Effect.forEach(rows, (row) => decodeLegacyDecision(legacyBindingRows, legacyBindingLaneRows, legacyBindingCheckpointRows, legacyDispatches, legacyLanes, metadataTablePresent, legacyMetadata, row))))
|
|
582
|
+
: [];
|
|
583
|
+
yield* Effect.forEach(legacyDecisions, (handoff) => {
|
|
584
|
+
const dispatch = legacyDispatches.find(({ handoffId }) => handoffId === handoff.id);
|
|
585
|
+
const binding = dispatch === undefined
|
|
586
|
+
? undefined
|
|
587
|
+
: legacyBindings.find(({ request }) => request.dispatchRequestId === dispatch.dispatchRequestId);
|
|
588
|
+
if (dispatch === undefined || binding === undefined) {
|
|
589
|
+
return Effect.fail(new WorkStoreError({
|
|
590
|
+
cause: { binding, dispatch, handoff },
|
|
591
|
+
operation: "sql-work.initialize.legacy-agent-binding.lifecycle"
|
|
592
|
+
}));
|
|
593
|
+
}
|
|
594
|
+
return Effect.gen(function* () {
|
|
595
|
+
yield* requireLifecycleAuthority(dispatch.dispatchRequestId, binding.checkpoint.occurredAt, "sql-work.initialize.legacy-agent-binding.lifecycle");
|
|
596
|
+
if (!metadataTablePresent)
|
|
597
|
+
return;
|
|
598
|
+
const matchingMetadata = legacyMetadata.filter(({ dispatchRequestId }) => dispatchRequestId === dispatch.dispatchRequestId);
|
|
599
|
+
const metadata = matchingMetadata[0];
|
|
600
|
+
const encodedWorkLink = metadata?.workLink;
|
|
601
|
+
const encodedRoute = metadata?.route;
|
|
602
|
+
if (matchingMetadata.length !== 1 || encodedWorkLink === null || encodedWorkLink === undefined ||
|
|
603
|
+
encodedRoute === undefined) {
|
|
604
|
+
return yield* new WorkStoreError({
|
|
605
|
+
cause: { dispatch, matchingMetadata },
|
|
606
|
+
operation: "sql-work.migrate.legacy-metadata-authority"
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
const workLink = yield* Effect.try({
|
|
610
|
+
try: () => Schema.decodeUnknownSync(LegacyMetadataWorkLink)(JSON.parse(encodedWorkLink)),
|
|
611
|
+
catch: storeError("sql-work.migrate.legacy-metadata-authority")
|
|
612
|
+
});
|
|
613
|
+
yield* requireMetadataAuthority(dispatch.dispatchRequestId, encodedRoute, workLink.lineage, "sql-work.migrate.legacy-metadata-authority");
|
|
614
|
+
});
|
|
615
|
+
}, { discard: true });
|
|
616
|
+
if (legacyLanes.length > 0 || legacyDecisions.length > 0 ||
|
|
617
|
+
!laneColumns.some(({ name }) => name === "goal_id") ||
|
|
618
|
+
!decisionColumns.some(({ name }) => name === "session_id")) {
|
|
619
|
+
if (!laneColumns.some(({ name }) => name === "goal_id")) {
|
|
620
|
+
yield* sql `ALTER TABLE work_lane_claims ADD COLUMN goal_id TEXT`;
|
|
621
|
+
yield* sql `ALTER TABLE work_lane_claims ADD COLUMN operation_id TEXT`;
|
|
622
|
+
yield* sql `ALTER TABLE work_lane_claims ADD COLUMN phase TEXT`;
|
|
623
|
+
yield* Effect.forEach(legacyLanes, (lane) => sql `UPDATE work_lane_claims SET goal_id = ${lane.goalId}, operation_id = ${lane.operationId},
|
|
624
|
+
phase = ${lane.phase}, record = ${JSON.stringify(lane)} WHERE lane_id = ${lane.laneId}`, { discard: true });
|
|
625
|
+
}
|
|
626
|
+
if (!decisionColumns.some(({ name }) => name === "session_id")) {
|
|
627
|
+
yield* sql `ALTER TABLE work_decision_handoffs ADD COLUMN session_id TEXT`;
|
|
628
|
+
}
|
|
629
|
+
yield* Effect.forEach(legacyDecisions, (handoff) => sql `UPDATE work_decision_handoffs
|
|
630
|
+
SET session_id = ${handoff.sessionId}, record = ${JSON.stringify(handoff)}
|
|
631
|
+
WHERE handoff_id = ${handoff.id}`, { discard: true });
|
|
632
|
+
yield* Effect.forEach(legacyDispatches, (dispatch) => {
|
|
633
|
+
const handoff = legacyDecisions.find(({ id }) => id === dispatch.handoffId);
|
|
634
|
+
if (handoff === undefined)
|
|
635
|
+
return Effect.void;
|
|
636
|
+
const workLink = { handoff, lineage: handoff.dispatchIds };
|
|
637
|
+
return Effect.all([
|
|
638
|
+
sql `UPDATE work_dispatch_handoffs SET record = ${JSON.stringify(handoff)}
|
|
639
|
+
WHERE dispatch_request_id = ${dispatch.dispatchRequestId}`,
|
|
640
|
+
tables.some(({ name }) => name === "orchestrator_dispatch_metadata")
|
|
641
|
+
? sql `UPDATE orchestrator_dispatch_metadata SET work_link = ${JSON.stringify(workLink)}
|
|
642
|
+
WHERE dispatch_request_id = ${dispatch.dispatchRequestId}`
|
|
643
|
+
: Effect.void
|
|
644
|
+
], { discard: true });
|
|
645
|
+
}, { discard: true });
|
|
646
|
+
}
|
|
647
|
+
const storedDecisions = yield* sql `
|
|
648
|
+
SELECT handoff_id AS "handoffId", session_id AS "sessionId", lane_id AS "laneId",
|
|
649
|
+
occurred_at AS "occurredAt", record
|
|
650
|
+
FROM work_decision_handoffs
|
|
651
|
+
`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(DecisionRow))), Effect.mapError(storeError("sql-work.initialize.persisted-handoffs")));
|
|
652
|
+
const orphanDispatch = legacyDispatches.find(({ handoffId }) => !storedDecisions.some((decision) => decision.handoffId === handoffId));
|
|
653
|
+
if (orphanDispatch !== undefined) {
|
|
654
|
+
return yield* new WorkStoreError({
|
|
655
|
+
cause: { dispatch: orphanDispatch, storedDecisions },
|
|
656
|
+
operation: "sql-work.initialize.dispatch-decision"
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
yield* requireCoordinatorSchema("sql-work.initialize");
|
|
660
|
+
if (metadataTablePresent) {
|
|
661
|
+
const linkedMetadata = yield* sql `
|
|
662
|
+
SELECT dispatch_request_id AS "dispatchRequestId",
|
|
663
|
+
CASE WHEN work_link IS NULL THEN 0 ELSE 1 END AS "hasWorkLink",
|
|
664
|
+
CASE WHEN json_valid(work_link) AND json_type(work_link, '$.handoff.id') = 'text'
|
|
665
|
+
THEN json_extract(work_link, '$.handoff.id') END AS "handoffId",
|
|
666
|
+
route
|
|
667
|
+
FROM orchestrator_dispatch_metadata AS metadata
|
|
668
|
+
WHERE work_link IS NOT NULL OR EXISTS (
|
|
669
|
+
SELECT 1 FROM orchestrator_dispatch_metadata AS linked
|
|
670
|
+
WHERE linked.dispatch_request_id = metadata.dispatch_request_id
|
|
671
|
+
AND linked.work_link IS NOT NULL
|
|
672
|
+
) OR (json_valid(route) AND json_extract(route, '$.model') = 'gpt-5.6-sol')
|
|
673
|
+
LIMIT ${workDecisionMaxRecords + 1}
|
|
674
|
+
`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(MetadataHandoffIdentityRow))), Effect.mapError(storeError("sql-work.initialize.metadata-decision")));
|
|
675
|
+
if (linkedMetadata.length > workDecisionMaxRecords) {
|
|
676
|
+
return yield* new WorkStoreError({
|
|
677
|
+
cause: linkedMetadata.length,
|
|
678
|
+
operation: "sql-work.initialize.metadata-capacity"
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
const metadataCounts = new Map();
|
|
682
|
+
const dispatchCounts = new Map();
|
|
683
|
+
const decisionCounts = new Map();
|
|
684
|
+
for (const metadata of linkedMetadata) {
|
|
685
|
+
metadataCounts.set(metadata.dispatchRequestId, (metadataCounts.get(metadata.dispatchRequestId) ?? 0) + 1);
|
|
686
|
+
}
|
|
687
|
+
for (const dispatch of legacyDispatches) {
|
|
688
|
+
const identity = JSON.stringify([dispatch.dispatchRequestId, dispatch.handoffId]);
|
|
689
|
+
dispatchCounts.set(identity, (dispatchCounts.get(identity) ?? 0) + 1);
|
|
690
|
+
}
|
|
691
|
+
for (const decision of storedDecisions) {
|
|
692
|
+
decisionCounts.set(decision.handoffId, (decisionCounts.get(decision.handoffId) ?? 0) + 1);
|
|
693
|
+
}
|
|
694
|
+
const invalidMetadataFlags = yield* Effect.forEach(linkedMetadata, (metadata) => Effect.gen(function* () {
|
|
695
|
+
if (metadata.route === null)
|
|
696
|
+
return true;
|
|
697
|
+
const routeText = metadata.route;
|
|
698
|
+
const storageAuthority = yield* routeStorageAuthority(metadata.dispatchRequestId, "sql-work.initialize.metadata-decision");
|
|
699
|
+
const requiresWorkLink = yield* Effect.try({
|
|
700
|
+
try: () => coordinatorRouteRequiresWorkLink(routeText, storageAuthority, "sql-work.initialize.metadata-decision"),
|
|
701
|
+
catch: (cause) => Schema.is(WorkStoreError)(cause)
|
|
702
|
+
? cause
|
|
703
|
+
: new WorkStoreError({ cause, operation: "sql-work.initialize.metadata-decision" })
|
|
704
|
+
});
|
|
705
|
+
if (!requiresWorkLink)
|
|
706
|
+
return metadata.hasWorkLink === 1;
|
|
707
|
+
const dispatchIdentity = JSON.stringify([metadata.dispatchRequestId, metadata.handoffId]);
|
|
708
|
+
return metadata.handoffId === null || metadataCounts.get(metadata.dispatchRequestId) !== 1 ||
|
|
709
|
+
dispatchCounts.get(dispatchIdentity) !== 1 || decisionCounts.get(metadata.handoffId) !== 1;
|
|
710
|
+
}));
|
|
711
|
+
const invalidMetadataIndex = invalidMetadataFlags.findIndex(Boolean);
|
|
712
|
+
const invalidMetadata = invalidMetadataIndex === -1
|
|
713
|
+
? undefined
|
|
714
|
+
: linkedMetadata[invalidMetadataIndex];
|
|
715
|
+
if (invalidMetadata !== undefined) {
|
|
716
|
+
return yield* new WorkStoreError({
|
|
717
|
+
cause: { dispatches: legacyDispatches, linkedMetadata, metadata: invalidMetadata, storedDecisions },
|
|
718
|
+
operation: "sql-work.initialize.metadata-decision"
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
const migrationResults = yield* Effect.forEach(storedDecisions, (row) => Effect.gen(function* () {
|
|
723
|
+
const input = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Json))(row.record).pipe(Effect.mapError(storeError("sql-work.initialize.parse-handoff")));
|
|
724
|
+
const previous = decodePreviousDecisionHandoff(input);
|
|
725
|
+
const handoffDispatches = tables.some(({ name }) => name === "work_dispatch_handoffs")
|
|
726
|
+
? yield* sql `
|
|
727
|
+
SELECT dispatch_request_id AS "dispatchRequestId", handoff_id AS "handoffId",
|
|
728
|
+
lane_id AS "laneId", occurred_at AS "occurredAt", lineage, record
|
|
729
|
+
FROM work_dispatch_handoffs WHERE handoff_id = ${row.handoffId}
|
|
730
|
+
`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(LegacyDispatchRow))), Effect.mapError(storeError("sql-work.initialize.handoff-dispatches")))
|
|
731
|
+
: [];
|
|
732
|
+
if (handoffDispatches.length > 1) {
|
|
733
|
+
return yield* new WorkStoreError({
|
|
734
|
+
cause: { handoffDispatches, row },
|
|
735
|
+
operation: "sql-work.initialize.dispatch-cardinality"
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
const linkedMetadata = tables.some(({ name }) => name === "orchestrator_dispatch_metadata")
|
|
739
|
+
? yield* sql `
|
|
740
|
+
SELECT dispatch_request_id AS "dispatchRequestId", route, work_link AS "workLink"
|
|
741
|
+
FROM orchestrator_dispatch_metadata
|
|
742
|
+
WHERE work_link IS NOT NULL
|
|
743
|
+
AND CASE WHEN json_valid(work_link)
|
|
744
|
+
THEN json_extract(work_link, '$.handoff.id') END = ${row.handoffId}
|
|
745
|
+
`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(MetadataWorkLinkRow))), Effect.mapError(storeError("sql-work.initialize.metadata-authority")))
|
|
746
|
+
: [];
|
|
747
|
+
if (tables.some(({ name }) => name === "orchestrator_dispatch_metadata") &&
|
|
748
|
+
(linkedMetadata.length !== handoffDispatches.length ||
|
|
749
|
+
linkedMetadata.some((metadata) => !handoffDispatches.some(({ dispatchRequestId }) => dispatchRequestId === metadata.dispatchRequestId)))) {
|
|
750
|
+
return yield* new WorkStoreError({
|
|
751
|
+
cause: { handoffDispatches, linkedMetadata, row },
|
|
752
|
+
operation: "sql-work.initialize.metadata-authority"
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
if (previous._tag === "current") {
|
|
756
|
+
if (previous.value.id !== row.handoffId || previous.value.sessionId !== row.sessionId ||
|
|
757
|
+
previous.value.laneId !== row.laneId || previous.value.occurredAt !== row.occurredAt) {
|
|
758
|
+
return yield* new WorkStoreError({
|
|
759
|
+
cause: { handoff: previous.value, row },
|
|
760
|
+
operation: "sql-work.initialize.handoff-identity"
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
yield* Effect.forEach(handoffDispatches, (dispatch) => Effect.gen(function* () {
|
|
764
|
+
const decoded = yield* Effect.try({
|
|
765
|
+
try: () => ({
|
|
766
|
+
handoff: Schema.decodeUnknownSync(WorkDecisionHandoff)(JSON.parse(dispatch.record)),
|
|
767
|
+
lineage: Schema.decodeUnknownSync(WorkDispatchHandoff.fields.lineage)(JSON.parse(dispatch.lineage))
|
|
768
|
+
}),
|
|
769
|
+
catch: storeError("sql-work.initialize.decode-dispatch-authority")
|
|
770
|
+
});
|
|
771
|
+
if (dispatch.laneId !== previous.value.laneId ||
|
|
772
|
+
dispatch.occurredAt !== previous.value.occurredAt ||
|
|
773
|
+
!currentDecisionHandoffEquivalent(decoded.handoff, previous.value) ||
|
|
774
|
+
!workDispatchLineageContainedBy(decoded.lineage, previous.value.dispatchIds)) {
|
|
775
|
+
return yield* new WorkStoreError({
|
|
776
|
+
cause: { decoded, dispatch, handoff: previous.value },
|
|
777
|
+
operation: "sql-work.initialize.dispatch-authority"
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
if (!tables.some(({ name }) => name === "orchestrator_dispatch_metadata"))
|
|
781
|
+
return;
|
|
782
|
+
const metadata = linkedMetadata.find(({ dispatchRequestId }) => dispatchRequestId === dispatch.dispatchRequestId);
|
|
783
|
+
const encodedWorkLink = metadata?.workLink;
|
|
784
|
+
const encodedRoute = metadata?.route;
|
|
785
|
+
if (encodedWorkLink === null || encodedWorkLink === undefined || encodedRoute === undefined) {
|
|
786
|
+
return yield* new WorkStoreError({
|
|
787
|
+
cause: { dispatch, linkedMetadata },
|
|
788
|
+
operation: "sql-work.initialize.metadata-authority"
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
const workLink = yield* Effect.try({
|
|
792
|
+
try: () => Schema.decodeUnknownSync(CurrentMetadataWorkLink)(JSON.parse(encodedWorkLink)),
|
|
793
|
+
catch: storeError("sql-work.initialize.decode-metadata-authority")
|
|
794
|
+
});
|
|
795
|
+
yield* requireMetadataAuthority(dispatch.dispatchRequestId, encodedRoute, workLink.lineage, "sql-work.initialize.metadata-authority");
|
|
796
|
+
if (!currentDecisionHandoffEquivalent(workLink.handoff, previous.value) ||
|
|
797
|
+
!workDispatchLineageEquivalent(workLink.lineage, decoded.lineage)) {
|
|
798
|
+
return yield* new WorkStoreError({
|
|
799
|
+
cause: { handoff: previous.value, workLink },
|
|
800
|
+
operation: "sql-work.initialize.metadata-authority"
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
yield* requireCurrentAgentBindingAuthority(dispatch.dispatchRequestId, previous.value, "sql-work.initialize.current-agent-binding");
|
|
804
|
+
}), { discard: true });
|
|
805
|
+
return false;
|
|
806
|
+
}
|
|
807
|
+
if (previous._tag === "invalid") {
|
|
808
|
+
return yield* new WorkStoreError({
|
|
809
|
+
cause: previous.cause,
|
|
810
|
+
operation: "sql-work.initialize.invalid-handoff"
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
const laneRows = yield* sql `SELECT revision FROM work_lane_claims WHERE lane_id = ${row.laneId}`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(LaneRevisionRow))), Effect.mapError(storeError("sql-work.initialize.handoff-lane-revision")));
|
|
814
|
+
const laneRevision = laneRows[0];
|
|
815
|
+
if (laneRevision === undefined) {
|
|
816
|
+
return yield* new WorkStoreError({ cause: row, operation: "sql-work.initialize.handoff-lane" });
|
|
817
|
+
}
|
|
818
|
+
const bindingRows = tables.some(({ name }) => name === "work_agent_bindings")
|
|
819
|
+
? yield* sql `
|
|
820
|
+
SELECT binding.dispatch_request_id AS "dispatchRequestId", binding.lane_id AS "laneId",
|
|
821
|
+
binding.expected_revision AS "expectedRevision", binding.revision,
|
|
822
|
+
binding.agent_id AS "agentId", binding.host, binding.record
|
|
823
|
+
FROM work_agent_bindings binding
|
|
824
|
+
JOIN work_dispatch_handoffs dispatch
|
|
825
|
+
ON dispatch.dispatch_request_id = binding.dispatch_request_id
|
|
826
|
+
WHERE dispatch.handoff_id = ${row.handoffId}
|
|
827
|
+
`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(AgentBindingRow))), Effect.mapError(storeError("sql-work.initialize.handoff-binding-row")))
|
|
828
|
+
: [];
|
|
829
|
+
const bindingRow = bindingRows[0];
|
|
830
|
+
const handoffDispatch = bindingRow === undefined
|
|
831
|
+
? undefined
|
|
832
|
+
: handoffDispatches.find(({ dispatchRequestId }) => dispatchRequestId === bindingRow.dispatchRequestId);
|
|
833
|
+
if (bindingRows.length !== 1 || bindingRow === undefined || handoffDispatch === undefined) {
|
|
834
|
+
return yield* new WorkStoreError({
|
|
835
|
+
cause: { bindingRows, row },
|
|
836
|
+
operation: "sql-work.initialize.handoff-revision"
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
const bindingDecision = decodeAgentBindingRow(bindingRow, { dispatchRequestId: handoffDispatch.dispatchRequestId, laneId: previous.value.laneId }, "sql-work.initialize.agent-binding");
|
|
840
|
+
if (bindingDecision._tag === "invalid")
|
|
841
|
+
return yield* bindingDecision.error;
|
|
842
|
+
if (laneRevision.revision < bindingDecision.binding.lane.revision) {
|
|
843
|
+
return yield* new WorkStoreError({
|
|
844
|
+
cause: { binding: bindingDecision.binding, laneRevision, previous: previous.value },
|
|
845
|
+
operation: "sql-work.initialize.handoff-lane-revision"
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
if (bindingDecision.binding.lane.goalId !== previous.value.goalId) {
|
|
849
|
+
return yield* new WorkStoreError({
|
|
850
|
+
cause: { binding: bindingDecision.binding, previous: previous.value },
|
|
851
|
+
operation: "sql-work.initialize.agent-binding.goal"
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
const bindingCompanions = yield* Effect.all({
|
|
855
|
+
checkpointRows: sql `
|
|
856
|
+
SELECT event_id AS "eventId", goal_id AS "goalId", occurred_at AS "occurredAt", record
|
|
857
|
+
FROM work_goal_events WHERE event_id = ${bindingDecision.binding.checkpoint.eventId}
|
|
858
|
+
`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(AgentBindingGoalEventRow))), Effect.mapError(storeError("sql-work.initialize.agent-binding.checkpoint-companion"))),
|
|
859
|
+
laneRows: sql `
|
|
860
|
+
SELECT operation_id AS "operationId", lane_id AS "laneId", goal_id AS "goalId",
|
|
861
|
+
phase, revision, record
|
|
862
|
+
FROM work_lane_operations WHERE operation_id = ${bindingDecision.binding.lane.operationId}
|
|
863
|
+
`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(AgentBindingLaneOperationRow))), Effect.mapError(storeError("sql-work.initialize.agent-binding.lane-companion")))
|
|
864
|
+
});
|
|
865
|
+
const bindingReadbackError = agentBindingReadbackError(bindingDecision.binding, bindingCompanions.laneRows[0], bindingCompanions.checkpointRows[0], "sql-work.initialize.agent-binding");
|
|
866
|
+
if (bindingReadbackError !== undefined)
|
|
867
|
+
return yield* bindingReadbackError;
|
|
868
|
+
yield* requireLifecycleAuthority(bindingDecision.binding.request.dispatchRequestId, bindingDecision.binding.checkpoint.occurredAt, "sql-work.initialize.agent-binding.lifecycle");
|
|
869
|
+
const verifiedDispatches = yield* Effect.forEach(handoffDispatches, (dispatch) => Effect.gen(function* () {
|
|
870
|
+
const decoded = yield* Effect.try({
|
|
871
|
+
try: () => ({
|
|
872
|
+
handoff: Schema.decodeUnknownSync(PreviousWorkDecisionHandoff)(JSON.parse(dispatch.record)),
|
|
873
|
+
lineage: Schema.decodeUnknownSync(WorkDispatchHandoff.fields.lineage)(JSON.parse(dispatch.lineage))
|
|
874
|
+
}),
|
|
875
|
+
catch: storeError("sql-work.initialize.decode-dispatch-authority")
|
|
876
|
+
});
|
|
877
|
+
if (dispatch.laneId !== previous.value.laneId || dispatch.occurredAt !== previous.value.occurredAt ||
|
|
878
|
+
!previousDecisionHandoffEquivalent(decoded.handoff, previous.value) ||
|
|
879
|
+
!workDispatchLineageContainedBy(decoded.lineage, previous.value.dispatchIds)) {
|
|
880
|
+
return yield* new WorkStoreError({
|
|
881
|
+
cause: { decoded, dispatch, previous: previous.value },
|
|
882
|
+
operation: "sql-work.initialize.dispatch-authority"
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
if (tables.some(({ name }) => name === "orchestrator_dispatch_metadata")) {
|
|
886
|
+
const metadata = linkedMetadata.find(({ dispatchRequestId }) => dispatchRequestId === dispatch.dispatchRequestId);
|
|
887
|
+
const encodedWorkLink = metadata?.workLink;
|
|
888
|
+
const encodedRoute = metadata?.route;
|
|
889
|
+
if (encodedWorkLink === null || encodedWorkLink === undefined || encodedRoute === undefined) {
|
|
890
|
+
return yield* new WorkStoreError({
|
|
891
|
+
cause: { dispatch, linkedMetadata },
|
|
892
|
+
operation: "sql-work.initialize.metadata-authority"
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
const workLink = yield* Effect.try({
|
|
896
|
+
try: () => Schema.decodeUnknownSync(PreviousMetadataWorkLink)(JSON.parse(encodedWorkLink)),
|
|
897
|
+
catch: storeError("sql-work.initialize.decode-metadata-authority")
|
|
898
|
+
});
|
|
899
|
+
yield* requireMetadataAuthority(dispatch.dispatchRequestId, encodedRoute, workLink.lineage, "sql-work.initialize.metadata-authority");
|
|
900
|
+
if (!previousDecisionHandoffEquivalent(workLink.handoff, previous.value) ||
|
|
901
|
+
!workDispatchLineageEquivalent(workLink.lineage, decoded.lineage)) {
|
|
902
|
+
return yield* new WorkStoreError({
|
|
903
|
+
cause: { previous: previous.value, workLink },
|
|
904
|
+
operation: "sql-work.initialize.metadata-authority"
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return { dispatch, lineage: decoded.lineage };
|
|
909
|
+
}));
|
|
910
|
+
const migrated = yield* Effect.try({
|
|
911
|
+
try: () => upgradePreviousDecisionHandoff(previous.value, bindingDecision.binding.request.expectedRevision),
|
|
912
|
+
catch: storeError("sql-work.initialize.upgrade-handoff")
|
|
913
|
+
});
|
|
914
|
+
if (migrated.id !== row.handoffId || migrated.sessionId !== row.sessionId ||
|
|
915
|
+
migrated.laneId !== row.laneId || migrated.occurredAt !== row.occurredAt) {
|
|
916
|
+
return yield* new WorkStoreError({
|
|
917
|
+
cause: { migrated, row },
|
|
918
|
+
operation: "sql-work.initialize.handoff-identity"
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
const encoded = JSON.stringify(migrated);
|
|
922
|
+
yield* sql `UPDATE work_decision_handoffs SET record = ${encoded} WHERE handoff_id = ${row.handoffId}`;
|
|
923
|
+
yield* sql `UPDATE work_dispatch_handoffs SET record = ${encoded} WHERE handoff_id = ${row.handoffId}`;
|
|
924
|
+
if (tables.some(({ name }) => name === "orchestrator_dispatch_metadata")) {
|
|
925
|
+
yield* Effect.forEach(verifiedDispatches, ({ dispatch, lineage }) => sql `UPDATE orchestrator_dispatch_metadata
|
|
926
|
+
SET work_link = ${JSON.stringify({ handoff: migrated, lineage })}
|
|
927
|
+
WHERE dispatch_request_id = ${dispatch.dispatchRequestId}`, { discard: true });
|
|
928
|
+
}
|
|
929
|
+
return true;
|
|
930
|
+
}));
|
|
931
|
+
if (legacyDecisions.length > 0 || migrationResults.includes(true)) {
|
|
932
|
+
const migratedLedger = yield* sql `
|
|
933
|
+
SELECT COALESCE(SUM(
|
|
934
|
+
length(CAST(handoff_id AS BLOB)) + length(CAST(record AS BLOB))
|
|
935
|
+
), 0) AS "decisionBytes"
|
|
936
|
+
FROM work_decision_handoffs
|
|
937
|
+
`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(Schema.Struct({ decisionBytes: Schema.Number })))), Effect.mapError(storeError("sql-work.initialize.handoff-capacity")));
|
|
938
|
+
const migratedDecisionBytes = migratedLedger[0];
|
|
939
|
+
if (migratedDecisionBytes === undefined) {
|
|
940
|
+
return yield* new WorkStoreError({
|
|
941
|
+
cause: migratedLedger,
|
|
942
|
+
operation: "sql-work.initialize.missing-handoff-capacity"
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
if (migratedDecisionBytes.decisionBytes > workDecisionMaxBytes) {
|
|
946
|
+
return yield* new WorkStoreError({
|
|
947
|
+
cause: migratedLedger,
|
|
948
|
+
operation: "sql-work.initialize.handoff-capacity"
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
if (metadataTablePresent &&
|
|
953
|
+
coordinatorDispatchColumns.some(({ name }) => name === "is_routed")) {
|
|
954
|
+
const pageSize = 512;
|
|
955
|
+
let cursor = null;
|
|
956
|
+
while (true) {
|
|
957
|
+
const rows = yield* (cursor === null
|
|
958
|
+
? sql `
|
|
959
|
+
SELECT CAST(metadata.rowid AS TEXT) AS "rowId",
|
|
960
|
+
metadata.dispatch_request_id AS "dispatchRequestId",
|
|
961
|
+
dispatch.activity_idempotency_key AS "activityIdempotencyKey",
|
|
962
|
+
dispatch.command, dispatch.is_routed AS "isRouted",
|
|
963
|
+
CASE WHEN metadata.work_link IS NULL THEN 0 ELSE 1 END AS "hasWorkLink",
|
|
964
|
+
metadata.route
|
|
965
|
+
FROM orchestrator_dispatch_metadata AS metadata
|
|
966
|
+
LEFT JOIN orchestrator_dispatches AS dispatch
|
|
967
|
+
ON dispatch.dispatch_request_id = metadata.dispatch_request_id
|
|
968
|
+
ORDER BY metadata.rowid ASC LIMIT ${pageSize}
|
|
969
|
+
`
|
|
970
|
+
: sql `
|
|
971
|
+
SELECT CAST(metadata.rowid AS TEXT) AS "rowId",
|
|
972
|
+
metadata.dispatch_request_id AS "dispatchRequestId",
|
|
973
|
+
dispatch.activity_idempotency_key AS "activityIdempotencyKey",
|
|
974
|
+
dispatch.command, dispatch.is_routed AS "isRouted",
|
|
975
|
+
CASE WHEN metadata.work_link IS NULL THEN 0 ELSE 1 END AS "hasWorkLink",
|
|
976
|
+
metadata.route
|
|
977
|
+
FROM orchestrator_dispatch_metadata AS metadata
|
|
978
|
+
LEFT JOIN orchestrator_dispatches AS dispatch
|
|
979
|
+
ON dispatch.dispatch_request_id = metadata.dispatch_request_id
|
|
980
|
+
WHERE metadata.rowid > CAST(${cursor} AS INTEGER)
|
|
981
|
+
ORDER BY metadata.rowid ASC LIMIT ${pageSize}
|
|
982
|
+
`).pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(CoordinatorMetadataRouteRow))), Effect.mapError(storeError("sql-work.initialize.metadata-route")));
|
|
983
|
+
yield* Effect.forEach(rows, (row) => Effect.gen(function* () {
|
|
984
|
+
const isLegacyUnroutedOrphan = row.activityIdempotencyKey === null && row.command === null &&
|
|
985
|
+
row.isRouted === null && row.route === null && row.hasWorkLink === 0;
|
|
986
|
+
if (isLegacyUnroutedOrphan)
|
|
987
|
+
return;
|
|
988
|
+
if (row.activityIdempotencyKey === null || row.command === null ||
|
|
989
|
+
row.route === null || row.isRouted === null) {
|
|
990
|
+
return yield* new WorkStoreError({
|
|
991
|
+
cause: row,
|
|
992
|
+
operation: "sql-work.initialize.metadata-route"
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
const activityIdempotencyKey = row.activityIdempotencyKey;
|
|
996
|
+
const command = row.command;
|
|
997
|
+
const route = row.route;
|
|
998
|
+
const isRouted = row.isRouted;
|
|
999
|
+
yield* Effect.try({
|
|
1000
|
+
try: () => requireCoordinatorRouteBinding(command, activityIdempotencyKey, route, row.hasWorkLink === 1, { _tag: "routed_discriminator", isRouted }, "sql-work.initialize.metadata-route"),
|
|
1001
|
+
catch: (cause) => Schema.is(WorkStoreError)(cause)
|
|
1002
|
+
? cause
|
|
1003
|
+
: new WorkStoreError({ cause, operation: "sql-work.initialize.metadata-route" })
|
|
1004
|
+
});
|
|
1005
|
+
}), { discard: true });
|
|
1006
|
+
const last = rows.at(-1);
|
|
1007
|
+
if (rows.length < pageSize || last === undefined)
|
|
1008
|
+
break;
|
|
1009
|
+
cursor = last.rowId;
|
|
1010
|
+
}
|
|
1011
|
+
const invalidRoutedMetadataRows = yield* sql `
|
|
1012
|
+
SELECT dispatch.dispatch_request_id AS "dispatchRequestId",
|
|
1013
|
+
COUNT(metadata.dispatch_request_id) AS "metadataCount"
|
|
1014
|
+
FROM orchestrator_dispatches AS dispatch
|
|
1015
|
+
LEFT JOIN orchestrator_dispatch_metadata AS metadata
|
|
1016
|
+
ON metadata.dispatch_request_id = dispatch.dispatch_request_id
|
|
1017
|
+
WHERE dispatch.is_routed = 1
|
|
1018
|
+
GROUP BY dispatch.dispatch_request_id
|
|
1019
|
+
HAVING COUNT(metadata.dispatch_request_id) <> 1
|
|
1020
|
+
LIMIT 1
|
|
1021
|
+
`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(RoutedMetadataCardinalityRow))), Effect.mapError(storeError("sql-work.initialize.routed-metadata")));
|
|
1022
|
+
const invalidRoutedMetadata = invalidRoutedMetadataRows[0];
|
|
1023
|
+
if (invalidRoutedMetadata !== undefined) {
|
|
1024
|
+
return yield* new WorkStoreError({
|
|
1025
|
+
cause: invalidRoutedMetadata,
|
|
1026
|
+
operation: "sql-work.initialize.routed-metadata"
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
yield* sql `CREATE UNIQUE INDEX IF NOT EXISTS work_lane_claims_one_active_goal
|
|
1031
|
+
ON work_lane_claims (goal_id) WHERE phase <> 'shipped'`;
|
|
1032
|
+
yield* sql `
|
|
1033
|
+
CREATE INDEX IF NOT EXISTS work_lane_operations_lane_revision
|
|
1034
|
+
ON work_lane_operations (lane_id, revision)
|
|
1035
|
+
`.pipe(Effect.mapError(storeError("sql-work.initialize.lane-operation-index")));
|
|
1036
|
+
yield* sql `
|
|
1037
|
+
CREATE TRIGGER IF NOT EXISTS work_lane_operations_after_insert
|
|
1038
|
+
AFTER INSERT ON work_lane_operations
|
|
1039
|
+
BEGIN
|
|
1040
|
+
UPDATE work_lane_operation_totals
|
|
1041
|
+
SET operation_count = operation_count + 1,
|
|
1042
|
+
operation_bytes = operation_bytes +
|
|
1043
|
+
length(CAST(NEW.operation_id AS BLOB)) + length(CAST(NEW.record AS BLOB))
|
|
1044
|
+
WHERE singleton = 1;
|
|
1045
|
+
END
|
|
1046
|
+
`.pipe(Effect.mapError(storeError("sql-work.initialize.lane-operation-trigger")));
|
|
1047
|
+
yield* sql `
|
|
1048
|
+
INSERT OR IGNORE INTO work_lane_operation_totals
|
|
1049
|
+
(singleton, operation_count, operation_bytes)
|
|
1050
|
+
SELECT 1, COUNT(*), COALESCE(SUM(
|
|
1051
|
+
length(CAST(operation_id AS BLOB)) + length(CAST(record AS BLOB))
|
|
1052
|
+
), 0)
|
|
1053
|
+
FROM work_lane_operations
|
|
1054
|
+
`.pipe(Effect.mapError(storeError("sql-work.initialize.lane-operation-totals-row")));
|
|
1055
|
+
yield* sql `
|
|
1056
|
+
INSERT OR IGNORE INTO work_lane_operations
|
|
1057
|
+
(operation_id, lane_id, goal_id, phase, revision, record)
|
|
1058
|
+
SELECT operation_id, lane_id, goal_id, phase, revision, record
|
|
1059
|
+
FROM work_lane_claims
|
|
1060
|
+
`.pipe(Effect.mapError(storeError("sql-work.initialize.lane-operation-backfill")));
|
|
1061
|
+
yield* sql `
|
|
1062
|
+
CREATE TRIGGER IF NOT EXISTS work_decision_handoffs_after_insert
|
|
1063
|
+
AFTER INSERT ON work_decision_handoffs
|
|
1064
|
+
BEGIN
|
|
1065
|
+
UPDATE work_decision_totals
|
|
1066
|
+
SET decision_count = decision_count + 1,
|
|
1067
|
+
decision_bytes = decision_bytes +
|
|
1068
|
+
length(CAST(NEW.handoff_id AS BLOB)) + length(CAST(NEW.record AS BLOB))
|
|
1069
|
+
WHERE singleton = 1;
|
|
1070
|
+
END
|
|
1071
|
+
`.pipe(Effect.mapError(storeError("sql-work.initialize.trigger")));
|
|
1072
|
+
yield* sql `
|
|
1073
|
+
CREATE INDEX IF NOT EXISTS work_dispatch_handoffs_lane
|
|
1074
|
+
ON work_dispatch_handoffs (lane_id, occurred_at, dispatch_request_id)
|
|
1075
|
+
`.pipe(Effect.mapError(storeError("sql-work.initialize.dispatch-index")));
|
|
1076
|
+
yield* sql `
|
|
1077
|
+
CREATE INDEX IF NOT EXISTS work_agent_bindings_lane_revision
|
|
1078
|
+
ON work_agent_bindings (lane_id, revision)
|
|
1079
|
+
`.pipe(Effect.mapError(storeError("sql-work.initialize.agent-binding-index")));
|
|
1080
|
+
yield* sql `
|
|
1081
|
+
CREATE UNIQUE INDEX IF NOT EXISTS work_decision_handoffs_session
|
|
1082
|
+
ON work_decision_handoffs (session_id)
|
|
1083
|
+
`.pipe(Effect.mapError(storeError("sql-work.initialize.session-index")));
|
|
1084
|
+
yield* sql `
|
|
1085
|
+
INSERT OR REPLACE INTO work_decision_totals
|
|
1086
|
+
(singleton, decision_count, decision_bytes)
|
|
1087
|
+
SELECT 1, COUNT(*), COALESCE(SUM(
|
|
1088
|
+
length(CAST(handoff_id AS BLOB)) + length(CAST(record AS BLOB))
|
|
1089
|
+
), 0)
|
|
1090
|
+
FROM work_decision_handoffs
|
|
1091
|
+
`.pipe(Effect.mapError(storeError("sql-work.initialize.totals-row")));
|
|
1092
|
+
}));
|
|
1093
|
+
});
|
|
1094
|
+
const readDecision = (handoff) => sql `
|
|
1095
|
+
SELECT handoff_id AS "handoffId", session_id AS "sessionId", lane_id AS "laneId",
|
|
1096
|
+
occurred_at AS "occurredAt", record
|
|
1097
|
+
FROM work_decision_handoffs WHERE handoff_id = ${handoff.id}
|
|
1098
|
+
`.pipe(Effect.mapError(storeError("sql-work.read-handoff")), Effect.flatMap((rows) => Schema.decodeUnknownEffect(Schema.Array(DecisionRow))(rows).pipe(Effect.mapError(storeError("sql-work.decode-handoff-row")), Effect.flatMap((decoded) => {
|
|
1099
|
+
const row = decoded[0];
|
|
1100
|
+
return row === undefined
|
|
1101
|
+
? Effect.succeed(null)
|
|
1102
|
+
: decodeHandoff(row).pipe(Effect.map((value) => ({ row, value })));
|
|
1103
|
+
}))));
|
|
1104
|
+
const readDecisionBySession = (handoff) => sql `
|
|
1105
|
+
SELECT handoff_id AS "handoffId", session_id AS "sessionId", lane_id AS "laneId",
|
|
1106
|
+
occurred_at AS "occurredAt", record
|
|
1107
|
+
FROM work_decision_handoffs WHERE session_id = ${handoff.sessionId}
|
|
1108
|
+
`.pipe(Effect.mapError(storeError("sql-work.read-session-handoff")), Effect.flatMap((rows) => Schema.decodeUnknownEffect(Schema.Array(DecisionRow))(rows).pipe(Effect.mapError(storeError("sql-work.decode-session-handoff-row")), Effect.flatMap((decoded) => {
|
|
1109
|
+
const row = decoded[0];
|
|
1110
|
+
return row === undefined
|
|
1111
|
+
? Effect.succeed(null)
|
|
1112
|
+
: decodeHandoff(row).pipe(Effect.map((value) => ({ row, value })));
|
|
1113
|
+
}))));
|
|
1114
|
+
const readBinding = (dispatchRequestId) => sql `
|
|
1115
|
+
SELECT dispatch_request_id AS "dispatchRequestId", handoff_id AS "handoffId",
|
|
1116
|
+
lane_id AS "laneId", occurred_at AS "occurredAt", lineage, record
|
|
1117
|
+
FROM work_dispatch_handoffs WHERE dispatch_request_id = ${dispatchRequestId}
|
|
1118
|
+
`.pipe(Effect.mapError(storeError("sql-work.read-dispatch-handoff")), Effect.flatMap((rows) => Schema.decodeUnknownEffect(Schema.Array(DispatchHandoffRow))(rows).pipe(Effect.mapError(storeError("sql-work.decode-dispatch-handoff-row")), Effect.flatMap((decoded) => {
|
|
1119
|
+
const row = decoded[0];
|
|
1120
|
+
return row === undefined
|
|
1121
|
+
? Effect.succeed(null)
|
|
1122
|
+
: decodeDispatchHandoff(row).pipe(Effect.map((value) => ({ row, value })));
|
|
1123
|
+
}))));
|
|
1124
|
+
const readAgentBinding = (dispatchRequestId) => sql `
|
|
1125
|
+
SELECT dispatch_request_id AS "dispatchRequestId", lane_id AS "laneId",
|
|
1126
|
+
expected_revision AS "expectedRevision", revision, agent_id AS "agentId", host, record
|
|
1127
|
+
FROM work_agent_bindings WHERE dispatch_request_id = ${dispatchRequestId}
|
|
1128
|
+
`.pipe(Effect.mapError(storeError("sql-work.read-agent-binding")), Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(AgentBindingRow))), Effect.mapError(storeError("sql-work.decode-agent-binding-row")), Effect.flatMap((rows) => {
|
|
1129
|
+
const row = rows[0];
|
|
1130
|
+
if (row === undefined)
|
|
1131
|
+
return Effect.succeed(null);
|
|
1132
|
+
return Effect.try({
|
|
1133
|
+
try: () => Schema.decodeUnknownSync(WorkAgentBinding)(JSON.parse(row.record)),
|
|
1134
|
+
catch: storeError("sql-work.decode-agent-binding-record")
|
|
1135
|
+
}).pipe(Effect.flatMap((binding) => {
|
|
1136
|
+
if (row.dispatchRequestId !== binding.request.dispatchRequestId ||
|
|
1137
|
+
row.laneId !== binding.request.laneId ||
|
|
1138
|
+
row.expectedRevision !== binding.request.expectedRevision ||
|
|
1139
|
+
row.revision !== binding.lane.revision ||
|
|
1140
|
+
row.agentId !== binding.request.worker.agentId ||
|
|
1141
|
+
row.host.toLowerCase() !== binding.request.worker.host.toLowerCase()) {
|
|
1142
|
+
return Effect.fail(new WorkStoreError({
|
|
1143
|
+
cause: { binding, row },
|
|
1144
|
+
operation: "sql-work.agent-binding.identity-mismatch"
|
|
1145
|
+
}));
|
|
1146
|
+
}
|
|
1147
|
+
return Effect.all({
|
|
1148
|
+
checkpointRows: sql `
|
|
1149
|
+
SELECT event_id AS "eventId", goal_id AS "goalId", occurred_at AS "occurredAt", record
|
|
1150
|
+
FROM work_goal_events WHERE event_id = ${binding.checkpoint.eventId}
|
|
1151
|
+
`.pipe(Effect.mapError(storeError("sql-work.agent-binding.read-checkpoint-companion")), Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(AgentBindingGoalEventRow))), Effect.mapError(storeError("sql-work.agent-binding.decode-checkpoint-companion"))),
|
|
1152
|
+
laneRows: sql `
|
|
1153
|
+
SELECT operation_id AS "operationId", lane_id AS "laneId", goal_id AS "goalId",
|
|
1154
|
+
phase, revision, record
|
|
1155
|
+
FROM work_lane_operations WHERE operation_id = ${binding.lane.operationId}
|
|
1156
|
+
`.pipe(Effect.mapError(storeError("sql-work.agent-binding.read-lane-companion")), Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(AgentBindingLaneOperationRow))), Effect.mapError(storeError("sql-work.agent-binding.decode-lane-companion")))
|
|
1157
|
+
}).pipe(Effect.flatMap(({ checkpointRows, laneRows }) => {
|
|
1158
|
+
const error = agentBindingReadbackError(binding, laneRows[0], checkpointRows[0], "sql-work.agent-binding.readback");
|
|
1159
|
+
return error === undefined ? Effect.succeed(binding) : Effect.fail(error);
|
|
1160
|
+
}));
|
|
1161
|
+
}));
|
|
1162
|
+
}));
|
|
1163
|
+
const conflict = (binding) => new WorkDispatchHandoffConflictError({
|
|
1164
|
+
dispatchRequestId: binding.dispatchRequestId,
|
|
1165
|
+
handoffId: binding.handoff.id
|
|
1166
|
+
});
|
|
1167
|
+
const readValidatedLaneClaims = Effect.fn("SqliteWorkBridge.readValidatedLaneClaims")(function* () {
|
|
1168
|
+
const rows = yield* sql `
|
|
1169
|
+
SELECT lane_id AS "laneId", goal_id AS "goalId", operation_id AS "operationId",
|
|
1170
|
+
phase, revision, record
|
|
1171
|
+
FROM work_lane_claims ORDER BY lane_id ASC LIMIT ${workSnapshotMaxGoals + 1}
|
|
1172
|
+
`.pipe(Effect.mapError(storeError("sql-work.read-lane-authority")), Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(LaneRow))), Effect.mapError(storeError("sql-work.decode-lane-authority-rows")));
|
|
1173
|
+
if (rows.length > workSnapshotMaxGoals) {
|
|
1174
|
+
return yield* new WorkStoreError({ cause: rows.length, operation: "sql-work.lane-authority.capacity" });
|
|
1175
|
+
}
|
|
1176
|
+
const claims = yield* Effect.forEach(rows, (row) => Effect.try({
|
|
1177
|
+
try: () => Schema.decodeUnknownSync(WorkLaneClaimed)(JSON.parse(row.record)),
|
|
1178
|
+
catch: storeError("sql-work.decode-lane-authority-record")
|
|
1179
|
+
}).pipe(Effect.flatMap((claim) => claim.goalId !== row.goalId ||
|
|
1180
|
+
claim.laneId !== row.laneId ||
|
|
1181
|
+
claim.operationId !== row.operationId ||
|
|
1182
|
+
claim.phase !== row.phase ||
|
|
1183
|
+
claim.revision !== row.revision
|
|
1184
|
+
? Effect.fail(new WorkStoreError({
|
|
1185
|
+
cause: { claim, row },
|
|
1186
|
+
operation: "sql-work.lane-authority.identity-mismatch"
|
|
1187
|
+
}))
|
|
1188
|
+
: Effect.succeed(claim))));
|
|
1189
|
+
return claims;
|
|
1190
|
+
});
|
|
1191
|
+
const requireActiveAuthority = Effect.fn("SqliteWorkBridge.requireActiveAuthority")(function* (handoff) {
|
|
1192
|
+
const claims = yield* readValidatedLaneClaims();
|
|
1193
|
+
const activeGoalClaims = claims.filter((claim) => claim.goalId === handoff.goalId && claim.phase !== "shipped");
|
|
1194
|
+
const activeClaim = activeGoalClaims[0];
|
|
1195
|
+
if (activeGoalClaims.length !== 1 || activeClaim?.laneId !== handoff.laneId) {
|
|
1196
|
+
return yield* new WorkDecisionAuthorityConflictError({ goalId: handoff.goalId, laneId: handoff.laneId });
|
|
1197
|
+
}
|
|
1198
|
+
if (activeClaim.revision !== handoff.expectedRevision) {
|
|
1199
|
+
return yield* new WorkDecisionRevisionConflictError({
|
|
1200
|
+
actualRevision: activeClaim.revision,
|
|
1201
|
+
expectedRevision: handoff.expectedRevision,
|
|
1202
|
+
laneId: handoff.laneId
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
});
|
|
1206
|
+
const acceptAgentBinding = Effect.fn("SqliteWorkBridge.acceptAgentBinding")(function* (request, boundaryTime) {
|
|
1207
|
+
const decoded = yield* Schema.decodeUnknownEffect(WorkAgentBindingRequest)(request).pipe(Effect.mapError(storeError("sql-work.agent-binding.decode")));
|
|
1208
|
+
const decodedBoundaryTime = yield* Schema.decodeUnknownEffect(WorkGoalCheckpoint.fields.occurredAt)(boundaryTime).pipe(Effect.mapError(storeError("sql-work.agent-binding.decode-time")));
|
|
1209
|
+
const existing = yield* readAgentBinding(decoded.dispatchRequestId);
|
|
1210
|
+
if (existing !== null) {
|
|
1211
|
+
return Equal.equals(existing.request, decoded)
|
|
1212
|
+
? { _tag: "replayed", binding: existing }
|
|
1213
|
+
: yield* new WorkAgentBindingConflictError({ dispatchRequestId: decoded.dispatchRequestId });
|
|
1214
|
+
}
|
|
1215
|
+
const laneClaims = yield* readValidatedLaneClaims();
|
|
1216
|
+
const lane = laneClaims.find(({ laneId }) => laneId === decoded.laneId);
|
|
1217
|
+
if (lane === undefined) {
|
|
1218
|
+
return yield* new WorkAgentBindingAuthorityError({
|
|
1219
|
+
actualRevision: 0,
|
|
1220
|
+
expectedRevision: decoded.expectedRevision,
|
|
1221
|
+
laneId: decoded.laneId,
|
|
1222
|
+
reason: "missing_lane"
|
|
1223
|
+
});
|
|
1224
|
+
}
|
|
1225
|
+
if (lane.revision !== decoded.expectedRevision) {
|
|
1226
|
+
return yield* new WorkAgentBindingAuthorityError({
|
|
1227
|
+
actualRevision: lane.revision,
|
|
1228
|
+
expectedRevision: decoded.expectedRevision,
|
|
1229
|
+
laneId: decoded.laneId,
|
|
1230
|
+
reason: "stale_revision"
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
if (lane.phase === "shipped") {
|
|
1234
|
+
return yield* new WorkAgentBindingAuthorityError({
|
|
1235
|
+
actualRevision: lane.revision,
|
|
1236
|
+
expectedRevision: decoded.expectedRevision,
|
|
1237
|
+
laneId: decoded.laneId,
|
|
1238
|
+
reason: "shipped_lane"
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
const activeGoalClaims = laneClaims.filter((claim) => claim.goalId === lane.goalId && claim.phase !== "shipped");
|
|
1242
|
+
if (activeGoalClaims.length !== 1 || activeGoalClaims[0]?.laneId !== lane.laneId) {
|
|
1243
|
+
return yield* new WorkStoreError({
|
|
1244
|
+
cause: { activeGoalClaims, lane },
|
|
1245
|
+
operation: "sql-work.agent-binding.goal-authority-conflict"
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
const currentRows = yield* sql `
|
|
1249
|
+
SELECT event_id AS "eventId", goal_id AS "goalId", occurred_at AS "occurredAt", record
|
|
1250
|
+
FROM work_goal_events
|
|
1251
|
+
WHERE goal_id = ${lane.goalId}
|
|
1252
|
+
ORDER BY occurred_at DESC, event_id DESC LIMIT 1
|
|
1253
|
+
`.pipe(Effect.mapError(storeError("sql-work.agent-binding.read-goal")), Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(AgentBindingGoalEventRow))), Effect.mapError(storeError("sql-work.agent-binding.decode-goal-row")));
|
|
1254
|
+
const currentRow = currentRows[0];
|
|
1255
|
+
if (currentRow === undefined) {
|
|
1256
|
+
return yield* new WorkAgentBindingAuthorityError({
|
|
1257
|
+
actualRevision: lane.revision,
|
|
1258
|
+
expectedRevision: decoded.expectedRevision,
|
|
1259
|
+
laneId: decoded.laneId,
|
|
1260
|
+
reason: "missing_goal"
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
const currentDecision = decodeAgentBindingGoalEvent(currentRow, "sql-work.agent-binding.current-goal");
|
|
1264
|
+
if (currentDecision._tag === "invalid")
|
|
1265
|
+
return yield* currentDecision.error;
|
|
1266
|
+
const current = currentDecision.checkpoint;
|
|
1267
|
+
if (current.goal.state === "completed") {
|
|
1268
|
+
return yield* new WorkAgentBindingAuthorityError({
|
|
1269
|
+
actualRevision: lane.revision,
|
|
1270
|
+
expectedRevision: decoded.expectedRevision,
|
|
1271
|
+
laneId: decoded.laneId,
|
|
1272
|
+
reason: "terminal_goal"
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
if (current.occurredAt > decodedBoundaryTime) {
|
|
1276
|
+
return yield* new WorkProjectionError({
|
|
1277
|
+
cause: { boundaryTime: decodedBoundaryTime, checkpoint: current },
|
|
1278
|
+
detail: "work agent binding cannot advance beyond the coordinator clock",
|
|
1279
|
+
reason: "inconsistent_history"
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
if (current.occurredAt >= 8_640_000_000_000_000) {
|
|
1283
|
+
return yield* new WorkProjectionError({
|
|
1284
|
+
cause: decoded,
|
|
1285
|
+
detail: "work agent binding timestamp cannot advance",
|
|
1286
|
+
reason: "capacity_exceeded"
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
const occurredAt = Math.max(decodedBoundaryTime, current.occurredAt + 1);
|
|
1290
|
+
const binding = makeWorkAgentBinding(decoded, lane, current, occurredAt);
|
|
1291
|
+
const encodedLane = JSON.stringify(binding.lane);
|
|
1292
|
+
const history = yield* sql `
|
|
1293
|
+
SELECT event_id AS "eventId", goal_id AS "goalId", occurred_at AS "occurredAt", record
|
|
1294
|
+
FROM work_goal_events ORDER BY occurred_at ASC, event_id ASC
|
|
1295
|
+
LIMIT ${workHistoryMaxEvents + 1}
|
|
1296
|
+
`.pipe(Effect.mapError(storeError("sql-work.agent-binding.read-history")), Effect.flatMap((rows) => Schema.decodeUnknownEffect(Schema.Array(AgentBindingGoalEventRow))(rows).pipe(Effect.mapError(storeError("sql-work.agent-binding.decode-history-rows")))), Effect.flatMap((rows) => Effect.forEach(rows, (row) => {
|
|
1297
|
+
const decision = decodeAgentBindingGoalEvent(row, "sql-work.agent-binding.history");
|
|
1298
|
+
return decision._tag === "valid" ? Effect.succeed(decision.checkpoint) : Effect.fail(decision.error);
|
|
1299
|
+
})));
|
|
1300
|
+
const operationTotalsRows = yield* sql `
|
|
1301
|
+
SELECT operation_count AS "operationCount", operation_bytes AS "operationBytes"
|
|
1302
|
+
FROM work_lane_operation_totals WHERE singleton = 1
|
|
1303
|
+
`.pipe(Effect.mapError(storeError("sql-work.agent-binding.read-operation-totals")), Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(LaneOperationLedgerTotalsRow))), Effect.mapError(storeError("sql-work.agent-binding.decode-operation-totals")));
|
|
1304
|
+
const operationTotals = operationTotalsRows[0];
|
|
1305
|
+
if (operationTotals === undefined) {
|
|
1306
|
+
return yield* new WorkStoreError({
|
|
1307
|
+
cause: operationTotalsRows,
|
|
1308
|
+
operation: "sql-work.agent-binding.missing-operation-totals"
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
const admissionError = agentBindingAdmissionError({
|
|
1312
|
+
candidate: binding.checkpoint,
|
|
1313
|
+
candidateOperationBytes: encodedBytes(binding.lane.operationId) + encodedBytes(encodedLane),
|
|
1314
|
+
history,
|
|
1315
|
+
operationBytes: operationTotals.operationBytes,
|
|
1316
|
+
operationCount: operationTotals.operationCount
|
|
1317
|
+
});
|
|
1318
|
+
if (admissionError !== undefined)
|
|
1319
|
+
return yield* admissionError;
|
|
1320
|
+
const updated = yield* sql `
|
|
1321
|
+
UPDATE work_lane_claims
|
|
1322
|
+
SET operation_id = ${binding.lane.operationId}, revision = ${binding.lane.revision}, record = ${encodedLane}
|
|
1323
|
+
WHERE lane_id = ${decoded.laneId} AND revision = ${decoded.expectedRevision}
|
|
1324
|
+
RETURNING lane_id
|
|
1325
|
+
`.pipe(Effect.mapError(storeError("sql-work.agent-binding.update-lane")));
|
|
1326
|
+
if (updated.length !== 1) {
|
|
1327
|
+
return yield* new WorkAgentBindingAuthorityError({
|
|
1328
|
+
actualRevision: lane.revision,
|
|
1329
|
+
expectedRevision: decoded.expectedRevision,
|
|
1330
|
+
laneId: decoded.laneId,
|
|
1331
|
+
reason: "stale_revision"
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
yield* sql `
|
|
1335
|
+
INSERT INTO work_lane_operations (operation_id, lane_id, goal_id, phase, revision, record)
|
|
1336
|
+
VALUES (${binding.lane.operationId}, ${binding.lane.laneId}, ${binding.lane.goalId},
|
|
1337
|
+
${binding.lane.phase}, ${binding.lane.revision}, ${encodedLane})
|
|
1338
|
+
`.pipe(Effect.mapError(storeError("sql-work.agent-binding.insert-lane-operation")));
|
|
1339
|
+
yield* sql `
|
|
1340
|
+
INSERT INTO work_goal_events (event_id, goal_id, occurred_at, record)
|
|
1341
|
+
VALUES (${binding.checkpoint.eventId}, ${binding.checkpoint.goal.id},
|
|
1342
|
+
${binding.checkpoint.occurredAt}, ${JSON.stringify(binding.checkpoint)})
|
|
1343
|
+
`.pipe(Effect.mapError(storeError("sql-work.agent-binding.insert-goal-event")));
|
|
1344
|
+
yield* sql `
|
|
1345
|
+
INSERT INTO work_agent_bindings
|
|
1346
|
+
(dispatch_request_id, lane_id, expected_revision, revision, agent_id, host, record)
|
|
1347
|
+
VALUES (${decoded.dispatchRequestId}, ${decoded.laneId}, ${decoded.expectedRevision},
|
|
1348
|
+
${binding.lane.revision}, ${decoded.worker.agentId}, ${decoded.worker.host}, ${JSON.stringify(binding)})
|
|
1349
|
+
`.pipe(Effect.mapError(storeError("sql-work.agent-binding.insert")));
|
|
1350
|
+
return { _tag: "inserted", binding };
|
|
1351
|
+
});
|
|
1352
|
+
const requireAgentBinding = Effect.fn("SqliteWorkBridge.requireAgentBinding")(function* (request) {
|
|
1353
|
+
const decoded = yield* Schema.decodeUnknownEffect(WorkAgentBindingRequest)(request).pipe(Effect.mapError(storeError("sql-work.agent-binding.require.decode")));
|
|
1354
|
+
const existing = yield* readAgentBinding(decoded.dispatchRequestId);
|
|
1355
|
+
if (existing === null) {
|
|
1356
|
+
return yield* new WorkStoreError({ cause: decoded, operation: "sql-work.agent-binding.require.missing" });
|
|
1357
|
+
}
|
|
1358
|
+
return Equal.equals(existing.request, decoded)
|
|
1359
|
+
? existing
|
|
1360
|
+
: yield* new WorkAgentBindingConflictError({ dispatchRequestId: decoded.dispatchRequestId });
|
|
1361
|
+
});
|
|
1362
|
+
const acceptDispatchHandoff = Effect.fn("SqliteWorkBridge.acceptDispatchHandoff")(function* (binding) {
|
|
1363
|
+
const decoded = yield* Schema.decodeUnknownEffect(WorkDispatchHandoff)(binding).pipe(Effect.mapError(storeError("sql-work.accept.decode")));
|
|
1364
|
+
const existingBinding = yield* readBinding(decoded.dispatchRequestId);
|
|
1365
|
+
if (existingBinding !== null) {
|
|
1366
|
+
if (!Equal.equals(existingBinding.value, decoded))
|
|
1367
|
+
return yield* conflict(decoded);
|
|
1368
|
+
const existingDecision = yield* readDecision(decoded.handoff);
|
|
1369
|
+
return existingDecision !== null && Equal.equals(existingDecision.value, decoded.handoff)
|
|
1370
|
+
? existingBinding.value
|
|
1371
|
+
: yield* new WorkStoreError({ cause: decoded, operation: "sql-work.accept.decision-mismatch" });
|
|
1372
|
+
}
|
|
1373
|
+
yield* requireActiveAuthority(decoded.handoff);
|
|
1374
|
+
const existingSessionHandoff = yield* readDecisionBySession(decoded.handoff);
|
|
1375
|
+
if (existingSessionHandoff !== null && !Equal.equals(existingSessionHandoff.value, decoded.handoff)) {
|
|
1376
|
+
return yield* new WorkCoordinatorHandoffConflictError({ sessionId: decoded.handoff.sessionId });
|
|
1377
|
+
}
|
|
1378
|
+
const existingHandoff = yield* readDecision(decoded.handoff);
|
|
1379
|
+
if (existingHandoff !== null && !Equal.equals(existingHandoff.value, decoded.handoff)) {
|
|
1380
|
+
return yield* new WorkDecisionHandoffConflictError({ handoffId: decoded.handoff.id });
|
|
1381
|
+
}
|
|
1382
|
+
if (existingHandoff === null) {
|
|
1383
|
+
const encodedHandoff = JSON.stringify(decoded.handoff);
|
|
1384
|
+
const totals = yield* sql `
|
|
1385
|
+
SELECT decision_count AS "decisionCount", decision_bytes AS "decisionBytes"
|
|
1386
|
+
FROM work_decision_totals WHERE singleton = 1
|
|
1387
|
+
`.pipe(Effect.mapError(storeError("sql-work.read-totals")), Effect.flatMap((rows) => Schema.decodeUnknownEffect(Schema.Array(DecisionLedgerTotalsRow))(rows).pipe(Effect.mapError(storeError("sql-work.decode-totals")), Effect.flatMap((decodedRows) => {
|
|
1388
|
+
const row = decodedRows[0];
|
|
1389
|
+
return row === undefined
|
|
1390
|
+
? Effect.fail(new WorkStoreError({ cause: decodedRows, operation: "sql-work.missing-totals" }))
|
|
1391
|
+
: Effect.succeed(row);
|
|
1392
|
+
}))));
|
|
1393
|
+
const handoffBytes = encodedBytes(decoded.handoff.id) + encodedBytes(encodedHandoff);
|
|
1394
|
+
if (totals.decisionCount >= workDecisionMaxRecords) {
|
|
1395
|
+
return yield* new WorkProjectionError({
|
|
1396
|
+
cause: decoded,
|
|
1397
|
+
detail: `work decision history cannot exceed ${workDecisionMaxRecords} handoffs`,
|
|
1398
|
+
reason: "capacity_exceeded"
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
if (totals.decisionBytes + handoffBytes > workDecisionMaxBytes) {
|
|
1402
|
+
return yield* new WorkProjectionError({
|
|
1403
|
+
cause: decoded,
|
|
1404
|
+
detail: `work decision history cannot exceed ${workDecisionMaxBytes} encoded bytes`,
|
|
1405
|
+
reason: "capacity_exceeded"
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
yield* sql `
|
|
1409
|
+
INSERT INTO work_decision_handoffs (handoff_id, session_id, lane_id, occurred_at, record)
|
|
1410
|
+
VALUES (${decoded.handoff.id}, ${decoded.handoff.sessionId}, ${decoded.handoff.laneId},
|
|
1411
|
+
${decoded.handoff.occurredAt}, ${encodedHandoff})
|
|
1412
|
+
`.pipe(Effect.mapError(storeError("sql-work.insert-handoff")));
|
|
1413
|
+
}
|
|
1414
|
+
const encodedLineage = JSON.stringify(decoded.lineage);
|
|
1415
|
+
const encodedRecord = JSON.stringify(decoded.handoff);
|
|
1416
|
+
const inserted = yield* sql `
|
|
1417
|
+
INSERT INTO work_dispatch_handoffs
|
|
1418
|
+
(dispatch_request_id, handoff_id, lane_id, occurred_at, lineage, record)
|
|
1419
|
+
VALUES (${decoded.dispatchRequestId}, ${decoded.handoff.id}, ${decoded.handoff.laneId},
|
|
1420
|
+
${decoded.handoff.occurredAt}, ${encodedLineage}, ${encodedRecord})
|
|
1421
|
+
ON CONFLICT DO NOTHING
|
|
1422
|
+
RETURNING dispatch_request_id AS "dispatchRequestId", handoff_id AS "handoffId",
|
|
1423
|
+
lane_id AS "laneId", occurred_at AS "occurredAt", lineage, record
|
|
1424
|
+
`.pipe(Effect.mapError(storeError("sql-work.insert-dispatch-handoff")));
|
|
1425
|
+
if (inserted.length > 0)
|
|
1426
|
+
return decoded;
|
|
1427
|
+
const winner = yield* readBinding(decoded.dispatchRequestId);
|
|
1428
|
+
return winner !== null && Equal.equals(winner.value, decoded)
|
|
1429
|
+
? winner.value
|
|
1430
|
+
: yield* conflict(decoded);
|
|
1431
|
+
});
|
|
1432
|
+
const requireDispatchHandoff = Effect.fn("SqliteWorkBridge.requireDispatchHandoff")(function* (binding) {
|
|
1433
|
+
const decoded = yield* Schema.decodeUnknownEffect(WorkDispatchHandoff)(binding).pipe(Effect.mapError(storeError("sql-work.require.decode")));
|
|
1434
|
+
const existing = yield* readBinding(decoded.dispatchRequestId);
|
|
1435
|
+
if (existing === null) {
|
|
1436
|
+
return yield* new WorkStoreError({ cause: decoded, operation: "sql-work.require.missing" });
|
|
1437
|
+
}
|
|
1438
|
+
if (!Equal.equals(existing.value, decoded))
|
|
1439
|
+
return yield* conflict(decoded);
|
|
1440
|
+
const decision = yield* readDecision(decoded.handoff);
|
|
1441
|
+
if (decision === null || !Equal.equals(decision.value, decoded.handoff)) {
|
|
1442
|
+
return yield* new WorkStoreError({ cause: decoded, operation: "sql-work.require.decision-mismatch" });
|
|
1443
|
+
}
|
|
1444
|
+
return existing.value;
|
|
1445
|
+
});
|
|
1446
|
+
return {
|
|
1447
|
+
acceptAgentBinding,
|
|
1448
|
+
acceptDispatchHandoff,
|
|
1449
|
+
initialize,
|
|
1450
|
+
requireAgentBinding,
|
|
1451
|
+
requireDispatchHandoff
|
|
1452
|
+
};
|
|
1453
|
+
};
|
|
1454
|
+
//# sourceMappingURL=sql.js.map
|