@feltdb/core 0.9.1 → 0.10.0
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.
- package/dist/cli/commands.js +19 -21
- package/dist/cli/development-session.js +16 -0
- package/dist/cli/studio-artifact.js +33 -0
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +8 -1
- package/dist/create/server-source/crates/feltdb/src/application.rs +16 -0
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +21 -22
- package/dist/create/server-source/crates/feltdb/src/lib.rs +451 -33
- package/dist/create/server-source/crates/feltdb/src/state_contract.rs +48 -3
- package/dist/create/server-source/crates/feltdb/src/state_model.rs +569 -4
- package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +1 -0
- package/dist/create/server-source/crates/feltdb/src/versioned_object_substrate.rs +708 -0
- package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +1 -0
- package/dist/create/server-source/crates/feltdb/src/workload.rs +480 -44
- package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_evidence.rs +123 -0
- package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_substrate_completion.rs +281 -0
- package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +15 -3
- package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +28 -18
- package/dist/create/server-source/crates/feltdb/tests/storage_independent_versioned_object_substrate.rs +328 -0
- package/dist/create/server-source/crates/feltdb/tests/workload_envelope_contract.rs +20 -3
- package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +14 -4
- package/dist/create/server-source/crates/feltdb-server/src/backup.rs +0 -4
- package/dist/create/server-source/crates/feltdb-server/src/canonical_principals.rs +1093 -0
- package/dist/create/server-source/crates/feltdb-server/src/causal.rs +905 -144
- package/dist/create/server-source/crates/feltdb-server/src/connections.rs +9 -6
- package/dist/create/server-source/crates/feltdb-server/src/control_plane.rs +90 -0
- package/dist/create/server-source/crates/feltdb-server/src/lib.rs +1 -0
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +408 -74
- package/dist/create/server-source/crates/feltdb-server/src/principals.rs +9 -501
- package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +22 -0
- package/dist/create/server-source/crates/feltdb-server/tests/principal_tenant_migration.rs +247 -0
- package/dist/db.d.ts +6 -5
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +18 -24
- package/dist/deployment.d.ts.map +1 -1
- package/dist/deployment.js +4 -2
- package/dist/embedded-transaction.d.ts.map +1 -1
- package/dist/embedded-transaction.js +4 -0
- package/dist/error-codes.d.ts +12 -0
- package/dist/error-codes.d.ts.map +1 -1
- package/dist/error-codes.js +20 -2
- package/dist/feltdb.d.ts +4 -0
- package/dist/feltdb.d.ts.map +1 -1
- package/dist/file-db.d.ts +10 -0
- package/dist/file-db.d.ts.map +1 -1
- package/dist/file-db.js +18 -0
- package/dist/http-db.d.ts +4 -0
- package/dist/http-db.d.ts.map +1 -1
- package/dist/http-db.js +73 -12
- package/dist/index-core.d.ts +1 -0
- package/dist/index-core.d.ts.map +1 -1
- package/dist/index-core.js +1 -0
- package/dist/public-contract.d.ts +112 -0
- package/dist/public-contract.d.ts.map +1 -0
- package/dist/public-contract.js +125 -0
- package/dist/studio/app.d.ts +23 -0
- package/dist/studio/app.d.ts.map +1 -1
- package/dist/studio/components/ContractInspector.d.ts +5 -0
- package/dist/studio/components/ContractInspector.d.ts.map +1 -1
- package/dist/studio/components/SettingsPanel.d.ts +4 -1
- package/dist/studio/components/SettingsPanel.d.ts.map +1 -1
- package/dist/studio/components/index.js +4 -4
- package/dist/studio/{components-CehkcX_k.js → components-CJFgdKG6.js} +322 -303
- package/dist/studio/control-plane/Data.d.ts.map +1 -1
- package/dist/studio/control-plane/Instance.d.ts +10 -0
- package/dist/studio/control-plane/Instance.d.ts.map +1 -1
- package/dist/studio/control-plane/Operations.d.ts +3 -0
- package/dist/studio/control-plane/Operations.d.ts.map +1 -1
- package/dist/studio/control-plane/State.d.ts.map +1 -1
- package/dist/studio/control-plane/index.d.ts +4 -2
- package/dist/studio/control-plane/index.d.ts.map +1 -1
- package/dist/studio/control-plane/index.js +2 -2
- package/dist/studio/control-plane-DP8ljTDO.js +3207 -0
- package/dist/studio/index.js +96 -75
- package/dist/studio/utils/api.d.ts +1 -1
- package/dist/studio/utils/api.d.ts.map +1 -1
- package/dist/studio/utils/control-plane-api.d.ts +12 -1
- package/dist/studio/utils/control-plane-api.d.ts.map +1 -1
- package/dist/studio/utils/control-plane-api.js +1 -1
- package/dist/studio/utils/index.js +16 -16
- package/dist/studio/utils/service-api.d.ts +8 -0
- package/dist/studio/utils/service-api.d.ts.map +1 -1
- package/dist/studio/utils/service-api.js +15 -12
- package/dist/studio-app/assets/{feltdb_wasm-DksXDnLa.js → feltdb_wasm-CfNb3IoF.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-D9d3gY-E.wasm +0 -0
- package/dist/studio-app/assets/index-SDXdROi1.js +30 -0
- package/dist/studio-app/feltdb-studio.json +6 -0
- package/dist/studio-app/index.html +1 -1
- package/dist/transaction.d.ts +9 -1
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +6 -2
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/dist/workspace/local-development-authority.d.ts +8 -0
- package/dist/workspace/local-development-authority.d.ts.map +1 -1
- package/dist/workspace/local-development-authority.js +136 -1
- package/package.json +2 -2
- package/dist/studio/control-plane-D6TT2fCQ.js +0 -3064
- package/dist/studio-app/assets/feltdb_wasm_bg-Yy4QRHJT.wasm +0 -0
- package/dist/studio-app/assets/index-CZ55nLgp.js +0 -30
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
use feltdb::{
|
|
2
|
+
ConflictClass, DurableObjectStore, LocalObjectStore, MaterializationProjection, PathComponent,
|
|
3
|
+
Reachability, S3CompatibleBucket, S3CompatibleObjectStore, StateId, StateTopology,
|
|
4
|
+
SubstrateError, VersionedObjectSubstrate,
|
|
5
|
+
};
|
|
6
|
+
use serde_json::json;
|
|
7
|
+
use std::sync::{Arc, Barrier};
|
|
8
|
+
use std::thread;
|
|
9
|
+
use tempfile::TempDir;
|
|
10
|
+
|
|
11
|
+
#[derive(Debug, PartialEq, Eq)]
|
|
12
|
+
struct Snapshot {
|
|
13
|
+
revisions: Vec<(String, Option<String>, String)>,
|
|
14
|
+
refs: Vec<(String, String, String, u64)>,
|
|
15
|
+
relationship: String,
|
|
16
|
+
has_common_ancestor: bool,
|
|
17
|
+
diff_len: usize,
|
|
18
|
+
conflict: ConflictClass,
|
|
19
|
+
materialized: String,
|
|
20
|
+
reachability_revisions: Vec<String>,
|
|
21
|
+
reachability_objects: Vec<String>,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
fn workload<S: DurableObjectStore>(substrate: &VersionedObjectSubstrate<S>) -> Snapshot {
|
|
25
|
+
let a = substrate
|
|
26
|
+
.create_revision("graph:item", json!({"n":"A"}).to_string(), None, "auth".into())
|
|
27
|
+
.unwrap();
|
|
28
|
+
let b = substrate
|
|
29
|
+
.create_revision(
|
|
30
|
+
"graph:item",
|
|
31
|
+
json!({"n":"B"}).to_string(),
|
|
32
|
+
Some(a.id.clone()),
|
|
33
|
+
"auth".into(),
|
|
34
|
+
)
|
|
35
|
+
.unwrap();
|
|
36
|
+
let c = substrate
|
|
37
|
+
.create_revision(
|
|
38
|
+
"graph:item",
|
|
39
|
+
json!({"n":"C","shared":true}).to_string(),
|
|
40
|
+
Some(b.id.clone()),
|
|
41
|
+
"auth".into(),
|
|
42
|
+
)
|
|
43
|
+
.unwrap();
|
|
44
|
+
let d = substrate
|
|
45
|
+
.create_revision(
|
|
46
|
+
"graph:item",
|
|
47
|
+
json!({"n":"D","shared":true}).to_string(),
|
|
48
|
+
Some(b.id.clone()),
|
|
49
|
+
"auth".into(),
|
|
50
|
+
)
|
|
51
|
+
.unwrap();
|
|
52
|
+
|
|
53
|
+
substrate
|
|
54
|
+
.create_ref("heads", "main", c.id.clone(), "auth".into())
|
|
55
|
+
.unwrap();
|
|
56
|
+
substrate
|
|
57
|
+
.create_ref("heads", "feature", d.id.clone(), "auth".into())
|
|
58
|
+
.unwrap();
|
|
59
|
+
|
|
60
|
+
let revisions = substrate.list_revisions().unwrap();
|
|
61
|
+
let mut topology = StateTopology::new();
|
|
62
|
+
for revision in &revisions {
|
|
63
|
+
topology.add_revision(revision.clone());
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let conflict = substrate
|
|
67
|
+
.conflict_classification(&b.id, &c.id, &d.id)
|
|
68
|
+
.unwrap()
|
|
69
|
+
.overall;
|
|
70
|
+
let reconciled = substrate.reconcile(&b.id, &c.id, &d.id, "reconciler").unwrap();
|
|
71
|
+
let materialized = substrate
|
|
72
|
+
.materialize(
|
|
73
|
+
&reconciled.id,
|
|
74
|
+
&MaterializationProjection::select(vec![vec![PathComponent::Key("n".into())]]),
|
|
75
|
+
)
|
|
76
|
+
.unwrap()
|
|
77
|
+
.to_string();
|
|
78
|
+
|
|
79
|
+
let reachability = substrate.reachability().unwrap();
|
|
80
|
+
|
|
81
|
+
let mut revisions_snapshot = substrate
|
|
82
|
+
.list_revisions()
|
|
83
|
+
.unwrap()
|
|
84
|
+
.into_iter()
|
|
85
|
+
.map(|r| {
|
|
86
|
+
(
|
|
87
|
+
r.id.as_hex().to_string(),
|
|
88
|
+
r.parent_id.map(|p| p.as_hex().to_string()),
|
|
89
|
+
r.content_id.as_hex().to_string(),
|
|
90
|
+
)
|
|
91
|
+
})
|
|
92
|
+
.collect::<Vec<_>>();
|
|
93
|
+
revisions_snapshot.sort();
|
|
94
|
+
|
|
95
|
+
let refs_snapshot = substrate
|
|
96
|
+
.list_refs(None)
|
|
97
|
+
.unwrap()
|
|
98
|
+
.into_iter()
|
|
99
|
+
.map(|r| {
|
|
100
|
+
(
|
|
101
|
+
r.namespace,
|
|
102
|
+
r.name,
|
|
103
|
+
r.target.as_hex().to_string(),
|
|
104
|
+
r.version,
|
|
105
|
+
)
|
|
106
|
+
})
|
|
107
|
+
.collect::<Vec<_>>();
|
|
108
|
+
|
|
109
|
+
let relationship = format!("{:?}", topology.relationship(&c.id, &d.id));
|
|
110
|
+
let has_common_ancestor = topology.common_ancestor(&c.id, &d.id).is_some();
|
|
111
|
+
let diff_len = feltdb::SemanticDiff::compute(
|
|
112
|
+
&serde_json::from_str::<serde_json::Value>(&c.content).unwrap(),
|
|
113
|
+
&serde_json::from_str::<serde_json::Value>(&d.content).unwrap(),
|
|
114
|
+
)
|
|
115
|
+
.changes
|
|
116
|
+
.len();
|
|
117
|
+
|
|
118
|
+
Snapshot {
|
|
119
|
+
revisions: revisions_snapshot,
|
|
120
|
+
refs: refs_snapshot,
|
|
121
|
+
relationship,
|
|
122
|
+
has_common_ancestor,
|
|
123
|
+
diff_len,
|
|
124
|
+
conflict,
|
|
125
|
+
materialized,
|
|
126
|
+
reachability_revisions: reachability
|
|
127
|
+
.revisions
|
|
128
|
+
.iter()
|
|
129
|
+
.map(|id| id.as_hex().to_string())
|
|
130
|
+
.collect(),
|
|
131
|
+
reachability_objects: reachability
|
|
132
|
+
.objects
|
|
133
|
+
.iter()
|
|
134
|
+
.map(|id| id.as_hex().to_string())
|
|
135
|
+
.collect(),
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
#[test]
|
|
140
|
+
fn local_and_s3_compatible_backends_produce_identical_version_semantics() {
|
|
141
|
+
let local_dir = TempDir::new().unwrap();
|
|
142
|
+
let local = VersionedObjectSubstrate::new(LocalObjectStore::new(local_dir.path()).unwrap());
|
|
143
|
+
|
|
144
|
+
let bucket = S3CompatibleBucket::default();
|
|
145
|
+
let s3 = VersionedObjectSubstrate::new(S3CompatibleObjectStore::new(bucket));
|
|
146
|
+
|
|
147
|
+
let local_snapshot = workload(&local);
|
|
148
|
+
let s3_snapshot = workload(&s3);
|
|
149
|
+
assert_eq!(local_snapshot, s3_snapshot);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
#[test]
|
|
153
|
+
fn durable_survives_cache_loss_but_cache_never_becomes_authority() {
|
|
154
|
+
let local_dir = TempDir::new().unwrap();
|
|
155
|
+
let backend = LocalObjectStore::new(local_dir.path()).unwrap();
|
|
156
|
+
|
|
157
|
+
let service = VersionedObjectSubstrate::new(backend.clone());
|
|
158
|
+
let a = service
|
|
159
|
+
.create_revision("docs:1", json!({"v":1}).to_string(), None, "auth".into())
|
|
160
|
+
.unwrap();
|
|
161
|
+
let b = service
|
|
162
|
+
.create_revision(
|
|
163
|
+
"docs:1",
|
|
164
|
+
json!({"v":2}).to_string(),
|
|
165
|
+
Some(a.id.clone()),
|
|
166
|
+
"auth".into(),
|
|
167
|
+
)
|
|
168
|
+
.unwrap();
|
|
169
|
+
service
|
|
170
|
+
.create_ref("heads", "main", b.id.clone(), "auth".into())
|
|
171
|
+
.unwrap();
|
|
172
|
+
|
|
173
|
+
let restart = VersionedObjectSubstrate::new(backend.clone());
|
|
174
|
+
let restored = restart.get_revision(&b.id).unwrap();
|
|
175
|
+
assert_eq!(restored.id, b.id);
|
|
176
|
+
|
|
177
|
+
// Populate cache then remove durable object; reads must fail closed.
|
|
178
|
+
let _ = restart.get_object(&b.content_id).unwrap();
|
|
179
|
+
backend.delete(&format!("object:{}", b.content_id.as_hex())).unwrap();
|
|
180
|
+
let failed = restart.get_revision(&b.id);
|
|
181
|
+
assert!(matches!(failed, Err(SubstrateError::NotFound(_))));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
#[test]
|
|
185
|
+
fn shared_history_gc_parity_across_backends_and_restart() {
|
|
186
|
+
fn scenario<S: DurableObjectStore>(backend: S) -> (Reachability, Reachability) {
|
|
187
|
+
let substrate = VersionedObjectSubstrate::new(backend.clone());
|
|
188
|
+
let a = substrate
|
|
189
|
+
.create_revision("graph:item", json!({"n":"A"}).to_string(), None, "auth".into())
|
|
190
|
+
.unwrap();
|
|
191
|
+
let b = substrate
|
|
192
|
+
.create_revision(
|
|
193
|
+
"graph:item",
|
|
194
|
+
json!({"n":"B"}).to_string(),
|
|
195
|
+
Some(a.id.clone()),
|
|
196
|
+
"auth".into(),
|
|
197
|
+
)
|
|
198
|
+
.unwrap();
|
|
199
|
+
let c = substrate
|
|
200
|
+
.create_revision(
|
|
201
|
+
"graph:item",
|
|
202
|
+
json!({"n":"C"}).to_string(),
|
|
203
|
+
Some(b.id.clone()),
|
|
204
|
+
"auth".into(),
|
|
205
|
+
)
|
|
206
|
+
.unwrap();
|
|
207
|
+
let d = substrate
|
|
208
|
+
.create_revision(
|
|
209
|
+
"graph:item",
|
|
210
|
+
json!({"n":"D"}).to_string(),
|
|
211
|
+
Some(b.id.clone()),
|
|
212
|
+
"auth".into(),
|
|
213
|
+
)
|
|
214
|
+
.unwrap();
|
|
215
|
+
substrate
|
|
216
|
+
.create_ref("heads", "main", c.id.clone(), "auth".into())
|
|
217
|
+
.unwrap();
|
|
218
|
+
substrate
|
|
219
|
+
.create_ref("heads", "feature", d.id.clone(), "auth".into())
|
|
220
|
+
.unwrap();
|
|
221
|
+
|
|
222
|
+
substrate.delete_ref("heads", "feature", Some(1)).unwrap();
|
|
223
|
+
let after_gc = substrate.collect_unreachable().unwrap();
|
|
224
|
+
assert!(after_gc.revisions.iter().any(|id| id == &b.id));
|
|
225
|
+
assert!(after_gc.revisions.iter().any(|id| id == &c.id));
|
|
226
|
+
assert!(!after_gc.revisions.iter().any(|id| id == &d.id));
|
|
227
|
+
|
|
228
|
+
let restarted = VersionedObjectSubstrate::new(backend);
|
|
229
|
+
let after_restart = restarted.reachability().unwrap();
|
|
230
|
+
(after_gc, after_restart)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
let local_dir = TempDir::new().unwrap();
|
|
234
|
+
let (local_gc, local_restart) = scenario(LocalObjectStore::new(local_dir.path()).unwrap());
|
|
235
|
+
assert_eq!(local_gc, local_restart);
|
|
236
|
+
|
|
237
|
+
let bucket = S3CompatibleBucket::default();
|
|
238
|
+
let (s3_gc, s3_restart) = scenario(S3CompatibleObjectStore::new(bucket));
|
|
239
|
+
assert_eq!(s3_gc, s3_restart);
|
|
240
|
+
assert_eq!(local_gc, s3_gc);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
#[test]
|
|
244
|
+
fn concurrent_ref_cas_allows_only_one_winner_across_backends() {
|
|
245
|
+
fn run<S: DurableObjectStore>(backend: S) {
|
|
246
|
+
let substrate = Arc::new(VersionedObjectSubstrate::new(backend));
|
|
247
|
+
let a = substrate
|
|
248
|
+
.create_revision("docs:1", json!({"v":1}).to_string(), None, "auth".into())
|
|
249
|
+
.unwrap();
|
|
250
|
+
let b = substrate
|
|
251
|
+
.create_revision(
|
|
252
|
+
"docs:1",
|
|
253
|
+
json!({"v":2}).to_string(),
|
|
254
|
+
Some(a.id.clone()),
|
|
255
|
+
"auth".into(),
|
|
256
|
+
)
|
|
257
|
+
.unwrap();
|
|
258
|
+
let c = substrate
|
|
259
|
+
.create_revision(
|
|
260
|
+
"docs:1",
|
|
261
|
+
json!({"v":3}).to_string(),
|
|
262
|
+
Some(b.id.clone()),
|
|
263
|
+
"auth".into(),
|
|
264
|
+
)
|
|
265
|
+
.unwrap();
|
|
266
|
+
substrate
|
|
267
|
+
.create_ref("heads", "main", b.id.clone(), "auth".into())
|
|
268
|
+
.unwrap();
|
|
269
|
+
|
|
270
|
+
let barrier = Arc::new(Barrier::new(2));
|
|
271
|
+
let left = {
|
|
272
|
+
let substrate = Arc::clone(&substrate);
|
|
273
|
+
let barrier = Arc::clone(&barrier);
|
|
274
|
+
let c = c.id.clone();
|
|
275
|
+
thread::spawn(move || {
|
|
276
|
+
barrier.wait();
|
|
277
|
+
substrate.cas_move_ref("heads", "main", 1, c, "left".into())
|
|
278
|
+
})
|
|
279
|
+
};
|
|
280
|
+
let right = {
|
|
281
|
+
let substrate = Arc::clone(&substrate);
|
|
282
|
+
let barrier = Arc::clone(&barrier);
|
|
283
|
+
let a = a.id.clone();
|
|
284
|
+
thread::spawn(move || {
|
|
285
|
+
barrier.wait();
|
|
286
|
+
substrate.cas_move_ref("heads", "main", 1, a, "right".into())
|
|
287
|
+
})
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
let one = left.join().unwrap();
|
|
291
|
+
let two = right.join().unwrap();
|
|
292
|
+
let wins = usize::from(one.is_ok()) + usize::from(two.is_ok());
|
|
293
|
+
assert_eq!(wins, 1);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
let local_dir = TempDir::new().unwrap();
|
|
297
|
+
run(LocalObjectStore::new(local_dir.path()).unwrap());
|
|
298
|
+
run(S3CompatibleObjectStore::new(S3CompatibleBucket::default()));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
#[test]
|
|
302
|
+
fn duplicate_object_writes_and_restart_are_stable() {
|
|
303
|
+
fn run<S: DurableObjectStore>(backend: S) {
|
|
304
|
+
let substrate = VersionedObjectSubstrate::new(backend.clone());
|
|
305
|
+
let one = substrate.store_object(r#"{"same":true}"#).unwrap();
|
|
306
|
+
let two = substrate.store_object(r#"{"same":true}"#).unwrap();
|
|
307
|
+
assert_eq!(one, two);
|
|
308
|
+
|
|
309
|
+
let restarted = VersionedObjectSubstrate::new(backend);
|
|
310
|
+
let value = restarted.get_object(&one).unwrap();
|
|
311
|
+
assert_eq!(value, r#"{"same":true}"#);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
let local_dir = TempDir::new().unwrap();
|
|
315
|
+
run(LocalObjectStore::new(local_dir.path()).unwrap());
|
|
316
|
+
run(S3CompatibleObjectStore::new(S3CompatibleBucket::default()));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
#[test]
|
|
320
|
+
fn ref_is_not_published_when_target_is_not_durable() {
|
|
321
|
+
let local_dir = TempDir::new().unwrap();
|
|
322
|
+
let backend = LocalObjectStore::new(local_dir.path()).unwrap();
|
|
323
|
+
let substrate = VersionedObjectSubstrate::new(backend.clone());
|
|
324
|
+
|
|
325
|
+
let fake = StateId::from_hex("a".repeat(64));
|
|
326
|
+
let create = substrate.create_ref("heads", "broken", fake, "auth".into());
|
|
327
|
+
assert!(matches!(create, Err(SubstrateError::NotFound(_))));
|
|
328
|
+
}
|
|
@@ -339,9 +339,26 @@ fn a_transaction_commits_under_a_barrier_regardless_of_the_mode() {
|
|
|
339
339
|
fn the_retention_envelope_has_the_measured_shape() {
|
|
340
340
|
let directory = TempDir::new().unwrap();
|
|
341
341
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
342
|
+
// This binary runs its envelope cases concurrently. Use the best of three
|
|
343
|
+
// complete, isolated samples so scheduler and filesystem contention cannot
|
|
344
|
+
// invert a wall-clock ordering while retaining the frozen workload shape.
|
|
345
|
+
let sample = |workload: Workload| {
|
|
346
|
+
(0..3)
|
|
347
|
+
.map(|index| {
|
|
348
|
+
let sample_directory = directory.path().join(format!(
|
|
349
|
+
"retention-{}-{index}",
|
|
350
|
+
workload.name
|
|
351
|
+
));
|
|
352
|
+
std::fs::create_dir_all(&sample_directory).unwrap();
|
|
353
|
+
run(&sample_directory, workload, DurabilityMode::Flushed)
|
|
354
|
+
})
|
|
355
|
+
.min_by_key(|measurement| measurement.elapsed)
|
|
356
|
+
.unwrap()
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
let unbounded = sample(WORKLOADS[0]);
|
|
360
|
+
let keep5 = sample(WORKLOADS[1]);
|
|
361
|
+
let keep50 = sample(WORKLOADS[2]);
|
|
345
362
|
|
|
346
363
|
assert_eq!(unbounded.revisions, 301, "unbounded keeps everything");
|
|
347
364
|
assert_eq!(keep5.revisions, 5);
|
|
@@ -18,9 +18,11 @@ pub struct BoundedQueryCursor {
|
|
|
18
18
|
|
|
19
19
|
use crate::{
|
|
20
20
|
application_contract::ApplicationContractStore, artifacts::ArtifactStore, audit::AuditLog,
|
|
21
|
-
auth::KeyStore,
|
|
21
|
+
auth::KeyStore, canonical_principals::CanonicalPrincipalAuthority,
|
|
22
|
+
causal::CanonicalEventAuthority,
|
|
23
|
+
clock::LeaseClock, cluster::ClusterStore,
|
|
22
24
|
connections::ConnectionStore, content::ContentStore, identity::IdentityStore,
|
|
23
|
-
leases::LeaseStore, metrics::Metrics, portable_bundle::BundleStore,
|
|
25
|
+
leases::LeaseStore, metrics::Metrics, portable_bundle::BundleStore,
|
|
24
26
|
providers::ProviderStore, releases::ReleaseStore, sessions::SessionVerifier,
|
|
25
27
|
tenancy::TenancyStore,
|
|
26
28
|
};
|
|
@@ -49,13 +51,21 @@ pub struct AppState {
|
|
|
49
51
|
pub peer_client: reqwest::Client,
|
|
50
52
|
pub peer_token: Option<Arc<str>>,
|
|
51
53
|
pub lifecycle_lock: Arc<tokio::sync::Mutex<()>>,
|
|
54
|
+
/// Bounds concurrent schema snapshots before canonical commit admission.
|
|
55
|
+
///
|
|
56
|
+
/// State-contract transactions inspect a full authority snapshot before
|
|
57
|
+
/// entering the database's atomic commit. Keeping that interval exclusive
|
|
58
|
+
/// prevents an unbounded request burst from retaining one snapshot per
|
|
59
|
+
/// request. The database's guarded transaction remains the atomic commit
|
|
60
|
+
/// arbiter; this semaphore only bounds pre-commit working memory.
|
|
61
|
+
pub state_transaction_admission: Arc<tokio::sync::Semaphore>,
|
|
52
62
|
pub worker_enabled: bool,
|
|
53
63
|
pub leases: LeaseStore,
|
|
54
64
|
pub content: ContentStore,
|
|
55
65
|
pub artifacts: ArtifactStore,
|
|
56
66
|
pub bundles: BundleStore,
|
|
57
67
|
pub releases: ReleaseStore,
|
|
58
|
-
pub
|
|
68
|
+
pub events: CanonicalEventAuthority,
|
|
59
69
|
pub providers: ProviderStore,
|
|
60
70
|
pub contracts: ApplicationContractStore,
|
|
61
71
|
pub advertise_url: Option<Arc<str>>,
|
|
@@ -64,7 +74,7 @@ pub struct AppState {
|
|
|
64
74
|
pub backup_artifacts: Arc<Vec<(String, PathBuf)>>,
|
|
65
75
|
pub tenancy: TenancyStore,
|
|
66
76
|
pub sessions: SessionVerifier,
|
|
67
|
-
pub principals:
|
|
77
|
+
pub principals: CanonicalPrincipalAuthority,
|
|
68
78
|
pub connections: ConnectionStore,
|
|
69
79
|
pub identities: IdentityStore,
|
|
70
80
|
pub applications: ApplicationStore,
|
|
@@ -50,10 +50,6 @@ pub fn server_artifacts(data: &Path, keys: &Path, audit: &Path) -> Vec<(String,
|
|
|
50
50
|
"application-runtimes.json".into(),
|
|
51
51
|
data.with_extension("application-runtimes.json"),
|
|
52
52
|
),
|
|
53
|
-
(
|
|
54
|
-
"principals.json".into(),
|
|
55
|
-
data.with_extension("principals.json"),
|
|
56
|
-
),
|
|
57
53
|
(
|
|
58
54
|
"connections.json".into(),
|
|
59
55
|
data.with_extension("connections.json"),
|