@feltdb/core 0.15.0 → 0.16.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/agent-state.d.ts +20 -0
- package/dist/agent-state.d.ts.map +1 -1
- package/dist/agent-state.js +36 -0
- package/dist/build-identity.json +12 -4
- package/dist/cli/commands.js +187 -4
- package/dist/cli/publish-engine.js +116 -16
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +3 -0
- package/dist/create/build-identity.json +12 -4
- package/dist/create/managed-account.js +93 -35
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/Cargo.lock +6 -509
- package/dist/create/server-source/Cargo.toml +2 -13
- package/dist/create/server-source/crates/feltdb-client/Cargo.toml +1 -1
- package/dist/create/server-source/crates/feltdb-server/Cargo.toml +1 -1
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +255 -12
- package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +1 -1
- package/dist/db.d.ts +11 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +15 -2
- package/dist/error-codes.d.ts +5 -0
- package/dist/error-codes.d.ts.map +1 -1
- package/dist/error-codes.js +9 -1
- package/dist/http-db.d.ts +16 -0
- package/dist/http-db.d.ts.map +1 -1
- package/dist/http-db.js +36 -6
- package/dist/managed-recovery.d.ts +6 -0
- package/dist/managed-recovery.d.ts.map +1 -1
- package/dist/managed-recovery.js +9 -0
- package/dist/studio-app/assets/{feltdb_wasm-BjM1ylL3.js → feltdb_wasm-DtybUY0q.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-DsgIUwaB.wasm +0 -0
- package/dist/studio-app/assets/index-C4OffsZl.js +37 -0
- package/dist/studio-app/index.html +1 -1
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/dist/wasm/package.json +1 -1
- package/dist/workload.d.ts +55 -3
- package/dist/workload.d.ts.map +1 -1
- package/dist/workload.js +23 -0
- package/dist/workspace/workspace-connection.d.ts.map +1 -1
- package/dist/workspace/workspace-connection.js +14 -3
- package/package.json +1 -1
- package/dist/create/server-source/crates/feltdb/Cargo.lock +0 -175
- package/dist/create/server-source/crates/feltdb/Cargo.toml +0 -102
- package/dist/create/server-source/crates/feltdb/LICENSE +0 -21
- package/dist/create/server-source/crates/feltdb/README.md +0 -75
- package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +0 -370
- package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +0 -589
- package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +0 -247
- package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +0 -440
- package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +0 -275
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +0 -381
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +0 -467
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +0 -489
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +0 -281
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +0 -606
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +0 -531
- package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +0 -462
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +0 -505
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +0 -484
- package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +0 -758
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +0 -421
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +0 -329
- package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +0 -1705
- package/dist/create/server-source/crates/feltdb/src/acquisition.rs +0 -286
- package/dist/create/server-source/crates/feltdb/src/admission.rs +0 -185
- package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +0 -507
- package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +0 -539
- package/dist/create/server-source/crates/feltdb/src/agent_state.rs +0 -320
- package/dist/create/server-source/crates/feltdb/src/agent_transition_contract.rs +0 -1545
- package/dist/create/server-source/crates/feltdb/src/analytics.rs +0 -521
- package/dist/create/server-source/crates/feltdb/src/application.rs +0 -2674
- package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +0 -1070
- package/dist/create/server-source/crates/feltdb/src/authority.rs +0 -479
- package/dist/create/server-source/crates/feltdb/src/authority_failover.rs +0 -847
- package/dist/create/server-source/crates/feltdb/src/authorization.rs +0 -1030
- package/dist/create/server-source/crates/feltdb/src/authorization_security_tests.rs +0 -1122
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority-client.rs +0 -32
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority.rs +0 -19
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +0 -1474
- package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +0 -8
- package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +0 -418
- package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +0 -482
- package/dist/create/server-source/crates/feltdb/src/capability.rs +0 -903
- package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +0 -263
- package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +0 -77
- package/dist/create/server-source/crates/feltdb/src/causal_backlog_bound.rs +0 -496
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +0 -2574
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +0 -212
- package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +0 -422
- package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +0 -445
- package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +0 -465
- package/dist/create/server-source/crates/feltdb/src/convergence.rs +0 -634
- package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +0 -427
- package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +0 -376
- package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +0 -365
- package/dist/create/server-source/crates/feltdb/src/cron.rs +0 -294
- package/dist/create/server-source/crates/feltdb/src/dedup_bound_investigation.rs +0 -483
- package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +0 -477
- package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +0 -1003
- package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +0 -1334
- package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +0 -368
- package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +0 -231
- package/dist/create/server-source/crates/feltdb/src/durable_operation_identity.rs +0 -482
- package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +0 -203
- package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +0 -317
- package/dist/create/server-source/crates/feltdb/src/equality_index.rs +0 -595
- package/dist/create/server-source/crates/feltdb/src/execution.rs +0 -426
- package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +0 -214
- package/dist/create/server-source/crates/feltdb/src/indexing.rs +0 -776
- package/dist/create/server-source/crates/feltdb/src/lib.rs +0 -7365
- package/dist/create/server-source/crates/feltdb/src/managed_cas_tests.rs +0 -234
- package/dist/create/server-source/crates/feltdb/src/materialization.rs +0 -184
- package/dist/create/server-source/crates/feltdb/src/metrics.rs +0 -266
- package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +0 -310
- package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +0 -736
- package/dist/create/server-source/crates/feltdb/src/observability.rs +0 -379
- package/dist/create/server-source/crates/feltdb/src/operation.rs +0 -290
- package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +0 -439
- package/dist/create/server-source/crates/feltdb/src/operation_log.rs +0 -349
- package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +0 -332
- package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +0 -718
- package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +0 -487
- package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +0 -166
- package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +0 -289
- package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +0 -566
- package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +0 -3391
- package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +0 -203
- package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +0 -261
- package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +0 -502
- package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +0 -216
- package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +0 -303
- package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +0 -323
- package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +0 -531
- package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +0 -406
- package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +0 -436
- package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +0 -387
- package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +0 -378
- package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +0 -359
- package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +0 -372
- package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +0 -404
- package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +0 -376
- package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +0 -461
- package/dist/create/server-source/crates/feltdb/src/policy_evaluation.rs +0 -2125
- package/dist/create/server-source/crates/feltdb/src/production_api.rs +0 -418
- package/dist/create/server-source/crates/feltdb/src/provenance.rs +0 -207
- package/dist/create/server-source/crates/feltdb/src/query_execution_diagnostics.rs +0 -126
- package/dist/create/server-source/crates/feltdb/src/query_performance.rs +0 -218
- package/dist/create/server-source/crates/feltdb/src/read_basis.rs +0 -257
- package/dist/create/server-source/crates/feltdb/src/references.rs +0 -404
- package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +0 -401
- package/dist/create/server-source/crates/feltdb/src/replica_acknowledgements.rs +0 -501
- package/dist/create/server-source/crates/feltdb/src/replica_membership.rs +0 -680
- package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +0 -163
- package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +0 -133
- package/dist/create/server-source/crates/feltdb/src/routing.rs +0 -409
- package/dist/create/server-source/crates/feltdb/src/semantic_decision.rs +0 -2029
- package/dist/create/server-source/crates/feltdb/src/semantic_query.rs +0 -599
- package/dist/create/server-source/crates/feltdb/src/sharding.rs +0 -549
- package/dist/create/server-source/crates/feltdb/src/state_conflict_contract.rs +0 -516
- package/dist/create/server-source/crates/feltdb/src/state_contract.rs +0 -6003
- package/dist/create/server-source/crates/feltdb/src/state_diff_contract.rs +0 -222
- package/dist/create/server-source/crates/feltdb/src/state_facade.rs +0 -320
- package/dist/create/server-source/crates/feltdb/src/state_hash.rs +0 -291
- package/dist/create/server-source/crates/feltdb/src/state_model.rs +0 -4158
- package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +0 -379
- package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +0 -673
- package/dist/create/server-source/crates/feltdb/src/storage.rs +0 -273
- package/dist/create/server-source/crates/feltdb/src/submission.rs +0 -471
- package/dist/create/server-source/crates/feltdb/src/sync.rs +0 -495
- package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +0 -822
- package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +0 -433
- package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +0 -462
- package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +0 -965
- package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +0 -1120
- package/dist/create/server-source/crates/feltdb/src/transactions.rs +0 -643
- package/dist/create/server-source/crates/feltdb/src/trigger.rs +0 -390
- package/dist/create/server-source/crates/feltdb/src/trigger_contract.rs +0 -749
- package/dist/create/server-source/crates/feltdb/src/versioned_object_substrate.rs +0 -708
- package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +0 -1269
- package/dist/create/server-source/crates/feltdb/src/workflow.rs +0 -713
- package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +0 -510
- package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +0 -354
- package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +0 -594
- package/dist/create/server-source/crates/feltdb/src/workload.rs +0 -4222
- package/dist/create/server-source/crates/feltdb/src/workload_diagnostics.rs +0 -456
- package/dist/create/server-source/crates/feltdb/tests/agent_state_native.rs +0 -1188
- package/dist/create/server-source/crates/feltdb/tests/agent_transition_contract_conformance.rs +0 -2884
- package/dist/create/server-source/crates/feltdb/tests/authority_process.rs +0 -297
- package/dist/create/server-source/crates/feltdb/tests/authority_transaction_identity.rs +0 -534
- package/dist/create/server-source/crates/feltdb/tests/bounded_read_contract.rs +0 -132
- package/dist/create/server-source/crates/feltdb/tests/branching_evidence.rs +0 -299
- package/dist/create/server-source/crates/feltdb/tests/common/mod.rs +0 -96
- package/dist/create/server-source/crates/feltdb/tests/compaction_stall_contract.rs +0 -272
- package/dist/create/server-source/crates/feltdb/tests/crash_durability_contract.rs +0 -467
- package/dist/create/server-source/crates/feltdb/tests/current_revision_authority_evidence.rs +0 -309
- package/dist/create/server-source/crates/feltdb/tests/durable_backup_contract.rs +0 -445
- package/dist/create/server-source/crates/feltdb/tests/durable_corruption_contract.rs +0 -518
- package/dist/create/server-source/crates/feltdb/tests/durable_format_compatibility.rs +0 -392
- package/dist/create/server-source/crates/feltdb/tests/feltdb_state_boundary_tests.rs +0 -850
- package/dist/create/server-source/crates/feltdb/tests/fixtures/agent_transition_corpus.json +0 -23340
- package/dist/create/server-source/crates/feltdb/tests/fixtures/state_conflict_contract_corpus.json +0 -1916
- package/dist/create/server-source/crates/feltdb/tests/fixtures/state_diff_contract_corpus.json +0 -1878
- package/dist/create/server-source/crates/feltdb/tests/fixtures/trigger_contract_corpus.json +0 -1862
- package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_evidence.rs +0 -123
- package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_substrate_completion.rs +0 -299
- package/dist/create/server-source/crates/feltdb/tests/local_process_ownership.rs +0 -69
- package/dist/create/server-source/crates/feltdb/tests/managed_incident_regression.rs +0 -191
- package/dist/create/server-source/crates/feltdb/tests/operational_health_contract.rs +0 -278
- package/dist/create/server-source/crates/feltdb/tests/pr34_query_collection.rs +0 -234
- package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +0 -947
- package/dist/create/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +0 -404
- package/dist/create/server-source/crates/feltdb/tests/pr8_vocabulary_assessment.rs +0 -916
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase2_boundary_tests.rs +0 -1045
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3a_path_a_tests.rs +0 -347
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_authorized_mutations.rs +0 -361
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_role_based_authorization.rs +0 -330
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_simple_auth_delete.rs +0 -93
- package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_team_delete_role_authorization.rs +0 -622
- package/dist/create/server-source/crates/feltdb/tests/pr9_teams_role_based_access.rs +0 -517
- package/dist/create/server-source/crates/feltdb/tests/production_certification.rs +0 -1153
- package/dist/create/server-source/crates/feltdb/tests/production_contract.rs +0 -771
- package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +0 -1383
- package/dist/create/server-source/crates/feltdb/tests/reconciliation_application.rs +0 -868
- package/dist/create/server-source/crates/feltdb/tests/reconciliation_wire_format_evidence.rs +0 -221
- package/dist/create/server-source/crates/feltdb/tests/replicated_history_contract.rs +0 -417
- package/dist/create/server-source/crates/feltdb/tests/resource_scoped_revisions.rs +0 -338
- package/dist/create/server-source/crates/feltdb/tests/revision_identity_contract.rs +0 -1039
- package/dist/create/server-source/crates/feltdb/tests/revision_model_decision.rs +0 -739
- package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +0 -452
- package/dist/create/server-source/crates/feltdb/tests/saas_authorization_integration.rs +0 -81
- package/dist/create/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +0 -438
- package/dist/create/server-source/crates/feltdb/tests/semantic_decision.rs +0 -1165
- package/dist/create/server-source/crates/feltdb/tests/semantic_query.rs +0 -767
- package/dist/create/server-source/crates/feltdb/tests/semantic_query_contract.rs +0 -363
- package/dist/create/server-source/crates/feltdb/tests/state_conflict_contract_conformance.rs +0 -1799
- package/dist/create/server-source/crates/feltdb/tests/state_diff_contract_conformance.rs +0 -1316
- package/dist/create/server-source/crates/feltdb/tests/state_model_integration.rs +0 -358
- package/dist/create/server-source/crates/feltdb/tests/state_persistence_integration.rs +0 -365
- package/dist/create/server-source/crates/feltdb/tests/state_ref_authority.rs +0 -918
- package/dist/create/server-source/crates/feltdb/tests/state_store_boundary_evidence.rs +0 -299
- package/dist/create/server-source/crates/feltdb/tests/storage_independent_versioned_object_substrate.rs +0 -328
- package/dist/create/server-source/crates/feltdb/tests/sync_divergence_evidence.rs +0 -255
- package/dist/create/server-source/crates/feltdb/tests/three_way_input_boundary_evidence.rs +0 -249
- package/dist/create/server-source/crates/feltdb/tests/trigger_contract_conformance.rs +0 -994
- package/dist/create/server-source/crates/feltdb/tests/workload_envelope_contract.rs +0 -484
- package/dist/studio-app/assets/feltdb_wasm_bg-TseJn2Ax.wasm +0 -0
- package/dist/studio-app/assets/index-1wd0uKua.js +0 -30
package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_evidence.rs
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
//! Evidence for generalized version-control semantics over data representations.
|
|
2
|
-
//!
|
|
3
|
-
//! This test suite adds no new capability. It checks that the same revision
|
|
4
|
-
//! machinery behaves the same for source-shaped, artifact-shaped, and
|
|
5
|
-
//! application-shaped state.
|
|
6
|
-
|
|
7
|
-
use feltdb::{
|
|
8
|
-
reconcile, ConflictClass, ConflictClassification, ReconciliationPlan, Relationship, SemanticDiff,
|
|
9
|
-
StateStore, StateTopology,
|
|
10
|
-
};
|
|
11
|
-
use serde_json::{json, Value};
|
|
12
|
-
|
|
13
|
-
struct Scenario {
|
|
14
|
-
resource: &'static str,
|
|
15
|
-
base: Value,
|
|
16
|
-
left: Value,
|
|
17
|
-
right: Value,
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
#[derive(Debug, PartialEq, Eq)]
|
|
21
|
-
struct Outcome {
|
|
22
|
-
relationship: Relationship,
|
|
23
|
-
has_lca: bool,
|
|
24
|
-
left_changes: usize,
|
|
25
|
-
right_changes: usize,
|
|
26
|
-
overall: ConflictClass,
|
|
27
|
-
path_conflicts: usize,
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
fn run_scenario(scenario: Scenario) -> Outcome {
|
|
31
|
-
let store = StateStore::new_volatile();
|
|
32
|
-
let base = store
|
|
33
|
-
.create(scenario.resource, scenario.base.to_string(), "audit".to_string())
|
|
34
|
-
.expect("base");
|
|
35
|
-
let left = store
|
|
36
|
-
.commit(scenario.left.to_string(), &base, "audit".to_string())
|
|
37
|
-
.expect("left");
|
|
38
|
-
let right = store
|
|
39
|
-
.commit(scenario.right.to_string(), &base, "audit".to_string())
|
|
40
|
-
.expect("right");
|
|
41
|
-
|
|
42
|
-
let mut topology = StateTopology::new();
|
|
43
|
-
for revision in [&base, &left, &right] {
|
|
44
|
-
topology.add_revision(revision.clone());
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
let left_diff = SemanticDiff::compute(&scenario.base, &scenario.left);
|
|
48
|
-
let right_diff = SemanticDiff::compute(&scenario.base, &scenario.right);
|
|
49
|
-
let classification = ConflictClassification::classify(&base, &left, &right);
|
|
50
|
-
|
|
51
|
-
let plan = ReconciliationPlan::new(left.id.clone(), right.id.clone(), base.id.clone(), true);
|
|
52
|
-
let merged = reconcile(&base, &left, &right, &plan, "reconciler".to_string()).expect("merge");
|
|
53
|
-
assert_eq!(
|
|
54
|
-
merged.materialized_state.parent_id.as_ref(),
|
|
55
|
-
Some(&left.id),
|
|
56
|
-
"the selected parent is honored independent of representation"
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
Outcome {
|
|
60
|
-
relationship: topology.relationship(&left.id, &right.id),
|
|
61
|
-
has_lca: topology.common_ancestor(&left.id, &right.id).is_some(),
|
|
62
|
-
left_changes: left_diff.changes.len(),
|
|
63
|
-
right_changes: right_diff.changes.len(),
|
|
64
|
-
overall: classification.overall,
|
|
65
|
-
path_conflicts: classification.path_conflicts.len(),
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
#[test]
|
|
70
|
-
fn source_artifact_and_application_states_share_the_same_version_control_behavior() {
|
|
71
|
-
let source = run_scenario(Scenario {
|
|
72
|
-
resource: "source:src/main.rs",
|
|
73
|
-
base: json!({"body":"fn main() {}","digest":"h1"}),
|
|
74
|
-
left: json!({"body":"fn main() { println!(\"ok\"); }","digest":"h1"}),
|
|
75
|
-
right: json!({"body":"fn main() {}","digest":"h2"}),
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
let artifact = run_scenario(Scenario {
|
|
79
|
-
resource: "artifact:dist/app.wasm",
|
|
80
|
-
base: json!({"payload":"AA==","digest":"h1"}),
|
|
81
|
-
left: json!({"payload":"AQ==","digest":"h1"}),
|
|
82
|
-
right: json!({"payload":"AA==","digest":"h2"}),
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
let application = run_scenario(Scenario {
|
|
86
|
-
resource: "application:tasks/1",
|
|
87
|
-
base: json!({"state":"open","digest":"h1"}),
|
|
88
|
-
left: json!({"state":"closed","digest":"h1"}),
|
|
89
|
-
right: json!({"state":"open","digest":"h2"}),
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
let expected = Outcome {
|
|
93
|
-
relationship: Relationship::Diverged,
|
|
94
|
-
has_lca: true,
|
|
95
|
-
left_changes: 1,
|
|
96
|
-
right_changes: 1,
|
|
97
|
-
overall: ConflictClass::Independent,
|
|
98
|
-
path_conflicts: 2,
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
assert_eq!(source, expected);
|
|
102
|
-
assert_eq!(artifact, expected);
|
|
103
|
-
assert_eq!(application, expected);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
#[test]
|
|
107
|
-
fn mutating_resource_identity_changes_ids_not_version_control_semantics() {
|
|
108
|
-
let source_namespace = run_scenario(Scenario {
|
|
109
|
-
resource: "source:module/alpha",
|
|
110
|
-
base: json!({"value":1,"tag":"stable"}),
|
|
111
|
-
left: json!({"value":2,"tag":"stable"}),
|
|
112
|
-
right: json!({"value":1,"tag":"candidate"}),
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
let artifact_namespace = run_scenario(Scenario {
|
|
116
|
-
resource: "artifact:module/alpha",
|
|
117
|
-
base: json!({"value":1,"tag":"stable"}),
|
|
118
|
-
left: json!({"value":2,"tag":"stable"}),
|
|
119
|
-
right: json!({"value":1,"tag":"candidate"}),
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
assert_eq!(source_namespace, artifact_namespace);
|
|
123
|
-
}
|
|
@@ -1,299 +0,0 @@
|
|
|
1
|
-
use feltdb::{
|
|
2
|
-
MaterializationError, MaterializationProjection, PathComponent, RefError, StateId, StateStore,
|
|
3
|
-
};
|
|
4
|
-
use feltdb::state_model::GarbageCollectionReport;
|
|
5
|
-
use serde_json::json;
|
|
6
|
-
use std::collections::BTreeMap;
|
|
7
|
-
use std::sync::Arc;
|
|
8
|
-
use tempfile::TempDir;
|
|
9
|
-
|
|
10
|
-
fn durable_store(name: &str) -> (TempDir, StateStore) {
|
|
11
|
-
let directory = TempDir::new().unwrap();
|
|
12
|
-
let db = Arc::new(feltdb::FeltDb::open(directory.path().join(name)).unwrap());
|
|
13
|
-
let store = StateStore::with_feltdb(db).unwrap();
|
|
14
|
-
(directory, store)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
fn projection(paths: Vec<Vec<PathComponent>>) -> MaterializationProjection {
|
|
18
|
-
MaterializationProjection::select(paths)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
#[test]
|
|
22
|
-
fn refs_cas_enforcement_rejects_stale_moves() {
|
|
23
|
-
let store = StateStore::new_volatile();
|
|
24
|
-
let a = store
|
|
25
|
-
.create("docs:1", json!({"n": 1}).to_string(), "auth".into())
|
|
26
|
-
.unwrap();
|
|
27
|
-
let b = store
|
|
28
|
-
.commit(json!({"n": 2}).to_string(), &a, "auth".into())
|
|
29
|
-
.unwrap();
|
|
30
|
-
store
|
|
31
|
-
.create_ref("refs", "main", a.id.clone(), "auth".into(), BTreeMap::new())
|
|
32
|
-
.unwrap();
|
|
33
|
-
|
|
34
|
-
store
|
|
35
|
-
.compare_and_swap_ref("refs", "main", 1, b.id.clone(), "auth".into(), None)
|
|
36
|
-
.unwrap();
|
|
37
|
-
let stale = store.compare_and_swap_ref("refs", "main", 1, a.id.clone(), "auth".into(), None);
|
|
38
|
-
assert!(matches!(
|
|
39
|
-
stale,
|
|
40
|
-
Err(RefError::VersionConflict {
|
|
41
|
-
expected: 1,
|
|
42
|
-
current: 2
|
|
43
|
-
})
|
|
44
|
-
));
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
#[test]
|
|
48
|
-
fn refs_persist_target_and_versions_across_restart() {
|
|
49
|
-
let (directory, store) = durable_store("refs.log");
|
|
50
|
-
let a = store
|
|
51
|
-
.create("docs:1", json!({"n": 1}).to_string(), "auth".into())
|
|
52
|
-
.unwrap();
|
|
53
|
-
let b = store
|
|
54
|
-
.commit(json!({"n": 2}).to_string(), &a, "auth".into())
|
|
55
|
-
.unwrap();
|
|
56
|
-
|
|
57
|
-
store
|
|
58
|
-
.create_ref("refs", "main", a.id.clone(), "auth".into(), BTreeMap::new())
|
|
59
|
-
.unwrap();
|
|
60
|
-
let moved = store.move_ref("refs", "main", b.id.clone(), "auth2".into()).unwrap();
|
|
61
|
-
assert_eq!(moved.target, b.id);
|
|
62
|
-
assert_eq!(moved.version, 2);
|
|
63
|
-
|
|
64
|
-
let reopened = StateStore::with_feltdb(Arc::new(
|
|
65
|
-
feltdb::FeltDb::open(directory.path().join("refs.log")).unwrap(),
|
|
66
|
-
))
|
|
67
|
-
.unwrap();
|
|
68
|
-
let persisted = reopened.get_ref("refs", "main").unwrap();
|
|
69
|
-
assert_eq!(persisted.target, moved.target);
|
|
70
|
-
assert_eq!(persisted.version, moved.version);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
#[test]
|
|
74
|
-
fn refs_namespace_isolation_deletion_and_enumeration() {
|
|
75
|
-
let store = StateStore::new_volatile();
|
|
76
|
-
let revision = store
|
|
77
|
-
.create("docs:1", json!({"n": 1}).to_string(), "auth".into())
|
|
78
|
-
.unwrap();
|
|
79
|
-
|
|
80
|
-
store
|
|
81
|
-
.create_ref("refs", "main", revision.id.clone(), "auth".into(), BTreeMap::new())
|
|
82
|
-
.unwrap();
|
|
83
|
-
store
|
|
84
|
-
.create_ref("tags", "main", revision.id.clone(), "auth".into(), BTreeMap::new())
|
|
85
|
-
.unwrap();
|
|
86
|
-
|
|
87
|
-
let refs_all = store.list_refs(None);
|
|
88
|
-
assert_eq!(refs_all.len(), 2);
|
|
89
|
-
assert_eq!(store.list_refs(Some("refs")).len(), 1);
|
|
90
|
-
assert_eq!(store.list_refs(Some("tags")).len(), 1);
|
|
91
|
-
|
|
92
|
-
store.delete_ref("tags", "main", Some(1)).unwrap();
|
|
93
|
-
assert!(store.get_ref("tags", "main").is_none());
|
|
94
|
-
assert!(store.get_ref("refs", "main").is_some());
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
#[test]
|
|
98
|
-
fn materialization_projection_filters_revision_selection_and_nested_paths() {
|
|
99
|
-
let store = StateStore::new_volatile();
|
|
100
|
-
let base = store
|
|
101
|
-
.create(
|
|
102
|
-
"source:file",
|
|
103
|
-
json!({"tree":{"src":{"lib.rs":"v1","main.rs":"m1"}},"meta":{"lang":"rust"}})
|
|
104
|
-
.to_string(),
|
|
105
|
-
"auth".into(),
|
|
106
|
-
)
|
|
107
|
-
.unwrap();
|
|
108
|
-
let head = store
|
|
109
|
-
.commit(
|
|
110
|
-
json!({"tree":{"src":{"lib.rs":"v2","main.rs":"m1"}},"meta":{"lang":"rust"}})
|
|
111
|
-
.to_string(),
|
|
112
|
-
&base,
|
|
113
|
-
"auth".into(),
|
|
114
|
-
)
|
|
115
|
-
.unwrap();
|
|
116
|
-
|
|
117
|
-
let view = store
|
|
118
|
-
.materialize(
|
|
119
|
-
&head.id,
|
|
120
|
-
&projection(vec![
|
|
121
|
-
vec![PathComponent::Key("tree".into()), PathComponent::Key("src".into()), PathComponent::Key("lib.rs".into())],
|
|
122
|
-
]),
|
|
123
|
-
)
|
|
124
|
-
.unwrap();
|
|
125
|
-
assert_eq!(view, json!({"tree":{"src":{"lib.rs":"v2"}}}));
|
|
126
|
-
|
|
127
|
-
let base_view = store
|
|
128
|
-
.materialize(
|
|
129
|
-
&base.id,
|
|
130
|
-
&projection(vec![
|
|
131
|
-
vec![PathComponent::Key("tree".into()), PathComponent::Key("src".into()), PathComponent::Key("lib.rs".into())],
|
|
132
|
-
]),
|
|
133
|
-
)
|
|
134
|
-
.unwrap();
|
|
135
|
-
assert_eq!(base_view, json!({"tree":{"src":{"lib.rs":"v1"}}}));
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
#[test]
|
|
139
|
-
fn materialization_missing_paths_and_deterministic_output_are_enforced() {
|
|
140
|
-
let store = StateStore::new_volatile();
|
|
141
|
-
let revision = store
|
|
142
|
-
.create(
|
|
143
|
-
"artifact:item",
|
|
144
|
-
json!({"manifest":{"digest":"abc","size":7},"chunks":[{"id":1},{"id":2}]}).to_string(),
|
|
145
|
-
"auth".into(),
|
|
146
|
-
)
|
|
147
|
-
.unwrap();
|
|
148
|
-
|
|
149
|
-
let missing = store.materialize(
|
|
150
|
-
&revision.id,
|
|
151
|
-
&projection(vec![vec![
|
|
152
|
-
PathComponent::Key("manifest".into()),
|
|
153
|
-
PathComponent::Key("missing".into()),
|
|
154
|
-
]]),
|
|
155
|
-
);
|
|
156
|
-
assert!(matches!(missing, Err(MaterializationError::MissingPath(_))));
|
|
157
|
-
|
|
158
|
-
let first = store
|
|
159
|
-
.materialize(
|
|
160
|
-
&revision.id,
|
|
161
|
-
&projection(vec![
|
|
162
|
-
vec![PathComponent::Key("manifest".into()), PathComponent::Key("size".into())],
|
|
163
|
-
vec![PathComponent::Key("chunks".into()), PathComponent::Index(1)],
|
|
164
|
-
]),
|
|
165
|
-
)
|
|
166
|
-
.unwrap();
|
|
167
|
-
let second = store
|
|
168
|
-
.materialize(
|
|
169
|
-
&revision.id,
|
|
170
|
-
&projection(vec![
|
|
171
|
-
vec![PathComponent::Key("chunks".into()), PathComponent::Index(1)],
|
|
172
|
-
vec![PathComponent::Key("manifest".into()), PathComponent::Key("size".into())],
|
|
173
|
-
]),
|
|
174
|
-
)
|
|
175
|
-
.unwrap();
|
|
176
|
-
assert_eq!(first, second);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
fn setup_gc_graph(store: &StateStore) -> (StateId, StateId, StateId, StateId) {
|
|
180
|
-
let a = store
|
|
181
|
-
.create("graph:r", json!({"node":"A"}).to_string(), "auth".into())
|
|
182
|
-
.unwrap();
|
|
183
|
-
let b = store
|
|
184
|
-
.commit(json!({"node":"B"}).to_string(), &a, "auth".into())
|
|
185
|
-
.unwrap();
|
|
186
|
-
let c = store
|
|
187
|
-
.commit(json!({"node":"C"}).to_string(), &b, "auth".into())
|
|
188
|
-
.unwrap();
|
|
189
|
-
let d = store
|
|
190
|
-
.commit(json!({"node":"D"}).to_string(), &b, "auth".into())
|
|
191
|
-
.unwrap();
|
|
192
|
-
(a.id, b.id, c.id, d.id)
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
fn collect(store: &StateStore) -> GarbageCollectionReport {
|
|
196
|
-
store.collect_unreachable().unwrap()
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
#[test]
|
|
200
|
-
fn gc_root_discovery_and_unreachable_collection() {
|
|
201
|
-
let store = StateStore::new_volatile();
|
|
202
|
-
let (_a, _b, _c, _d) = setup_gc_graph(&store);
|
|
203
|
-
let report = collect(&store);
|
|
204
|
-
// Roots are refs, each resource's latest revisions, and anything younger
|
|
205
|
-
// than the grace period (adrs/state-ref-authority-and-safe-reclamation.md,
|
|
206
|
-
// option A). Before that decision this test asserted that a store with no
|
|
207
|
-
// refs collected all four revisions, which is the history loss the
|
|
208
|
-
// decision removes. Collection of a stale fork is covered, with an explicit
|
|
209
|
-
// clock, by `state_model::tests::gc_keeps_latest_revisions_refs_and_all_their_ancestors`.
|
|
210
|
-
assert_eq!(report.roots.len(), 0, "no refs");
|
|
211
|
-
assert_eq!(report.reachable_revisions.len(), 4);
|
|
212
|
-
assert_eq!(report.reachable_objects.len(), 4);
|
|
213
|
-
assert!(report.collected_revisions.is_empty());
|
|
214
|
-
assert!(report.collected_objects.is_empty());
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
#[test]
|
|
218
|
-
fn gc_parent_and_content_traversal_preserves_shared_history() {
|
|
219
|
-
let store = StateStore::new_volatile();
|
|
220
|
-
let (a, b, c, d) = setup_gc_graph(&store);
|
|
221
|
-
store
|
|
222
|
-
.create_ref("refs", "c", c.clone(), "auth".into(), BTreeMap::new())
|
|
223
|
-
.unwrap();
|
|
224
|
-
store
|
|
225
|
-
.create_ref("refs", "d", d.clone(), "auth".into(), BTreeMap::new())
|
|
226
|
-
.unwrap();
|
|
227
|
-
|
|
228
|
-
let keep_all = collect(&store);
|
|
229
|
-
assert!(keep_all.collected_revisions.is_empty());
|
|
230
|
-
|
|
231
|
-
// Shared history is reached through both refs.
|
|
232
|
-
for ancestor in [&a, &b] {
|
|
233
|
-
assert!(keep_all.reachable_revisions.contains(ancestor));
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Deleting d's ref no longer makes d collectable: d ties c at the highest
|
|
237
|
-
// sequence, so it is one of the resource's latest revisions, and it is
|
|
238
|
-
// younger than the grace period. (Before option A this asserted that d was
|
|
239
|
-
// collected.) Its ancestors are still reached, and nothing is lost.
|
|
240
|
-
store.delete_ref("refs", "d", Some(1)).unwrap();
|
|
241
|
-
let after_delete = collect(&store);
|
|
242
|
-
assert!(after_delete.collected_revisions.is_empty());
|
|
243
|
-
assert!(after_delete.collected_objects.is_empty());
|
|
244
|
-
for kept in [&a, &b, &c, &d] {
|
|
245
|
-
assert!(store.exists(kept));
|
|
246
|
-
assert!(after_delete.reachable_revisions.contains(kept));
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
#[test]
|
|
251
|
-
fn substrate_is_domain_neutral_for_refs_materialization_and_gc() {
|
|
252
|
-
let store = StateStore::new_volatile();
|
|
253
|
-
|
|
254
|
-
let scenarios = vec![
|
|
255
|
-
(
|
|
256
|
-
"source:module",
|
|
257
|
-
json!({"tree":{"src":{"lib.rs":"v1","main.rs":"v1"}}}),
|
|
258
|
-
vec![PathComponent::Key("tree".into()), PathComponent::Key("src".into()), PathComponent::Key("lib.rs".into())],
|
|
259
|
-
),
|
|
260
|
-
(
|
|
261
|
-
"artifact:bundle",
|
|
262
|
-
json!({"manifest":{"digest":"h1"},"payload":{"chunks":["a","b"]}}),
|
|
263
|
-
vec![PathComponent::Key("manifest".into()), PathComponent::Key("digest".into())],
|
|
264
|
-
),
|
|
265
|
-
(
|
|
266
|
-
"application:state",
|
|
267
|
-
json!({"entities":{"tasks":{"1":{"status":"open"}}},"meta":{"v":1}}),
|
|
268
|
-
vec![PathComponent::Key("entities".into()), PathComponent::Key("tasks".into()), PathComponent::Key("1".into()), PathComponent::Key("status".into())],
|
|
269
|
-
),
|
|
270
|
-
];
|
|
271
|
-
|
|
272
|
-
for (resource, content, path) in scenarios {
|
|
273
|
-
let rev = store
|
|
274
|
-
.create(resource, content.to_string(), "auth".into())
|
|
275
|
-
.unwrap();
|
|
276
|
-
store
|
|
277
|
-
.create_ref("heads", resource, rev.id.clone(), "auth".into(), BTreeMap::new())
|
|
278
|
-
.unwrap();
|
|
279
|
-
let view = store
|
|
280
|
-
.materialize(&rev.id, &projection(vec![path]))
|
|
281
|
-
.unwrap();
|
|
282
|
-
assert!(view.is_object());
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
let keep = collect(&store);
|
|
286
|
-
assert_eq!(keep.collected_revisions.len(), 0);
|
|
287
|
-
|
|
288
|
-
for reference in store.list_refs(Some("heads")) {
|
|
289
|
-
store
|
|
290
|
-
.delete_ref("heads", &reference.name, Some(reference.version))
|
|
291
|
-
.unwrap();
|
|
292
|
-
}
|
|
293
|
-
// Without their refs each resource's only revision is still its latest, so
|
|
294
|
-
// nothing is collected. (Before option A this asserted all three were.)
|
|
295
|
-
let collected = collect(&store);
|
|
296
|
-
assert!(collected.collected_revisions.is_empty());
|
|
297
|
-
assert!(collected.collected_objects.is_empty());
|
|
298
|
-
assert_eq!(collected.reachable_revisions.len(), 3);
|
|
299
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
use std::process::Command;
|
|
2
|
-
|
|
3
|
-
use feltdb::{AtomicMutation, FeltDb};
|
|
4
|
-
use serde_json::json;
|
|
5
|
-
use tempfile::TempDir;
|
|
6
|
-
|
|
7
|
-
const CHILD_ENV: &str = "FELTDB_OWNERSHIP_CHILD";
|
|
8
|
-
const PATH_ENV: &str = "FELTDB_OWNERSHIP_PATH";
|
|
9
|
-
const EXPECT_LOCKED_ENV: &str = "FELTDB_EXPECT_LOCKED";
|
|
10
|
-
|
|
11
|
-
#[test]
|
|
12
|
-
fn ownership_child() {
|
|
13
|
-
if std::env::var_os(CHILD_ENV).is_none() {
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
let path = std::env::var(PATH_ENV).unwrap();
|
|
17
|
-
let expect_locked = std::env::var_os(EXPECT_LOCKED_ENV).is_some();
|
|
18
|
-
match FeltDb::open(path) {
|
|
19
|
-
Ok(database) => {
|
|
20
|
-
assert!(!expect_locked, "a second owner opened the database");
|
|
21
|
-
let mutation = AtomicMutation {
|
|
22
|
-
capability: "probe".into(),
|
|
23
|
-
key: "probe:child".into(),
|
|
24
|
-
value: Some(json!({ "owner": "child" })),
|
|
25
|
-
};
|
|
26
|
-
database
|
|
27
|
-
.apply_atomic_transaction("child", None, &[], &[mutation], None)
|
|
28
|
-
.unwrap();
|
|
29
|
-
}
|
|
30
|
-
Err(error) => {
|
|
31
|
-
assert!(expect_locked, "an unowned database failed to open: {error}");
|
|
32
|
-
assert!(error.to_string().contains("already owned"));
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
fn child(path: &std::path::Path, expect_locked: bool) -> std::process::ExitStatus {
|
|
38
|
-
let mut command = Command::new(std::env::current_exe().unwrap());
|
|
39
|
-
command
|
|
40
|
-
.arg("--exact")
|
|
41
|
-
.arg("ownership_child")
|
|
42
|
-
.arg("--nocapture")
|
|
43
|
-
.env(CHILD_ENV, "1")
|
|
44
|
-
.env(PATH_ENV, path);
|
|
45
|
-
if expect_locked {
|
|
46
|
-
command.env(EXPECT_LOCKED_ENV, "1");
|
|
47
|
-
}
|
|
48
|
-
command.status().unwrap()
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
#[test]
|
|
52
|
-
fn another_process_cannot_read_write_or_transact_while_owned() {
|
|
53
|
-
let directory = TempDir::new().unwrap();
|
|
54
|
-
let path = directory.path().join("shared.db");
|
|
55
|
-
let _owner = FeltDb::open(&path).unwrap();
|
|
56
|
-
assert!(child(&path, true).success());
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
#[test]
|
|
60
|
-
fn ownership_is_released_when_the_process_exits() {
|
|
61
|
-
let directory = TempDir::new().unwrap();
|
|
62
|
-
let path = directory.path().join("shared.db");
|
|
63
|
-
assert!(child(&path, false).success());
|
|
64
|
-
let database = FeltDb::open(path).unwrap();
|
|
65
|
-
assert_eq!(
|
|
66
|
-
database.get::<serde_json::Value>("probe:child").unwrap(),
|
|
67
|
-
Some(json!({ "owner": "child" }))
|
|
68
|
-
);
|
|
69
|
-
}
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
//! The managed incident, composed.
|
|
2
|
-
//!
|
|
3
|
-
//! The four remediations each have their own contract: `compaction_stall_contract`
|
|
4
|
-
//! covers the deferred rewrite, `bounded_read_contract` covers scoped reads, and
|
|
5
|
-
//! the server crate's `admission_tests` cover the refusal. Every one of them
|
|
6
|
-
//! exercises its fix in isolation, on an idle database.
|
|
7
|
-
//!
|
|
8
|
-
//! The incident was not any one of them in isolation. It was their composition:
|
|
9
|
-
//! a populated collection, a timer firing compaction into the single lock that
|
|
10
|
-
//! serializes every read and write, and scoped requests arriving throughout.
|
|
11
|
-
//! A database that satisfies each contract separately can still fail the shape
|
|
12
|
-
//! that actually happened, so the shape gets its own test.
|
|
13
|
-
//!
|
|
14
|
-
//! # What this file does not establish
|
|
15
|
-
//!
|
|
16
|
-
//! **It does not measure latency, and it does not prove the incident cannot
|
|
17
|
-
//! recur.** No assertion here bounds how long a read waits, and every read
|
|
18
|
-
//! still queues behind the same mutex. That is claim `M6`, and it is recorded
|
|
19
|
-
//! in the certification matrix as `Unproven` precisely because a test like this
|
|
20
|
-
//! one is not evidence for it. A timing assertion added here would either be
|
|
21
|
-
//! flaky or be tuned until it passed, and both are worse than an honest gap.
|
|
22
|
-
//!
|
|
23
|
-
//! What it does establish is that under the composed load, the timer does not
|
|
24
|
-
//! rewrite, scoped reads stay bounded, and no read observes a torn or missing
|
|
25
|
-
//! database.
|
|
26
|
-
|
|
27
|
-
use feltdb::{CompactionOutcome, CompactionPolicy, FeltDb};
|
|
28
|
-
use serde_json::json;
|
|
29
|
-
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
|
30
|
-
use std::sync::Arc;
|
|
31
|
-
use tempfile::TempDir;
|
|
32
|
-
|
|
33
|
-
const ROWS: usize = 800;
|
|
34
|
-
|
|
35
|
-
/// A database shaped like the affected instance: one large collection, every
|
|
36
|
-
/// operation acknowledged so a compaction always has work to do.
|
|
37
|
-
fn incident_shaped() -> (TempDir, Arc<FeltDb>) {
|
|
38
|
-
let directory = TempDir::new().unwrap();
|
|
39
|
-
let db = Arc::new(FeltDb::open(directory.path().join("incident.log")).unwrap());
|
|
40
|
-
db.set_compaction_policy(CompactionPolicy::default());
|
|
41
|
-
|
|
42
|
-
for index in 0..ROWS {
|
|
43
|
-
db.insert(
|
|
44
|
-
&format!("docs:{index:05}"),
|
|
45
|
-
json!({ "n": index, "body": "x".repeat(200) }),
|
|
46
|
-
)
|
|
47
|
-
.unwrap();
|
|
48
|
-
}
|
|
49
|
-
let versions = db.operation_versions().unwrap();
|
|
50
|
-
db.acknowledge_peer_versions("peer-1".to_string(), versions)
|
|
51
|
-
.unwrap();
|
|
52
|
-
|
|
53
|
-
(directory, db)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/// **Scoped reads stay correct and bounded while the compaction timer runs.**
|
|
57
|
-
///
|
|
58
|
-
/// The compacting thread calls the same entry point the server's timer calls,
|
|
59
|
-
/// continuously, for the whole run. The reading thread does what the handlers
|
|
60
|
-
/// now do: a keyed lookup and a limited search, never a materializing scan.
|
|
61
|
-
///
|
|
62
|
-
/// Three properties are asserted, and each one is a way the incident could
|
|
63
|
-
/// return:
|
|
64
|
-
///
|
|
65
|
-
/// 1. every read returns the right answer — a compaction running concurrently
|
|
66
|
-
/// never exposes a partially pruned database;
|
|
67
|
-
/// 2. every bounded search visits exactly its limit — the amplifier is still
|
|
68
|
-
/// absent under load, not just on an idle database;
|
|
69
|
-
/// 3. the timer performs no durable rewrite — the expensive half stays deferred
|
|
70
|
-
/// when the policy says it should, and does not creep back in because
|
|
71
|
-
/// something else about the workload changed.
|
|
72
|
-
#[test]
|
|
73
|
-
fn scoped_reads_stay_correct_and_bounded_while_compaction_runs() {
|
|
74
|
-
let (_directory, db) = incident_shaped();
|
|
75
|
-
|
|
76
|
-
let stop = Arc::new(AtomicBool::new(false));
|
|
77
|
-
let rewrites = Arc::new(AtomicUsize::new(0));
|
|
78
|
-
let ticks = Arc::new(AtomicUsize::new(0));
|
|
79
|
-
|
|
80
|
-
let compactor = {
|
|
81
|
-
let db = db.clone();
|
|
82
|
-
let stop = stop.clone();
|
|
83
|
-
let rewrites = rewrites.clone();
|
|
84
|
-
let ticks = ticks.clone();
|
|
85
|
-
std::thread::spawn(move || {
|
|
86
|
-
while !stop.load(Ordering::Relaxed) {
|
|
87
|
-
let outcome = db
|
|
88
|
-
.maybe_compact_operation_log(&["peer-1".to_string()])
|
|
89
|
-
.expect("a timer-driven compaction does not fail");
|
|
90
|
-
ticks.fetch_add(1, Ordering::Relaxed);
|
|
91
|
-
if matches!(outcome, CompactionOutcome::Rewritten { .. }) {
|
|
92
|
-
rewrites.fetch_add(1, Ordering::Relaxed);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
})
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
for round in 0..200 {
|
|
99
|
-
let key = format!("docs:{:05}", round % ROWS);
|
|
100
|
-
let row = db
|
|
101
|
-
.get_collection_record("docs", &key)
|
|
102
|
-
.unwrap()
|
|
103
|
-
.unwrap_or_else(|| panic!("{key} is readable while compaction runs"));
|
|
104
|
-
assert_eq!(row.value["n"], json!(round % ROWS));
|
|
105
|
-
|
|
106
|
-
let visited = AtomicUsize::new(0);
|
|
107
|
-
let matched = db
|
|
108
|
-
.query_collection("docs", Some(10), |row| {
|
|
109
|
-
visited.fetch_add(1, Ordering::Relaxed);
|
|
110
|
-
row.value["body"].is_string()
|
|
111
|
-
})
|
|
112
|
-
.unwrap();
|
|
113
|
-
assert_eq!(matched.len(), 10, "the limit is honoured under load");
|
|
114
|
-
assert_eq!(
|
|
115
|
-
visited.load(Ordering::Relaxed),
|
|
116
|
-
10,
|
|
117
|
-
"and the search did not walk the collection"
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
stop.store(true, Ordering::Relaxed);
|
|
122
|
-
compactor.join().unwrap();
|
|
123
|
-
|
|
124
|
-
assert!(
|
|
125
|
-
ticks.load(Ordering::Relaxed) > 0,
|
|
126
|
-
"the compaction thread actually ran"
|
|
127
|
-
);
|
|
128
|
-
assert_eq!(
|
|
129
|
-
rewrites.load(Ordering::Relaxed),
|
|
130
|
-
0,
|
|
131
|
-
"the timer rewrote the durable log during the run; the deferred-rewrite \
|
|
132
|
-
fix does not hold under the composed workload"
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/// **The composed run leaves the database intact.**
|
|
137
|
-
///
|
|
138
|
-
/// The incident's worst plausible outcome was not slowness, it was a database
|
|
139
|
-
/// damaged by a rewrite interleaved with traffic. After the concurrent run the
|
|
140
|
-
/// full collection is still present, still correct, and still there after a
|
|
141
|
-
/// reopen — including once a rewrite is forced to happen.
|
|
142
|
-
#[test]
|
|
143
|
-
fn a_concurrent_compaction_leaves_the_database_intact() {
|
|
144
|
-
let directory = TempDir::new().unwrap();
|
|
145
|
-
let path = directory.path().join("intact.log");
|
|
146
|
-
let db = Arc::new(FeltDb::open(&path).unwrap());
|
|
147
|
-
db.set_compaction_policy(CompactionPolicy::default());
|
|
148
|
-
|
|
149
|
-
for index in 0..ROWS {
|
|
150
|
-
db.insert(&format!("docs:{index:05}"), json!({ "n": index }))
|
|
151
|
-
.unwrap();
|
|
152
|
-
}
|
|
153
|
-
let versions = db.operation_versions().unwrap();
|
|
154
|
-
db.acknowledge_peer_versions("peer-1".to_string(), versions)
|
|
155
|
-
.unwrap();
|
|
156
|
-
|
|
157
|
-
let stop = Arc::new(AtomicBool::new(false));
|
|
158
|
-
let compactor = {
|
|
159
|
-
let db = db.clone();
|
|
160
|
-
let stop = stop.clone();
|
|
161
|
-
std::thread::spawn(move || {
|
|
162
|
-
while !stop.load(Ordering::Relaxed) {
|
|
163
|
-
db.maybe_compact_operation_log(&["peer-1".to_string()])
|
|
164
|
-
.unwrap();
|
|
165
|
-
}
|
|
166
|
-
})
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
for index in ROWS..ROWS + 200 {
|
|
170
|
-
db.insert(&format!("docs:{index:05}"), json!({ "n": index }))
|
|
171
|
-
.unwrap();
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
stop.store(true, Ordering::Relaxed);
|
|
175
|
-
compactor.join().unwrap();
|
|
176
|
-
|
|
177
|
-
// An explicit compaction still rewrites — the deferral is a policy, not a
|
|
178
|
-
// disablement — and the rewrite happens over a database that concurrent
|
|
179
|
-
// traffic has just been mutating.
|
|
180
|
-
db.compact_operation_log(&["peer-1".to_string()]).unwrap();
|
|
181
|
-
drop(db);
|
|
182
|
-
|
|
183
|
-
let reopened = FeltDb::open(&path).unwrap();
|
|
184
|
-
for index in 0..ROWS + 200 {
|
|
185
|
-
let value: serde_json::Value = reopened
|
|
186
|
-
.get(&format!("docs:{index:05}"))
|
|
187
|
-
.unwrap()
|
|
188
|
-
.unwrap_or_else(|| panic!("docs:{index:05} survived the composed run"));
|
|
189
|
-
assert_eq!(value["n"], json!(index));
|
|
190
|
-
}
|
|
191
|
-
}
|