@feltdb/core 0.4.12 → 0.4.13

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 (164) hide show
  1. package/dist/cli/commands.js +5 -2
  2. package/dist/cli/index.js +1 -1
  3. package/dist/create/cli.js +16 -1
  4. package/dist/create/create.js +30 -16
  5. package/dist/create/docker-compose-generator.js +48 -9
  6. package/dist/create/package-versions.js +1 -1
  7. package/dist/create/server-source/Cargo.lock +2238 -0
  8. package/dist/create/server-source/Cargo.toml +7 -0
  9. package/dist/create/server-source/crates/feltdb/Cargo.lock +175 -0
  10. package/dist/create/server-source/crates/feltdb/Cargo.toml +79 -0
  11. package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +370 -0
  12. package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +589 -0
  13. package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +259 -0
  14. package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +446 -0
  15. package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +272 -0
  16. package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +378 -0
  17. package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +414 -0
  18. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +486 -0
  19. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +273 -0
  20. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +587 -0
  21. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +513 -0
  22. package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +395 -0
  23. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +469 -0
  24. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +409 -0
  25. package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +627 -0
  26. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +383 -0
  27. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +298 -0
  28. package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +1698 -0
  29. package/dist/create/server-source/crates/feltdb/src/acquisition.rs +286 -0
  30. package/dist/create/server-source/crates/feltdb/src/admission.rs +192 -0
  31. package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +477 -0
  32. package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +566 -0
  33. package/dist/create/server-source/crates/feltdb/src/analytics.rs +475 -0
  34. package/dist/create/server-source/crates/feltdb/src/application.rs +2244 -0
  35. package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +1070 -0
  36. package/dist/create/server-source/crates/feltdb/src/authorization.rs +1030 -0
  37. package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +266 -0
  38. package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +8 -0
  39. package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +418 -0
  40. package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +482 -0
  41. package/dist/create/server-source/crates/feltdb/src/capability.rs +903 -0
  42. package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +261 -0
  43. package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +78 -0
  44. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +2214 -0
  45. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +194 -0
  46. package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +427 -0
  47. package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +453 -0
  48. package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +465 -0
  49. package/dist/create/server-source/crates/feltdb/src/convergence.rs +618 -0
  50. package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +418 -0
  51. package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +380 -0
  52. package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +362 -0
  53. package/dist/create/server-source/crates/feltdb/src/cron.rs +294 -0
  54. package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +474 -0
  55. package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +1003 -0
  56. package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +536 -0
  57. package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +364 -0
  58. package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +235 -0
  59. package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +207 -0
  60. package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +316 -0
  61. package/dist/create/server-source/crates/feltdb/src/execution.rs +426 -0
  62. package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +219 -0
  63. package/dist/create/server-source/crates/feltdb/src/indexing.rs +779 -0
  64. package/dist/create/server-source/crates/feltdb/src/lib.rs +2838 -0
  65. package/dist/create/server-source/crates/feltdb/src/materialization.rs +184 -0
  66. package/dist/create/server-source/crates/feltdb/src/metrics.rs +267 -0
  67. package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +311 -0
  68. package/dist/create/server-source/crates/feltdb/src/observability.rs +366 -0
  69. package/dist/create/server-source/crates/feltdb/src/operation.rs +251 -0
  70. package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +438 -0
  71. package/dist/create/server-source/crates/feltdb/src/operation_log.rs +344 -0
  72. package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +477 -0
  73. package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +166 -0
  74. package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +261 -0
  75. package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +560 -0
  76. package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +3226 -0
  77. package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +201 -0
  78. package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +263 -0
  79. package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +484 -0
  80. package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +216 -0
  81. package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +281 -0
  82. package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +323 -0
  83. package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +573 -0
  84. package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +404 -0
  85. package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +418 -0
  86. package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +381 -0
  87. package/dist/create/server-source/crates/feltdb/src/phase_1c_three_node.rs +523 -0
  88. package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +334 -0
  89. package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +306 -0
  90. package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +355 -0
  91. package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +395 -0
  92. package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +346 -0
  93. package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +430 -0
  94. package/dist/create/server-source/crates/feltdb/src/production_api.rs +400 -0
  95. package/dist/create/server-source/crates/feltdb/src/provenance.rs +207 -0
  96. package/dist/create/server-source/crates/feltdb/src/query_performance.rs +217 -0
  97. package/dist/create/server-source/crates/feltdb/src/references.rs +404 -0
  98. package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +401 -0
  99. package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +160 -0
  100. package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +132 -0
  101. package/dist/create/server-source/crates/feltdb/src/routing.rs +409 -0
  102. package/dist/create/server-source/crates/feltdb/src/sharding.rs +523 -0
  103. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +3118 -0
  104. package/dist/create/server-source/crates/feltdb/src/state_hash.rs +291 -0
  105. package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +376 -0
  106. package/dist/create/server-source/crates/feltdb/src/storage.rs +267 -0
  107. package/dist/create/server-source/crates/feltdb/src/submission.rs +477 -0
  108. package/dist/create/server-source/crates/feltdb/src/sync.rs +483 -0
  109. package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +822 -0
  110. package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +366 -0
  111. package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +473 -0
  112. package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +949 -0
  113. package/dist/create/server-source/crates/feltdb/src/transactions.rs +646 -0
  114. package/dist/create/server-source/crates/feltdb/src/trigger.rs +390 -0
  115. package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +928 -0
  116. package/dist/create/server-source/crates/feltdb/src/workflow.rs +713 -0
  117. package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +510 -0
  118. package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +354 -0
  119. package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +594 -0
  120. package/dist/create/server-source/crates/feltdb/src/workload.rs +1310 -0
  121. package/dist/create/server-source/crates/feltdb-server/Cargo.toml +23 -0
  122. package/dist/create/server-source/crates/feltdb-server/README.md +79 -0
  123. package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +73 -0
  124. package/dist/create/server-source/crates/feltdb-server/src/application_contract.rs +425 -0
  125. package/dist/create/server-source/crates/feltdb-server/src/artifacts.rs +449 -0
  126. package/dist/create/server-source/crates/feltdb-server/src/audit.rs +59 -0
  127. package/dist/create/server-source/crates/feltdb-server/src/auth.rs +308 -0
  128. package/dist/create/server-source/crates/feltdb-server/src/authorization.rs +228 -0
  129. package/dist/create/server-source/crates/feltdb-server/src/backup.rs +416 -0
  130. package/dist/create/server-source/crates/feltdb-server/src/causal.rs +218 -0
  131. package/dist/create/server-source/crates/feltdb-server/src/certification.rs +223 -0
  132. package/dist/create/server-source/crates/feltdb-server/src/clock.rs +132 -0
  133. package/dist/create/server-source/crates/feltdb-server/src/cluster.rs +165 -0
  134. package/dist/create/server-source/crates/feltdb-server/src/connections.rs +1044 -0
  135. package/dist/create/server-source/crates/feltdb-server/src/content.rs +111 -0
  136. package/dist/create/server-source/crates/feltdb-server/src/identity.rs +250 -0
  137. package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +78 -0
  138. package/dist/create/server-source/crates/feltdb-server/src/key_provider.rs +247 -0
  139. package/dist/create/server-source/crates/feltdb-server/src/leases.rs +123 -0
  140. package/dist/create/server-source/crates/feltdb-server/src/lib.rs +25 -0
  141. package/dist/create/server-source/crates/feltdb-server/src/main.rs +8715 -0
  142. package/dist/create/server-source/crates/feltdb-server/src/metrics.rs +97 -0
  143. package/dist/create/server-source/crates/feltdb-server/src/portable_bundle.rs +350 -0
  144. package/dist/create/server-source/crates/feltdb-server/src/principals.rs +510 -0
  145. package/dist/create/server-source/crates/feltdb-server/src/providers.rs +269 -0
  146. package/dist/create/server-source/crates/feltdb-server/src/releases.rs +2563 -0
  147. package/dist/create/server-source/crates/feltdb-server/src/sessions.rs +156 -0
  148. package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +1097 -0
  149. package/dist/create/server-source/crates/feltdb-server/src/versions.rs +264 -0
  150. package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +31 -0
  151. package/dist/create/server-source/crates/feltdb-wasm/src/lib.rs +1086 -0
  152. package/dist/create/server-source/crates/feltdb-wasm/test.db +0 -0
  153. package/dist/flowspec.d.ts +2 -0
  154. package/dist/flowspec.d.ts.map +1 -1
  155. package/dist/flowspec.js +28 -4
  156. package/dist/state-contract.d.ts +7 -1
  157. package/dist/state-contract.d.ts.map +1 -1
  158. package/dist/studio/components/ApplicationDesigner.d.ts.map +1 -1
  159. package/dist/studio/components/index.js +1 -1
  160. package/dist/studio/{components-Q0Nx6gLE.js → components-bHTARBin.js} +156 -46
  161. package/dist/studio/index.js +31 -28
  162. package/dist/studio-app/assets/{index-BHOmLZF-.js → index-BqKquLuU.js} +8 -8
  163. package/dist/studio-app/index.html +1 -1
  164. package/package.json +1 -1
@@ -0,0 +1,97 @@
1
+ use serde::Serialize;
2
+ use std::sync::{
3
+ atomic::{AtomicU64, Ordering},
4
+ Arc,
5
+ };
6
+
7
+ #[derive(Clone, Default)]
8
+ pub struct Metrics {
9
+ inner: Arc<Inner>,
10
+ }
11
+
12
+ #[derive(Default)]
13
+ struct Inner {
14
+ requests: AtomicU64,
15
+ authentication_failures: AtomicU64,
16
+ state_reads: AtomicU64,
17
+ state_mutations: AtomicU64,
18
+ event_connections: AtomicU64,
19
+ sync_operations_sent: AtomicU64,
20
+ sync_operations_received: AtomicU64,
21
+ peer_sync_successes: AtomicU64,
22
+ peer_sync_failures: AtomicU64,
23
+ operations_compacted: AtomicU64,
24
+ }
25
+
26
+ #[derive(Serialize)]
27
+ pub struct MetricsSnapshot {
28
+ pub requests: u64,
29
+ pub authentication_failures: u64,
30
+ pub state_reads: u64,
31
+ pub state_mutations: u64,
32
+ pub event_connections: u64,
33
+ pub sync_operations_sent: u64,
34
+ pub sync_operations_received: u64,
35
+ pub peer_sync_successes: u64,
36
+ pub peer_sync_failures: u64,
37
+ pub operations_compacted: u64,
38
+ }
39
+
40
+ impl Metrics {
41
+ pub fn request(&self) {
42
+ self.inner.requests.fetch_add(1, Ordering::Relaxed);
43
+ }
44
+ pub fn authentication_failure(&self) {
45
+ self.inner
46
+ .authentication_failures
47
+ .fetch_add(1, Ordering::Relaxed);
48
+ }
49
+ pub fn state_read(&self) {
50
+ self.inner.state_reads.fetch_add(1, Ordering::Relaxed);
51
+ }
52
+ pub fn state_mutation(&self) {
53
+ self.inner.state_mutations.fetch_add(1, Ordering::Relaxed);
54
+ }
55
+ pub fn event_connection(&self) {
56
+ self.inner.event_connections.fetch_add(1, Ordering::Relaxed);
57
+ }
58
+ pub fn sync_sent(&self, count: u64) {
59
+ self.inner
60
+ .sync_operations_sent
61
+ .fetch_add(count, Ordering::Relaxed);
62
+ }
63
+ pub fn sync_received(&self, count: u64) {
64
+ self.inner
65
+ .sync_operations_received
66
+ .fetch_add(count, Ordering::Relaxed);
67
+ }
68
+ pub fn peer_sync_success(&self) {
69
+ self.inner
70
+ .peer_sync_successes
71
+ .fetch_add(1, Ordering::Relaxed);
72
+ }
73
+ pub fn peer_sync_failure(&self) {
74
+ self.inner
75
+ .peer_sync_failures
76
+ .fetch_add(1, Ordering::Relaxed);
77
+ }
78
+ pub fn operations_compacted(&self, count: u64) {
79
+ self.inner
80
+ .operations_compacted
81
+ .fetch_add(count, Ordering::Relaxed);
82
+ }
83
+ pub fn snapshot(&self) -> MetricsSnapshot {
84
+ MetricsSnapshot {
85
+ requests: self.inner.requests.load(Ordering::Relaxed),
86
+ authentication_failures: self.inner.authentication_failures.load(Ordering::Relaxed),
87
+ state_reads: self.inner.state_reads.load(Ordering::Relaxed),
88
+ state_mutations: self.inner.state_mutations.load(Ordering::Relaxed),
89
+ event_connections: self.inner.event_connections.load(Ordering::Relaxed),
90
+ sync_operations_sent: self.inner.sync_operations_sent.load(Ordering::Relaxed),
91
+ sync_operations_received: self.inner.sync_operations_received.load(Ordering::Relaxed),
92
+ peer_sync_successes: self.inner.peer_sync_successes.load(Ordering::Relaxed),
93
+ peer_sync_failures: self.inner.peer_sync_failures.load(Ordering::Relaxed),
94
+ operations_compacted: self.inner.operations_compacted.load(Ordering::Relaxed),
95
+ }
96
+ }
97
+ }
@@ -0,0 +1,350 @@
1
+ use base64::{engine::general_purpose::STANDARD, Engine};
2
+ use serde::{Deserialize, Serialize};
3
+ use serde_json::Value;
4
+ use sha2::{Digest, Sha256};
5
+ use std::{
6
+ collections::BTreeMap,
7
+ fs,
8
+ path::PathBuf,
9
+ sync::{Arc, RwLock},
10
+ time::{SystemTime, UNIX_EPOCH},
11
+ };
12
+
13
+ pub const BUNDLE_FORMAT_VERSION: u32 = 1;
14
+ #[derive(Debug, Clone, Serialize, Deserialize, Default)]
15
+ pub struct ExportOptions {
16
+ pub include_data: bool,
17
+ pub include_artifacts: bool,
18
+ pub include_history: bool,
19
+ pub include_audit: bool,
20
+ pub include_provenance: bool,
21
+ }
22
+ #[derive(Debug, Clone, Serialize, Deserialize)]
23
+ #[serde(rename_all = "snake_case")]
24
+ pub enum ImportMode {
25
+ New,
26
+ Clone,
27
+ Restore,
28
+ Replace,
29
+ Merge,
30
+ }
31
+ #[derive(Debug, Clone, Serialize, Deserialize)]
32
+ pub struct BundleManifest {
33
+ pub bundle_id: String,
34
+ pub format_version: u32,
35
+ pub feltdb_version: String,
36
+ pub tenant_id: String,
37
+ pub application_id: String,
38
+ pub application_revision: Option<String>,
39
+ pub schema_versions: Vec<u64>,
40
+ pub state_version: Option<String>,
41
+ pub artifact_roots: Vec<String>,
42
+ pub environment_descriptors: Vec<String>,
43
+ pub provenance_root: Option<String>,
44
+ pub audit_root: Option<String>,
45
+ pub integrity_root: String,
46
+ pub created_at: u64,
47
+ pub options: ExportOptions,
48
+ }
49
+ #[derive(Debug, Clone, Serialize, Deserialize)]
50
+ pub struct BundleArtifact {
51
+ pub manifest: Value,
52
+ pub content_base64: String,
53
+ }
54
+ #[derive(Debug, Clone, Serialize, Deserialize)]
55
+ pub struct PortableBundle {
56
+ pub format: String,
57
+ pub bundle: BundleManifest,
58
+ pub manifest: Value,
59
+ pub revisions: Vec<Value>,
60
+ pub state: Option<Value>,
61
+ pub artifacts: Vec<BundleArtifact>,
62
+ pub workflows: Vec<Value>,
63
+ pub connections: Vec<Value>,
64
+ pub environments: Vec<Value>,
65
+ pub policies: Vec<Value>,
66
+ pub provenance: Vec<Value>,
67
+ pub audit: Vec<Value>,
68
+ pub checksums: BTreeMap<String, String>,
69
+ }
70
+ #[derive(Debug, Clone, Serialize, Deserialize)]
71
+ pub struct ImportPlan {
72
+ pub bundle_id: String,
73
+ pub mode: ImportMode,
74
+ pub destination_tenant: String,
75
+ pub destination_application: Option<String>,
76
+ pub creates: BTreeMap<String, u64>,
77
+ pub requires_secret_bindings: Vec<String>,
78
+ pub requires_provider_bindings: Vec<String>,
79
+ pub warnings: Vec<String>,
80
+ pub integrity_root: String,
81
+ pub mutates_destination: bool,
82
+ }
83
+ #[derive(Debug, Default, Serialize, Deserialize)]
84
+ struct Records {
85
+ bundles: BTreeMap<String, PortableBundle>,
86
+ }
87
+ #[derive(Clone)]
88
+ pub struct BundleStore {
89
+ path: PathBuf,
90
+ records: Arc<RwLock<Records>>,
91
+ }
92
+ fn now() -> u64 {
93
+ SystemTime::now()
94
+ .duration_since(UNIX_EPOCH)
95
+ .unwrap_or_default()
96
+ .as_secs()
97
+ }
98
+ fn hash(bytes: &[u8]) -> String {
99
+ format!("sha256:{:x}", Sha256::digest(bytes))
100
+ }
101
+ pub fn canonical(value: &Value) -> Value {
102
+ match value {
103
+ Value::Object(map) => Value::Object(
104
+ map.iter()
105
+ .map(|(k, v)| (k.clone(), canonical(v)))
106
+ .collect::<BTreeMap<_, _>>()
107
+ .into_iter()
108
+ .collect(),
109
+ ),
110
+ Value::Array(values) => Value::Array(values.iter().map(canonical).collect()),
111
+ v => v.clone(),
112
+ }
113
+ }
114
+ fn bytes(value: &Value) -> Result<Vec<u8>, String> {
115
+ serde_json::to_vec(&canonical(value)).map_err(|e| e.to_string())
116
+ }
117
+ fn forbidden_secret(value: &Value) -> bool {
118
+ match value {
119
+ Value::Object(map) => map.iter().any(|(k, v)| {
120
+ matches!(
121
+ k.to_ascii_lowercase().as_str(),
122
+ "password"
123
+ | "api_key"
124
+ | "apikey"
125
+ | "oauth_token"
126
+ | "private_key"
127
+ | "plaintext"
128
+ | "secret_value"
129
+ ) || forbidden_secret(v)
130
+ }),
131
+ Value::Array(v) => v.iter().any(forbidden_secret),
132
+ _ => false,
133
+ }
134
+ }
135
+ impl PortableBundle {
136
+ pub fn build(
137
+ tenant: String,
138
+ application: String,
139
+ manifest: Value,
140
+ revisions: Vec<Value>,
141
+ state: Option<Value>,
142
+ artifacts: Vec<BundleArtifact>,
143
+ connections: Vec<Value>,
144
+ options: ExportOptions,
145
+ ) -> Result<Self, String> {
146
+ if forbidden_secret(&manifest) || connections.iter().any(forbidden_secret) {
147
+ return Err("bundle contains forbidden plaintext secret material".into());
148
+ }
149
+ let mut checksums = BTreeMap::new();
150
+ checksums.insert("manifest.json".into(), hash(&bytes(&manifest)?));
151
+ for (i, r) in revisions.iter().enumerate() {
152
+ checksums.insert(format!("revisions/{i}.json"), hash(&bytes(r)?));
153
+ }
154
+ if let Some(s) = &state {
155
+ checksums.insert("state/snapshot.json".into(), hash(&bytes(s)?));
156
+ }
157
+ for (i, a) in artifacts.iter().enumerate() {
158
+ let content = STANDARD
159
+ .decode(&a.content_base64)
160
+ .map_err(|_| "artifact payload invalid")?;
161
+ let expected = a
162
+ .manifest
163
+ .get("content_hash")
164
+ .and_then(Value::as_str)
165
+ .ok_or("artifact hash missing")?;
166
+ if hash(&content).trim_start_matches("sha256:") != expected {
167
+ return Err("artifact integrity mismatch".into());
168
+ }
169
+ checksums.insert(format!("artifacts/{i}.json"), hash(&bytes(&a.manifest)?));
170
+ checksums.insert(format!("artifacts/{i}.content"), hash(&content));
171
+ }
172
+ for (i, c) in connections.iter().enumerate() {
173
+ checksums.insert(format!("connections/{i}.json"), hash(&bytes(c)?));
174
+ }
175
+ let integrity_root = hash(&serde_json::to_vec(&checksums).map_err(|e| e.to_string())?);
176
+ let bundle_id = format!(
177
+ "bundle_{}",
178
+ integrity_root
179
+ .trim_start_matches("sha256:")
180
+ .chars()
181
+ .take(24)
182
+ .collect::<String>()
183
+ );
184
+ let application_revision = revisions
185
+ .first()
186
+ .and_then(|v| v.get("revision_id"))
187
+ .and_then(Value::as_str)
188
+ .map(Into::into);
189
+ let artifact_roots = artifacts
190
+ .iter()
191
+ .filter_map(|a| a.manifest.get("id").and_then(Value::as_str).map(Into::into))
192
+ .collect();
193
+ let environments = manifest
194
+ .get("environments")
195
+ .and_then(Value::as_array)
196
+ .cloned()
197
+ .unwrap_or_default();
198
+ Ok(Self {
199
+ format: "feltdb-bundle/v1".into(),
200
+ bundle: BundleManifest {
201
+ bundle_id,
202
+ format_version: BUNDLE_FORMAT_VERSION,
203
+ feltdb_version: env!("CARGO_PKG_VERSION").into(),
204
+ tenant_id: tenant,
205
+ application_id: application,
206
+ application_revision,
207
+ schema_versions: vec![1],
208
+ state_version: state
209
+ .as_ref()
210
+ .and_then(|v| v.get("content_hash"))
211
+ .and_then(Value::as_str)
212
+ .map(Into::into),
213
+ artifact_roots,
214
+ environment_descriptors: environments
215
+ .iter()
216
+ .filter_map(|v| v.get("name").and_then(Value::as_str).map(Into::into))
217
+ .collect(),
218
+ provenance_root: None,
219
+ audit_root: None,
220
+ integrity_root,
221
+ created_at: now(),
222
+ options,
223
+ },
224
+ manifest,
225
+ revisions,
226
+ state,
227
+ artifacts,
228
+ workflows: vec![],
229
+ connections,
230
+ environments,
231
+ policies: vec![],
232
+ provenance: vec![],
233
+ audit: vec![],
234
+ checksums,
235
+ })
236
+ }
237
+ pub fn verify(&self) -> Result<(), String> {
238
+ if self.format != "feltdb-bundle/v1" || self.bundle.format_version != BUNDLE_FORMAT_VERSION
239
+ {
240
+ return Err("unsupported bundle format".into());
241
+ }
242
+ let rebuilt = Self::build(
243
+ self.bundle.tenant_id.clone(),
244
+ self.bundle.application_id.clone(),
245
+ self.manifest.clone(),
246
+ self.revisions.clone(),
247
+ self.state.clone(),
248
+ self.artifacts.clone(),
249
+ self.connections.clone(),
250
+ self.bundle.options.clone(),
251
+ )?;
252
+ if rebuilt.checksums != self.checksums
253
+ || rebuilt.bundle.integrity_root != self.bundle.integrity_root
254
+ || rebuilt.bundle.bundle_id != self.bundle.bundle_id
255
+ {
256
+ return Err("bundle integrity verification failed".into());
257
+ }
258
+ Ok(())
259
+ }
260
+ pub fn plan(
261
+ &self,
262
+ mode: ImportMode,
263
+ tenant: String,
264
+ application: Option<String>,
265
+ ) -> Result<ImportPlan, String> {
266
+ self.verify()?;
267
+ let secret_refs = self
268
+ .connections
269
+ .iter()
270
+ .flat_map(|c| {
271
+ c.get("secret_refs")
272
+ .and_then(Value::as_array)
273
+ .cloned()
274
+ .unwrap_or_default()
275
+ })
276
+ .filter_map(|v| v.get("secret_id").and_then(Value::as_str).map(Into::into))
277
+ .collect();
278
+ let providers = self
279
+ .connections
280
+ .iter()
281
+ .filter_map(|c| c.get("provider").and_then(Value::as_str).map(Into::into))
282
+ .collect();
283
+ Ok(ImportPlan{bundle_id:self.bundle.bundle_id.clone(),mode,destination_tenant:tenant,destination_application:application,creates:[("revisions".into(),self.revisions.len()as u64),("records".into(),self.state.as_ref().and_then(|v|v.get("rows")).and_then(Value::as_array).map(|v|v.len()as u64).unwrap_or(0)),("artifacts".into(),self.artifacts.len()as u64),("connections".into(),self.connections.len()as u64)].into(),requires_secret_bindings:secret_refs,requires_provider_bindings:providers,warnings:vec!["Imported principals and grants are never trusted; destination authorization is re-evaluated.".into()],integrity_root:self.bundle.integrity_root.clone(),mutates_destination:false})
284
+ }
285
+ }
286
+ impl BundleStore {
287
+ pub fn load(path: impl Into<PathBuf>) -> Result<Self, String> {
288
+ let path = path.into();
289
+ let records = if path.exists() {
290
+ serde_json::from_slice(&fs::read(&path).map_err(|e| e.to_string())?)
291
+ .map_err(|e| e.to_string())?
292
+ } else {
293
+ Records::default()
294
+ };
295
+ Ok(Self {
296
+ path,
297
+ records: Arc::new(RwLock::new(records)),
298
+ })
299
+ }
300
+ fn persist(&self, r: &Records) -> Result<(), String> {
301
+ if let Some(p) = self.path.parent() {
302
+ fs::create_dir_all(p).map_err(|e| e.to_string())?
303
+ }
304
+ let tmp = self.path.with_extension("tmp");
305
+ fs::write(
306
+ &tmp,
307
+ serde_json::to_vec_pretty(r).map_err(|e| e.to_string())?,
308
+ )
309
+ .map_err(|e| e.to_string())?;
310
+ fs::rename(tmp, &self.path).map_err(|e| e.to_string())
311
+ }
312
+ pub fn put(&self, bundle: PortableBundle) -> Result<(), String> {
313
+ bundle.verify()?;
314
+ let mut r = self
315
+ .records
316
+ .write()
317
+ .map_err(|_| "bundle store lock poisoned")?;
318
+ r.bundles.insert(bundle.bundle.bundle_id.clone(), bundle);
319
+ self.persist(&r)
320
+ }
321
+ pub fn get(&self, id: &str) -> Option<PortableBundle> {
322
+ self.records.read().ok()?.bundles.get(id).cloned()
323
+ }
324
+ }
325
+
326
+ #[cfg(test)]
327
+ mod tests {
328
+ use super::*;
329
+ #[test]
330
+ fn deterministic_round_trip_tamper_and_secret_safety() {
331
+ let options = ExportOptions {
332
+ include_data: true,
333
+ include_artifacts: false,
334
+ include_history: true,
335
+ include_audit: false,
336
+ include_provenance: false,
337
+ };
338
+ let b=PortableBundle::build("t".into(),"a".into(),serde_json::json!({"application_id":"a","environments":[]}),vec![serde_json::json!({"revision_id":"r1"})],Some(serde_json::json!({"rows":[],"content_hash":"h"})),vec![],vec![serde_json::json!({"provider":"postgres","secret_refs":[{"secret_id":"secret://unbound"}]})],options).unwrap();
339
+ b.verify().unwrap();
340
+ assert!(!serde_json::to_string(&b)
341
+ .unwrap()
342
+ .contains("password-value"));
343
+ let plan = b.plan(ImportMode::Clone, "other".into(), None).unwrap();
344
+ assert!(!plan.mutates_destination);
345
+ assert_eq!(plan.requires_secret_bindings.len(), 1);
346
+ let mut changed = b;
347
+ changed.manifest["application_id"] = "changed".into();
348
+ assert!(changed.verify().is_err());
349
+ }
350
+ }