@feltdb/core 0.8.3 → 0.8.5
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 +4 -1
- package/dist/cli/provisioning-neutrality.js +79 -0
- package/dist/collection.d.ts +43 -1
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +192 -22
- package/dist/create/create.js +25 -21
- package/dist/create/managed-account.js +11 -0
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/Cargo.lock +165 -0
- package/dist/create/server-source/Cargo.toml +9 -0
- package/dist/create/server-source/crates/feltdb/Cargo.toml +3 -0
- package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +44 -44
- package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +12 -24
- package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +7 -13
- package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +13 -10
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +12 -9
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +78 -25
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +16 -13
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +13 -5
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +41 -22
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +33 -15
- package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +100 -33
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +56 -20
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +116 -41
- package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +186 -55
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +64 -26
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +46 -15
- package/dist/create/server-source/crates/feltdb/src/admission.rs +8 -15
- package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +43 -13
- package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +15 -42
- package/dist/create/server-source/crates/feltdb/src/analytics.rs +65 -19
- package/dist/create/server-source/crates/feltdb/src/application.rs +113 -30
- package/dist/create/server-source/crates/feltdb/src/authorization_security_tests.rs +475 -140
- package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +17 -15
- package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +0 -1
- package/dist/create/server-source/crates/feltdb/src/causal_backlog_bound.rs +59 -15
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +266 -114
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +25 -7
- package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +10 -15
- package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +3 -11
- package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +14 -5
- package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +21 -25
- package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +14 -11
- package/dist/create/server-source/crates/feltdb/src/dedup_bound_investigation.rs +103 -22
- package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +18 -15
- package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +12 -8
- package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +1 -5
- package/dist/create/server-source/crates/feltdb/src/durable_operation_identity.rs +87 -23
- package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +3 -7
- package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +10 -9
- package/dist/create/server-source/crates/feltdb/src/equality_index.rs +595 -0
- package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +1 -6
- package/dist/create/server-source/crates/feltdb/src/indexing.rs +35 -38
- package/dist/create/server-source/crates/feltdb/src/lib.rs +1050 -117
- package/dist/create/server-source/crates/feltdb/src/managed_cas_tests.rs +4 -1
- package/dist/create/server-source/crates/feltdb/src/metrics.rs +0 -1
- package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +1 -2
- package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +107 -30
- package/dist/create/server-source/crates/feltdb/src/observability.rs +19 -6
- package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +12 -11
- package/dist/create/server-source/crates/feltdb/src/operation_log.rs +9 -4
- package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +65 -18
- package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +193 -57
- package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +37 -27
- package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +38 -10
- package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +20 -14
- package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +394 -229
- package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +8 -6
- package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +11 -13
- package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +90 -72
- package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +3 -3
- package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +33 -11
- package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +6 -6
- package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +14 -56
- package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +29 -27
- package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +35 -17
- package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +8 -2
- package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +59 -15
- package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +70 -17
- package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +23 -6
- package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +12 -3
- package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +41 -11
- package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +56 -25
- package/dist/create/server-source/crates/feltdb/src/policy_evaluation.rs +701 -245
- package/dist/create/server-source/crates/feltdb/src/production_api.rs +31 -13
- package/dist/create/server-source/crates/feltdb/src/query_execution_diagnostics.rs +126 -0
- package/dist/create/server-source/crates/feltdb/src/query_performance.rs +6 -8
- package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +5 -5
- package/dist/create/server-source/crates/feltdb/src/replica_acknowledgements.rs +48 -18
- package/dist/create/server-source/crates/feltdb/src/replica_membership.rs +30 -11
- package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +6 -3
- package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +4 -3
- package/dist/create/server-source/crates/feltdb/src/sharding.rs +36 -10
- package/dist/create/server-source/crates/feltdb/src/state_conflict_contract.rs +516 -0
- package/dist/create/server-source/crates/feltdb/src/state_contract.rs +305 -6
- package/dist/create/server-source/crates/feltdb/src/state_diff_contract.rs +222 -0
- package/dist/create/server-source/crates/feltdb/src/state_facade.rs +82 -54
- package/dist/create/server-source/crates/feltdb/src/state_hash.rs +2 -2
- package/dist/create/server-source/crates/feltdb/src/state_model.rs +1514 -537
- package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +6 -3
- package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +672 -0
- package/dist/create/server-source/crates/feltdb/src/submission.rs +5 -11
- package/dist/create/server-source/crates/feltdb/src/sync.rs +12 -0
- package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +6 -8
- package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +24 -35
- package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +24 -8
- package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +248 -59
- package/dist/create/server-source/crates/feltdb/src/transactions.rs +17 -20
- package/dist/create/server-source/crates/feltdb/src/trigger_contract.rs +749 -0
- package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +1 -0
- package/dist/create/server-source/crates/feltdb/src/workload.rs +512 -4
- package/dist/create/server-source/crates/feltdb/src/workload_diagnostics.rs +442 -0
- package/dist/create/server-source/crates/feltdb/tests/branching_evidence.rs +299 -0
- package/dist/create/server-source/crates/feltdb/tests/current_revision_authority_evidence.rs +288 -0
- package/dist/create/server-source/crates/feltdb/tests/durable_format_compatibility.rs +392 -0
- package/dist/create/server-source/crates/feltdb/tests/feltdb_state_boundary_tests.rs +436 -220
- package/dist/create/server-source/crates/feltdb/tests/fixtures/state_conflict_contract_corpus.json +1916 -0
- package/dist/create/server-source/crates/feltdb/tests/fixtures/state_diff_contract_corpus.json +1878 -0
- package/dist/create/server-source/crates/feltdb/tests/fixtures/trigger_contract_corpus.json +1862 -0
- package/dist/create/server-source/crates/feltdb/tests/pr34_query_collection.rs +234 -0
- package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +947 -0
- package/dist/create/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +5 -8
- package/dist/create/server-source/crates/feltdb/tests/pr8_vocabulary_assessment.rs +52 -44
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase2_boundary_tests.rs +33 -16
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3a_path_a_tests.rs +22 -7
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_authorized_mutations.rs +41 -22
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_role_based_authorization.rs +25 -8
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_simple_auth_delete.rs +9 -6
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_team_delete_role_authorization.rs +120 -69
- package/dist/create/server-source/crates/feltdb/tests/pr9_teams_role_based_access.rs +21 -10
- package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +1032 -0
- package/dist/create/server-source/crates/feltdb/tests/reconciliation_application.rs +868 -0
- package/dist/create/server-source/crates/feltdb/tests/reconciliation_wire_format_evidence.rs +221 -0
- package/dist/create/server-source/crates/feltdb/tests/resource_scoped_revisions.rs +338 -0
- package/dist/create/server-source/crates/feltdb/tests/revision_identity_contract.rs +1039 -0
- package/dist/create/server-source/crates/feltdb/tests/revision_model_decision.rs +739 -0
- package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +427 -0
- package/dist/create/server-source/crates/feltdb/tests/saas_authorization_integration.rs +3 -3
- package/dist/create/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +25 -22
- package/dist/create/server-source/crates/feltdb/tests/state_conflict_contract_conformance.rs +1799 -0
- package/dist/create/server-source/crates/feltdb/tests/state_diff_contract_conformance.rs +1316 -0
- package/dist/create/server-source/crates/feltdb/tests/state_model_integration.rs +53 -61
- package/dist/create/server-source/crates/feltdb/tests/state_persistence_integration.rs +156 -61
- package/dist/create/server-source/crates/feltdb/tests/state_store_boundary_evidence.rs +299 -0
- package/dist/create/server-source/crates/feltdb/tests/sync_divergence_evidence.rs +255 -0
- package/dist/create/server-source/crates/feltdb/tests/three_way_input_boundary_evidence.rs +249 -0
- package/dist/create/server-source/crates/feltdb/tests/trigger_contract_conformance.rs +994 -0
- package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +2 -1
- package/dist/create/server-source/crates/feltdb-server/src/audit.rs +1137 -29
- package/dist/create/server-source/crates/feltdb-server/src/auth.rs +164 -13
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +800 -34
- package/dist/db.d.ts +33 -34
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +74 -20
- package/dist/deployment.d.ts +30 -0
- package/dist/deployment.d.ts.map +1 -0
- package/dist/deployment.js +130 -0
- package/dist/embedded-transaction.d.ts +22 -4
- package/dist/embedded-transaction.d.ts.map +1 -1
- package/dist/embedded-transaction.js +51 -5
- package/dist/feltdb.d.ts +14 -2
- package/dist/feltdb.d.ts.map +1 -1
- package/dist/file-db.js +1 -1
- package/dist/http-client.d.ts +14 -0
- package/dist/http-client.d.ts.map +1 -1
- package/dist/http-client.js +23 -5
- package/dist/http-db.d.ts +119 -1
- package/dist/http-db.d.ts.map +1 -1
- package/dist/http-db.js +346 -31
- package/dist/index-core.d.ts +2 -0
- package/dist/index-core.d.ts.map +1 -1
- package/dist/index-core.js +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/indexeddb-db.d.ts.map +1 -1
- package/dist/indexeddb-db.js +35 -21
- package/dist/managed-recovery.d.ts +192 -0
- package/dist/managed-recovery.d.ts.map +1 -0
- package/dist/managed-recovery.js +242 -0
- package/dist/memory-db.js +1 -1
- package/dist/studio-app/assets/{feltdb_wasm-DB8cX151.js → feltdb_wasm-DaNwCLRX.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-DnsHNv6g.wasm +0 -0
- package/dist/studio-app/assets/index-j8IlhNqJ.js +29 -0
- package/dist/studio-app/index.html +1 -1
- package/dist/transaction.d.ts +30 -0
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +41 -0
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/dist/workload.d.ts +2 -1
- package/dist/workload.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-ClhDHp0S.wasm +0 -0
- package/dist/studio-app/assets/index-B0k4UAlI.js +0 -29
|
@@ -34,9 +34,7 @@
|
|
|
34
34
|
|
|
35
35
|
#[cfg(test)]
|
|
36
36
|
mod tests {
|
|
37
|
-
use crate::{
|
|
38
|
-
AtomicMutation, FeltDb, FlowError, PreconditionFailure, RecordPrecondition,
|
|
39
|
-
};
|
|
37
|
+
use crate::{AtomicMutation, FeltDb, FlowError, PreconditionFailure, RecordPrecondition};
|
|
40
38
|
use serde_json::json;
|
|
41
39
|
use tempfile::TempDir;
|
|
42
40
|
|
|
@@ -60,7 +58,11 @@ mod tests {
|
|
|
60
58
|
}
|
|
61
59
|
|
|
62
60
|
fn delete(collection: &str, id: &str) -> AtomicMutation {
|
|
63
|
-
AtomicMutation {
|
|
61
|
+
AtomicMutation {
|
|
62
|
+
capability: collection.to_string(),
|
|
63
|
+
key: key(collection, id),
|
|
64
|
+
value: None,
|
|
65
|
+
}
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
fn precondition(collection: &str, id: &str) -> RecordPrecondition {
|
|
@@ -72,7 +74,10 @@ mod tests {
|
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
fn expect_version(collection: &str, id: &str, version: u64) -> RecordPrecondition {
|
|
75
|
-
RecordPrecondition {
|
|
77
|
+
RecordPrecondition {
|
|
78
|
+
expected_version: Some(version),
|
|
79
|
+
..precondition(collection, id)
|
|
80
|
+
}
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
fn present(db: &FeltDb, collection: &str, id: &str) -> bool {
|
|
@@ -95,7 +100,10 @@ mod tests {
|
|
|
95
100
|
let mut value = json!({ "id": id, "__version": version });
|
|
96
101
|
if let Some(fields) = extra.as_object() {
|
|
97
102
|
for (name, field) in fields {
|
|
98
|
-
value
|
|
103
|
+
value
|
|
104
|
+
.as_object_mut()
|
|
105
|
+
.unwrap()
|
|
106
|
+
.insert(name.clone(), field.clone());
|
|
99
107
|
}
|
|
100
108
|
}
|
|
101
109
|
db.apply_atomic_transaction(
|
|
@@ -138,7 +146,11 @@ mod tests {
|
|
|
138
146
|
"tx-ok",
|
|
139
147
|
&[expect_version("sessions", "s1", 7)],
|
|
140
148
|
&[
|
|
141
|
-
set(
|
|
149
|
+
set(
|
|
150
|
+
"sessions",
|
|
151
|
+
"s1",
|
|
152
|
+
json!({ "id": "s1", "__version": 8, "state": "active" }),
|
|
153
|
+
),
|
|
142
154
|
set("changes", "c1", json!({ "id": "c1" })),
|
|
143
155
|
delete("outbox", "o1"),
|
|
144
156
|
],
|
|
@@ -148,7 +160,10 @@ mod tests {
|
|
|
148
160
|
assert_eq!(commit.rows.len(), 3);
|
|
149
161
|
assert_eq!(version_of(&db, "sessions", "s1"), 8);
|
|
150
162
|
assert!(present(&db, "changes", "c1"));
|
|
151
|
-
assert!(
|
|
163
|
+
assert!(
|
|
164
|
+
!present(&db, "outbox", "o1"),
|
|
165
|
+
"the staged delete applied too"
|
|
166
|
+
);
|
|
152
167
|
}
|
|
153
168
|
|
|
154
169
|
#[test]
|
|
@@ -169,7 +184,11 @@ mod tests {
|
|
|
169
184
|
"tx-stale",
|
|
170
185
|
&[expect_version("records", "a", 7)],
|
|
171
186
|
&[
|
|
172
|
-
set(
|
|
187
|
+
set(
|
|
188
|
+
"records",
|
|
189
|
+
"a",
|
|
190
|
+
json!({ "id": "a", "__version": 8, "written": true }),
|
|
191
|
+
),
|
|
173
192
|
set("records", "b", json!({ "id": "b", "state": "overwritten" })),
|
|
174
193
|
set("records", "c", json!({ "id": "c" })),
|
|
175
194
|
],
|
|
@@ -188,13 +207,20 @@ mod tests {
|
|
|
188
207
|
from a broken deployment"
|
|
189
208
|
);
|
|
190
209
|
|
|
191
|
-
assert_eq!(
|
|
210
|
+
assert_eq!(
|
|
211
|
+
version_of(&db, "records", "a"),
|
|
212
|
+
8,
|
|
213
|
+
"the guarded record is untouched"
|
|
214
|
+
);
|
|
192
215
|
assert_eq!(
|
|
193
216
|
field(&db, "records", "b", "state"),
|
|
194
217
|
"original",
|
|
195
218
|
"and so is every other staged record"
|
|
196
219
|
);
|
|
197
|
-
assert!(
|
|
220
|
+
assert!(
|
|
221
|
+
!present(&db, "records", "c"),
|
|
222
|
+
"a staged write to a new record did not appear"
|
|
223
|
+
);
|
|
198
224
|
}
|
|
199
225
|
|
|
200
226
|
#[test]
|
|
@@ -213,14 +239,25 @@ mod tests {
|
|
|
213
239
|
"owner-1-commit",
|
|
214
240
|
&[expect_version("sessions", "session", 12)],
|
|
215
241
|
&[
|
|
216
|
-
set(
|
|
242
|
+
set(
|
|
243
|
+
"sessions",
|
|
244
|
+
"session",
|
|
245
|
+
json!({ "id": "session", "__version": 13, "generation": 12 }),
|
|
246
|
+
),
|
|
217
247
|
set("changes", "change-1", json!({ "id": "change-1" })),
|
|
218
248
|
set("outbox", "outbox-1", json!({ "id": "outbox-1" })),
|
|
219
249
|
],
|
|
220
250
|
)
|
|
221
251
|
.expect_err("the losing owner must be refused");
|
|
222
252
|
|
|
223
|
-
assert!(matches!(
|
|
253
|
+
assert!(matches!(
|
|
254
|
+
failure(error),
|
|
255
|
+
PreconditionFailure::Version {
|
|
256
|
+
expected: 12,
|
|
257
|
+
actual: 13,
|
|
258
|
+
..
|
|
259
|
+
}
|
|
260
|
+
));
|
|
224
261
|
assert_eq!(
|
|
225
262
|
field(&db, "sessions", "session", "generation"),
|
|
226
263
|
13,
|
|
@@ -241,7 +278,10 @@ mod tests {
|
|
|
241
278
|
guarded(
|
|
242
279
|
&db,
|
|
243
280
|
"tx-epoch-ok",
|
|
244
|
-
&[RecordPrecondition {
|
|
281
|
+
&[RecordPrecondition {
|
|
282
|
+
expected_epoch: Some(4),
|
|
283
|
+
..precondition("cells", "c")
|
|
284
|
+
}],
|
|
245
285
|
&[set("cells", "written", json!({ "id": "written" }))],
|
|
246
286
|
)
|
|
247
287
|
.expect("a matching epoch commits");
|
|
@@ -250,7 +290,10 @@ mod tests {
|
|
|
250
290
|
let error = guarded(
|
|
251
291
|
&db,
|
|
252
292
|
"tx-epoch-stale",
|
|
253
|
-
&[RecordPrecondition {
|
|
293
|
+
&[RecordPrecondition {
|
|
294
|
+
expected_epoch: Some(3),
|
|
295
|
+
..precondition("cells", "c")
|
|
296
|
+
}],
|
|
254
297
|
&[set("cells", "never", json!({ "id": "never" }))],
|
|
255
298
|
)
|
|
256
299
|
.expect_err("a stale epoch rejects");
|
|
@@ -272,15 +315,26 @@ mod tests {
|
|
|
272
315
|
let dir = TempDir::new().unwrap();
|
|
273
316
|
let db = db(&dir);
|
|
274
317
|
let future = crate::now_ms() as u64 + 600_000;
|
|
275
|
-
seed(
|
|
318
|
+
seed(
|
|
319
|
+
&db,
|
|
320
|
+
"cells",
|
|
321
|
+
"c",
|
|
322
|
+
1,
|
|
323
|
+
json!({ "lease": { "leaseId": "lease-a", "expiresAt": future } }),
|
|
324
|
+
);
|
|
276
325
|
|
|
277
326
|
let hold = |lease: &str| RecordPrecondition {
|
|
278
327
|
expected_lease_id: Some(lease.to_string()),
|
|
279
328
|
..precondition("cells", "c")
|
|
280
329
|
};
|
|
281
330
|
|
|
282
|
-
guarded(
|
|
283
|
-
|
|
331
|
+
guarded(
|
|
332
|
+
&db,
|
|
333
|
+
"tx-lease-ok",
|
|
334
|
+
&[hold("lease-a")],
|
|
335
|
+
&[set("cells", "w1", json!({ "id": "w1" }))],
|
|
336
|
+
)
|
|
337
|
+
.expect("the held lease commits");
|
|
284
338
|
assert!(present(&db, "cells", "w1"));
|
|
285
339
|
|
|
286
340
|
let error = guarded(
|
|
@@ -303,7 +357,13 @@ mod tests {
|
|
|
303
357
|
|
|
304
358
|
// An expired lease is not held, so naming it is a conflict: the caller
|
|
305
359
|
// believes it owns something it no longer does.
|
|
306
|
-
seed(
|
|
360
|
+
seed(
|
|
361
|
+
&db,
|
|
362
|
+
"cells",
|
|
363
|
+
"expired",
|
|
364
|
+
1,
|
|
365
|
+
json!({ "lease": { "leaseId": "lease-a", "expiresAt": 1u64 } }),
|
|
366
|
+
);
|
|
307
367
|
let error = guarded(
|
|
308
368
|
&db,
|
|
309
369
|
"tx-lease-expired",
|
|
@@ -336,7 +396,14 @@ mod tests {
|
|
|
336
396
|
&[set("cells", "w", json!({ "id": "w" }))],
|
|
337
397
|
)
|
|
338
398
|
.expect_err("one failing predicate refuses the whole transaction");
|
|
339
|
-
assert!(matches!(
|
|
399
|
+
assert!(matches!(
|
|
400
|
+
failure(error),
|
|
401
|
+
PreconditionFailure::Epoch {
|
|
402
|
+
expected: 9,
|
|
403
|
+
actual: 2,
|
|
404
|
+
..
|
|
405
|
+
}
|
|
406
|
+
));
|
|
340
407
|
assert!(!present(&db, "cells", "w"));
|
|
341
408
|
|
|
342
409
|
// Several records, one of them stale.
|
|
@@ -344,11 +411,17 @@ mod tests {
|
|
|
344
411
|
let error = guarded(
|
|
345
412
|
&db,
|
|
346
413
|
"tx-mixed-2",
|
|
347
|
-
&[
|
|
414
|
+
&[
|
|
415
|
+
expect_version("cells", "c", 5),
|
|
416
|
+
expect_version("cells", "d", 99),
|
|
417
|
+
],
|
|
348
418
|
&[set("cells", "w", json!({ "id": "w" }))],
|
|
349
419
|
)
|
|
350
420
|
.expect_err("a failing predicate on any record refuses the transaction");
|
|
351
|
-
assert!(matches!(
|
|
421
|
+
assert!(matches!(
|
|
422
|
+
failure(error),
|
|
423
|
+
PreconditionFailure::Version { expected: 99, .. }
|
|
424
|
+
));
|
|
352
425
|
assert!(!present(&db, "cells", "w"));
|
|
353
426
|
}
|
|
354
427
|
|
|
@@ -367,7 +440,10 @@ mod tests {
|
|
|
367
440
|
.expect_err("expecting a version of a record that does not exist is a conflict");
|
|
368
441
|
assert_eq!(
|
|
369
442
|
failure(error),
|
|
370
|
-
PreconditionFailure::Missing {
|
|
443
|
+
PreconditionFailure::Missing {
|
|
444
|
+
collection: "cells".into(),
|
|
445
|
+
key: "cells:absent".into()
|
|
446
|
+
}
|
|
371
447
|
);
|
|
372
448
|
assert!(!present(&db, "cells", "w"));
|
|
373
449
|
}
|
|
@@ -393,10 +469,18 @@ mod tests {
|
|
|
393
469
|
fn require_absent_still_means_absent() {
|
|
394
470
|
let dir = TempDir::new().unwrap();
|
|
395
471
|
let db = db(&dir);
|
|
396
|
-
let absent = |id: &str| RecordPrecondition {
|
|
472
|
+
let absent = |id: &str| RecordPrecondition {
|
|
473
|
+
require_absent: true,
|
|
474
|
+
..precondition("cells", id)
|
|
475
|
+
};
|
|
397
476
|
|
|
398
|
-
guarded(
|
|
399
|
-
|
|
477
|
+
guarded(
|
|
478
|
+
&db,
|
|
479
|
+
"tx-new",
|
|
480
|
+
&[absent("fresh")],
|
|
481
|
+
&[set("cells", "fresh", json!({ "id": "fresh" }))],
|
|
482
|
+
)
|
|
483
|
+
.expect("absent record commits");
|
|
400
484
|
assert!(present(&db, "cells", "fresh"));
|
|
401
485
|
assert_eq!(version_of(&db, "cells", "fresh"), 1);
|
|
402
486
|
|
|
@@ -409,7 +493,10 @@ mod tests {
|
|
|
409
493
|
.expect_err("an existing record refuses a require-absent transaction");
|
|
410
494
|
assert_eq!(
|
|
411
495
|
failure(error),
|
|
412
|
-
PreconditionFailure::Present {
|
|
496
|
+
PreconditionFailure::Present {
|
|
497
|
+
collection: "cells".into(),
|
|
498
|
+
key: "cells:fresh".into()
|
|
499
|
+
}
|
|
413
500
|
);
|
|
414
501
|
assert!(!present(&db, "cells", "other"));
|
|
415
502
|
}
|
|
@@ -443,7 +530,10 @@ mod tests {
|
|
|
443
530
|
&[set("cells", "w", json!({ "id": "w", "n": 2 }))],
|
|
444
531
|
)
|
|
445
532
|
.expect("a committed id replays rather than failing");
|
|
446
|
-
assert!(
|
|
533
|
+
assert!(
|
|
534
|
+
retry.duplicate,
|
|
535
|
+
"the retry is recognised as the same transaction"
|
|
536
|
+
);
|
|
447
537
|
assert_eq!(
|
|
448
538
|
field(&db, "cells", "w", "n"),
|
|
449
539
|
1,
|
|
@@ -498,7 +588,10 @@ mod tests {
|
|
|
498
588
|
.expect("commits");
|
|
499
589
|
}
|
|
500
590
|
let reopened = FeltDb::open(&path).expect("reopen");
|
|
501
|
-
assert!(
|
|
591
|
+
assert!(
|
|
592
|
+
present(&reopened, "cells", "w"),
|
|
593
|
+
"a guarded commit is as durable as any other"
|
|
594
|
+
);
|
|
502
595
|
let retry = reopened
|
|
503
596
|
.apply_atomic_transaction_guarded(
|
|
504
597
|
"tx-durable",
|
|
@@ -547,7 +640,11 @@ mod tests {
|
|
|
547
640
|
}
|
|
548
641
|
}],
|
|
549
642
|
&[
|
|
550
|
-
set(
|
|
643
|
+
set(
|
|
644
|
+
"cells",
|
|
645
|
+
"shared",
|
|
646
|
+
json!({ "id": "shared", "__version": 2, "by": id }),
|
|
647
|
+
),
|
|
551
648
|
set("cells", second, json!({ "id": second })),
|
|
552
649
|
],
|
|
553
650
|
None,
|
|
@@ -556,9 +653,18 @@ mod tests {
|
|
|
556
653
|
}));
|
|
557
654
|
}
|
|
558
655
|
|
|
559
|
-
let outcomes: Vec<_> = handles
|
|
560
|
-
|
|
561
|
-
|
|
656
|
+
let outcomes: Vec<_> = handles
|
|
657
|
+
.into_iter()
|
|
658
|
+
.map(|handle| handle.join().unwrap())
|
|
659
|
+
.collect();
|
|
660
|
+
let winners: Vec<_> = outcomes
|
|
661
|
+
.iter()
|
|
662
|
+
.filter_map(|outcome| outcome.as_ref().ok())
|
|
663
|
+
.collect();
|
|
664
|
+
let losers: Vec<_> = outcomes
|
|
665
|
+
.iter()
|
|
666
|
+
.filter_map(|outcome| outcome.as_ref().err())
|
|
667
|
+
.collect();
|
|
562
668
|
|
|
563
669
|
assert_eq!(winners.len(), 1, "exactly one transaction may commit");
|
|
564
670
|
assert_eq!(losers.len(), 1);
|
|
@@ -574,8 +680,14 @@ mod tests {
|
|
|
574
680
|
|
|
575
681
|
let loser_record = if winner == "t1" { "c" } else { "b" };
|
|
576
682
|
let winner_record = if winner == "t1" { "b" } else { "c" };
|
|
577
|
-
assert!(
|
|
578
|
-
|
|
683
|
+
assert!(
|
|
684
|
+
present(&db, "cells", winner_record),
|
|
685
|
+
"the winner's second write committed"
|
|
686
|
+
);
|
|
687
|
+
assert!(
|
|
688
|
+
!present(&db, "cells", loser_record),
|
|
689
|
+
"the loser wrote neither of its records"
|
|
690
|
+
);
|
|
579
691
|
}
|
|
580
692
|
|
|
581
693
|
// -- the boundary that used to exist ----------------------------------
|
|
@@ -638,7 +750,11 @@ mod tests {
|
|
|
638
750
|
)
|
|
639
751
|
.expect("commits");
|
|
640
752
|
|
|
641
|
-
assert_eq!(
|
|
753
|
+
assert_eq!(
|
|
754
|
+
version_of(&db, "cells", "c"),
|
|
755
|
+
2,
|
|
756
|
+
"validated at 1, written at 2"
|
|
757
|
+
);
|
|
642
758
|
assert_eq!(field(&db, "cells", "c", "state"), "written");
|
|
643
759
|
}
|
|
644
760
|
|
|
@@ -657,7 +773,11 @@ mod tests {
|
|
|
657
773
|
&[set("cells", "c", json!({ "id": "c", "__version": 99 }))],
|
|
658
774
|
)
|
|
659
775
|
.expect("commits");
|
|
660
|
-
assert_eq!(
|
|
776
|
+
assert_eq!(
|
|
777
|
+
version_of(&db, "cells", "c"),
|
|
778
|
+
5,
|
|
779
|
+
"the authority's transition, not the caller's"
|
|
780
|
+
);
|
|
661
781
|
}
|
|
662
782
|
|
|
663
783
|
#[test]
|
|
@@ -681,10 +801,17 @@ mod tests {
|
|
|
681
801
|
&[expect_version("cells", "c", 7)],
|
|
682
802
|
&[set("cells", "c", json!({ "id": "c", "by": "second" }))],
|
|
683
803
|
);
|
|
684
|
-
assert!(
|
|
804
|
+
assert!(
|
|
805
|
+
second.is_err(),
|
|
806
|
+
"ifVersion is now a fence: the successor is refused"
|
|
807
|
+
);
|
|
685
808
|
assert!(matches!(
|
|
686
809
|
failure(second.unwrap_err()),
|
|
687
|
-
PreconditionFailure::Version {
|
|
810
|
+
PreconditionFailure::Version {
|
|
811
|
+
expected: 7,
|
|
812
|
+
actual: 8,
|
|
813
|
+
..
|
|
814
|
+
}
|
|
688
815
|
));
|
|
689
816
|
assert_eq!(field(&db, "cells", "c", "by"), "first");
|
|
690
817
|
}
|
|
@@ -695,10 +822,20 @@ mod tests {
|
|
|
695
822
|
let db = db(&dir);
|
|
696
823
|
seed(&db, "cells", "c", 1, json!({}));
|
|
697
824
|
|
|
698
|
-
guarded(
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
825
|
+
guarded(
|
|
826
|
+
&db,
|
|
827
|
+
"s1",
|
|
828
|
+
&[expect_version("cells", "c", 1)],
|
|
829
|
+
&[set("cells", "c", json!({ "id": "c" }))],
|
|
830
|
+
)
|
|
831
|
+
.expect("first");
|
|
832
|
+
guarded(
|
|
833
|
+
&db,
|
|
834
|
+
"s2",
|
|
835
|
+
&[expect_version("cells", "c", 2)],
|
|
836
|
+
&[set("cells", "c", json!({ "id": "c" }))],
|
|
837
|
+
)
|
|
838
|
+
.expect("second, on the advanced version");
|
|
702
839
|
assert_eq!(version_of(&db, "cells", "c"), 3);
|
|
703
840
|
}
|
|
704
841
|
|
|
@@ -712,7 +849,10 @@ mod tests {
|
|
|
712
849
|
guarded(
|
|
713
850
|
&db,
|
|
714
851
|
"tx-multi",
|
|
715
|
-
&[
|
|
852
|
+
&[
|
|
853
|
+
expect_version("cells", "a", 1),
|
|
854
|
+
expect_version("cells", "b", 5),
|
|
855
|
+
],
|
|
716
856
|
&[
|
|
717
857
|
set("cells", "a", json!({ "id": "a" })),
|
|
718
858
|
set("cells", "b", json!({ "id": "b" })),
|
|
@@ -763,11 +903,19 @@ mod tests {
|
|
|
763
903
|
"tx-plain",
|
|
764
904
|
None,
|
|
765
905
|
&[],
|
|
766
|
-
&[set(
|
|
906
|
+
&[set(
|
|
907
|
+
"cells",
|
|
908
|
+
"c",
|
|
909
|
+
json!({ "id": "c", "__version": 4, "state": "same" }),
|
|
910
|
+
)],
|
|
767
911
|
None,
|
|
768
912
|
)
|
|
769
913
|
.expect("commits");
|
|
770
|
-
assert_eq!(
|
|
914
|
+
assert_eq!(
|
|
915
|
+
version_of(&db, "cells", "c"),
|
|
916
|
+
4,
|
|
917
|
+
"unchanged, because nothing fenced it"
|
|
918
|
+
);
|
|
771
919
|
|
|
772
920
|
db.apply_atomic_transaction(
|
|
773
921
|
"tx-plain-2",
|
|
@@ -799,9 +947,17 @@ mod tests {
|
|
|
799
947
|
assert!(stale.is_err(), "a stale fence refuses the delete");
|
|
800
948
|
assert!(present(&db, "cells", "c"));
|
|
801
949
|
|
|
802
|
-
guarded(
|
|
803
|
-
|
|
804
|
-
|
|
950
|
+
guarded(
|
|
951
|
+
&db,
|
|
952
|
+
"tx-del",
|
|
953
|
+
&[expect_version("cells", "c", 2)],
|
|
954
|
+
&[delete("cells", "c")],
|
|
955
|
+
)
|
|
956
|
+
.expect("the current fence permits it");
|
|
957
|
+
assert!(
|
|
958
|
+
!present(&db, "cells", "c"),
|
|
959
|
+
"fence, then delete; there is no record left to version"
|
|
960
|
+
);
|
|
805
961
|
}
|
|
806
962
|
|
|
807
963
|
#[test]
|
|
@@ -812,17 +968,32 @@ mod tests {
|
|
|
812
968
|
guarded(
|
|
813
969
|
&db,
|
|
814
970
|
"tx-insert",
|
|
815
|
-
&[RecordPrecondition {
|
|
816
|
-
|
|
971
|
+
&[RecordPrecondition {
|
|
972
|
+
require_absent: true,
|
|
973
|
+
..precondition("cells", "new")
|
|
974
|
+
}],
|
|
975
|
+
&[set(
|
|
976
|
+
"cells",
|
|
977
|
+
"new",
|
|
978
|
+
json!({ "id": "new", "__version": 999, "state": "created" }),
|
|
979
|
+
)],
|
|
817
980
|
)
|
|
818
981
|
.expect("commits");
|
|
819
|
-
assert_eq!(
|
|
982
|
+
assert_eq!(
|
|
983
|
+
version_of(&db, "cells", "new"),
|
|
984
|
+
1,
|
|
985
|
+
"the authority owns the initial version"
|
|
986
|
+
);
|
|
820
987
|
|
|
821
988
|
guarded(
|
|
822
989
|
&db,
|
|
823
990
|
"tx-update-new",
|
|
824
991
|
&[expect_version("cells", "new", 1)],
|
|
825
|
-
&[set(
|
|
992
|
+
&[set(
|
|
993
|
+
"cells",
|
|
994
|
+
"new",
|
|
995
|
+
json!({ "id": "new", "state": "updated" }),
|
|
996
|
+
)],
|
|
826
997
|
)
|
|
827
998
|
.expect("the newly created version can be fenced immediately");
|
|
828
999
|
assert_eq!(version_of(&db, "cells", "new"), 2);
|
|
@@ -834,7 +1005,10 @@ mod tests {
|
|
|
834
1005
|
&[set("cells", "absent", json!({ "id": "absent" }))],
|
|
835
1006
|
);
|
|
836
1007
|
assert!(
|
|
837
|
-
matches!(
|
|
1008
|
+
matches!(
|
|
1009
|
+
failure(missing.unwrap_err()),
|
|
1010
|
+
PreconditionFailure::Missing { .. }
|
|
1011
|
+
),
|
|
838
1012
|
"a version-fenced write against an absent record is a conflict, not an insert"
|
|
839
1013
|
);
|
|
840
1014
|
}
|
|
@@ -843,7 +1017,10 @@ mod tests {
|
|
|
843
1017
|
fn replayed_conditional_create_does_not_create_or_advance_twice() {
|
|
844
1018
|
let dir = TempDir::new().unwrap();
|
|
845
1019
|
let db = db(&dir);
|
|
846
|
-
let absent = RecordPrecondition {
|
|
1020
|
+
let absent = RecordPrecondition {
|
|
1021
|
+
require_absent: true,
|
|
1022
|
+
..precondition("cells", "new")
|
|
1023
|
+
};
|
|
847
1024
|
|
|
848
1025
|
let first = guarded(
|
|
849
1026
|
&db,
|
|
@@ -872,8 +1049,13 @@ mod tests {
|
|
|
872
1049
|
let db = db(&dir);
|
|
873
1050
|
seed(&db, "cells", "c", 1, json!({}));
|
|
874
1051
|
|
|
875
|
-
guarded(
|
|
876
|
-
|
|
1052
|
+
guarded(
|
|
1053
|
+
&db,
|
|
1054
|
+
"tx-once",
|
|
1055
|
+
&[expect_version("cells", "c", 1)],
|
|
1056
|
+
&[set("cells", "c", json!({ "id": "c" }))],
|
|
1057
|
+
)
|
|
1058
|
+
.expect("commits");
|
|
877
1059
|
assert_eq!(version_of(&db, "cells", "c"), 2);
|
|
878
1060
|
|
|
879
1061
|
let replay = guarded(
|
|
@@ -884,7 +1066,11 @@ mod tests {
|
|
|
884
1066
|
)
|
|
885
1067
|
.expect("a committed id replays");
|
|
886
1068
|
assert!(replay.duplicate);
|
|
887
|
-
assert_eq!(
|
|
1069
|
+
assert_eq!(
|
|
1070
|
+
version_of(&db, "cells", "c"),
|
|
1071
|
+
2,
|
|
1072
|
+
"a replay cannot advance the version again"
|
|
1073
|
+
);
|
|
888
1074
|
}
|
|
889
1075
|
|
|
890
1076
|
#[test]
|
|
@@ -920,7 +1106,10 @@ mod tests {
|
|
|
920
1106
|
.map(|_| id)
|
|
921
1107
|
}));
|
|
922
1108
|
}
|
|
923
|
-
let outcomes: Vec<_> = handles
|
|
1109
|
+
let outcomes: Vec<_> = handles
|
|
1110
|
+
.into_iter()
|
|
1111
|
+
.map(|handle| handle.join().unwrap())
|
|
1112
|
+
.collect();
|
|
924
1113
|
assert_eq!(outcomes.iter().filter(|outcome| outcome.is_ok()).count(), 1);
|
|
925
1114
|
assert_eq!(
|
|
926
1115
|
version_of(&db, "cells", "race"),
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
use crate::convergence::VectorClock;
|
|
2
|
+
use serde::{Deserialize, Serialize};
|
|
1
3
|
/// Transaction system - Verified state transitions with atomicity guarantees
|
|
2
4
|
///
|
|
3
5
|
/// Implements the STATE_MODEL.md specification:
|
|
@@ -6,10 +8,7 @@
|
|
|
6
8
|
/// - Read/write dependency tracking
|
|
7
9
|
/// - Configurable consistency contracts
|
|
8
10
|
/// - Authoritative operation log with derived indexes
|
|
9
|
-
|
|
10
11
|
use std::collections::{HashMap, HashSet};
|
|
11
|
-
use crate::convergence::VectorClock;
|
|
12
|
-
use serde::{Deserialize, Serialize};
|
|
13
12
|
|
|
14
13
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
15
14
|
pub struct OperationId {
|
|
@@ -316,12 +315,7 @@ impl TransactionExecutor {
|
|
|
316
315
|
to_version.state_hash = format!("{:x}", hasher.finish());
|
|
317
316
|
}
|
|
318
317
|
|
|
319
|
-
StateTransition::new(
|
|
320
|
-
parent_version,
|
|
321
|
-
operations,
|
|
322
|
-
to_version,
|
|
323
|
-
consistency_contract,
|
|
324
|
-
)
|
|
318
|
+
StateTransition::new(parent_version, operations, to_version, consistency_contract)
|
|
325
319
|
}
|
|
326
320
|
|
|
327
321
|
pub fn mark_applied(&mut self, operation_id: &OperationId) {
|
|
@@ -357,8 +351,12 @@ impl CommitValidator {
|
|
|
357
351
|
))
|
|
358
352
|
}
|
|
359
353
|
}
|
|
360
|
-
TransitionResult::Aborted { ref reason } =>
|
|
361
|
-
|
|
354
|
+
TransitionResult::Aborted { ref reason } => {
|
|
355
|
+
Err(format!("Transaction aborted: {}", reason))
|
|
356
|
+
}
|
|
357
|
+
TransitionResult::Failed { ref reason } => {
|
|
358
|
+
Err(format!("Transaction failed: {}", reason))
|
|
359
|
+
}
|
|
362
360
|
}
|
|
363
361
|
}
|
|
364
362
|
|
|
@@ -376,10 +374,7 @@ impl CommitValidator {
|
|
|
376
374
|
for op in &transition.operations {
|
|
377
375
|
for other_op in &other.operations {
|
|
378
376
|
if op.operation_id == other_op.operation_id {
|
|
379
|
-
conflicts.push(format!(
|
|
380
|
-
"Duplicate operation: {:?}",
|
|
381
|
-
op.operation_id
|
|
382
|
-
));
|
|
377
|
+
conflicts.push(format!("Duplicate operation: {:?}", op.operation_id));
|
|
383
378
|
continue;
|
|
384
379
|
}
|
|
385
380
|
|
|
@@ -503,8 +498,12 @@ mod tests {
|
|
|
503
498
|
"system".to_string(),
|
|
504
499
|
);
|
|
505
500
|
|
|
506
|
-
let transition1 =
|
|
507
|
-
|
|
501
|
+
let transition1 = executor.execute_transaction(
|
|
502
|
+
"tx1".to_string(),
|
|
503
|
+
version.clone(),
|
|
504
|
+
vec![op.clone()],
|
|
505
|
+
ConsistencyContract::local(),
|
|
506
|
+
);
|
|
508
507
|
|
|
509
508
|
assert!(transition1.is_successful());
|
|
510
509
|
assert_eq!(transition1.operations.len(), 1);
|
|
@@ -589,9 +588,7 @@ mod tests {
|
|
|
589
588
|
|
|
590
589
|
let conflicts = CommitValidator::detect_conflicts(&transition1, &[transition2]);
|
|
591
590
|
assert!(!conflicts.is_empty());
|
|
592
|
-
assert!(conflicts
|
|
593
|
-
.iter()
|
|
594
|
-
.any(|c| c.contains("Write-write conflict")));
|
|
591
|
+
assert!(conflicts.iter().any(|c| c.contains("Write-write conflict")));
|
|
595
592
|
}
|
|
596
593
|
|
|
597
594
|
#[test]
|