@lix-js/sdk 0.6.0-preview.1 → 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 (205) hide show
  1. package/SKILL.md +304 -320
  2. package/dist/engine-wasm/wasm/lix_engine.d.ts +5 -0
  3. package/dist/engine-wasm/wasm/lix_engine.js +9 -13
  4. package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
  5. package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +1 -0
  6. package/dist/generated/builtin-schemas.d.ts +87 -162
  7. package/dist/generated/builtin-schemas.js +139 -236
  8. package/dist/open-lix.d.ts +103 -14
  9. package/dist/open-lix.js +3 -0
  10. package/dist/sqlite/index.js +99 -22
  11. package/dist-engine-src/README.md +18 -0
  12. package/dist-engine-src/src/backend/kv.rs +358 -0
  13. package/dist-engine-src/src/backend/mod.rs +12 -0
  14. package/dist-engine-src/src/backend/testing.rs +658 -0
  15. package/dist-engine-src/src/backend/types.rs +96 -0
  16. package/dist-engine-src/src/binary_cas/chunking.rs +31 -0
  17. package/dist-engine-src/src/binary_cas/codec.rs +346 -0
  18. package/dist-engine-src/src/binary_cas/context.rs +139 -0
  19. package/dist-engine-src/src/binary_cas/kv.rs +1063 -0
  20. package/dist-engine-src/src/binary_cas/mod.rs +11 -0
  21. package/dist-engine-src/src/binary_cas/types.rs +121 -0
  22. package/dist-engine-src/src/catalog/context.rs +412 -0
  23. package/dist-engine-src/src/catalog/mod.rs +10 -0
  24. package/dist-engine-src/src/catalog/schema.rs +4 -0
  25. package/dist-engine-src/src/catalog/snapshot.rs +1114 -0
  26. package/dist-engine-src/src/cel/context.rs +86 -0
  27. package/dist-engine-src/src/cel/error.rs +19 -0
  28. package/dist-engine-src/src/cel/mod.rs +8 -0
  29. package/dist-engine-src/src/cel/provider.rs +9 -0
  30. package/dist-engine-src/src/cel/runtime.rs +167 -0
  31. package/dist-engine-src/src/cel/value.rs +50 -0
  32. package/dist-engine-src/src/commit_graph/context.rs +901 -0
  33. package/dist-engine-src/src/commit_graph/mod.rs +11 -0
  34. package/dist-engine-src/src/commit_graph/types.rs +109 -0
  35. package/dist-engine-src/src/commit_graph/walker.rs +756 -0
  36. package/dist-engine-src/src/commit_store/codec.rs +887 -0
  37. package/dist-engine-src/src/commit_store/context.rs +944 -0
  38. package/dist-engine-src/src/commit_store/materialization.rs +84 -0
  39. package/dist-engine-src/src/commit_store/mod.rs +16 -0
  40. package/dist-engine-src/src/commit_store/storage.rs +600 -0
  41. package/dist-engine-src/src/commit_store/types.rs +215 -0
  42. package/dist-engine-src/src/common/error.rs +313 -0
  43. package/dist-engine-src/src/common/fingerprint.rs +3 -0
  44. package/dist-engine-src/src/common/fs_path.rs +1336 -0
  45. package/dist-engine-src/src/common/identity.rs +145 -0
  46. package/dist-engine-src/src/common/json_pointer.rs +67 -0
  47. package/dist-engine-src/src/common/metadata.rs +40 -0
  48. package/dist-engine-src/src/common/mod.rs +23 -0
  49. package/dist-engine-src/src/common/types.rs +105 -0
  50. package/dist-engine-src/src/common/wire.rs +222 -0
  51. package/dist-engine-src/src/domain.rs +324 -0
  52. package/dist-engine-src/src/engine.rs +225 -0
  53. package/dist-engine-src/src/entity_identity.rs +405 -0
  54. package/dist-engine-src/src/functions/context.rs +292 -0
  55. package/dist-engine-src/src/functions/deterministic.rs +113 -0
  56. package/dist-engine-src/src/functions/mod.rs +18 -0
  57. package/dist-engine-src/src/functions/provider.rs +130 -0
  58. package/dist-engine-src/src/functions/state.rs +336 -0
  59. package/dist-engine-src/src/functions/types.rs +37 -0
  60. package/dist-engine-src/src/init.rs +558 -0
  61. package/dist-engine-src/src/json_store/compression.rs +77 -0
  62. package/dist-engine-src/src/json_store/context.rs +423 -0
  63. package/dist-engine-src/src/json_store/encoded.rs +15 -0
  64. package/dist-engine-src/src/json_store/mod.rs +12 -0
  65. package/dist-engine-src/src/json_store/store.rs +1109 -0
  66. package/dist-engine-src/src/json_store/types.rs +217 -0
  67. package/dist-engine-src/src/lib.rs +62 -0
  68. package/dist-engine-src/src/live_state/context.rs +2019 -0
  69. package/dist-engine-src/src/live_state/mod.rs +15 -0
  70. package/dist-engine-src/src/live_state/overlay.rs +75 -0
  71. package/dist-engine-src/src/live_state/reader.rs +23 -0
  72. package/dist-engine-src/src/live_state/types.rs +222 -0
  73. package/dist-engine-src/src/live_state/visibility.rs +223 -0
  74. package/dist-engine-src/src/plugin/archive.rs +438 -0
  75. package/dist-engine-src/src/plugin/component.rs +183 -0
  76. package/dist-engine-src/src/plugin/install.rs +619 -0
  77. package/dist-engine-src/src/plugin/manifest.rs +516 -0
  78. package/dist-engine-src/src/plugin/materializer.rs +477 -0
  79. package/dist-engine-src/src/plugin/mod.rs +33 -0
  80. package/dist-engine-src/src/plugin/plugin_manifest.json +118 -0
  81. package/dist-engine-src/src/plugin/storage.rs +74 -0
  82. package/dist-engine-src/src/schema/annotations/defaults.rs +275 -0
  83. package/dist-engine-src/src/schema/annotations/mod.rs +1 -0
  84. package/dist-engine-src/src/schema/builtin/lix_account.json +21 -0
  85. package/dist-engine-src/src/schema/builtin/lix_active_account.json +29 -0
  86. package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +29 -0
  87. package/dist-engine-src/src/schema/builtin/lix_change.json +63 -0
  88. package/dist-engine-src/src/schema/builtin/lix_change_author.json +45 -0
  89. package/dist-engine-src/src/schema/builtin/lix_commit.json +24 -0
  90. package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +53 -0
  91. package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +52 -0
  92. package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +52 -0
  93. package/dist-engine-src/src/schema/builtin/lix_key_value.json +40 -0
  94. package/dist-engine-src/src/schema/builtin/lix_label.json +29 -0
  95. package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +74 -0
  96. package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +25 -0
  97. package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +34 -0
  98. package/dist-engine-src/src/schema/builtin/lix_version_ref.json +48 -0
  99. package/dist-engine-src/src/schema/builtin/mod.rs +222 -0
  100. package/dist-engine-src/src/schema/compatibility.rs +787 -0
  101. package/dist-engine-src/src/schema/definition.json +187 -0
  102. package/dist-engine-src/src/schema/definition.rs +742 -0
  103. package/dist-engine-src/src/schema/key.rs +138 -0
  104. package/dist-engine-src/src/schema/mod.rs +20 -0
  105. package/dist-engine-src/src/schema/seed.rs +14 -0
  106. package/dist-engine-src/src/schema/tests.rs +780 -0
  107. package/dist-engine-src/src/session/context.rs +364 -0
  108. package/dist-engine-src/src/session/create_version.rs +88 -0
  109. package/dist-engine-src/src/session/execute.rs +478 -0
  110. package/dist-engine-src/src/session/merge/analysis.rs +102 -0
  111. package/dist-engine-src/src/session/merge/apply.rs +23 -0
  112. package/dist-engine-src/src/session/merge/conflicts.rs +63 -0
  113. package/dist-engine-src/src/session/merge/mod.rs +11 -0
  114. package/dist-engine-src/src/session/merge/stats.rs +65 -0
  115. package/dist-engine-src/src/session/merge/version.rs +427 -0
  116. package/dist-engine-src/src/session/mod.rs +27 -0
  117. package/dist-engine-src/src/session/optimization9_sql2_bench.rs +100 -0
  118. package/dist-engine-src/src/session/switch_version.rs +109 -0
  119. package/dist-engine-src/src/sql2/change_provider.rs +331 -0
  120. package/dist-engine-src/src/sql2/classify.rs +182 -0
  121. package/dist-engine-src/src/sql2/context.rs +311 -0
  122. package/dist-engine-src/src/sql2/directory_history_provider.rs +631 -0
  123. package/dist-engine-src/src/sql2/directory_provider.rs +2453 -0
  124. package/dist-engine-src/src/sql2/dml.rs +148 -0
  125. package/dist-engine-src/src/sql2/entity_history_provider.rs +440 -0
  126. package/dist-engine-src/src/sql2/entity_provider.rs +3211 -0
  127. package/dist-engine-src/src/sql2/error.rs +216 -0
  128. package/dist-engine-src/src/sql2/execute.rs +3440 -0
  129. package/dist-engine-src/src/sql2/file_history_provider.rs +910 -0
  130. package/dist-engine-src/src/sql2/file_provider.rs +3679 -0
  131. package/dist-engine-src/src/sql2/filesystem_planner.rs +1490 -0
  132. package/dist-engine-src/src/sql2/filesystem_predicates.rs +159 -0
  133. package/dist-engine-src/src/sql2/filesystem_visibility.rs +383 -0
  134. package/dist-engine-src/src/sql2/history_projection.rs +56 -0
  135. package/dist-engine-src/src/sql2/history_provider.rs +412 -0
  136. package/dist-engine-src/src/sql2/history_route.rs +657 -0
  137. package/dist-engine-src/src/sql2/lix_state_provider.rs +2512 -0
  138. package/dist-engine-src/src/sql2/mod.rs +46 -0
  139. package/dist-engine-src/src/sql2/predicate_typecheck.rs +246 -0
  140. package/dist-engine-src/src/sql2/public_bind/assignment.rs +46 -0
  141. package/dist-engine-src/src/sql2/public_bind/capability.rs +41 -0
  142. package/dist-engine-src/src/sql2/public_bind/dml.rs +166 -0
  143. package/dist-engine-src/src/sql2/public_bind/mod.rs +25 -0
  144. package/dist-engine-src/src/sql2/public_bind/table.rs +168 -0
  145. package/dist-engine-src/src/sql2/read_only.rs +63 -0
  146. package/dist-engine-src/src/sql2/record_batch.rs +17 -0
  147. package/dist-engine-src/src/sql2/result_metadata.rs +29 -0
  148. package/dist-engine-src/src/sql2/runtime.rs +60 -0
  149. package/dist-engine-src/src/sql2/session.rs +132 -0
  150. package/dist-engine-src/src/sql2/udfs/common.rs +295 -0
  151. package/dist-engine-src/src/sql2/udfs/lix_active_version_commit_id.rs +53 -0
  152. package/dist-engine-src/src/sql2/udfs/lix_empty_blob.rs +47 -0
  153. package/dist-engine-src/src/sql2/udfs/lix_json.rs +100 -0
  154. package/dist-engine-src/src/sql2/udfs/lix_json_get.rs +99 -0
  155. package/dist-engine-src/src/sql2/udfs/lix_json_get_text.rs +99 -0
  156. package/dist-engine-src/src/sql2/udfs/lix_text_decode.rs +82 -0
  157. package/dist-engine-src/src/sql2/udfs/lix_text_encode.rs +85 -0
  158. package/dist-engine-src/src/sql2/udfs/lix_timestamp.rs +76 -0
  159. package/dist-engine-src/src/sql2/udfs/lix_uuid_v7.rs +76 -0
  160. package/dist-engine-src/src/sql2/udfs/mod.rs +89 -0
  161. package/dist-engine-src/src/sql2/udfs/public_call.rs +211 -0
  162. package/dist-engine-src/src/sql2/version_provider.rs +1202 -0
  163. package/dist-engine-src/src/sql2/version_scope.rs +394 -0
  164. package/dist-engine-src/src/sql2/write_normalization.rs +345 -0
  165. package/dist-engine-src/src/storage/context.rs +356 -0
  166. package/dist-engine-src/src/storage/mod.rs +14 -0
  167. package/dist-engine-src/src/storage/read_scope.rs +88 -0
  168. package/dist-engine-src/src/storage/types.rs +501 -0
  169. package/dist-engine-src/src/storage_bench.rs +4863 -0
  170. package/dist-engine-src/src/test_support.rs +228 -0
  171. package/dist-engine-src/src/tracked_state/by_file_index.rs +98 -0
  172. package/dist-engine-src/src/tracked_state/codec.rs +2085 -0
  173. package/dist-engine-src/src/tracked_state/context.rs +1867 -0
  174. package/dist-engine-src/src/tracked_state/diff.rs +686 -0
  175. package/dist-engine-src/src/tracked_state/materialization.rs +403 -0
  176. package/dist-engine-src/src/tracked_state/materializer.rs +488 -0
  177. package/dist-engine-src/src/tracked_state/merge.rs +492 -0
  178. package/dist-engine-src/src/tracked_state/mod.rs +32 -0
  179. package/dist-engine-src/src/tracked_state/storage.rs +375 -0
  180. package/dist-engine-src/src/tracked_state/tree.rs +3187 -0
  181. package/dist-engine-src/src/tracked_state/types.rs +231 -0
  182. package/dist-engine-src/src/transaction/commit.rs +1484 -0
  183. package/dist-engine-src/src/transaction/context.rs +1548 -0
  184. package/dist-engine-src/src/transaction/live_state_overlay.rs +35 -0
  185. package/dist-engine-src/src/transaction/mod.rs +13 -0
  186. package/dist-engine-src/src/transaction/normalization.rs +890 -0
  187. package/dist-engine-src/src/transaction/prep.rs +37 -0
  188. package/dist-engine-src/src/transaction/schema_resolver.rs +149 -0
  189. package/dist-engine-src/src/transaction/staging.rs +1731 -0
  190. package/dist-engine-src/src/transaction/types.rs +460 -0
  191. package/dist-engine-src/src/transaction/validation.rs +5830 -0
  192. package/dist-engine-src/src/untracked_state/codec.rs +307 -0
  193. package/dist-engine-src/src/untracked_state/context.rs +98 -0
  194. package/dist-engine-src/src/untracked_state/materialization.rs +63 -0
  195. package/dist-engine-src/src/untracked_state/mod.rs +15 -0
  196. package/dist-engine-src/src/untracked_state/storage.rs +396 -0
  197. package/dist-engine-src/src/untracked_state/types.rs +146 -0
  198. package/dist-engine-src/src/version/context.rs +40 -0
  199. package/dist-engine-src/src/version/lifecycle.rs +221 -0
  200. package/dist-engine-src/src/version/mod.rs +13 -0
  201. package/dist-engine-src/src/version/refs.rs +330 -0
  202. package/dist-engine-src/src/version/stage_rows.rs +67 -0
  203. package/dist-engine-src/src/version/types.rs +21 -0
  204. package/dist-engine-src/src/wasm/mod.rs +60 -0
  205. package/package.json +68 -64
@@ -0,0 +1,619 @@
1
+ //! Plugin install write helpers.
2
+ //!
3
+ //! This module owns plugin archive parsing, registered-schema staging, and the
4
+ //! prepared write construction needed to install a plugin into the engine.
5
+
6
+ use std::collections::BTreeMap;
7
+
8
+ use async_trait::async_trait;
9
+ use serde_json::{json, Value as JsonValue};
10
+
11
+ use crate::catalog::{ResolvedRelation, SurfaceRegistry};
12
+ use crate::common::stable_content_fingerprint_hex;
13
+ use crate::common::{NormalizedDirectoryPath, ParsedFilePath};
14
+ use crate::plugin::{
15
+ parse_plugin_archive_for_install, plugin_storage_archive_file_id, plugin_storage_archive_path,
16
+ ParsedPluginArchive, PLUGIN_STORAGE_ROOT_DIRECTORY_PATH,
17
+ };
18
+ use crate::schema::{schema_key_from_definition, validate_lix_schema_definition};
19
+ use crate::sql::{
20
+ ChangeBatch, CommitPreconditions, ExpectedHead, IdempotencyKey, OptionalTextPatch, PlanEffects,
21
+ PlannedFilesystemDescriptor, PlannedFilesystemFile, PlannedFilesystemState, PlannedStateRow,
22
+ PreparedWriteOperationKind, PreparedWriteStatementKind, PublicChange, ResultContract,
23
+ SchemaLiveTableRequirement, SemanticEffect, WriteDiagnosticContext, WriteLane, WriteMode,
24
+ };
25
+ use crate::streams::{
26
+ state_commit_stream_changes_from_changes, StateCommitStreamOperation,
27
+ StateCommitStreamRuntimeMetadata,
28
+ };
29
+ use crate::transaction::{
30
+ PreparedPublicSurfaceRegistryEffect, PreparedPublicSurfaceRegistryMutation,
31
+ PreparedPublicWrite, PreparedPublicWriteContract, PreparedPublicWriteExecution,
32
+ PreparedPublicWriteMaterialization, PreparedPublicWritePlanArtifact,
33
+ PreparedResolvedWritePartition, PreparedResolvedWritePlan, PreparedWriteArtifact,
34
+ PreparedWriteFunctionBindings, PreparedWriteStatement,
35
+ };
36
+ use crate::{LixError, Value};
37
+
38
+ use crate::transaction::WriteCommand;
39
+ const REGISTERED_SCHEMA_STORAGE_SCHEMA_KEY: &str = "lix_registered_schema";
40
+ const FILESYSTEM_DESCRIPTOR_SCHEMA_KEY: &str = "lix_file_descriptor";
41
+ const FILESYSTEM_BINARY_BLOB_REF_SCHEMA_KEY: &str = "lix_binary_blob_ref";
42
+
43
+ #[derive(Clone)]
44
+ pub(crate) struct PluginInstallWriteContext {
45
+ function_bindings: PreparedWriteFunctionBindings,
46
+ public_surface_registry: SurfaceRegistry,
47
+ target_version_id: String,
48
+ active_account_ids: Vec<String>,
49
+ origin_key: Option<String>,
50
+ }
51
+
52
+ impl PluginInstallWriteContext {
53
+ pub(crate) fn new(
54
+ function_bindings: PreparedWriteFunctionBindings,
55
+ public_surface_registry: SurfaceRegistry,
56
+ target_version_id: impl Into<String>,
57
+ active_account_ids: Vec<String>,
58
+ origin_key: Option<String>,
59
+ ) -> Self {
60
+ Self {
61
+ function_bindings,
62
+ public_surface_registry,
63
+ target_version_id: target_version_id.into(),
64
+ active_account_ids,
65
+ origin_key,
66
+ }
67
+ }
68
+
69
+ fn target_version_id(&self) -> &str {
70
+ &self.target_version_id
71
+ }
72
+ }
73
+
74
+ #[async_trait(?Send)]
75
+ pub(crate) trait PluginInstallWriteExecutor {
76
+ fn plugin_install_write_context(&self) -> PluginInstallWriteContext;
77
+
78
+ fn stage_prepared_write_statement(&mut self, statement: WriteCommand) -> Result<(), LixError>;
79
+
80
+ async fn resolve_directory_id(
81
+ &mut self,
82
+ path: &NormalizedDirectoryPath,
83
+ ) -> Result<Option<String>, LixError>;
84
+ }
85
+
86
+ pub(crate) async fn install_plugin_archive_with_writer(
87
+ archive_bytes: &[u8],
88
+ executor: &mut dyn PluginInstallWriteExecutor,
89
+ ) -> Result<(), LixError> {
90
+ let parsed = parse_plugin_archive_for_install(archive_bytes)?;
91
+ install_plugin_with_writer(executor, &parsed, archive_bytes).await
92
+ }
93
+
94
+ pub(crate) fn prepare_registered_schema_write_statement(
95
+ schema: &JsonValue,
96
+ context: &PluginInstallWriteContext,
97
+ ) -> Result<WriteCommand, LixError> {
98
+ prepare_registered_schema_write_statement_from_schemas(std::slice::from_ref(schema), context)
99
+ }
100
+
101
+ async fn install_plugin_with_writer(
102
+ executor: &mut dyn PluginInstallWriteExecutor,
103
+ parsed: &ParsedPluginArchive,
104
+ archive_bytes: &[u8],
105
+ ) -> Result<(), LixError> {
106
+ let plugin_install_context = executor.plugin_install_write_context();
107
+
108
+ if !parsed.schemas.is_empty() {
109
+ executor.stage_prepared_write_statement(
110
+ prepare_registered_schema_write_statement_from_schemas(
111
+ &parsed.schemas,
112
+ &plugin_install_context,
113
+ )?,
114
+ )?;
115
+ }
116
+
117
+ let plugin_root =
118
+ NormalizedDirectoryPath::from_normalized(PLUGIN_STORAGE_ROOT_DIRECTORY_PATH.to_string());
119
+ let plugin_directory_id = executor
120
+ .resolve_directory_id(&plugin_root)
121
+ .await?
122
+ .ok_or_else(|| {
123
+ LixError::new(
124
+ "LIX_ERROR_UNKNOWN",
125
+ format!(
126
+ "plugin storage directory '{}' is missing",
127
+ PLUGIN_STORAGE_ROOT_DIRECTORY_PATH
128
+ ),
129
+ )
130
+ })?;
131
+ executor.stage_prepared_write_statement(prepare_plugin_archive_write_statement(
132
+ parsed,
133
+ archive_bytes,
134
+ &plugin_directory_id,
135
+ &plugin_install_context,
136
+ )?)?;
137
+
138
+ Ok(())
139
+ }
140
+
141
+ #[derive(Clone)]
142
+ struct RegisteredSchemaRowSpec {
143
+ entity_id: String,
144
+ registered_schema_key: String,
145
+ snapshot: JsonValue,
146
+ schema_json: JsonValue,
147
+ }
148
+
149
+ fn prepare_registered_schema_write_statement_from_schemas(
150
+ schemas: &[JsonValue],
151
+ context: &PluginInstallWriteContext,
152
+ ) -> Result<WriteCommand, LixError> {
153
+ let target = require_resolved_surface(
154
+ &context.public_surface_registry,
155
+ "lix_registered_schema_by_version",
156
+ )?;
157
+ let schema_rows = schemas
158
+ .iter()
159
+ .map(registered_schema_row_spec_from_json)
160
+ .collect::<Result<Vec<_>, _>>()?;
161
+ let intended_post_state = schema_rows
162
+ .iter()
163
+ .map(|row| registered_schema_planned_row(row, context.target_version_id()))
164
+ .collect::<Vec<_>>();
165
+ let changes = schema_rows
166
+ .iter()
167
+ .map(|row| PublicChange {
168
+ entity_id: row.entity_id.clone(),
169
+ schema_key: REGISTERED_SCHEMA_STORAGE_SCHEMA_KEY.to_string(),
170
+ file_id: None,
171
+ plugin_key: None,
172
+ snapshot_content: Some(row.snapshot.to_string()),
173
+ metadata: None,
174
+ version_id: context.target_version_id().to_string(),
175
+ origin_key: context.origin_key.clone(),
176
+ })
177
+ .collect::<Vec<_>>();
178
+ let schema_live_table_requirements = schema_rows
179
+ .iter()
180
+ .map(|row| SchemaLiveTableRequirement {
181
+ schema_key: row.registered_schema_key.clone(),
182
+ schema_definition: Some(row.schema_json.clone()),
183
+ })
184
+ .collect::<Vec<_>>();
185
+
186
+ prepare_public_tracked_write_statement(
187
+ context,
188
+ target,
189
+ "lix_registered_schema_by_version",
190
+ intended_post_state,
191
+ PlannedFilesystemState::default(),
192
+ changes,
193
+ schema_live_table_requirements,
194
+ PreparedPublicSurfaceRegistryEffect::ApplyMutations(
195
+ schema_rows
196
+ .iter()
197
+ .map(
198
+ |row| PreparedPublicSurfaceRegistryMutation::UpsertRegisteredSchemaSnapshot {
199
+ snapshot: row.snapshot.clone(),
200
+ },
201
+ )
202
+ .collect(),
203
+ ),
204
+ "semantic.register_schema",
205
+ )
206
+ }
207
+
208
+ fn prepare_plugin_archive_write_statement(
209
+ parsed: &ParsedPluginArchive,
210
+ archive_bytes: &[u8],
211
+ plugin_directory_id: &str,
212
+ context: &PluginInstallWriteContext,
213
+ ) -> Result<WriteCommand, LixError> {
214
+ let target = require_resolved_surface(&context.public_surface_registry, "lix_file_by_version")?;
215
+ let archive_id = plugin_storage_archive_file_id(parsed.manifest.key.as_str());
216
+ let archive_path = plugin_storage_archive_path(parsed.manifest.key.as_str())?;
217
+ let parsed_path = ParsedFilePath::try_from_path(&archive_path)?;
218
+ let descriptor = PlannedFilesystemDescriptor {
219
+ directory_id: plugin_directory_id.to_string(),
220
+ name: parsed_path.name.clone(),
221
+ metadata: None,
222
+ hidden: false,
223
+ };
224
+ let target_version_id = context.target_version_id();
225
+ let filesystem_state = PlannedFilesystemState {
226
+ files: [(
227
+ (archive_id.clone(), target_version_id.to_string()),
228
+ PlannedFilesystemFile {
229
+ file_id: archive_id.clone(),
230
+ version_id: target_version_id.to_string(),
231
+ untracked: false,
232
+ descriptor: Some(descriptor.clone()),
233
+ metadata_patch: OptionalTextPatch::Unchanged,
234
+ data: Some(archive_bytes.to_vec()),
235
+ deleted: false,
236
+ },
237
+ )]
238
+ .into_iter()
239
+ .collect(),
240
+ };
241
+ let intended_post_state = vec![
242
+ plugin_archive_file_descriptor_row(&archive_id, target_version_id, &descriptor),
243
+ plugin_archive_binary_blob_ref_row(&archive_id, target_version_id, archive_bytes)?,
244
+ ];
245
+ let changes = intended_post_state
246
+ .iter()
247
+ .map(planned_row_to_public_change)
248
+ .collect::<Result<Vec<_>, _>>()?;
249
+
250
+ prepare_public_tracked_write_statement(
251
+ context,
252
+ target,
253
+ "lix_file_by_version",
254
+ intended_post_state,
255
+ filesystem_state,
256
+ changes,
257
+ Vec::new(),
258
+ PreparedPublicSurfaceRegistryEffect::None,
259
+ "semantic.install_plugin_archive",
260
+ )
261
+ }
262
+
263
+ fn registered_schema_row_spec_from_json(
264
+ schema: &JsonValue,
265
+ ) -> Result<RegisteredSchemaRowSpec, LixError> {
266
+ validate_lix_schema_definition(schema)?;
267
+ let schema_key = schema_key_from_definition(schema)?;
268
+ Ok(RegisteredSchemaRowSpec {
269
+ entity_id: schema_key.entity_id(),
270
+ registered_schema_key: schema_key.schema_key,
271
+ snapshot: json!({ "value": schema }),
272
+ schema_json: schema.clone(),
273
+ })
274
+ }
275
+
276
+ fn registered_schema_planned_row(
277
+ row: &RegisteredSchemaRowSpec,
278
+ target_version_id: &str,
279
+ ) -> PlannedStateRow {
280
+ let mut values = BTreeMap::new();
281
+ values.insert("entity_id".to_string(), Value::Text(row.entity_id.clone()));
282
+ values.insert(
283
+ "schema_key".to_string(),
284
+ Value::Text(REGISTERED_SCHEMA_STORAGE_SCHEMA_KEY.to_string()),
285
+ );
286
+ values.insert("file_id".to_string(), Value::Null);
287
+ values.insert("plugin_key".to_string(), Value::Null);
288
+ values.insert(
289
+ "snapshot_content".to_string(),
290
+ Value::Json(row.snapshot.clone()),
291
+ );
292
+ values.insert(
293
+ "version_id".to_string(),
294
+ Value::Text(target_version_id.to_string()),
295
+ );
296
+ PlannedStateRow {
297
+ entity_id: row.entity_id.clone(),
298
+ schema_key: REGISTERED_SCHEMA_STORAGE_SCHEMA_KEY.to_string(),
299
+ version_id: Some(target_version_id.to_string()),
300
+ values,
301
+ origin_key: None,
302
+ tombstone: false,
303
+ }
304
+ }
305
+
306
+ fn plugin_archive_file_descriptor_row(
307
+ archive_id: &str,
308
+ target_version_id: &str,
309
+ descriptor: &PlannedFilesystemDescriptor,
310
+ ) -> PlannedStateRow {
311
+ let snapshot_content = json!({
312
+ "id": archive_id,
313
+ "directory_id": descriptor.directory_id,
314
+ "name": descriptor.name,
315
+ "hidden": descriptor.hidden,
316
+ })
317
+ .to_string();
318
+ let mut values = BTreeMap::new();
319
+ values.insert("entity_id".to_string(), Value::Text(archive_id.to_string()));
320
+ values.insert(
321
+ "schema_key".to_string(),
322
+ Value::Text(FILESYSTEM_DESCRIPTOR_SCHEMA_KEY.to_string()),
323
+ );
324
+ values.insert("file_id".to_string(), Value::Null);
325
+ values.insert("plugin_key".to_string(), Value::Null);
326
+ values.insert(
327
+ "snapshot_content".to_string(),
328
+ Value::Text(snapshot_content),
329
+ );
330
+ values.insert(
331
+ "version_id".to_string(),
332
+ Value::Text(target_version_id.to_string()),
333
+ );
334
+ PlannedStateRow {
335
+ entity_id: archive_id.to_string(),
336
+ schema_key: FILESYSTEM_DESCRIPTOR_SCHEMA_KEY.to_string(),
337
+ version_id: Some(target_version_id.to_string()),
338
+ values,
339
+ origin_key: None,
340
+ tombstone: false,
341
+ }
342
+ }
343
+
344
+ fn plugin_archive_binary_blob_ref_row(
345
+ archive_id: &str,
346
+ target_version_id: &str,
347
+ archive_bytes: &[u8],
348
+ ) -> Result<PlannedStateRow, LixError> {
349
+ let size_bytes = u64::try_from(archive_bytes.len()).map_err(|_| {
350
+ LixError::new(
351
+ "LIX_ERROR_UNKNOWN",
352
+ format!(
353
+ "plugin archive '{}' exceeds supported size range",
354
+ archive_id
355
+ ),
356
+ )
357
+ })?;
358
+ let snapshot_content = json!({
359
+ "id": archive_id,
360
+ "blob_hash": stable_content_fingerprint_hex(archive_bytes),
361
+ "size_bytes": size_bytes,
362
+ })
363
+ .to_string();
364
+ let mut values = BTreeMap::new();
365
+ values.insert("entity_id".to_string(), Value::Text(archive_id.to_string()));
366
+ values.insert(
367
+ "schema_key".to_string(),
368
+ Value::Text(FILESYSTEM_BINARY_BLOB_REF_SCHEMA_KEY.to_string()),
369
+ );
370
+ values.insert("file_id".to_string(), Value::Text(archive_id.to_string()));
371
+ values.insert("plugin_key".to_string(), Value::Null);
372
+ values.insert(
373
+ "snapshot_content".to_string(),
374
+ Value::Text(snapshot_content),
375
+ );
376
+ values.insert(
377
+ "version_id".to_string(),
378
+ Value::Text(target_version_id.to_string()),
379
+ );
380
+ Ok(PlannedStateRow {
381
+ entity_id: archive_id.to_string(),
382
+ schema_key: FILESYSTEM_BINARY_BLOB_REF_SCHEMA_KEY.to_string(),
383
+ version_id: Some(target_version_id.to_string()),
384
+ values,
385
+ origin_key: None,
386
+ tombstone: false,
387
+ })
388
+ }
389
+
390
+ fn prepare_public_tracked_write_statement(
391
+ context: &PluginInstallWriteContext,
392
+ target: ResolvedRelation,
393
+ relation_name: &str,
394
+ intended_post_state: Vec<PlannedStateRow>,
395
+ filesystem_state: PlannedFilesystemState,
396
+ changes: Vec<PublicChange>,
397
+ schema_live_table_requirements: Vec<SchemaLiveTableRequirement>,
398
+ public_surface_registry_effect: PreparedPublicSurfaceRegistryEffect,
399
+ idempotency_purpose: &str,
400
+ ) -> Result<WriteCommand, LixError> {
401
+ let semantic_effects =
402
+ semantic_plan_effects_from_changes(&changes, context.origin_key.as_deref())?;
403
+ let write_payload = json!({
404
+ "rows": intended_post_state.iter().map(summarize_planned_row).collect::<Vec<_>>(),
405
+ "changes": changes.iter().map(summarize_change).collect::<Vec<_>>(),
406
+ "filesystem_files": filesystem_state.files.keys().cloned().collect::<Vec<_>>(),
407
+ });
408
+ WriteCommand::build(
409
+ PreparedWriteStatement {
410
+ statement_kind: PreparedWriteStatementKind::Write,
411
+ result_contract: ResultContract::DmlNoReturning,
412
+ artifact: PreparedWriteArtifact::PublicWrite(PreparedPublicWrite {
413
+ contract: PreparedPublicWriteContract {
414
+ operation_kind: PreparedWriteOperationKind::Insert,
415
+ target,
416
+ on_conflict_action: None,
417
+ requested_version_id: Some(context.target_version_id().to_string()),
418
+ active_account_ids: context.active_account_ids.clone(),
419
+ origin_key: context.origin_key.clone(),
420
+ resolved_write_plan: Some(PreparedResolvedWritePlan {
421
+ partitions: vec![PreparedResolvedWritePartition {
422
+ execution_mode: WriteMode::Tracked,
423
+ authoritative_pre_state_rows: Vec::new(),
424
+ intended_post_state,
425
+ filesystem_state,
426
+ }],
427
+ }),
428
+ },
429
+ execution: PreparedPublicWritePlanArtifact::Materialize(
430
+ PreparedPublicWriteMaterialization {
431
+ partitions: vec![PreparedPublicWriteExecution {
432
+ execution_mode: WriteMode::Tracked,
433
+ intended_post_state: Vec::new(),
434
+ schema_live_table_requirements,
435
+ change_batch: Some(ChangeBatch {
436
+ changes: changes.clone(),
437
+ write_lane: WriteLane::GlobalAdmin,
438
+ origin_key: context.origin_key.clone(),
439
+ semantic_effects: semantic_effect_markers_from_changes(&changes),
440
+ }),
441
+ create_preconditions: Some(CommitPreconditions {
442
+ write_lane: WriteLane::GlobalAdmin,
443
+ expected_head: ExpectedHead::CurrentHead,
444
+ idempotency_key: semantic_idempotency_key(
445
+ idempotency_purpose,
446
+ &write_payload,
447
+ )?,
448
+ }),
449
+ semantic_effects,
450
+ persist_filesystem_payloads_before_write: false,
451
+ }],
452
+ },
453
+ ),
454
+ }),
455
+ diagnostic_context: WriteDiagnosticContext::new(vec![relation_name.to_string()]),
456
+ public_surface_registry_effect,
457
+ },
458
+ &context.function_bindings,
459
+ )
460
+ }
461
+
462
+ fn semantic_plan_effects_from_changes(
463
+ changes: &[PublicChange],
464
+ origin_key: Option<&str>,
465
+ ) -> Result<PlanEffects, LixError> {
466
+ Ok(PlanEffects {
467
+ state_commit_stream_changes: state_commit_stream_changes_from_changes(
468
+ changes,
469
+ StateCommitStreamOperation::Insert,
470
+ StateCommitStreamRuntimeMetadata::from_runtime_origin_key(origin_key),
471
+ )?,
472
+ ..PlanEffects::default()
473
+ })
474
+ }
475
+
476
+ fn semantic_effect_markers_from_changes(changes: &[PublicChange]) -> Vec<SemanticEffect> {
477
+ changes
478
+ .iter()
479
+ .map(|change| SemanticEffect {
480
+ effect_key: "state.upsert".to_string(),
481
+ target: format!(
482
+ "{}:{}@{}",
483
+ change.schema_key, change.entity_id, change.version_id
484
+ ),
485
+ })
486
+ .collect()
487
+ }
488
+
489
+ fn planned_row_to_public_change(row: &PlannedStateRow) -> Result<PublicChange, LixError> {
490
+ Ok(PublicChange {
491
+ entity_id: row.entity_id.clone(),
492
+ schema_key: row.schema_key.clone(),
493
+ file_id: planned_row_text_value(row, "file_id"),
494
+ plugin_key: planned_row_text_value(row, "plugin_key"),
495
+ snapshot_content: if row.tombstone {
496
+ None
497
+ } else {
498
+ planned_row_json_text_value(row, "snapshot_content")
499
+ },
500
+ metadata: planned_row_json_text_value(row, "metadata"),
501
+ version_id: row
502
+ .version_id
503
+ .clone()
504
+ .or_else(|| planned_row_text_value(row, "version_id"))
505
+ .ok_or_else(|| {
506
+ LixError::new(
507
+ "LIX_ERROR_UNKNOWN",
508
+ "semantic tracked write requires a concrete version_id",
509
+ )
510
+ })?,
511
+ origin_key: row.origin_key.clone(),
512
+ })
513
+ }
514
+
515
+ fn planned_row_text_value(row: &PlannedStateRow, key: &str) -> Option<String> {
516
+ match row.values.get(key) {
517
+ Some(Value::Text(value)) => Some(value.clone()),
518
+ Some(Value::Integer(value)) => Some(value.to_string()),
519
+ Some(Value::Boolean(value)) => Some(value.to_string()),
520
+ Some(Value::Real(value)) => Some(value.to_string()),
521
+ _ => None,
522
+ }
523
+ }
524
+
525
+ fn planned_row_json_text_value(row: &PlannedStateRow, key: &str) -> Option<String> {
526
+ match row.values.get(key) {
527
+ Some(Value::Json(value)) => Some(value.to_string()),
528
+ _ => planned_row_text_value(row, key),
529
+ }
530
+ }
531
+
532
+ fn semantic_idempotency_key(
533
+ purpose: &str,
534
+ payload: &JsonValue,
535
+ ) -> Result<IdempotencyKey, LixError> {
536
+ let bytes = serde_json::to_vec(payload).map_err(|error| {
537
+ LixError::new(
538
+ "LIX_ERROR_UNKNOWN",
539
+ format!("semantic idempotency payload serialization failed: {error}"),
540
+ )
541
+ })?;
542
+ Ok(IdempotencyKey(
543
+ json!({
544
+ "purpose": purpose,
545
+ "fingerprint": stable_content_fingerprint_hex(&bytes),
546
+ })
547
+ .to_string(),
548
+ ))
549
+ }
550
+
551
+ fn summarize_change(change: &PublicChange) -> JsonValue {
552
+ json!({
553
+ "entity_id": change.entity_id,
554
+ "schema_key": change.schema_key,
555
+ "file_id": change.file_id,
556
+ "plugin_key": change.plugin_key,
557
+ "version_id": change.version_id,
558
+ "origin_key": change.origin_key,
559
+ "snapshot_content": change.snapshot_content.as_ref().map(|snapshot| {
560
+ stable_content_fingerprint_hex(snapshot.as_bytes())
561
+ }),
562
+ })
563
+ }
564
+
565
+ fn summarize_planned_row(row: &PlannedStateRow) -> JsonValue {
566
+ json!({
567
+ "entity_id": row.entity_id,
568
+ "schema_key": row.schema_key,
569
+ "version_id": row.version_id,
570
+ "tombstone": row.tombstone,
571
+ "values": row
572
+ .values
573
+ .iter()
574
+ .map(|(key, value)| {
575
+ (
576
+ key.clone(),
577
+ match value {
578
+ Value::Null => json!({ "kind": "null" }),
579
+ Value::Text(text) => json!({
580
+ "kind": "text",
581
+ "sha256": stable_content_fingerprint_hex(text.as_bytes()),
582
+ "len": text.len(),
583
+ }),
584
+ Value::Json(value) => {
585
+ let encoded = value.to_string();
586
+ json!({
587
+ "kind": "json",
588
+ "sha256": stable_content_fingerprint_hex(encoded.as_bytes()),
589
+ "len": encoded.len(),
590
+ })
591
+ }
592
+ Value::Blob(bytes) => json!({
593
+ "kind": "blob",
594
+ "sha256": stable_content_fingerprint_hex(bytes),
595
+ "len": bytes.len(),
596
+ }),
597
+ Value::Integer(value) => json!({ "kind": "integer", "value": value }),
598
+ Value::Real(value) => json!({ "kind": "real", "value": value }),
599
+ Value::Boolean(value) => json!({ "kind": "boolean", "value": value }),
600
+ },
601
+ )
602
+ })
603
+ .collect::<serde_json::Map<_, _>>(),
604
+ })
605
+ }
606
+
607
+ fn require_resolved_surface(
608
+ public_surface_registry: &SurfaceRegistry,
609
+ relation_name: &str,
610
+ ) -> Result<ResolvedRelation, LixError> {
611
+ public_surface_registry
612
+ .bind_relation_name(relation_name)
613
+ .ok_or_else(|| {
614
+ LixError::new(
615
+ "LIX_ERROR_UNKNOWN",
616
+ format!("public surface '{relation_name}' is not registered"),
617
+ )
618
+ })
619
+ }