@lix-js/sdk 0.6.0-preview.4 → 0.6.0-preview.5

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 (234) hide show
  1. package/README.md +1 -1
  2. package/SKILL.md +65 -64
  3. package/dist/engine-wasm/index.js +4 -4
  4. package/dist/engine-wasm/wasm/lix_engine.d.ts +5 -5
  5. package/dist/engine-wasm/wasm/lix_engine.js +130 -118
  6. package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
  7. package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +9 -8
  8. package/dist/generated/builtin-schemas.d.ts +69 -69
  9. package/dist/generated/builtin-schemas.js +94 -94
  10. package/dist/open-lix.d.ts +33 -26
  11. package/dist/open-lix.js +10 -10
  12. package/dist/sqlite/index.js +86 -30
  13. package/dist-engine-src/README.md +3 -3
  14. package/dist-engine-src/src/backend/capabilities.rs +67 -0
  15. package/dist-engine-src/src/backend/conformance/baseline.rs +1127 -0
  16. package/dist-engine-src/src/backend/conformance/factory.rs +93 -0
  17. package/dist-engine-src/src/backend/conformance/failure_tests.rs +608 -0
  18. package/dist-engine-src/src/backend/conformance/fixtures.rs +26 -0
  19. package/dist-engine-src/src/backend/conformance/mod.rs +75 -0
  20. package/dist-engine-src/src/backend/conformance/model.rs +28 -0
  21. package/dist-engine-src/src/backend/conformance/model_based.rs +257 -0
  22. package/dist-engine-src/src/backend/conformance/persistence.rs +204 -0
  23. package/dist-engine-src/src/backend/conformance/projection.rs +21 -0
  24. package/dist-engine-src/src/backend/conformance/pushdown.rs +24 -0
  25. package/dist-engine-src/src/backend/conformance/runner.rs +90 -0
  26. package/dist-engine-src/src/backend/conformance/scan.rs +24 -0
  27. package/dist-engine-src/src/backend/conformance/write.rs +16 -0
  28. package/dist-engine-src/src/backend/error.rs +94 -0
  29. package/dist-engine-src/src/backend/in_memory.rs +670 -0
  30. package/dist-engine-src/src/backend/mod.rs +36 -9
  31. package/dist-engine-src/src/backend/predicate.rs +80 -0
  32. package/dist-engine-src/src/backend/traits.rs +260 -0
  33. package/dist-engine-src/src/backend/types.rs +224 -81
  34. package/dist-engine-src/src/binary_cas/context.rs +8 -8
  35. package/dist-engine-src/src/binary_cas/kv.rs +234 -259
  36. package/dist-engine-src/src/{version → branch}/context.rs +12 -12
  37. package/dist-engine-src/src/branch/lifecycle.rs +221 -0
  38. package/dist-engine-src/src/branch/mod.rs +13 -0
  39. package/dist-engine-src/src/branch/refs.rs +321 -0
  40. package/dist-engine-src/src/branch/stage_rows.rs +67 -0
  41. package/dist-engine-src/src/branch/types.rs +21 -0
  42. package/dist-engine-src/src/catalog/context.rs +18 -18
  43. package/dist-engine-src/src/catalog/snapshot.rs +8 -8
  44. package/dist-engine-src/src/changelog/bench_support.rs +785 -0
  45. package/dist-engine-src/src/changelog/change.rs +1 -0
  46. package/dist-engine-src/src/changelog/codec.rs +497 -0
  47. package/dist-engine-src/src/changelog/commit.rs +1 -0
  48. package/dist-engine-src/src/changelog/context.rs +1614 -0
  49. package/dist-engine-src/src/changelog/mod.rs +29 -0
  50. package/dist-engine-src/src/changelog/store.rs +163 -0
  51. package/dist-engine-src/src/changelog/test_support.rs +54 -0
  52. package/dist-engine-src/src/changelog/types.rs +213 -0
  53. package/dist-engine-src/src/commit_graph/context.rs +317 -274
  54. package/dist-engine-src/src/commit_graph/mod.rs +2 -4
  55. package/dist-engine-src/src/commit_graph/types.rs +22 -42
  56. package/dist-engine-src/src/commit_graph/walker.rs +133 -103
  57. package/dist-engine-src/src/common/error.rs +52 -18
  58. package/dist-engine-src/src/common/identity.rs +2 -2
  59. package/dist-engine-src/src/common/mod.rs +1 -1
  60. package/dist-engine-src/src/domain.rs +42 -46
  61. package/dist-engine-src/src/engine.rs +74 -96
  62. package/dist-engine-src/src/{entity_identity.rs → entity_pk.rs} +89 -92
  63. package/dist-engine-src/src/functions/context.rs +56 -52
  64. package/dist-engine-src/src/functions/state.rs +51 -52
  65. package/dist-engine-src/src/init.rs +288 -154
  66. package/dist-engine-src/src/json_store/context.rs +15 -266
  67. package/dist-engine-src/src/json_store/mod.rs +26 -0
  68. package/dist-engine-src/src/json_store/store.rs +103 -718
  69. package/dist-engine-src/src/json_store/types.rs +4 -9
  70. package/dist-engine-src/src/lib.rs +49 -19
  71. package/dist-engine-src/src/live_state/context.rs +654 -790
  72. package/dist-engine-src/src/live_state/mod.rs +9 -3
  73. package/dist-engine-src/src/live_state/overlay.rs +4 -4
  74. package/dist-engine-src/src/live_state/types.rs +30 -21
  75. package/dist-engine-src/src/live_state/visibility.rs +514 -71
  76. package/dist-engine-src/src/plugin/install.rs +48 -48
  77. package/dist-engine-src/src/plugin/manifest.rs +7 -7
  78. package/dist-engine-src/src/plugin/materializer.rs +0 -275
  79. package/dist-engine-src/src/plugin/plugin_manifest.json +4 -3
  80. package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +2 -2
  81. package/dist-engine-src/src/schema/builtin/lix_branch_descriptor.json +34 -0
  82. package/dist-engine-src/src/schema/builtin/lix_branch_ref.json +48 -0
  83. package/dist-engine-src/src/schema/builtin/lix_change.json +3 -3
  84. package/dist-engine-src/src/schema/builtin/lix_commit.json +1 -1
  85. package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +6 -6
  86. package/dist-engine-src/src/schema/builtin/mod.rs +18 -20
  87. package/dist-engine-src/src/schema/compatibility.rs +11 -11
  88. package/dist-engine-src/src/schema/definition.json +2 -2
  89. package/dist-engine-src/src/schema/definition.rs +5 -5
  90. package/dist-engine-src/src/schema/key.rs +3 -3
  91. package/dist-engine-src/src/schema/mod.rs +1 -1
  92. package/dist-engine-src/src/schema/tests.rs +18 -18
  93. package/dist-engine-src/src/session/context.rs +803 -148
  94. package/dist-engine-src/src/session/create_branch.rs +94 -0
  95. package/dist-engine-src/src/session/execute.rs +223 -83
  96. package/dist-engine-src/src/session/merge/analysis.rs +9 -3
  97. package/dist-engine-src/src/session/merge/{version.rs → branch.rs} +119 -129
  98. package/dist-engine-src/src/session/merge/conflicts.rs +2 -2
  99. package/dist-engine-src/src/session/merge/mod.rs +5 -6
  100. package/dist-engine-src/src/session/merge/stats.rs +7 -11
  101. package/dist-engine-src/src/session/mod.rs +15 -12
  102. package/dist-engine-src/src/session/switch_branch.rs +113 -0
  103. package/dist-engine-src/src/session/transaction.rs +495 -14
  104. package/dist-engine-src/src/sql2/{classify.rs → bind/classify.rs} +3 -75
  105. package/dist-engine-src/src/sql2/bind/error.rs +5 -0
  106. package/dist-engine-src/src/sql2/bind/expr.rs +29 -0
  107. package/dist-engine-src/src/sql2/bind/mod.rs +12 -0
  108. package/dist-engine-src/src/sql2/{udfs/public_call.rs → bind/public_udf.rs} +71 -3
  109. package/dist-engine-src/src/sql2/bind/read.rs +65 -0
  110. package/dist-engine-src/src/sql2/bind/statement.rs +2236 -0
  111. package/dist-engine-src/src/sql2/bind/table.rs +273 -0
  112. package/dist-engine-src/src/sql2/bind/write.rs +86 -0
  113. package/dist-engine-src/src/sql2/branch_scope.rs +436 -0
  114. package/dist-engine-src/src/sql2/catalog/capability.rs +20 -0
  115. package/dist-engine-src/src/sql2/catalog/entity_surface.rs +296 -0
  116. package/dist-engine-src/src/sql2/catalog/mod.rs +15 -0
  117. package/dist-engine-src/src/sql2/catalog/registry.rs +556 -0
  118. package/dist-engine-src/src/sql2/catalog/schema.rs +88 -0
  119. package/dist-engine-src/src/sql2/catalog/surface.rs +41 -0
  120. package/dist-engine-src/src/sql2/change_materialization.rs +122 -0
  121. package/dist-engine-src/src/sql2/context.rs +36 -30
  122. package/dist-engine-src/src/sql2/error.rs +1 -1
  123. package/dist-engine-src/src/sql2/exec/bound_public_write.rs +1593 -0
  124. package/dist-engine-src/src/sql2/exec/datafusion.rs +5266 -0
  125. package/dist-engine-src/src/sql2/exec/fast_write.rs +82 -0
  126. package/dist-engine-src/src/sql2/exec/mod.rs +24 -0
  127. package/dist-engine-src/src/sql2/exec/write.rs +661 -0
  128. package/dist-engine-src/src/sql2/filesystem_planner.rs +72 -77
  129. package/dist-engine-src/src/sql2/filesystem_visibility.rs +21 -21
  130. package/dist-engine-src/src/sql2/history_projection.rs +8 -8
  131. package/dist-engine-src/src/sql2/history_route.rs +35 -31
  132. package/dist-engine-src/src/sql2/mod.rs +28 -23
  133. package/dist-engine-src/src/sql2/optimize/datafusion.rs +1 -0
  134. package/dist-engine-src/src/sql2/optimize/mod.rs +2 -0
  135. package/dist-engine-src/src/sql2/optimize/simple_write.rs +116 -0
  136. package/dist-engine-src/src/sql2/parse/mod.rs +69 -0
  137. package/dist-engine-src/src/sql2/parse/normalize.rs +1 -0
  138. package/dist-engine-src/src/sql2/plan/branch_scope.rs +24 -0
  139. package/dist-engine-src/src/sql2/plan/mod.rs +5 -0
  140. package/dist-engine-src/src/sql2/plan/predicate.rs +22 -0
  141. package/dist-engine-src/src/sql2/plan/write.rs +147 -0
  142. package/dist-engine-src/src/sql2/predicate_typecheck.rs +258 -0
  143. package/dist-engine-src/src/sql2/{version_provider.rs → providers/branch.rs} +218 -214
  144. package/dist-engine-src/src/sql2/{change_provider.rs → providers/change.rs} +156 -42
  145. package/dist-engine-src/src/sql2/{directory_provider.rs → providers/directory.rs} +291 -322
  146. package/dist-engine-src/src/sql2/{directory_history_provider.rs → providers/directory_history.rs} +56 -42
  147. package/dist-engine-src/src/sql2/providers/entity.rs +1484 -0
  148. package/dist-engine-src/src/sql2/{entity_history_provider.rs → providers/entity_history.rs} +43 -31
  149. package/dist-engine-src/src/sql2/{file_provider.rs → providers/file.rs} +323 -316
  150. package/dist-engine-src/src/sql2/{file_history_provider.rs → providers/file_history.rs} +60 -46
  151. package/dist-engine-src/src/sql2/{history_provider.rs → providers/history.rs} +46 -32
  152. package/dist-engine-src/src/sql2/{lix_state_provider.rs → providers/lix_state.rs} +359 -329
  153. package/dist-engine-src/src/sql2/providers/mod.rs +508 -0
  154. package/dist-engine-src/src/sql2/read_only.rs +2 -2
  155. package/dist-engine-src/src/sql2/session.rs +47 -96
  156. package/dist-engine-src/src/sql2/storage/constraints.rs +1 -0
  157. package/dist-engine-src/src/sql2/storage/mod.rs +1 -0
  158. package/dist-engine-src/src/sql2/test_support/differential.rs +712 -0
  159. package/dist-engine-src/src/sql2/test_support/generators.rs +354 -0
  160. package/dist-engine-src/src/sql2/test_support/mod.rs +2 -0
  161. package/dist-engine-src/src/sql2/udfs/{lix_active_version_commit_id.rs → lix_active_branch_commit_id.rs} +7 -7
  162. package/dist-engine-src/src/sql2/udfs/mod.rs +3 -6
  163. package/dist-engine-src/src/sql2/write_normalization.rs +45 -22
  164. package/dist-engine-src/src/storage/conformance.rs +399 -0
  165. package/dist-engine-src/src/storage/context.rs +552 -288
  166. package/dist-engine-src/src/storage/mod.rs +48 -10
  167. package/dist-engine-src/src/storage/point.rs +440 -0
  168. package/dist-engine-src/src/storage/read_scope.rs +43 -64
  169. package/dist-engine-src/src/storage/reader.rs +867 -0
  170. package/dist-engine-src/src/storage/scan.rs +784 -0
  171. package/dist-engine-src/src/storage/spaces.rs +236 -0
  172. package/dist-engine-src/src/storage/stats.rs +80 -0
  173. package/dist-engine-src/src/storage/write_set.rs +962 -0
  174. package/dist-engine-src/src/storage_bench.rs +136 -4828
  175. package/dist-engine-src/src/test_support.rs +360 -138
  176. package/dist-engine-src/src/tracked_state/bench_support.rs +394 -0
  177. package/dist-engine-src/src/tracked_state/codec.rs +155 -1057
  178. package/dist-engine-src/src/tracked_state/commit_root_rebuild.rs +358 -0
  179. package/dist-engine-src/src/tracked_state/context.rs +1927 -993
  180. package/dist-engine-src/src/tracked_state/diff.rs +1715 -261
  181. package/dist-engine-src/src/tracked_state/merge.rs +74 -88
  182. package/dist-engine-src/src/tracked_state/mod.rs +19 -16
  183. package/dist-engine-src/src/tracked_state/{materialization.rs → row_materialization.rs} +50 -178
  184. package/dist-engine-src/src/tracked_state/storage.rs +243 -191
  185. package/dist-engine-src/src/tracked_state/tree.rs +247 -371
  186. package/dist-engine-src/src/tracked_state/types.rs +49 -42
  187. package/dist-engine-src/src/transaction/bench_support.rs +407 -0
  188. package/dist-engine-src/src/transaction/commit.rs +821 -713
  189. package/dist-engine-src/src/transaction/context.rs +705 -600
  190. package/dist-engine-src/src/transaction/mod.rs +13 -2
  191. package/dist-engine-src/src/transaction/normalization.rs +63 -76
  192. package/dist-engine-src/src/transaction/prep.rs +13 -13
  193. package/dist-engine-src/src/transaction/schema_resolver.rs +19 -5
  194. package/dist-engine-src/src/transaction/staging.rs +228 -434
  195. package/dist-engine-src/src/transaction/types.rs +41 -98
  196. package/dist-engine-src/src/transaction/validation.rs +382 -446
  197. package/dist-engine-src/src/untracked_state/codec.rs +337 -29
  198. package/dist-engine-src/src/untracked_state/context.rs +7 -7
  199. package/dist-engine-src/src/untracked_state/materialization.rs +2 -2
  200. package/dist-engine-src/src/untracked_state/mod.rs +1 -1
  201. package/dist-engine-src/src/untracked_state/storage.rs +659 -157
  202. package/dist-engine-src/src/untracked_state/types.rs +21 -21
  203. package/package.json +71 -68
  204. package/dist-engine-src/src/backend/kv.rs +0 -358
  205. package/dist-engine-src/src/backend/testing.rs +0 -658
  206. package/dist-engine-src/src/commit_store/codec.rs +0 -887
  207. package/dist-engine-src/src/commit_store/context.rs +0 -944
  208. package/dist-engine-src/src/commit_store/materialization.rs +0 -84
  209. package/dist-engine-src/src/commit_store/mod.rs +0 -16
  210. package/dist-engine-src/src/commit_store/storage.rs +0 -600
  211. package/dist-engine-src/src/commit_store/types.rs +0 -215
  212. package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +0 -34
  213. package/dist-engine-src/src/schema/builtin/lix_version_ref.json +0 -48
  214. package/dist-engine-src/src/session/create_version.rs +0 -88
  215. package/dist-engine-src/src/session/merge/apply.rs +0 -23
  216. package/dist-engine-src/src/session/optimization9_sql2_bench.rs +0 -100
  217. package/dist-engine-src/src/session/switch_version.rs +0 -110
  218. package/dist-engine-src/src/sql2/entity_provider.rs +0 -3211
  219. package/dist-engine-src/src/sql2/execute.rs +0 -3533
  220. package/dist-engine-src/src/sql2/public_bind/assignment.rs +0 -46
  221. package/dist-engine-src/src/sql2/public_bind/capability.rs +0 -41
  222. package/dist-engine-src/src/sql2/public_bind/dml.rs +0 -172
  223. package/dist-engine-src/src/sql2/public_bind/mod.rs +0 -26
  224. package/dist-engine-src/src/sql2/public_bind/table.rs +0 -168
  225. package/dist-engine-src/src/sql2/version_scope.rs +0 -394
  226. package/dist-engine-src/src/storage/types.rs +0 -501
  227. package/dist-engine-src/src/tracked_state/by_file_index.rs +0 -98
  228. package/dist-engine-src/src/tracked_state/materializer.rs +0 -488
  229. package/dist-engine-src/src/transaction/live_state_overlay.rs +0 -35
  230. package/dist-engine-src/src/version/lifecycle.rs +0 -221
  231. package/dist-engine-src/src/version/mod.rs +0 -13
  232. package/dist-engine-src/src/version/refs.rs +0 -330
  233. package/dist-engine-src/src/version/stage_rows.rs +0 -67
  234. package/dist-engine-src/src/version/types.rs +0 -21
@@ -1,31 +1,34 @@
1
- use crate::commit_store::{Change, CommitDraftRef, CommitStoreContext};
2
- use crate::entity_identity::EntityIdentity;
1
+ use crate::branch::{BRANCH_DESCRIPTOR_SCHEMA_KEY, BRANCH_REF_SCHEMA_KEY};
2
+ use crate::changelog::{
3
+ ChangeRecord, ChangelogAppend, ChangelogContext, ChangelogWriter, CommitChangeRef,
4
+ CommitChangeRefSet, CommitRecord,
5
+ };
6
+ use crate::entity_pk::EntityPk;
3
7
  use crate::functions::{
4
8
  FunctionProvider, FunctionProviderHandle, SharedFunctionProvider, SystemFunctionProvider,
5
9
  };
6
- use crate::json_store::{JsonRef, JsonStoreContext, JsonWritePlacementRef, NormalizedJsonRef};
10
+ use crate::json_store::JsonRef;
11
+ use crate::json_store::{JsonStoreContext, JsonWritePlacementRef, NormalizedJsonRef};
7
12
  use crate::schema::{
8
- registered_schema_entity_id, schema_key_from_definition, seed_schema_definitions,
13
+ registered_schema_entity_pk, schema_key_from_definition, seed_schema_definitions,
9
14
  };
15
+ use crate::storage::StorageBackend;
10
16
  use crate::storage::{StorageContext, StorageWriteSet};
11
17
  use crate::tracked_state::{TrackedStateContext, TrackedStateDeltaRef};
12
18
  use crate::untracked_state::{UntrackedStateContext, UntrackedStateRow};
13
- use crate::version::{VERSION_DESCRIPTOR_SCHEMA_KEY, VERSION_REF_SCHEMA_KEY};
14
19
  use crate::LixError;
15
- use crate::GLOBAL_VERSION_ID;
20
+ use crate::GLOBAL_BRANCH_ID;
16
21
  use serde_json::json;
17
- #[cfg(test)]
18
- use std::sync::Arc;
19
22
 
20
23
  const KEY_VALUE_SCHEMA_KEY: &str = "lix_key_value";
21
24
  const LIX_ID_KEY: &str = "lix_id";
22
- const WORKSPACE_VERSION_KEY: &str = "lix_workspace_version_id";
25
+ const WORKSPACE_BRANCH_KEY: &str = "lix_workspace_branch_id";
23
26
  const REGISTERED_SCHEMA_KEY: &str = "lix_registered_schema";
24
27
 
25
28
  /// Pure seed plan for initializing an engine repository.
26
29
  ///
27
- /// Tracked bootstrap facts go to the commit store. Moving refs such as
28
- /// `lix_version_ref` are seeded as untracked local state so repository heads
30
+ /// Tracked bootstrap facts go to the changelog. Moving refs such as
31
+ /// `lix_branch_ref` are seeded as untracked local state so repository heads
29
32
  /// can advance without becoming commit members.
30
33
  pub(crate) struct InitSeedPlan {
31
34
  commit: InitSeedCommit,
@@ -46,7 +49,7 @@ struct InitSeedCommit {
46
49
  #[derive(Debug, Clone, PartialEq, Eq)]
47
50
  struct InitSeedChange {
48
51
  id: String,
49
- entity_id: EntityIdentity,
52
+ entity_pk: EntityPk,
50
53
  schema_key: String,
51
54
  snapshot_content: String,
52
55
  created_at: String,
@@ -54,30 +57,30 @@ struct InitSeedChange {
54
57
 
55
58
  #[derive(Debug, Clone, PartialEq, Eq)]
56
59
  struct InitSeedLiveRow {
57
- entity_id: EntityIdentity,
60
+ entity_pk: EntityPk,
58
61
  schema_key: String,
59
62
  snapshot_content: String,
60
63
  created_at: String,
61
64
  updated_at: String,
62
65
  global: bool,
63
- version_id: String,
66
+ branch_id: String,
64
67
  }
65
68
 
66
69
  /// Values generated while planning the initial repository seed.
67
70
  #[derive(Debug, Clone, PartialEq, Eq)]
68
71
  pub struct InitReceipt {
69
72
  pub lix_id: String,
70
- pub global_version_id: String,
71
- pub main_version_id: String,
73
+ pub global_branch_id: String,
74
+ pub main_branch_id: String,
72
75
  pub initial_commit_id: String,
73
76
  }
74
77
 
75
78
  /// Builds the canonical bootstrap changes for a new engine repository.
76
79
  ///
77
- /// The initial commit tracks durable content rows. Version refs are moving
80
+ /// The initial commit tracks durable content rows. Branch refs are moving
78
81
  /// pointers and therefore live in untracked local state instead of the commit.
79
82
  pub(crate) fn plan_init_seed(functions: FunctionProviderHandle) -> Result<InitSeedPlan, LixError> {
80
- let main_version_id = functions.call_uuid_v7();
83
+ let main_branch_id = functions.call_uuid_v7();
81
84
  let lix_id = functions.call_uuid_v7();
82
85
  let initial_commit_id = functions.call_uuid_v7();
83
86
  let timestamp = functions.call_timestamp();
@@ -87,30 +90,30 @@ pub(crate) fn plan_init_seed(functions: FunctionProviderHandle) -> Result<InitSe
87
90
  let key = schema_key_from_definition(schema)?;
88
91
  registered_schema_changes.push(canonical_change(
89
92
  functions.call_uuid_v7(),
90
- registered_schema_entity_id(&key.schema_key)?,
93
+ registered_schema_entity_pk(&key.schema_key)?,
91
94
  REGISTERED_SCHEMA_KEY,
92
95
  registered_schema_snapshot(schema)?,
93
96
  &timestamp,
94
97
  ));
95
98
  }
96
99
 
97
- let global_version_descriptor_change = canonical_change(
98
- GLOBAL_VERSION_ID.to_string(),
99
- EntityIdentity::single(GLOBAL_VERSION_ID),
100
- VERSION_DESCRIPTOR_SCHEMA_KEY,
101
- version_descriptor_snapshot(GLOBAL_VERSION_ID, "global", true)?,
100
+ let global_branch_descriptor_change = canonical_change(
101
+ GLOBAL_BRANCH_ID.to_string(),
102
+ EntityPk::single(GLOBAL_BRANCH_ID),
103
+ BRANCH_DESCRIPTOR_SCHEMA_KEY,
104
+ branch_descriptor_snapshot(GLOBAL_BRANCH_ID, "global", true)?,
102
105
  &timestamp,
103
106
  );
104
- let main_version_descriptor_change = canonical_change(
107
+ let main_branch_descriptor_change = canonical_change(
105
108
  functions.call_uuid_v7(),
106
- EntityIdentity::single(&main_version_id),
107
- VERSION_DESCRIPTOR_SCHEMA_KEY,
108
- version_descriptor_snapshot(&main_version_id, "main", false)?,
109
+ EntityPk::single(&main_branch_id),
110
+ BRANCH_DESCRIPTOR_SCHEMA_KEY,
111
+ branch_descriptor_snapshot(&main_branch_id, "main", false)?,
109
112
  &timestamp,
110
113
  );
111
114
  let kv_lix_id_change = canonical_change(
112
115
  functions.call_uuid_v7(),
113
- EntityIdentity::single(LIX_ID_KEY),
116
+ EntityPk::single(LIX_ID_KEY),
114
117
  KEY_VALUE_SCHEMA_KEY,
115
118
  key_value_snapshot(LIX_ID_KEY, &lix_id)?,
116
119
  &timestamp,
@@ -123,22 +126,22 @@ pub(crate) fn plan_init_seed(functions: FunctionProviderHandle) -> Result<InitSe
123
126
  author_account_ids: Vec::new(),
124
127
  created_at: timestamp.clone(),
125
128
  };
126
- let global_version_ref_row = untracked_row(
127
- EntityIdentity::single(GLOBAL_VERSION_ID),
128
- VERSION_REF_SCHEMA_KEY,
129
- version_ref_snapshot(GLOBAL_VERSION_ID, &initial_commit_id)?,
129
+ let global_branch_ref_row = untracked_row(
130
+ EntityPk::single(GLOBAL_BRANCH_ID),
131
+ BRANCH_REF_SCHEMA_KEY,
132
+ branch_ref_snapshot(GLOBAL_BRANCH_ID, &initial_commit_id)?,
130
133
  &timestamp,
131
134
  );
132
- let main_version_ref_row = untracked_row(
133
- EntityIdentity::single(&main_version_id),
134
- VERSION_REF_SCHEMA_KEY,
135
- version_ref_snapshot(&main_version_id, &initial_commit_id)?,
135
+ let main_branch_ref_row = untracked_row(
136
+ EntityPk::single(&main_branch_id),
137
+ BRANCH_REF_SCHEMA_KEY,
138
+ branch_ref_snapshot(&main_branch_id, &initial_commit_id)?,
136
139
  &timestamp,
137
140
  );
138
- let workspace_version_row = untracked_row(
139
- EntityIdentity::single(WORKSPACE_VERSION_KEY),
141
+ let workspace_branch_row = untracked_row(
142
+ EntityPk::single(WORKSPACE_BRANCH_KEY),
140
143
  KEY_VALUE_SCHEMA_KEY,
141
- key_value_snapshot(WORKSPACE_VERSION_KEY, &main_version_id)?,
144
+ key_value_snapshot(WORKSPACE_BRANCH_KEY, &main_branch_id)?,
142
145
  &timestamp,
143
146
  );
144
147
 
@@ -147,20 +150,20 @@ pub(crate) fn plan_init_seed(functions: FunctionProviderHandle) -> Result<InitSe
147
150
  changes: registered_schema_changes
148
151
  .into_iter()
149
152
  .chain([
150
- global_version_descriptor_change,
151
- main_version_descriptor_change,
153
+ global_branch_descriptor_change,
154
+ main_branch_descriptor_change,
152
155
  kv_lix_id_change,
153
156
  ])
154
157
  .collect(),
155
158
  untracked_rows: vec![
156
- global_version_ref_row,
157
- main_version_ref_row,
158
- workspace_version_row,
159
+ global_branch_ref_row,
160
+ main_branch_ref_row,
161
+ workspace_branch_row,
159
162
  ],
160
163
  receipt: InitReceipt {
161
164
  lix_id,
162
- global_version_id: GLOBAL_VERSION_ID.to_string(),
163
- main_version_id,
165
+ global_branch_id: GLOBAL_BRANCH_ID.to_string(),
166
+ main_branch_id,
164
167
  initial_commit_id,
165
168
  },
166
169
  })
@@ -170,56 +173,35 @@ pub(crate) fn plan_init_seed(functions: FunctionProviderHandle) -> Result<InitSe
170
173
  ///
171
174
  /// The pure seed planner decides which bootstrap facts exist. This function is
172
175
  /// only responsible for durably writing those facts to their owning stores:
173
- /// commit_store for tracked changes, and live_state for the serving projection
176
+ /// changelog for tracked changes, and live_state for the serving state
174
177
  /// plus untracked moving refs.
175
- pub(crate) async fn initialize(
176
- storage: StorageContext,
177
- commit_store: &CommitStoreContext,
178
+ pub(crate) async fn initialize<B>(
179
+ storage: StorageContext<B>,
178
180
  tracked_state: &TrackedStateContext,
179
181
  untracked_state: &UntrackedStateContext,
180
- ) -> Result<InitReceipt, LixError> {
182
+ ) -> Result<InitReceipt, LixError>
183
+ where
184
+ B: StorageBackend + Clone + Send + Sync + 'static,
185
+ for<'backend> B::Read<'backend>: Clone + Send + Sync + 'static,
186
+ for<'backend> B::Write<'backend>: Send,
187
+ {
181
188
  let functions = SharedFunctionProvider::new(
182
189
  Box::new(SystemFunctionProvider) as Box<dyn FunctionProvider + Send>
183
190
  );
184
191
  let plan = plan_init_seed(functions)?;
185
192
  let receipt = plan.receipt.clone();
186
193
 
187
- let mut transaction = storage.begin_write_transaction().await?;
194
+ let mut read = storage.begin_read(crate::storage::StorageReadOptions::default())?;
188
195
  let mut writes = StorageWriteSet::new();
189
196
 
190
197
  let authored_changes = plan
191
198
  .changes
192
199
  .iter()
193
- .map(seed_change_to_commit_store_change)
194
- .collect::<Result<Vec<_>, _>>()?;
195
- JsonStoreContext::new().writer().stage_batch(
196
- &mut writes,
197
- JsonWritePlacementRef::CommitPack {
198
- commit_id: &plan.commit.id,
199
- pack_id: 0,
200
- },
201
- plan.changes
202
- .iter()
203
- .map(|change| NormalizedJsonRef::new(change.snapshot_content.as_str())),
204
- )?;
200
+ .map(seed_change_to_change_record)
201
+ .collect::<Vec<_>>();
205
202
 
206
- let staged_commit = {
207
- let commit = CommitDraftRef {
208
- id: &plan.commit.id,
209
- change_id: &plan.commit.change_id,
210
- parent_ids: &plan.commit.parent_ids,
211
- author_account_ids: &plan.commit.author_account_ids,
212
- created_at: &plan.commit.created_at,
213
- };
214
- let mut writer = commit_store.writer(transaction.as_mut(), &mut writes);
215
- writer
216
- .stage_tracked_commit_draft(
217
- commit,
218
- authored_changes.iter().map(Change::as_ref).collect(),
219
- Vec::new(),
220
- )
221
- .await?
222
- };
203
+ stage_init_json_payloads(&mut writes, &plan)?;
204
+ stage_init_changelog_commit(&mut read, &mut writes, &plan, authored_changes.clone()).await?;
223
205
 
224
206
  let untracked_rows = plan
225
207
  .untracked_rows
@@ -231,42 +213,139 @@ pub(crate) async fn initialize(
231
213
  untracked_state
232
214
  .writer(&mut writes)
233
215
  .stage_rows(untracked_rows.iter().map(|row| row.as_ref()))?;
234
- let deltas = authored_changes
216
+ let commit_row_change = seed_commit_row_change_record(&plan.commit)?;
217
+ let mut deltas = authored_changes
235
218
  .iter()
236
- .zip(&staged_commit.authored_locators)
237
- .map(|(change, locator)| TrackedStateDeltaRef {
238
- change: change.as_ref(),
239
- locator: locator.as_ref(),
219
+ .map(|change| TrackedStateDeltaRef {
220
+ schema_key: &change.schema_key,
221
+ file_id: change.file_id.as_deref(),
222
+ entity_pk: &change.entity_pk,
223
+ change_id: &change.change_id,
224
+ commit_id: &plan.commit.id,
225
+ snapshot_ref: change.snapshot_ref.as_ref(),
226
+ metadata_ref: change.metadata_ref.as_ref(),
227
+ deleted: change.snapshot_ref.is_none(),
240
228
  created_at: &change.created_at,
241
229
  updated_at: &change.created_at,
242
230
  })
243
231
  .collect::<Vec<_>>();
244
- let mut writer = tracked_state.writer(transaction.as_mut(), &mut writes);
232
+ deltas.push(TrackedStateDeltaRef {
233
+ schema_key: &commit_row_change.schema_key,
234
+ file_id: commit_row_change.file_id.as_deref(),
235
+ entity_pk: &commit_row_change.entity_pk,
236
+ change_id: &commit_row_change.change_id,
237
+ commit_id: &plan.commit.id,
238
+ snapshot_ref: commit_row_change.snapshot_ref.as_ref(),
239
+ metadata_ref: commit_row_change.metadata_ref.as_ref(),
240
+ deleted: commit_row_change.snapshot_ref.is_none(),
241
+ created_at: &commit_row_change.created_at,
242
+ updated_at: &commit_row_change.created_at,
243
+ });
244
+ let mut writer = tracked_state.writer(&read, &mut writes);
245
245
  writer
246
- .stage_delta(&receipt.initial_commit_id, None, &deltas)
246
+ .stage_commit_root(&receipt.initial_commit_id, None, deltas)
247
247
  .await?;
248
248
  }
249
249
 
250
- writes.apply(&mut transaction.as_mut()).await?;
251
- transaction.commit().await?;
250
+ storage.commit_write_set(writes, crate::storage::StorageWriteOptions::default())?;
252
251
  Ok(receipt)
253
252
  }
254
253
 
255
- fn seed_change_to_commit_store_change(change: &InitSeedChange) -> Result<Change, LixError> {
256
- Ok(Change {
257
- id: change.id.clone(),
258
- entity_id: change.entity_id.clone(),
254
+ fn seed_change_to_change_record(change: &InitSeedChange) -> ChangeRecord {
255
+ ChangeRecord {
256
+ format_version: 1,
257
+ change_id: change.id.clone(),
258
+ entity_pk: change.entity_pk.clone(),
259
259
  schema_key: change.schema_key.clone(),
260
260
  file_id: None,
261
261
  snapshot_ref: Some(JsonRef::for_content(change.snapshot_content.as_bytes())),
262
262
  metadata_ref: None,
263
263
  created_at: change.created_at.clone(),
264
+ }
265
+ }
266
+
267
+ fn seed_commit_row_change_record(commit: &InitSeedCommit) -> Result<ChangeRecord, LixError> {
268
+ let snapshot_content = commit_row_snapshot_content(&commit.id)?;
269
+ Ok(ChangeRecord {
270
+ format_version: 1,
271
+ change_id: commit.change_id.clone(),
272
+ entity_pk: EntityPk::single(commit.id.clone()),
273
+ schema_key: "lix_commit".to_string(),
274
+ file_id: None,
275
+ snapshot_ref: Some(JsonRef::for_content(snapshot_content.as_bytes())),
276
+ metadata_ref: None,
277
+ created_at: commit.created_at.clone(),
264
278
  })
265
279
  }
266
280
 
281
+ fn stage_init_json_payloads(
282
+ writes: &mut StorageWriteSet,
283
+ plan: &InitSeedPlan,
284
+ ) -> Result<(), LixError> {
285
+ let commit_snapshot = commit_row_snapshot_content(&plan.commit.id)?;
286
+ JsonStoreContext::new().writer().stage_batch(
287
+ writes,
288
+ JsonWritePlacementRef::OutOfBand,
289
+ plan.changes
290
+ .iter()
291
+ .map(|change| NormalizedJsonRef::new(change.snapshot_content.as_str()))
292
+ .chain(std::iter::once(NormalizedJsonRef::new(
293
+ commit_snapshot.as_str(),
294
+ ))),
295
+ )?;
296
+ Ok(())
297
+ }
298
+
299
+ async fn stage_init_changelog_commit(
300
+ read: &mut (impl crate::storage::StorageRead + Send + Sync),
301
+ writes: &mut StorageWriteSet,
302
+ plan: &InitSeedPlan,
303
+ changes: Vec<ChangeRecord>,
304
+ ) -> Result<(), LixError> {
305
+ let commit = CommitRecord {
306
+ format_version: 1,
307
+ commit_id: plan.commit.id.clone(),
308
+ parent_commit_ids: plan.commit.parent_ids.clone(),
309
+ change_id: plan.commit.change_id.clone(),
310
+ author_account_ids: plan.commit.author_account_ids.clone(),
311
+ created_at: plan.commit.created_at.clone(),
312
+ };
313
+ let commit_change_refs = CommitChangeRefSet {
314
+ commit_id: plan.commit.id.clone(),
315
+ entries: plan
316
+ .changes
317
+ .iter()
318
+ .map(commit_change_ref_from_seed_change)
319
+ .collect(),
320
+ };
321
+ let mut writer = ChangelogContext::new().writer(read, writes);
322
+ writer
323
+ .stage_append(ChangelogAppend {
324
+ commits: vec![commit],
325
+ changes,
326
+ commit_change_refs: vec![commit_change_refs],
327
+ })
328
+ .await
329
+ }
330
+
331
+ fn commit_row_snapshot_content(commit_id: &str) -> Result<String, LixError> {
332
+ encode_snapshot(json!({
333
+ "id": commit_id,
334
+ }))
335
+ }
336
+
337
+ fn commit_change_ref_from_seed_change(change: &InitSeedChange) -> CommitChangeRef {
338
+ CommitChangeRef {
339
+ schema_key: change.schema_key.clone(),
340
+ file_id: None,
341
+ entity_pk: change.entity_pk.clone(),
342
+ change_id: change.id.clone(),
343
+ }
344
+ }
345
+
267
346
  fn untracked_state_row_from_seed(row: &InitSeedLiveRow) -> Result<UntrackedStateRow, LixError> {
268
347
  Ok(UntrackedStateRow {
269
- entity_id: row.entity_id.clone(),
348
+ entity_pk: row.entity_pk.clone(),
270
349
  schema_key: row.schema_key.clone(),
271
350
  file_id: None,
272
351
  snapshot_content: Some(row.snapshot_content.clone()),
@@ -274,44 +353,44 @@ fn untracked_state_row_from_seed(row: &InitSeedLiveRow) -> Result<UntrackedState
274
353
  created_at: row.created_at.clone(),
275
354
  updated_at: row.updated_at.clone(),
276
355
  global: row.global,
277
- version_id: row.version_id.clone(),
356
+ branch_id: row.branch_id.clone(),
278
357
  })
279
358
  }
280
359
 
281
360
  fn untracked_row(
282
- entity_id: EntityIdentity,
361
+ entity_pk: EntityPk,
283
362
  schema_key: &str,
284
363
  snapshot_content: String,
285
364
  timestamp: &str,
286
365
  ) -> InitSeedLiveRow {
287
366
  InitSeedLiveRow {
288
- entity_id,
367
+ entity_pk,
289
368
  schema_key: schema_key.to_string(),
290
369
  snapshot_content,
291
370
  created_at: timestamp.to_string(),
292
371
  updated_at: timestamp.to_string(),
293
372
  global: true,
294
- version_id: GLOBAL_VERSION_ID.to_string(),
373
+ branch_id: GLOBAL_BRANCH_ID.to_string(),
295
374
  }
296
375
  }
297
376
 
298
377
  fn canonical_change(
299
378
  id: String,
300
- entity_id: EntityIdentity,
379
+ entity_pk: EntityPk,
301
380
  schema_key: &str,
302
381
  snapshot_content: String,
303
382
  created_at: &str,
304
383
  ) -> InitSeedChange {
305
384
  InitSeedChange {
306
385
  id,
307
- entity_id,
386
+ entity_pk,
308
387
  schema_key: schema_key.to_string(),
309
388
  snapshot_content,
310
389
  created_at: created_at.to_string(),
311
390
  }
312
391
  }
313
392
 
314
- fn version_descriptor_snapshot(id: &str, name: &str, hidden: bool) -> Result<String, LixError> {
393
+ fn branch_descriptor_snapshot(id: &str, name: &str, hidden: bool) -> Result<String, LixError> {
315
394
  encode_snapshot(json!({
316
395
  "id": id,
317
396
  "name": name,
@@ -332,7 +411,7 @@ fn registered_schema_snapshot(schema: &serde_json::Value) -> Result<String, LixE
332
411
  }))
333
412
  }
334
413
 
335
- fn version_ref_snapshot(id: &str, commit_id: &str) -> Result<String, LixError> {
414
+ fn branch_ref_snapshot(id: &str, commit_id: &str) -> Result<String, LixError> {
336
415
  encode_snapshot(json!({
337
416
  "id": id,
338
417
  "commit_id": commit_id,
@@ -353,8 +432,9 @@ mod tests {
353
432
  use serde_json::Value as JsonValue;
354
433
 
355
434
  use super::*;
356
- use crate::backend::{testing::UnitTestBackend, Backend};
435
+ use crate::changelog::ChangelogReader;
357
436
  use crate::functions::{FunctionProvider, SharedFunctionProvider};
437
+ use crate::storage::InMemoryStorageBackend;
358
438
  use crate::storage::StorageContext;
359
439
  use crate::tracked_state::TrackedStateContext;
360
440
  use crate::untracked_state::UntrackedStateContext;
@@ -365,8 +445,8 @@ mod tests {
365
445
 
366
446
  assert_eq!(plan.changes.len(), seed_schema_definitions().len() + 3);
367
447
  assert_eq!(plan.untracked_rows.len(), 3);
368
- assert_eq!(plan.receipt.global_version_id, GLOBAL_VERSION_ID);
369
- assert_eq!(plan.receipt.main_version_id, "test-uuid-1");
448
+ assert_eq!(plan.receipt.global_branch_id, GLOBAL_BRANCH_ID);
449
+ assert_eq!(plan.receipt.main_branch_id, "test-uuid-1");
370
450
  assert_eq!(plan.receipt.lix_id, "test-uuid-2");
371
451
  assert_eq!(plan.receipt.initial_commit_id, "test-uuid-3");
372
452
  }
@@ -429,22 +509,22 @@ mod tests {
429
509
  }
430
510
 
431
511
  #[test]
432
- fn plan_init_seed_version_refs_point_to_initial_commit() {
512
+ fn plan_init_seed_branch_refs_point_to_initial_commit() {
433
513
  let plan = plan_init_seed(test_functions()).expect("init seed should plan");
434
- let version_refs = plan
514
+ let branch_refs = plan
435
515
  .untracked_rows
436
516
  .iter()
437
- .filter(|row| row.schema_key == VERSION_REF_SCHEMA_KEY)
517
+ .filter(|row| row.schema_key == BRANCH_REF_SCHEMA_KEY)
438
518
  .collect::<Vec<_>>();
439
519
 
440
- assert_eq!(version_refs.len(), 2);
520
+ assert_eq!(branch_refs.len(), 2);
441
521
  assert!(plan
442
522
  .changes
443
523
  .iter()
444
- .all(|change| change.schema_key != VERSION_REF_SCHEMA_KEY));
445
- for row in version_refs {
446
- assert_eq!(row.schema_key, VERSION_REF_SCHEMA_KEY);
447
- assert_eq!(row.version_id, GLOBAL_VERSION_ID);
524
+ .all(|change| change.schema_key != BRANCH_REF_SCHEMA_KEY));
525
+ for row in branch_refs {
526
+ assert_eq!(row.schema_key, BRANCH_REF_SCHEMA_KEY);
527
+ assert_eq!(row.branch_id, GLOBAL_BRANCH_ID);
448
528
  let snapshot = untracked_snapshot(row);
449
529
  assert_eq!(
450
530
  snapshot.get("commit_id").and_then(JsonValue::as_str),
@@ -454,74 +534,128 @@ mod tests {
454
534
  }
455
535
 
456
536
  #[test]
457
- fn plan_init_seed_workspace_version_points_to_main_version() {
537
+ fn plan_init_seed_workspace_branch_points_to_main_branch() {
458
538
  let plan = plan_init_seed(test_functions()).expect("init seed should plan");
459
539
  let workspace_row = plan
460
540
  .untracked_rows
461
541
  .iter()
462
542
  .find(|row| {
463
543
  row.schema_key == KEY_VALUE_SCHEMA_KEY
464
- && row.entity_id
465
- == crate::entity_identity::EntityIdentity::single(WORKSPACE_VERSION_KEY)
544
+ && row.entity_pk == crate::entity_pk::EntityPk::single(WORKSPACE_BRANCH_KEY)
466
545
  })
467
- .expect("workspace version row should exist");
546
+ .expect("workspace branch row should exist");
468
547
 
469
- assert_eq!(workspace_row.version_id, GLOBAL_VERSION_ID);
548
+ assert_eq!(workspace_row.branch_id, GLOBAL_BRANCH_ID);
470
549
  assert!(workspace_row.global);
471
550
  let snapshot = untracked_snapshot(workspace_row);
472
551
  assert_eq!(
473
552
  snapshot.get("key").and_then(JsonValue::as_str),
474
- Some(WORKSPACE_VERSION_KEY)
553
+ Some(WORKSPACE_BRANCH_KEY)
475
554
  );
476
555
  assert_eq!(
477
556
  snapshot.get("value").and_then(JsonValue::as_str),
478
- Some(plan.receipt.main_version_id.as_str())
557
+ Some(plan.receipt.main_branch_id.as_str())
479
558
  );
480
559
  }
481
560
 
482
561
  #[tokio::test]
483
- async fn initialize_writes_initial_commit_through_commit_store() {
484
- let backend: Arc<dyn Backend + Send + Sync> = Arc::new(UnitTestBackend::new());
562
+ async fn initialize_writes_initial_commit_through_changelog() {
563
+ let backend = InMemoryStorageBackend::new();
485
564
  let storage = StorageContext::new(backend);
486
- let commit_store = CommitStoreContext::new();
487
565
  let tracked_state = TrackedStateContext::new();
488
566
  let untracked_state = UntrackedStateContext::new();
489
567
 
490
- let receipt = initialize(
491
- storage.clone(),
492
- &commit_store,
493
- &tracked_state,
494
- &untracked_state,
495
- )
496
- .await
497
- .expect("engine should initialize");
498
- let reader = commit_store.reader(storage.clone());
499
- let commit = reader
500
- .load_commit(&receipt.initial_commit_id)
568
+ let receipt = initialize(storage.clone(), &tracked_state, &untracked_state)
501
569
  .await
502
- .expect("commit should load")
503
- .expect("initial commit should exist");
504
-
505
- assert_eq!(commit.id, receipt.initial_commit_id);
506
- assert_eq!(commit.change_pack_count, 1);
507
- assert_eq!(commit.membership_pack_count, 0);
508
-
509
- let change_pack = reader
510
- .load_change_pack(&commit.id, 0)
570
+ .expect("engine should initialize");
571
+ let mut reader = ChangelogContext::new().reader(
572
+ storage
573
+ .begin_read(crate::storage::StorageReadOptions::default())
574
+ .expect("read should open"),
575
+ );
576
+ let commits = reader
577
+ .load_commits(crate::changelog::CommitLoadRequest {
578
+ commit_ids: &[receipt.initial_commit_id.clone()],
579
+ projection: crate::changelog::CommitProjection::Full,
580
+ })
511
581
  .await
512
- .expect("change pack should load")
513
- .expect("initial change pack should exist");
514
- assert_eq!(change_pack.len(), seed_schema_definitions().len() + 3);
515
- assert!(change_pack
582
+ .expect("commit should load");
583
+ let Some(crate::changelog::CommitLoadEntry::Full {
584
+ record,
585
+ change_ref_chunks,
586
+ }) = commits.entries.into_iter().next().flatten()
587
+ else {
588
+ panic!("initial commit should exist");
589
+ };
590
+
591
+ assert_eq!(record.commit_id, receipt.initial_commit_id);
592
+ let commit_change_id = record.change_id.clone();
593
+ let change_refs = change_ref_chunks
516
594
  .iter()
517
- .all(|change| change.id != commit.change_id));
595
+ .flat_map(|chunk| chunk.entries.iter())
596
+ .collect::<Vec<_>>();
597
+ assert_eq!(change_refs.len(), seed_schema_definitions().len() + 3);
598
+ assert!(
599
+ !change_refs
600
+ .iter()
601
+ .any(|change_ref| change_ref.change_id == record.change_id),
602
+ "initial commit row is derived from changelog.commit, not stored in commit refs"
603
+ );
518
604
 
519
- let entries = reader
520
- .load_change_index_entries(&[commit.change_id.clone(), "global".to_string()])
605
+ let changes = reader
606
+ .load_changes(crate::changelog::ChangeLoadRequest {
607
+ change_ids: &["global".to_string()],
608
+ })
521
609
  .await
522
610
  .expect("change index should load");
523
- assert!(entries[0].is_some());
524
- assert!(entries[1].is_some());
611
+ assert!(matches!(
612
+ changes.entries.as_slice(),
613
+ [Some(change)] if change.change_id == "global"
614
+ ));
615
+ let missing_derivable = reader
616
+ .load_changes(crate::changelog::ChangeLoadRequest {
617
+ change_ids: &[commit_change_id.clone()],
618
+ })
619
+ .await
620
+ .expect("derivable change lookup should load");
621
+ assert!(matches!(missing_derivable.entries.as_slice(), [None]));
622
+ {
623
+ let read = storage
624
+ .begin_read(crate::storage::StorageReadOptions::default())
625
+ .expect("read should open");
626
+ let mut writes = storage.new_write_set();
627
+ tracked_state
628
+ .root_rebuilder(&read, &mut writes)
629
+ .rebuild_commit_root_at(&receipt.initial_commit_id)
630
+ .await
631
+ .expect("initial commit root should rebuild from changelog refs");
632
+ drop(read);
633
+ storage
634
+ .commit_write_set(writes, crate::storage::StorageWriteOptions::default())
635
+ .expect("rebuilt initial commit root should commit");
636
+ }
637
+ let mut tracked_reader = tracked_state.reader(
638
+ storage
639
+ .begin_read(crate::storage::StorageReadOptions::default())
640
+ .expect("read should open"),
641
+ );
642
+ let rows = tracked_reader
643
+ .scan_rows_at_commit(
644
+ &receipt.initial_commit_id,
645
+ &crate::tracked_state::TrackedStateScanRequest {
646
+ filter: crate::tracked_state::TrackedStateFilter {
647
+ schema_keys: vec!["lix_commit".to_string()],
648
+ ..Default::default()
649
+ },
650
+ ..Default::default()
651
+ },
652
+ )
653
+ .await
654
+ .expect("tracked initial root should scan");
655
+ assert!(
656
+ rows.iter().any(|row| row.change_id == commit_change_id),
657
+ "initial commit root should surface its lix_commit row"
658
+ );
525
659
  }
526
660
 
527
661
  fn snapshot(change: &InitSeedChange) -> JsonValue {