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

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