videobook-engine 2.0.1 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -68
- package/dist/artifacts.d.ts +4 -9
- package/dist/artifacts.d.ts.map +1 -1
- package/dist/artifacts.js +78 -138
- package/dist/artifacts.js.map +1 -1
- package/dist/books.d.ts +1 -1
- package/dist/books.d.ts.map +1 -1
- package/dist/books.js +18 -10
- 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 +9 -6
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +42 -51
- package/dist/context.js.map +1 -1
- package/dist/domain.d.ts +39 -2
- package/dist/domain.d.ts.map +1 -1
- package/dist/domain.js +846 -64
- 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 +171 -86
- package/dist/engine-types.d.ts.map +1 -1
- package/dist/engine-types.js +32 -0
- package/dist/engine-types.js.map +1 -1
- package/dist/engine.d.ts +89 -26
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +27 -21
- package/dist/engine.js.map +1 -1
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +32 -28
- package/dist/files.js.map +1 -1
- package/dist/fork.d.ts +148 -0
- package/dist/fork.d.ts.map +1 -0
- package/dist/fork.js +602 -0
- package/dist/fork.js.map +1 -0
- package/dist/generations.d.ts +9 -0
- package/dist/generations.d.ts.map +1 -0
- package/dist/generations.js +175 -0
- package/dist/generations.js.map +1 -0
- package/dist/history.d.ts +2 -5
- package/dist/history.d.ts.map +1 -1
- package/dist/history.js +183 -640
- package/dist/history.js.map +1 -1
- package/dist/index.d.ts +21 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -5
- 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 +3 -3
- package/dist/media.d.ts.map +1 -1
- package/dist/media.js +6 -5
- package/dist/media.js.map +1 -1
- package/dist/merge-policy.d.ts +83 -0
- package/dist/merge-policy.d.ts.map +1 -0
- package/dist/merge-policy.js +203 -0
- package/dist/merge-policy.js.map +1 -0
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +17 -23
- 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 +564 -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 +85 -8
- package/dist/notebook/types.d.ts.map +1 -1
- package/dist/notebook-grid.d.ts +37 -0
- package/dist/notebook-grid.d.ts.map +1 -0
- package/dist/notebook-grid.js +160 -0
- package/dist/notebook-grid.js.map +1 -0
- package/dist/notebook-media-artifact.d.ts +23 -0
- package/dist/notebook-media-artifact.d.ts.map +1 -0
- package/dist/notebook-media-artifact.js +67 -0
- package/dist/notebook-media-artifact.js.map +1 -0
- package/dist/notebook-mentions.d.ts +20 -0
- package/dist/notebook-mentions.d.ts.map +1 -0
- package/dist/notebook-mentions.js +53 -0
- package/dist/notebook-mentions.js.map +1 -0
- 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 +8 -11
- package/dist/runtime-services.js.map +1 -1
- package/dist/schema.d.ts +8 -5
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +624 -141
- 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 +10 -10
- package/dist/similarity.js.map +1 -1
- package/dist/status.js +8 -10
- package/dist/status.js.map +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 +64 -3
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +354 -68
- 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 +1551 -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 +345 -110
- 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 +42 -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/resolver.d.ts +0 -15
- package/dist/resolver.d.ts.map +0 -1
- package/dist/resolver.js +0 -81
- package/dist/resolver.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,12 @@
|
|
|
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";
|
|
4
|
-
import {
|
|
5
|
+
import { LEGACY_NOTEBOOK_CELL_TYPE_ALIASES, NOTEBOOK_CELL_TYPES, } from "./schema.js";
|
|
6
|
+
import { firstEmptyNotebookGridSlots, isNotebookGridSlot, } from "./notebook-grid.js";
|
|
7
|
+
import { canonicalJson, commitDateIso, parseCommitMessage, parseJson, } from "./store.js";
|
|
5
8
|
import { EngineFault } from "./store.js";
|
|
9
|
+
const NOTEBOOK_CELL_TYPE_SET = new Set(NOTEBOOK_CELL_TYPES);
|
|
6
10
|
export function createEntitiesApi(context) {
|
|
7
11
|
return {
|
|
8
12
|
create: (type, name, input = {}) => createEntity(context, type, name, input),
|
|
@@ -25,7 +29,13 @@ export function createNotebooksApi(context) {
|
|
|
25
29
|
}),
|
|
26
30
|
list: () => listNotebooks(context),
|
|
27
31
|
read: (notebookId) => syncResultOf(() => requiredNotebook(context, notebookId)),
|
|
28
|
-
write: (notebook) => writeNotebook(context, notebook),
|
|
32
|
+
write: (notebook, options) => writeNotebook(context, notebook, options),
|
|
33
|
+
cellHistory: (notebookId, cellId, options) => cellHistory(context, notebookId, cellId, options),
|
|
34
|
+
executionHistory: (notebookId, cellId, options) => executionHistory(context, notebookId, cellId, options),
|
|
35
|
+
insertCell: (notebookId, cell) => insertNotebookCell(context, notebookId, cell),
|
|
36
|
+
updateCell: (notebookId, cell) => updateNotebookCell(context, notebookId, cell),
|
|
37
|
+
moveCell: (notebookId, cellId, slot) => moveNotebookCell(context, notebookId, cellId, slot),
|
|
38
|
+
removeCell: (notebookId, cellId) => removeNotebookCell(context, notebookId, cellId),
|
|
29
39
|
delete: (notebookId) => deleteNotebook(context, notebookId),
|
|
30
40
|
recordRun: (run) => recordNotebookRun(context, run),
|
|
31
41
|
};
|
|
@@ -37,9 +47,10 @@ async function createEntity(context, type, name, input) {
|
|
|
37
47
|
const entityId = newUuidV7();
|
|
38
48
|
const mutation = await context.store.semantic({
|
|
39
49
|
operation: "create_entity",
|
|
50
|
+
tables: ["entities"],
|
|
40
51
|
details: { entityId, type, name: normalizedName },
|
|
41
52
|
writeSet: [`entity:${entityId}`],
|
|
42
|
-
},
|
|
53
|
+
}, (_operationId, now) => {
|
|
43
54
|
context.store.db
|
|
44
55
|
.prepare(`INSERT INTO entities(
|
|
45
56
|
entity_id, type, name, description, prompt, data_json, created_at
|
|
@@ -70,9 +81,10 @@ async function writeEntity(context, entity) {
|
|
|
70
81
|
const name = requiredText(entity.name, "Entity name");
|
|
71
82
|
const mutation = await context.store.semantic({
|
|
72
83
|
operation: "write_entity",
|
|
84
|
+
tables: ["entities"],
|
|
73
85
|
details: { entityId: entity.id, type: entity.type },
|
|
74
86
|
writeSet: [`entity:${entity.id}`],
|
|
75
|
-
},
|
|
87
|
+
}, () => {
|
|
76
88
|
context.store.db
|
|
77
89
|
.prepare(`UPDATE entities
|
|
78
90
|
SET type=?, name=?, description=?, prompt=?, data_json=?
|
|
@@ -88,7 +100,7 @@ async function deleteEntity(context, entityId) {
|
|
|
88
100
|
requiredEntity(context, entityId);
|
|
89
101
|
const cells = context.store.db
|
|
90
102
|
.prepare(`SELECT notebook_id, cell_id FROM cells
|
|
91
|
-
WHERE
|
|
103
|
+
WHERE output_entity_id=? ORDER BY notebook_id, cell_id`)
|
|
92
104
|
.all(entityId);
|
|
93
105
|
if (cells.length > 0) {
|
|
94
106
|
throw new EngineFault({
|
|
@@ -96,7 +108,7 @@ async function deleteEntity(context, entityId) {
|
|
|
96
108
|
message: `Entity is still referenced: ${entityId}`,
|
|
97
109
|
details: {
|
|
98
110
|
references: cells.map((cell) => ({
|
|
99
|
-
kind: "cell.
|
|
111
|
+
kind: "cell.output_entity",
|
|
100
112
|
id: `${cell.notebook_id}/${cell.cell_id}`,
|
|
101
113
|
})),
|
|
102
114
|
},
|
|
@@ -104,9 +116,10 @@ async function deleteEntity(context, entityId) {
|
|
|
104
116
|
}
|
|
105
117
|
const mutation = await context.store.semantic({
|
|
106
118
|
operation: "delete_entity",
|
|
119
|
+
tables: ["entities"],
|
|
107
120
|
details: { entityId },
|
|
108
121
|
writeSet: [`entity:${entityId}`],
|
|
109
|
-
},
|
|
122
|
+
}, () => {
|
|
110
123
|
context.store.db
|
|
111
124
|
.prepare("DELETE FROM entities WHERE entity_id=?")
|
|
112
125
|
.run(entityId);
|
|
@@ -120,13 +133,14 @@ async function createNotebook(context, name) {
|
|
|
120
133
|
const notebookId = newUuidV7();
|
|
121
134
|
const mutation = await context.store.semantic({
|
|
122
135
|
operation: "create_notebook",
|
|
136
|
+
tables: ["notebooks"],
|
|
123
137
|
details: { notebookId, name: normalizedName },
|
|
124
138
|
writeSet: [`notebook:${notebookId}`],
|
|
125
|
-
},
|
|
139
|
+
}, (_operationId, now) => {
|
|
126
140
|
context.store.db
|
|
127
141
|
.prepare(`INSERT INTO notebooks(
|
|
128
|
-
notebook_id, name,
|
|
129
|
-
) VALUES (?, ?,
|
|
142
|
+
notebook_id, name, created_at
|
|
143
|
+
) VALUES (?, ?, ?)`)
|
|
130
144
|
.run(notebookId, normalizedName, now);
|
|
131
145
|
});
|
|
132
146
|
return ok(requiredNotebook(context, notebookId), mutation.revision);
|
|
@@ -138,38 +152,326 @@ function listNotebooks(context) {
|
|
|
138
152
|
.all();
|
|
139
153
|
return rows.map((row) => notebookFromRows(context, row));
|
|
140
154
|
}
|
|
141
|
-
async function writeNotebook(context, notebook) {
|
|
155
|
+
async function writeNotebook(context, notebook, options) {
|
|
142
156
|
return resultOf(async () => {
|
|
143
157
|
assertUuidV7(notebook.id, "Notebook ID");
|
|
144
158
|
requiredNotebook(context, notebook.id);
|
|
145
159
|
validateNotebook(context, notebook);
|
|
146
160
|
const mutation = await context.store.semantic({
|
|
147
|
-
operation: "write_notebook",
|
|
148
|
-
|
|
161
|
+
operation: options?.operation ?? "write_notebook",
|
|
162
|
+
tables: [
|
|
163
|
+
"notebooks",
|
|
164
|
+
"notebook_fields",
|
|
165
|
+
"cells",
|
|
166
|
+
"notebook_cell_executions",
|
|
167
|
+
"notebook_generation_plans",
|
|
168
|
+
"notebook_run_plans",
|
|
169
|
+
"edges",
|
|
170
|
+
"cell_references",
|
|
171
|
+
"pinned_search_results",
|
|
172
|
+
],
|
|
173
|
+
details: { notebookId: notebook.id, ...options?.details },
|
|
174
|
+
...(options?.artifactId ? { artifactId: options.artifactId } : {}),
|
|
149
175
|
writeSet: [
|
|
150
176
|
`notebook:${notebook.id}`,
|
|
151
177
|
...notebook.cells.map((cell) => `cell:${notebook.id}:${cell.id}`),
|
|
152
178
|
...notebook.edges.map((edge) => `edge:${notebook.id}:${edge.id}`),
|
|
153
179
|
],
|
|
154
|
-
},
|
|
180
|
+
}, () => {
|
|
155
181
|
context.store.db
|
|
156
|
-
.prepare(`UPDATE notebooks SET name
|
|
157
|
-
|
|
158
|
-
.run(requiredText(notebook.name, "Notebook name"), canonicalJson(notebook.properties ?? {}), notebook.id);
|
|
182
|
+
.prepare(`UPDATE notebooks SET name=? WHERE notebook_id=?`)
|
|
183
|
+
.run(requiredText(notebook.name, "Notebook name"), notebook.id);
|
|
159
184
|
synchronizeNotebookChildren(context, notebook);
|
|
185
|
+
synchronizeNotebookState(context, notebook);
|
|
186
|
+
});
|
|
187
|
+
return ok(revisionFor(context, mutation.revision), mutation.revision);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
function cellHistory(context, notebookId, cellId, options) {
|
|
191
|
+
assertUuidV7(notebookId, "Notebook ID");
|
|
192
|
+
assertUuidV7(cellId, "Cell ID");
|
|
193
|
+
const limit = Math.max(1, Math.min(options?.limit ?? 50, 500));
|
|
194
|
+
const commitMeta = commitMetaByHash(context);
|
|
195
|
+
let rows;
|
|
196
|
+
try {
|
|
197
|
+
rows = context.store.db
|
|
198
|
+
.prepare(`SELECT notebook_id, cell_id, type, label, grid_row, grid_column,
|
|
199
|
+
output_entity_id, prompt, provider, model, operation, tool,
|
|
200
|
+
inputs_json, output_artifact_id, commit_hash, committer, commit_date
|
|
201
|
+
FROM dolt_history_cells
|
|
202
|
+
WHERE notebook_id=? AND cell_id=?
|
|
203
|
+
ORDER BY commit_date DESC
|
|
204
|
+
LIMIT ?`)
|
|
205
|
+
.all(notebookId, cellId, limit);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
rows = context.store.db.doltHistoryOf("cells")
|
|
209
|
+
.filter((row) => row.notebook_id === notebookId && row.cell_id === cellId)
|
|
210
|
+
.sort((left, right) => right.commit_date.localeCompare(left.commit_date))
|
|
211
|
+
.slice(0, limit);
|
|
212
|
+
}
|
|
213
|
+
return rows.map((row) => {
|
|
214
|
+
const meta = commitMeta.get(row.commit_hash);
|
|
215
|
+
return {
|
|
216
|
+
commitHash: row.commit_hash,
|
|
217
|
+
commitDate: commitDateIso(row.commit_date),
|
|
218
|
+
committer: row.committer,
|
|
219
|
+
...(meta?.operation ? { operation: meta.operation } : {}),
|
|
220
|
+
...(meta?.operationId ? { operationId: meta.operationId } : {}),
|
|
221
|
+
...(meta?.artifactId ? { artifactId: meta.artifactId } : {}),
|
|
222
|
+
...(meta?.details ? { details: meta.details } : {}),
|
|
223
|
+
cell: rowToCell(row, [], []),
|
|
224
|
+
};
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
function executionHistory(context, notebookId, cellId, options) {
|
|
228
|
+
assertUuidV7(notebookId, "Notebook ID");
|
|
229
|
+
assertUuidV7(cellId, "Cell ID");
|
|
230
|
+
const limit = Math.max(1, Math.min(options?.limit ?? 50, 500));
|
|
231
|
+
const commitMeta = commitMetaByHash(context);
|
|
232
|
+
let rows;
|
|
233
|
+
try {
|
|
234
|
+
rows = context.store.db
|
|
235
|
+
.prepare(`SELECT notebook_id, cell_id, fingerprint, status, output_artifact_id,
|
|
236
|
+
provider_artifact_id, run_id, completed_at, started_at,
|
|
237
|
+
updated_at, tool, error, stale, fixture_baseline,
|
|
238
|
+
commit_hash, committer, commit_date
|
|
239
|
+
FROM dolt_history_notebook_cell_executions
|
|
240
|
+
WHERE notebook_id=? AND cell_id=?
|
|
241
|
+
ORDER BY commit_date DESC
|
|
242
|
+
LIMIT ?`)
|
|
243
|
+
.all(notebookId, cellId, limit);
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
rows = context.store.db.doltHistoryOf("notebook_cell_executions")
|
|
247
|
+
.filter((row) => row.notebook_id === notebookId && row.cell_id === cellId)
|
|
248
|
+
.sort((left, right) => right.commit_date.localeCompare(left.commit_date))
|
|
249
|
+
.slice(0, limit);
|
|
250
|
+
}
|
|
251
|
+
return rows.map((row) => {
|
|
252
|
+
const meta = commitMeta.get(row.commit_hash);
|
|
253
|
+
return {
|
|
254
|
+
commitHash: row.commit_hash,
|
|
255
|
+
commitDate: commitDateIso(row.commit_date),
|
|
256
|
+
committer: row.committer,
|
|
257
|
+
notebookId: row.notebook_id,
|
|
258
|
+
cellId: row.cell_id,
|
|
259
|
+
...(meta?.operation ? { operation: meta.operation } : {}),
|
|
260
|
+
...(meta?.operationId ? { operationId: meta.operationId } : {}),
|
|
261
|
+
...(meta?.artifactId ? { artifactId: meta.artifactId } : {}),
|
|
262
|
+
...(meta?.details ? { details: meta.details } : {}),
|
|
263
|
+
execution: executionFromRow(row),
|
|
264
|
+
};
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
function commitMetaByHash(context) {
|
|
268
|
+
const map = new Map();
|
|
269
|
+
for (const commit of context.store.db.doltLog()) {
|
|
270
|
+
const parsed = parseCommitMessage(commit.message);
|
|
271
|
+
if (!parsed)
|
|
272
|
+
continue;
|
|
273
|
+
map.set(commit.commit_hash, {
|
|
274
|
+
operation: parsed.operation,
|
|
275
|
+
operationId: parsed.operationId,
|
|
276
|
+
...(parsed.artifactId ? { artifactId: parsed.artifactId } : {}),
|
|
277
|
+
details: parsed.details,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
return map;
|
|
281
|
+
}
|
|
282
|
+
async function insertNotebookCell(context, notebookId, cell) {
|
|
283
|
+
return resultOf(async () => {
|
|
284
|
+
assertUuidV7(notebookId, "Notebook ID");
|
|
285
|
+
const notebook = requiredNotebook(context, notebookId);
|
|
286
|
+
if (notebook.cells.some((existing) => existing.id === cell.id)) {
|
|
287
|
+
throw new Error(`Cell already exists: ${cell.id}`);
|
|
288
|
+
}
|
|
289
|
+
const prospective = { ...notebook, cells: [...notebook.cells, cell] };
|
|
290
|
+
validateCellFields(context, prospective, cell);
|
|
291
|
+
assertCellSlotFree(context, notebookId, cell);
|
|
292
|
+
const mutation = await context.store.semantic({
|
|
293
|
+
operation: "insert_cell",
|
|
294
|
+
tables: ["cells", "cell_references", "pinned_search_results"],
|
|
295
|
+
details: { notebookId, cellId: cell.id },
|
|
296
|
+
writeSet: [`notebook:${notebookId}`, `cell:${notebookId}:${cell.id}`],
|
|
297
|
+
}, () => {
|
|
298
|
+
upsertNotebookCell(context, notebookId, cell);
|
|
299
|
+
repairCellSlots(context, notebookId, cell.id);
|
|
300
|
+
});
|
|
301
|
+
return ok(revisionFor(context, mutation.revision), mutation.revision);
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
async function updateNotebookCell(context, notebookId, cell) {
|
|
305
|
+
return resultOf(async () => {
|
|
306
|
+
assertUuidV7(notebookId, "Notebook ID");
|
|
307
|
+
const notebook = requiredNotebook(context, notebookId);
|
|
308
|
+
if (!notebook.cells.some((existing) => existing.id === cell.id)) {
|
|
309
|
+
throw new EngineFault({
|
|
310
|
+
code: "NOT_FOUND",
|
|
311
|
+
message: `Cell not found: ${cell.id}`,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
const prospective = {
|
|
315
|
+
...notebook,
|
|
316
|
+
cells: notebook.cells.map((existing) => existing.id === cell.id ? cell : existing),
|
|
317
|
+
};
|
|
318
|
+
validateCellFields(context, prospective, cell);
|
|
319
|
+
assertCellSlotFree(context, notebookId, cell);
|
|
320
|
+
const mutation = await context.store.semantic({
|
|
321
|
+
operation: "update_cell",
|
|
322
|
+
tables: ["cells", "cell_references", "pinned_search_results"],
|
|
323
|
+
details: { notebookId, cellId: cell.id },
|
|
324
|
+
writeSet: [`notebook:${notebookId}`, `cell:${notebookId}:${cell.id}`],
|
|
325
|
+
}, () => {
|
|
326
|
+
upsertNotebookCell(context, notebookId, cell);
|
|
327
|
+
repairCellSlots(context, notebookId, cell.id);
|
|
160
328
|
});
|
|
161
329
|
return ok(revisionFor(context, mutation.revision), mutation.revision);
|
|
162
330
|
});
|
|
163
331
|
}
|
|
332
|
+
async function moveNotebookCell(context, notebookId, cellId, slot) {
|
|
333
|
+
return resultOf(async () => {
|
|
334
|
+
assertUuidV7(notebookId, "Notebook ID");
|
|
335
|
+
assertUuidV7(cellId, "Cell ID");
|
|
336
|
+
const notebook = requiredNotebook(context, notebookId);
|
|
337
|
+
const existing = notebook.cells.find((cell) => cell.id === cellId);
|
|
338
|
+
if (!existing) {
|
|
339
|
+
throw new EngineFault({
|
|
340
|
+
code: "NOT_FOUND",
|
|
341
|
+
message: `Cell not found: ${cellId}`,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
return updateNotebookCell(context, notebookId, { ...existing, slot });
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
async function removeNotebookCell(context, notebookId, cellId) {
|
|
348
|
+
return resultOf(async () => {
|
|
349
|
+
assertUuidV7(notebookId, "Notebook ID");
|
|
350
|
+
assertUuidV7(cellId, "Cell ID");
|
|
351
|
+
requiredNotebook(context, notebookId);
|
|
352
|
+
const found = context.store.db
|
|
353
|
+
.prepare("SELECT 1 AS present FROM cells WHERE notebook_id=? AND cell_id=?")
|
|
354
|
+
.get(notebookId, cellId);
|
|
355
|
+
if (!found) {
|
|
356
|
+
throw new EngineFault({
|
|
357
|
+
code: "NOT_FOUND",
|
|
358
|
+
message: `Cell not found: ${cellId}`,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
const mutation = await context.store.semantic({
|
|
362
|
+
operation: "remove_cell",
|
|
363
|
+
tables: [
|
|
364
|
+
"cells",
|
|
365
|
+
"generations",
|
|
366
|
+
"notebook_cell_executions",
|
|
367
|
+
"notebook_generation_plans",
|
|
368
|
+
"edges",
|
|
369
|
+
"cell_references",
|
|
370
|
+
"pinned_search_results",
|
|
371
|
+
],
|
|
372
|
+
details: { notebookId, cellId },
|
|
373
|
+
writeSet: [`notebook:${notebookId}`, `cell:${notebookId}:${cellId}`],
|
|
374
|
+
}, () => {
|
|
375
|
+
context.store.db
|
|
376
|
+
.prepare("DELETE FROM cells WHERE notebook_id=? AND cell_id=?")
|
|
377
|
+
.run(notebookId, cellId);
|
|
378
|
+
repairCellSlots(context, notebookId);
|
|
379
|
+
});
|
|
380
|
+
return ok(revisionFor(context, mutation.revision), mutation.revision);
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
function assertCellSlotFree(context, notebookId, cell) {
|
|
384
|
+
const found = context.store.db
|
|
385
|
+
.prepare(`SELECT cell_id FROM cells
|
|
386
|
+
WHERE notebook_id=? AND grid_row=? AND grid_column=? AND cell_id<>?`)
|
|
387
|
+
.get(notebookId, cell.slot.row, cell.slot.column, cell.id);
|
|
388
|
+
if (found) {
|
|
389
|
+
throw new Error(`Cell slot is occupied: ${cell.slot.row}:${cell.slot.column}`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function repairCellSlots(context, notebookId, protectedCellId) {
|
|
393
|
+
const rows = context.store.db
|
|
394
|
+
.prepare(`SELECT cell_id, grid_row, grid_column FROM cells
|
|
395
|
+
WHERE notebook_id=?
|
|
396
|
+
ORDER BY grid_row, grid_column, cell_id`)
|
|
397
|
+
.all(notebookId);
|
|
398
|
+
const slots = new Map();
|
|
399
|
+
for (const row of rows) {
|
|
400
|
+
const key = `${row.grid_row}:${row.grid_column}`;
|
|
401
|
+
const group = slots.get(key) ?? [];
|
|
402
|
+
group.push(row);
|
|
403
|
+
slots.set(key, group);
|
|
404
|
+
}
|
|
405
|
+
const occupied = [];
|
|
406
|
+
const losers = [];
|
|
407
|
+
for (const group of slots.values()) {
|
|
408
|
+
const winner = protectedCellId && group.some((row) => row.cell_id === protectedCellId)
|
|
409
|
+
? protectedCellId
|
|
410
|
+
: group[0].cell_id;
|
|
411
|
+
const winnerRow = group.find((row) => row.cell_id === winner);
|
|
412
|
+
occupied.push({
|
|
413
|
+
row: winnerRow.grid_row,
|
|
414
|
+
column: winnerRow.grid_column,
|
|
415
|
+
});
|
|
416
|
+
losers.push(...group
|
|
417
|
+
.filter((row) => row.cell_id !== winner)
|
|
418
|
+
.map((row) => row.cell_id));
|
|
419
|
+
}
|
|
420
|
+
const destinations = firstEmptyNotebookGridSlots(occupied, losers.length);
|
|
421
|
+
const move = context.store.db.prepare(`UPDATE cells SET grid_row=?, grid_column=?
|
|
422
|
+
WHERE notebook_id=? AND cell_id=?`);
|
|
423
|
+
for (const [index, cellId] of losers.entries()) {
|
|
424
|
+
const slot = destinations[index];
|
|
425
|
+
move.run(slot.row, slot.column, notebookId, cellId);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function upsertNotebookCell(context, notebookId, cell) {
|
|
429
|
+
const normalized = normalizeCellForWrite(cell);
|
|
430
|
+
context.store.db
|
|
431
|
+
.prepare(`INSERT INTO cells(
|
|
432
|
+
notebook_id, cell_id, type, label, grid_row, grid_column,
|
|
433
|
+
output_entity_id, prompt, provider, model, operation, tool,
|
|
434
|
+
inputs_json, output_artifact_id
|
|
435
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
436
|
+
ON CONFLICT(notebook_id, cell_id) DO UPDATE SET
|
|
437
|
+
type=excluded.type,
|
|
438
|
+
label=excluded.label,
|
|
439
|
+
grid_row=excluded.grid_row,
|
|
440
|
+
grid_column=excluded.grid_column,
|
|
441
|
+
output_entity_id=excluded.output_entity_id,
|
|
442
|
+
prompt=excluded.prompt,
|
|
443
|
+
provider=excluded.provider,
|
|
444
|
+
model=excluded.model,
|
|
445
|
+
operation=excluded.operation,
|
|
446
|
+
tool=excluded.tool,
|
|
447
|
+
inputs_json=excluded.inputs_json,
|
|
448
|
+
output_artifact_id=excluded.output_artifact_id`)
|
|
449
|
+
.run(notebookId, normalized.id, normalized.type, normalized.label?.trim() || null, 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);
|
|
450
|
+
synchronizeCellReferences(context, notebookId, normalized);
|
|
451
|
+
synchronizePinnedResults(context, notebookId, normalized);
|
|
452
|
+
}
|
|
164
453
|
async function deleteNotebook(context, notebookId) {
|
|
165
454
|
return resultOf(async () => {
|
|
166
455
|
assertUuidV7(notebookId, "Notebook ID");
|
|
167
456
|
requiredNotebook(context, notebookId);
|
|
168
457
|
const mutation = await context.store.semantic({
|
|
169
458
|
operation: "delete_notebook",
|
|
459
|
+
tables: [
|
|
460
|
+
"notebooks",
|
|
461
|
+
"notebook_fields",
|
|
462
|
+
"cells",
|
|
463
|
+
"generations",
|
|
464
|
+
"notebook_cell_executions",
|
|
465
|
+
"notebook_generation_plans",
|
|
466
|
+
"notebook_run_plans",
|
|
467
|
+
"edges",
|
|
468
|
+
"runs",
|
|
469
|
+
"cell_references",
|
|
470
|
+
"pinned_search_results",
|
|
471
|
+
],
|
|
170
472
|
details: { notebookId },
|
|
171
473
|
writeSet: [`notebook:${notebookId}`],
|
|
172
|
-
},
|
|
474
|
+
}, () => {
|
|
173
475
|
context.store.db
|
|
174
476
|
.prepare("DELETE FROM notebooks WHERE notebook_id=?")
|
|
175
477
|
.run(notebookId);
|
|
@@ -190,9 +492,10 @@ async function recordNotebookRun(context, run) {
|
|
|
190
492
|
}
|
|
191
493
|
const mutation = await context.store.semantic({
|
|
192
494
|
operation: "record_notebook_run",
|
|
495
|
+
tables: ["runs"],
|
|
193
496
|
details: { notebookId: run.notebookId, runId: run.id },
|
|
194
497
|
writeSet: [`run:${run.id}`],
|
|
195
|
-
},
|
|
498
|
+
}, () => {
|
|
196
499
|
context.store.db
|
|
197
500
|
.prepare(`INSERT INTO runs(
|
|
198
501
|
run_id, notebook_id, status, started_at,
|
|
@@ -231,47 +534,104 @@ function requiredNotebook(context, notebookId) {
|
|
|
231
534
|
return notebookFromRows(context, row);
|
|
232
535
|
}
|
|
233
536
|
function notebookFromRows(context, row) {
|
|
537
|
+
const fields = context.store.db
|
|
538
|
+
.prepare(`SELECT field, value_json
|
|
539
|
+
FROM notebook_fields WHERE notebook_id=?
|
|
540
|
+
ORDER BY field`)
|
|
541
|
+
.all(row.notebook_id);
|
|
234
542
|
const cells = context.store.db
|
|
235
|
-
.prepare(`SELECT cell_id, type,
|
|
236
|
-
prompt, model,
|
|
237
|
-
|
|
543
|
+
.prepare(`SELECT cell_id, type, label, grid_row, grid_column, output_entity_id,
|
|
544
|
+
prompt, provider, model, operation, tool,
|
|
545
|
+
inputs_json, output_artifact_id
|
|
546
|
+
FROM cells WHERE notebook_id=?
|
|
547
|
+
ORDER BY grid_row, grid_column, cell_id`)
|
|
238
548
|
.all(row.notebook_id);
|
|
239
549
|
const edges = context.store.db
|
|
240
550
|
.prepare(`SELECT edge_id, source_cell_id, target_cell_id, target_input
|
|
241
551
|
FROM edges WHERE notebook_id=? ORDER BY edge_id`)
|
|
242
552
|
.all(row.notebook_id);
|
|
553
|
+
const references = context.store.db
|
|
554
|
+
.prepare(`SELECT cell_id, reference_id, kind, target_id, snapshot_json, ordinal
|
|
555
|
+
FROM cell_references
|
|
556
|
+
WHERE notebook_id=?
|
|
557
|
+
ORDER BY cell_id, ordinal, reference_id`)
|
|
558
|
+
.all(row.notebook_id);
|
|
559
|
+
const pinnedResults = context.store.db
|
|
560
|
+
.prepare(`SELECT cell_id, result_id, artifact_id, object_hash, location_json,
|
|
561
|
+
representative_json, query_json, signals_json,
|
|
562
|
+
selected_revision, ordinal, created_at
|
|
563
|
+
FROM pinned_search_results
|
|
564
|
+
WHERE notebook_id=?
|
|
565
|
+
ORDER BY cell_id, ordinal, result_id`)
|
|
566
|
+
.all(row.notebook_id);
|
|
567
|
+
const executions = context.store.db
|
|
568
|
+
.prepare(`SELECT cell_id, fingerprint, status, output_artifact_id,
|
|
569
|
+
provider_artifact_id, run_id, completed_at, started_at,
|
|
570
|
+
updated_at, tool, error, stale, fixture_baseline
|
|
571
|
+
FROM notebook_cell_executions
|
|
572
|
+
WHERE notebook_id=? ORDER BY cell_id`)
|
|
573
|
+
.all(row.notebook_id);
|
|
574
|
+
const generationPlans = context.store.db
|
|
575
|
+
.prepare(`SELECT plan_id, cell_id, status, plan_json, output_artifact_id,
|
|
576
|
+
error, created_at, updated_at
|
|
577
|
+
FROM notebook_generation_plans
|
|
578
|
+
WHERE notebook_id=? ORDER BY created_at, plan_id`)
|
|
579
|
+
.all(row.notebook_id);
|
|
580
|
+
const runPlans = context.store.db
|
|
581
|
+
.prepare(`SELECT plan_id, status, plan_json, paid_cell_ids_json,
|
|
582
|
+
cell_fingerprints_json, known_cost_usd, unknown_cost_count,
|
|
583
|
+
created_at, updated_at, run_id, outputs_json, error
|
|
584
|
+
FROM notebook_run_plans
|
|
585
|
+
WHERE notebook_id=? ORDER BY created_at, plan_id`)
|
|
586
|
+
.all(row.notebook_id);
|
|
587
|
+
const field = new Map(fields.map((item) => [
|
|
588
|
+
item.field,
|
|
589
|
+
parseJson(item.value_json, null),
|
|
590
|
+
]));
|
|
243
591
|
return {
|
|
244
592
|
id: row.notebook_id,
|
|
245
593
|
name: row.name,
|
|
246
|
-
|
|
247
|
-
|
|
594
|
+
...(typeof field.get("description") === "string"
|
|
595
|
+
? { description: field.get("description") }
|
|
596
|
+
: {}),
|
|
597
|
+
...(typeof field.get("lifecycle_state") === "string"
|
|
598
|
+
? { lifecycleState: field.get("lifecycle_state") }
|
|
599
|
+
: {}),
|
|
600
|
+
...(typeof field.get("workflow_version") === "number"
|
|
601
|
+
? { workflowVersion: field.get("workflow_version") }
|
|
602
|
+
: {}),
|
|
603
|
+
...(typeof field.get("analysis_revision") === "string"
|
|
604
|
+
? { analysisRevision: field.get("analysis_revision") }
|
|
605
|
+
: {}),
|
|
606
|
+
...(isRecord(field.get("fixture"))
|
|
607
|
+
? { fixture: field.get("fixture") }
|
|
608
|
+
: {}),
|
|
609
|
+
execution: Object.fromEntries(executions.map((execution) => [
|
|
610
|
+
execution.cell_id,
|
|
611
|
+
executionFromRow(execution),
|
|
612
|
+
])),
|
|
613
|
+
generationPlans: generationPlans.map(generationPlanFromRow),
|
|
614
|
+
notebookRunPlans: runPlans.map(runPlanFromRow),
|
|
615
|
+
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
616
|
edges: edges.map(rowToEdge),
|
|
249
617
|
createdAt: new Date(row.created_at).toISOString(),
|
|
250
618
|
};
|
|
251
619
|
}
|
|
252
620
|
function validateNotebook(context, notebook) {
|
|
253
621
|
const cellIds = new Set();
|
|
622
|
+
const occupiedSlots = new Set();
|
|
254
623
|
for (const cell of notebook.cells) {
|
|
255
|
-
assertUuidV7(cell.id, "Cell ID");
|
|
256
624
|
if (cellIds.has(cell.id))
|
|
257
625
|
throw new Error(`Duplicate cell ID: ${cell.id}`);
|
|
258
626
|
cellIds.add(cell.id);
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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);
|
|
272
|
-
}
|
|
627
|
+
const slot = `${cell.slot.row}:${cell.slot.column}`;
|
|
628
|
+
if (occupiedSlots.has(slot))
|
|
629
|
+
throw new Error(`Duplicate cell slot: ${slot}`);
|
|
630
|
+
occupiedSlots.add(slot);
|
|
631
|
+
validateCellFields(context, notebook, cell);
|
|
273
632
|
}
|
|
274
633
|
const edgeIds = new Set();
|
|
634
|
+
const occupiedInputs = new Set();
|
|
275
635
|
for (const edge of notebook.edges) {
|
|
276
636
|
assertUuidV7(edge.id, "Edge ID");
|
|
277
637
|
if (edgeIds.has(edge.id))
|
|
@@ -280,29 +640,40 @@ function validateNotebook(context, notebook) {
|
|
|
280
640
|
if (!cellIds.has(edge.source) || !cellIds.has(edge.target)) {
|
|
281
641
|
throw new Error(`Edge ${edge.id} must reference cells in the notebook`);
|
|
282
642
|
}
|
|
283
|
-
requiredText(edge.targetInput, "Edge targetInput");
|
|
643
|
+
const targetInput = requiredText(edge.targetInput, "Edge targetInput");
|
|
644
|
+
const inputKey = `${edge.target}:${targetInput}`;
|
|
645
|
+
if (occupiedInputs.has(inputKey)) {
|
|
646
|
+
throw new Error(`Duplicate target input: ${edge.target} ${targetInput}`);
|
|
647
|
+
}
|
|
648
|
+
occupiedInputs.add(inputKey);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
function validateCellFields(context, notebook, cell) {
|
|
652
|
+
assertUuidV7(cell.id, "Cell ID");
|
|
653
|
+
if (!NOTEBOOK_CELL_TYPE_SET.has(cell.type)) {
|
|
654
|
+
throw new Error(`Invalid cell type: ${cell.type}`);
|
|
655
|
+
}
|
|
656
|
+
if (!isNotebookGridSlot(cell.slot)) {
|
|
657
|
+
throw new Error(`Cell slot must be within @a1-@z13: ${cell.id}`);
|
|
284
658
|
}
|
|
659
|
+
if (cell.outputEntityId) {
|
|
660
|
+
assertUuidV7(cell.outputEntityId, "Cell output entity ID");
|
|
661
|
+
requiredEntity(context, cell.outputEntityId);
|
|
662
|
+
}
|
|
663
|
+
if (cell.outputArtifactId) {
|
|
664
|
+
assertUuidV7(cell.outputArtifactId, "Cell output artifact ID");
|
|
665
|
+
context.artifactRowById(cell.outputArtifactId);
|
|
666
|
+
}
|
|
667
|
+
validateCellReferences(context, notebook, cell);
|
|
668
|
+
validatePinnedResults(context, cell);
|
|
285
669
|
}
|
|
286
670
|
function synchronizeNotebookChildren(context, notebook) {
|
|
287
671
|
deleteMissing(context, "edges", "edge_id", notebook.id, notebook.edges.map((edge) => edge.id));
|
|
288
672
|
deleteMissing(context, "cells", "cell_id", notebook.id, notebook.cells.map((cell) => cell.id));
|
|
289
|
-
const upsertCell = context.store.db.prepare(`INSERT INTO cells(
|
|
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`);
|
|
303
673
|
for (const cell of notebook.cells) {
|
|
304
|
-
|
|
674
|
+
upsertNotebookCell(context, notebook.id, cell);
|
|
305
675
|
}
|
|
676
|
+
repairCellSlots(context, notebook.id);
|
|
306
677
|
const upsertEdge = context.store.db.prepare(`INSERT INTO edges(
|
|
307
678
|
notebook_id, edge_id, source_cell_id, target_cell_id, target_input
|
|
308
679
|
) VALUES (?, ?, ?, ?, ?)
|
|
@@ -314,6 +685,132 @@ function synchronizeNotebookChildren(context, notebook) {
|
|
|
314
685
|
upsertEdge.run(notebook.id, edge.id, edge.source, edge.target, edge.targetInput);
|
|
315
686
|
}
|
|
316
687
|
}
|
|
688
|
+
function synchronizeNotebookState(context, notebook) {
|
|
689
|
+
synchronizeNotebookFields(context, notebook);
|
|
690
|
+
synchronizeCellExecutions(context, notebook);
|
|
691
|
+
synchronizeGenerationPlans(context, notebook);
|
|
692
|
+
synchronizeRunPlans(context, notebook);
|
|
693
|
+
}
|
|
694
|
+
function synchronizeNotebookFields(context, notebook) {
|
|
695
|
+
const values = new Map();
|
|
696
|
+
if (notebook.description !== undefined) {
|
|
697
|
+
values.set("description", notebook.description);
|
|
698
|
+
}
|
|
699
|
+
if (notebook.lifecycleState !== undefined) {
|
|
700
|
+
values.set("lifecycle_state", notebook.lifecycleState);
|
|
701
|
+
}
|
|
702
|
+
if (notebook.workflowVersion !== undefined) {
|
|
703
|
+
if (!Number.isSafeInteger(notebook.workflowVersion)) {
|
|
704
|
+
throw new Error("Notebook workflow version must be an integer");
|
|
705
|
+
}
|
|
706
|
+
values.set("workflow_version", notebook.workflowVersion);
|
|
707
|
+
}
|
|
708
|
+
if (notebook.analysisRevision !== undefined) {
|
|
709
|
+
values.set("analysis_revision", notebook.analysisRevision);
|
|
710
|
+
}
|
|
711
|
+
if (notebook.fixture !== undefined) {
|
|
712
|
+
values.set("fixture", notebook.fixture);
|
|
713
|
+
}
|
|
714
|
+
deleteMissingNotebookRows(context, "notebook_fields", "field", notebook.id, [
|
|
715
|
+
...values.keys(),
|
|
716
|
+
]);
|
|
717
|
+
const upsert = context.store.db.prepare(`INSERT INTO notebook_fields(notebook_id, field, value_json)
|
|
718
|
+
VALUES (?, ?, ?)
|
|
719
|
+
ON CONFLICT(notebook_id, field) DO UPDATE SET
|
|
720
|
+
value_json=excluded.value_json`);
|
|
721
|
+
for (const [field, value] of values) {
|
|
722
|
+
upsert.run(notebook.id, field, canonicalJson(value));
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
function synchronizeCellExecutions(context, notebook) {
|
|
726
|
+
const entries = Object.entries(notebook.execution ?? {});
|
|
727
|
+
const cellIds = new Set(notebook.cells.map((cell) => cell.id));
|
|
728
|
+
for (const [cellId] of entries) {
|
|
729
|
+
if (!cellIds.has(cellId)) {
|
|
730
|
+
throw new Error(`Notebook execution references missing cell: ${cellId}`);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
deleteMissingNotebookRows(context, "notebook_cell_executions", "cell_id", notebook.id, entries.map(([cellId]) => cellId));
|
|
734
|
+
const upsert = context.store.db.prepare(`INSERT INTO notebook_cell_executions(
|
|
735
|
+
notebook_id, cell_id, fingerprint, status, output_artifact_id,
|
|
736
|
+
provider_artifact_id, run_id, completed_at, started_at, updated_at,
|
|
737
|
+
tool, error, stale, fixture_baseline
|
|
738
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
739
|
+
ON CONFLICT(notebook_id, cell_id) DO UPDATE SET
|
|
740
|
+
fingerprint=excluded.fingerprint,
|
|
741
|
+
status=excluded.status,
|
|
742
|
+
output_artifact_id=excluded.output_artifact_id,
|
|
743
|
+
provider_artifact_id=excluded.provider_artifact_id,
|
|
744
|
+
run_id=excluded.run_id,
|
|
745
|
+
completed_at=excluded.completed_at,
|
|
746
|
+
started_at=excluded.started_at,
|
|
747
|
+
updated_at=excluded.updated_at,
|
|
748
|
+
tool=excluded.tool,
|
|
749
|
+
error=excluded.error,
|
|
750
|
+
stale=excluded.stale,
|
|
751
|
+
fixture_baseline=excluded.fixture_baseline`);
|
|
752
|
+
for (const [cellId, execution] of entries) {
|
|
753
|
+
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);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
function synchronizeGenerationPlans(context, notebook) {
|
|
757
|
+
const plans = notebook.generationPlans ?? [];
|
|
758
|
+
assertUnique(plans.map((plan) => plan.planId), "generation plan ID");
|
|
759
|
+
deleteMissingNotebookRows(context, "notebook_generation_plans", "plan_id", notebook.id, plans.map((plan) => plan.planId));
|
|
760
|
+
const upsert = context.store.db.prepare(`INSERT INTO notebook_generation_plans(
|
|
761
|
+
notebook_id, plan_id, cell_id, status, plan_json,
|
|
762
|
+
output_artifact_id, error, created_at, updated_at
|
|
763
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
764
|
+
ON CONFLICT(notebook_id, plan_id) DO UPDATE SET
|
|
765
|
+
cell_id=excluded.cell_id,
|
|
766
|
+
status=excluded.status,
|
|
767
|
+
plan_json=excluded.plan_json,
|
|
768
|
+
output_artifact_id=excluded.output_artifact_id,
|
|
769
|
+
error=excluded.error,
|
|
770
|
+
created_at=excluded.created_at,
|
|
771
|
+
updated_at=excluded.updated_at`);
|
|
772
|
+
for (const plan of plans) {
|
|
773
|
+
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"));
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
function synchronizeRunPlans(context, notebook) {
|
|
777
|
+
const plans = notebook.notebookRunPlans ?? [];
|
|
778
|
+
assertUnique(plans.map((plan) => plan.planId), "notebook run plan ID");
|
|
779
|
+
deleteMissingNotebookRows(context, "notebook_run_plans", "plan_id", notebook.id, plans.map((plan) => plan.planId));
|
|
780
|
+
const upsert = context.store.db.prepare(`INSERT INTO notebook_run_plans(
|
|
781
|
+
notebook_id, plan_id, status, plan_json, paid_cell_ids_json,
|
|
782
|
+
cell_fingerprints_json, known_cost_usd, unknown_cost_count,
|
|
783
|
+
created_at, updated_at, run_id, outputs_json, error
|
|
784
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
785
|
+
ON CONFLICT(notebook_id, plan_id) DO UPDATE SET
|
|
786
|
+
status=excluded.status,
|
|
787
|
+
plan_json=excluded.plan_json,
|
|
788
|
+
paid_cell_ids_json=excluded.paid_cell_ids_json,
|
|
789
|
+
cell_fingerprints_json=excluded.cell_fingerprints_json,
|
|
790
|
+
known_cost_usd=excluded.known_cost_usd,
|
|
791
|
+
unknown_cost_count=excluded.unknown_cost_count,
|
|
792
|
+
created_at=excluded.created_at,
|
|
793
|
+
updated_at=excluded.updated_at,
|
|
794
|
+
run_id=excluded.run_id,
|
|
795
|
+
outputs_json=excluded.outputs_json,
|
|
796
|
+
error=excluded.error`);
|
|
797
|
+
for (const plan of plans) {
|
|
798
|
+
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);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
function deleteMissingNotebookRows(context, table, idColumn, notebookId, ids) {
|
|
802
|
+
if (ids.length === 0) {
|
|
803
|
+
context.store.db
|
|
804
|
+
.prepare(`DELETE FROM ${table} WHERE notebook_id=?`)
|
|
805
|
+
.run(notebookId);
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
809
|
+
context.store.db
|
|
810
|
+
.prepare(`DELETE FROM ${table}
|
|
811
|
+
WHERE notebook_id=? AND ${idColumn} NOT IN (${placeholders})`)
|
|
812
|
+
.run(notebookId, ...ids);
|
|
813
|
+
}
|
|
317
814
|
function deleteMissing(context, table, idColumn, notebookId, ids) {
|
|
318
815
|
if (ids.length === 0) {
|
|
319
816
|
context.store.db
|
|
@@ -327,21 +824,231 @@ function deleteMissing(context, table, idColumn, notebookId, ids) {
|
|
|
327
824
|
WHERE notebook_id=? AND ${idColumn} NOT IN (${placeholders})`)
|
|
328
825
|
.run(notebookId, ...ids);
|
|
329
826
|
}
|
|
330
|
-
function
|
|
827
|
+
function validateCellReferences(context, notebook, cell) {
|
|
828
|
+
const ids = new Set();
|
|
829
|
+
const ordinals = new Set();
|
|
830
|
+
for (const reference of cell.references ?? []) {
|
|
831
|
+
assertUuidV7(reference.id, "Cell reference ID");
|
|
832
|
+
if (ids.has(reference.id)) {
|
|
833
|
+
throw new Error(`Duplicate cell reference ID: ${reference.id}`);
|
|
834
|
+
}
|
|
835
|
+
ids.add(reference.id);
|
|
836
|
+
validateOrdinal(reference.ordinal, "Cell reference ordinal");
|
|
837
|
+
if (ordinals.has(reference.ordinal)) {
|
|
838
|
+
throw new Error(`Duplicate cell reference ordinal: ${reference.ordinal}`);
|
|
839
|
+
}
|
|
840
|
+
ordinals.add(reference.ordinal);
|
|
841
|
+
requiredText(reference.targetId, "Cell reference target ID");
|
|
842
|
+
assertReferenceTarget(context, notebook, reference);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
function assertReferenceTarget(context, notebook, reference) {
|
|
846
|
+
if (reference.kind === "artifact") {
|
|
847
|
+
context.artifactRowById(reference.targetId);
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
if (reference.kind === "cell-output") {
|
|
851
|
+
if (!notebook.cells.some((cell) => cell.id === reference.targetId)) {
|
|
852
|
+
throw new Error(`Cell output target not found: ${reference.targetId}`);
|
|
853
|
+
}
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
const table = reference.kind === "transcript"
|
|
857
|
+
? "transcripts"
|
|
858
|
+
: reference.kind === "sequence"
|
|
859
|
+
? "sequences"
|
|
860
|
+
: "artifact_streams";
|
|
861
|
+
const column = reference.kind === "transcript"
|
|
862
|
+
? "transcript_id"
|
|
863
|
+
: reference.kind === "sequence"
|
|
864
|
+
? "sequence_id"
|
|
865
|
+
: "stream_id";
|
|
866
|
+
const found = context.store.db
|
|
867
|
+
.prepare(`SELECT 1 AS present FROM ${table} WHERE ${column}=?`)
|
|
868
|
+
.get(reference.targetId);
|
|
869
|
+
if (!found) {
|
|
870
|
+
throw new Error(`${reference.kind} target not found: ${reference.targetId}`);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
function validatePinnedResults(context, cell) {
|
|
874
|
+
const ids = new Set();
|
|
875
|
+
const ordinals = new Set();
|
|
876
|
+
for (const result of cell.pinnedResults ?? []) {
|
|
877
|
+
assertUuidV7(result.id, "Pinned search result ID");
|
|
878
|
+
if (ids.has(result.id)) {
|
|
879
|
+
throw new Error(`Duplicate pinned search result ID: ${result.id}`);
|
|
880
|
+
}
|
|
881
|
+
ids.add(result.id);
|
|
882
|
+
validateOrdinal(result.ordinal, "Pinned search result ordinal");
|
|
883
|
+
if (ordinals.has(result.ordinal)) {
|
|
884
|
+
throw new Error(`Duplicate pinned search result ordinal: ${result.ordinal}`);
|
|
885
|
+
}
|
|
886
|
+
ordinals.add(result.ordinal);
|
|
887
|
+
context.artifactRowById(result.artifactId);
|
|
888
|
+
requiredText(result.objectHash, "Pinned search result object hash");
|
|
889
|
+
normalizeSearchLocation(result.location);
|
|
890
|
+
requiredText(result.selectedRevision, "Pinned search result revision");
|
|
891
|
+
if (!Number.isSafeInteger(result.createdAt) || result.createdAt < 0) {
|
|
892
|
+
throw new Error("Pinned search result createdAt must be a positive integer");
|
|
893
|
+
}
|
|
894
|
+
const object = context.store.db
|
|
895
|
+
.prepare("SELECT 1 AS present FROM objects WHERE object_hash=?")
|
|
896
|
+
.get(result.objectHash);
|
|
897
|
+
if (!object) {
|
|
898
|
+
throw new Error(`Pinned search result object not found: ${result.objectHash}`);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
function validateOrdinal(value, label) {
|
|
903
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
904
|
+
throw new Error(`${label} must be a non-negative safe integer`);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
function synchronizeCellReferences(context, notebookId, cell) {
|
|
908
|
+
const references = cell.references ?? [];
|
|
909
|
+
deleteMissingChildren(context, "cell_references", "reference_id", notebookId, cell.id, references.map((reference) => reference.id));
|
|
910
|
+
const upsert = context.store.db.prepare(`INSERT INTO cell_references(
|
|
911
|
+
notebook_id, cell_id, reference_id, kind,
|
|
912
|
+
target_id, snapshot_json, ordinal
|
|
913
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
914
|
+
ON CONFLICT(notebook_id, cell_id, reference_id) DO UPDATE SET
|
|
915
|
+
kind=excluded.kind,
|
|
916
|
+
target_id=excluded.target_id,
|
|
917
|
+
snapshot_json=excluded.snapshot_json,
|
|
918
|
+
ordinal=excluded.ordinal`);
|
|
919
|
+
for (const reference of references) {
|
|
920
|
+
upsert.run(notebookId, cell.id, reference.id, reference.kind, reference.targetId, canonicalJson(reference.snapshot), reference.ordinal);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
function synchronizePinnedResults(context, notebookId, cell) {
|
|
924
|
+
const pinnedResults = cell.pinnedResults ?? [];
|
|
925
|
+
deleteMissingChildren(context, "pinned_search_results", "result_id", notebookId, cell.id, pinnedResults.map((result) => result.id));
|
|
926
|
+
const upsert = context.store.db.prepare(`INSERT INTO pinned_search_results(
|
|
927
|
+
notebook_id, cell_id, result_id, artifact_id, object_hash,
|
|
928
|
+
location_json, representative_json, query_json, signals_json,
|
|
929
|
+
selected_revision, ordinal, created_at
|
|
930
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
931
|
+
ON CONFLICT(notebook_id, cell_id, result_id) DO UPDATE SET
|
|
932
|
+
artifact_id=excluded.artifact_id,
|
|
933
|
+
object_hash=excluded.object_hash,
|
|
934
|
+
location_json=excluded.location_json,
|
|
935
|
+
representative_json=excluded.representative_json,
|
|
936
|
+
query_json=excluded.query_json,
|
|
937
|
+
signals_json=excluded.signals_json,
|
|
938
|
+
selected_revision=excluded.selected_revision,
|
|
939
|
+
ordinal=excluded.ordinal,
|
|
940
|
+
created_at=excluded.created_at`);
|
|
941
|
+
for (const result of pinnedResults) {
|
|
942
|
+
upsert.run(notebookId, cell.id, result.id, result.artifactId, result.objectHash, canonicalJson(normalizeSearchLocation(result.location)), result.representativeTick === undefined
|
|
943
|
+
? null
|
|
944
|
+
: canonicalJson(result.representativeTick), canonicalJson(result.query), canonicalJson(result.signals), result.selectedRevision, result.ordinal, result.createdAt);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
function deleteMissingChildren(context, table, idColumn, notebookId, cellId, ids) {
|
|
948
|
+
if (ids.length === 0) {
|
|
949
|
+
context.store.db
|
|
950
|
+
.prepare(`DELETE FROM ${table} WHERE notebook_id=? AND cell_id=?`)
|
|
951
|
+
.run(notebookId, cellId);
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
955
|
+
context.store.db
|
|
956
|
+
.prepare(`DELETE FROM ${table}
|
|
957
|
+
WHERE notebook_id=? AND cell_id=? AND ${idColumn} NOT IN (${placeholders})`)
|
|
958
|
+
.run(notebookId, cellId, ...ids);
|
|
959
|
+
}
|
|
960
|
+
function rowToCellReference(row) {
|
|
961
|
+
return {
|
|
962
|
+
id: row.reference_id,
|
|
963
|
+
kind: row.kind,
|
|
964
|
+
targetId: row.target_id,
|
|
965
|
+
snapshot: parseJson(row.snapshot_json, {}),
|
|
966
|
+
ordinal: row.ordinal,
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
function rowToPinnedSearchResult(row) {
|
|
970
|
+
const representativeTick = row.representative_json === null
|
|
971
|
+
? undefined
|
|
972
|
+
: parseJson(row.representative_json, undefined);
|
|
973
|
+
return {
|
|
974
|
+
id: row.result_id,
|
|
975
|
+
artifactId: row.artifact_id,
|
|
976
|
+
objectHash: row.object_hash,
|
|
977
|
+
location: normalizeSearchLocation(parseJson(row.location_json, {
|
|
978
|
+
kind: "still",
|
|
979
|
+
artifactId: row.artifact_id,
|
|
980
|
+
sourcePath: "unknown",
|
|
981
|
+
objectHash: row.object_hash,
|
|
982
|
+
})),
|
|
983
|
+
...(representativeTick === undefined ? {} : { representativeTick }),
|
|
984
|
+
query: parseJson(row.query_json, {}),
|
|
985
|
+
signals: parseJson(row.signals_json, []),
|
|
986
|
+
selectedRevision: row.selected_revision,
|
|
987
|
+
ordinal: row.ordinal,
|
|
988
|
+
createdAt: row.created_at,
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
function rowToCell(row, references, pinnedResults) {
|
|
992
|
+
const inputs = parseJson(row.inputs_json, {});
|
|
993
|
+
const legacyProvider = optionalString(inputs.provider);
|
|
994
|
+
const legacyOperation = optionalString(inputs.operation);
|
|
995
|
+
const provider = optionalString(row.provider) ?? legacyProvider;
|
|
996
|
+
const operation = optionalString(row.operation) ?? legacyOperation;
|
|
997
|
+
const model = optionalString(row.model);
|
|
998
|
+
const tool = optionalString(row.tool) ??
|
|
999
|
+
(looksLikeGenerationTool(model) ? model : undefined);
|
|
331
1000
|
return {
|
|
332
1001
|
id: row.cell_id,
|
|
333
|
-
type: row.type,
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
...(row.
|
|
1002
|
+
type: notebookCellType(row.type),
|
|
1003
|
+
...(row.label ? { label: row.label } : {}),
|
|
1004
|
+
slot: { row: row.grid_row, column: row.grid_column },
|
|
1005
|
+
...(row.output_entity_id ? { outputEntityId: row.output_entity_id } : {}),
|
|
337
1006
|
...(row.prompt ? { prompt: row.prompt } : {}),
|
|
338
|
-
...(
|
|
339
|
-
|
|
1007
|
+
...(provider ? { provider } : {}),
|
|
1008
|
+
...(model ? { model } : {}),
|
|
1009
|
+
...(operation ? { operation } : {}),
|
|
1010
|
+
...(tool ? { tool } : {}),
|
|
1011
|
+
inputs,
|
|
340
1012
|
...(row.output_artifact_id
|
|
341
1013
|
? { outputArtifactId: row.output_artifact_id }
|
|
342
1014
|
: {}),
|
|
1015
|
+
references: references.map(rowToCellReference),
|
|
1016
|
+
pinnedResults: pinnedResults.map(rowToPinnedSearchResult),
|
|
343
1017
|
};
|
|
344
1018
|
}
|
|
1019
|
+
function notebookCellType(type) {
|
|
1020
|
+
const resolved = LEGACY_NOTEBOOK_CELL_TYPE_ALIASES[type] ?? type;
|
|
1021
|
+
if (!NOTEBOOK_CELL_TYPE_SET.has(resolved)) {
|
|
1022
|
+
throw new Error(`Invalid cell type: ${type}`);
|
|
1023
|
+
}
|
|
1024
|
+
return resolved;
|
|
1025
|
+
}
|
|
1026
|
+
function normalizeCellForWrite(cell) {
|
|
1027
|
+
const inputs = { ...(cell.inputs ?? {}) };
|
|
1028
|
+
const provider = optionalString(cell.provider) ?? optionalString(inputs.provider);
|
|
1029
|
+
const operation = optionalString(cell.operation) ?? optionalString(inputs.operation);
|
|
1030
|
+
const model = optionalString(cell.model);
|
|
1031
|
+
const tool = optionalString(cell.tool) ??
|
|
1032
|
+
(looksLikeGenerationTool(model) ? model : undefined);
|
|
1033
|
+
return {
|
|
1034
|
+
...cell,
|
|
1035
|
+
...(provider ? { provider } : { provider: undefined }),
|
|
1036
|
+
...(operation ? { operation } : { operation: undefined }),
|
|
1037
|
+
...(tool ? { tool } : { tool: undefined }),
|
|
1038
|
+
inputs,
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
function optionalString(value) {
|
|
1042
|
+
if (typeof value !== "string")
|
|
1043
|
+
return undefined;
|
|
1044
|
+
const trimmed = value.trim();
|
|
1045
|
+
return trimmed ? trimmed : undefined;
|
|
1046
|
+
}
|
|
1047
|
+
function looksLikeGenerationTool(value) {
|
|
1048
|
+
if (!value)
|
|
1049
|
+
return false;
|
|
1050
|
+
return value.startsWith("generate_") || value.includes("/");
|
|
1051
|
+
}
|
|
345
1052
|
function rowToEdge(row) {
|
|
346
1053
|
return {
|
|
347
1054
|
id: row.edge_id,
|
|
@@ -383,6 +1090,81 @@ function validateRunStatus(status) {
|
|
|
383
1090
|
throw new Error(`Invalid notebook run status: ${status}`);
|
|
384
1091
|
}
|
|
385
1092
|
}
|
|
1093
|
+
function executionFromRow(row) {
|
|
1094
|
+
return {
|
|
1095
|
+
...(row.fingerprint ? { fingerprint: row.fingerprint } : {}),
|
|
1096
|
+
...(row.status ? { status: row.status } : {}),
|
|
1097
|
+
...(row.output_artifact_id
|
|
1098
|
+
? { outputArtifactId: row.output_artifact_id }
|
|
1099
|
+
: {}),
|
|
1100
|
+
...(row.provider_artifact_id
|
|
1101
|
+
? { providerArtifactId: row.provider_artifact_id }
|
|
1102
|
+
: {}),
|
|
1103
|
+
...(row.run_id ? { runId: row.run_id } : {}),
|
|
1104
|
+
...(row.completed_at ? { completedAt: row.completed_at } : {}),
|
|
1105
|
+
...(row.started_at ? { startedAt: row.started_at } : {}),
|
|
1106
|
+
...(row.updated_at ? { updatedAt: row.updated_at } : {}),
|
|
1107
|
+
...(row.tool ? { tool: row.tool } : {}),
|
|
1108
|
+
...(row.error ? { error: row.error } : {}),
|
|
1109
|
+
...(row.stale === 1 ? { stale: true } : {}),
|
|
1110
|
+
...(row.fixture_baseline === 1 ? { fixtureBaseline: true } : {}),
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
function generationPlanFromRow(row) {
|
|
1114
|
+
return {
|
|
1115
|
+
planId: row.plan_id,
|
|
1116
|
+
cellId: row.cell_id,
|
|
1117
|
+
status: row.status,
|
|
1118
|
+
plan: parseJson(row.plan_json, {}),
|
|
1119
|
+
...(row.output_artifact_id
|
|
1120
|
+
? { outputArtifactId: row.output_artifact_id }
|
|
1121
|
+
: {}),
|
|
1122
|
+
...(row.error ? { error: row.error } : {}),
|
|
1123
|
+
createdAt: row.created_at,
|
|
1124
|
+
updatedAt: row.updated_at,
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
function runPlanFromRow(row) {
|
|
1128
|
+
return {
|
|
1129
|
+
planId: row.plan_id,
|
|
1130
|
+
status: row.status,
|
|
1131
|
+
plan: parseJson(row.plan_json, {}),
|
|
1132
|
+
paidCellIds: parseJson(row.paid_cell_ids_json, []),
|
|
1133
|
+
cellDefinitionFingerprints: parseJson(row.cell_fingerprints_json, {}),
|
|
1134
|
+
knownCostUsd: row.known_cost_usd,
|
|
1135
|
+
unknownCostCount: row.unknown_cost_count,
|
|
1136
|
+
createdAt: row.created_at,
|
|
1137
|
+
updatedAt: row.updated_at,
|
|
1138
|
+
...(row.run_id ? { runId: row.run_id } : {}),
|
|
1139
|
+
...(row.outputs_json
|
|
1140
|
+
? {
|
|
1141
|
+
outputs: parseJson(row.outputs_json, {}),
|
|
1142
|
+
}
|
|
1143
|
+
: {}),
|
|
1144
|
+
...(row.error ? { error: row.error } : {}),
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
function assertUnique(values, label) {
|
|
1148
|
+
const unique = new Set(values);
|
|
1149
|
+
if (unique.size !== values.length) {
|
|
1150
|
+
throw new Error(`Duplicate ${label}`);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
function nonNegativeFinite(value, label) {
|
|
1154
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
1155
|
+
throw new Error(`${label} must be a non-negative finite number`);
|
|
1156
|
+
}
|
|
1157
|
+
return value;
|
|
1158
|
+
}
|
|
1159
|
+
function nonNegativeInteger(value, label) {
|
|
1160
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
1161
|
+
throw new Error(`${label} must be a non-negative integer`);
|
|
1162
|
+
}
|
|
1163
|
+
return value;
|
|
1164
|
+
}
|
|
1165
|
+
function isRecord(value) {
|
|
1166
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1167
|
+
}
|
|
386
1168
|
function callerOrNewId(id, label) {
|
|
387
1169
|
const resolved = id ?? newUuidV7();
|
|
388
1170
|
assertUuidV7(resolved, label);
|
|
@@ -393,7 +1175,7 @@ const ENTITY_SELECT = `
|
|
|
393
1175
|
FROM entities
|
|
394
1176
|
`;
|
|
395
1177
|
const NOTEBOOK_SELECT = `
|
|
396
|
-
SELECT notebook_id, name,
|
|
1178
|
+
SELECT notebook_id, name, created_at
|
|
397
1179
|
FROM notebooks
|
|
398
1180
|
`;
|
|
399
1181
|
//# sourceMappingURL=domain.js.map
|