@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,449 @@
1
+ use crate::{content::ContentStore, tenancy::TenancyStore};
2
+ use rand::{distributions::Alphanumeric, Rng};
3
+ use serde::{Deserialize, Serialize};
4
+ use serde_json::Value;
5
+ use std::{
6
+ collections::{BTreeMap, BTreeSet},
7
+ fs,
8
+ path::PathBuf,
9
+ sync::{Arc, RwLock},
10
+ time::{SystemTime, UNIX_EPOCH},
11
+ };
12
+
13
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
14
+ #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
15
+ pub enum ArtifactKind {
16
+ Source,
17
+ Document,
18
+ Image,
19
+ Dataset,
20
+ Form,
21
+ Build,
22
+ Package,
23
+ ModelOutput,
24
+ ExecutionOutput,
25
+ Log,
26
+ Report,
27
+ Backup,
28
+ Export,
29
+ Import,
30
+ Application,
31
+ Configuration,
32
+ Other(String),
33
+ }
34
+ #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
35
+ #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
36
+ pub enum ArtifactLifecycle {
37
+ Created,
38
+ Active,
39
+ Superseded,
40
+ Archived,
41
+ Deleted,
42
+ }
43
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
44
+ pub struct ProducerRef {
45
+ pub kind: String,
46
+ pub id: String,
47
+ }
48
+ #[derive(Debug, Clone, Serialize, Deserialize, Default)]
49
+ pub struct RetentionPolicy {
50
+ pub retain_until: Option<u64>,
51
+ pub retention_class: Option<String>,
52
+ pub legal_hold: bool,
53
+ pub delete_after: Option<u64>,
54
+ }
55
+ #[derive(Debug, Clone, Serialize, Deserialize, Default)]
56
+ pub struct ArtifactMetadata {
57
+ pub name: String,
58
+ pub environment: Option<String>,
59
+ #[serde(default)]
60
+ pub tags: BTreeSet<String>,
61
+ #[serde(default)]
62
+ pub labels: BTreeMap<String, String>,
63
+ pub sensitivity: Option<String>,
64
+ pub classification: Option<String>,
65
+ #[serde(default)]
66
+ pub retention: RetentionPolicy,
67
+ #[serde(default)]
68
+ pub application: Value,
69
+ #[serde(default)]
70
+ pub provider: Value,
71
+ }
72
+ #[derive(Debug, Clone, Serialize, Deserialize)]
73
+ pub struct Artifact {
74
+ pub id: String,
75
+ pub tenant_id: String,
76
+ pub application_id: String,
77
+ pub content_hash: String,
78
+ pub content_type: String,
79
+ pub size: u64,
80
+ pub kind: ArtifactKind,
81
+ pub lifecycle: ArtifactLifecycle,
82
+ pub created_by: String,
83
+ pub producer: ProducerRef,
84
+ pub source: Option<String>,
85
+ #[serde(default)]
86
+ pub parents: Vec<String>,
87
+ pub supersedes: Option<String>,
88
+ pub revision: Option<String>,
89
+ pub workload: Option<String>,
90
+ pub execution: Option<String>,
91
+ pub metadata: ArtifactMetadata,
92
+ pub created_at: u64,
93
+ }
94
+ impl Artifact {
95
+ pub fn reference(&self) -> String {
96
+ format!(
97
+ "artifact://{}/{}/{}",
98
+ self.tenant_id, self.application_id, self.id
99
+ )
100
+ }
101
+ }
102
+ #[derive(Debug, Default, Serialize, Deserialize)]
103
+ struct Records {
104
+ artifacts: Vec<Artifact>,
105
+ }
106
+ #[derive(Clone)]
107
+ pub struct ArtifactStore {
108
+ path: PathBuf,
109
+ content: ContentStore,
110
+ records: Arc<RwLock<Records>>,
111
+ }
112
+ fn now() -> u64 {
113
+ SystemTime::now()
114
+ .duration_since(UNIX_EPOCH)
115
+ .unwrap_or_default()
116
+ .as_secs()
117
+ }
118
+ fn id() -> String {
119
+ format!(
120
+ "artifact_{}",
121
+ rand::thread_rng()
122
+ .sample_iter(&Alphanumeric)
123
+ .take(20)
124
+ .map(char::from)
125
+ .collect::<String>()
126
+ )
127
+ }
128
+ impl ArtifactStore {
129
+ pub fn load(path: impl Into<PathBuf>, content: ContentStore) -> Result<Self, String> {
130
+ let path = path.into();
131
+ let records = if path.exists() {
132
+ serde_json::from_slice(&fs::read(&path).map_err(|e| e.to_string())?)
133
+ .map_err(|e| e.to_string())?
134
+ } else {
135
+ Records::default()
136
+ };
137
+ Ok(Self {
138
+ path,
139
+ content,
140
+ records: Arc::new(RwLock::new(records)),
141
+ })
142
+ }
143
+ fn persist(&self, r: &Records) -> Result<(), String> {
144
+ if let Some(p) = self.path.parent() {
145
+ fs::create_dir_all(p).map_err(|e| e.to_string())?
146
+ }
147
+ let tmp = self.path.with_extension("tmp");
148
+ fs::write(
149
+ &tmp,
150
+ serde_json::to_vec_pretty(r).map_err(|e| e.to_string())?,
151
+ )
152
+ .map_err(|e| e.to_string())?;
153
+ fs::rename(tmp, &self.path).map_err(|e| e.to_string())
154
+ }
155
+ fn allowed(actor: &str, tenancy: &TenancyStore, app: &str, capability: &str) -> bool {
156
+ tenancy
157
+ .application_tenant(app)
158
+ .map(|tenant| {
159
+ tenancy.authorize_application(actor, &tenant, app, capability)
160
+ || tenancy.authorize_application(actor, &tenant, app, "applications:write")
161
+ || tenancy.authorize_application(actor, &tenant, app, "applications:read")
162
+ })
163
+ .unwrap_or(false)
164
+ }
165
+ pub fn create(
166
+ &self,
167
+ actor: &str,
168
+ tenancy: &TenancyStore,
169
+ application_id: &str,
170
+ kind: ArtifactKind,
171
+ content_type: String,
172
+ bytes: &[u8],
173
+ producer: ProducerRef,
174
+ parents: Vec<String>,
175
+ supersedes: Option<String>,
176
+ metadata: ArtifactMetadata,
177
+ revision: Option<String>,
178
+ workload: Option<String>,
179
+ execution: Option<String>,
180
+ ) -> Result<Artifact, String> {
181
+ if !Self::allowed(actor, tenancy, application_id, "artifact:create") {
182
+ return Err("forbidden".into());
183
+ }
184
+ let tenant_id = tenancy
185
+ .application_tenant(application_id)
186
+ .ok_or("application not found")?;
187
+ let records = self
188
+ .records
189
+ .read()
190
+ .map_err(|_| "artifact store lock poisoned")?;
191
+ for reference in parents.iter().chain(supersedes.iter()) {
192
+ if !records.artifacts.iter().any(|a| {
193
+ &a.id == reference
194
+ && a.tenant_id == tenant_id
195
+ && a.application_id == application_id
196
+ && a.lifecycle != ArtifactLifecycle::Deleted
197
+ }) {
198
+ return Err("parent artifact unavailable or outside application boundary".into());
199
+ }
200
+ }
201
+ drop(records);
202
+ let hash = self.content.store(bytes).map_err(|e| e.to_string())?;
203
+ let artifact = Artifact {
204
+ id: id(),
205
+ tenant_id,
206
+ application_id: application_id.into(),
207
+ content_hash: hash,
208
+ content_type,
209
+ size: bytes.len() as u64,
210
+ kind,
211
+ lifecycle: ArtifactLifecycle::Active,
212
+ created_by: actor.into(),
213
+ producer,
214
+ source: None,
215
+ parents,
216
+ supersedes,
217
+ revision,
218
+ workload,
219
+ execution,
220
+ metadata,
221
+ created_at: now(),
222
+ };
223
+ let mut r = self
224
+ .records
225
+ .write()
226
+ .map_err(|_| "artifact store lock poisoned")?;
227
+ if let Some(old) = artifact
228
+ .supersedes
229
+ .as_ref()
230
+ .and_then(|id| r.artifacts.iter_mut().find(|a| &a.id == id))
231
+ {
232
+ old.lifecycle = ArtifactLifecycle::Superseded
233
+ }
234
+ r.artifacts.push(artifact.clone());
235
+ self.persist(&r)?;
236
+ Ok(artifact)
237
+ }
238
+ pub fn list(
239
+ &self,
240
+ actor: &str,
241
+ tenancy: &TenancyStore,
242
+ app: &str,
243
+ ) -> Result<Vec<Artifact>, String> {
244
+ if !Self::allowed(actor, tenancy, app, "artifact:read") {
245
+ return Err("forbidden".into());
246
+ }
247
+ Ok(self
248
+ .records
249
+ .read()
250
+ .map_err(|_| "artifact store lock poisoned")?
251
+ .artifacts
252
+ .iter()
253
+ .filter(|a| a.application_id == app && a.lifecycle != ArtifactLifecycle::Deleted)
254
+ .cloned()
255
+ .collect())
256
+ }
257
+ pub fn get(&self, actor: &str, tenancy: &TenancyStore, id: &str) -> Result<Artifact, String> {
258
+ let r = self
259
+ .records
260
+ .read()
261
+ .map_err(|_| "artifact store lock poisoned")?;
262
+ let a = r
263
+ .artifacts
264
+ .iter()
265
+ .find(|a| a.id == id && a.lifecycle != ArtifactLifecycle::Deleted)
266
+ .ok_or("artifact not found")?;
267
+ if !Self::allowed(actor, tenancy, &a.application_id, "artifact:read") {
268
+ return Err("forbidden".into());
269
+ }
270
+ Ok(a.clone())
271
+ }
272
+ pub fn content(
273
+ &self,
274
+ actor: &str,
275
+ tenancy: &TenancyStore,
276
+ id: &str,
277
+ ) -> Result<Vec<u8>, String> {
278
+ let a = self.get(actor, tenancy, id)?;
279
+ self.content
280
+ .get(&a.content_hash)
281
+ .map_err(|_| "artifact integrity failure".to_string())?
282
+ .ok_or("artifact content missing".into())
283
+ }
284
+ pub fn lifecycle(
285
+ &self,
286
+ actor: &str,
287
+ tenancy: &TenancyStore,
288
+ id: &str,
289
+ next: ArtifactLifecycle,
290
+ ) -> Result<Artifact, String> {
291
+ let mut r = self
292
+ .records
293
+ .write()
294
+ .map_err(|_| "artifact store lock poisoned")?;
295
+ let a = r
296
+ .artifacts
297
+ .iter_mut()
298
+ .find(|a| a.id == id)
299
+ .ok_or("artifact not found")?;
300
+ if !Self::allowed(actor, tenancy, &a.application_id, "artifact:write") {
301
+ return Err("forbidden".into());
302
+ }
303
+ let valid = matches!(
304
+ (a.lifecycle, next),
305
+ (ArtifactLifecycle::Active, ArtifactLifecycle::Archived)
306
+ | (ArtifactLifecycle::Archived, ArtifactLifecycle::Active)
307
+ | (_, ArtifactLifecycle::Deleted)
308
+ );
309
+ if !valid {
310
+ return Err("invalid artifact lifecycle transition".into());
311
+ }
312
+ if next == ArtifactLifecycle::Deleted
313
+ && (a.metadata.retention.legal_hold
314
+ || a.metadata
315
+ .retention
316
+ .retain_until
317
+ .is_some_and(|at| at > now()))
318
+ {
319
+ return Err("artifact retention policy prevents deletion".into());
320
+ }
321
+ a.lifecycle = next;
322
+ let out = a.clone();
323
+ self.persist(&r)?;
324
+ Ok(out)
325
+ }
326
+ pub fn parents(
327
+ &self,
328
+ actor: &str,
329
+ tenancy: &TenancyStore,
330
+ id: &str,
331
+ ) -> Result<Vec<Artifact>, String> {
332
+ let a = self.get(actor, tenancy, id)?;
333
+ let r = self
334
+ .records
335
+ .read()
336
+ .map_err(|_| "artifact store lock poisoned")?;
337
+ Ok(r.artifacts
338
+ .iter()
339
+ .filter(|candidate| {
340
+ a.parents.contains(&candidate.id) || a.supersedes.as_ref() == Some(&candidate.id)
341
+ })
342
+ .cloned()
343
+ .collect())
344
+ }
345
+ pub fn children(
346
+ &self,
347
+ actor: &str,
348
+ tenancy: &TenancyStore,
349
+ id: &str,
350
+ ) -> Result<Vec<Artifact>, String> {
351
+ let a = self.get(actor, tenancy, id)?;
352
+ let r = self
353
+ .records
354
+ .read()
355
+ .map_err(|_| "artifact store lock poisoned")?;
356
+ Ok(r.artifacts
357
+ .iter()
358
+ .filter(|candidate| {
359
+ candidate.application_id == a.application_id
360
+ && (candidate.parents.contains(&a.id)
361
+ || candidate.supersedes.as_ref() == Some(&a.id))
362
+ })
363
+ .cloned()
364
+ .collect())
365
+ }
366
+ pub fn verify(&self, actor: &str, tenancy: &TenancyStore, id: &str) -> Result<bool, String> {
367
+ self.content(actor, tenancy, id).map(|_| true)
368
+ }
369
+ pub fn content_references(&self, hash: &str) -> usize {
370
+ self.records
371
+ .read()
372
+ .map(|r| {
373
+ r.artifacts
374
+ .iter()
375
+ .filter(|a| a.content_hash == hash && a.lifecycle != ArtifactLifecycle::Deleted)
376
+ .count()
377
+ })
378
+ .unwrap_or(0)
379
+ }
380
+ }
381
+
382
+ #[cfg(test)]
383
+ mod tests {
384
+ use super::*;
385
+ #[test]
386
+ fn upload_dedup_immutability_lineage_and_gc_refs() {
387
+ let suffix = id();
388
+ let tenancy =
389
+ TenancyStore::load(std::env::temp_dir().join(format!("{suffix}-tenant"))).unwrap();
390
+ let tenant = tenancy.create_tenant("owner", "Artifacts").unwrap();
391
+ let app = tenancy
392
+ .create_application("owner", &tenant.id, "App")
393
+ .unwrap();
394
+ let root = std::env::temp_dir().join(format!("{suffix}-content"));
395
+ let content = ContentStore::new(root.clone()).unwrap();
396
+ let store = ArtifactStore::load(
397
+ std::env::temp_dir().join(format!("{suffix}-artifacts")),
398
+ content,
399
+ )
400
+ .unwrap();
401
+ let producer = ProducerRef {
402
+ kind: "human".into(),
403
+ id: "owner".into(),
404
+ };
405
+ let a = store
406
+ .create(
407
+ "owner",
408
+ &tenancy,
409
+ &app.id,
410
+ ArtifactKind::Document,
411
+ "text/plain".into(),
412
+ b"same",
413
+ producer.clone(),
414
+ vec![],
415
+ None,
416
+ ArtifactMetadata::default(),
417
+ None,
418
+ None,
419
+ None,
420
+ )
421
+ .unwrap();
422
+ let b = store
423
+ .create(
424
+ "owner",
425
+ &tenancy,
426
+ &app.id,
427
+ ArtifactKind::Report,
428
+ "text/plain".into(),
429
+ b"same",
430
+ producer,
431
+ vec![a.id.clone()],
432
+ Some(a.id.clone()),
433
+ ArtifactMetadata::default(),
434
+ None,
435
+ Some("workload-1".into()),
436
+ Some("execution-1".into()),
437
+ )
438
+ .unwrap();
439
+ assert_eq!(a.content_hash, b.content_hash);
440
+ assert_eq!(store.content_references(&a.content_hash), 2);
441
+ assert_eq!(store.parents("owner", &tenancy, &b.id).unwrap().len(), 1);
442
+ assert_eq!(store.content("owner", &tenancy, &b.id).unwrap(), b"same");
443
+ store
444
+ .lifecycle("owner", &tenancy, &b.id, ArtifactLifecycle::Deleted)
445
+ .unwrap();
446
+ assert_eq!(store.content_references(&a.content_hash), 1);
447
+ let _ = fs::remove_dir_all(root);
448
+ }
449
+ }
@@ -0,0 +1,59 @@
1
+ use std::{
2
+ fs::OpenOptions,
3
+ io::Write,
4
+ path::PathBuf,
5
+ sync::{Arc, Mutex},
6
+ time::{SystemTime, UNIX_EPOCH},
7
+ };
8
+
9
+ use serde::Serialize;
10
+
11
+ #[derive(Clone)]
12
+ pub struct AuditLog {
13
+ path: Arc<PathBuf>,
14
+ lock: Arc<Mutex<()>>,
15
+ }
16
+
17
+ #[derive(Serialize)]
18
+ pub struct AuditEvent<'a> {
19
+ pub timestamp_ms: u128,
20
+ pub namespace: &'a str,
21
+ pub principal: &'a str,
22
+ pub action: &'a str,
23
+ pub target: &'a str,
24
+ pub outcome: &'a str,
25
+ pub status: u16,
26
+ }
27
+
28
+ impl AuditLog {
29
+ pub fn new(path: PathBuf) -> Self {
30
+ Self {
31
+ path: Arc::new(path),
32
+ lock: Arc::new(Mutex::new(())),
33
+ }
34
+ }
35
+
36
+ pub fn record(&self, mut event: AuditEvent<'_>) -> Result<(), String> {
37
+ let _guard = self
38
+ .lock
39
+ .lock()
40
+ .map_err(|_| "audit log lock poisoned".to_string())?;
41
+ if let Some(parent) = self.path.parent() {
42
+ std::fs::create_dir_all(parent).map_err(|error| error.to_string())?;
43
+ }
44
+ event.timestamp_ms = SystemTime::now()
45
+ .duration_since(UNIX_EPOCH)
46
+ .map_err(|error| error.to_string())?
47
+ .as_millis();
48
+ let mut file = OpenOptions::new()
49
+ .create(true)
50
+ .append(true)
51
+ .open(&*self.path)
52
+ .map_err(|error| error.to_string())?;
53
+ serde_json::to_writer(&mut file, &event).map_err(|error| error.to_string())?;
54
+ file.write_all(b"\n").map_err(|error| error.to_string())?;
55
+ file.flush().map_err(|error| error.to_string())?;
56
+ file.sync_data().map_err(|error| error.to_string())?;
57
+ Ok(())
58
+ }
59
+ }