@feltdb/core 0.8.4 → 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/dist/create/package-versions.js +1 -1
  2. package/dist/create/server-source/Cargo.lock +165 -0
  3. package/dist/create/server-source/Cargo.toml +9 -0
  4. package/dist/create/server-source/crates/feltdb/Cargo.toml +3 -0
  5. package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +44 -44
  6. package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +12 -24
  7. package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +7 -13
  8. package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +13 -10
  9. package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +12 -9
  10. package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +78 -25
  11. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +16 -13
  12. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +13 -5
  13. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +41 -22
  14. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +33 -15
  15. package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +100 -33
  16. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +56 -20
  17. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +116 -41
  18. package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +186 -55
  19. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +64 -26
  20. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +46 -15
  21. package/dist/create/server-source/crates/feltdb/src/admission.rs +8 -15
  22. package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +43 -13
  23. package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +15 -42
  24. package/dist/create/server-source/crates/feltdb/src/analytics.rs +65 -19
  25. package/dist/create/server-source/crates/feltdb/src/application.rs +113 -30
  26. package/dist/create/server-source/crates/feltdb/src/authorization_security_tests.rs +475 -140
  27. package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +17 -15
  28. package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +0 -1
  29. package/dist/create/server-source/crates/feltdb/src/causal_backlog_bound.rs +59 -15
  30. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +266 -114
  31. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +25 -7
  32. package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +10 -15
  33. package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +3 -11
  34. package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +14 -5
  35. package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +21 -25
  36. package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +14 -11
  37. package/dist/create/server-source/crates/feltdb/src/dedup_bound_investigation.rs +103 -22
  38. package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +18 -15
  39. package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +12 -8
  40. package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +1 -5
  41. package/dist/create/server-source/crates/feltdb/src/durable_operation_identity.rs +87 -23
  42. package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +3 -7
  43. package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +10 -9
  44. package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +1 -6
  45. package/dist/create/server-source/crates/feltdb/src/indexing.rs +35 -38
  46. package/dist/create/server-source/crates/feltdb/src/lib.rs +1648 -46
  47. package/dist/create/server-source/crates/feltdb/src/managed_cas_tests.rs +4 -1
  48. package/dist/create/server-source/crates/feltdb/src/metrics.rs +0 -1
  49. package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +1 -2
  50. package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +107 -30
  51. package/dist/create/server-source/crates/feltdb/src/observability.rs +19 -6
  52. package/dist/create/server-source/crates/feltdb/src/operation.rs +39 -0
  53. package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +12 -11
  54. package/dist/create/server-source/crates/feltdb/src/operation_log.rs +9 -4
  55. package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +65 -18
  56. package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +193 -57
  57. package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +37 -27
  58. package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +38 -10
  59. package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +20 -14
  60. package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +394 -229
  61. package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +8 -6
  62. package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +11 -13
  63. package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +79 -70
  64. package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +3 -3
  65. package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +33 -11
  66. package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +6 -6
  67. package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +14 -56
  68. package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +29 -27
  69. package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +35 -17
  70. package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +8 -2
  71. package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +59 -15
  72. package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +70 -17
  73. package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +23 -6
  74. package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +12 -3
  75. package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +41 -11
  76. package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +56 -25
  77. package/dist/create/server-source/crates/feltdb/src/policy_evaluation.rs +701 -245
  78. package/dist/create/server-source/crates/feltdb/src/production_api.rs +31 -13
  79. package/dist/create/server-source/crates/feltdb/src/query_performance.rs +6 -8
  80. package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +5 -5
  81. package/dist/create/server-source/crates/feltdb/src/replica_acknowledgements.rs +48 -18
  82. package/dist/create/server-source/crates/feltdb/src/replica_membership.rs +30 -11
  83. package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +6 -3
  84. package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +4 -3
  85. package/dist/create/server-source/crates/feltdb/src/sharding.rs +36 -10
  86. package/dist/create/server-source/crates/feltdb/src/state_conflict_contract.rs +516 -0
  87. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +13 -4
  88. package/dist/create/server-source/crates/feltdb/src/state_diff_contract.rs +222 -0
  89. package/dist/create/server-source/crates/feltdb/src/state_facade.rs +82 -54
  90. package/dist/create/server-source/crates/feltdb/src/state_hash.rs +2 -2
  91. package/dist/create/server-source/crates/feltdb/src/state_model.rs +1565 -536
  92. package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +6 -3
  93. package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +672 -0
  94. package/dist/create/server-source/crates/feltdb/src/storage.rs +9 -3
  95. package/dist/create/server-source/crates/feltdb/src/submission.rs +5 -11
  96. package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +6 -8
  97. package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +24 -35
  98. package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +24 -8
  99. package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +248 -59
  100. package/dist/create/server-source/crates/feltdb/src/transactions.rs +17 -20
  101. package/dist/create/server-source/crates/feltdb/src/trigger_contract.rs +749 -0
  102. package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +1 -0
  103. package/dist/create/server-source/crates/feltdb/src/workload.rs +512 -4
  104. package/dist/create/server-source/crates/feltdb/src/workload_diagnostics.rs +3 -4
  105. package/dist/create/server-source/crates/feltdb/tests/bounded_read_contract.rs +132 -0
  106. package/dist/create/server-source/crates/feltdb/tests/branching_evidence.rs +299 -0
  107. package/dist/create/server-source/crates/feltdb/tests/compaction_stall_contract.rs +272 -0
  108. package/dist/create/server-source/crates/feltdb/tests/crash_durability_contract.rs +467 -0
  109. package/dist/create/server-source/crates/feltdb/tests/current_revision_authority_evidence.rs +309 -0
  110. package/dist/create/server-source/crates/feltdb/tests/durable_backup_contract.rs +445 -0
  111. package/dist/create/server-source/crates/feltdb/tests/durable_corruption_contract.rs +518 -0
  112. package/dist/create/server-source/crates/feltdb/tests/durable_format_compatibility.rs +392 -0
  113. package/dist/create/server-source/crates/feltdb/tests/feltdb_state_boundary_tests.rs +436 -220
  114. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_conflict_contract_corpus.json +1916 -0
  115. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_diff_contract_corpus.json +1878 -0
  116. package/dist/create/server-source/crates/feltdb/tests/fixtures/trigger_contract_corpus.json +1862 -0
  117. package/dist/create/server-source/crates/feltdb/tests/operational_health_contract.rs +278 -0
  118. package/dist/create/server-source/crates/feltdb/tests/pr34_query_collection.rs +2 -1
  119. package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +80 -25
  120. package/dist/create/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +5 -8
  121. package/dist/create/server-source/crates/feltdb/tests/pr8_vocabulary_assessment.rs +52 -44
  122. package/dist/create/server-source/crates/feltdb/tests/pr9_phase2_boundary_tests.rs +33 -16
  123. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3a_path_a_tests.rs +22 -7
  124. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_authorized_mutations.rs +41 -22
  125. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_role_based_authorization.rs +25 -8
  126. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_simple_auth_delete.rs +9 -6
  127. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_team_delete_role_authorization.rs +120 -69
  128. package/dist/create/server-source/crates/feltdb/tests/pr9_teams_role_based_access.rs +21 -10
  129. package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +1365 -0
  130. package/dist/create/server-source/crates/feltdb/tests/reconciliation_application.rs +868 -0
  131. package/dist/create/server-source/crates/feltdb/tests/reconciliation_wire_format_evidence.rs +221 -0
  132. package/dist/create/server-source/crates/feltdb/tests/replicated_history_contract.rs +417 -0
  133. package/dist/create/server-source/crates/feltdb/tests/resource_scoped_revisions.rs +338 -0
  134. package/dist/create/server-source/crates/feltdb/tests/revision_identity_contract.rs +1039 -0
  135. package/dist/create/server-source/crates/feltdb/tests/revision_model_decision.rs +739 -0
  136. package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +427 -0
  137. package/dist/create/server-source/crates/feltdb/tests/saas_authorization_integration.rs +3 -3
  138. package/dist/create/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +25 -22
  139. package/dist/create/server-source/crates/feltdb/tests/state_conflict_contract_conformance.rs +1799 -0
  140. package/dist/create/server-source/crates/feltdb/tests/state_diff_contract_conformance.rs +1316 -0
  141. package/dist/create/server-source/crates/feltdb/tests/state_model_integration.rs +53 -61
  142. package/dist/create/server-source/crates/feltdb/tests/state_persistence_integration.rs +156 -61
  143. package/dist/create/server-source/crates/feltdb/tests/state_store_boundary_evidence.rs +299 -0
  144. package/dist/create/server-source/crates/feltdb/tests/sync_divergence_evidence.rs +255 -0
  145. package/dist/create/server-source/crates/feltdb/tests/three_way_input_boundary_evidence.rs +249 -0
  146. package/dist/create/server-source/crates/feltdb/tests/trigger_contract_conformance.rs +994 -0
  147. package/dist/create/server-source/crates/feltdb/tests/workload_envelope_contract.rs +442 -0
  148. package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +16 -1
  149. package/dist/create/server-source/crates/feltdb-server/src/auth.rs +164 -13
  150. package/dist/create/server-source/crates/feltdb-server/src/main.rs +695 -47
  151. package/dist/create/server-source/crates/feltdb-server/src/metrics.rs +21 -0
  152. package/dist/studio-app/assets/{feltdb_wasm-CVQWgXO-.js → feltdb_wasm-C1VhI-U5.js} +1 -1
  153. package/dist/studio-app/assets/feltdb_wasm_bg-C8HXbAXb.wasm +0 -0
  154. package/dist/studio-app/assets/{index-DwgNAIIX.js → index-Bbos1m2U.js} +1 -1
  155. package/dist/studio-app/index.html +1 -1
  156. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  157. package/dist/workload.d.ts +2 -1
  158. package/dist/workload.d.ts.map +1 -1
  159. package/package.json +1 -1
  160. package/dist/studio-app/assets/feltdb_wasm_bg-CNVpvaZV.wasm +0 -0
@@ -0,0 +1,278 @@
1
+ //! Operational health.
2
+ //!
3
+ //! Everything from the format work onward proved what FeltDB *does*. This file
4
+ //! asks a different question:
5
+ //!
6
+ //! > **Can an operator reliably tell what FeltDB knows about itself?**
7
+ //!
8
+ //! The rule the whole surface is built on:
9
+ //!
10
+ //! > **Health can expose a proven fact. It cannot create a stronger
11
+ //! > guarantee.**
12
+ //!
13
+ //! So `DatabaseHealth` reports the durable format an open accepted and whether
14
+ //! replay discarded an incomplete final append, and nothing else. There is no
15
+ //! replication health, no backup freshness and no durability claim, because
16
+ //! none of those has an operational contract yet and inventing one here would
17
+ //! reintroduce exactly the defect this replaces: a reassuring label that
18
+ //! observes nothing.
19
+ //!
20
+ //! See `docs/architecture/operational-health.md`.
21
+
22
+ use feltdb::{
23
+ inspect_durable_format, DatabaseHealth, DurableFormat, FeltDb, FlowError, LogRecovery,
24
+ StorageHealth, DURABLE_FORMAT_VERSION,
25
+ };
26
+ use serde_json::{json, Value};
27
+ use std::io::Write;
28
+ use std::path::Path;
29
+ use tempfile::TempDir;
30
+
31
+ fn three_writes(path: &Path) -> Vec<u8> {
32
+ let db = FeltDb::open(path).unwrap();
33
+ for n in 0..3 {
34
+ db.insert(&format!("tasks:{n}"), json!({ "n": n })).unwrap();
35
+ }
36
+ drop(db);
37
+ std::fs::read(path).unwrap()
38
+ }
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // The five questions health has to answer
42
+ // ---------------------------------------------------------------------------
43
+
44
+ /// **A clean startup reports clean.**
45
+ #[test]
46
+ fn a_clean_open_reports_clean_storage() {
47
+ let directory = TempDir::new().unwrap();
48
+ let path = directory.path().join("clean.log");
49
+ three_writes(&path);
50
+
51
+ let db = FeltDb::open(&path).unwrap();
52
+ let health = db.health();
53
+
54
+ assert_eq!(health.storage, StorageHealth::Clean);
55
+ assert_eq!(health.recovery, LogRecovery::Clean);
56
+ assert_eq!(
57
+ health.durable_format,
58
+ DurableFormat::Versioned(DURABLE_FORMAT_VERSION)
59
+ );
60
+ assert!(health.is_nominal());
61
+ assert_eq!(health.storage.label(), "clean");
62
+ }
63
+
64
+ /// **The regression that matters.** A database that discarded a torn tail must
65
+ /// not report the same health as one that replayed cleanly.
66
+ ///
67
+ /// This is the operational consequence of the corruption work: the distinction
68
+ /// between a clean open and a recovered one was established there, and it is
69
+ /// worth nothing to an operator unless it reaches them.
70
+ #[test]
71
+ fn a_recovered_open_does_not_report_the_same_health_as_a_clean_one() {
72
+ let directory = TempDir::new().unwrap();
73
+ let clean_path = directory.path().join("clean.log");
74
+ let torn_path = directory.path().join("torn.log");
75
+ let full = three_writes(&clean_path);
76
+
77
+ // The same database, with its final append cut short.
78
+ std::fs::write(&torn_path, &full[..full.len() - 30]).unwrap();
79
+
80
+ let clean = FeltDb::open(&clean_path).unwrap().health();
81
+ let torn = FeltDb::open(&torn_path).unwrap().health();
82
+
83
+ assert_ne!(
84
+ clean, torn,
85
+ "a database that lost its last write must not look identical to one that did not"
86
+ );
87
+ assert!(clean.is_nominal());
88
+ assert!(!torn.is_nominal(), "recovered is not nominal");
89
+ assert_ne!(clean.storage.label(), torn.storage.label());
90
+
91
+ match torn.storage {
92
+ StorageHealth::RecoveredIncompleteWrite {
93
+ byte_offset,
94
+ discarded_bytes,
95
+ } => {
96
+ assert!(byte_offset > 0, "the operator is told where");
97
+ assert!(discarded_bytes > 0, "and how much");
98
+ }
99
+ other => panic!("expected a reported recovery, got {other:?}"),
100
+ }
101
+
102
+ // And it says so in words an operator can act on.
103
+ let described = torn.storage.to_string();
104
+ assert!(described.contains("incomplete append"));
105
+ assert!(described.contains("last write did not land"));
106
+ }
107
+
108
+ /// **A corrupt database does not become healthy.** It does not open at all.
109
+ ///
110
+ /// The ambiguity being removed here is "healthy process, unhealthy database":
111
+ /// there is no state in which a server is serving a database whose durable log
112
+ /// failed to replay.
113
+ #[test]
114
+ fn a_corrupt_database_never_reaches_a_health_report() {
115
+ let directory = TempDir::new().unwrap();
116
+ let path = directory.path().join("corrupt.log");
117
+ three_writes(&path);
118
+
119
+ let mut file = std::fs::OpenOptions::new()
120
+ .append(true)
121
+ .open(&path)
122
+ .unwrap();
123
+ writeln!(file, "this is not json at all").unwrap();
124
+ drop(file);
125
+
126
+ match FeltDb::open(&path) {
127
+ Err(FlowError::CorruptLogLine(_)) => {}
128
+ Err(other) => panic!("wrong error: {other}"),
129
+ Ok(_) => panic!("a corrupt database opened and could have reported health"),
130
+ }
131
+ }
132
+
133
+ /// **An incompatible database stays refused**, so a running server cannot be
134
+ /// advertising one.
135
+ #[test]
136
+ fn an_incompatible_database_never_reaches_a_health_report() {
137
+ let directory = TempDir::new().unwrap();
138
+ let path = directory.path().join("future.log");
139
+ std::fs::write(
140
+ &path,
141
+ format!(
142
+ "{}\n",
143
+ json!({
144
+ "record_type": "feltdb.format.v1",
145
+ "format_version": DURABLE_FORMAT_VERSION + 1
146
+ })
147
+ ),
148
+ )
149
+ .unwrap();
150
+
151
+ assert!(!inspect_durable_format(&path).unwrap().is_compatible());
152
+ assert!(matches!(
153
+ FeltDb::open(&path),
154
+ Err(FlowError::IncompatibleFormat(_))
155
+ ));
156
+ }
157
+
158
+ /// Health survives the thing it describes: reopening a repaired log reports
159
+ /// clean again, because the condition is genuinely gone.
160
+ ///
161
+ /// A status that stayed degraded forever would be as useless as one that never
162
+ /// was.
163
+ #[test]
164
+ fn health_reflects_the_current_open_not_the_database_s_past() {
165
+ let directory = TempDir::new().unwrap();
166
+ let path = directory.path().join("repaired.log");
167
+ let full = three_writes(&path);
168
+ std::fs::write(&path, &full[..full.len() - 30]).unwrap();
169
+
170
+ let recovered = FeltDb::open(&path).unwrap();
171
+ assert!(!recovered.health().is_nominal());
172
+ drop(recovered);
173
+
174
+ let reopened = FeltDb::open(&path).unwrap();
175
+ assert!(
176
+ reopened.health().is_nominal(),
177
+ "the incomplete append is gone, so the condition is too"
178
+ );
179
+ }
180
+
181
+ /// A database with no torn tail but written before format versioning is
182
+ /// reported as what it is, rather than as an error or as current.
183
+ #[test]
184
+ fn an_unversioned_database_reports_its_actual_format() {
185
+ let directory = TempDir::new().unwrap();
186
+ let path = directory.path().join("unversioned.log");
187
+ std::fs::write(
188
+ &path,
189
+ format!(
190
+ "{}\n",
191
+ json!({
192
+ "capability": "tasks", "key": "tasks:1",
193
+ "rust_type": "serde_json::value::Value", "value": {"n": 1},
194
+ "unix_ms": 1, "content_hash": null, "flow_ref": null,
195
+ "deleted": false, "operation": null
196
+ })
197
+ ),
198
+ )
199
+ .unwrap();
200
+
201
+ let health = FeltDb::open(&path).unwrap().health();
202
+ assert_eq!(health.durable_format, DurableFormat::Unversioned);
203
+ assert!(health.is_nominal(), "unversioned is not unhealthy");
204
+ }
205
+
206
+ // ---------------------------------------------------------------------------
207
+ // What health deliberately does not say
208
+ // ---------------------------------------------------------------------------
209
+
210
+ /// **Health does not manufacture a guarantee the database does not have.**
211
+ ///
212
+ /// The defect being replaced was `storage: "durable"` — a string constant that
213
+ /// inspected nothing and asserted the one property still unproven. The
214
+ /// replacement is a small closed set of observed conditions, and every label in
215
+ /// it names what was seen rather than what would be reassuring.
216
+ ///
217
+ /// In particular nothing here claims durability against power loss, which
218
+ /// remains unproven; nothing claims the database is replicated, backed up, or
219
+ /// within any capacity; and there is no "probably fine".
220
+ #[test]
221
+ fn health_reports_only_conditions_that_were_observed() {
222
+ let directory = TempDir::new().unwrap();
223
+ let clean = directory.path().join("a.log");
224
+ let torn = directory.path().join("b.log");
225
+ let full = three_writes(&clean);
226
+ std::fs::write(&torn, &full[..full.len() - 30]).unwrap();
227
+
228
+ let labels: Vec<&str> = [&clean, &torn]
229
+ .iter()
230
+ .map(|path| FeltDb::open(path).unwrap().health().storage.label())
231
+ .collect();
232
+
233
+ // Exactly two conditions, both naming an observation.
234
+ assert_eq!(labels, vec!["clean", "recovered-incomplete-write"]);
235
+ for label in &labels {
236
+ assert!(
237
+ !label.contains("durable") && !label.contains("healthy"),
238
+ "'{label}' would assert more than was observed"
239
+ );
240
+ }
241
+ }
242
+
243
+ /// The health value is derived from the recovery the open recorded, not from
244
+ /// anything a caller can set.
245
+ #[test]
246
+ fn health_is_derived_from_the_open_rather_than_configured() {
247
+ let directory = TempDir::new().unwrap();
248
+ let path = directory.path().join("derived.log");
249
+ let full = three_writes(&path);
250
+ std::fs::write(&path, &full[..full.len() - 30]).unwrap();
251
+
252
+ let db = FeltDb::open(&path).unwrap();
253
+ let health: DatabaseHealth = db.health();
254
+
255
+ // The two agree because one is computed from the other.
256
+ match (&health.recovery, &health.storage) {
257
+ (
258
+ LogRecovery::RecoveredTornTail {
259
+ byte_offset: recovered_at,
260
+ discarded_bytes: recovered_bytes,
261
+ },
262
+ StorageHealth::RecoveredIncompleteWrite {
263
+ byte_offset,
264
+ discarded_bytes,
265
+ },
266
+ ) => {
267
+ assert_eq!(recovered_at, byte_offset);
268
+ assert_eq!(recovered_bytes, discarded_bytes);
269
+ }
270
+ other => panic!("recovery and storage disagree: {other:?}"),
271
+ }
272
+
273
+ // Writing more does not change what the open found.
274
+ db.insert("tasks:9", json!({"n": 9})).unwrap();
275
+ assert_eq!(db.health(), health);
276
+ let value: Value = db.get("tasks:9").unwrap().unwrap();
277
+ assert_eq!(value["n"], json!(9), "and the database is fully usable");
278
+ }
@@ -166,7 +166,8 @@ fn a_scan_result_survives_concurrent_mutation() {
166
166
  // The scan returns owned records, so mutation after the lock is released
167
167
  // cannot invalidate or rewrite what the caller already holds.
168
168
  for index in 0..200 {
169
- db.delete(&format!("items:record-{index:05}")).expect("delete");
169
+ db.delete(&format!("items:record-{index:05}"))
170
+ .expect("delete");
170
171
  }
171
172
 
172
173
  assert_eq!(matches.len(), 50);
@@ -41,7 +41,8 @@ fn database() -> (TempDir, FeltDb) {
41
41
  /// A database with `status` and `tenantId` indexed on `items`.
42
42
  fn indexed() -> (TempDir, FeltDb) {
43
43
  let (directory, db) = database();
44
- db.create_equality_index("items", "status").expect("declare");
44
+ db.create_equality_index("items", "status")
45
+ .expect("declare");
45
46
  db.create_equality_index("items", "tenantId")
46
47
  .expect("declare");
47
48
  (directory, db)
@@ -53,7 +54,9 @@ fn indexed() -> (TempDir, FeltDb) {
53
54
  fn assert_index_matches_state(db: &FeltDb, after: &str) {
54
55
  match db.verify_equality_index().expect("verify") {
55
56
  Ok(()) => {}
56
- Err(divergence) => panic!("index diverged from authoritative state after {after}: {divergence}"),
57
+ Err(divergence) => {
58
+ panic!("index diverged from authoritative state after {after}: {divergence}")
59
+ }
57
60
  }
58
61
  }
59
62
 
@@ -67,7 +70,11 @@ fn ids(db: &FeltDb, field: &str, value: Value) -> Vec<String> {
67
70
  candidates(db, field, value)
68
71
  .expect("the field is indexed and the value is indexable")
69
72
  .into_iter()
70
- .map(|key| key.split_once(':').map(|(_, id)| id.to_string()).unwrap_or(key))
73
+ .map(|key| {
74
+ key.split_once(':')
75
+ .map(|(_, id)| id.to_string())
76
+ .unwrap_or(key)
77
+ })
71
78
  .collect()
72
79
  }
73
80
 
@@ -155,7 +162,11 @@ fn an_update_that_leaves_the_indexed_field_alone_leaves_the_index_alone() {
155
162
  db.update("items:a", &json!({ "status": "active", "note": "after" }))
156
163
  .expect("update");
157
164
 
158
- assert_eq!(snapshot(&db), before, "an unrelated field change is invisible to the index");
165
+ assert_eq!(
166
+ snapshot(&db),
167
+ before,
168
+ "an unrelated field change is invisible to the index"
169
+ );
159
170
  assert_index_matches_state(&db, "an unrelated field changing");
160
171
  }
161
172
 
@@ -260,7 +271,11 @@ fn unindexable_values_are_excluded_rather_than_coerced() {
260
271
  put(&db, "object", json!({ "status": { "state": "active" } }));
261
272
  put(&db, "array", json!({ "status": ["active"] }));
262
273
  put(&db, "scalar", json!({ "status": "active" }));
263
- put(&db, "not-an-object", json!("items are usually objects, but need not be"));
274
+ put(
275
+ &db,
276
+ "not-an-object",
277
+ json!("items are usually objects, but need not be"),
278
+ );
264
279
 
265
280
  assert_eq!(
266
281
  ids(&db, "status", json!("active")),
@@ -351,8 +366,16 @@ fn conditional_creation_maintains_the_index_when_it_wins_and_not_when_it_loses()
351
366
  key: "items:a".into(),
352
367
  value: Some(json!({ "status": "active" })),
353
368
  };
354
- db.apply_atomic_transaction_guarded("pr35-put-1", None, None, &[], &[absent.clone()], &[write.clone()], None)
355
- .expect("the record is absent, so the creation wins");
369
+ db.apply_atomic_transaction_guarded(
370
+ "pr35-put-1",
371
+ None,
372
+ None,
373
+ &[],
374
+ &[absent.clone()],
375
+ &[write.clone()],
376
+ None,
377
+ )
378
+ .expect("the record is absent, so the creation wins");
356
379
  assert_eq!(ids(&db, "status", json!("active")), vec!["a".to_string()]);
357
380
  assert_index_matches_state(&db, "a conditional creation that won");
358
381
 
@@ -520,14 +543,17 @@ fn declaring_an_index_populates_it_from_state_that_already_exists() {
520
543
  None,
521
544
  "before declaration there is no index to consult",
522
545
  );
523
- assert!(db.create_equality_index("items", "status").expect("declare"));
546
+ assert!(db
547
+ .create_equality_index("items", "status")
548
+ .expect("declare"));
524
549
  assert_eq!(
525
550
  ids(&db, "status", json!("active")),
526
551
  vec!["a".to_string()],
527
552
  "declaration derives the index from the records already held",
528
553
  );
529
554
  assert!(
530
- !db.create_equality_index("items", "status").expect("declare"),
555
+ !db.create_equality_index("items", "status")
556
+ .expect("declare"),
531
557
  "re-declaring an existing index reports that it created nothing",
532
558
  );
533
559
  assert_index_matches_state(&db, "declaring an index over existing state");
@@ -548,8 +574,11 @@ fn an_explicit_rebuild_reproduces_the_maintained_index_exactly() {
548
574
  );
549
575
  }
550
576
  db.delete("items:record-000").expect("delete");
551
- db.update("items:record-001", &json!({ "status": "active", "tenantId": "tenant-9" }))
552
- .expect("update");
577
+ db.update(
578
+ "items:record-001",
579
+ &json!({ "status": "active", "tenantId": "tenant-9" }),
580
+ )
581
+ .expect("update");
553
582
 
554
583
  let maintained = snapshot(&db);
555
584
  db.rebuild_equality_indexes().expect("rebuild");
@@ -567,8 +596,10 @@ fn reopening_the_database_rebuilds_the_index_from_durable_state() {
567
596
 
568
597
  let before = {
569
598
  let db = FeltDb::open(&path).expect("open");
570
- db.create_equality_index("items", "status").expect("declare");
571
- db.create_equality_index("items", "tenantId").expect("declare");
599
+ db.create_equality_index("items", "status")
600
+ .expect("declare");
601
+ db.create_equality_index("items", "tenantId")
602
+ .expect("declare");
572
603
  for step in 0..48 {
573
604
  db.insert(
574
605
  &format!("items:record-{step:03}"),
@@ -577,8 +608,11 @@ fn reopening_the_database_rebuilds_the_index_from_durable_state() {
577
608
  .expect("insert");
578
609
  }
579
610
  db.delete("items:record-005").expect("delete");
580
- db.update("items:record-006", &json!({ "status": "active", "tenantId": "tenant-0" }))
581
- .expect("update");
611
+ db.update(
612
+ "items:record-006",
613
+ &json!({ "status": "active", "tenantId": "tenant-0" }),
614
+ )
615
+ .expect("update");
582
616
  db.apply_atomic_transaction_guarded(
583
617
  "pr35-restart-tx",
584
618
  None,
@@ -587,7 +621,10 @@ fn reopening_the_database_rebuilds_the_index_from_durable_state() {
587
621
  &[],
588
622
  &[
589
623
  mutation("items:record-007", None),
590
- mutation("items:record-100", Some(json!({ "status": "active", "tenantId": "tenant-1" }))),
624
+ mutation(
625
+ "items:record-100",
626
+ Some(json!({ "status": "active", "tenantId": "tenant-1" })),
627
+ ),
591
628
  ],
592
629
  None,
593
630
  )
@@ -600,8 +637,12 @@ fn reopening_the_database_rebuilds_the_index_from_durable_state() {
600
637
  reopened.equality_indexes().expect("indexes").is_empty(),
601
638
  "an index is derived state: nothing about it is read back from the log",
602
639
  );
603
- reopened.create_equality_index("items", "status").expect("declare");
604
- reopened.create_equality_index("items", "tenantId").expect("declare");
640
+ reopened
641
+ .create_equality_index("items", "status")
642
+ .expect("declare");
643
+ reopened
644
+ .create_equality_index("items", "tenantId")
645
+ .expect("declare");
605
646
 
606
647
  assert_eq!(
607
648
  snapshot(&reopened),
@@ -699,8 +740,11 @@ fn indexed_execution_returns_exactly_what_the_scan_returns() {
699
740
 
700
741
  // The conjunction actually narrows rather than merely re-filtering: the
701
742
  // intersection is smaller than either side.
702
- let both = indexed_query(&db, &[("status", json!("active")), ("tenantId", json!("tenant-1"))])
703
- .expect("both fields are indexed");
743
+ let both = indexed_query(
744
+ &db,
745
+ &[("status", json!("active")), ("tenantId", json!("tenant-1"))],
746
+ )
747
+ .expect("both fields are indexed");
704
748
  let one = indexed_query(&db, &[("status", json!("active"))]).expect("indexed");
705
749
  assert!(!both.is_empty() && both.len() < one.len());
706
750
  }
@@ -718,8 +762,11 @@ fn the_index_selects_candidates_and_the_predicate_still_decides() {
718
762
  "the index answers with candidates, both of which satisfy only the indexed condition",
719
763
  );
720
764
 
721
- let result = indexed_query(&db, &[("status", json!("active")), ("role", json!("admin"))])
722
- .expect("indexed");
765
+ let result = indexed_query(
766
+ &db,
767
+ &[("status", json!("active")), ("role", json!("admin"))],
768
+ )
769
+ .expect("indexed");
723
770
  assert_eq!(
724
771
  result,
725
772
  vec!["items:a".to_string()],
@@ -834,14 +881,19 @@ fn concurrent_mutation_and_indexed_query_stay_coherent() {
834
881
  // mean a partially committed transaction became queryable.
835
882
  for (sibling, count) in halves {
836
883
  if count != 2 {
837
- failures.push(format!("transaction pair {sibling} was observed {count} times, not 2"));
884
+ failures.push(format!(
885
+ "transaction pair {sibling} was observed {count} times, not 2"
886
+ ));
838
887
  }
839
888
  }
840
889
  observations += 1;
841
890
  }
842
891
 
843
892
  running.store(false, Ordering::Relaxed);
844
- let cycles: usize = writers.into_iter().map(|handle| handle.join().expect("writer")).sum();
893
+ let cycles: usize = writers
894
+ .into_iter()
895
+ .map(|handle| handle.join().expect("writer"))
896
+ .sum();
845
897
 
846
898
  assert!(cycles > 0, "the writers actually mutated the collection");
847
899
  assert!(observations > 0, "the reader actually queried");
@@ -881,7 +933,10 @@ fn indexed_and_scan_execution_agree_on_a_concurrently_built_state() {
881
933
  for conditions in [
882
934
  vec![("status", json!("active"))],
883
935
  vec![("tenantId", json!("tenant-3"))],
884
- vec![("status", json!("archived")), ("tenantId", json!("tenant-2"))],
936
+ vec![
937
+ ("status", json!("archived")),
938
+ ("tenantId", json!("tenant-2")),
939
+ ],
885
940
  ] {
886
941
  assert_eq!(
887
942
  indexed_query(&db, &conditions).expect("indexed"),
@@ -17,10 +17,8 @@
17
17
 
18
18
  use feltdb::{
19
19
  state_contract::{
20
- StateSchema, CollectionSchema, FieldSchema, FieldType, PrimitiveType,
21
- FieldConstraints,
22
- AuthorizationContext, CanonicalQuery, begin_read, execute_query,
23
- QueryFilter,
20
+ begin_read, execute_query, AuthorizationContext, CanonicalQuery, CollectionSchema,
21
+ FieldConstraints, FieldSchema, FieldType, PrimitiveType, QueryFilter, StateSchema,
24
22
  },
25
23
  FeltDb,
26
24
  };
@@ -324,8 +322,7 @@ fn pr7_phase3_self_authorization_proof() -> Result<(), Box<dyn std::error::Error
324
322
  field_projections: Default::default(),
325
323
  };
326
324
 
327
- let _bob_context = begin_read(&db, &schema, "app", bob_auth)
328
- .map_err(|e| format!("{:?}", e))?;
325
+ let _bob_context = begin_read(&db, &schema, "app", bob_auth).map_err(|e| format!("{:?}", e))?;
329
326
 
330
327
  // Query as Bob with self(user) policy
331
328
  let _query = CanonicalQuery {
@@ -360,8 +357,8 @@ fn pr7_phase3_self_authorization_proof() -> Result<(), Box<dyn std::error::Error
360
357
  field_projections: Default::default(),
361
358
  };
362
359
 
363
- let _alice_context = begin_read(&db, &schema, "app", alice_auth)
364
- .map_err(|e| format!("{:?}", e))?;
360
+ let _alice_context =
361
+ begin_read(&db, &schema, "app", alice_auth).map_err(|e| format!("{:?}", e))?;
365
362
 
366
363
  println!(" • Alice is member of Org A");
367
364
  println!(" • Invitation.organization = {}", org_a_id);