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/history.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { rm } from "node:fs/promises";
|
|
2
2
|
import { ok } from "./engine-types.js";
|
|
3
|
-
import { resultOf
|
|
3
|
+
import { resultOf } from "./context.js";
|
|
4
4
|
import { artifactSlug } from "./artifacts.js";
|
|
5
5
|
import { materializeArtifact } from "./files.js";
|
|
6
6
|
import { assertUuidV7, isUuidV7, newUuidV7 } from "./ids.js";
|
|
7
|
-
import {
|
|
7
|
+
import { SCHEMA_VERSION, SEMANTIC_TABLES } from "./schema.js";
|
|
8
|
+
import { canonicalJson, commitDateIso, EngineFault, parseCommitMessage, } from "./store.js";
|
|
8
9
|
export function createHistoryApi(context) {
|
|
9
10
|
return {
|
|
10
11
|
revisions: (limit = 20) => revisionHistory(context, limit),
|
|
@@ -15,9 +16,6 @@ export function createHistoryApi(context) {
|
|
|
15
16
|
restore: (revision) => restoreBook(context, revision),
|
|
16
17
|
logAction: (action, payload) => logAction(context, action, payload),
|
|
17
18
|
actionLog: (options) => actionLog(context, options),
|
|
18
|
-
actions: (options) => actions(context, options),
|
|
19
|
-
action: (actionId) => action(context, actionId),
|
|
20
|
-
recordAction: (input) => recordAction(context, input),
|
|
21
19
|
};
|
|
22
20
|
}
|
|
23
21
|
function revisionForHash(context, hash) {
|
|
@@ -30,18 +28,21 @@ function revisionForHash(context, hash) {
|
|
|
30
28
|
message: `Revision not found: ${hash}`,
|
|
31
29
|
});
|
|
32
30
|
}
|
|
31
|
+
const parsed = parseCommitMessage(commit.message);
|
|
32
|
+
if (parsed)
|
|
33
|
+
return revisionFromCommit(context, commit, parsed);
|
|
33
34
|
return {
|
|
34
35
|
hash,
|
|
35
36
|
message: commit.message,
|
|
36
|
-
date: commit.date,
|
|
37
|
+
date: commitDateIso(commit.date),
|
|
37
38
|
author: commit.committer,
|
|
38
39
|
};
|
|
39
40
|
}
|
|
40
41
|
function revisionHistory(context, limit) {
|
|
41
|
-
return
|
|
42
|
+
return commitRevisions(context).slice(0, Math.max(0, limit));
|
|
42
43
|
}
|
|
43
44
|
function artifactHistory(context, artifactReference, limit) {
|
|
44
|
-
const revisions =
|
|
45
|
+
const revisions = commitRevisions(context);
|
|
45
46
|
let artifactId;
|
|
46
47
|
if (isUuidV7(artifactReference)) {
|
|
47
48
|
artifactId = artifactReference;
|
|
@@ -63,61 +64,52 @@ function artifactHistory(context, artifactReference, limit) {
|
|
|
63
64
|
.filter((revision) => revision.artifactId === artifactId)
|
|
64
65
|
.slice(0, Math.max(0, limit));
|
|
65
66
|
}
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
// History listings are direct commit-log reads: every engine commit carries
|
|
68
|
+
// its operation, parameters, and write set in its structured message, so no
|
|
69
|
+
// per-commit table diffs are needed to describe a revision.
|
|
70
|
+
function commitRevisions(context) {
|
|
68
71
|
const revisions = [];
|
|
69
|
-
for (
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
.filter((row) => row.diff_type === "added")
|
|
75
|
-
.map(operationFromDiff)
|
|
76
|
-
.filter((operation) => operation !== null);
|
|
77
|
-
for (const operation of operations) {
|
|
78
|
-
const details = parseJson(operation.details_json, {});
|
|
79
|
-
const writeSet = parseJson(operation.write_set_json, []);
|
|
80
|
-
const artifactSlugAtRevision = operation.artifact_id
|
|
81
|
-
? artifactSlugAt(context, operation.artifact_id, commit.commit_hash)
|
|
82
|
-
: undefined;
|
|
83
|
-
const fileChanges = revisionFileChanges(context.store.diff(parent.commit_hash, commit.commit_hash, "artifact_files"), operation.artifact_id);
|
|
84
|
-
revisions.push({
|
|
85
|
-
hash: commit.commit_hash,
|
|
86
|
-
message: commit.message,
|
|
87
|
-
date: new Date(operation.created_at).toISOString(),
|
|
88
|
-
author: operation.author,
|
|
89
|
-
operationId: operation.operation_id,
|
|
90
|
-
operation: operation.operation,
|
|
91
|
-
...(operation.artifact_id
|
|
92
|
-
? { artifactId: operation.artifact_id }
|
|
93
|
-
: {}),
|
|
94
|
-
...(artifactSlugAtRevision
|
|
95
|
-
? { artifactSlug: artifactSlugAtRevision }
|
|
96
|
-
: {}),
|
|
97
|
-
details: {
|
|
98
|
-
...details,
|
|
99
|
-
...(writeSet.length > 0 ? { writeSet } : {}),
|
|
100
|
-
...(operation.base_revision
|
|
101
|
-
? { baseRevision: operation.base_revision }
|
|
102
|
-
: {}),
|
|
103
|
-
},
|
|
104
|
-
files: fileChanges.map((change) => change.file),
|
|
105
|
-
fileChanges,
|
|
106
|
-
});
|
|
107
|
-
}
|
|
72
|
+
for (const commit of context.store.db.doltLog()) {
|
|
73
|
+
const parsed = parseCommitMessage(commit.message);
|
|
74
|
+
if (!parsed)
|
|
75
|
+
continue;
|
|
76
|
+
revisions.push(revisionFromCommit(context, commit, parsed));
|
|
108
77
|
}
|
|
109
78
|
return revisions;
|
|
110
79
|
}
|
|
80
|
+
function revisionFromCommit(context, commit, parsed) {
|
|
81
|
+
const artifactSlugAtRevision = parsed.artifactId
|
|
82
|
+
? artifactSlugAt(context, parsed.artifactId, commit.commit_hash)
|
|
83
|
+
: undefined;
|
|
84
|
+
return {
|
|
85
|
+
hash: commit.commit_hash,
|
|
86
|
+
message: commit.message,
|
|
87
|
+
date: commitDateIso(commit.date),
|
|
88
|
+
author: parsed.actor ?? commit.committer,
|
|
89
|
+
operationId: parsed.operationId,
|
|
90
|
+
operation: parsed.operation,
|
|
91
|
+
...(parsed.artifactId ? { artifactId: parsed.artifactId } : {}),
|
|
92
|
+
...(artifactSlugAtRevision ? { artifactSlug: artifactSlugAtRevision } : {}),
|
|
93
|
+
details: {
|
|
94
|
+
...parsed.details,
|
|
95
|
+
...(parsed.writeSet.length > 0 ? { writeSet: parsed.writeSet } : {}),
|
|
96
|
+
...(parsed.baseRevision ? { baseRevision: parsed.baseRevision } : {}),
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
111
100
|
function resolveRevision(context, reference) {
|
|
112
|
-
const historical =
|
|
101
|
+
const historical = commitRevisions(context).find((revision) => revision.hash === reference || revision.hash.startsWith(reference));
|
|
113
102
|
if (historical)
|
|
114
103
|
return historical;
|
|
115
|
-
const commit = context.store.db
|
|
104
|
+
const commit = context.store.db
|
|
105
|
+
.doltLog()
|
|
106
|
+
.find((item) => item.commit_hash === reference ||
|
|
107
|
+
item.commit_hash.startsWith(reference));
|
|
116
108
|
return commit
|
|
117
109
|
? {
|
|
118
110
|
hash: commit.commit_hash,
|
|
119
111
|
message: commit.message,
|
|
120
|
-
date: commit.date,
|
|
112
|
+
date: commitDateIso(commit.date),
|
|
121
113
|
author: commit.committer,
|
|
122
114
|
}
|
|
123
115
|
: null;
|
|
@@ -139,15 +131,17 @@ async function recordOperation(context, operation, artifactReference, details) {
|
|
|
139
131
|
: null;
|
|
140
132
|
const mutation = await context.store.semantic({
|
|
141
133
|
operation,
|
|
134
|
+
tables: [],
|
|
142
135
|
...(artifact ? { artifactId: artifact.artifact_id } : {}),
|
|
143
136
|
details: {
|
|
144
137
|
...(details ?? {}),
|
|
145
138
|
...(artifact ? { artifactSlug: artifact.slug } : {}),
|
|
146
139
|
},
|
|
147
|
-
writeSet: artifact
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
140
|
+
writeSet: artifact ? [`artifact:${artifact.artifact_id}`] : ["book"],
|
|
141
|
+
// Provenance-only operations change no semantic rows; the commit
|
|
142
|
+
// itself is their record, so it must be minted even when empty.
|
|
143
|
+
allowEmpty: true,
|
|
144
|
+
}, () => undefined);
|
|
151
145
|
return ok(revisionForHash(context, mutation.revision), mutation.revision);
|
|
152
146
|
});
|
|
153
147
|
}
|
|
@@ -191,18 +185,16 @@ async function restoreArtifact(context, artifactId, revisionReference, replaceme
|
|
|
191
185
|
.get(revision.hash, artifactId);
|
|
192
186
|
const mutation = await context.store.semantic({
|
|
193
187
|
operation: "restore_artifact",
|
|
188
|
+
tables: [
|
|
189
|
+
"artifacts",
|
|
190
|
+
"artifact_files",
|
|
191
|
+
"artifact_metadata",
|
|
192
|
+
"audio_waveforms",
|
|
193
|
+
],
|
|
194
194
|
artifactId,
|
|
195
195
|
details: { fromRevision: revision.hash, slug: desiredSlug },
|
|
196
|
-
writeSet: [
|
|
197
|
-
|
|
198
|
-
`artifact-slug:${desiredSlug}`,
|
|
199
|
-
],
|
|
200
|
-
}, [
|
|
201
|
-
"artifacts",
|
|
202
|
-
"artifact_files",
|
|
203
|
-
"artifact_metadata",
|
|
204
|
-
"audio_waveforms",
|
|
205
|
-
], (_operationId, now) => {
|
|
196
|
+
writeSet: [`artifact:${artifactId}`, `artifact-slug:${desiredSlug}`],
|
|
197
|
+
}, (_operationId, now) => {
|
|
206
198
|
context.store.db
|
|
207
199
|
.prepare(`INSERT INTO artifacts(
|
|
208
200
|
artifact_id, slug, kind, created_at
|
|
@@ -235,8 +227,11 @@ async function restoreArtifact(context, artifactId, revisionReference, replaceme
|
|
|
235
227
|
}
|
|
236
228
|
resetArtifactRuntime(context, artifactId, now);
|
|
237
229
|
});
|
|
238
|
-
await rm(context.artifactPath(artifactId), {
|
|
239
|
-
|
|
230
|
+
await rm(context.artifactPath(artifactId), {
|
|
231
|
+
recursive: true,
|
|
232
|
+
force: true,
|
|
233
|
+
});
|
|
234
|
+
await materializeIgnoringForgotten(context, artifactId);
|
|
240
235
|
return ok(revisionForHash(context, mutation.revision), mutation.revision);
|
|
241
236
|
});
|
|
242
237
|
}
|
|
@@ -259,160 +254,33 @@ async function restoreBook(context, revisionReference) {
|
|
|
259
254
|
message: "A restore cannot replace the catalog's stable book ID",
|
|
260
255
|
});
|
|
261
256
|
}
|
|
257
|
+
const targetSchema = context.store.db
|
|
258
|
+
.prepare("SELECT version FROM dolt_at_engine_schema(?) WHERE singleton=1")
|
|
259
|
+
.get(revision.hash);
|
|
260
|
+
if (!targetSchema || targetSchema.version !== SCHEMA_VERSION) {
|
|
261
|
+
throw new EngineFault({
|
|
262
|
+
code: "SCHEMA_INCOMPATIBLE",
|
|
263
|
+
message: `Cannot restore ${revision.hash}: it records schema version ` +
|
|
264
|
+
`${targetSchema?.version ?? "unknown"}, not the current ${SCHEMA_VERSION}`,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
262
267
|
const targetArtifacts = context.store.db
|
|
263
268
|
.prepare(`SELECT artifact_id, slug, kind, created_at
|
|
264
269
|
FROM dolt_at_artifacts(?)
|
|
265
270
|
ORDER BY artifact_id`)
|
|
266
271
|
.all(revision.hash);
|
|
267
272
|
const targetIds = targetArtifacts.map((row) => row.artifact_id);
|
|
268
|
-
const targetFiles = rowsForArtifactIds(context, "artifact_files", "artifact_id, path, object_hash, mtime_ms, created_at", revision.hash, targetIds);
|
|
269
|
-
const bookMetadata = context.store.db
|
|
270
|
-
.prepare("SELECT key, value_json FROM dolt_at_book_metadata(?)")
|
|
271
|
-
.all(revision.hash);
|
|
272
|
-
const artifactMetadata = rowsForArtifactIds(context, "artifact_metadata", "artifact_id, key, value_json", revision.hash, targetIds);
|
|
273
|
-
const waveforms = rowsForArtifactIds(context, "audio_waveforms", "artifact_id, peaks_json", revision.hash, targetIds);
|
|
274
|
-
const entities = context.store.db
|
|
275
|
-
.prepare(`SELECT entity_id, type, name, description, prompt,
|
|
276
|
-
data_json, created_at
|
|
277
|
-
FROM dolt_at_entities(?)`)
|
|
278
|
-
.all(revision.hash);
|
|
279
|
-
const notebooks = context.store.db
|
|
280
|
-
.prepare(`SELECT notebook_id, name, properties_json, created_at
|
|
281
|
-
FROM dolt_at_notebooks(?)`)
|
|
282
|
-
.all(revision.hash);
|
|
283
|
-
const notebookIds = notebooks.map((row) => row.notebook_id);
|
|
284
|
-
const notebookCells = rowsForNotebookIds(context, "cells", `notebook_id, cell_id, type, title, position_x, position_y,
|
|
285
|
-
entity_id, prompt, model, inputs_json, output_artifact_id`, revision.hash, notebookIds);
|
|
286
|
-
const notebookEdges = rowsForNotebookIds(context, "edges", "notebook_id, edge_id, source_cell_id, target_cell_id, target_input", revision.hash, notebookIds);
|
|
287
|
-
const notebookRuns = rowsForNotebookIds(context, "runs", `run_id, notebook_id, status, started_at, completed_at,
|
|
288
|
-
cell_order_json, outputs_json, error`, revision.hash, notebookIds);
|
|
289
|
-
const timeline = context.store.db
|
|
290
|
-
.prepare("SELECT book_id, render FROM dolt_at_timeline(?)")
|
|
291
|
-
.get(revision.hash);
|
|
292
|
-
const timelineSlots = context.store.db
|
|
293
|
-
.prepare(`SELECT slot_id, artifact_id, ordinal, volume,
|
|
294
|
-
audio_fade_in, audio_fade_out
|
|
295
|
-
FROM dolt_at_timeline_slots(?)`)
|
|
296
|
-
.all(revision.hash);
|
|
297
|
-
const timelineAudio = context.store.db
|
|
298
|
-
.prepare(`SELECT audio_id, artifact_id, ordinal, start_frame, duration_frames,
|
|
299
|
-
volume, fade_in, fade_out
|
|
300
|
-
FROM dolt_at_timeline_audio(?)`)
|
|
301
|
-
.all(revision.hash);
|
|
302
|
-
const prompts = context.store.db
|
|
303
|
-
.prepare(`SELECT prompt_id, surface, prompt, context_json, created_at
|
|
304
|
-
FROM dolt_at_prompt_entries(?)`)
|
|
305
|
-
.all(revision.hash);
|
|
306
|
-
const messages = context.store.db
|
|
307
|
-
.prepare(`SELECT message_id, role, body_json, created_at
|
|
308
|
-
FROM dolt_at_messages(?)`)
|
|
309
|
-
.all(revision.hash);
|
|
310
273
|
const currentArtifactIds = context.store.db
|
|
311
274
|
.prepare("SELECT artifact_id FROM artifacts")
|
|
312
275
|
.all()
|
|
313
276
|
.map((row) => row.artifact_id);
|
|
314
277
|
const mutation = await context.store.semantic({
|
|
315
278
|
operation: "restore",
|
|
279
|
+
tables: SEMANTIC_TABLES,
|
|
316
280
|
details: { fromRevision: revision.hash },
|
|
317
281
|
writeSet: ["book"],
|
|
318
|
-
},
|
|
319
|
-
|
|
320
|
-
"artifacts",
|
|
321
|
-
"artifact_files",
|
|
322
|
-
"book_metadata",
|
|
323
|
-
"artifact_metadata",
|
|
324
|
-
"audio_waveforms",
|
|
325
|
-
"entities",
|
|
326
|
-
"notebooks",
|
|
327
|
-
"cells",
|
|
328
|
-
"edges",
|
|
329
|
-
"runs",
|
|
330
|
-
"timeline",
|
|
331
|
-
"timeline_slots",
|
|
332
|
-
"timeline_audio",
|
|
333
|
-
"prompt_entries",
|
|
334
|
-
"messages",
|
|
335
|
-
"job_runs",
|
|
336
|
-
], (_operationId, now) => {
|
|
337
|
-
context.store.db.prepare("DELETE FROM timeline_slots").run();
|
|
338
|
-
context.store.db.prepare("DELETE FROM timeline_audio").run();
|
|
339
|
-
context.store.db.prepare("DELETE FROM edges").run();
|
|
340
|
-
context.store.db.prepare("DELETE FROM runs").run();
|
|
341
|
-
context.store.db.prepare("DELETE FROM cells").run();
|
|
342
|
-
context.store.db.prepare("DELETE FROM notebooks").run();
|
|
343
|
-
context.store.db.prepare("DELETE FROM artifact_metadata").run();
|
|
344
|
-
context.store.db.prepare("DELETE FROM audio_waveforms").run();
|
|
345
|
-
context.store.db.prepare("DELETE FROM artifact_files").run();
|
|
346
|
-
context.store.db.prepare("DELETE FROM entities").run();
|
|
347
|
-
context.store.db.prepare("DELETE FROM artifacts").run();
|
|
348
|
-
context.store.db
|
|
349
|
-
.prepare("UPDATE book SET slug=?, created_at=? WHERE book_id=?")
|
|
350
|
-
.run(targetBook.slug, targetBook.created_at, targetBook.book_id);
|
|
351
|
-
const insertArtifact = context.store.db.prepare(`INSERT INTO artifacts(artifact_id, slug, kind, created_at)
|
|
352
|
-
VALUES (?, ?, ?, ?)`);
|
|
353
|
-
for (const row of targetArtifacts) {
|
|
354
|
-
insertArtifact.run(row.artifact_id, row.slug, row.kind, row.created_at);
|
|
355
|
-
}
|
|
356
|
-
insertFiles(context, targetFiles);
|
|
357
|
-
const insertArtifactMetadata = context.store.db.prepare(`INSERT INTO artifact_metadata(artifact_id, key, value_json)
|
|
358
|
-
VALUES (?, ?, ?)`);
|
|
359
|
-
for (const row of artifactMetadata) {
|
|
360
|
-
insertArtifactMetadata.run(row.artifact_id, row.key, row.value_json);
|
|
361
|
-
}
|
|
362
|
-
const insertWaveform = context.store.db.prepare(`INSERT INTO audio_waveforms(artifact_id, peaks_json)
|
|
363
|
-
VALUES (?, ?)`);
|
|
364
|
-
for (const row of waveforms) {
|
|
365
|
-
if (!row.artifact_id)
|
|
366
|
-
continue;
|
|
367
|
-
insertWaveform.run(row.artifact_id, row.peaks_json);
|
|
368
|
-
}
|
|
369
|
-
context.store.db.prepare("DELETE FROM book_metadata").run();
|
|
370
|
-
const insertBookMetadata = context.store.db.prepare("INSERT INTO book_metadata(key, value_json) VALUES (?, ?)");
|
|
371
|
-
for (const row of bookMetadata) {
|
|
372
|
-
insertBookMetadata.run(row.key, row.value_json);
|
|
373
|
-
}
|
|
374
|
-
const insertEntity = context.store.db.prepare(`INSERT INTO entities(
|
|
375
|
-
entity_id, type, name, description, prompt, data_json, created_at
|
|
376
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?)`);
|
|
377
|
-
for (const row of entities) {
|
|
378
|
-
insertEntity.run(row.entity_id, row.type, row.name, row.description, row.prompt, row.data_json, row.created_at);
|
|
379
|
-
}
|
|
380
|
-
const insertNotebook = context.store.db.prepare(`INSERT INTO notebooks(
|
|
381
|
-
notebook_id, name, properties_json, created_at
|
|
382
|
-
) VALUES (?, ?, ?, ?)`);
|
|
383
|
-
for (const row of notebooks) {
|
|
384
|
-
insertNotebook.run(row.notebook_id, row.name, row.properties_json, row.created_at);
|
|
385
|
-
}
|
|
386
|
-
insertNotebookChildren(context, notebookCells, notebookEdges, notebookRuns);
|
|
387
|
-
context.store.db
|
|
388
|
-
.prepare("UPDATE timeline SET render=? WHERE book_id=?")
|
|
389
|
-
.run(timeline?.render ?? "landscape", targetBook.book_id);
|
|
390
|
-
const insertSlot = context.store.db.prepare(`INSERT INTO timeline_slots(
|
|
391
|
-
slot_id, artifact_id, ordinal, volume, audio_fade_in, audio_fade_out
|
|
392
|
-
) VALUES (?, ?, ?, ?, ?, ?)`);
|
|
393
|
-
for (const row of timelineSlots) {
|
|
394
|
-
insertSlot.run(row.slot_id, row.artifact_id, row.ordinal, row.volume, row.audio_fade_in, row.audio_fade_out);
|
|
395
|
-
}
|
|
396
|
-
const insertAudio = context.store.db.prepare(`INSERT INTO timeline_audio(
|
|
397
|
-
audio_id, artifact_id, ordinal, start_frame, duration_frames,
|
|
398
|
-
volume, fade_in, fade_out
|
|
399
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
400
|
-
for (const row of timelineAudio) {
|
|
401
|
-
insertAudio.run(row.audio_id, row.artifact_id, row.ordinal, row.start_frame, row.duration_frames, row.volume, row.fade_in, row.fade_out);
|
|
402
|
-
}
|
|
403
|
-
context.store.db.prepare("DELETE FROM prompt_entries").run();
|
|
404
|
-
const insertPrompt = context.store.db.prepare(`INSERT INTO prompt_entries(
|
|
405
|
-
prompt_id, surface, prompt, context_json, created_at
|
|
406
|
-
) VALUES (?, ?, ?, ?, ?)`);
|
|
407
|
-
for (const row of prompts) {
|
|
408
|
-
insertPrompt.run(row.prompt_id, row.surface, row.prompt, row.context_json, row.created_at);
|
|
409
|
-
}
|
|
410
|
-
context.store.db.prepare("DELETE FROM messages").run();
|
|
411
|
-
const insertMessage = context.store.db.prepare(`INSERT INTO messages(message_id, role, body_json, created_at)
|
|
412
|
-
VALUES (?, ?, ?, ?)`);
|
|
413
|
-
for (const row of messages) {
|
|
414
|
-
insertMessage.run(row.message_id, row.role, row.body_json, row.created_at);
|
|
415
|
-
}
|
|
282
|
+
}, (_operationId, now) => {
|
|
283
|
+
reloadSemanticTables(context, revision.hash);
|
|
416
284
|
abortActiveJobs(context, now, "Book restored");
|
|
417
285
|
context.store.db
|
|
418
286
|
.prepare(`UPDATE runtime_resource_leases
|
|
@@ -424,7 +292,9 @@ async function restoreBook(context, revisionReference) {
|
|
|
424
292
|
context.store.db.prepare("DELETE FROM runtime_artifact_views").run();
|
|
425
293
|
context.store.db.prepare("DELETE FROM runtime_pending_tasks").run();
|
|
426
294
|
context.store.db.prepare("DELETE FROM runtime_generation_errors").run();
|
|
427
|
-
context.store.db
|
|
295
|
+
context.store.db
|
|
296
|
+
.prepare("DELETE FROM runtime_similarity_embeddings")
|
|
297
|
+
.run();
|
|
428
298
|
context.store.db
|
|
429
299
|
.prepare("DELETE FROM runtime_text_similarity_documents")
|
|
430
300
|
.run();
|
|
@@ -433,14 +303,94 @@ async function restoreBook(context, revisionReference) {
|
|
|
433
303
|
}
|
|
434
304
|
});
|
|
435
305
|
for (const artifactId of new Set([...currentArtifactIds, ...targetIds])) {
|
|
436
|
-
await rm(context.artifactPath(artifactId), {
|
|
306
|
+
await rm(context.artifactPath(artifactId), {
|
|
307
|
+
recursive: true,
|
|
308
|
+
force: true,
|
|
309
|
+
});
|
|
437
310
|
}
|
|
438
311
|
for (const artifact of targetArtifacts) {
|
|
439
|
-
await
|
|
312
|
+
await materializeIgnoringForgotten(context, artifact.artifact_id);
|
|
440
313
|
}
|
|
441
314
|
return ok(revisionForHash(context, mutation.revision), mutation.revision);
|
|
442
315
|
});
|
|
443
316
|
}
|
|
317
|
+
/**
|
|
318
|
+
* Workspace materialization after a restore tolerates forgotten objects:
|
|
319
|
+
* the restore commit is already durable, so a file whose bytes were
|
|
320
|
+
* deliberately deleted must not fail the whole restore — reads of that file
|
|
321
|
+
* surface OBJECT_UNAVAILABLE instead.
|
|
322
|
+
*/
|
|
323
|
+
async function materializeIgnoringForgotten(context, artifactId) {
|
|
324
|
+
try {
|
|
325
|
+
await materializeArtifact(context, artifactId);
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
329
|
+
if (!/object unavailable/i.test(message))
|
|
330
|
+
throw error;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Restore is mechanical: every semantic table is reloaded from its
|
|
335
|
+
* `dolt_at_<table>` projection at the target revision, so the resulting
|
|
336
|
+
* state is exactly the state that revision recorded and no hand-maintained
|
|
337
|
+
* table list can drift. `SEMANTIC_TABLES` is ordered parent-before-child,
|
|
338
|
+
* so deleting in reverse order and reinserting in forward order satisfies
|
|
339
|
+
* foreign keys; columns come from `PRAGMA table_info`, not per-table code.
|
|
340
|
+
* Reinserted rows identical to HEAD produce no diff, so unchanged tables
|
|
341
|
+
* are never staged by the surrounding commit.
|
|
342
|
+
*
|
|
343
|
+
* The single deliberate exception is `objects`: its rows are permanent
|
|
344
|
+
* tombstone records, so restore merges instead of wiping — rows are never
|
|
345
|
+
* deleted and `forgotten_at` never rewinds (see `restoreObjectRows`).
|
|
346
|
+
*/
|
|
347
|
+
function reloadSemanticTables(context, revision) {
|
|
348
|
+
const db = context.store.db;
|
|
349
|
+
const snapshots = SEMANTIC_TABLES.map((table) => {
|
|
350
|
+
const columns = db.prepare(`PRAGMA table_info(${table})`).all().map((column) => column.name);
|
|
351
|
+
const rows = db
|
|
352
|
+
.prepare(`SELECT * FROM dolt_at_${table}(?)`)
|
|
353
|
+
.all(revision);
|
|
354
|
+
return { table, columns, rows };
|
|
355
|
+
});
|
|
356
|
+
for (const { table } of [...snapshots].reverse()) {
|
|
357
|
+
// `objects` is the one exception to the mechanical wipe: rows are never
|
|
358
|
+
// deleted (a row is the permanent tombstone for every historical
|
|
359
|
+
// reference) and `forgotten_at` never rewinds — see the upsert below.
|
|
360
|
+
if (table === "objects")
|
|
361
|
+
continue;
|
|
362
|
+
db.prepare(`DELETE FROM ${table}`).run();
|
|
363
|
+
}
|
|
364
|
+
for (const { table, columns, rows } of snapshots) {
|
|
365
|
+
if (rows.length === 0)
|
|
366
|
+
continue;
|
|
367
|
+
if (table === "objects") {
|
|
368
|
+
restoreObjectRows(db, columns, rows);
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
const insert = db.prepare(`INSERT INTO ${table}(${columns.join(", ")})
|
|
372
|
+
VALUES (${columns.map(() => "?").join(", ")})`);
|
|
373
|
+
for (const row of rows) {
|
|
374
|
+
insert.run(...columns.map((column) => row[column] ?? null));
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Restores `objects` with merge semantics instead of wipe-and-reload:
|
|
380
|
+
* rows created after the target revision survive (object rows are never
|
|
381
|
+
* deleted), and an existing forget tombstone always wins over the target
|
|
382
|
+
* revision's live state (`forgotten_at` never rewinds — deleted bytes do
|
|
383
|
+
* not come back because history was restored).
|
|
384
|
+
*/
|
|
385
|
+
function restoreObjectRows(db, columns, rows) {
|
|
386
|
+
const upsert = db.prepare(`INSERT INTO objects(${columns.join(", ")})
|
|
387
|
+
VALUES (${columns.map(() => "?").join(", ")})
|
|
388
|
+
ON CONFLICT(object_hash) DO UPDATE SET
|
|
389
|
+
forgotten_at=COALESCE(objects.forgotten_at, excluded.forgotten_at)`);
|
|
390
|
+
for (const row of rows) {
|
|
391
|
+
upsert.run(...columns.map((column) => row[column] ?? null));
|
|
392
|
+
}
|
|
393
|
+
}
|
|
444
394
|
async function logAction(context, actionName, payload) {
|
|
445
395
|
return resultOf(async () => {
|
|
446
396
|
const result = await recordOperation(context, `action:${actionName}`, undefined, {
|
|
@@ -464,248 +414,10 @@ function actionLog(context, options = {}) {
|
|
|
464
414
|
.map((revision) => ({
|
|
465
415
|
hash: revision.hash,
|
|
466
416
|
action: revision.operation.slice("action:".length),
|
|
467
|
-
payload: revision.details?.payload ??
|
|
468
|
-
{},
|
|
417
|
+
payload: revision.details?.payload ?? {},
|
|
469
418
|
date: revision.date,
|
|
470
419
|
}));
|
|
471
420
|
}
|
|
472
|
-
function actions(context, options = {}) {
|
|
473
|
-
return syncResultOf(() => {
|
|
474
|
-
const limit = Math.max(1, options.limit ?? 200);
|
|
475
|
-
const params = [];
|
|
476
|
-
let cursorClause = "";
|
|
477
|
-
if (options.cursor) {
|
|
478
|
-
const cursor = context.store.db
|
|
479
|
-
.prepare("SELECT created_at, action_id FROM actions WHERE action_id=?")
|
|
480
|
-
.get(options.cursor);
|
|
481
|
-
if (cursor) {
|
|
482
|
-
cursorClause = "WHERE (created_at < ? OR (created_at = ? AND action_id < ?))";
|
|
483
|
-
params.push(cursor.created_at, cursor.created_at, cursor.action_id);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
params.push(limit + 1);
|
|
487
|
-
const rows = context.store.db
|
|
488
|
-
.prepare(`${ACTION_SELECT}
|
|
489
|
-
${cursorClause}
|
|
490
|
-
ORDER BY created_at DESC, action_id DESC
|
|
491
|
-
LIMIT ?`)
|
|
492
|
-
.all(...params);
|
|
493
|
-
const page = rows.slice(0, limit);
|
|
494
|
-
return {
|
|
495
|
-
headRevision: context.store.head,
|
|
496
|
-
actions: page
|
|
497
|
-
.map((row) => actionFromRow(context, row))
|
|
498
|
-
.sort((left, right) => left.date.localeCompare(right.date)),
|
|
499
|
-
...(rows.length > limit ? { nextCursor: page.at(-1)?.action_id } : {}),
|
|
500
|
-
};
|
|
501
|
-
});
|
|
502
|
-
}
|
|
503
|
-
function action(context, actionId) {
|
|
504
|
-
return syncResultOf(() => requiredAction(context, actionId));
|
|
505
|
-
}
|
|
506
|
-
async function recordAction(context, input) {
|
|
507
|
-
return resultOf(async () => {
|
|
508
|
-
assertWriteSet(context, input.baseRevision, input.writeSet ?? []);
|
|
509
|
-
const actionId = input.actionId ?? newUuidV7();
|
|
510
|
-
assertUuidV7(actionId, "Action ID");
|
|
511
|
-
const phase = input.phase ?? "completed";
|
|
512
|
-
const scope = input.scope ?? "book";
|
|
513
|
-
assertActionPhase(phase);
|
|
514
|
-
assertActionScope(scope);
|
|
515
|
-
for (const parentId of input.parentActionIds ?? []) {
|
|
516
|
-
assertUuidV7(parentId, "Parent action ID");
|
|
517
|
-
requiredAction(context, parentId);
|
|
518
|
-
}
|
|
519
|
-
if (input.targetActionId) {
|
|
520
|
-
assertUuidV7(input.targetActionId, "Target action ID");
|
|
521
|
-
}
|
|
522
|
-
const actor = input.actor ?? "videobook";
|
|
523
|
-
const inputArtifacts = resolveArtifactReferences(context, input.inputArtifactIds ?? []);
|
|
524
|
-
const outputArtifacts = resolveArtifactReferences(context, input.outputArtifactIds ?? []);
|
|
525
|
-
const targetArtifactId = input.targetArtifactId
|
|
526
|
-
? context.artifactRow(input.targetArtifactId).artifact_id
|
|
527
|
-
: null;
|
|
528
|
-
const mutation = await context.store.semantic({
|
|
529
|
-
operation: `action:${input.operation}`,
|
|
530
|
-
...(targetArtifactId ? { artifactId: targetArtifactId } : {}),
|
|
531
|
-
details: {
|
|
532
|
-
...(input.details ?? {}),
|
|
533
|
-
actionId,
|
|
534
|
-
phase,
|
|
535
|
-
scope,
|
|
536
|
-
lane: input.lane ?? actor,
|
|
537
|
-
},
|
|
538
|
-
...(input.baseRevision ? { baseRevision: input.baseRevision } : {}),
|
|
539
|
-
writeSet: input.writeSet ?? [],
|
|
540
|
-
}, [
|
|
541
|
-
"actions",
|
|
542
|
-
"action_events",
|
|
543
|
-
"action_parents",
|
|
544
|
-
"action_artifacts",
|
|
545
|
-
"action_write_set",
|
|
546
|
-
], (operationId, now) => {
|
|
547
|
-
context.store.db
|
|
548
|
-
.prepare(`INSERT INTO actions(
|
|
549
|
-
action_id, operation, scope, actor, lane, phase,
|
|
550
|
-
base_revision, target_artifact_id, target_action_id,
|
|
551
|
-
layout_json, details_json, created_at
|
|
552
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
553
|
-
ON CONFLICT(action_id) DO UPDATE SET
|
|
554
|
-
operation=excluded.operation,
|
|
555
|
-
scope=excluded.scope,
|
|
556
|
-
actor=excluded.actor,
|
|
557
|
-
lane=excluded.lane,
|
|
558
|
-
phase=excluded.phase,
|
|
559
|
-
base_revision=excluded.base_revision,
|
|
560
|
-
target_artifact_id=excluded.target_artifact_id,
|
|
561
|
-
target_action_id=excluded.target_action_id,
|
|
562
|
-
layout_json=excluded.layout_json,
|
|
563
|
-
details_json=excluded.details_json`)
|
|
564
|
-
.run(actionId, input.operation, scope, actor, input.lane ?? actor, phase, input.baseRevision ?? null, targetArtifactId, input.targetActionId ?? null, input.layout ? canonicalJson(input.layout) : null, canonicalJson(input.details ?? {}), now);
|
|
565
|
-
context.store.db
|
|
566
|
-
.prepare(`INSERT INTO action_events(
|
|
567
|
-
event_id, action_id, operation_id, phase,
|
|
568
|
-
details_json, created_at
|
|
569
|
-
) VALUES (?, ?, ?, ?, ?, ?)`)
|
|
570
|
-
.run(newUuidV7(), actionId, operationId, phase, canonicalJson(input.details ?? {}), now);
|
|
571
|
-
replaceActionLinks(context, actionId, input.parentActionIds ?? [], inputArtifacts, outputArtifacts, input.writeSet ?? []);
|
|
572
|
-
});
|
|
573
|
-
return ok({
|
|
574
|
-
action: requiredAction(context, actionId),
|
|
575
|
-
revision: revisionForHash(context, mutation.revision),
|
|
576
|
-
}, mutation.revision);
|
|
577
|
-
});
|
|
578
|
-
}
|
|
579
|
-
function requiredAction(context, actionId) {
|
|
580
|
-
const row = context.store.db
|
|
581
|
-
.prepare(`${ACTION_SELECT} WHERE action_id=?`)
|
|
582
|
-
.get(actionId);
|
|
583
|
-
if (!row) {
|
|
584
|
-
throw new EngineFault({
|
|
585
|
-
code: "NOT_FOUND",
|
|
586
|
-
message: `History action not found: ${actionId}`,
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
return actionFromRow(context, row);
|
|
590
|
-
}
|
|
591
|
-
function actionFromRow(context, row) {
|
|
592
|
-
const parents = context.store.db
|
|
593
|
-
.prepare("SELECT parent_action_id FROM action_parents WHERE action_id=?")
|
|
594
|
-
.all(row.action_id).map((item) => item.parent_action_id);
|
|
595
|
-
const links = context.store.db
|
|
596
|
-
.prepare(`SELECT artifact_id, direction
|
|
597
|
-
FROM action_artifacts WHERE action_id=?`)
|
|
598
|
-
.all(row.action_id);
|
|
599
|
-
const events = context.store.db
|
|
600
|
-
.prepare(`SELECT event_id, action_id, operation_id, phase,
|
|
601
|
-
details_json, created_at
|
|
602
|
-
FROM action_events WHERE action_id=?
|
|
603
|
-
ORDER BY created_at, event_id`)
|
|
604
|
-
.all(row.action_id);
|
|
605
|
-
const revisionsByOperation = new Map(allRevisions(context).map((revision) => [revision.operationId, revision]));
|
|
606
|
-
return {
|
|
607
|
-
id: row.action_id,
|
|
608
|
-
operation: row.operation,
|
|
609
|
-
title: titleForOperation(row.operation),
|
|
610
|
-
scope: row.scope,
|
|
611
|
-
actor: row.actor,
|
|
612
|
-
lane: row.lane,
|
|
613
|
-
date: new Date(row.created_at).toISOString(),
|
|
614
|
-
phase: row.phase,
|
|
615
|
-
...(row.base_revision ? { baseRevision: row.base_revision } : {}),
|
|
616
|
-
parentActionIds: parents,
|
|
617
|
-
inputArtifacts: links
|
|
618
|
-
.filter((link) => link.direction === "input")
|
|
619
|
-
.map((link) => artifactRef(context, link.artifact_id)),
|
|
620
|
-
outputArtifacts: links
|
|
621
|
-
.filter((link) => link.direction === "output")
|
|
622
|
-
.map((link) => artifactRef(context, link.artifact_id)),
|
|
623
|
-
...(row.target_artifact_id
|
|
624
|
-
? { targetArtifactId: row.target_artifact_id }
|
|
625
|
-
: {}),
|
|
626
|
-
...(row.target_action_id ? { targetActionId: row.target_action_id } : {}),
|
|
627
|
-
...(row.layout_json
|
|
628
|
-
? {
|
|
629
|
-
layout: parseJson(row.layout_json, { stage: 0, column: 0 }),
|
|
630
|
-
}
|
|
631
|
-
: {}),
|
|
632
|
-
details: parseJson(row.details_json, {}),
|
|
633
|
-
events: events.map((event) => {
|
|
634
|
-
const revision = revisionsByOperation.get(event.operation_id);
|
|
635
|
-
return {
|
|
636
|
-
id: event.event_id,
|
|
637
|
-
revision: revision?.hash ?? context.store.head,
|
|
638
|
-
phase: event.phase,
|
|
639
|
-
date: new Date(event.created_at).toISOString(),
|
|
640
|
-
details: parseJson(event.details_json, {}),
|
|
641
|
-
files: revision?.files ?? [],
|
|
642
|
-
fileChanges: revision?.fileChanges ?? [],
|
|
643
|
-
};
|
|
644
|
-
}),
|
|
645
|
-
};
|
|
646
|
-
}
|
|
647
|
-
function replaceActionLinks(context, actionId, parents, inputs, outputs, writeSet) {
|
|
648
|
-
context.store.db
|
|
649
|
-
.prepare("DELETE FROM action_parents WHERE action_id=?")
|
|
650
|
-
.run(actionId);
|
|
651
|
-
context.store.db
|
|
652
|
-
.prepare("DELETE FROM action_artifacts WHERE action_id=?")
|
|
653
|
-
.run(actionId);
|
|
654
|
-
context.store.db
|
|
655
|
-
.prepare("DELETE FROM action_write_set WHERE action_id=?")
|
|
656
|
-
.run(actionId);
|
|
657
|
-
const insertParent = context.store.db.prepare("INSERT INTO action_parents(action_id, parent_action_id) VALUES (?, ?)");
|
|
658
|
-
for (const parent of new Set(parents))
|
|
659
|
-
insertParent.run(actionId, parent);
|
|
660
|
-
const insertArtifact = context.store.db.prepare(`INSERT INTO action_artifacts(action_id, artifact_id, direction)
|
|
661
|
-
VALUES (?, ?, ?)`);
|
|
662
|
-
for (const artifactId of new Set(inputs)) {
|
|
663
|
-
insertArtifact.run(actionId, artifactId, "input");
|
|
664
|
-
}
|
|
665
|
-
for (const artifactId of new Set(outputs)) {
|
|
666
|
-
insertArtifact.run(actionId, artifactId, "output");
|
|
667
|
-
}
|
|
668
|
-
const insertResource = context.store.db.prepare("INSERT INTO action_write_set(action_id, resource) VALUES (?, ?)");
|
|
669
|
-
for (const resource of new Set(writeSet))
|
|
670
|
-
insertResource.run(actionId, resource);
|
|
671
|
-
}
|
|
672
|
-
function assertWriteSet(context, baseRevision, writeSet) {
|
|
673
|
-
if (!baseRevision || baseRevision === context.store.head)
|
|
674
|
-
return;
|
|
675
|
-
const revisions = allRevisions(context);
|
|
676
|
-
let baseIndex = revisions.findIndex((revision) => revision.hash === baseRevision || revision.hash.startsWith(baseRevision));
|
|
677
|
-
if (baseIndex < 0) {
|
|
678
|
-
const commit = context.store.db.doltLog().find((item) => item.commit_hash === baseRevision || item.commit_hash.startsWith(baseRevision));
|
|
679
|
-
if (!commit) {
|
|
680
|
-
throw new EngineFault({
|
|
681
|
-
code: "STALE_REVISION",
|
|
682
|
-
message: `Base revision not found: ${baseRevision}`,
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
// Initialization has no operation record, so it is older than every
|
|
686
|
-
// operation-bearing revision we can inspect for a write-set conflict.
|
|
687
|
-
baseIndex = revisions.length;
|
|
688
|
-
}
|
|
689
|
-
const requested = new Set(writeSet);
|
|
690
|
-
const conflicts = new Set();
|
|
691
|
-
for (const revision of revisions.slice(0, baseIndex)) {
|
|
692
|
-
const changed = revision.details?.writeSet;
|
|
693
|
-
if (!Array.isArray(changed))
|
|
694
|
-
continue;
|
|
695
|
-
for (const resource of changed) {
|
|
696
|
-
if (typeof resource === "string" && requested.has(resource)) {
|
|
697
|
-
conflicts.add(resource);
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
if (conflicts.size > 0) {
|
|
702
|
-
throw new EngineFault({
|
|
703
|
-
code: "ACTION_CONFLICT",
|
|
704
|
-
message: `Action conflicts with newer changes: ${[...conflicts].join(", ")}`,
|
|
705
|
-
details: { resources: [...conflicts] },
|
|
706
|
-
});
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
421
|
function resetArtifactRuntime(context, artifactId, now) {
|
|
710
422
|
context.store.db
|
|
711
423
|
.prepare(`INSERT INTO runtime_artifact_views(
|
|
@@ -759,111 +471,17 @@ function abortActiveJobs(context, now, message) {
|
|
|
759
471
|
}
|
|
760
472
|
function filesAt(context, revision, artifactId) {
|
|
761
473
|
return context.store.db
|
|
762
|
-
.prepare(`SELECT artifact_id, path, object_hash,
|
|
474
|
+
.prepare(`SELECT artifact_id, path, object_hash, created_at
|
|
763
475
|
FROM dolt_at_artifact_files(?) WHERE artifact_id=? ORDER BY path`)
|
|
764
476
|
.all(revision, artifactId);
|
|
765
477
|
}
|
|
766
|
-
function rowsForArtifactIds(context, table, columns, revision, artifactIds) {
|
|
767
|
-
if (artifactIds.length === 0)
|
|
768
|
-
return [];
|
|
769
|
-
const placeholders = artifactIds.map(() => "?").join(", ");
|
|
770
|
-
return context.store.db
|
|
771
|
-
.prepare(`SELECT ${columns} FROM dolt_at_${table}(?)
|
|
772
|
-
WHERE artifact_id IN (${placeholders})`)
|
|
773
|
-
.all(revision, ...artifactIds);
|
|
774
|
-
}
|
|
775
|
-
function rowsForNotebookIds(context, table, columns, revision, notebookIds) {
|
|
776
|
-
if (notebookIds.length === 0)
|
|
777
|
-
return [];
|
|
778
|
-
const placeholders = notebookIds.map(() => "?").join(", ");
|
|
779
|
-
return context.store.db
|
|
780
|
-
.prepare(`SELECT ${columns} FROM dolt_at_${table}(?)
|
|
781
|
-
WHERE notebook_id IN (${placeholders})`)
|
|
782
|
-
.all(revision, ...notebookIds);
|
|
783
|
-
}
|
|
784
478
|
function insertFiles(context, files) {
|
|
785
479
|
const insert = context.store.db.prepare(`INSERT INTO artifact_files(
|
|
786
|
-
artifact_id, path, object_hash,
|
|
787
|
-
) VALUES (?, ?, ?,
|
|
480
|
+
artifact_id, path, object_hash, created_at
|
|
481
|
+
) VALUES (?, ?, ?, ?)`);
|
|
788
482
|
for (const row of files) {
|
|
789
|
-
insert.run(row.artifact_id, row.path, row.object_hash, row.
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
function insertNotebookChildren(context, cells, edges, runs) {
|
|
793
|
-
const insertCell = context.store.db.prepare(`INSERT INTO cells(
|
|
794
|
-
notebook_id, cell_id, type, title, position_x, position_y,
|
|
795
|
-
entity_id, prompt, model, inputs_json, output_artifact_id
|
|
796
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
797
|
-
for (const row of cells) {
|
|
798
|
-
insertCell.run(row.notebook_id, row.cell_id, row.type, row.title, row.position_x, row.position_y, row.entity_id, row.prompt, row.model, row.inputs_json, row.output_artifact_id);
|
|
799
|
-
}
|
|
800
|
-
const insertEdge = context.store.db.prepare(`INSERT INTO edges(
|
|
801
|
-
notebook_id, edge_id, source_cell_id, target_cell_id, target_input
|
|
802
|
-
) VALUES (?, ?, ?, ?, ?)`);
|
|
803
|
-
for (const row of edges) {
|
|
804
|
-
insertEdge.run(row.notebook_id, row.edge_id, row.source_cell_id, row.target_cell_id, row.target_input);
|
|
805
|
-
}
|
|
806
|
-
const insertRun = context.store.db.prepare(`INSERT INTO runs(
|
|
807
|
-
run_id, notebook_id, status, started_at, completed_at,
|
|
808
|
-
cell_order_json, outputs_json, error
|
|
809
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
810
|
-
for (const row of runs) {
|
|
811
|
-
insertRun.run(row.run_id, row.notebook_id, row.status, row.started_at, row.completed_at, row.cell_order_json, row.outputs_json, row.error);
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
function operationFromDiff(row) {
|
|
815
|
-
const prefix = row.diff_type === "removed" ? "from_" : "to_";
|
|
816
|
-
const operationId = row[`${prefix}operation_id`];
|
|
817
|
-
const operation = row[`${prefix}operation`];
|
|
818
|
-
const detailsJson = row[`${prefix}details_json`];
|
|
819
|
-
const writeSetJson = row[`${prefix}write_set_json`];
|
|
820
|
-
const createdAt = row[`${prefix}created_at`];
|
|
821
|
-
const author = row[`${prefix}author`];
|
|
822
|
-
if (typeof operationId !== "string" ||
|
|
823
|
-
typeof operation !== "string" ||
|
|
824
|
-
typeof detailsJson !== "string" ||
|
|
825
|
-
typeof writeSetJson !== "string" ||
|
|
826
|
-
typeof createdAt !== "number" ||
|
|
827
|
-
typeof author !== "string") {
|
|
828
|
-
return null;
|
|
829
|
-
}
|
|
830
|
-
const artifactId = row[`${prefix}artifact_id`];
|
|
831
|
-
const baseRevision = row[`${prefix}base_revision`];
|
|
832
|
-
return {
|
|
833
|
-
operation_id: operationId,
|
|
834
|
-
operation,
|
|
835
|
-
artifact_id: typeof artifactId === "string" ? artifactId : null,
|
|
836
|
-
details_json: detailsJson,
|
|
837
|
-
write_set_json: writeSetJson,
|
|
838
|
-
base_revision: typeof baseRevision === "string" ? baseRevision : null,
|
|
839
|
-
created_at: createdAt,
|
|
840
|
-
author,
|
|
841
|
-
};
|
|
842
|
-
}
|
|
843
|
-
function revisionFileChanges(rows, artifactId) {
|
|
844
|
-
const changes = [];
|
|
845
|
-
for (const row of rows) {
|
|
846
|
-
const fromArtifact = row.from_artifact_id;
|
|
847
|
-
const toArtifact = row.to_artifact_id;
|
|
848
|
-
if (artifactId &&
|
|
849
|
-
fromArtifact !== artifactId &&
|
|
850
|
-
toArtifact !== artifactId) {
|
|
851
|
-
continue;
|
|
852
|
-
}
|
|
853
|
-
const fromPath = typeof row.from_path === "string" ? row.from_path : undefined;
|
|
854
|
-
const toPath = typeof row.to_path === "string" ? row.to_path : undefined;
|
|
855
|
-
const file = toPath ?? fromPath;
|
|
856
|
-
if (!file)
|
|
857
|
-
continue;
|
|
858
|
-
changes.push({
|
|
859
|
-
status: row.diff_type,
|
|
860
|
-
file,
|
|
861
|
-
...(fromPath && toPath && fromPath !== toPath
|
|
862
|
-
? { oldFile: fromPath }
|
|
863
|
-
: {}),
|
|
864
|
-
});
|
|
483
|
+
insert.run(row.artifact_id, row.path, row.object_hash, row.created_at);
|
|
865
484
|
}
|
|
866
|
-
return changes;
|
|
867
485
|
}
|
|
868
486
|
function artifactSlugAt(context, artifactId, revision) {
|
|
869
487
|
const row = context.store.db
|
|
@@ -871,51 +489,4 @@ function artifactSlugAt(context, artifactId, revision) {
|
|
|
871
489
|
.get(revision, artifactId);
|
|
872
490
|
return row?.slug;
|
|
873
491
|
}
|
|
874
|
-
function resolveArtifactReferences(context, references) {
|
|
875
|
-
return [
|
|
876
|
-
...new Set(references.map((reference) => context.artifactRow(reference).artifact_id)),
|
|
877
|
-
];
|
|
878
|
-
}
|
|
879
|
-
function artifactRef(context, artifactId) {
|
|
880
|
-
try {
|
|
881
|
-
const artifact = context.artifactRowById(artifactId);
|
|
882
|
-
return {
|
|
883
|
-
id: artifact.artifact_id,
|
|
884
|
-
slug: artifact.slug,
|
|
885
|
-
kind: historyArtifactKind(artifact.kind),
|
|
886
|
-
};
|
|
887
|
-
}
|
|
888
|
-
catch {
|
|
889
|
-
return { id: artifactId, slug: artifactId, kind: "unknown" };
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
function historyArtifactKind(kind) {
|
|
893
|
-
return kind;
|
|
894
|
-
}
|
|
895
|
-
function assertActionPhase(value) {
|
|
896
|
-
if (![
|
|
897
|
-
"requested",
|
|
898
|
-
"started",
|
|
899
|
-
"completed",
|
|
900
|
-
"failed",
|
|
901
|
-
"cancelled",
|
|
902
|
-
"conflicted",
|
|
903
|
-
].includes(value)) {
|
|
904
|
-
throw new Error(`Invalid action phase: ${value}`);
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
function assertActionScope(value) {
|
|
908
|
-
if (!["book", "artifact", "layout", "external", "system"].includes(value)) {
|
|
909
|
-
throw new Error(`Invalid action scope: ${value}`);
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
function titleForOperation(operation) {
|
|
913
|
-
return operation.replaceAll("_", " ").replaceAll("-", " ");
|
|
914
|
-
}
|
|
915
|
-
const ACTION_SELECT = `
|
|
916
|
-
SELECT action_id, operation, scope, actor, lane, phase,
|
|
917
|
-
base_revision, target_artifact_id, target_action_id,
|
|
918
|
-
layout_json, details_json, created_at
|
|
919
|
-
FROM actions
|
|
920
|
-
`;
|
|
921
492
|
//# sourceMappingURL=history.js.map
|