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.
Files changed (148) hide show
  1. package/README.md +87 -41
  2. package/dist/artifact-names.d.ts +6 -0
  3. package/dist/artifact-names.d.ts.map +1 -0
  4. package/dist/artifact-names.js +56 -0
  5. package/dist/artifact-names.js.map +1 -0
  6. package/dist/artifacts.d.ts.map +1 -1
  7. package/dist/artifacts.js +57 -23
  8. package/dist/artifacts.js.map +1 -1
  9. package/dist/books.js +2 -1
  10. package/dist/books.js.map +1 -1
  11. package/dist/cas.d.ts +27 -1
  12. package/dist/cas.d.ts.map +1 -1
  13. package/dist/cas.js +78 -6
  14. package/dist/cas.js.map +1 -1
  15. package/dist/catalog-metadata.d.ts +56 -0
  16. package/dist/catalog-metadata.d.ts.map +1 -0
  17. package/dist/catalog-metadata.js +42 -0
  18. package/dist/catalog-metadata.js.map +1 -0
  19. package/dist/communications.js +4 -2
  20. package/dist/communications.js.map +1 -1
  21. package/dist/context.d.ts +6 -1
  22. package/dist/context.d.ts.map +1 -1
  23. package/dist/context.js +35 -6
  24. package/dist/context.js.map +1 -1
  25. package/dist/domain.d.ts +4 -0
  26. package/dist/domain.d.ts.map +1 -1
  27. package/dist/domain.js +870 -58
  28. package/dist/domain.js.map +1 -1
  29. package/dist/edit-policy.d.ts +3 -0
  30. package/dist/edit-policy.d.ts.map +1 -0
  31. package/dist/edit-policy.js +6 -0
  32. package/dist/edit-policy.js.map +1 -0
  33. package/dist/edits.d.ts +10 -0
  34. package/dist/edits.d.ts.map +1 -0
  35. package/dist/edits.js +1392 -0
  36. package/dist/edits.js.map +1 -0
  37. package/dist/engine-types.d.ts +110 -60
  38. package/dist/engine-types.d.ts.map +1 -1
  39. package/dist/engine-types.js +33 -0
  40. package/dist/engine-types.js.map +1 -1
  41. package/dist/engine.d.ts +69 -8
  42. package/dist/engine.d.ts.map +1 -1
  43. package/dist/engine.js +24 -18
  44. package/dist/engine.js.map +1 -1
  45. package/dist/files.d.ts.map +1 -1
  46. package/dist/files.js +31 -27
  47. package/dist/files.js.map +1 -1
  48. package/dist/fork.d.ts +147 -0
  49. package/dist/fork.d.ts.map +1 -0
  50. package/dist/fork.js +609 -0
  51. package/dist/fork.js.map +1 -0
  52. package/dist/history.d.ts +0 -4
  53. package/dist/history.d.ts.map +1 -1
  54. package/dist/history.js +166 -595
  55. package/dist/history.js.map +1 -1
  56. package/dist/index.d.ts +16 -4
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +12 -2
  59. package/dist/index.js.map +1 -1
  60. package/dist/job-queue.d.ts +2 -0
  61. package/dist/job-queue.d.ts.map +1 -1
  62. package/dist/job-queue.js +29 -2
  63. package/dist/job-queue.js.map +1 -1
  64. package/dist/media.d.ts.map +1 -1
  65. package/dist/media.js +1 -0
  66. package/dist/media.js.map +1 -1
  67. package/dist/merge-policy.d.ts +99 -0
  68. package/dist/merge-policy.d.ts.map +1 -0
  69. package/dist/merge-policy.js +310 -0
  70. package/dist/merge-policy.js.map +1 -0
  71. package/dist/metadata.d.ts.map +1 -1
  72. package/dist/metadata.js +15 -20
  73. package/dist/metadata.js.map +1 -1
  74. package/dist/migration.d.ts +9 -0
  75. package/dist/migration.d.ts.map +1 -0
  76. package/dist/migration.js +534 -0
  77. package/dist/migration.js.map +1 -0
  78. package/dist/mvp-contract-fixtures.d.ts +244 -0
  79. package/dist/mvp-contract-fixtures.d.ts.map +1 -0
  80. package/dist/mvp-contract-fixtures.js +556 -0
  81. package/dist/mvp-contract-fixtures.js.map +1 -0
  82. package/dist/mvp-contracts.d.ts +761 -0
  83. package/dist/mvp-contracts.d.ts.map +1 -0
  84. package/dist/mvp-contracts.js +59 -0
  85. package/dist/mvp-contracts.js.map +1 -0
  86. package/dist/mvp-time.d.ts +54 -0
  87. package/dist/mvp-time.d.ts.map +1 -0
  88. package/dist/mvp-time.js +148 -0
  89. package/dist/mvp-time.js.map +1 -0
  90. package/dist/notebook/types.d.ts +113 -8
  91. package/dist/notebook/types.d.ts.map +1 -1
  92. package/dist/order-keys.d.ts +22 -0
  93. package/dist/order-keys.d.ts.map +1 -0
  94. package/dist/order-keys.js +262 -0
  95. package/dist/order-keys.js.map +1 -0
  96. package/dist/runtime-services.js +4 -2
  97. package/dist/runtime-services.js.map +1 -1
  98. package/dist/schema.d.ts +24 -5
  99. package/dist/schema.d.ts.map +1 -1
  100. package/dist/schema.js +644 -132
  101. package/dist/schema.js.map +1 -1
  102. package/dist/search-benchmark.d.ts +4 -0
  103. package/dist/search-benchmark.d.ts.map +1 -0
  104. package/dist/search-benchmark.js +144 -0
  105. package/dist/search-benchmark.js.map +1 -0
  106. package/dist/sequences.d.ts +20 -0
  107. package/dist/sequences.d.ts.map +1 -0
  108. package/dist/sequences.js +825 -0
  109. package/dist/sequences.js.map +1 -0
  110. package/dist/similarity.js +1 -1
  111. package/dist/storage.d.ts +3 -1
  112. package/dist/storage.d.ts.map +1 -1
  113. package/dist/storage.js +388 -7
  114. package/dist/storage.js.map +1 -1
  115. package/dist/store.d.ts +63 -2
  116. package/dist/store.d.ts.map +1 -1
  117. package/dist/store.js +351 -65
  118. package/dist/store.js.map +1 -1
  119. package/dist/streams.d.ts +10 -0
  120. package/dist/streams.d.ts.map +1 -0
  121. package/dist/streams.js +183 -0
  122. package/dist/streams.js.map +1 -0
  123. package/dist/temporal-models.d.ts +48 -0
  124. package/dist/temporal-models.d.ts.map +1 -0
  125. package/dist/temporal-models.js +278 -0
  126. package/dist/temporal-models.js.map +1 -0
  127. package/dist/temporal-search.d.ts +29 -0
  128. package/dist/temporal-search.d.ts.map +1 -0
  129. package/dist/temporal-search.js +1507 -0
  130. package/dist/temporal-search.js.map +1 -0
  131. package/dist/transcripts.d.ts +16 -0
  132. package/dist/transcripts.d.ts.map +1 -0
  133. package/dist/transcripts.js +519 -0
  134. package/dist/transcripts.js.map +1 -0
  135. package/docs/engine-layout.md +346 -106
  136. package/docs/mvp-prd.md +918 -0
  137. package/fixtures/media/vancat.mp4 +0 -0
  138. package/fixtures/media/vancat_profile.jpg +0 -0
  139. package/fixtures/v5/contract-fixtures.json +1532 -0
  140. package/package.json +30 -5
  141. package/dist/history-types.d.ts +0 -74
  142. package/dist/history-types.d.ts.map +0 -1
  143. package/dist/history-types.js +0 -2
  144. package/dist/history-types.js.map +0 -1
  145. package/dist/timeline.d.ts +0 -9
  146. package/dist/timeline.d.ts.map +0 -1
  147. package/dist/timeline.js +0 -236
  148. package/dist/timeline.js.map +0 -1
@@ -1,8 +1,9 @@
1
1
  # Videobook engine layout
2
2
 
3
3
  This document is the column-by-column layout of the engine-owned data model.
4
- It describes schema version 4 as implemented today, then identifies the
5
- additional structures needed for a full non-linear video editor.
4
+ It describes the original schema version 4 layout, notes where later schema
5
+ versions changed the picture (currently v18), and identifies the additional
6
+ structures needed for a full non-linear video editor.
6
7
 
7
8
  The executable source of truth remains
8
9
  [`src/schema.ts`](../src/schema.ts). If this document and the DDL disagree, the
@@ -41,7 +42,7 @@ flowchart LR
41
42
  OUTBOX --> STAGE
42
43
  STAGE --> REV[Dolt main revision]
43
44
 
44
- FILES[Artifact file APIs] --> CAS[Immutable SHA-256 objects]
45
+ FILES[Artifact file APIs] --> CAS[SHA-256 objects, forgettable]
45
46
  FILES --> MAP[Versioned artifact_files mapping]
46
47
  CAS --> WS[Disposable artifact workspace]
47
48
  MAP --> WS
@@ -52,15 +53,24 @@ flowchart LR
52
53
 
53
54
  ## Global invariants
54
55
 
55
- - One engine catalog contains exactly one `book` row and one matching
56
- `timeline` row. There is no project scope.
56
+ - One engine catalog contains exactly one `book` row and one primary
57
+ `sequences` row. There is no project scope.
57
58
  - The only supported live Dolt branch is `main`.
58
59
  - Semantic mutations are SQL transactions followed by forward-only Dolt
59
60
  commits. A restore creates a new commit; it never rewinds the live branch.
60
- - `SEMANTIC_TABLES` is the staging allowlist. Every mutation also records an
61
- `operations` row.
62
- - `runtime_%` and `sqlite_sequence` are ignored by Dolt. Runtime state can be
63
- rebuilt, expired, or invalidated without changing semantic history.
61
+ - `SEMANTIC_TABLES` is the staging allowlist. Every operation declares the
62
+ semantic tables it may write (`OperationInput.tables`, including ON DELETE
63
+ CASCADE targets); a commit probes exactly that set with row-level diffs,
64
+ stages the truly dirty tables, asserts them clean afterwards, and is
65
+ skipped when only runtime bookkeeping changed. A full-catalog sweep runs
66
+ once per open and faults on rows no operation attributed (doltStatus
67
+ itself over-reports and is never trusted for dirtiness). The commit itself
68
+ is the provenance record: the operation, its parameters, write set, and
69
+ base revision ride in a structured commit message under the configured
70
+ identity as `--author`.
71
+ - `runtime_%`, `job_runs`, and `sqlite_sequence` are ignored by Dolt. Runtime
72
+ state can be rebuilt, expired, or invalidated without changing semantic
73
+ history.
64
74
  - Engine-generated surrogate identities are stable UUIDv7 strings; the SQL
65
75
  columns are `TEXT`, so UUID form is enforced by engine APIs rather than a
66
76
  database check. Content identity is a lowercase, 64-character SHA-256.
@@ -68,8 +78,16 @@ flowchart LR
68
78
  currently modeled use integer frames.
69
79
  - JSON is stored as canonical text with recursively sorted object keys.
70
80
  - Deletes are hard deletes. Owned rows cascade; live artifact/entity
71
- references restrict deletion; immutable objects and prior Dolt revisions
72
- remain available.
81
+ references restrict deletion; prior Dolt revisions remain available.
82
+ - Objects are content-immutable but forgettable. `engine.storage.deleteObject`
83
+ forgets one object (refusing `IN_USE` references at HEAD unless forced);
84
+ `engine.storage.gc` sweeps every object nothing references at HEAD.
85
+ Forgetting is a semantic commit that sets `objects.forgotten_at`; the row
86
+ is never deleted, so it remains as the tombstone (hash + size + forgotten
87
+ timestamp) for historical references, and reads of forgotten content
88
+ surface `OBJECT_UNAVAILABLE` rather than failing or lying. Published
89
+ history is append-only, so bulk forgettable text lives behind CAS hashes
90
+ (see "Forgettable data and raw-text audit" below).
73
91
 
74
92
  ## Dolt-versioned semantic schema
75
93
 
@@ -81,16 +99,16 @@ Notation used below:
81
99
  - `→` names the referenced column.
82
100
  - Defaults and checks are shown inline.
83
101
 
84
- There are 25 allowlisted semantic tables.
102
+ There are 34 allowlisted semantic tables.
85
103
 
86
104
  ### Catalog, artifacts, and content
87
105
 
88
106
  | Table | Columns | Keys, constraints, and purpose |
89
107
  | --- | --- | --- |
90
- | `engine_schema` | `singleton INTEGER`<br>`version INTEGER`<br>`created_at INTEGER` | `singleton PK CHECK(singleton = 1)`. Records the clean-break catalog version. Version 4 rejects older catalogs rather than migrating them. |
108
+ | `engine_schema` | `singleton INTEGER`<br>`version INTEGER`<br>`created_at INTEGER` | `singleton PK CHECK(singleton = 1)`. Records the clean-break catalog version. The recorded version rejects older catalogs rather than migrating them. |
91
109
  | `book` | `book_id TEXT`<br>`slug TEXT`<br>`created_at INTEGER` | `book_id PK`; `slug UQ`. Exactly one row per engine root. |
92
110
  | `artifacts` | `artifact_id TEXT`<br>`slug TEXT`<br>`kind TEXT`<br>`created_at INTEGER` | `artifact_id PK`; `slug UQ`; `kind CHECK IN (video, image, audio, script, character, prompt, scene, final)`. The artifact is the stable identity for source media, generated media, documents, and final renders. |
93
- | `objects` | `object_hash TEXT`<br>`size_bytes INTEGER`<br>`created_at INTEGER` | `object_hash PK`; `size_bytes CHECK >= 0`. This is versioned metadata for immutable bytes stored outside the database. |
111
+ | `objects` | `object_hash TEXT`<br>`size_bytes INTEGER`<br>`created_at INTEGER`<br>`forgotten_at INTEGER?` | `object_hash PK`; `size_bytes CHECK >= 0`. Versioned metadata for bytes stored outside the database. Rows are append-only: `forgotten_at` marks a tombstone whose bytes were deleted by `deleteObject`/`gc`. |
94
112
  | `artifact_files` | `artifact_id TEXT`<br>`path TEXT`<br>`object_hash TEXT`<br>`mtime_ms INTEGER`<br>`created_at INTEGER` | `(artifact_id, path) PK`; `artifact_id FK → artifacts.artifact_id ON DELETE CASCADE`; `object_hash FK → objects.object_hash ON DELETE RESTRICT`. Maps a logical artifact path to immutable content. |
95
113
  | `book_metadata` | `key TEXT`<br>`value_json TEXT` | `key PK`. Singleton-book key/value metadata; no redundant `book_id`. |
96
114
  | `artifact_metadata` | `artifact_id TEXT`<br>`key TEXT`<br>`value_json TEXT` | `(artifact_id, key) PK`; `artifact_id FK → artifacts.artifact_id ON DELETE CASCADE`. Extensible artifact metadata that participates in revisions. |
@@ -100,36 +118,47 @@ There are 25 allowlisted semantic tables.
100
118
  | Table | Columns | Keys, constraints, and purpose |
101
119
  | --- | --- | --- |
102
120
  | `entities` | `entity_id TEXT`<br>`type TEXT`<br>`name TEXT`<br>`description TEXT?`<br>`prompt TEXT?`<br>`data_json TEXT DEFAULT '{}'`<br>`created_at INTEGER` | `entity_id PK`; `type CHECK IN (prompt, character, scene)`. Normalized reusable creative concepts. |
103
- | `notebooks` | `notebook_id TEXT`<br>`name TEXT`<br>`properties_json TEXT DEFAULT '{}'`<br>`created_at INTEGER` | `notebook_id PK`. Owns a generation/authoring graph. |
104
- | `cells` | `notebook_id TEXT`<br>`cell_id TEXT`<br>`type TEXT`<br>`title TEXT`<br>`position_x REAL`<br>`position_y REAL`<br>`entity_id TEXT?`<br>`prompt TEXT?`<br>`model TEXT?`<br>`inputs_json TEXT DEFAULT '{}'`<br>`output_artifact_id TEXT?` | `(notebook_id, cell_id) PK`; notebook `FK → notebooks ON DELETE CASCADE`; entity `FK → entities ON DELETE RESTRICT`; output `FK artifacts ON DELETE RESTRICT`; `type CHECK IN (prompt, character, scene, asset, image, video)`. |
121
+ | `notebooks` | `notebook_id TEXT`<br>`name TEXT`<br>`created_at INTEGER` | `notebook_id PK`. Owns a generation/authoring graph. Schema v18 removed the monolithic `properties_json` cell. |
122
+ | `notebook_fields` | `notebook_id TEXT`<br>`field TEXT`<br>`value_json TEXT` | `(notebook_id, field) PK`; notebook `FK → notebooks ON DELETE CASCADE`; `field` is restricted to the typed public notebook fields. Stores optional notebook-level workflow values independently. |
123
+ | `cells` | `notebook_id TEXT`<br>`cell_id TEXT`<br>`type TEXT`<br>`slug TEXT`<br>`grid_row INTEGER`<br>`grid_column INTEGER`<br>`output_entity_id TEXT?`<br>`prompt TEXT?`<br>`provider TEXT?`<br>`model TEXT?`<br>`operation TEXT?`<br>`tool TEXT?`<br>`inputs_json TEXT DEFAULT '{}'`<br>`output_artifact_id TEXT?` | `(notebook_id, cell_id) PK`; `(notebook_id, slug) UQ`; notebook `FK → notebooks ON DELETE CASCADE`; entity and output artifacts use `ON DELETE RESTRICT`. Cell types and kind-specific slug prefixes are checked by the schema. |
124
+ | `notebook_cell_executions` | `notebook_id TEXT`<br>`cell_id TEXT`<br>fingerprint/status/output/provider/run/timestamp/tool/error fields<br>`stale INTEGER`<br>`fixture_baseline INTEGER` | `(notebook_id, cell_id) PK` and composite cell FK with cascade. Gives each cell's execution and staleness state its own merge boundary. |
125
+ | `notebook_generation_plans` | `notebook_id TEXT`<br>`plan_id TEXT`<br>`cell_id TEXT`<br>`status TEXT`<br>`plan_json TEXT`<br>output/error/timestamp fields | `(notebook_id, plan_id) PK`; composite cell FK with cascade. |
126
+ | `notebook_run_plans` | `notebook_id TEXT`<br>`plan_id TEXT`<br>`status TEXT`<br>plan/cost/fingerprint/output fields<br>timestamps | `(notebook_id, plan_id) PK`. Stores approval and execution plans as independently mergeable rows. |
127
+ | `notebook_transcript_edits` | `notebook_id TEXT`<br>`action_id TEXT`<br>`kind TEXT`<br>`restored INTEGER`<br>`payload_json TEXT` | `(notebook_id, action_id) PK`; notebook FK with cascade. |
128
+ | `notebook_transcript_attachments` | `notebook_id TEXT`<br>`attachment_id TEXT`<br>`payload_json TEXT` | `(notebook_id, attachment_id) PK`; notebook FK with cascade. |
105
129
  | `edges` | `notebook_id TEXT`<br>`edge_id TEXT`<br>`source_cell_id TEXT`<br>`target_cell_id TEXT`<br>`target_input TEXT` | `(notebook_id, edge_id) PK`; notebook `FK → notebooks ON DELETE CASCADE`; composite source and target FKs reference cells in the same notebook and cascade on cell deletion. |
106
130
  | `runs` | `run_id TEXT`<br>`notebook_id TEXT`<br>`status TEXT`<br>`started_at INTEGER`<br>`completed_at INTEGER`<br>`cell_order_json TEXT`<br>`outputs_json TEXT`<br>`error TEXT?` | `run_id PK`; notebook `FK → notebooks ON DELETE CASCADE`; `status CHECK IN (completed, failed, aborted)`. Terminal, versioned notebook execution records. |
107
131
 
108
- ### Current timeline and media editing state
132
+ ### Sequence timeline and media editing state
133
+
134
+ Sequences are the single timeline model. `sequences`, `sequence_tracks`,
135
+ `sequence_clips`, `clip_links`, `clip_transforms`, `transitions`, and
136
+ `caption_cues` hold the edit; `engine.sequences` and `engine.edits` are the
137
+ only timeline APIs. The legacy schema-v4 `timeline`, `timeline_slots`, and
138
+ `timeline_audio` tables were removed in v17 — schema-v4 imports convert
139
+ still-image slots into clips on the primary sequence instead. See
140
+ [`src/schema.ts`](../src/schema.ts) for the full sequence DDL.
109
141
 
110
142
  | Table | Columns | Keys, constraints, and purpose |
111
143
  | --- | --- | --- |
112
- | `timeline` | `book_id TEXT`<br>`render TEXT DEFAULT 'landscape'` | `book_id PK FK → book.book_id ON DELETE CASCADE`; `render CHECK IN (landscape, portrait, square)`. One sequence-level orientation row. |
113
- | `timeline_slots` | `slot_id TEXT`<br>`artifact_id TEXT`<br>`ordinal INTEGER`<br>`volume REAL?`<br>`audio_fade_in REAL?`<br>`audio_fade_out REAL?` | `slot_id PK`; artifact `FK → artifacts ON DELETE RESTRICT`; `ordinal CHECK >= 0`; volume and fades are null or `>= 0`. A single ordered visual lane; ownership by the singleton timeline is implicit. |
114
- | `timeline_audio` | `audio_id TEXT`<br>`artifact_id TEXT`<br>`ordinal INTEGER`<br>`start_frame INTEGER`<br>`duration_frames INTEGER`<br>`volume REAL?`<br>`fade_in REAL?`<br>`fade_out REAL?` | `audio_id PK`; artifact `FK → artifacts ON DELETE RESTRICT`; ordinal/start `CHECK >= 0`; duration `CHECK > 0`; volume and fades are null or `>= 0`. Ordered overlays with explicit timeline timing. |
115
144
  | `audio_waveforms` | `artifact_id TEXT`<br>`peaks_json TEXT` | `artifact_id PK FK → artifacts.artifact_id ON DELETE CASCADE`. Versioned waveform peaks used by editing UI. |
116
145
 
117
146
  The normalized current relationship is:
118
147
 
119
148
  ```mermaid
120
149
  erDiagram
121
- BOOK ||--|| TIMELINE : owns
122
- ARTIFACTS ||--o{ TIMELINE_SLOTS : supplies
123
- ARTIFACTS ||--o{ TIMELINE_AUDIO : supplies
150
+ BOOK ||--o{ SEQUENCES : owns
151
+ SEQUENCES ||--o{ SEQUENCE_TRACKS : orders
152
+ SEQUENCE_TRACKS ||--o{ SEQUENCE_CLIPS : places
153
+ ARTIFACTS ||--o{ SEQUENCE_CLIPS : supplies
124
154
  ARTIFACTS ||--o| AUDIO_WAVEFORMS : has
125
155
  ARTIFACTS ||--o{ ARTIFACT_FILES : maps
126
156
  OBJECTS ||--o{ ARTIFACT_FILES : backs
127
157
  ```
128
158
 
129
- `timeline_slots` order is defined by `(ordinal, slot_id)`. It does not
130
- currently store timeline start, source in/out, duration, track, transform,
131
- speed, opacity, effects, or transitions. `timeline_audio` does store start and
132
- duration frames, but there is no catalog-level frame-rate/timebase row yet.
159
+ Sequence timing is integer frames against each sequence's rational frame
160
+ rate; timed clip sources use rational timebase ticks. Track and clip
161
+ ordering uses fractional order keys with the row UUID as tie-breaker.
133
162
 
134
163
  ### Prompts and messages
135
164
 
@@ -138,25 +167,39 @@ duration frames, but there is no catalog-level frame-rate/timebase row yet.
138
167
  | `prompt_entries` | `prompt_id TEXT`<br>`surface TEXT`<br>`prompt TEXT`<br>`context_json TEXT DEFAULT '{}'`<br>`created_at INTEGER` | `prompt_id PK`. Semantic prompt history grouped by UI or agent surface. |
139
168
  | `messages` | `message_id TEXT`<br>`role TEXT`<br>`body_json TEXT`<br>`created_at INTEGER` | `message_id PK`. Structured semantic conversation history. |
140
169
 
141
- ### Operations, action graph, and terminal jobs
170
+ ### Provenance and terminal jobs
142
171
 
143
- | Table | Columns | Keys, constraints, and purpose |
144
- | --- | --- | --- |
145
- | `operations` | `operation_id TEXT`<br>`operation TEXT`<br>`artifact_id TEXT?`<br>`details_json TEXT DEFAULT '{}'`<br>`write_set_json TEXT DEFAULT '[]'`<br>`base_revision TEXT?`<br>`created_at INTEGER`<br>`author TEXT` | `operation_id PK`. One low-level provenance row is written with every semantic mutation. Artifact and revision references are deliberately loose so audit history survives deletion. |
146
- | `actions` | `action_id TEXT`<br>`operation TEXT`<br>`scope TEXT`<br>`actor TEXT`<br>`lane TEXT`<br>`phase TEXT`<br>`base_revision TEXT?`<br>`target_artifact_id TEXT?`<br>`target_action_id TEXT?`<br>`layout_json TEXT?`<br>`details_json TEXT DEFAULT '{}'`<br>`created_at INTEGER` | `action_id PK`; scope `CHECK IN (book, artifact, layout, external, system)`; phase `CHECK IN (requested, started, completed, failed, cancelled, conflicted)`. High-level workflow/action projection. Targets are loose audit references. |
147
- | `action_events` | `event_id TEXT`<br>`action_id TEXT`<br>`operation_id TEXT`<br>`phase TEXT`<br>`details_json TEXT DEFAULT '{}'`<br>`created_at INTEGER` | `event_id PK`; action `FK → actions ON DELETE CASCADE`; same phase check as `actions`. `operation_id` is a loose provenance link. |
148
- | `action_parents` | `action_id TEXT`<br>`parent_action_id TEXT` | `(action_id, parent_action_id) PK`; child `FK → actions ON DELETE CASCADE`; parent `FK → actions ON DELETE RESTRICT`. Forms the action DAG. |
149
- | `action_artifacts` | `action_id TEXT`<br>`artifact_id TEXT`<br>`direction TEXT` | `(action_id, artifact_id, direction) PK`; action `FK → actions ON DELETE CASCADE`; `direction CHECK IN (input, output)`. Artifact ID is deliberately loose for durable lineage. |
150
- | `action_write_set` | `action_id TEXT`<br>`resource TEXT` | `(action_id, resource) PK`; action `FK → actions ON DELETE CASCADE`. Normalized resources used for overlap/conflict reasoning. |
151
- | `job_runs` | `run_id TEXT`<br>`artifact_id TEXT?`<br>`job_type TEXT`<br>`state TEXT`<br>`payload_json TEXT`<br>`result_json TEXT?`<br>`error_json TEXT?`<br>`started_at INTEGER?`<br>`finished_at INTEGER` | `run_id PK`; `state CHECK IN (done, failed, aborted)`. Terminal job audit. Artifact references are loose so completed history survives artifact deletion. |
152
-
153
- `actions.layout_json` currently maps to the public
154
- `HistoryLayout { stage: number; column: number }`. It is workflow layout
155
- metadata, not spatial video composition.
172
+ Provenance is not a set of tables; it is the Dolt commit log itself. Every
173
+ semantic commit message is structured and machine-parseable:
174
+
175
+ ```
176
+ <operation>[ artifact:<artifactId>]
177
+
178
+ op-id: <uuidv7>
179
+ base-revision: <commit hash> (when the mutation declared one)
180
+ actor: <operation actor> (when the mutation declared one)
181
+ write-set: <canonical JSON array> (when non-empty)
182
+ details: <canonical JSON object> (when non-empty)
183
+ ```
184
+
185
+ History listings, per-artifact history, stale write-set conflict checks, and
186
+ edit restore are all derived from `dolt_log`, `dolt_diff`, and
187
+ `dolt_at_<table>(revision)` projections — there is no parallel `operations`,
188
+ `actions`, or `edit_batches` record to merge. doltlite rejects commit
189
+ messages of 65536 bytes or more, so an oversized `details` or `write-set`
190
+ payload is dropped in favor of a `details-omitted` / `write-set-omitted`
191
+ trailer that records the payload size; projections treat omitted trailers as
192
+ empty.
193
+
194
+ Terminal job audit rows live in `job_runs` (`run_id PK`; `state CHECK IN
195
+ (done, failed, aborted)`), an ignored runtime table rather than a semantic
196
+ one: job payload blobs are rebuildable bookkeeping, so recording a terminal
197
+ job never mints a commit. Artifact references are loose so completed history
198
+ survives artifact deletion.
156
199
 
157
200
  ### Committed Dolt policy table
158
201
 
159
- `dolt_ignore` is created and staged separately from the 25-table allowlist:
202
+ `dolt_ignore` is created and staged separately from the 28-table allowlist:
160
203
 
161
204
  | Column | Constraint |
162
205
  | --- | --- |
@@ -168,8 +211,147 @@ Its committed rows are:
168
211
  | Pattern | Ignored | Meaning |
169
212
  | --- | ---: | --- |
170
213
  | `runtime_%` | `1` | Never version any engine runtime table. |
214
+ | `job_runs` | `1` | Never version terminal job audit blobs. |
171
215
  | `sqlite_sequence` | `1` | Never version local AUTOINCREMENT counters. |
172
216
 
217
+ ### Merge policy per constraint class
218
+
219
+ Merges run through `mergeWithPolicy` (`src/merge-policy.ts`), which encodes
220
+ one rule per constraint class. doltlite verifies the merged working set
221
+ against UNIQUE, CHECK, and foreign-key constraints and rolls a violating
222
+ merge back atomically ("working set with constraint violations"); there is
223
+ no `dolt_verify_constraints()` in doltlite, so the merge itself plus
224
+ post-merge scans (`PRAGMA foreign_key_check`, duplicate-singleton scans)
225
+ are the constraint-verification primitives.
226
+
227
+ - **Precondition: same schema version.** Both sides must carry the same
228
+ `engine_schema.version`; a mismatch is refused with
229
+ `SCHEMA_INCOMPATIBLE` before any merge is attempted
230
+ (`assertSameSchemaVersion`). This was previously only implied by the
231
+ engine's open-time version gate.
232
+ - **`artifacts.slug` (globally unique) → user-facing conflict.** Two forks
233
+ minting the same slug for different artifacts is detected before the
234
+ merge by a three-way (merge-base/ours/theirs) simulation over the
235
+ artifacts slug projection (`findSlugConflicts`, using `dolt_merge_base`
236
+ and `dolt_at_artifacts`), and refused with `MERGE_CONFLICT` listing the
237
+ slug and both artifact ids. doltlite's own verification is the backstop:
238
+ refusals it reports are re-diagnosed and surface as `MERGE_CONFLICT`
239
+ when attributable to slugs. Row-level same-row edits surface as
240
+ `MERGE_CONFLICT` as well. At create time, slug dedup runs inside the
241
+ serialized write chain (not as a read-then-write outside it), so
242
+ concurrent creates cannot race onto the same slug.
243
+ - **RESTRICT foreign keys → verification-surfaced typed violation.** A
244
+ fork that deletes a row another fork newly references is caught by
245
+ doltlite's merge-time working-set verification, which refuses and rolls
246
+ back; the policy maps that refusal to `MERGE_VIOLATION` (never a raw
247
+ `IO_ERROR`) and re-verifies referential integrity after every successful
248
+ merge (`verifyConstraintHealth`). Delete-time `IN_USE` pre-checks cover
249
+ every RESTRICT referencing table of `artifacts` (cells, streams,
250
+ transcripts, clips, pinned search results) so single-branch deletes also
251
+ fail with typed errors.
252
+ - **`transcripts.state='current'` / `sequences.is_primary` → derived
253
+ singletons.** A merge of forks that each crowned a different row yields
254
+ duplicates. Reads resolve a deterministic winner and the post-merge
255
+ reconcile (`reconcileSingletonFlags`) rewrites losers: transcripts keep
256
+ the latest `created_at` (ties: lowest `transcript_id`), sequences keep
257
+ the earliest `created_at` — the original primary (ties: lowest
258
+ `sequence_id`). The reconcile is Dolt-committed so the working set is
259
+ clean for the next merge.
260
+ - **Grid slots and order keys** need no resolution: `(grid_row,
261
+ grid_column)` collisions and identical fractional order keys are both
262
+ resolved at read time by the stable row-UUID tie-break (see the order
263
+ rules above). Writers self-repair: `moveTrack` runs `reconcileOrderKeys`
264
+ over a duplicate-key sibling group before computing a between-key, so the
265
+ position between two merge-minted duplicates stays reachable.
266
+ - **`objects.forgotten_at` → forget wins, earliest stamp.** The same
267
+ takedown applied independently on fork and upstream produces a same-row
268
+ different-value cell (wall clocks differ). The projection merge resolves
269
+ it instead of conflicting: when both sides agree on everything except
270
+ `forgotten_at`, a set value beats NULL (deleted bytes stay deleted on
271
+ both lineages) and two set values keep the earlier timestamp
272
+ (`resolveObjectsRow` in `src/fork.ts`).
273
+
274
+ ve-wsu: doltlite currently corrupts secondary UNIQUE indexes on
275
+ `dolt_checkout` once a working set has three or more tables, corrupts full
276
+ engine catalogs on checkout and `dolt_clone` (the cloned file's schema does
277
+ not even parse: `invalid rootpage` on a secondary autoindex), misfires its
278
+ "uncommitted changes" merge guard on the full 28-table catalog (every
279
+ table reports a phantom `modified` status with zero row diffs), and — when
280
+ the guard is bypassed by committing the phantom dirt — dies in schema
281
+ loading on true merges (`schema conflict on table 'sqlite_autoindex_*'`).
282
+ The dedicated merge-back flow therefore runs this policy around a
283
+ projection-level three-way merge instead of `dolt_merge`; see "Forks and
284
+ merge-back integration" below. `mergeWithPolicy` remains the drop-in merge
285
+ mechanism once the upstream bugs are fixed, exercised against the real
286
+ semantic DDL in `tests/merge-policy.test.ts`.
287
+
288
+ ### Forks and merge-back integration
289
+
290
+ A fork of a public book is, from the engine's point of view:
291
+
292
+ 1. **A platform fork.** Creating the hosted copy of a catalog and giving
293
+ it a URL is the hosting layer's job; it is out of engine scope.
294
+ 2. **A clone of the catalog into a local engine root** (`bootstrapFork` in
295
+ [`src/fork.ts`](../src/fork.ts)). Because `dolt_clone` corrupts full
296
+ catalogs (ve-wsu), bootstrap takes a byte snapshot of a healthy upstream
297
+ `videobook.db` (captured while the upstream engine is closed) and opens
298
+ it as a normal engine — no `initialBookSlug`, the singleton book row
299
+ comes along with the snapshot. A URL bootstrap path attempts
300
+ `dolt_clone` and health-validates the result, surfacing a typed
301
+ `FEATURE_UNAVAILABLE` while the upstream bug stands; it starts working
302
+ unchanged once doltlite is fixed.
303
+ 3. **A public-read object store keyed by SHA-256.** `ContentStore` stays
304
+ the abstraction; the existing `ensureLocal` lazy fetch in
305
+ [`src/cas.ts`](../src/cas.ts) downloads any object the fork lacks on
306
+ first touch, so upstream objects of a public book are readable to
307
+ forkers without a bulk copy. Whether the fork's store proxies to
308
+ upstream's is a hosting concern — an engine root only ever sees one
309
+ `ContentStore`.
310
+
311
+ The fork is then a full citizen: it commits on its own `main` and backs up
312
+ to its own catalog remote and object store.
313
+
314
+ **The live-engine never-pulls rule stands.** An open engine never fetches,
315
+ pulls, or merges, and `main` remains the only supported live branch.
316
+ Integration is a dedicated flow, `mergeBack` in
317
+ [`src/fork.ts`](../src/fork.ts), separate from any live engine:
318
+
319
+ 1. Copy a healthy upstream `videobook.db` (engine closed) into a throwaway
320
+ temp workspace. The flow never requires or mutates the user's open
321
+ catalog — the source file is only read.
322
+ 2. Register/fetch the fork remote (`dolt_remote`, `dolt_fetch`) and
323
+ resolve heads and the merge base (`dolt_merge_base`; commit hashes via
324
+ `dolt_log`/`dolt_branches` — `doltHashOf` returns content hashes, not
325
+ commit hashes; the fetched remote-tracking ref gets a local branch
326
+ pointer, a ref-only write that is safe under ve-wsu).
327
+ 3. Run the merge policy: same-schema precondition, pre-merge slug-conflict
328
+ detection (`MERGE_CONFLICT`), then a projection-level three-way row
329
+ merge over `dolt_at_<table>` snapshots of base/ours/theirs (row
330
+ semantics mirror Dolt: one-sided changes win, identical changes
331
+ resolve, incompatible changes abort with `MERGE_CONFLICT`), deterministic
332
+ singleton-flag reconcile, and post-merge constraint verification
333
+ (`MERGE_VIOLATION`). The working-set rewrite uses the restore idiom —
334
+ delete all semantic tables in reverse, reinsert parent-before-child.
335
+ 4. Upload the fork's new objects (rows in `dolt_at_objects(theirs)` not at
336
+ ours, tombstones excluded) from the fork's object store to upstream's
337
+ BEFORE the catalog ref moves — the same objects-before-push ordering as
338
+ `engine.storage.backup`.
339
+ 5. Land one forward integration commit on `main` and `dolt_push` it.
340
+
341
+ ve-wsu makes a true two-parent `dolt_merge` commit impossible on full
342
+ catalogs today, so the integration commit is single-parent and records the
343
+ integrated fork head in a `merged-revision` commit-message trailer (plus
344
+ `base-revision`, per the structured-message convention). Re-running the
345
+ flow is a no-op when the fork's net changes are already on `main`. The
346
+ projection merge in `mergeRefs` is the single swap point: when doltlite is
347
+ fixed, `mergeWithPolicy` + `dolt_merge` replace it and the commit becomes
348
+ a true merge commit.
349
+
350
+ When a plain `engine.storage.backup()` push is rejected because upstream
351
+ moved, the backup surfaces `DIVERGED` with guidance into this flow —
352
+ someone with a healthy upstream catalog runs `mergeBack`; the fork never
353
+ pulls to catch up.
354
+
173
355
  ### Semantic indexes
174
356
 
175
357
  Primary keys and unique declarations create their own backing indexes. The
@@ -186,15 +368,8 @@ schema additionally defines every index below.
186
368
  | `edges_source` | `edges(notebook_id, source_cell_id)` |
187
369
  | `edges_target` | `edges(notebook_id, target_cell_id)` |
188
370
  | `runs_notebook_completed` | `runs(notebook_id, completed_at, run_id)` |
189
- | `timeline_slots_order` | `timeline_slots(ordinal, slot_id)` |
190
- | `timeline_slots_artifact` | `timeline_slots(artifact_id)` |
191
- | `timeline_audio_order` | `timeline_audio(ordinal, audio_id)` |
192
- | `timeline_audio_artifact` | `timeline_audio(artifact_id)` |
193
371
  | `prompt_entries_lookup` | `prompt_entries(surface, created_at, prompt_id)` |
194
372
  | `messages_created` | `messages(created_at, message_id)` |
195
- | `operations_created` | `operations(created_at, operation_id)` |
196
- | `operations_artifact_created` | `operations(artifact_id, created_at, operation_id)` |
197
- | `action_events_action_created` | `action_events(action_id, created_at, event_id)` |
198
373
 
199
374
  ## Local-only runtime schema
200
375
 
@@ -252,57 +427,106 @@ to recover safely:
252
427
 
253
428
  1. Start `BEGIN IMMEDIATE`.
254
429
  2. Change the requested semantic rows.
255
- 3. Insert the mutation's `operations` row.
256
- 4. Insert `runtime_commit_outbox(operation_id, tables_json, message, created_at)`.
257
- 5. Commit the SQL transaction.
258
- 6. Stage only changed names from `SEMANTIC_TABLES`.
259
- 7. Create a Dolt commit on `main`.
260
- 8. Delete the runtime outbox row in a runtime transaction.
261
-
262
- On reopen, the engine drains any surviving outbox record. Runtime staging is
263
- checked before writes and after commits. Historical reads use
430
+ 3. Insert `runtime_commit_outbox(operation_id, tables_json, message, created_at)`;
431
+ `tables_json` carries the operation's declared write set alongside the
432
+ allow-empty flag, so the declaration commits atomically with the mutation
433
+ and recovery stages the same tables.
434
+ 4. Commit the SQL transaction.
435
+ 5. Probe each declared table with a row-level diff (`dolt_diff_<table>`,
436
+ which also sees staged-but-uncommitted rows during recovery) and stage
437
+ the dirty ones one `dolt_add` at a time doltlite's multi-argument
438
+ `dolt_add` over-stages, and its `doltStatus` phantom `modified` entries
439
+ are why status is never consulted for dirtiness.
440
+ 6. Create a Dolt commit on `main` with the configured identity as `--author`;
441
+ when nothing semantic changed, clear the outbox row instead of minting an
442
+ empty commit.
443
+ 7. Assert the committed tables are no longer dirty, then delete the runtime
444
+ outbox row.
445
+
446
+ On reopen, the engine drains any surviving outbox record (legacy rows
447
+ without a declared table list fall back to probing the full allowlist) and
448
+ then sweeps the whole catalog: any semantic table with unattributed
449
+ uncommitted rows faults the open with `STORAGE_ERROR`. Staged tables are
450
+ asserted against the allowlist before every commit. Opening a catalog never creates a
451
+ commit: terminal-job reconciliation writes the ignored `job_runs` table
452
+ through a runtime transaction. Historical reads use
264
453
  `dolt_at_<semantic_table>(revision)`; history and conflict projections use
265
- Dolt log, status, and diff APIs. Remote catalog support is push-backup only.
454
+ Dolt log, status, and diff APIs. A whole-book restore reloads **every** table
455
+ in `SEMANTIC_TABLES` from its `dolt_at_*` projection at the target revision —
456
+ deleting in reverse and reinserting in forward, parent-before-child order,
457
+ with columns taken from `PRAGMA table_info` — so the restored state is
458
+ exactly the recorded state and no hand-maintained table list can drift.
459
+ Remote catalog support is push-backup only.
460
+
461
+ ## Forgettable data and raw-text audit
462
+
463
+ Published history is append-only: once a catalog has been pushed anywhere
464
+ someone could fork it, its commits are permanent. Forgetting therefore means
465
+ deleting content-addressed objects, never rewriting history. History squash
466
+ tooling would only be legal before the first push (before anyone can fork);
467
+ the engine deliberately does not implement it.
468
+
469
+ ### Object deletion and GC
470
+
471
+ - `engine.storage.deleteObject(hash, { force?, remote? })` forgets one
472
+ object. It refuses with `IN_USE` (listing the HEAD references) when a HEAD
473
+ row still names the hash, unless `force` is given — the takedown path.
474
+ - `engine.storage.gc({ dryRun?, remote?, doltGc? })` sweeps every object
475
+ nothing references at HEAD, plus stray local files that never got an
476
+ `objects` row. A hash is referenced at HEAD when a HEAD row names it in a
477
+ first-class `object_hash`/`payload_hash` column (`artifact_files`,
478
+ `artifact_streams`, `pinned_search_results`, `sequence_clips`,
479
+ `transcripts`) or embeds it as an `objectHash` in a `cell_references`
480
+ snapshot. Historical revisions are not consulted.
481
+ - Both record forgetting as a semantic commit (`delete_object` /
482
+ `gc_objects`) that sets `objects.forgotten_at`. The row is never deleted:
483
+ it stays as the tombstone (hash + size + forgotten timestamp) for every
484
+ historical row that named the object, and backup never tries to publish a
485
+ forgotten object again. Re-importing the same bytes resurrects the row
486
+ (`forgotten_at` is cleared when the object is re-linked).
487
+ - Restoring an old revision whose object was forgotten relinks the
488
+ tombstone: the forward restore commit stands, and reads of the missing
489
+ bytes surface `OBJECT_UNAVAILABLE` through the existing error path instead
490
+ of crashing. Deleting a referenced object with `force` behaves the same
491
+ way at HEAD.
492
+ - With `remote: true`, deletion also unpublishes via
493
+ `ContentStore.delete(key)` and clears the `runtime_object_publications`
494
+ marker so a later re-import is published again.
495
+ - doltlite exposes `dolt_gc()` as a SQL function (verified: it returns a
496
+ `"N chunks removed, M chunks kept"` summary). `gc({ doltGc: true })` runs
497
+ it after collecting to physically reclaim chunks left behind by dropped
498
+ table data in the versioned catalog.
499
+ - Run `deleteObject` and `gc` only while no imports are in flight; CAS puts
500
+ happen outside the serialized write chain, so a concurrent import could
501
+ race the sweep.
502
+
503
+ ### Raw-text audit
504
+
505
+ | Table / column | Decision | Rationale |
506
+ | --- | --- | --- |
507
+ | `transcript_segments.text`, `transcript_words.text` | **Moved behind CAS** (schema v16) | Full transcripts are the bulk-text case. Segment/word text lives in one CAS object named by `transcripts.payload_hash`; the versioned rows keep only structure (IDs, ordinals, ticks, speaker, confidence, kind), so `transcripts.selectionRange` and caption-cue word references keep working after the payload is forgotten. `transcripts.delete` removes the rows; the payload then becomes GC-collectable. |
508
+ | `caption_cues.text` | Accepted permanent | Short editorial cue text needed to render without CAS access; a deliberate editorial snapshot that can diverge from the transcript. |
509
+ | `prompt_entries.prompt`, `cells.prompt`, `entities.prompt`/`description` | Accepted permanent | Small user-authored creative working data; part of the semantic record users expect to persist, like commit messages. |
510
+ | `messages.body_json` | Accepted permanent | Small per-row authored conversation record. |
511
+ | `pinned_search_results.query_json`/`signals_json`/`representative_json`, `cell_references.snapshot_json` | Accepted permanent | Small selection snapshots; may embed short excerpts. Snapshot `objectHash` values are honored as loose GC roots. |
512
+ | `runs.cell_order_json`/`outputs_json`/`error`, `book_metadata`/`artifact_metadata.value_json` | Accepted permanent | Structural or small key/value data. |
513
+ | `runtime_segment_text.text`, `runtime_text_similarity_chunks.chunk_text` | Out of scope | `runtime_*` tables are never versioned or pushed, so they never enter the public record; rebuild or drop them locally at will. |
266
514
 
267
515
  ## Video-editing structures exposed today
268
516
 
269
517
  The SQL schema is only one part of the editing model. These structures and
270
518
  conventions are also important.
271
519
 
272
- ### Timeline contract
273
-
274
- ```ts
275
- type TimelineRender = "landscape" | "portrait" | "square";
276
-
277
- interface Timeline {
278
- bookId: string;
279
- render: TimelineRender;
280
- slots: TimelineSlot[];
281
- audio: TimelineAudio[];
282
- }
283
-
284
- interface TimelineSlot {
285
- id: string; // UUIDv7
286
- artifactId: string;
287
- volume?: number;
288
- audioFadeIn?: number;
289
- audioFadeOut?: number;
290
- }
291
-
292
- interface TimelineAudio {
293
- id: string; // UUIDv7
294
- artifactId: string;
295
- startFrame: number;
296
- durationFrames: number;
297
- volume?: number;
298
- fadeIn?: number;
299
- fadeOut?: number;
300
- }
301
- ```
520
+ ### Sequence contract
302
521
 
303
- `engine.timeline.set()` replaces/synchronizes the normalized rows while
304
- preserving caller-supplied stable IDs. `getAtRevision()` projects the three
305
- timeline tables at a Dolt revision.
522
+ The timeline is a `Sequence` projection: rational frame rate and pixel
523
+ aspect, an audio sample rate and channel layout, ordered video/audio/caption
524
+ tracks, clips with source ranges and transforms, transitions, and caption
525
+ cues. `engine.sequences` reads and mutates sequence structure (tracks,
526
+ names, primary sequence); `engine.edits` applies transactional clip,
527
+ transition, and caption operations. `getAtRevision()` projects the sequence
528
+ tables at a Dolt revision. The full contract types live in
529
+ [`src/mvp-contracts.ts`](../src/mvp-contracts.ts).
306
530
 
307
531
  ### Artifact manifest and content
308
532
 
@@ -360,12 +584,10 @@ status, and error. Entities provide structured prompts, characters, and scenes.
360
584
 
361
585
  ### Provenance and concurrency
362
586
 
363
- - `Revision` projects Dolt commit hash, operation, artifact, details, and file
364
- changes.
365
- - `HistoryAction` projects the action DAG, lifecycle events, input/output
366
- artifact lineage, workflow layout, and write set.
367
- - `base_revision` plus normalized write sets detect overlapping semantic
368
- actions.
587
+ - `Revision` projects the Dolt commit hash, operation, artifact, and
588
+ parameters parsed from the structured commit message.
589
+ - `base_revision` plus the write sets carried in commit messages detect
590
+ overlapping semantic changes.
369
591
  - Runtime jobs, resource leases, artifact views, owner IDs, expiry times, and
370
592
  monotonic fences coordinate processors without polluting Dolt history.
371
593
 
@@ -381,10 +603,15 @@ status, and error. Entities provide structured prompts, characters, and scenes.
381
603
 
382
604
  ## Full NLE structures not yet implemented
383
605
 
384
- Schema v4 can assemble one ordered visual lane plus timed audio overlays. It is
385
- not yet a complete non-linear editor model. The following is a concrete
386
- candidate layout for a future clean-break schema. Every name in this section
387
- is proposed and does **not** exist in schema v4.
606
+ The core sequence model proposed below has since landed (`sequences`,
607
+ `sequence_tracks`, `sequence_clips`, `clip_links`, `clip_transforms`,
608
+ `transitions`, `caption_cues`) and is now the only timeline model; the
609
+ legacy v4 lane described here was removed in v17. The remaining proposals —
610
+ effects, keyframes, audio buses, markers, derivatives — are still open.
611
+ Schema v4 assembled one ordered visual lane plus timed audio overlays. The
612
+ following is a concrete candidate layout for the remaining pieces. Every
613
+ name in this section is proposed and, unless noted above, does **not** exist
614
+ in the catalog.
388
615
 
389
616
  ### Core sequence model
390
617
 
@@ -460,7 +687,16 @@ must reproduce a render.
460
687
  - Preserve loose references only in audit/lineage records that must outlive the
461
688
  target.
462
689
  - Keep order as an integer plus stable ID tie-breaker, or use a documented
463
- fractional ordering key if frequent insertion warrants it.
690
+ fractional ordering key if frequent insertion warrants it. The engine uses
691
+ fractional/lexicographic order keys (base-62, `"a0"`-style midpoint keys,
692
+ see `src/order-keys.ts`) for engine-maintained orderings — sequence tracks
693
+ and, before the legacy triple was removed in v17, timeline slots and
694
+ timeline audio — with the row UUID as tie-breaker, so inserting or moving
695
+ one row never renumbers its neighbors. Notebook cells
696
+ keep their explicit integer `(grid_row, grid_column)` slot as the ordering
697
+ key with the cell UUID as tie-breaker; slot uniqueness is no longer
698
+ enforced by the schema and collisions left by a merge are repaired on the
699
+ next write instead.
464
700
  - Put user intent and accepted edit decisions in Dolt. Put playhead position,
465
701
  selections, UI panels, decode caches, thumbnails, temporary renders, active
466
702
  jobs, locks, and presence in ignored runtime tables.
@@ -478,12 +714,16 @@ must reproduce a render.
478
714
  | Concern | Implementation |
479
715
  | --- | --- |
480
716
  | DDL, table allowlists, indexes | [`src/schema.ts`](../src/schema.ts) |
717
+ | Fractional order keys and minimal rekeying | [`src/order-keys.ts`](../src/order-keys.ts) |
481
718
  | SQL transactions, Dolt staging, commits, outbox recovery | [`src/store.ts`](../src/store.ts) |
719
+ | Merge policy per constraint class | [`src/merge-policy.ts`](../src/merge-policy.ts) |
720
+ | Fork bootstrap and merge-back integration | [`src/fork.ts`](../src/fork.ts) |
482
721
  | Engine paths and row projections | [`src/context.ts`](../src/context.ts) |
483
- | Immutable object layout and remote keys | [`src/cas.ts`](../src/cas.ts) |
722
+ | Content-addressed object layout, remote keys, deletion | [`src/cas.ts`](../src/cas.ts) |
723
+ | Object publication, deletion, GC, and catalog backup | [`src/storage.ts`](../src/storage.ts) |
484
724
  | Artifact mappings and workspace materialization | [`src/files.ts`](../src/files.ts) |
485
- | Current timeline API and row synchronization | [`src/timeline.ts`](../src/timeline.ts) |
486
- | Public timeline, manifest, job, status, and similarity types | [`src/engine-types.ts`](../src/engine-types.ts) |
725
+ | Sequence reads/structure and transactional edits | [`src/sequences.ts`](../src/sequences.ts), [`src/edits.ts`](../src/edits.ts) |
726
+ | Public manifest, job, status, and similarity types | [`src/engine-types.ts`](../src/engine-types.ts) |
487
727
  | Entity/notebook graph types | [`src/notebook/types.ts`](../src/notebook/types.ts) |
488
728
  | Revision and action projections/restores | [`src/history.ts`](../src/history.ts), [`src/history-types.ts`](../src/history-types.ts) |
489
729
  | Media naming and discovery conventions | [`src/media.ts`](../src/media.ts) |