videobook-engine 2.0.1 → 4.2.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 +87 -41
- package/dist/artifact-names.d.ts +6 -0
- package/dist/artifact-names.d.ts.map +1 -0
- package/dist/artifact-names.js +56 -0
- package/dist/artifact-names.js.map +1 -0
- package/dist/artifacts.d.ts.map +1 -1
- package/dist/artifacts.js +57 -23
- package/dist/artifacts.js.map +1 -1
- package/dist/books.js +2 -1
- package/dist/books.js.map +1 -1
- package/dist/cas.d.ts +27 -1
- package/dist/cas.d.ts.map +1 -1
- package/dist/cas.js +78 -6
- package/dist/cas.js.map +1 -1
- package/dist/catalog-metadata.d.ts +56 -0
- package/dist/catalog-metadata.d.ts.map +1 -0
- package/dist/catalog-metadata.js +42 -0
- package/dist/catalog-metadata.js.map +1 -0
- package/dist/communications.js +4 -2
- package/dist/communications.js.map +1 -1
- package/dist/context.d.ts +6 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +35 -6
- package/dist/context.js.map +1 -1
- package/dist/domain.d.ts +4 -0
- package/dist/domain.d.ts.map +1 -1
- package/dist/domain.js +870 -58
- package/dist/domain.js.map +1 -1
- package/dist/edit-policy.d.ts +3 -0
- package/dist/edit-policy.d.ts.map +1 -0
- package/dist/edit-policy.js +6 -0
- package/dist/edit-policy.js.map +1 -0
- package/dist/edits.d.ts +10 -0
- package/dist/edits.d.ts.map +1 -0
- package/dist/edits.js +1392 -0
- package/dist/edits.js.map +1 -0
- package/dist/engine-types.d.ts +110 -60
- package/dist/engine-types.d.ts.map +1 -1
- package/dist/engine-types.js +33 -0
- package/dist/engine-types.js.map +1 -1
- package/dist/engine.d.ts +69 -8
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +24 -18
- package/dist/engine.js.map +1 -1
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +31 -27
- package/dist/files.js.map +1 -1
- package/dist/fork.d.ts +147 -0
- package/dist/fork.d.ts.map +1 -0
- package/dist/fork.js +609 -0
- package/dist/fork.js.map +1 -0
- package/dist/history.d.ts +0 -4
- package/dist/history.d.ts.map +1 -1
- package/dist/history.js +166 -595
- package/dist/history.js.map +1 -1
- package/dist/index.d.ts +16 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/dist/job-queue.d.ts +2 -0
- package/dist/job-queue.d.ts.map +1 -1
- package/dist/job-queue.js +29 -2
- package/dist/job-queue.js.map +1 -1
- package/dist/media.d.ts.map +1 -1
- package/dist/media.js +1 -0
- package/dist/media.js.map +1 -1
- package/dist/merge-policy.d.ts +99 -0
- package/dist/merge-policy.d.ts.map +1 -0
- package/dist/merge-policy.js +310 -0
- package/dist/merge-policy.js.map +1 -0
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +15 -20
- package/dist/metadata.js.map +1 -1
- package/dist/migration.d.ts +9 -0
- package/dist/migration.d.ts.map +1 -0
- package/dist/migration.js +534 -0
- package/dist/migration.js.map +1 -0
- package/dist/mvp-contract-fixtures.d.ts +244 -0
- package/dist/mvp-contract-fixtures.d.ts.map +1 -0
- package/dist/mvp-contract-fixtures.js +556 -0
- package/dist/mvp-contract-fixtures.js.map +1 -0
- package/dist/mvp-contracts.d.ts +761 -0
- package/dist/mvp-contracts.d.ts.map +1 -0
- package/dist/mvp-contracts.js +59 -0
- package/dist/mvp-contracts.js.map +1 -0
- package/dist/mvp-time.d.ts +54 -0
- package/dist/mvp-time.d.ts.map +1 -0
- package/dist/mvp-time.js +148 -0
- package/dist/mvp-time.js.map +1 -0
- package/dist/notebook/types.d.ts +113 -8
- package/dist/notebook/types.d.ts.map +1 -1
- package/dist/order-keys.d.ts +22 -0
- package/dist/order-keys.d.ts.map +1 -0
- package/dist/order-keys.js +262 -0
- package/dist/order-keys.js.map +1 -0
- package/dist/runtime-services.js +4 -2
- package/dist/runtime-services.js.map +1 -1
- package/dist/schema.d.ts +24 -5
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +644 -132
- package/dist/schema.js.map +1 -1
- package/dist/search-benchmark.d.ts +4 -0
- package/dist/search-benchmark.d.ts.map +1 -0
- package/dist/search-benchmark.js +144 -0
- package/dist/search-benchmark.js.map +1 -0
- package/dist/sequences.d.ts +20 -0
- package/dist/sequences.d.ts.map +1 -0
- package/dist/sequences.js +825 -0
- package/dist/sequences.js.map +1 -0
- package/dist/similarity.js +1 -1
- package/dist/storage.d.ts +3 -1
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +388 -7
- package/dist/storage.js.map +1 -1
- package/dist/store.d.ts +63 -2
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +351 -65
- package/dist/store.js.map +1 -1
- package/dist/streams.d.ts +10 -0
- package/dist/streams.d.ts.map +1 -0
- package/dist/streams.js +183 -0
- package/dist/streams.js.map +1 -0
- package/dist/temporal-models.d.ts +48 -0
- package/dist/temporal-models.d.ts.map +1 -0
- package/dist/temporal-models.js +278 -0
- package/dist/temporal-models.js.map +1 -0
- package/dist/temporal-search.d.ts +29 -0
- package/dist/temporal-search.d.ts.map +1 -0
- package/dist/temporal-search.js +1507 -0
- package/dist/temporal-search.js.map +1 -0
- package/dist/transcripts.d.ts +16 -0
- package/dist/transcripts.d.ts.map +1 -0
- package/dist/transcripts.js +519 -0
- package/dist/transcripts.js.map +1 -0
- package/docs/engine-layout.md +346 -106
- package/docs/mvp-prd.md +918 -0
- package/fixtures/media/vancat.mp4 +0 -0
- package/fixtures/media/vancat_profile.jpg +0 -0
- package/fixtures/v5/contract-fixtures.json +1532 -0
- package/package.json +30 -5
- package/dist/history-types.d.ts +0 -74
- package/dist/history-types.d.ts.map +0 -1
- package/dist/history-types.js +0 -2
- package/dist/history-types.js.map +0 -1
- package/dist/timeline.d.ts +0 -9
- package/dist/timeline.d.ts.map +0 -1
- package/dist/timeline.js +0 -236
- package/dist/timeline.js.map +0 -1
package/dist/domain.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { ok } from "./engine-types.js";
|
|
2
|
-
import {
|
|
2
|
+
import { normalizeSearchLocation } from "./mvp-time.js";
|
|
3
|
+
import { resultOf, syncResultOf } from "./context.js";
|
|
3
4
|
import { assertUuidV7, newUuidV7 } from "./ids.js";
|
|
5
|
+
import { isValidNotebookCellSlug, NOTEBOOK_CELL_TYPES } from "./schema.js";
|
|
4
6
|
import { canonicalJson, parseJson } from "./store.js";
|
|
5
7
|
import { EngineFault } from "./store.js";
|
|
8
|
+
const NOTEBOOK_CELL_TYPE_SET = new Set(NOTEBOOK_CELL_TYPES);
|
|
6
9
|
export function createEntitiesApi(context) {
|
|
7
10
|
return {
|
|
8
11
|
create: (type, name, input = {}) => createEntity(context, type, name, input),
|
|
@@ -26,6 +29,10 @@ export function createNotebooksApi(context) {
|
|
|
26
29
|
list: () => listNotebooks(context),
|
|
27
30
|
read: (notebookId) => syncResultOf(() => requiredNotebook(context, notebookId)),
|
|
28
31
|
write: (notebook) => writeNotebook(context, notebook),
|
|
32
|
+
insertCell: (notebookId, cell) => insertNotebookCell(context, notebookId, cell),
|
|
33
|
+
updateCell: (notebookId, cell) => updateNotebookCell(context, notebookId, cell),
|
|
34
|
+
moveCell: (notebookId, cellId, slot) => moveNotebookCell(context, notebookId, cellId, slot),
|
|
35
|
+
removeCell: (notebookId, cellId) => removeNotebookCell(context, notebookId, cellId),
|
|
29
36
|
delete: (notebookId) => deleteNotebook(context, notebookId),
|
|
30
37
|
recordRun: (run) => recordNotebookRun(context, run),
|
|
31
38
|
};
|
|
@@ -37,9 +44,10 @@ async function createEntity(context, type, name, input) {
|
|
|
37
44
|
const entityId = newUuidV7();
|
|
38
45
|
const mutation = await context.store.semantic({
|
|
39
46
|
operation: "create_entity",
|
|
47
|
+
tables: ["entities"],
|
|
40
48
|
details: { entityId, type, name: normalizedName },
|
|
41
49
|
writeSet: [`entity:${entityId}`],
|
|
42
|
-
},
|
|
50
|
+
}, (_operationId, now) => {
|
|
43
51
|
context.store.db
|
|
44
52
|
.prepare(`INSERT INTO entities(
|
|
45
53
|
entity_id, type, name, description, prompt, data_json, created_at
|
|
@@ -70,9 +78,10 @@ async function writeEntity(context, entity) {
|
|
|
70
78
|
const name = requiredText(entity.name, "Entity name");
|
|
71
79
|
const mutation = await context.store.semantic({
|
|
72
80
|
operation: "write_entity",
|
|
81
|
+
tables: ["entities"],
|
|
73
82
|
details: { entityId: entity.id, type: entity.type },
|
|
74
83
|
writeSet: [`entity:${entity.id}`],
|
|
75
|
-
},
|
|
84
|
+
}, () => {
|
|
76
85
|
context.store.db
|
|
77
86
|
.prepare(`UPDATE entities
|
|
78
87
|
SET type=?, name=?, description=?, prompt=?, data_json=?
|
|
@@ -88,7 +97,7 @@ async function deleteEntity(context, entityId) {
|
|
|
88
97
|
requiredEntity(context, entityId);
|
|
89
98
|
const cells = context.store.db
|
|
90
99
|
.prepare(`SELECT notebook_id, cell_id FROM cells
|
|
91
|
-
WHERE
|
|
100
|
+
WHERE output_entity_id=? ORDER BY notebook_id, cell_id`)
|
|
92
101
|
.all(entityId);
|
|
93
102
|
if (cells.length > 0) {
|
|
94
103
|
throw new EngineFault({
|
|
@@ -96,7 +105,7 @@ async function deleteEntity(context, entityId) {
|
|
|
96
105
|
message: `Entity is still referenced: ${entityId}`,
|
|
97
106
|
details: {
|
|
98
107
|
references: cells.map((cell) => ({
|
|
99
|
-
kind: "cell.
|
|
108
|
+
kind: "cell.output_entity",
|
|
100
109
|
id: `${cell.notebook_id}/${cell.cell_id}`,
|
|
101
110
|
})),
|
|
102
111
|
},
|
|
@@ -104,9 +113,10 @@ async function deleteEntity(context, entityId) {
|
|
|
104
113
|
}
|
|
105
114
|
const mutation = await context.store.semantic({
|
|
106
115
|
operation: "delete_entity",
|
|
116
|
+
tables: ["entities"],
|
|
107
117
|
details: { entityId },
|
|
108
118
|
writeSet: [`entity:${entityId}`],
|
|
109
|
-
},
|
|
119
|
+
}, () => {
|
|
110
120
|
context.store.db
|
|
111
121
|
.prepare("DELETE FROM entities WHERE entity_id=?")
|
|
112
122
|
.run(entityId);
|
|
@@ -120,13 +130,14 @@ async function createNotebook(context, name) {
|
|
|
120
130
|
const notebookId = newUuidV7();
|
|
121
131
|
const mutation = await context.store.semantic({
|
|
122
132
|
operation: "create_notebook",
|
|
133
|
+
tables: ["notebooks"],
|
|
123
134
|
details: { notebookId, name: normalizedName },
|
|
124
135
|
writeSet: [`notebook:${notebookId}`],
|
|
125
|
-
},
|
|
136
|
+
}, (_operationId, now) => {
|
|
126
137
|
context.store.db
|
|
127
138
|
.prepare(`INSERT INTO notebooks(
|
|
128
|
-
notebook_id, name,
|
|
129
|
-
) VALUES (?, ?,
|
|
139
|
+
notebook_id, name, created_at
|
|
140
|
+
) VALUES (?, ?, ?)`)
|
|
130
141
|
.run(notebookId, normalizedName, now);
|
|
131
142
|
});
|
|
132
143
|
return ok(requiredNotebook(context, notebookId), mutation.revision);
|
|
@@ -145,31 +156,267 @@ async function writeNotebook(context, notebook) {
|
|
|
145
156
|
validateNotebook(context, notebook);
|
|
146
157
|
const mutation = await context.store.semantic({
|
|
147
158
|
operation: "write_notebook",
|
|
159
|
+
tables: [
|
|
160
|
+
"notebooks",
|
|
161
|
+
"notebook_fields",
|
|
162
|
+
"cells",
|
|
163
|
+
"notebook_cell_executions",
|
|
164
|
+
"notebook_generation_plans",
|
|
165
|
+
"notebook_run_plans",
|
|
166
|
+
"notebook_transcript_edits",
|
|
167
|
+
"notebook_transcript_attachments",
|
|
168
|
+
"edges",
|
|
169
|
+
"cell_references",
|
|
170
|
+
"pinned_search_results",
|
|
171
|
+
],
|
|
148
172
|
details: { notebookId: notebook.id },
|
|
149
173
|
writeSet: [
|
|
150
174
|
`notebook:${notebook.id}`,
|
|
151
175
|
...notebook.cells.map((cell) => `cell:${notebook.id}:${cell.id}`),
|
|
152
176
|
...notebook.edges.map((edge) => `edge:${notebook.id}:${edge.id}`),
|
|
153
177
|
],
|
|
154
|
-
},
|
|
178
|
+
}, () => {
|
|
155
179
|
context.store.db
|
|
156
|
-
.prepare(`UPDATE notebooks SET name
|
|
157
|
-
|
|
158
|
-
.run(requiredText(notebook.name, "Notebook name"), canonicalJson(notebook.properties ?? {}), notebook.id);
|
|
180
|
+
.prepare(`UPDATE notebooks SET name=? WHERE notebook_id=?`)
|
|
181
|
+
.run(requiredText(notebook.name, "Notebook name"), notebook.id);
|
|
159
182
|
synchronizeNotebookChildren(context, notebook);
|
|
183
|
+
synchronizeNotebookState(context, notebook);
|
|
184
|
+
});
|
|
185
|
+
return ok(revisionFor(context, mutation.revision), mutation.revision);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
async function insertNotebookCell(context, notebookId, cell) {
|
|
189
|
+
return resultOf(async () => {
|
|
190
|
+
assertUuidV7(notebookId, "Notebook ID");
|
|
191
|
+
const notebook = requiredNotebook(context, notebookId);
|
|
192
|
+
if (notebook.cells.some((existing) => existing.id === cell.id)) {
|
|
193
|
+
throw new Error(`Cell already exists: ${cell.id}`);
|
|
194
|
+
}
|
|
195
|
+
const prospective = { ...notebook, cells: [...notebook.cells, cell] };
|
|
196
|
+
validateCellFields(context, prospective, cell);
|
|
197
|
+
assertCellSlugFree(context, notebookId, cell);
|
|
198
|
+
assertCellSlotFree(context, notebookId, cell);
|
|
199
|
+
const mutation = await context.store.semantic({
|
|
200
|
+
operation: "insert_cell",
|
|
201
|
+
tables: ["cells", "cell_references", "pinned_search_results"],
|
|
202
|
+
details: { notebookId, cellId: cell.id },
|
|
203
|
+
writeSet: [`notebook:${notebookId}`, `cell:${notebookId}:${cell.id}`],
|
|
204
|
+
}, () => {
|
|
205
|
+
upsertNotebookCell(context, notebookId, cell);
|
|
206
|
+
repairCellSlots(context, notebookId, cell.id);
|
|
207
|
+
});
|
|
208
|
+
return ok(revisionFor(context, mutation.revision), mutation.revision);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
async function updateNotebookCell(context, notebookId, cell) {
|
|
212
|
+
return resultOf(async () => {
|
|
213
|
+
assertUuidV7(notebookId, "Notebook ID");
|
|
214
|
+
const notebook = requiredNotebook(context, notebookId);
|
|
215
|
+
if (!notebook.cells.some((existing) => existing.id === cell.id)) {
|
|
216
|
+
throw new EngineFault({
|
|
217
|
+
code: "NOT_FOUND",
|
|
218
|
+
message: `Cell not found: ${cell.id}`,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
const prospective = {
|
|
222
|
+
...notebook,
|
|
223
|
+
cells: notebook.cells.map((existing) => existing.id === cell.id ? cell : existing),
|
|
224
|
+
};
|
|
225
|
+
validateCellFields(context, prospective, cell);
|
|
226
|
+
assertCellSlugFree(context, notebookId, cell);
|
|
227
|
+
assertCellSlotFree(context, notebookId, cell);
|
|
228
|
+
const mutation = await context.store.semantic({
|
|
229
|
+
operation: "update_cell",
|
|
230
|
+
tables: ["cells", "cell_references", "pinned_search_results"],
|
|
231
|
+
details: { notebookId, cellId: cell.id },
|
|
232
|
+
writeSet: [`notebook:${notebookId}`, `cell:${notebookId}:${cell.id}`],
|
|
233
|
+
}, () => {
|
|
234
|
+
upsertNotebookCell(context, notebookId, cell);
|
|
235
|
+
repairCellSlots(context, notebookId, cell.id);
|
|
236
|
+
});
|
|
237
|
+
return ok(revisionFor(context, mutation.revision), mutation.revision);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
async function moveNotebookCell(context, notebookId, cellId, slot) {
|
|
241
|
+
return resultOf(async () => {
|
|
242
|
+
assertUuidV7(notebookId, "Notebook ID");
|
|
243
|
+
assertUuidV7(cellId, "Cell ID");
|
|
244
|
+
const notebook = requiredNotebook(context, notebookId);
|
|
245
|
+
const existing = notebook.cells.find((cell) => cell.id === cellId);
|
|
246
|
+
if (!existing) {
|
|
247
|
+
throw new EngineFault({
|
|
248
|
+
code: "NOT_FOUND",
|
|
249
|
+
message: `Cell not found: ${cellId}`,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
return updateNotebookCell(context, notebookId, { ...existing, slot });
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
async function removeNotebookCell(context, notebookId, cellId) {
|
|
256
|
+
return resultOf(async () => {
|
|
257
|
+
assertUuidV7(notebookId, "Notebook ID");
|
|
258
|
+
assertUuidV7(cellId, "Cell ID");
|
|
259
|
+
requiredNotebook(context, notebookId);
|
|
260
|
+
const found = context.store.db
|
|
261
|
+
.prepare("SELECT 1 AS present FROM cells WHERE notebook_id=? AND cell_id=?")
|
|
262
|
+
.get(notebookId, cellId);
|
|
263
|
+
if (!found) {
|
|
264
|
+
throw new EngineFault({
|
|
265
|
+
code: "NOT_FOUND",
|
|
266
|
+
message: `Cell not found: ${cellId}`,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
const mutation = await context.store.semantic({
|
|
270
|
+
operation: "remove_cell",
|
|
271
|
+
tables: [
|
|
272
|
+
"cells",
|
|
273
|
+
"notebook_cell_executions",
|
|
274
|
+
"notebook_generation_plans",
|
|
275
|
+
"edges",
|
|
276
|
+
"cell_references",
|
|
277
|
+
"pinned_search_results",
|
|
278
|
+
],
|
|
279
|
+
details: { notebookId, cellId },
|
|
280
|
+
writeSet: [`notebook:${notebookId}`, `cell:${notebookId}:${cellId}`],
|
|
281
|
+
}, () => {
|
|
282
|
+
context.store.db
|
|
283
|
+
.prepare("DELETE FROM cells WHERE notebook_id=? AND cell_id=?")
|
|
284
|
+
.run(notebookId, cellId);
|
|
285
|
+
repairCellSlots(context, notebookId);
|
|
160
286
|
});
|
|
161
287
|
return ok(revisionFor(context, mutation.revision), mutation.revision);
|
|
162
288
|
});
|
|
163
289
|
}
|
|
290
|
+
function assertCellSlugFree(context, notebookId, cell) {
|
|
291
|
+
const found = context.store.db
|
|
292
|
+
.prepare(`SELECT cell_id FROM cells
|
|
293
|
+
WHERE notebook_id=? AND slug=? AND cell_id<>?`)
|
|
294
|
+
.get(notebookId, cell.slug, cell.id);
|
|
295
|
+
if (found) {
|
|
296
|
+
throw new Error(`Duplicate cell slug: ${cell.slug}`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function assertCellSlotFree(context, notebookId, cell) {
|
|
300
|
+
const found = context.store.db
|
|
301
|
+
.prepare(`SELECT cell_id FROM cells
|
|
302
|
+
WHERE notebook_id=? AND grid_row=? AND grid_column=? AND cell_id<>?`)
|
|
303
|
+
.get(notebookId, cell.slot.row, cell.slot.column, cell.id);
|
|
304
|
+
if (found) {
|
|
305
|
+
throw new Error(`Cell slot is occupied: ${cell.slot.row}:${cell.slot.column}`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Repairs grid-slot collisions left behind by a merge (the schema no longer
|
|
310
|
+
* enforces slot uniqueness so two forks can land cells on the same slot).
|
|
311
|
+
* Within each colliding slot the protected cell wins when present, otherwise
|
|
312
|
+
* the lowest cell id; losers keep their column and move to fresh rows below
|
|
313
|
+
* the current grid, in cell-id order.
|
|
314
|
+
*/
|
|
315
|
+
function repairCellSlots(context, notebookId, protectedCellId) {
|
|
316
|
+
const rows = context.store.db
|
|
317
|
+
.prepare(`SELECT cell_id, grid_row, grid_column FROM cells
|
|
318
|
+
WHERE notebook_id=?
|
|
319
|
+
ORDER BY grid_row, grid_column, cell_id`)
|
|
320
|
+
.all(notebookId);
|
|
321
|
+
const slots = new Map();
|
|
322
|
+
let maxRow = -1;
|
|
323
|
+
for (const row of rows) {
|
|
324
|
+
maxRow = Math.max(maxRow, row.grid_row);
|
|
325
|
+
const key = `${row.grid_row}:${row.grid_column}`;
|
|
326
|
+
const group = slots.get(key) ?? [];
|
|
327
|
+
group.push({ cell_id: row.cell_id, grid_column: row.grid_column });
|
|
328
|
+
slots.set(key, group);
|
|
329
|
+
}
|
|
330
|
+
let nextRow = maxRow + 1;
|
|
331
|
+
const move = context.store.db.prepare("UPDATE cells SET grid_row=? WHERE notebook_id=? AND cell_id=?");
|
|
332
|
+
for (const group of slots.values()) {
|
|
333
|
+
if (group.length < 2)
|
|
334
|
+
continue;
|
|
335
|
+
const winner = protectedCellId && group.some((row) => row.cell_id === protectedCellId)
|
|
336
|
+
? protectedCellId
|
|
337
|
+
: group[0].cell_id;
|
|
338
|
+
for (const row of group) {
|
|
339
|
+
if (row.cell_id === winner)
|
|
340
|
+
continue;
|
|
341
|
+
move.run(nextRow, notebookId, row.cell_id);
|
|
342
|
+
nextRow += 1;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* `UNIQUE(notebook_id, slug)` is immediate and the upsert loop writes cells
|
|
348
|
+
* one at a time, so a document that validly swaps or rotates slugs between
|
|
349
|
+
* surviving cells would collide mid-loop. Every surviving cell whose slug is
|
|
350
|
+
* about to change is first parked on a unique temporary slug derived from
|
|
351
|
+
* its current one — same type prefix, so the slug/type CHECK holds — and
|
|
352
|
+
* the upsert pass then assigns the final slugs.
|
|
353
|
+
*/
|
|
354
|
+
function evacuateChangedCellSlugs(context, notebook) {
|
|
355
|
+
const existing = context.store.db
|
|
356
|
+
.prepare("SELECT cell_id, slug FROM cells WHERE notebook_id=?")
|
|
357
|
+
.all(notebook.id);
|
|
358
|
+
if (existing.length === 0)
|
|
359
|
+
return;
|
|
360
|
+
const incomingSlugs = new Map(notebook.cells.map((cell) => [
|
|
361
|
+
cell.id,
|
|
362
|
+
normalizeCellForWrite(cell).slug ?? "",
|
|
363
|
+
]));
|
|
364
|
+
const evacuate = context.store.db.prepare("UPDATE cells SET slug=? WHERE notebook_id=? AND cell_id=?");
|
|
365
|
+
for (const row of existing) {
|
|
366
|
+
const nextSlug = incomingSlugs.get(row.cell_id);
|
|
367
|
+
if (nextSlug === undefined || nextSlug === row.slug)
|
|
368
|
+
continue;
|
|
369
|
+
evacuate.run(`${row.slug}-evac-${newUuidV7().replace(/-/g, "")}`, notebook.id, row.cell_id);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function upsertNotebookCell(context, notebookId, cell) {
|
|
373
|
+
const normalized = normalizeCellForWrite(cell);
|
|
374
|
+
context.store.db
|
|
375
|
+
.prepare(`INSERT INTO cells(
|
|
376
|
+
notebook_id, cell_id, type, slug, grid_row, grid_column,
|
|
377
|
+
output_entity_id, prompt, provider, model, operation, tool,
|
|
378
|
+
inputs_json, output_artifact_id
|
|
379
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
380
|
+
ON CONFLICT(notebook_id, cell_id) DO UPDATE SET
|
|
381
|
+
type=excluded.type,
|
|
382
|
+
slug=excluded.slug,
|
|
383
|
+
grid_row=excluded.grid_row,
|
|
384
|
+
grid_column=excluded.grid_column,
|
|
385
|
+
output_entity_id=excluded.output_entity_id,
|
|
386
|
+
prompt=excluded.prompt,
|
|
387
|
+
provider=excluded.provider,
|
|
388
|
+
model=excluded.model,
|
|
389
|
+
operation=excluded.operation,
|
|
390
|
+
tool=excluded.tool,
|
|
391
|
+
inputs_json=excluded.inputs_json,
|
|
392
|
+
output_artifact_id=excluded.output_artifact_id`)
|
|
393
|
+
.run(notebookId, normalized.id, normalized.type, requiredText(normalized.slug, "Cell slug"), normalized.slot.row, normalized.slot.column, normalized.outputEntityId ?? null, normalized.prompt ?? null, normalized.provider ?? null, normalized.model ?? null, normalized.operation ?? null, normalized.tool ?? null, canonicalJson(normalized.inputs ?? {}), normalized.outputArtifactId ?? null);
|
|
394
|
+
synchronizeCellReferences(context, notebookId, normalized);
|
|
395
|
+
synchronizePinnedResults(context, notebookId, normalized);
|
|
396
|
+
}
|
|
164
397
|
async function deleteNotebook(context, notebookId) {
|
|
165
398
|
return resultOf(async () => {
|
|
166
399
|
assertUuidV7(notebookId, "Notebook ID");
|
|
167
400
|
requiredNotebook(context, notebookId);
|
|
168
401
|
const mutation = await context.store.semantic({
|
|
169
402
|
operation: "delete_notebook",
|
|
403
|
+
tables: [
|
|
404
|
+
"notebooks",
|
|
405
|
+
"notebook_fields",
|
|
406
|
+
"cells",
|
|
407
|
+
"notebook_cell_executions",
|
|
408
|
+
"notebook_generation_plans",
|
|
409
|
+
"notebook_run_plans",
|
|
410
|
+
"notebook_transcript_edits",
|
|
411
|
+
"notebook_transcript_attachments",
|
|
412
|
+
"edges",
|
|
413
|
+
"runs",
|
|
414
|
+
"cell_references",
|
|
415
|
+
"pinned_search_results",
|
|
416
|
+
],
|
|
170
417
|
details: { notebookId },
|
|
171
418
|
writeSet: [`notebook:${notebookId}`],
|
|
172
|
-
},
|
|
419
|
+
}, () => {
|
|
173
420
|
context.store.db
|
|
174
421
|
.prepare("DELETE FROM notebooks WHERE notebook_id=?")
|
|
175
422
|
.run(notebookId);
|
|
@@ -190,9 +437,10 @@ async function recordNotebookRun(context, run) {
|
|
|
190
437
|
}
|
|
191
438
|
const mutation = await context.store.semantic({
|
|
192
439
|
operation: "record_notebook_run",
|
|
440
|
+
tables: ["runs"],
|
|
193
441
|
details: { notebookId: run.notebookId, runId: run.id },
|
|
194
442
|
writeSet: [`run:${run.id}`],
|
|
195
|
-
},
|
|
443
|
+
}, () => {
|
|
196
444
|
context.store.db
|
|
197
445
|
.prepare(`INSERT INTO runs(
|
|
198
446
|
run_id, notebook_id, status, started_at,
|
|
@@ -231,47 +479,131 @@ function requiredNotebook(context, notebookId) {
|
|
|
231
479
|
return notebookFromRows(context, row);
|
|
232
480
|
}
|
|
233
481
|
function notebookFromRows(context, row) {
|
|
482
|
+
const fields = context.store.db
|
|
483
|
+
.prepare(`SELECT field, value_json
|
|
484
|
+
FROM notebook_fields WHERE notebook_id=?
|
|
485
|
+
ORDER BY field`)
|
|
486
|
+
.all(row.notebook_id);
|
|
234
487
|
const cells = context.store.db
|
|
235
|
-
.prepare(`SELECT cell_id, type,
|
|
236
|
-
prompt, model,
|
|
237
|
-
|
|
488
|
+
.prepare(`SELECT cell_id, type, slug, grid_row, grid_column, output_entity_id,
|
|
489
|
+
prompt, provider, model, operation, tool,
|
|
490
|
+
inputs_json, output_artifact_id
|
|
491
|
+
FROM cells WHERE notebook_id=?
|
|
492
|
+
ORDER BY grid_row, grid_column, cell_id`)
|
|
238
493
|
.all(row.notebook_id);
|
|
239
494
|
const edges = context.store.db
|
|
240
495
|
.prepare(`SELECT edge_id, source_cell_id, target_cell_id, target_input
|
|
241
496
|
FROM edges WHERE notebook_id=? ORDER BY edge_id`)
|
|
242
497
|
.all(row.notebook_id);
|
|
498
|
+
const references = context.store.db
|
|
499
|
+
.prepare(`SELECT cell_id, reference_id, kind, target_id, snapshot_json, ordinal
|
|
500
|
+
FROM cell_references
|
|
501
|
+
WHERE notebook_id=?
|
|
502
|
+
ORDER BY cell_id, ordinal, reference_id`)
|
|
503
|
+
.all(row.notebook_id);
|
|
504
|
+
const pinnedResults = context.store.db
|
|
505
|
+
.prepare(`SELECT cell_id, result_id, artifact_id, object_hash, location_json,
|
|
506
|
+
representative_json, query_json, signals_json,
|
|
507
|
+
selected_revision, ordinal, created_at
|
|
508
|
+
FROM pinned_search_results
|
|
509
|
+
WHERE notebook_id=?
|
|
510
|
+
ORDER BY cell_id, ordinal, result_id`)
|
|
511
|
+
.all(row.notebook_id);
|
|
512
|
+
const executions = context.store.db
|
|
513
|
+
.prepare(`SELECT cell_id, fingerprint, status, output_artifact_id,
|
|
514
|
+
provider_artifact_id, run_id, completed_at, started_at,
|
|
515
|
+
updated_at, tool, error, stale, fixture_baseline
|
|
516
|
+
FROM notebook_cell_executions
|
|
517
|
+
WHERE notebook_id=? ORDER BY cell_id`)
|
|
518
|
+
.all(row.notebook_id);
|
|
519
|
+
const generationPlans = context.store.db
|
|
520
|
+
.prepare(`SELECT plan_id, cell_id, status, plan_json, output_artifact_id,
|
|
521
|
+
error, created_at, updated_at
|
|
522
|
+
FROM notebook_generation_plans
|
|
523
|
+
WHERE notebook_id=? ORDER BY created_at, plan_id`)
|
|
524
|
+
.all(row.notebook_id);
|
|
525
|
+
const runPlans = context.store.db
|
|
526
|
+
.prepare(`SELECT plan_id, status, plan_json, paid_cell_ids_json,
|
|
527
|
+
cell_fingerprints_json, known_cost_usd, unknown_cost_count,
|
|
528
|
+
created_at, updated_at, run_id, outputs_json, error
|
|
529
|
+
FROM notebook_run_plans
|
|
530
|
+
WHERE notebook_id=? ORDER BY created_at, plan_id`)
|
|
531
|
+
.all(row.notebook_id);
|
|
532
|
+
const transcriptEdits = context.store.db
|
|
533
|
+
.prepare(`SELECT action_id, kind, restored, payload_json
|
|
534
|
+
FROM notebook_transcript_edits
|
|
535
|
+
WHERE notebook_id=? ORDER BY action_id`)
|
|
536
|
+
.all(row.notebook_id);
|
|
537
|
+
const transcriptAttachments = context.store.db
|
|
538
|
+
.prepare(`SELECT attachment_id, payload_json
|
|
539
|
+
FROM notebook_transcript_attachments
|
|
540
|
+
WHERE notebook_id=? ORDER BY attachment_id`)
|
|
541
|
+
.all(row.notebook_id);
|
|
542
|
+
const field = new Map(fields.map((item) => [
|
|
543
|
+
item.field,
|
|
544
|
+
parseJson(item.value_json, null),
|
|
545
|
+
]));
|
|
243
546
|
return {
|
|
244
547
|
id: row.notebook_id,
|
|
245
548
|
name: row.name,
|
|
246
|
-
|
|
247
|
-
|
|
549
|
+
...(typeof field.get("description") === "string"
|
|
550
|
+
? { description: field.get("description") }
|
|
551
|
+
: {}),
|
|
552
|
+
...(typeof field.get("lifecycle_state") === "string"
|
|
553
|
+
? { lifecycleState: field.get("lifecycle_state") }
|
|
554
|
+
: {}),
|
|
555
|
+
...(typeof field.get("workflow_version") === "number"
|
|
556
|
+
? { workflowVersion: field.get("workflow_version") }
|
|
557
|
+
: {}),
|
|
558
|
+
...(typeof field.get("analysis_revision") === "string"
|
|
559
|
+
? { analysisRevision: field.get("analysis_revision") }
|
|
560
|
+
: {}),
|
|
561
|
+
...(isRecord(field.get("audio_spine"))
|
|
562
|
+
? {
|
|
563
|
+
audioSpine: field.get("audio_spine"),
|
|
564
|
+
}
|
|
565
|
+
: {}),
|
|
566
|
+
...(isRecord(field.get("current_selection"))
|
|
567
|
+
? {
|
|
568
|
+
currentSelection: field.get("current_selection"),
|
|
569
|
+
}
|
|
570
|
+
: {}),
|
|
571
|
+
...(isRecord(field.get("fixture"))
|
|
572
|
+
? { fixture: field.get("fixture") }
|
|
573
|
+
: {}),
|
|
574
|
+
execution: Object.fromEntries(executions.map((execution) => [
|
|
575
|
+
execution.cell_id,
|
|
576
|
+
executionFromRow(execution),
|
|
577
|
+
])),
|
|
578
|
+
generationPlans: generationPlans.map(generationPlanFromRow),
|
|
579
|
+
notebookRunPlans: runPlans.map(runPlanFromRow),
|
|
580
|
+
transcriptEdits: transcriptEdits.map(transcriptEditFromRow),
|
|
581
|
+
transcriptAttachments: transcriptAttachments.map(transcriptAttachmentFromRow),
|
|
582
|
+
cells: cells.map((cell) => rowToCell(cell, references.filter((reference) => reference.cell_id === cell.cell_id), pinnedResults.filter((result) => result.cell_id === cell.cell_id))),
|
|
248
583
|
edges: edges.map(rowToEdge),
|
|
249
584
|
createdAt: new Date(row.created_at).toISOString(),
|
|
250
585
|
};
|
|
251
586
|
}
|
|
252
587
|
function validateNotebook(context, notebook) {
|
|
253
588
|
const cellIds = new Set();
|
|
589
|
+
const cellSlugs = new Set();
|
|
590
|
+
const occupiedSlots = new Set();
|
|
254
591
|
for (const cell of notebook.cells) {
|
|
255
|
-
assertUuidV7(cell.id, "Cell ID");
|
|
256
592
|
if (cellIds.has(cell.id))
|
|
257
593
|
throw new Error(`Duplicate cell ID: ${cell.id}`);
|
|
258
594
|
cellIds.add(cell.id);
|
|
259
|
-
if (
|
|
260
|
-
throw new Error(`
|
|
261
|
-
}
|
|
262
|
-
if (!Number.isFinite(cell.position.x) || !Number.isFinite(cell.position.y)) {
|
|
263
|
-
throw new Error(`Cell position must be finite: ${cell.id}`);
|
|
264
|
-
}
|
|
265
|
-
if (cell.entityId) {
|
|
266
|
-
assertUuidV7(cell.entityId, "Cell entity ID");
|
|
267
|
-
requiredEntity(context, cell.entityId);
|
|
268
|
-
}
|
|
269
|
-
if (cell.outputArtifactId) {
|
|
270
|
-
assertUuidV7(cell.outputArtifactId, "Cell output artifact ID");
|
|
271
|
-
context.artifactRowById(cell.outputArtifactId);
|
|
595
|
+
if (cellSlugs.has(cell.slug)) {
|
|
596
|
+
throw new Error(`Duplicate cell slug: ${cell.slug}`);
|
|
272
597
|
}
|
|
598
|
+
cellSlugs.add(cell.slug);
|
|
599
|
+
const slot = `${cell.slot.row}:${cell.slot.column}`;
|
|
600
|
+
if (occupiedSlots.has(slot))
|
|
601
|
+
throw new Error(`Duplicate cell slot: ${slot}`);
|
|
602
|
+
occupiedSlots.add(slot);
|
|
603
|
+
validateCellFields(context, notebook, cell);
|
|
273
604
|
}
|
|
274
605
|
const edgeIds = new Set();
|
|
606
|
+
const occupiedInputs = new Set();
|
|
275
607
|
for (const edge of notebook.edges) {
|
|
276
608
|
assertUuidV7(edge.id, "Edge ID");
|
|
277
609
|
if (edgeIds.has(edge.id))
|
|
@@ -280,29 +612,47 @@ function validateNotebook(context, notebook) {
|
|
|
280
612
|
if (!cellIds.has(edge.source) || !cellIds.has(edge.target)) {
|
|
281
613
|
throw new Error(`Edge ${edge.id} must reference cells in the notebook`);
|
|
282
614
|
}
|
|
283
|
-
requiredText(edge.targetInput, "Edge targetInput");
|
|
615
|
+
const targetInput = requiredText(edge.targetInput, "Edge targetInput");
|
|
616
|
+
const inputKey = `${edge.target}:${targetInput}`;
|
|
617
|
+
if (occupiedInputs.has(inputKey)) {
|
|
618
|
+
throw new Error(`Duplicate target input: ${edge.target} ${targetInput}`);
|
|
619
|
+
}
|
|
620
|
+
occupiedInputs.add(inputKey);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
function validateCellFields(context, notebook, cell) {
|
|
624
|
+
assertUuidV7(cell.id, "Cell ID");
|
|
625
|
+
if (!NOTEBOOK_CELL_TYPE_SET.has(cell.type)) {
|
|
626
|
+
throw new Error(`Invalid cell type: ${cell.type}`);
|
|
627
|
+
}
|
|
628
|
+
const slug = requiredText(cell.slug, "Cell slug");
|
|
629
|
+
if (slug !== cell.slug || !isValidNotebookCellSlug(cell.type, slug)) {
|
|
630
|
+
throw new Error(`Invalid ${cell.type} cell slug: ${cell.slug}`);
|
|
631
|
+
}
|
|
632
|
+
if (!Number.isInteger(cell.slot.row) ||
|
|
633
|
+
cell.slot.row < 0 ||
|
|
634
|
+
!Number.isInteger(cell.slot.column)) {
|
|
635
|
+
throw new Error(`Cell slot row must be nonnegative and column must be an integer: ${cell.id}`);
|
|
636
|
+
}
|
|
637
|
+
if (cell.outputEntityId) {
|
|
638
|
+
assertUuidV7(cell.outputEntityId, "Cell output entity ID");
|
|
639
|
+
requiredEntity(context, cell.outputEntityId);
|
|
284
640
|
}
|
|
641
|
+
if (cell.outputArtifactId) {
|
|
642
|
+
assertUuidV7(cell.outputArtifactId, "Cell output artifact ID");
|
|
643
|
+
context.artifactRowById(cell.outputArtifactId);
|
|
644
|
+
}
|
|
645
|
+
validateCellReferences(context, notebook, cell);
|
|
646
|
+
validatePinnedResults(context, cell);
|
|
285
647
|
}
|
|
286
648
|
function synchronizeNotebookChildren(context, notebook) {
|
|
287
649
|
deleteMissing(context, "edges", "edge_id", notebook.id, notebook.edges.map((edge) => edge.id));
|
|
288
650
|
deleteMissing(context, "cells", "cell_id", notebook.id, notebook.cells.map((cell) => cell.id));
|
|
289
|
-
|
|
290
|
-
notebook_id, cell_id, type, title, position_x, position_y,
|
|
291
|
-
entity_id, prompt, model, inputs_json, output_artifact_id
|
|
292
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
293
|
-
ON CONFLICT(notebook_id, cell_id) DO UPDATE SET
|
|
294
|
-
type=excluded.type,
|
|
295
|
-
title=excluded.title,
|
|
296
|
-
position_x=excluded.position_x,
|
|
297
|
-
position_y=excluded.position_y,
|
|
298
|
-
entity_id=excluded.entity_id,
|
|
299
|
-
prompt=excluded.prompt,
|
|
300
|
-
model=excluded.model,
|
|
301
|
-
inputs_json=excluded.inputs_json,
|
|
302
|
-
output_artifact_id=excluded.output_artifact_id`);
|
|
651
|
+
evacuateChangedCellSlugs(context, notebook);
|
|
303
652
|
for (const cell of notebook.cells) {
|
|
304
|
-
|
|
653
|
+
upsertNotebookCell(context, notebook.id, cell);
|
|
305
654
|
}
|
|
655
|
+
repairCellSlots(context, notebook.id);
|
|
306
656
|
const upsertEdge = context.store.db.prepare(`INSERT INTO edges(
|
|
307
657
|
notebook_id, edge_id, source_cell_id, target_cell_id, target_input
|
|
308
658
|
) VALUES (?, ?, ?, ?, ?)
|
|
@@ -314,6 +664,168 @@ function synchronizeNotebookChildren(context, notebook) {
|
|
|
314
664
|
upsertEdge.run(notebook.id, edge.id, edge.source, edge.target, edge.targetInput);
|
|
315
665
|
}
|
|
316
666
|
}
|
|
667
|
+
function synchronizeNotebookState(context, notebook) {
|
|
668
|
+
synchronizeNotebookFields(context, notebook);
|
|
669
|
+
synchronizeCellExecutions(context, notebook);
|
|
670
|
+
synchronizeGenerationPlans(context, notebook);
|
|
671
|
+
synchronizeRunPlans(context, notebook);
|
|
672
|
+
synchronizeTranscriptEdits(context, notebook);
|
|
673
|
+
synchronizeTranscriptAttachments(context, notebook);
|
|
674
|
+
}
|
|
675
|
+
function synchronizeNotebookFields(context, notebook) {
|
|
676
|
+
const values = new Map();
|
|
677
|
+
if (notebook.description !== undefined) {
|
|
678
|
+
values.set("description", notebook.description);
|
|
679
|
+
}
|
|
680
|
+
if (notebook.lifecycleState !== undefined) {
|
|
681
|
+
values.set("lifecycle_state", notebook.lifecycleState);
|
|
682
|
+
}
|
|
683
|
+
if (notebook.workflowVersion !== undefined) {
|
|
684
|
+
if (!Number.isSafeInteger(notebook.workflowVersion)) {
|
|
685
|
+
throw new Error("Notebook workflow version must be an integer");
|
|
686
|
+
}
|
|
687
|
+
values.set("workflow_version", notebook.workflowVersion);
|
|
688
|
+
}
|
|
689
|
+
if (notebook.analysisRevision !== undefined) {
|
|
690
|
+
values.set("analysis_revision", notebook.analysisRevision);
|
|
691
|
+
}
|
|
692
|
+
if (notebook.audioSpine !== undefined) {
|
|
693
|
+
values.set("audio_spine", notebook.audioSpine);
|
|
694
|
+
}
|
|
695
|
+
if (notebook.currentSelection !== undefined) {
|
|
696
|
+
values.set("current_selection", notebook.currentSelection);
|
|
697
|
+
}
|
|
698
|
+
if (notebook.fixture !== undefined) {
|
|
699
|
+
values.set("fixture", notebook.fixture);
|
|
700
|
+
}
|
|
701
|
+
deleteMissingNotebookRows(context, "notebook_fields", "field", notebook.id, [
|
|
702
|
+
...values.keys(),
|
|
703
|
+
]);
|
|
704
|
+
const upsert = context.store.db.prepare(`INSERT INTO notebook_fields(notebook_id, field, value_json)
|
|
705
|
+
VALUES (?, ?, ?)
|
|
706
|
+
ON CONFLICT(notebook_id, field) DO UPDATE SET
|
|
707
|
+
value_json=excluded.value_json`);
|
|
708
|
+
for (const [field, value] of values) {
|
|
709
|
+
upsert.run(notebook.id, field, canonicalJson(value));
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
function synchronizeCellExecutions(context, notebook) {
|
|
713
|
+
const entries = Object.entries(notebook.execution ?? {});
|
|
714
|
+
const cellIds = new Set(notebook.cells.map((cell) => cell.id));
|
|
715
|
+
for (const [cellId] of entries) {
|
|
716
|
+
if (!cellIds.has(cellId)) {
|
|
717
|
+
throw new Error(`Notebook execution references missing cell: ${cellId}`);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
deleteMissingNotebookRows(context, "notebook_cell_executions", "cell_id", notebook.id, entries.map(([cellId]) => cellId));
|
|
721
|
+
const upsert = context.store.db.prepare(`INSERT INTO notebook_cell_executions(
|
|
722
|
+
notebook_id, cell_id, fingerprint, status, output_artifact_id,
|
|
723
|
+
provider_artifact_id, run_id, completed_at, started_at, updated_at,
|
|
724
|
+
tool, error, stale, fixture_baseline
|
|
725
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
726
|
+
ON CONFLICT(notebook_id, cell_id) DO UPDATE SET
|
|
727
|
+
fingerprint=excluded.fingerprint,
|
|
728
|
+
status=excluded.status,
|
|
729
|
+
output_artifact_id=excluded.output_artifact_id,
|
|
730
|
+
provider_artifact_id=excluded.provider_artifact_id,
|
|
731
|
+
run_id=excluded.run_id,
|
|
732
|
+
completed_at=excluded.completed_at,
|
|
733
|
+
started_at=excluded.started_at,
|
|
734
|
+
updated_at=excluded.updated_at,
|
|
735
|
+
tool=excluded.tool,
|
|
736
|
+
error=excluded.error,
|
|
737
|
+
stale=excluded.stale,
|
|
738
|
+
fixture_baseline=excluded.fixture_baseline`);
|
|
739
|
+
for (const [cellId, execution] of entries) {
|
|
740
|
+
upsert.run(notebook.id, cellId, execution.fingerprint ?? null, execution.status ?? null, execution.outputArtifactId ?? null, execution.providerArtifactId ?? null, execution.runId ?? null, execution.completedAt ?? null, execution.startedAt ?? null, execution.updatedAt ?? null, execution.tool ?? null, execution.error ?? null, execution.stale === true ? 1 : 0, execution.fixtureBaseline === true ? 1 : 0);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
function synchronizeGenerationPlans(context, notebook) {
|
|
744
|
+
const plans = notebook.generationPlans ?? [];
|
|
745
|
+
assertUnique(plans.map((plan) => plan.planId), "generation plan ID");
|
|
746
|
+
deleteMissingNotebookRows(context, "notebook_generation_plans", "plan_id", notebook.id, plans.map((plan) => plan.planId));
|
|
747
|
+
const upsert = context.store.db.prepare(`INSERT INTO notebook_generation_plans(
|
|
748
|
+
notebook_id, plan_id, cell_id, status, plan_json,
|
|
749
|
+
output_artifact_id, error, created_at, updated_at
|
|
750
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
751
|
+
ON CONFLICT(notebook_id, plan_id) DO UPDATE SET
|
|
752
|
+
cell_id=excluded.cell_id,
|
|
753
|
+
status=excluded.status,
|
|
754
|
+
plan_json=excluded.plan_json,
|
|
755
|
+
output_artifact_id=excluded.output_artifact_id,
|
|
756
|
+
error=excluded.error,
|
|
757
|
+
created_at=excluded.created_at,
|
|
758
|
+
updated_at=excluded.updated_at`);
|
|
759
|
+
for (const plan of plans) {
|
|
760
|
+
upsert.run(notebook.id, requiredText(plan.planId, "Generation plan ID"), requiredText(plan.cellId, "Generation plan cell ID"), requiredText(plan.status, "Generation plan status"), canonicalJson(plan.plan), plan.outputArtifactId ?? null, plan.error ?? null, requiredText(plan.createdAt, "Generation plan created time"), requiredText(plan.updatedAt, "Generation plan updated time"));
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
function synchronizeRunPlans(context, notebook) {
|
|
764
|
+
const plans = notebook.notebookRunPlans ?? [];
|
|
765
|
+
assertUnique(plans.map((plan) => plan.planId), "notebook run plan ID");
|
|
766
|
+
deleteMissingNotebookRows(context, "notebook_run_plans", "plan_id", notebook.id, plans.map((plan) => plan.planId));
|
|
767
|
+
const upsert = context.store.db.prepare(`INSERT INTO notebook_run_plans(
|
|
768
|
+
notebook_id, plan_id, status, plan_json, paid_cell_ids_json,
|
|
769
|
+
cell_fingerprints_json, known_cost_usd, unknown_cost_count,
|
|
770
|
+
created_at, updated_at, run_id, outputs_json, error
|
|
771
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
772
|
+
ON CONFLICT(notebook_id, plan_id) DO UPDATE SET
|
|
773
|
+
status=excluded.status,
|
|
774
|
+
plan_json=excluded.plan_json,
|
|
775
|
+
paid_cell_ids_json=excluded.paid_cell_ids_json,
|
|
776
|
+
cell_fingerprints_json=excluded.cell_fingerprints_json,
|
|
777
|
+
known_cost_usd=excluded.known_cost_usd,
|
|
778
|
+
unknown_cost_count=excluded.unknown_cost_count,
|
|
779
|
+
created_at=excluded.created_at,
|
|
780
|
+
updated_at=excluded.updated_at,
|
|
781
|
+
run_id=excluded.run_id,
|
|
782
|
+
outputs_json=excluded.outputs_json,
|
|
783
|
+
error=excluded.error`);
|
|
784
|
+
for (const plan of plans) {
|
|
785
|
+
upsert.run(notebook.id, requiredText(plan.planId, "Notebook run plan ID"), requiredText(plan.status, "Notebook run plan status"), canonicalJson(plan.plan), canonicalJson(plan.paidCellIds), canonicalJson(plan.cellDefinitionFingerprints), nonNegativeFinite(plan.knownCostUsd, "Notebook run plan known cost"), nonNegativeInteger(plan.unknownCostCount, "Notebook run plan unknown cost count"), requiredText(plan.createdAt, "Notebook run plan created time"), requiredText(plan.updatedAt, "Notebook run plan updated time"), plan.runId ?? null, plan.outputs === undefined ? null : canonicalJson(plan.outputs), plan.error ?? null);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
function synchronizeTranscriptEdits(context, notebook) {
|
|
789
|
+
const edits = notebook.transcriptEdits ?? [];
|
|
790
|
+
assertUnique(edits.map((edit) => edit.actionId), "transcript edit action ID");
|
|
791
|
+
deleteMissingNotebookRows(context, "notebook_transcript_edits", "action_id", notebook.id, edits.map((edit) => edit.actionId));
|
|
792
|
+
const upsert = context.store.db.prepare(`INSERT INTO notebook_transcript_edits(
|
|
793
|
+
notebook_id, action_id, kind, restored, payload_json
|
|
794
|
+
) VALUES (?, ?, ?, ?, ?)
|
|
795
|
+
ON CONFLICT(notebook_id, action_id) DO UPDATE SET
|
|
796
|
+
kind=excluded.kind,
|
|
797
|
+
restored=excluded.restored,
|
|
798
|
+
payload_json=excluded.payload_json`);
|
|
799
|
+
for (const edit of edits) {
|
|
800
|
+
upsert.run(notebook.id, requiredText(edit.actionId, "Transcript edit action ID"), requiredText(edit.kind, "Transcript edit kind"), edit.restored === true ? 1 : 0, canonicalJson(edit));
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
function synchronizeTranscriptAttachments(context, notebook) {
|
|
804
|
+
const attachments = notebook.transcriptAttachments ?? [];
|
|
805
|
+
assertUnique(attachments.map((attachment) => attachment.id), "transcript attachment ID");
|
|
806
|
+
deleteMissingNotebookRows(context, "notebook_transcript_attachments", "attachment_id", notebook.id, attachments.map((attachment) => attachment.id));
|
|
807
|
+
const upsert = context.store.db.prepare(`INSERT INTO notebook_transcript_attachments(
|
|
808
|
+
notebook_id, attachment_id, payload_json
|
|
809
|
+
) VALUES (?, ?, ?)
|
|
810
|
+
ON CONFLICT(notebook_id, attachment_id) DO UPDATE SET
|
|
811
|
+
payload_json=excluded.payload_json`);
|
|
812
|
+
for (const attachment of attachments) {
|
|
813
|
+
upsert.run(notebook.id, requiredText(attachment.id, "Transcript attachment ID"), canonicalJson(attachment));
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
function deleteMissingNotebookRows(context, table, idColumn, notebookId, ids) {
|
|
817
|
+
if (ids.length === 0) {
|
|
818
|
+
context.store.db
|
|
819
|
+
.prepare(`DELETE FROM ${table} WHERE notebook_id=?`)
|
|
820
|
+
.run(notebookId);
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
824
|
+
context.store.db
|
|
825
|
+
.prepare(`DELETE FROM ${table}
|
|
826
|
+
WHERE notebook_id=? AND ${idColumn} NOT IN (${placeholders})`)
|
|
827
|
+
.run(notebookId, ...ids);
|
|
828
|
+
}
|
|
317
829
|
function deleteMissing(context, table, idColumn, notebookId, ids) {
|
|
318
830
|
if (ids.length === 0) {
|
|
319
831
|
context.store.db
|
|
@@ -327,21 +839,230 @@ function deleteMissing(context, table, idColumn, notebookId, ids) {
|
|
|
327
839
|
WHERE notebook_id=? AND ${idColumn} NOT IN (${placeholders})`)
|
|
328
840
|
.run(notebookId, ...ids);
|
|
329
841
|
}
|
|
330
|
-
function
|
|
842
|
+
function validateCellReferences(context, notebook, cell) {
|
|
843
|
+
const ids = new Set();
|
|
844
|
+
const ordinals = new Set();
|
|
845
|
+
for (const reference of cell.references ?? []) {
|
|
846
|
+
assertUuidV7(reference.id, "Cell reference ID");
|
|
847
|
+
if (ids.has(reference.id)) {
|
|
848
|
+
throw new Error(`Duplicate cell reference ID: ${reference.id}`);
|
|
849
|
+
}
|
|
850
|
+
ids.add(reference.id);
|
|
851
|
+
validateOrdinal(reference.ordinal, "Cell reference ordinal");
|
|
852
|
+
if (ordinals.has(reference.ordinal)) {
|
|
853
|
+
throw new Error(`Duplicate cell reference ordinal: ${reference.ordinal}`);
|
|
854
|
+
}
|
|
855
|
+
ordinals.add(reference.ordinal);
|
|
856
|
+
requiredText(reference.targetId, "Cell reference target ID");
|
|
857
|
+
assertReferenceTarget(context, notebook, reference);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
function assertReferenceTarget(context, notebook, reference) {
|
|
861
|
+
if (reference.kind === "artifact") {
|
|
862
|
+
context.artifactRowById(reference.targetId);
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
if (reference.kind === "cell-output") {
|
|
866
|
+
if (!notebook.cells.some((cell) => cell.id === reference.targetId)) {
|
|
867
|
+
throw new Error(`Cell output target not found: ${reference.targetId}`);
|
|
868
|
+
}
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
const table = reference.kind === "transcript"
|
|
872
|
+
? "transcripts"
|
|
873
|
+
: reference.kind === "sequence"
|
|
874
|
+
? "sequences"
|
|
875
|
+
: "artifact_streams";
|
|
876
|
+
const column = reference.kind === "transcript"
|
|
877
|
+
? "transcript_id"
|
|
878
|
+
: reference.kind === "sequence"
|
|
879
|
+
? "sequence_id"
|
|
880
|
+
: "stream_id";
|
|
881
|
+
const found = context.store.db
|
|
882
|
+
.prepare(`SELECT 1 AS present FROM ${table} WHERE ${column}=?`)
|
|
883
|
+
.get(reference.targetId);
|
|
884
|
+
if (!found) {
|
|
885
|
+
throw new Error(`${reference.kind} target not found: ${reference.targetId}`);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
function validatePinnedResults(context, cell) {
|
|
889
|
+
const ids = new Set();
|
|
890
|
+
const ordinals = new Set();
|
|
891
|
+
for (const result of cell.pinnedResults ?? []) {
|
|
892
|
+
assertUuidV7(result.id, "Pinned search result ID");
|
|
893
|
+
if (ids.has(result.id)) {
|
|
894
|
+
throw new Error(`Duplicate pinned search result ID: ${result.id}`);
|
|
895
|
+
}
|
|
896
|
+
ids.add(result.id);
|
|
897
|
+
validateOrdinal(result.ordinal, "Pinned search result ordinal");
|
|
898
|
+
if (ordinals.has(result.ordinal)) {
|
|
899
|
+
throw new Error(`Duplicate pinned search result ordinal: ${result.ordinal}`);
|
|
900
|
+
}
|
|
901
|
+
ordinals.add(result.ordinal);
|
|
902
|
+
context.artifactRowById(result.artifactId);
|
|
903
|
+
requiredText(result.objectHash, "Pinned search result object hash");
|
|
904
|
+
normalizeSearchLocation(result.location);
|
|
905
|
+
requiredText(result.selectedRevision, "Pinned search result revision");
|
|
906
|
+
if (!Number.isSafeInteger(result.createdAt) || result.createdAt < 0) {
|
|
907
|
+
throw new Error("Pinned search result createdAt must be a positive integer");
|
|
908
|
+
}
|
|
909
|
+
const object = context.store.db
|
|
910
|
+
.prepare("SELECT 1 AS present FROM objects WHERE object_hash=?")
|
|
911
|
+
.get(result.objectHash);
|
|
912
|
+
if (!object) {
|
|
913
|
+
throw new Error(`Pinned search result object not found: ${result.objectHash}`);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
function validateOrdinal(value, label) {
|
|
918
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
919
|
+
throw new Error(`${label} must be a non-negative safe integer`);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
function synchronizeCellReferences(context, notebookId, cell) {
|
|
923
|
+
const references = cell.references ?? [];
|
|
924
|
+
deleteMissingChildren(context, "cell_references", "reference_id", notebookId, cell.id, references.map((reference) => reference.id));
|
|
925
|
+
const upsert = context.store.db.prepare(`INSERT INTO cell_references(
|
|
926
|
+
notebook_id, cell_id, reference_id, kind,
|
|
927
|
+
target_id, snapshot_json, ordinal
|
|
928
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
929
|
+
ON CONFLICT(notebook_id, cell_id, reference_id) DO UPDATE SET
|
|
930
|
+
kind=excluded.kind,
|
|
931
|
+
target_id=excluded.target_id,
|
|
932
|
+
snapshot_json=excluded.snapshot_json,
|
|
933
|
+
ordinal=excluded.ordinal`);
|
|
934
|
+
for (const reference of references) {
|
|
935
|
+
upsert.run(notebookId, cell.id, reference.id, reference.kind, reference.targetId, canonicalJson(reference.snapshot), reference.ordinal);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
function synchronizePinnedResults(context, notebookId, cell) {
|
|
939
|
+
const pinnedResults = cell.pinnedResults ?? [];
|
|
940
|
+
deleteMissingChildren(context, "pinned_search_results", "result_id", notebookId, cell.id, pinnedResults.map((result) => result.id));
|
|
941
|
+
const upsert = context.store.db.prepare(`INSERT INTO pinned_search_results(
|
|
942
|
+
notebook_id, cell_id, result_id, artifact_id, object_hash,
|
|
943
|
+
location_json, representative_json, query_json, signals_json,
|
|
944
|
+
selected_revision, ordinal, created_at
|
|
945
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
946
|
+
ON CONFLICT(notebook_id, cell_id, result_id) DO UPDATE SET
|
|
947
|
+
artifact_id=excluded.artifact_id,
|
|
948
|
+
object_hash=excluded.object_hash,
|
|
949
|
+
location_json=excluded.location_json,
|
|
950
|
+
representative_json=excluded.representative_json,
|
|
951
|
+
query_json=excluded.query_json,
|
|
952
|
+
signals_json=excluded.signals_json,
|
|
953
|
+
selected_revision=excluded.selected_revision,
|
|
954
|
+
ordinal=excluded.ordinal,
|
|
955
|
+
created_at=excluded.created_at`);
|
|
956
|
+
for (const result of pinnedResults) {
|
|
957
|
+
upsert.run(notebookId, cell.id, result.id, result.artifactId, result.objectHash, canonicalJson(normalizeSearchLocation(result.location)), result.representativeTick === undefined
|
|
958
|
+
? null
|
|
959
|
+
: canonicalJson(result.representativeTick), canonicalJson(result.query), canonicalJson(result.signals), result.selectedRevision, result.ordinal, result.createdAt);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
function deleteMissingChildren(context, table, idColumn, notebookId, cellId, ids) {
|
|
963
|
+
if (ids.length === 0) {
|
|
964
|
+
context.store.db
|
|
965
|
+
.prepare(`DELETE FROM ${table} WHERE notebook_id=? AND cell_id=?`)
|
|
966
|
+
.run(notebookId, cellId);
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
970
|
+
context.store.db
|
|
971
|
+
.prepare(`DELETE FROM ${table}
|
|
972
|
+
WHERE notebook_id=? AND cell_id=? AND ${idColumn} NOT IN (${placeholders})`)
|
|
973
|
+
.run(notebookId, cellId, ...ids);
|
|
974
|
+
}
|
|
975
|
+
function rowToCellReference(row) {
|
|
976
|
+
return {
|
|
977
|
+
id: row.reference_id,
|
|
978
|
+
kind: row.kind,
|
|
979
|
+
targetId: row.target_id,
|
|
980
|
+
snapshot: parseJson(row.snapshot_json, {}),
|
|
981
|
+
ordinal: row.ordinal,
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
function rowToPinnedSearchResult(row) {
|
|
985
|
+
const representativeTick = row.representative_json === null
|
|
986
|
+
? undefined
|
|
987
|
+
: parseJson(row.representative_json, undefined);
|
|
988
|
+
return {
|
|
989
|
+
id: row.result_id,
|
|
990
|
+
artifactId: row.artifact_id,
|
|
991
|
+
objectHash: row.object_hash,
|
|
992
|
+
location: normalizeSearchLocation(parseJson(row.location_json, {
|
|
993
|
+
kind: "still",
|
|
994
|
+
artifactId: row.artifact_id,
|
|
995
|
+
sourcePath: "unknown",
|
|
996
|
+
objectHash: row.object_hash,
|
|
997
|
+
})),
|
|
998
|
+
...(representativeTick === undefined ? {} : { representativeTick }),
|
|
999
|
+
query: parseJson(row.query_json, {}),
|
|
1000
|
+
signals: parseJson(row.signals_json, []),
|
|
1001
|
+
selectedRevision: row.selected_revision,
|
|
1002
|
+
ordinal: row.ordinal,
|
|
1003
|
+
createdAt: row.created_at,
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
function rowToCell(row, references, pinnedResults) {
|
|
1007
|
+
const inputs = parseJson(row.inputs_json, {});
|
|
1008
|
+
const legacyProvider = optionalString(inputs.provider);
|
|
1009
|
+
const legacyOperation = optionalString(inputs.operation);
|
|
1010
|
+
const provider = optionalString(row.provider) ?? legacyProvider;
|
|
1011
|
+
const operation = optionalString(row.operation) ?? legacyOperation;
|
|
1012
|
+
const model = optionalString(row.model);
|
|
1013
|
+
const tool = optionalString(row.tool) ??
|
|
1014
|
+
(looksLikeGenerationTool(model) ? model : undefined);
|
|
331
1015
|
return {
|
|
332
1016
|
id: row.cell_id,
|
|
333
|
-
type: row.type,
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
...(row.
|
|
1017
|
+
type: notebookCellType(row.type),
|
|
1018
|
+
slug: row.slug,
|
|
1019
|
+
slot: { row: row.grid_row, column: row.grid_column },
|
|
1020
|
+
...(row.output_entity_id ? { outputEntityId: row.output_entity_id } : {}),
|
|
337
1021
|
...(row.prompt ? { prompt: row.prompt } : {}),
|
|
338
|
-
...(
|
|
339
|
-
|
|
1022
|
+
...(provider ? { provider } : {}),
|
|
1023
|
+
...(model ? { model } : {}),
|
|
1024
|
+
...(operation ? { operation } : {}),
|
|
1025
|
+
...(tool ? { tool } : {}),
|
|
1026
|
+
inputs,
|
|
340
1027
|
...(row.output_artifact_id
|
|
341
1028
|
? { outputArtifactId: row.output_artifact_id }
|
|
342
1029
|
: {}),
|
|
1030
|
+
references: references.map(rowToCellReference),
|
|
1031
|
+
pinnedResults: pinnedResults.map(rowToPinnedSearchResult),
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
function notebookCellType(type) {
|
|
1035
|
+
if (!NOTEBOOK_CELL_TYPE_SET.has(type)) {
|
|
1036
|
+
throw new Error(`Invalid cell type: ${type}`);
|
|
1037
|
+
}
|
|
1038
|
+
return type;
|
|
1039
|
+
}
|
|
1040
|
+
function normalizeCellForWrite(cell) {
|
|
1041
|
+
const inputs = { ...(cell.inputs ?? {}) };
|
|
1042
|
+
const provider = optionalString(cell.provider) ?? optionalString(inputs.provider);
|
|
1043
|
+
const operation = optionalString(cell.operation) ?? optionalString(inputs.operation);
|
|
1044
|
+
const model = optionalString(cell.model);
|
|
1045
|
+
const tool = optionalString(cell.tool) ??
|
|
1046
|
+
(looksLikeGenerationTool(model) ? model : undefined);
|
|
1047
|
+
return {
|
|
1048
|
+
...cell,
|
|
1049
|
+
...(provider ? { provider } : { provider: undefined }),
|
|
1050
|
+
...(operation ? { operation } : { operation: undefined }),
|
|
1051
|
+
...(tool ? { tool } : { tool: undefined }),
|
|
1052
|
+
inputs,
|
|
343
1053
|
};
|
|
344
1054
|
}
|
|
1055
|
+
function optionalString(value) {
|
|
1056
|
+
if (typeof value !== "string")
|
|
1057
|
+
return undefined;
|
|
1058
|
+
const trimmed = value.trim();
|
|
1059
|
+
return trimmed ? trimmed : undefined;
|
|
1060
|
+
}
|
|
1061
|
+
function looksLikeGenerationTool(value) {
|
|
1062
|
+
if (!value)
|
|
1063
|
+
return false;
|
|
1064
|
+
return value.startsWith("generate_") || value.includes("/");
|
|
1065
|
+
}
|
|
345
1066
|
function rowToEdge(row) {
|
|
346
1067
|
return {
|
|
347
1068
|
id: row.edge_id,
|
|
@@ -383,6 +1104,97 @@ function validateRunStatus(status) {
|
|
|
383
1104
|
throw new Error(`Invalid notebook run status: ${status}`);
|
|
384
1105
|
}
|
|
385
1106
|
}
|
|
1107
|
+
function executionFromRow(row) {
|
|
1108
|
+
return {
|
|
1109
|
+
...(row.fingerprint ? { fingerprint: row.fingerprint } : {}),
|
|
1110
|
+
...(row.status ? { status: row.status } : {}),
|
|
1111
|
+
...(row.output_artifact_id
|
|
1112
|
+
? { outputArtifactId: row.output_artifact_id }
|
|
1113
|
+
: {}),
|
|
1114
|
+
...(row.provider_artifact_id
|
|
1115
|
+
? { providerArtifactId: row.provider_artifact_id }
|
|
1116
|
+
: {}),
|
|
1117
|
+
...(row.run_id ? { runId: row.run_id } : {}),
|
|
1118
|
+
...(row.completed_at ? { completedAt: row.completed_at } : {}),
|
|
1119
|
+
...(row.started_at ? { startedAt: row.started_at } : {}),
|
|
1120
|
+
...(row.updated_at ? { updatedAt: row.updated_at } : {}),
|
|
1121
|
+
...(row.tool ? { tool: row.tool } : {}),
|
|
1122
|
+
...(row.error ? { error: row.error } : {}),
|
|
1123
|
+
...(row.stale === 1 ? { stale: true } : {}),
|
|
1124
|
+
...(row.fixture_baseline === 1 ? { fixtureBaseline: true } : {}),
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
function generationPlanFromRow(row) {
|
|
1128
|
+
return {
|
|
1129
|
+
planId: row.plan_id,
|
|
1130
|
+
cellId: row.cell_id,
|
|
1131
|
+
status: row.status,
|
|
1132
|
+
plan: parseJson(row.plan_json, {}),
|
|
1133
|
+
...(row.output_artifact_id
|
|
1134
|
+
? { outputArtifactId: row.output_artifact_id }
|
|
1135
|
+
: {}),
|
|
1136
|
+
...(row.error ? { error: row.error } : {}),
|
|
1137
|
+
createdAt: row.created_at,
|
|
1138
|
+
updatedAt: row.updated_at,
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
function runPlanFromRow(row) {
|
|
1142
|
+
return {
|
|
1143
|
+
planId: row.plan_id,
|
|
1144
|
+
status: row.status,
|
|
1145
|
+
plan: parseJson(row.plan_json, {}),
|
|
1146
|
+
paidCellIds: parseJson(row.paid_cell_ids_json, []),
|
|
1147
|
+
cellDefinitionFingerprints: parseJson(row.cell_fingerprints_json, {}),
|
|
1148
|
+
knownCostUsd: row.known_cost_usd,
|
|
1149
|
+
unknownCostCount: row.unknown_cost_count,
|
|
1150
|
+
createdAt: row.created_at,
|
|
1151
|
+
updatedAt: row.updated_at,
|
|
1152
|
+
...(row.run_id ? { runId: row.run_id } : {}),
|
|
1153
|
+
...(row.outputs_json
|
|
1154
|
+
? {
|
|
1155
|
+
outputs: parseJson(row.outputs_json, {}),
|
|
1156
|
+
}
|
|
1157
|
+
: {}),
|
|
1158
|
+
...(row.error ? { error: row.error } : {}),
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
function transcriptEditFromRow(row) {
|
|
1162
|
+
const payload = parseJson(row.payload_json, {});
|
|
1163
|
+
return {
|
|
1164
|
+
...payload,
|
|
1165
|
+
actionId: row.action_id,
|
|
1166
|
+
kind: row.kind,
|
|
1167
|
+
...(row.restored === 1 ? { restored: true } : {}),
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
function transcriptAttachmentFromRow(row) {
|
|
1171
|
+
const payload = parseJson(row.payload_json, {});
|
|
1172
|
+
return {
|
|
1173
|
+
...payload,
|
|
1174
|
+
id: row.attachment_id,
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
function assertUnique(values, label) {
|
|
1178
|
+
const unique = new Set(values);
|
|
1179
|
+
if (unique.size !== values.length) {
|
|
1180
|
+
throw new Error(`Duplicate ${label}`);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
function nonNegativeFinite(value, label) {
|
|
1184
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
1185
|
+
throw new Error(`${label} must be a non-negative finite number`);
|
|
1186
|
+
}
|
|
1187
|
+
return value;
|
|
1188
|
+
}
|
|
1189
|
+
function nonNegativeInteger(value, label) {
|
|
1190
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
1191
|
+
throw new Error(`${label} must be a non-negative integer`);
|
|
1192
|
+
}
|
|
1193
|
+
return value;
|
|
1194
|
+
}
|
|
1195
|
+
function isRecord(value) {
|
|
1196
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1197
|
+
}
|
|
386
1198
|
function callerOrNewId(id, label) {
|
|
387
1199
|
const resolved = id ?? newUuidV7();
|
|
388
1200
|
assertUuidV7(resolved, label);
|
|
@@ -393,7 +1205,7 @@ const ENTITY_SELECT = `
|
|
|
393
1205
|
FROM entities
|
|
394
1206
|
`;
|
|
395
1207
|
const NOTEBOOK_SELECT = `
|
|
396
|
-
SELECT notebook_id, name,
|
|
1208
|
+
SELECT notebook_id, name, created_at
|
|
397
1209
|
FROM notebooks
|
|
398
1210
|
`;
|
|
399
1211
|
//# sourceMappingURL=domain.js.map
|