@lix-js/sdk 0.6.0-preview.2 → 0.6.0-preview.3

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 (165) hide show
  1. package/SKILL.md +4 -5
  2. package/dist/engine-wasm/wasm/lix_engine.js +1 -1
  3. package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
  4. package/dist/generated/builtin-schemas.d.ts +87 -162
  5. package/dist/generated/builtin-schemas.js +139 -236
  6. package/dist/open-lix.d.ts +1 -1
  7. package/dist-engine-src/src/binary_cas/types.rs +0 -6
  8. package/dist-engine-src/src/catalog/context.rs +412 -0
  9. package/dist-engine-src/src/catalog/mod.rs +10 -0
  10. package/dist-engine-src/src/catalog/schema.rs +4 -0
  11. package/dist-engine-src/src/catalog/snapshot.rs +1114 -0
  12. package/dist-engine-src/src/cel/mod.rs +1 -1
  13. package/dist-engine-src/src/cel/provider.rs +1 -1
  14. package/dist-engine-src/src/commit_graph/context.rs +328 -1015
  15. package/dist-engine-src/src/commit_graph/mod.rs +2 -3
  16. package/dist-engine-src/src/commit_graph/types.rs +7 -43
  17. package/dist-engine-src/src/commit_graph/walker.rs +57 -81
  18. package/dist-engine-src/src/commit_store/codec.rs +887 -0
  19. package/dist-engine-src/src/commit_store/context.rs +944 -0
  20. package/dist-engine-src/src/commit_store/materialization.rs +84 -0
  21. package/dist-engine-src/src/commit_store/mod.rs +16 -0
  22. package/dist-engine-src/src/commit_store/storage.rs +600 -0
  23. package/dist-engine-src/src/commit_store/types.rs +215 -0
  24. package/dist-engine-src/src/common/identity.rs +15 -5
  25. package/dist-engine-src/src/common/json_pointer.rs +67 -0
  26. package/dist-engine-src/src/common/metadata.rs +17 -12
  27. package/dist-engine-src/src/common/mod.rs +5 -5
  28. package/dist-engine-src/src/domain.rs +324 -0
  29. package/dist-engine-src/src/engine.rs +29 -43
  30. package/dist-engine-src/src/entity_identity.rs +238 -118
  31. package/dist-engine-src/src/functions/context.rs +17 -52
  32. package/dist-engine-src/src/functions/deterministic.rs +1 -1
  33. package/dist-engine-src/src/functions/mod.rs +1 -1
  34. package/dist-engine-src/src/functions/provider.rs +4 -4
  35. package/dist-engine-src/src/functions/state.rs +39 -66
  36. package/dist-engine-src/src/functions/types.rs +1 -1
  37. package/dist-engine-src/src/init.rs +204 -151
  38. package/dist-engine-src/src/json_store/context.rs +354 -60
  39. package/dist-engine-src/src/json_store/encoded.rs +6 -6
  40. package/dist-engine-src/src/json_store/mod.rs +4 -1
  41. package/dist-engine-src/src/json_store/store.rs +884 -11
  42. package/dist-engine-src/src/json_store/types.rs +166 -1
  43. package/dist-engine-src/src/lib.rs +10 -9
  44. package/dist-engine-src/src/live_state/context.rs +608 -830
  45. package/dist-engine-src/src/live_state/mod.rs +3 -3
  46. package/dist-engine-src/src/live_state/overlay.rs +7 -7
  47. package/dist-engine-src/src/live_state/reader.rs +5 -5
  48. package/dist-engine-src/src/live_state/types.rs +19 -36
  49. package/dist-engine-src/src/live_state/visibility.rs +19 -14
  50. package/dist-engine-src/src/plugin/archive.rs +3 -6
  51. package/dist-engine-src/src/plugin/install.rs +0 -18
  52. package/dist-engine-src/src/plugin/plugin_manifest.json +0 -1
  53. package/dist-engine-src/src/schema/annotations/defaults.rs +2 -7
  54. package/dist-engine-src/src/schema/builtin/lix_account.json +0 -1
  55. package/dist-engine-src/src/schema/builtin/lix_active_account.json +0 -1
  56. package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +0 -1
  57. package/dist-engine-src/src/schema/builtin/lix_change.json +11 -10
  58. package/dist-engine-src/src/schema/builtin/lix_change_author.json +0 -1
  59. package/dist-engine-src/src/schema/builtin/lix_commit.json +8 -46
  60. package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +29 -22
  61. package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +0 -1
  62. package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +0 -1
  63. package/dist-engine-src/src/schema/builtin/lix_key_value.json +0 -1
  64. package/dist-engine-src/src/schema/builtin/lix_label.json +10 -3
  65. package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +74 -0
  66. package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +2 -8
  67. package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +0 -1
  68. package/dist-engine-src/src/schema/builtin/lix_version_ref.json +0 -1
  69. package/dist-engine-src/src/schema/builtin/mod.rs +10 -59
  70. package/dist-engine-src/src/schema/compatibility.rs +787 -0
  71. package/dist-engine-src/src/schema/definition.json +47 -17
  72. package/dist-engine-src/src/schema/definition.rs +202 -96
  73. package/dist-engine-src/src/schema/key.rs +9 -77
  74. package/dist-engine-src/src/schema/mod.rs +4 -4
  75. package/dist-engine-src/src/schema/tests.rs +133 -92
  76. package/dist-engine-src/src/session/context.rs +40 -42
  77. package/dist-engine-src/src/session/create_version.rs +22 -14
  78. package/dist-engine-src/src/session/execute.rs +45 -14
  79. package/dist-engine-src/src/session/merge/apply.rs +4 -4
  80. package/dist-engine-src/src/session/merge/conflicts.rs +3 -2
  81. package/dist-engine-src/src/session/merge/stats.rs +1 -1
  82. package/dist-engine-src/src/session/merge/version.rs +35 -45
  83. package/dist-engine-src/src/session/mod.rs +4 -2
  84. package/dist-engine-src/src/session/optimization9_sql2_bench.rs +100 -0
  85. package/dist-engine-src/src/session/switch_version.rs +16 -28
  86. package/dist-engine-src/src/sql2/change_provider.rs +14 -20
  87. package/dist-engine-src/src/sql2/classify.rs +61 -26
  88. package/dist-engine-src/src/sql2/context.rs +22 -18
  89. package/dist-engine-src/src/sql2/directory_history_provider.rs +28 -20
  90. package/dist-engine-src/src/sql2/directory_provider.rs +131 -83
  91. package/dist-engine-src/src/sql2/entity_history_provider.rs +10 -14
  92. package/dist-engine-src/src/sql2/entity_provider.rs +680 -169
  93. package/dist-engine-src/src/sql2/error.rs +21 -1
  94. package/dist-engine-src/src/sql2/execute.rs +325 -264
  95. package/dist-engine-src/src/sql2/file_history_provider.rs +29 -21
  96. package/dist-engine-src/src/sql2/file_provider.rs +533 -108
  97. package/dist-engine-src/src/sql2/filesystem_planner.rs +58 -94
  98. package/dist-engine-src/src/sql2/filesystem_visibility.rs +37 -23
  99. package/dist-engine-src/src/sql2/history_projection.rs +3 -27
  100. package/dist-engine-src/src/sql2/history_provider.rs +11 -17
  101. package/dist-engine-src/src/sql2/history_route.rs +22 -8
  102. package/dist-engine-src/src/sql2/lix_state_provider.rs +178 -96
  103. package/dist-engine-src/src/sql2/mod.rs +6 -3
  104. package/dist-engine-src/src/sql2/predicate_typecheck.rs +246 -0
  105. package/dist-engine-src/src/sql2/public_bind/assignment.rs +46 -0
  106. package/dist-engine-src/src/sql2/public_bind/capability.rs +41 -0
  107. package/dist-engine-src/src/sql2/public_bind/dml.rs +166 -0
  108. package/dist-engine-src/src/sql2/public_bind/mod.rs +25 -0
  109. package/dist-engine-src/src/sql2/public_bind/table.rs +168 -0
  110. package/dist-engine-src/src/sql2/read_only.rs +10 -12
  111. package/dist-engine-src/src/sql2/session.rs +7 -10
  112. package/dist-engine-src/src/sql2/udfs/lix_timestamp.rs +76 -0
  113. package/dist-engine-src/src/sql2/udfs/mod.rs +8 -1
  114. package/dist-engine-src/src/sql2/udfs/public_call.rs +211 -0
  115. package/dist-engine-src/src/sql2/version_provider.rs +46 -31
  116. package/dist-engine-src/src/sql2/version_scope.rs +4 -4
  117. package/dist-engine-src/src/storage_bench.rs +1782 -325
  118. package/dist-engine-src/src/test_support.rs +183 -36
  119. package/dist-engine-src/src/tracked_state/by_file_index.rs +20 -24
  120. package/dist-engine-src/src/tracked_state/codec.rs +1519 -181
  121. package/dist-engine-src/src/tracked_state/context.rs +1155 -271
  122. package/dist-engine-src/src/tracked_state/diff.rs +249 -57
  123. package/dist-engine-src/src/tracked_state/materialization.rs +365 -103
  124. package/dist-engine-src/src/tracked_state/materializer.rs +488 -0
  125. package/dist-engine-src/src/tracked_state/merge.rs +37 -19
  126. package/dist-engine-src/src/tracked_state/mod.rs +8 -7
  127. package/dist-engine-src/src/tracked_state/storage.rs +138 -6
  128. package/dist-engine-src/src/tracked_state/tree.rs +695 -252
  129. package/dist-engine-src/src/tracked_state/types.rs +176 -6
  130. package/dist-engine-src/src/transaction/commit.rs +695 -435
  131. package/dist-engine-src/src/transaction/context.rs +551 -310
  132. package/dist-engine-src/src/transaction/live_state_overlay.rs +9 -8
  133. package/dist-engine-src/src/transaction/mod.rs +2 -0
  134. package/dist-engine-src/src/transaction/normalization.rs +311 -447
  135. package/dist-engine-src/src/transaction/prep.rs +37 -0
  136. package/dist-engine-src/src/transaction/schema_resolver.rs +93 -71
  137. package/dist-engine-src/src/transaction/staging.rs +701 -406
  138. package/dist-engine-src/src/transaction/types.rs +231 -122
  139. package/dist-engine-src/src/transaction/validation.rs +2717 -1698
  140. package/dist-engine-src/src/untracked_state/codec.rs +40 -96
  141. package/dist-engine-src/src/untracked_state/context.rs +21 -5
  142. package/dist-engine-src/src/untracked_state/materialization.rs +10 -104
  143. package/dist-engine-src/src/untracked_state/mod.rs +3 -5
  144. package/dist-engine-src/src/untracked_state/storage.rs +105 -57
  145. package/dist-engine-src/src/untracked_state/types.rs +63 -13
  146. package/dist-engine-src/src/version/context.rs +1 -13
  147. package/dist-engine-src/src/version/lifecycle.rs +221 -0
  148. package/dist-engine-src/src/version/mod.rs +3 -2
  149. package/dist-engine-src/src/version/refs.rs +12 -103
  150. package/dist-engine-src/src/version/stage_rows.rs +15 -19
  151. package/package.json +1 -1
  152. package/dist-engine-src/src/changelog/codec.rs +0 -321
  153. package/dist-engine-src/src/changelog/context.rs +0 -92
  154. package/dist-engine-src/src/changelog/materialization.rs +0 -121
  155. package/dist-engine-src/src/changelog/mod.rs +0 -13
  156. package/dist-engine-src/src/changelog/reader.rs +0 -20
  157. package/dist-engine-src/src/changelog/storage.rs +0 -220
  158. package/dist-engine-src/src/changelog/types.rs +0 -38
  159. package/dist-engine-src/src/schema/builtin/lix_change_set.json +0 -18
  160. package/dist-engine-src/src/schema/builtin/lix_change_set_element.json +0 -75
  161. package/dist-engine-src/src/schema/builtin/lix_entity_label.json +0 -63
  162. package/dist-engine-src/src/schema_registry.rs +0 -294
  163. package/dist-engine-src/src/sql2/commit_derived_provider.rs +0 -591
  164. package/dist-engine-src/src/tracked_state/rebuild.rs +0 -771
  165. package/dist-engine-src/src/tracked_state/tree_types.rs +0 -176
@@ -1,771 +0,0 @@
1
- use crate::commit_graph::CommitGraphContext;
2
- use crate::commit_graph::CommitGraphEntity;
3
- use crate::json_store::JsonStoreWriter;
4
- use crate::storage::{StorageReader, StorageWriteSet};
5
- use crate::tracked_state::{TrackedStateContext, TrackedStateRow};
6
- use crate::LixError;
7
-
8
- /// Summary of a tracked-state rebuild operation.
9
- #[derive(Debug, Clone, Copy, PartialEq, Eq)]
10
- pub(crate) struct TrackedStateRebuildReport {
11
- pub(crate) written_rows: usize,
12
- }
13
-
14
- /// Rebuilds tracked-state rows at one commit from the commit graph.
15
- ///
16
- /// The caller provides the read stores and owns the transaction write set.
17
- pub(super) async fn rebuild_state_at_commit<R, S>(
18
- tracked_state: &TrackedStateContext,
19
- commit_graph: &CommitGraphContext,
20
- read_store: R,
21
- tracked_store: &mut S,
22
- writes: &mut StorageWriteSet,
23
- json_writer: &mut JsonStoreWriter,
24
- head_commit_id: &str,
25
- ) -> Result<TrackedStateRebuildReport, LixError>
26
- where
27
- R: StorageReader,
28
- S: StorageReader + ?Sized,
29
- {
30
- let entities = commit_graph
31
- .reader(read_store)
32
- .entities_at(head_commit_id)
33
- .await?;
34
- let rows = rows_from_entities(entities);
35
- let written_rows = rows.len();
36
-
37
- tracked_state
38
- .writer()
39
- .stage_root(
40
- tracked_store,
41
- writes,
42
- json_writer,
43
- head_commit_id,
44
- None,
45
- &rows,
46
- )
47
- .await?;
48
-
49
- Ok(TrackedStateRebuildReport { written_rows })
50
- }
51
-
52
- /// Converts commit-graph entities into root-local tracked-state rows.
53
- ///
54
- /// The commit graph owns history resolution. This function only maps the
55
- /// effective canonical entities into the storage row shape tracked_state serves.
56
- pub(crate) fn rows_from_entities(entities: Vec<CommitGraphEntity>) -> Vec<TrackedStateRow> {
57
- entities
58
- .into_iter()
59
- .filter(|entity| !is_commit_graph_fact(&entity.change.schema_key))
60
- .map(tracked_row_from_entity)
61
- .collect()
62
- }
63
-
64
- fn tracked_row_from_entity(entity: CommitGraphEntity) -> TrackedStateRow {
65
- let CommitGraphEntity {
66
- change,
67
- source_commit_id,
68
- created_at,
69
- updated_at,
70
- ..
71
- } = entity;
72
- TrackedStateRow {
73
- entity_id: change.entity_id,
74
- schema_key: change.schema_key,
75
- file_id: change.file_id,
76
- snapshot_content: change.snapshot_content,
77
- metadata: change.metadata,
78
- schema_version: change.schema_version,
79
- created_at,
80
- updated_at,
81
- change_id: change.id,
82
- commit_id: source_commit_id,
83
- }
84
- }
85
-
86
- fn is_commit_graph_fact(schema_key: &str) -> bool {
87
- schema_key == "lix_commit"
88
- }
89
-
90
- #[cfg(test)]
91
- mod tests {
92
- use super::*;
93
- use std::sync::Arc;
94
-
95
- use crate::backend::testing::UnitTestBackend;
96
- use crate::changelog::{
97
- canonicalize_materialized_change, ChangelogContext, MaterializedCanonicalChange,
98
- };
99
- use crate::commit_graph::CommitGraphContext;
100
- use crate::json_store::JsonStoreContext;
101
- use crate::storage::{StorageContext, StorageWriteSet};
102
- use crate::tracked_state::{TrackedStateFilter, TrackedStateScanRequest};
103
- use serde_json::json;
104
-
105
- #[test]
106
- fn rows_from_entities_converts_normal_entity() {
107
- let rows = rows_from_entities(vec![entity("change-1", Some("{}"))]);
108
-
109
- assert_eq!(rows.len(), 1);
110
- let row = &rows[0];
111
- assert_eq!(
112
- row.entity_id,
113
- crate::entity_identity::EntityIdentity::single("entity-1")
114
- );
115
- assert_eq!(row.schema_key, "test_schema");
116
- assert_eq!(row.file_id.as_deref(), Some("file-1"));
117
- assert_eq!(row.snapshot_content.as_deref(), Some("{}"));
118
- assert_eq!(row.metadata.as_ref(), Some(&json!({"m": 1})));
119
- assert_eq!(row.schema_version, "1");
120
- assert_eq!(row.change_id, "change-1");
121
- assert_eq!(row.commit_id, "commit-1");
122
- }
123
-
124
- #[test]
125
- fn rows_from_entities_preserves_tombstones() {
126
- let rows = rows_from_entities(vec![entity("change-1", None)]);
127
-
128
- assert_eq!(rows[0].snapshot_content, None);
129
- }
130
-
131
- #[test]
132
- fn rows_from_entities_uses_commit_graph_timestamps() {
133
- let rows = rows_from_entities(vec![entity("change-1", Some("{}"))]);
134
-
135
- assert_eq!(rows[0].created_at, "2026-01-01T00:00:00Z");
136
- assert_eq!(rows[0].updated_at, "2026-01-02T00:00:00Z");
137
- }
138
-
139
- #[test]
140
- fn rows_from_entities_is_root_local_and_version_independent() {
141
- let rows = rows_from_entities(vec![entity("change-1", Some("{}"))]);
142
-
143
- assert_eq!(
144
- rows[0].entity_id,
145
- crate::entity_identity::EntityIdentity::single("entity-1")
146
- );
147
- assert_eq!(rows[0].schema_key, "test_schema");
148
- }
149
-
150
- #[test]
151
- fn rows_from_entities_excludes_commit_graph_facts() {
152
- let rows = rows_from_entities(vec![commit_entity("commit-1")]);
153
-
154
- assert_eq!(rows.len(), 0);
155
- }
156
-
157
- #[tokio::test]
158
- async fn rebuild_state_at_commit_writes_rows_from_commit_graph() {
159
- let backend = Arc::new(UnitTestBackend::new());
160
- let storage = StorageContext::new(backend.clone());
161
- let tracked_state = TrackedStateContext::new();
162
- let changelog = ChangelogContext::new();
163
- let commit_graph = CommitGraphContext::new(changelog);
164
- append_changes(
165
- storage.clone(),
166
- &[
167
- entity_change("change-1", "entity-1", "test_schema", Some("{}")),
168
- commit_change("commit-1-change", "commit-1", &["change-1"], &[]),
169
- ],
170
- )
171
- .await;
172
-
173
- let mut tx = storage
174
- .begin_write_transaction()
175
- .await
176
- .expect("transaction should open");
177
- let mut writes = StorageWriteSet::new();
178
- let mut json_writer = JsonStoreContext::new().writer();
179
- let report = rebuild_state_at_commit(
180
- &tracked_state,
181
- &commit_graph,
182
- storage.clone(),
183
- tx.as_mut(),
184
- &mut writes,
185
- &mut json_writer,
186
- "commit-1",
187
- )
188
- .await
189
- .expect("rebuild should succeed");
190
- writes
191
- .apply(&mut tx.as_mut())
192
- .await
193
- .expect("rebuild writes should apply");
194
- tx.commit().await.expect("transaction should commit");
195
-
196
- assert_eq!(report, TrackedStateRebuildReport { written_rows: 1 });
197
- let rows = scan_rows_at_commit(&tracked_state, storage.clone(), "commit-1").await;
198
- assert_eq!(rows.len(), 1);
199
- assert!(rows.iter().any(|row| row.schema_key == "test_schema"
200
- && row.entity_id == crate::entity_identity::EntityIdentity::single("entity-1")));
201
- }
202
-
203
- #[tokio::test]
204
- async fn rebuild_state_at_commit_writes_replacement_root_for_head_commit() {
205
- let backend = Arc::new(UnitTestBackend::new());
206
- let storage = StorageContext::new(backend.clone());
207
- let tracked_state = TrackedStateContext::new();
208
- let changelog = ChangelogContext::new();
209
- let commit_graph = CommitGraphContext::new(changelog);
210
- append_changes(
211
- storage.clone(),
212
- &[
213
- entity_change("change-new", "entity-new", "test_schema", Some("{}")),
214
- commit_change("commit-1-change", "commit-1", &["change-new"], &[]),
215
- ],
216
- )
217
- .await;
218
- seed_tracked_root(
219
- &tracked_state,
220
- storage.clone(),
221
- "stale-commit",
222
- &[stale_row("version-b", "stale-other")],
223
- )
224
- .await;
225
-
226
- let mut tx = storage
227
- .begin_write_transaction()
228
- .await
229
- .expect("transaction should open");
230
- let mut writes = StorageWriteSet::new();
231
- let mut json_writer = JsonStoreContext::new().writer();
232
- let report = rebuild_state_at_commit(
233
- &tracked_state,
234
- &commit_graph,
235
- storage.clone(),
236
- tx.as_mut(),
237
- &mut writes,
238
- &mut json_writer,
239
- "commit-1",
240
- )
241
- .await
242
- .expect("rebuild should succeed");
243
- writes
244
- .apply(&mut tx.as_mut())
245
- .await
246
- .expect("rebuild writes should apply");
247
- tx.commit().await.expect("transaction should commit");
248
-
249
- assert_eq!(report.written_rows, 1);
250
-
251
- let version_a_rows = scan_rows_at_commit(&tracked_state, storage.clone(), "commit-1").await;
252
- assert!(!version_a_rows
253
- .iter()
254
- .any(|row| row.entity_id
255
- == crate::entity_identity::EntityIdentity::single("stale-target")));
256
- assert!(version_a_rows.iter().any(
257
- |row| row.entity_id == crate::entity_identity::EntityIdentity::single("entity-new")
258
- ));
259
-
260
- let version_b_rows =
261
- scan_rows_at_commit(&tracked_state, storage.clone(), "stale-commit").await;
262
- assert_eq!(version_b_rows.len(), 1);
263
- assert_eq!(
264
- version_b_rows[0].entity_id,
265
- crate::entity_identity::EntityIdentity::single("stale-other")
266
- );
267
- }
268
-
269
- #[tokio::test]
270
- async fn rebuild_state_at_commit_is_content_address_deterministic() {
271
- let backend = Arc::new(UnitTestBackend::new());
272
- let storage = StorageContext::new(backend.clone());
273
- let tracked_state = TrackedStateContext::new();
274
- let changelog = ChangelogContext::new();
275
- let commit_graph = CommitGraphContext::new(changelog);
276
- append_changes(
277
- storage.clone(),
278
- &[
279
- entity_change("change-1", "entity-1", "test_schema", Some("{\"v\":1}")),
280
- entity_change("change-2", "entity-2", "test_schema", Some("{\"v\":2}")),
281
- commit_change(
282
- "commit-1-change",
283
- "commit-1",
284
- &["change-1", "change-2"],
285
- &[],
286
- ),
287
- ],
288
- )
289
- .await;
290
-
291
- rebuild_state_at_commit_for_test(
292
- &tracked_state,
293
- &commit_graph,
294
- storage.clone(),
295
- "commit-1",
296
- )
297
- .await;
298
- let first_root = load_root(&tracked_state, storage.clone(), "commit-1").await;
299
- delete_root(&tracked_state, storage.clone(), "commit-1").await;
300
- rebuild_state_at_commit_for_test(
301
- &tracked_state,
302
- &commit_graph,
303
- storage.clone(),
304
- "commit-1",
305
- )
306
- .await;
307
- let second_root = load_root(&tracked_state, storage.clone(), "commit-1").await;
308
-
309
- assert_eq!(
310
- first_root, second_root,
311
- "rebuilding the same changelog head should produce the same prolly root"
312
- );
313
- }
314
-
315
- #[tokio::test]
316
- async fn rebuild_state_at_commit_uses_latest_change_across_commits() {
317
- let backend = Arc::new(UnitTestBackend::new());
318
- let storage = StorageContext::new(backend.clone());
319
- let tracked_state = TrackedStateContext::new();
320
- let changelog = ChangelogContext::new();
321
- let commit_graph = CommitGraphContext::new(changelog);
322
- append_changes(
323
- storage.clone(),
324
- &[
325
- entity_change_at(
326
- "change-old",
327
- "entity-1",
328
- "test_schema",
329
- Some("{\"value\":\"old\"}"),
330
- "2026-01-01T00:00:00Z",
331
- ),
332
- entity_change_at(
333
- "change-new",
334
- "entity-1",
335
- "test_schema",
336
- Some("{\"value\":\"new\"}"),
337
- "2026-01-02T00:00:00Z",
338
- ),
339
- commit_change("commit-root-change", "commit-root", &["change-old"], &[]),
340
- commit_change(
341
- "commit-head-change",
342
- "commit-head",
343
- &["change-new"],
344
- &["commit-root"],
345
- ),
346
- ],
347
- )
348
- .await;
349
-
350
- rebuild_state_at_commit_for_test(
351
- &tracked_state,
352
- &commit_graph,
353
- storage.clone(),
354
- "commit-head",
355
- )
356
- .await;
357
-
358
- let rows = scan_rows_at_commit(&tracked_state, storage.clone(), "commit-head").await;
359
- let row = rows
360
- .iter()
361
- .find(|row| {
362
- row.schema_key == "test_schema"
363
- && row.entity_id == crate::entity_identity::EntityIdentity::single("entity-1")
364
- })
365
- .expect("rebuilt entity row should exist");
366
- assert_eq!(row.snapshot_content.as_deref(), Some("{\"value\":\"new\"}"));
367
- assert_eq!(row.change_id, "change-new");
368
- assert_eq!(row.commit_id, "commit-head");
369
- assert_eq!(row.created_at, "2026-01-01T00:00:00Z");
370
- assert_eq!(row.updated_at, "2026-01-02T00:00:00Z");
371
- }
372
-
373
- #[tokio::test]
374
- async fn rebuild_state_at_commit_preserves_tombstone_winner() {
375
- let backend = Arc::new(UnitTestBackend::new());
376
- let storage = StorageContext::new(backend.clone());
377
- let tracked_state = TrackedStateContext::new();
378
- let changelog = ChangelogContext::new();
379
- let commit_graph = CommitGraphContext::new(changelog);
380
- append_changes(
381
- storage.clone(),
382
- &[
383
- entity_change_at(
384
- "change-created",
385
- "entity-1",
386
- "test_schema",
387
- Some("{\"value\":\"created\"}"),
388
- "2026-01-01T00:00:00Z",
389
- ),
390
- entity_change_at(
391
- "change-deleted",
392
- "entity-1",
393
- "test_schema",
394
- None,
395
- "2026-01-02T00:00:00Z",
396
- ),
397
- commit_change(
398
- "commit-root-change",
399
- "commit-root",
400
- &["change-created"],
401
- &[],
402
- ),
403
- commit_change(
404
- "commit-head-change",
405
- "commit-head",
406
- &["change-deleted"],
407
- &["commit-root"],
408
- ),
409
- ],
410
- )
411
- .await;
412
-
413
- rebuild_state_at_commit_for_test(
414
- &tracked_state,
415
- &commit_graph,
416
- storage.clone(),
417
- "commit-head",
418
- )
419
- .await;
420
-
421
- let rows = scan_rows_at_commit(&tracked_state, storage.clone(), "commit-head").await;
422
- let row = rows
423
- .iter()
424
- .find(|row| {
425
- row.schema_key == "test_schema"
426
- && row.entity_id == crate::entity_identity::EntityIdentity::single("entity-1")
427
- })
428
- .expect("rebuilt tombstone row should exist");
429
- assert_eq!(row.snapshot_content, None);
430
- assert_eq!(row.change_id, "change-deleted");
431
- assert_eq!(row.commit_id, "commit-head");
432
- }
433
-
434
- #[tokio::test]
435
- async fn rebuild_state_at_commit_can_rebuild_global_commit_state() {
436
- let backend = Arc::new(UnitTestBackend::new());
437
- let storage = StorageContext::new(backend.clone());
438
- let tracked_state = TrackedStateContext::new();
439
- let changelog = ChangelogContext::new();
440
- let commit_graph = CommitGraphContext::new(changelog);
441
- append_changes(
442
- storage.clone(),
443
- &[
444
- entity_change("change-1", "entity-1", "test_schema", Some("{}")),
445
- commit_change("commit-1-change", "commit-1", &["change-1"], &[]),
446
- ],
447
- )
448
- .await;
449
-
450
- rebuild_state_at_commit_for_test(
451
- &tracked_state,
452
- &commit_graph,
453
- storage.clone(),
454
- "commit-1",
455
- )
456
- .await;
457
-
458
- let rows = scan_rows_at_commit(&tracked_state, storage.clone(), "commit-1").await;
459
- assert!(!rows.is_empty());
460
- assert!(rows.iter().any(|row| row.schema_key == "test_schema"
461
- && row.entity_id == crate::entity_identity::EntityIdentity::single("entity-1")));
462
- }
463
-
464
- #[tokio::test]
465
- async fn rebuilding_one_commit_state_does_not_rewrite_another_commit_root() {
466
- let backend = Arc::new(UnitTestBackend::new());
467
- let storage = StorageContext::new(backend.clone());
468
- let tracked_state = TrackedStateContext::new();
469
- let changelog = ChangelogContext::new();
470
- let commit_graph = CommitGraphContext::new(changelog);
471
- append_changes(
472
- storage.clone(),
473
- &[
474
- entity_change("change-global", "entity-global", "test_schema", Some("{}")),
475
- entity_change("change-main", "entity-main", "test_schema", Some("{}")),
476
- commit_change(
477
- "commit-global-change",
478
- "commit-global",
479
- &["change-global"],
480
- &[],
481
- ),
482
- commit_change("commit-main-change", "commit-main", &["change-main"], &[]),
483
- ],
484
- )
485
- .await;
486
-
487
- rebuild_state_at_commit_for_test(
488
- &tracked_state,
489
- &commit_graph,
490
- storage.clone(),
491
- "commit-global",
492
- )
493
- .await;
494
- let global_root_before = load_root(&tracked_state, storage.clone(), "commit-global").await;
495
-
496
- rebuild_state_at_commit_for_test(
497
- &tracked_state,
498
- &commit_graph,
499
- storage.clone(),
500
- "commit-main",
501
- )
502
- .await;
503
- let global_root_after = load_root(&tracked_state, storage.clone(), "commit-global").await;
504
-
505
- assert_eq!(
506
- global_root_after, global_root_before,
507
- "rebuilding one commit state must not rewrite another commit root"
508
- );
509
- let main_rows = scan_rows_at_commit(&tracked_state, storage.clone(), "commit-main").await;
510
- assert_eq!(main_rows.len(), 1);
511
- assert_eq!(
512
- main_rows[0].entity_id,
513
- crate::entity_identity::EntityIdentity::single("entity-main")
514
- );
515
- }
516
-
517
- fn entity(change_id: &str, snapshot_content: Option<&str>) -> CommitGraphEntity {
518
- CommitGraphEntity {
519
- change: MaterializedCanonicalChange {
520
- id: change_id.to_string(),
521
- entity_id: crate::entity_identity::EntityIdentity::single("entity-1"),
522
- schema_key: "test_schema".to_string(),
523
- schema_version: "1".to_string(),
524
- file_id: Some("file-1".to_string()),
525
- snapshot_content: snapshot_content.map(str::to_string),
526
- metadata: Some(json!({"m": 1})),
527
- created_at: "ignored-change-created-at".to_string(),
528
- },
529
- source_commit_id: "commit-1".to_string(),
530
- depth: 0,
531
- created_at: "2026-01-01T00:00:00Z".to_string(),
532
- updated_at: "2026-01-02T00:00:00Z".to_string(),
533
- }
534
- }
535
-
536
- fn commit_entity(commit_id: &str) -> CommitGraphEntity {
537
- CommitGraphEntity {
538
- change: commit_change(
539
- &format!("{commit_id}-change"),
540
- commit_id,
541
- &["change-1"],
542
- &[],
543
- ),
544
- source_commit_id: commit_id.to_string(),
545
- depth: 0,
546
- created_at: "2026-01-01T00:00:00Z".to_string(),
547
- updated_at: "2026-01-01T00:00:00Z".to_string(),
548
- }
549
- }
550
-
551
- async fn append_changes(storage: StorageContext, changes: &[MaterializedCanonicalChange]) {
552
- let changelog = ChangelogContext::new();
553
- let mut tx = storage
554
- .begin_write_transaction()
555
- .await
556
- .expect("transaction should open");
557
- let mut writes = StorageWriteSet::new();
558
- let canonical_changes = {
559
- let mut json_writer = JsonStoreContext::new().writer();
560
- changes
561
- .iter()
562
- .map(|change| {
563
- canonicalize_materialized_change(&mut writes, &mut json_writer, change)
564
- })
565
- .collect::<Result<Vec<_>, _>>()
566
- .expect("changes should canonicalize")
567
- };
568
- changelog
569
- .writer(&mut writes)
570
- .stage_changes(&canonical_changes)
571
- .expect("changes should append");
572
- writes
573
- .apply(&mut tx.as_mut())
574
- .await
575
- .expect("writes should apply");
576
- tx.commit().await.expect("transaction should commit");
577
- }
578
-
579
- async fn seed_tracked_root(
580
- tracked_state: &TrackedStateContext,
581
- storage: StorageContext,
582
- commit_id: &str,
583
- rows: &[TrackedStateRow],
584
- ) {
585
- let mut tx = storage
586
- .begin_write_transaction()
587
- .await
588
- .expect("transaction should open");
589
- let mut writes = StorageWriteSet::new();
590
- {
591
- let mut json_writer = JsonStoreContext::new().writer();
592
- tracked_state
593
- .writer()
594
- .stage_root(
595
- &mut tx.as_mut(),
596
- &mut writes,
597
- &mut json_writer,
598
- commit_id,
599
- None,
600
- rows,
601
- )
602
- .await
603
- .expect("rows should seed");
604
- }
605
- writes
606
- .apply(&mut tx.as_mut())
607
- .await
608
- .expect("rows should apply");
609
- tx.commit().await.expect("transaction should commit");
610
- }
611
-
612
- async fn rebuild_state_at_commit_for_test(
613
- tracked_state: &TrackedStateContext,
614
- commit_graph: &CommitGraphContext,
615
- storage: StorageContext,
616
- head_commit_id: &str,
617
- ) -> TrackedStateRebuildReport {
618
- let mut tx = storage
619
- .begin_write_transaction()
620
- .await
621
- .expect("transaction should open");
622
- let mut writes = StorageWriteSet::new();
623
- let mut json_writer = JsonStoreContext::new().writer();
624
- let report = rebuild_state_at_commit(
625
- tracked_state,
626
- commit_graph,
627
- storage.clone(),
628
- tx.as_mut(),
629
- &mut writes,
630
- &mut json_writer,
631
- head_commit_id,
632
- )
633
- .await
634
- .expect("rebuild should succeed");
635
- writes
636
- .apply(&mut tx.as_mut())
637
- .await
638
- .expect("rebuild writes should apply");
639
- tx.commit().await.expect("transaction should commit");
640
- report
641
- }
642
-
643
- async fn scan_rows_at_commit(
644
- tracked_state: &TrackedStateContext,
645
- storage: StorageContext,
646
- commit_id: &str,
647
- ) -> Vec<TrackedStateRow> {
648
- tracked_state
649
- .reader(storage)
650
- .scan_rows_at_commit(
651
- commit_id,
652
- &TrackedStateScanRequest {
653
- filter: TrackedStateFilter {
654
- include_tombstones: true,
655
- ..Default::default()
656
- },
657
- ..Default::default()
658
- },
659
- )
660
- .await
661
- .expect("tracked rows should scan")
662
- }
663
-
664
- async fn load_root(
665
- tracked_state: &TrackedStateContext,
666
- storage: StorageContext,
667
- commit_id: &str,
668
- ) -> crate::tracked_state::tree_types::TrackedStateRootId {
669
- let mut reader = tracked_state.reader(storage);
670
- reader
671
- .load_root_for_test(commit_id)
672
- .await
673
- .expect("root load should succeed")
674
- .expect("root should exist")
675
- }
676
-
677
- async fn delete_root(
678
- tracked_state: &TrackedStateContext,
679
- storage: StorageContext,
680
- commit_id: &str,
681
- ) {
682
- let mut tx = storage
683
- .begin_write_transaction()
684
- .await
685
- .expect("transaction should open");
686
- let mut writes = StorageWriteSet::new();
687
- tracked_state
688
- .writer()
689
- .stage_delete_root_for_rebuild(&mut writes, commit_id);
690
- writes
691
- .apply(&mut tx.as_mut())
692
- .await
693
- .expect("root delete should apply");
694
- tx.commit().await.expect("transaction should commit");
695
- }
696
-
697
- fn entity_change(
698
- change_id: &str,
699
- entity_id: &str,
700
- schema_key: &str,
701
- snapshot_content: Option<&str>,
702
- ) -> MaterializedCanonicalChange {
703
- entity_change_at(
704
- change_id,
705
- entity_id,
706
- schema_key,
707
- snapshot_content,
708
- "2026-01-01T00:00:00Z",
709
- )
710
- }
711
-
712
- fn entity_change_at(
713
- change_id: &str,
714
- entity_id: &str,
715
- schema_key: &str,
716
- snapshot_content: Option<&str>,
717
- created_at: &str,
718
- ) -> MaterializedCanonicalChange {
719
- MaterializedCanonicalChange {
720
- id: change_id.to_string(),
721
- entity_id: crate::entity_identity::EntityIdentity::single(entity_id),
722
- schema_key: schema_key.to_string(),
723
- schema_version: "1".to_string(),
724
- file_id: None,
725
- snapshot_content: snapshot_content.map(str::to_string),
726
- metadata: None,
727
- created_at: created_at.to_string(),
728
- }
729
- }
730
-
731
- fn commit_change(
732
- change_id: &str,
733
- commit_id: &str,
734
- change_ids: &[&str],
735
- parent_commit_ids: &[&str],
736
- ) -> MaterializedCanonicalChange {
737
- MaterializedCanonicalChange {
738
- id: change_id.to_string(),
739
- entity_id: crate::entity_identity::EntityIdentity::single(commit_id),
740
- schema_key: "lix_commit".to_string(),
741
- schema_version: "1".to_string(),
742
- file_id: None,
743
- snapshot_content: Some(
744
- serde_json::to_string(&json!({
745
- "id": commit_id,
746
- "change_set_id": format!("change-set-{commit_id}"),
747
- "change_ids": change_ids,
748
- "parent_commit_ids": parent_commit_ids,
749
- }))
750
- .expect("commit snapshot should serialize"),
751
- ),
752
- metadata: None,
753
- created_at: "2026-01-02T00:00:00Z".to_string(),
754
- }
755
- }
756
-
757
- fn stale_row(version_id: &str, entity_id: &str) -> TrackedStateRow {
758
- TrackedStateRow {
759
- entity_id: crate::entity_identity::EntityIdentity::single(entity_id),
760
- schema_key: "test_schema".to_string(),
761
- file_id: None,
762
- snapshot_content: Some("{}".to_string()),
763
- metadata: None,
764
- schema_version: "1".to_string(),
765
- created_at: "2026-01-01T00:00:00Z".to_string(),
766
- updated_at: "2026-01-01T00:00:00Z".to_string(),
767
- change_id: format!("change-{entity_id}"),
768
- commit_id: format!("commit-{version_id}"),
769
- }
770
- }
771
- }