@feltdb/core 0.13.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.
Files changed (300) hide show
  1. package/dist/agent-runtime.d.ts +64 -2
  2. package/dist/agent-runtime.d.ts.map +1 -1
  3. package/dist/agent-runtime.js +121 -11
  4. package/dist/agent-state.d.ts +68 -0
  5. package/dist/agent-state.d.ts.map +1 -1
  6. package/dist/agent-state.js +210 -12
  7. package/dist/agent.d.ts +10 -1
  8. package/dist/agent.d.ts.map +1 -1
  9. package/dist/agent.js +10 -1
  10. package/dist/build-identity.json +12 -4
  11. package/dist/cli/commands.js +214 -10
  12. package/dist/cli/publish-engine.js +116 -16
  13. package/dist/collection.d.ts +3 -1
  14. package/dist/collection.d.ts.map +1 -1
  15. package/dist/collection.js +33 -8
  16. package/dist/create/build-identity.json +12 -4
  17. package/dist/create/cli.js +1 -1
  18. package/dist/create/create.js +4 -6
  19. package/dist/create/managed-account.js +93 -35
  20. package/dist/create/package-versions.js +1 -1
  21. package/dist/create/server-source/Cargo.lock +8 -511
  22. package/dist/create/server-source/Cargo.toml +2 -13
  23. package/dist/create/server-source/crates/feltdb-client/Cargo.toml +1 -1
  24. package/dist/create/server-source/crates/feltdb-server/Cargo.toml +1 -1
  25. package/dist/create/server-source/crates/feltdb-server/README.md +6 -1
  26. package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +9 -1
  27. package/dist/create/server-source/crates/feltdb-server/src/auth.rs +85 -25
  28. package/dist/create/server-source/crates/feltdb-server/src/backup.rs +4 -0
  29. package/dist/create/server-source/crates/feltdb-server/src/console_accounts.rs +1009 -0
  30. package/dist/create/server-source/crates/feltdb-server/src/control_authorization.rs +441 -0
  31. package/dist/create/server-source/crates/feltdb-server/src/identity.rs +68 -6
  32. package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +355 -23
  33. package/dist/create/server-source/crates/feltdb-server/src/lib.rs +3 -0
  34. package/dist/create/server-source/crates/feltdb-server/src/main.rs +1134 -96
  35. package/dist/create/server-source/crates/feltdb-server/src/redaction.rs +208 -0
  36. package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +198 -19
  37. package/dist/create/server-source/crates/feltdb-server/tests/transaction_precondition_contract.rs +403 -0
  38. package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +1 -1
  39. package/dist/db.d.ts +75 -10
  40. package/dist/db.d.ts.map +1 -1
  41. package/dist/db.js +168 -29
  42. package/dist/deployment.d.ts +19 -0
  43. package/dist/deployment.d.ts.map +1 -1
  44. package/dist/deployment.js +38 -2
  45. package/dist/embedded-query.d.ts.map +1 -1
  46. package/dist/embedded-query.js +5 -4
  47. package/dist/embedded-transaction.d.ts +8 -4
  48. package/dist/embedded-transaction.d.ts.map +1 -1
  49. package/dist/embedded-transaction.js +55 -19
  50. package/dist/error-codes.d.ts +36 -2
  51. package/dist/error-codes.d.ts.map +1 -1
  52. package/dist/error-codes.js +50 -3
  53. package/dist/feltdb.d.ts +7 -0
  54. package/dist/feltdb.d.ts.map +1 -1
  55. package/dist/file-db.d.ts +27 -0
  56. package/dist/file-db.d.ts.map +1 -1
  57. package/dist/file-db.js +145 -7
  58. package/dist/http-client.d.ts +11 -3
  59. package/dist/http-client.d.ts.map +1 -1
  60. package/dist/http-client.js +27 -14
  61. package/dist/http-db.d.ts +43 -1
  62. package/dist/http-db.d.ts.map +1 -1
  63. package/dist/http-db.js +287 -59
  64. package/dist/index.js +1 -1
  65. package/dist/local-state-lock.d.ts +34 -0
  66. package/dist/local-state-lock.d.ts.map +1 -0
  67. package/dist/local-state-lock.js +225 -0
  68. package/dist/managed-recovery.d.ts +6 -0
  69. package/dist/managed-recovery.d.ts.map +1 -1
  70. package/dist/managed-recovery.js +9 -0
  71. package/dist/reactive-graph.d.ts +0 -3
  72. package/dist/reactive-graph.d.ts.map +1 -1
  73. package/dist/reactive-graph.js +11 -1
  74. package/dist/runtime-registry.d.ts +7 -2
  75. package/dist/runtime-registry.d.ts.map +1 -1
  76. package/dist/runtime-registry.js +2 -2
  77. package/dist/studio/components/index.js +1 -1
  78. package/dist/studio/{components-BSGmHfif.js → components-CJmzntBi.js} +2 -1
  79. package/dist/studio/index.js +1 -1
  80. package/dist/studio-app/assets/{feltdb_wasm-3hDngl1h.js → feltdb_wasm-DtybUY0q.js} +1 -1
  81. package/dist/studio-app/assets/feltdb_wasm_bg-DsgIUwaB.wasm +0 -0
  82. package/dist/studio-app/assets/index-C4OffsZl.js +37 -0
  83. package/dist/studio-app/index.html +1 -1
  84. package/dist/transaction.d.ts +27 -11
  85. package/dist/transaction.d.ts.map +1 -1
  86. package/dist/transaction.js +20 -14
  87. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  88. package/dist/wasm/package.json +1 -1
  89. package/dist/workload.d.ts +86 -6
  90. package/dist/workload.d.ts.map +1 -1
  91. package/dist/workload.js +51 -3
  92. package/dist/workspace/workspace-connection.d.ts.map +1 -1
  93. package/dist/workspace/workspace-connection.js +14 -3
  94. package/package.json +1 -1
  95. package/dist/create/server-source/crates/feltdb/Cargo.lock +0 -175
  96. package/dist/create/server-source/crates/feltdb/Cargo.toml +0 -102
  97. package/dist/create/server-source/crates/feltdb/LICENSE +0 -21
  98. package/dist/create/server-source/crates/feltdb/README.md +0 -75
  99. package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +0 -370
  100. package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +0 -589
  101. package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +0 -247
  102. package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +0 -440
  103. package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +0 -275
  104. package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +0 -381
  105. package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +0 -467
  106. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +0 -489
  107. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +0 -281
  108. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +0 -606
  109. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +0 -531
  110. package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +0 -462
  111. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +0 -505
  112. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +0 -484
  113. package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +0 -758
  114. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +0 -421
  115. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +0 -329
  116. package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +0 -1705
  117. package/dist/create/server-source/crates/feltdb/src/acquisition.rs +0 -286
  118. package/dist/create/server-source/crates/feltdb/src/admission.rs +0 -185
  119. package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +0 -507
  120. package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +0 -539
  121. package/dist/create/server-source/crates/feltdb/src/agent_state.rs +0 -320
  122. package/dist/create/server-source/crates/feltdb/src/agent_transition_contract.rs +0 -1545
  123. package/dist/create/server-source/crates/feltdb/src/analytics.rs +0 -521
  124. package/dist/create/server-source/crates/feltdb/src/application.rs +0 -2674
  125. package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +0 -1070
  126. package/dist/create/server-source/crates/feltdb/src/authority.rs +0 -479
  127. package/dist/create/server-source/crates/feltdb/src/authority_failover.rs +0 -847
  128. package/dist/create/server-source/crates/feltdb/src/authorization.rs +0 -1030
  129. package/dist/create/server-source/crates/feltdb/src/authorization_security_tests.rs +0 -1122
  130. package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority-client.rs +0 -32
  131. package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority.rs +0 -19
  132. package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +0 -1474
  133. package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +0 -8
  134. package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +0 -418
  135. package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +0 -482
  136. package/dist/create/server-source/crates/feltdb/src/capability.rs +0 -903
  137. package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +0 -263
  138. package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +0 -77
  139. package/dist/create/server-source/crates/feltdb/src/causal_backlog_bound.rs +0 -496
  140. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +0 -2574
  141. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +0 -212
  142. package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +0 -422
  143. package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +0 -445
  144. package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +0 -465
  145. package/dist/create/server-source/crates/feltdb/src/convergence.rs +0 -634
  146. package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +0 -427
  147. package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +0 -376
  148. package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +0 -365
  149. package/dist/create/server-source/crates/feltdb/src/cron.rs +0 -294
  150. package/dist/create/server-source/crates/feltdb/src/dedup_bound_investigation.rs +0 -483
  151. package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +0 -477
  152. package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +0 -1003
  153. package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +0 -1334
  154. package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +0 -368
  155. package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +0 -231
  156. package/dist/create/server-source/crates/feltdb/src/durable_operation_identity.rs +0 -482
  157. package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +0 -203
  158. package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +0 -317
  159. package/dist/create/server-source/crates/feltdb/src/equality_index.rs +0 -595
  160. package/dist/create/server-source/crates/feltdb/src/execution.rs +0 -426
  161. package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +0 -214
  162. package/dist/create/server-source/crates/feltdb/src/indexing.rs +0 -776
  163. package/dist/create/server-source/crates/feltdb/src/lib.rs +0 -7274
  164. package/dist/create/server-source/crates/feltdb/src/managed_cas_tests.rs +0 -234
  165. package/dist/create/server-source/crates/feltdb/src/materialization.rs +0 -184
  166. package/dist/create/server-source/crates/feltdb/src/metrics.rs +0 -266
  167. package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +0 -310
  168. package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +0 -736
  169. package/dist/create/server-source/crates/feltdb/src/observability.rs +0 -379
  170. package/dist/create/server-source/crates/feltdb/src/operation.rs +0 -290
  171. package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +0 -439
  172. package/dist/create/server-source/crates/feltdb/src/operation_log.rs +0 -349
  173. package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +0 -332
  174. package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +0 -718
  175. package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +0 -487
  176. package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +0 -166
  177. package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +0 -289
  178. package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +0 -566
  179. package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +0 -3391
  180. package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +0 -203
  181. package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +0 -261
  182. package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +0 -502
  183. package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +0 -216
  184. package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +0 -303
  185. package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +0 -323
  186. package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +0 -531
  187. package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +0 -406
  188. package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +0 -436
  189. package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +0 -387
  190. package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +0 -378
  191. package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +0 -359
  192. package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +0 -372
  193. package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +0 -404
  194. package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +0 -376
  195. package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +0 -461
  196. package/dist/create/server-source/crates/feltdb/src/policy_evaluation.rs +0 -2125
  197. package/dist/create/server-source/crates/feltdb/src/production_api.rs +0 -418
  198. package/dist/create/server-source/crates/feltdb/src/provenance.rs +0 -207
  199. package/dist/create/server-source/crates/feltdb/src/query_execution_diagnostics.rs +0 -126
  200. package/dist/create/server-source/crates/feltdb/src/query_performance.rs +0 -218
  201. package/dist/create/server-source/crates/feltdb/src/read_basis.rs +0 -257
  202. package/dist/create/server-source/crates/feltdb/src/references.rs +0 -404
  203. package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +0 -401
  204. package/dist/create/server-source/crates/feltdb/src/replica_acknowledgements.rs +0 -501
  205. package/dist/create/server-source/crates/feltdb/src/replica_membership.rs +0 -680
  206. package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +0 -163
  207. package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +0 -133
  208. package/dist/create/server-source/crates/feltdb/src/routing.rs +0 -409
  209. package/dist/create/server-source/crates/feltdb/src/semantic_decision.rs +0 -2029
  210. package/dist/create/server-source/crates/feltdb/src/semantic_query.rs +0 -599
  211. package/dist/create/server-source/crates/feltdb/src/sharding.rs +0 -549
  212. package/dist/create/server-source/crates/feltdb/src/state_conflict_contract.rs +0 -516
  213. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +0 -5894
  214. package/dist/create/server-source/crates/feltdb/src/state_diff_contract.rs +0 -222
  215. package/dist/create/server-source/crates/feltdb/src/state_facade.rs +0 -320
  216. package/dist/create/server-source/crates/feltdb/src/state_hash.rs +0 -291
  217. package/dist/create/server-source/crates/feltdb/src/state_model.rs +0 -4158
  218. package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +0 -379
  219. package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +0 -673
  220. package/dist/create/server-source/crates/feltdb/src/storage.rs +0 -273
  221. package/dist/create/server-source/crates/feltdb/src/submission.rs +0 -471
  222. package/dist/create/server-source/crates/feltdb/src/sync.rs +0 -495
  223. package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +0 -822
  224. package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +0 -433
  225. package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +0 -462
  226. package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +0 -965
  227. package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +0 -1120
  228. package/dist/create/server-source/crates/feltdb/src/transactions.rs +0 -643
  229. package/dist/create/server-source/crates/feltdb/src/trigger.rs +0 -390
  230. package/dist/create/server-source/crates/feltdb/src/trigger_contract.rs +0 -749
  231. package/dist/create/server-source/crates/feltdb/src/versioned_object_substrate.rs +0 -708
  232. package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +0 -1133
  233. package/dist/create/server-source/crates/feltdb/src/workflow.rs +0 -713
  234. package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +0 -510
  235. package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +0 -354
  236. package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +0 -594
  237. package/dist/create/server-source/crates/feltdb/src/workload.rs +0 -2886
  238. package/dist/create/server-source/crates/feltdb/src/workload_diagnostics.rs +0 -456
  239. package/dist/create/server-source/crates/feltdb/tests/agent_state_native.rs +0 -1188
  240. package/dist/create/server-source/crates/feltdb/tests/agent_transition_contract_conformance.rs +0 -2884
  241. package/dist/create/server-source/crates/feltdb/tests/authority_process.rs +0 -297
  242. package/dist/create/server-source/crates/feltdb/tests/authority_transaction_identity.rs +0 -534
  243. package/dist/create/server-source/crates/feltdb/tests/bounded_read_contract.rs +0 -132
  244. package/dist/create/server-source/crates/feltdb/tests/branching_evidence.rs +0 -299
  245. package/dist/create/server-source/crates/feltdb/tests/common/mod.rs +0 -96
  246. package/dist/create/server-source/crates/feltdb/tests/compaction_stall_contract.rs +0 -272
  247. package/dist/create/server-source/crates/feltdb/tests/crash_durability_contract.rs +0 -467
  248. package/dist/create/server-source/crates/feltdb/tests/current_revision_authority_evidence.rs +0 -309
  249. package/dist/create/server-source/crates/feltdb/tests/durable_backup_contract.rs +0 -445
  250. package/dist/create/server-source/crates/feltdb/tests/durable_corruption_contract.rs +0 -518
  251. package/dist/create/server-source/crates/feltdb/tests/durable_format_compatibility.rs +0 -392
  252. package/dist/create/server-source/crates/feltdb/tests/feltdb_state_boundary_tests.rs +0 -850
  253. package/dist/create/server-source/crates/feltdb/tests/fixtures/agent_transition_corpus.json +0 -23340
  254. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_conflict_contract_corpus.json +0 -1916
  255. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_diff_contract_corpus.json +0 -1878
  256. package/dist/create/server-source/crates/feltdb/tests/fixtures/trigger_contract_corpus.json +0 -1862
  257. package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_evidence.rs +0 -123
  258. package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_substrate_completion.rs +0 -299
  259. package/dist/create/server-source/crates/feltdb/tests/local_process_ownership.rs +0 -69
  260. package/dist/create/server-source/crates/feltdb/tests/managed_incident_regression.rs +0 -191
  261. package/dist/create/server-source/crates/feltdb/tests/operational_health_contract.rs +0 -278
  262. package/dist/create/server-source/crates/feltdb/tests/pr34_query_collection.rs +0 -234
  263. package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +0 -947
  264. package/dist/create/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +0 -404
  265. package/dist/create/server-source/crates/feltdb/tests/pr8_vocabulary_assessment.rs +0 -916
  266. package/dist/create/server-source/crates/feltdb/tests/pr9_phase2_boundary_tests.rs +0 -1045
  267. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3a_path_a_tests.rs +0 -347
  268. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_authorized_mutations.rs +0 -361
  269. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_role_based_authorization.rs +0 -330
  270. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_simple_auth_delete.rs +0 -93
  271. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_team_delete_role_authorization.rs +0 -622
  272. package/dist/create/server-source/crates/feltdb/tests/pr9_teams_role_based_access.rs +0 -517
  273. package/dist/create/server-source/crates/feltdb/tests/production_certification.rs +0 -1153
  274. package/dist/create/server-source/crates/feltdb/tests/production_contract.rs +0 -771
  275. package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +0 -1383
  276. package/dist/create/server-source/crates/feltdb/tests/reconciliation_application.rs +0 -868
  277. package/dist/create/server-source/crates/feltdb/tests/reconciliation_wire_format_evidence.rs +0 -221
  278. package/dist/create/server-source/crates/feltdb/tests/replicated_history_contract.rs +0 -417
  279. package/dist/create/server-source/crates/feltdb/tests/resource_scoped_revisions.rs +0 -338
  280. package/dist/create/server-source/crates/feltdb/tests/revision_identity_contract.rs +0 -1039
  281. package/dist/create/server-source/crates/feltdb/tests/revision_model_decision.rs +0 -739
  282. package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +0 -452
  283. package/dist/create/server-source/crates/feltdb/tests/saas_authorization_integration.rs +0 -81
  284. package/dist/create/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +0 -438
  285. package/dist/create/server-source/crates/feltdb/tests/semantic_decision.rs +0 -1165
  286. package/dist/create/server-source/crates/feltdb/tests/semantic_query.rs +0 -767
  287. package/dist/create/server-source/crates/feltdb/tests/semantic_query_contract.rs +0 -363
  288. package/dist/create/server-source/crates/feltdb/tests/state_conflict_contract_conformance.rs +0 -1799
  289. package/dist/create/server-source/crates/feltdb/tests/state_diff_contract_conformance.rs +0 -1316
  290. package/dist/create/server-source/crates/feltdb/tests/state_model_integration.rs +0 -358
  291. package/dist/create/server-source/crates/feltdb/tests/state_persistence_integration.rs +0 -365
  292. package/dist/create/server-source/crates/feltdb/tests/state_ref_authority.rs +0 -918
  293. package/dist/create/server-source/crates/feltdb/tests/state_store_boundary_evidence.rs +0 -299
  294. package/dist/create/server-source/crates/feltdb/tests/storage_independent_versioned_object_substrate.rs +0 -328
  295. package/dist/create/server-source/crates/feltdb/tests/sync_divergence_evidence.rs +0 -255
  296. package/dist/create/server-source/crates/feltdb/tests/three_way_input_boundary_evidence.rs +0 -249
  297. package/dist/create/server-source/crates/feltdb/tests/trigger_contract_conformance.rs +0 -994
  298. package/dist/create/server-source/crates/feltdb/tests/workload_envelope_contract.rs +0 -484
  299. package/dist/studio-app/assets/feltdb_wasm_bg-CFQT4gCy.wasm +0 -0
  300. package/dist/studio-app/assets/index-Dql1hS5m.js +0 -30
@@ -1,947 +0,0 @@
1
- //! PR35 — deterministic equality index over authoritative state.
2
- //!
3
- //! PR34 removed whole-collection *materialization* from bounded query
4
- //! execution. What it left behind was still `O(N)`: the conjunction had to be
5
- //! evaluated against every record of the collection. PR35 adds an equality index
6
- //! so an applicable query can reach its candidates directly.
7
- //!
8
- //! An index is only ever an optimization, which makes correctness the whole
9
- //! difficulty: a faster query that answers differently is not a faster query, it
10
- //! is a bug. These tests are therefore organized around the four claims that
11
- //! have to hold before performance is worth measuring at all.
12
- //!
13
- //! 1. **Maintenance.** After every mutation class — insert, update, delete,
14
- //! CAS accepted and CAS refused, transaction committed and transaction
15
- //! refused — the live index equals one rebuilt from authoritative state.
16
- //! `verify_equality_index` performs exactly that comparison, so these
17
- //! assertions are against the records, not against a second copy of the
18
- //! index's own bookkeeping.
19
- //! 2. **Candidate selection, not answers.** A lookup returns record keys. The
20
- //! predicate still decides.
21
- //! 3. **Recovery.** Reopening the log and re-declaring the index reproduces
22
- //! the index exactly, because a rebuild is the only way an index is ever
23
- //! populated.
24
- //! 4. **Values.** Strings, numbers, booleans, explicit nulls and missing
25
- //! fields stay distinguishable, and unindexable values are excluded rather
26
- //! than coerced.
27
-
28
- use feltdb::{AtomicMutation, FeltDb, RecordPrecondition, StoredRow};
29
- use serde_json::{json, Value};
30
- use std::collections::BTreeMap;
31
- use tempfile::TempDir;
32
-
33
- type IndexSnapshot = BTreeMap<String, BTreeMap<String, BTreeMap<String, Vec<String>>>>;
34
-
35
- fn database() -> (TempDir, FeltDb) {
36
- let directory = TempDir::new().expect("temp dir");
37
- let db = FeltDb::open(directory.path().join("pr35.log")).expect("open db");
38
- (directory, db)
39
- }
40
-
41
- /// A database with `status` and `tenantId` indexed on `items`.
42
- fn indexed() -> (TempDir, FeltDb) {
43
- let (directory, db) = database();
44
- db.create_equality_index("items", "status")
45
- .expect("declare");
46
- db.create_equality_index("items", "tenantId")
47
- .expect("declare");
48
- (directory, db)
49
- }
50
-
51
- /// The invariant every mutation must preserve: the maintained index is exactly
52
- /// what authoritative state derives.
53
- #[track_caller]
54
- fn assert_index_matches_state(db: &FeltDb, after: &str) {
55
- match db.verify_equality_index().expect("verify") {
56
- Ok(()) => {}
57
- Err(divergence) => {
58
- panic!("index diverged from authoritative state after {after}: {divergence}")
59
- }
60
- }
61
- }
62
-
63
- fn candidates(db: &FeltDb, field: &str, value: Value) -> Option<Vec<String>> {
64
- db.equality_index_candidates("items", field, &value)
65
- .expect("lookup")
66
- }
67
-
68
- /// Candidate keys for a value the index can answer, as record ids.
69
- fn ids(db: &FeltDb, field: &str, value: Value) -> Vec<String> {
70
- candidates(db, field, value)
71
- .expect("the field is indexed and the value is indexable")
72
- .into_iter()
73
- .map(|key| {
74
- key.split_once(':')
75
- .map(|(_, id)| id.to_string())
76
- .unwrap_or(key)
77
- })
78
- .collect()
79
- }
80
-
81
- fn put(db: &FeltDb, id: &str, value: Value) {
82
- db.insert(&format!("items:{id}"), &value).expect("insert");
83
- }
84
-
85
- fn snapshot(db: &FeltDb) -> IndexSnapshot {
86
- db.equality_index_snapshot().expect("snapshot")
87
- }
88
-
89
- // ---------------------------------------------------------------------------
90
- // Basic indexing
91
- // ---------------------------------------------------------------------------
92
-
93
- #[test]
94
- fn one_indexed_record_is_reachable_by_its_value() {
95
- let (_directory, db) = indexed();
96
- put(&db, "a", json!({ "status": "active" }));
97
-
98
- assert_eq!(ids(&db, "status", json!("active")), vec!["a".to_string()]);
99
- assert_index_matches_state(&db, "one insert");
100
- }
101
-
102
- #[test]
103
- fn records_sharing_a_value_share_a_bucket_and_different_values_do_not() {
104
- let (_directory, db) = indexed();
105
- put(&db, "a", json!({ "status": "active" }));
106
- put(&db, "b", json!({ "status": "active" }));
107
- put(&db, "c", json!({ "status": "archived" }));
108
-
109
- assert_eq!(
110
- ids(&db, "status", json!("active")),
111
- vec!["a".to_string(), "b".to_string()],
112
- );
113
- assert_eq!(ids(&db, "status", json!("archived")), vec!["c".to_string()]);
114
- assert!(
115
- ids(&db, "status", json!("nobody-holds-this")).is_empty(),
116
- "a value no record holds is an empty answer, not an inapplicable index",
117
- );
118
- assert_index_matches_state(&db, "inserts across several values");
119
- }
120
-
121
- #[test]
122
- fn an_unindexed_field_is_inapplicable_rather_than_empty() {
123
- let (_directory, db) = indexed();
124
- put(&db, "a", json!({ "status": "active", "role": "admin" }));
125
-
126
- assert_eq!(
127
- candidates(&db, "role", json!("admin")),
128
- None,
129
- "an unindexed field must report that the index cannot answer, so execution falls back",
130
- );
131
- assert_eq!(
132
- candidates(&db, "status", json!("nobody")),
133
- Some(Vec::new()),
134
- "an indexed field with no holder answers emptily, which is a different thing",
135
- );
136
- }
137
-
138
- // ---------------------------------------------------------------------------
139
- // Updates
140
- // ---------------------------------------------------------------------------
141
-
142
- #[test]
143
- fn changing_an_indexed_field_moves_the_record_between_buckets() {
144
- let (_directory, db) = indexed();
145
- put(&db, "a", json!({ "status": "active" }));
146
- db.update("items:a", &json!({ "status": "archived" }))
147
- .expect("update");
148
-
149
- assert!(
150
- ids(&db, "status", json!("active")).is_empty(),
151
- "the old value must not retain the record",
152
- );
153
- assert_eq!(ids(&db, "status", json!("archived")), vec!["a".to_string()]);
154
- assert_index_matches_state(&db, "an indexed field changing");
155
- }
156
-
157
- #[test]
158
- fn an_update_that_leaves_the_indexed_field_alone_leaves_the_index_alone() {
159
- let (_directory, db) = indexed();
160
- put(&db, "a", json!({ "status": "active", "note": "before" }));
161
- let before = snapshot(&db);
162
- db.update("items:a", &json!({ "status": "active", "note": "after" }))
163
- .expect("update");
164
-
165
- assert_eq!(
166
- snapshot(&db),
167
- before,
168
- "an unrelated field change is invisible to the index"
169
- );
170
- assert_index_matches_state(&db, "an unrelated field changing");
171
- }
172
-
173
- #[test]
174
- fn removing_and_adding_an_indexed_field_adds_and_removes_the_entry() {
175
- let (_directory, db) = indexed();
176
- put(&db, "a", json!({ "status": "active" }));
177
- db.update("items:a", &json!({ "note": "the status is gone" }))
178
- .expect("update");
179
-
180
- assert!(
181
- ids(&db, "status", json!("active")).is_empty(),
182
- "a record that no longer holds the field holds no entry for it",
183
- );
184
- assert_index_matches_state(&db, "an indexed field being removed");
185
-
186
- db.update("items:a", &json!({ "note": "back", "status": "active" }))
187
- .expect("update");
188
- assert_eq!(ids(&db, "status", json!("active")), vec!["a".to_string()]);
189
- assert_index_matches_state(&db, "an indexed field being added");
190
- }
191
-
192
- // ---------------------------------------------------------------------------
193
- // Deletes
194
- // ---------------------------------------------------------------------------
195
-
196
- #[test]
197
- fn deleting_records_empties_buckets_and_deleting_the_last_one_removes_it() {
198
- let (_directory, db) = indexed();
199
- put(&db, "a", json!({ "status": "active" }));
200
- put(&db, "b", json!({ "status": "active" }));
201
-
202
- db.delete("items:a").expect("delete");
203
- assert_eq!(
204
- ids(&db, "status", json!("active")),
205
- vec!["b".to_string()],
206
- "deleting one holder leaves the others",
207
- );
208
- assert_index_matches_state(&db, "deleting one of several holders");
209
-
210
- db.delete("items:b").expect("delete");
211
- assert!(ids(&db, "status", json!("active")).is_empty());
212
- assert_eq!(
213
- db.equality_index_stats().expect("stats").value_buckets,
214
- 0,
215
- "an emptied bucket is absent, which is the representation a rebuild produces",
216
- );
217
- assert_index_matches_state(&db, "deleting the final holder of a value");
218
- }
219
-
220
- // ---------------------------------------------------------------------------
221
- // Values
222
- // ---------------------------------------------------------------------------
223
-
224
- #[test]
225
- fn distinguishable_values_stay_distinguishable() {
226
- let (_directory, db) = indexed();
227
- let values = [
228
- ("null", json!(null)),
229
- ("true", json!(true)),
230
- ("false", json!(false)),
231
- ("zero", json!(0)),
232
- ("one", json!(1)),
233
- ("zero-text", json!("0")),
234
- ("one-text", json!("1")),
235
- ("forty-two", json!(42)),
236
- ("forty-two-text", json!("42")),
237
- ("true-text", json!("true")),
238
- ("empty-text", json!("")),
239
- ];
240
- for (id, value) in &values {
241
- put(&db, id, json!({ "status": value }));
242
- }
243
-
244
- for (id, value) in &values {
245
- assert_eq!(
246
- ids(&db, "status", value.clone()),
247
- vec![id.to_string()],
248
- "{value} must reach exactly the record holding it",
249
- );
250
- }
251
- assert_index_matches_state(&db, "records covering every scalar kind");
252
- }
253
-
254
- #[test]
255
- fn a_missing_field_is_not_an_explicit_null() {
256
- let (_directory, db) = indexed();
257
- put(&db, "explicit", json!({ "status": null }));
258
- put(&db, "missing", json!({ "note": "no status here" }));
259
-
260
- assert_eq!(
261
- ids(&db, "status", json!(null)),
262
- vec!["explicit".to_string()],
263
- "an absent field must not be reachable as null: the predicate does not treat them as equal",
264
- );
265
- assert_index_matches_state(&db, "an explicit null beside a missing field");
266
- }
267
-
268
- #[test]
269
- fn unindexable_values_are_excluded_rather_than_coerced() {
270
- let (_directory, db) = indexed();
271
- put(&db, "object", json!({ "status": { "state": "active" } }));
272
- put(&db, "array", json!({ "status": ["active"] }));
273
- put(&db, "scalar", json!({ "status": "active" }));
274
- put(
275
- &db,
276
- "not-an-object",
277
- json!("items are usually objects, but need not be"),
278
- );
279
-
280
- assert_eq!(
281
- ids(&db, "status", json!("active")),
282
- vec!["scalar".to_string()],
283
- "a compound value is never equal to a scalar, so excluding it keeps the candidate set complete",
284
- );
285
- assert_eq!(
286
- candidates(&db, "status", json!({ "state": "active" })),
287
- None,
288
- "a condition the index cannot represent must report inapplicable, never a wrong answer",
289
- );
290
- assert_index_matches_state(&db, "compound and non-object records");
291
- }
292
-
293
- #[test]
294
- fn record_id_is_never_indexable() {
295
- let (_directory, db) = database();
296
- let refusal = db
297
- .create_equality_index("items", "recordId")
298
- .expect_err("recordId is authority metadata on the query surface");
299
- assert!(
300
- refusal.to_string().contains("recordId"),
301
- "the refusal must name the field: {refusal}",
302
- );
303
- assert!(db.equality_indexes().expect("indexes").is_empty());
304
- }
305
-
306
- // ---------------------------------------------------------------------------
307
- // Mutation primitives: CAS and conditional creation
308
- // ---------------------------------------------------------------------------
309
-
310
- #[test]
311
- fn an_accepted_cas_moves_the_index_and_a_refused_one_changes_nothing() {
312
- let (_directory, db) = indexed();
313
- put(&db, "a", json!({ "status": "active", "__version": 1 }));
314
-
315
- let refused = db
316
- .compare_and_set_json(
317
- "items:a",
318
- 99,
319
- None,
320
- None,
321
- false,
322
- json!({ "status": "archived" }),
323
- )
324
- .expect("cas");
325
- assert!(
326
- matches!(refused, feltdb::JsonCasResult::VersionConflict { .. }),
327
- "the precondition must fail: {refused:?}",
328
- );
329
- assert_eq!(
330
- ids(&db, "status", json!("active")),
331
- vec!["a".to_string()],
332
- "a refused CAS writes neither the record nor the index",
333
- );
334
- assert_index_matches_state(&db, "a refused CAS");
335
-
336
- let accepted = db
337
- .compare_and_set_json(
338
- "items:a",
339
- 1,
340
- None,
341
- None,
342
- false,
343
- json!({ "status": "archived" }),
344
- )
345
- .expect("cas");
346
- assert!(matches!(accepted, feltdb::JsonCasResult::Updated { .. }));
347
- assert!(ids(&db, "status", json!("active")).is_empty());
348
- assert_eq!(ids(&db, "status", json!("archived")), vec!["a".to_string()]);
349
- assert_index_matches_state(&db, "an accepted CAS");
350
- }
351
-
352
- #[test]
353
- fn conditional_creation_maintains_the_index_when_it_wins_and_not_when_it_loses() {
354
- let (_directory, db) = indexed();
355
-
356
- // putIfAbsent, as the transaction surface expresses it: a staged write
357
- // fenced by `require_absent`.
358
- let absent = RecordPrecondition {
359
- capability: "items".into(),
360
- key: "items:a".into(),
361
- require_absent: true,
362
- ..RecordPrecondition::default()
363
- };
364
- let write = AtomicMutation {
365
- capability: "items".into(),
366
- key: "items:a".into(),
367
- value: Some(json!({ "status": "active" })),
368
- };
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");
379
- assert_eq!(ids(&db, "status", json!("active")), vec!["a".to_string()]);
380
- assert_index_matches_state(&db, "a conditional creation that won");
381
-
382
- let losing = AtomicMutation {
383
- capability: "items".into(),
384
- key: "items:a".into(),
385
- value: Some(json!({ "status": "archived" })),
386
- };
387
- db.apply_atomic_transaction_guarded("pr35-put-2", None, None, &[], &[absent], &[losing], None)
388
- .expect_err("the record now exists, so the creation must be refused");
389
- assert_eq!(
390
- ids(&db, "status", json!("active")),
391
- vec!["a".to_string()],
392
- "a refused conditional creation leaves the index exactly as it was",
393
- );
394
- assert!(ids(&db, "status", json!("archived")).is_empty());
395
- assert_index_matches_state(&db, "a conditional creation that lost");
396
- }
397
-
398
- // ---------------------------------------------------------------------------
399
- // Transactions
400
- // ---------------------------------------------------------------------------
401
-
402
- fn mutation(key: &str, value: Option<Value>) -> AtomicMutation {
403
- AtomicMutation {
404
- capability: "items".into(),
405
- key: key.into(),
406
- value,
407
- }
408
- }
409
-
410
- #[test]
411
- fn a_transaction_of_one_indexed_mutation_is_atomic_with_the_index() {
412
- let (_directory, db) = indexed();
413
- db.apply_atomic_transaction_guarded(
414
- "pr35-tx-1",
415
- None,
416
- None,
417
- &[],
418
- &[],
419
- &[mutation("items:a", Some(json!({ "status": "active" })))],
420
- None,
421
- )
422
- .expect("commit");
423
-
424
- assert_eq!(ids(&db, "status", json!("active")), vec!["a".to_string()]);
425
- assert_index_matches_state(&db, "a single-mutation transaction");
426
- }
427
-
428
- #[test]
429
- fn one_transaction_of_insert_update_and_delete_lands_wholly_in_the_index() {
430
- let (_directory, db) = indexed();
431
- put(&db, "a", json!({ "status": "active" }));
432
- put(&db, "b", json!({ "status": "pending" }));
433
-
434
- db.apply_atomic_transaction_guarded(
435
- "pr35-tx-mixed",
436
- None,
437
- None,
438
- &[],
439
- &[],
440
- &[
441
- // A: an indexed field changes value.
442
- mutation("items:a", Some(json!({ "status": "inactive" }))),
443
- // B: removed entirely.
444
- mutation("items:b", None),
445
- // C: created holding the value A just left.
446
- mutation("items:c", Some(json!({ "status": "active" }))),
447
- ],
448
- None,
449
- )
450
- .expect("commit");
451
-
452
- assert_eq!(ids(&db, "status", json!("active")), vec!["c".to_string()]);
453
- assert_eq!(ids(&db, "status", json!("inactive")), vec!["a".to_string()]);
454
- assert!(ids(&db, "status", json!("pending")).is_empty());
455
- assert_index_matches_state(&db, "a transaction of insert, update and delete");
456
- }
457
-
458
- #[test]
459
- fn a_refused_transaction_leaves_the_index_at_its_previous_state() {
460
- let (_directory, db) = indexed();
461
- put(&db, "a", json!({ "status": "active" }));
462
- let before = snapshot(&db);
463
-
464
- db.apply_atomic_transaction_guarded(
465
- "pr35-tx-refused",
466
- None,
467
- None,
468
- &[],
469
- &[RecordPrecondition {
470
- capability: "items".into(),
471
- key: "items:a".into(),
472
- require_absent: true,
473
- ..RecordPrecondition::default()
474
- }],
475
- &[
476
- mutation("items:a", Some(json!({ "status": "archived" }))),
477
- mutation("items:z", Some(json!({ "status": "archived" }))),
478
- ],
479
- None,
480
- )
481
- .expect_err("the precondition must refuse the transaction");
482
-
483
- assert_eq!(
484
- snapshot(&db),
485
- before,
486
- "a refused transaction publishes no part of itself, the index included",
487
- );
488
- assert!(
489
- ids(&db, "status", json!("archived")).is_empty(),
490
- "no mutation of a refused transaction is observable through the index",
491
- );
492
- assert_index_matches_state(&db, "a refused transaction");
493
- }
494
-
495
- #[test]
496
- fn a_transaction_touching_no_indexed_field_is_still_consistent() {
497
- let (_directory, db) = indexed();
498
- db.apply_atomic_transaction_guarded(
499
- "pr35-tx-unindexed",
500
- None,
501
- None,
502
- &[],
503
- &[],
504
- &[mutation("items:a", Some(json!({ "role": "admin" })))],
505
- None,
506
- )
507
- .expect("commit");
508
-
509
- assert!(ids(&db, "status", json!("active")).is_empty());
510
- assert_eq!(
511
- db.equality_index_stats().expect("stats").entries,
512
- 0,
513
- "a record holding no indexed field contributes no entries",
514
- );
515
- assert_index_matches_state(&db, "a transaction over unindexed fields");
516
- }
517
-
518
- #[test]
519
- fn an_unindexed_collection_is_untouched_by_index_maintenance() {
520
- let (_directory, db) = indexed();
521
- db.insert("orders:1", &json!({ "status": "active" }))
522
- .expect("insert");
523
- db.update("orders:1", &json!({ "status": "archived" }))
524
- .expect("update");
525
- db.delete("orders:1").expect("delete");
526
-
527
- assert!(snapshot(&db).is_empty());
528
- assert_index_matches_state(&db, "mutations of a collection nobody indexed");
529
- }
530
-
531
- // ---------------------------------------------------------------------------
532
- // Lifecycle and recovery
533
- // ---------------------------------------------------------------------------
534
-
535
- #[test]
536
- fn declaring_an_index_populates_it_from_state_that_already_exists() {
537
- let (_directory, db) = database();
538
- put(&db, "a", json!({ "status": "active" }));
539
- put(&db, "b", json!({ "status": "archived" }));
540
-
541
- assert_eq!(
542
- candidates(&db, "status", json!("active")),
543
- None,
544
- "before declaration there is no index to consult",
545
- );
546
- assert!(db
547
- .create_equality_index("items", "status")
548
- .expect("declare"));
549
- assert_eq!(
550
- ids(&db, "status", json!("active")),
551
- vec!["a".to_string()],
552
- "declaration derives the index from the records already held",
553
- );
554
- assert!(
555
- !db.create_equality_index("items", "status")
556
- .expect("declare"),
557
- "re-declaring an existing index reports that it created nothing",
558
- );
559
- assert_index_matches_state(&db, "declaring an index over existing state");
560
-
561
- assert!(db.drop_equality_index("items", "status").expect("drop"));
562
- assert_eq!(candidates(&db, "status", json!("active")), None);
563
- assert!(!db.drop_equality_index("items", "status").expect("drop"));
564
- }
565
-
566
- #[test]
567
- fn an_explicit_rebuild_reproduces_the_maintained_index_exactly() {
568
- let (_directory, db) = indexed();
569
- for step in 0..64 {
570
- put(
571
- &db,
572
- &format!("record-{step:03}"),
573
- json!({ "status": if step % 3 == 0 { "active" } else { "archived" }, "tenantId": format!("tenant-{}", step % 4) }),
574
- );
575
- }
576
- db.delete("items:record-000").expect("delete");
577
- db.update(
578
- "items:record-001",
579
- &json!({ "status": "active", "tenantId": "tenant-9" }),
580
- )
581
- .expect("update");
582
-
583
- let maintained = snapshot(&db);
584
- db.rebuild_equality_indexes().expect("rebuild");
585
- assert_eq!(
586
- snapshot(&db),
587
- maintained,
588
- "a rebuild is the definition of correct: maintenance must have produced the same thing",
589
- );
590
- }
591
-
592
- #[test]
593
- fn reopening_the_database_rebuilds_the_index_from_durable_state() {
594
- let directory = TempDir::new().expect("temp dir");
595
- let path = directory.path().join("pr35-restart.log");
596
-
597
- let before = {
598
- let db = FeltDb::open(&path).expect("open");
599
- db.create_equality_index("items", "status")
600
- .expect("declare");
601
- db.create_equality_index("items", "tenantId")
602
- .expect("declare");
603
- for step in 0..48 {
604
- db.insert(
605
- &format!("items:record-{step:03}"),
606
- &json!({ "status": if step % 5 == 0 { "active" } else { "archived" }, "tenantId": format!("tenant-{}", step % 3) }),
607
- )
608
- .expect("insert");
609
- }
610
- db.delete("items:record-005").expect("delete");
611
- db.update(
612
- "items:record-006",
613
- &json!({ "status": "active", "tenantId": "tenant-0" }),
614
- )
615
- .expect("update");
616
- db.apply_atomic_transaction_guarded(
617
- "pr35-restart-tx",
618
- None,
619
- None,
620
- &[],
621
- &[],
622
- &[
623
- mutation("items:record-007", None),
624
- mutation(
625
- "items:record-100",
626
- Some(json!({ "status": "active", "tenantId": "tenant-1" })),
627
- ),
628
- ],
629
- None,
630
- )
631
- .expect("commit");
632
- snapshot(&db)
633
- };
634
-
635
- let reopened = FeltDb::open(&path).expect("reopen");
636
- assert!(
637
- reopened.equality_indexes().expect("indexes").is_empty(),
638
- "an index is derived state: nothing about it is read back from the log",
639
- );
640
- reopened
641
- .create_equality_index("items", "status")
642
- .expect("declare");
643
- reopened
644
- .create_equality_index("items", "tenantId")
645
- .expect("declare");
646
-
647
- assert_eq!(
648
- snapshot(&reopened),
649
- before,
650
- "the index derived from recovered records must equal the index maintained before the restart",
651
- );
652
- assert_index_matches_state(&reopened, "a restart");
653
- }
654
-
655
- // ---------------------------------------------------------------------------
656
- // Candidate selection, conjunctions, and scan equivalence
657
- // ---------------------------------------------------------------------------
658
-
659
- /// The scan execution: the predicate against every record of the collection.
660
- fn scan(db: &FeltDb, conditions: &[(&str, Value)]) -> Vec<String> {
661
- let rows = db
662
- .query_collection("items", None, |row| matches_all(row, conditions))
663
- .expect("scan");
664
- rows.into_iter().map(|row| row.key).collect()
665
- }
666
-
667
- /// The indexed execution: candidates from the index, then the same predicate.
668
- fn indexed_query(db: &FeltDb, conditions: &[(&str, Value)]) -> Option<Vec<String>> {
669
- let equalities: Vec<(&str, &Value)> = conditions
670
- .iter()
671
- .map(|(field, value)| (*field, value))
672
- .collect();
673
- db.query_collection_by_equality("items", &equalities, |row| matches_all(row, conditions))
674
- .expect("indexed query")
675
- .map(|rows| rows.into_iter().map(|row| row.key).collect())
676
- }
677
-
678
- fn matches_all(row: &StoredRow, conditions: &[(&str, Value)]) -> bool {
679
- conditions
680
- .iter()
681
- .all(|(field, value)| row.value.get(field) == Some(value))
682
- }
683
-
684
- #[test]
685
- fn indexed_execution_returns_exactly_what_the_scan_returns() {
686
- let (_directory, db) = indexed();
687
- let statuses = ["active", "archived", "pending"];
688
- for step in 0..200 {
689
- put(
690
- &db,
691
- &format!("record-{step:03}"),
692
- json!({
693
- "status": statuses[step % 3],
694
- "tenantId": format!("tenant-{}", step % 7),
695
- "role": if step % 2 == 0 { "admin" } else { "member" },
696
- }),
697
- );
698
- }
699
-
700
- let corpus: Vec<Vec<(&str, Value)>> = vec![
701
- vec![("status", json!("active"))],
702
- vec![("status", json!("pending"))],
703
- vec![("status", json!("nobody-holds-this"))],
704
- vec![("tenantId", json!("tenant-3"))],
705
- // A indexed, B unindexed.
706
- vec![("status", json!("active")), ("role", json!("admin"))],
707
- // A unindexed, B indexed.
708
- vec![("role", json!("member")), ("tenantId", json!("tenant-2"))],
709
- // Both indexed.
710
- vec![("status", json!("active")), ("tenantId", json!("tenant-1"))],
711
- // Both indexed, no common member.
712
- vec![("status", json!("active")), ("status", json!("archived"))],
713
- // Three conditions, two of them indexed.
714
- vec![
715
- ("status", json!("archived")),
716
- ("tenantId", json!("tenant-5")),
717
- ("role", json!("admin")),
718
- ],
719
- // One condition matches, the other cannot.
720
- vec![("status", json!("active")), ("role", json!("nobody"))],
721
- // Neither indexed.
722
- vec![("role", json!("admin"))],
723
- ];
724
-
725
- for conditions in &corpus {
726
- let scanned = scan(&db, conditions);
727
- match indexed_query(&db, conditions) {
728
- Some(indexed) => assert_eq!(
729
- indexed, scanned,
730
- "indexed and scan execution disagreed on {conditions:?}",
731
- ),
732
- None => assert!(
733
- conditions
734
- .iter()
735
- .all(|(field, _)| !matches!(*field, "status" | "tenantId")),
736
- "only a query with no indexed equality may report the index inapplicable: {conditions:?}",
737
- ),
738
- }
739
- }
740
-
741
- // The conjunction actually narrows rather than merely re-filtering: the
742
- // intersection is smaller than either side.
743
- let both = indexed_query(
744
- &db,
745
- &[("status", json!("active")), ("tenantId", json!("tenant-1"))],
746
- )
747
- .expect("both fields are indexed");
748
- let one = indexed_query(&db, &[("status", json!("active"))]).expect("indexed");
749
- assert!(!both.is_empty() && both.len() < one.len());
750
- }
751
-
752
- #[test]
753
- fn the_index_selects_candidates_and_the_predicate_still_decides() {
754
- let (_directory, db) = indexed();
755
- put(&db, "a", json!({ "status": "active", "role": "admin" }));
756
- put(&db, "b", json!({ "status": "active", "role": "member" }));
757
-
758
- let candidates = candidates(&db, "status", json!("active")).expect("indexed");
759
- assert_eq!(
760
- candidates,
761
- vec!["items:a".to_string(), "items:b".to_string()],
762
- "the index answers with candidates, both of which satisfy only the indexed condition",
763
- );
764
-
765
- let result = indexed_query(
766
- &db,
767
- &[("status", json!("active")), ("role", json!("admin"))],
768
- )
769
- .expect("indexed");
770
- assert_eq!(
771
- result,
772
- vec!["items:a".to_string()],
773
- "the predicate, not the index, decides which candidate matches",
774
- );
775
- }
776
-
777
- // ---------------------------------------------------------------------------
778
- // Concurrency
779
- // ---------------------------------------------------------------------------
780
-
781
- /// Mutations and indexed queries against the same collection, at the same time.
782
- ///
783
- /// The claim under test is not a scheduling order — there is no new isolation
784
- /// guarantee here and none is being asserted. It is that *every* result a query
785
- /// observes corresponds to a coherent committed state: a candidate the index
786
- /// produced is a record that exists, a record the predicate accepted really
787
- /// satisfies it, and a transaction is never half-visible. A defect in the
788
- /// mutation boundary would show up here as a candidate with no record behind it
789
- /// or a record whose bucket no longer describes it.
790
- #[test]
791
- fn concurrent_mutation_and_indexed_query_stay_coherent() {
792
- use std::sync::atomic::{AtomicBool, Ordering};
793
- use std::sync::Arc;
794
-
795
- let (_directory, db) = indexed();
796
- for step in 0..400 {
797
- put(
798
- &db,
799
- &format!("seed-{step:03}"),
800
- json!({ "status": if step % 2 == 0 { "active" } else { "archived" }, "tenantId": "tenant-0" }),
801
- );
802
- }
803
-
804
- let running = Arc::new(AtomicBool::new(true));
805
- let mut writers = Vec::new();
806
- for worker in 0..4 {
807
- let db = db.clone();
808
- let running = running.clone();
809
- writers.push(std::thread::spawn(move || {
810
- let mut step = 0usize;
811
- while running.load(Ordering::Relaxed) && step < 200 {
812
- let id = format!("items:churn-{worker}-{step}");
813
- let created = json!({ "status": "active", "tenantId": "tenant-0", "__version": 1 });
814
- db.insert(&id, &created).expect("insert");
815
- db.update(&id, &json!({ "status": "archived", "tenantId": "tenant-1", "__version": 1 }))
816
- .expect("update");
817
- db.compare_and_set_json(&id, 1, None, None, false, json!({ "status": "active", "tenantId": "tenant-1" }))
818
- .expect("cas");
819
- // A transaction whose two halves must never be seen apart.
820
- db.apply_atomic_transaction_guarded(
821
- &format!("pr35-race-{worker}-{step}"),
822
- None,
823
- None,
824
- &[],
825
- &[],
826
- &[
827
- mutation(&format!("{id}-tx-a"), Some(json!({ "status": "paired", "tenantId": "tenant-2", "pair": worker }))),
828
- mutation(&format!("{id}-tx-b"), Some(json!({ "status": "paired", "tenantId": "tenant-2", "pair": worker }))),
829
- ],
830
- None,
831
- )
832
- .expect("commit");
833
- db.delete(&id).expect("delete");
834
- step += 1;
835
- }
836
- step
837
- }));
838
- }
839
-
840
- let mut observations = 0usize;
841
- let mut failures: Vec<String> = Vec::new();
842
- for _ in 0..400 {
843
- // Every record the indexed execution reaches must actually hold the
844
- // value it was bucketed under.
845
- //
846
- // The check runs *inside* the execution, because candidate selection and
847
- // the record read have to observe one state to mean anything. Copying
848
- // candidate keys out and reading the records afterwards would be two
849
- // acquisitions of the state lock with a writer between them, and the
850
- // dangling key that produces is an artifact of asking the question in
851
- // two parts — not a defect in the index. The query path does not ask it
852
- // in two parts, and neither does this.
853
- for (field, value) in [("status", json!("active")), ("tenantId", json!("tenant-1"))] {
854
- // The predicate keeps everything the index reached, so what comes
855
- // back is the candidate set itself, read coherently.
856
- let reached = db
857
- .query_collection_by_equality("items", &[(field, &value)], |_| true)
858
- .expect("indexed query")
859
- .expect("the field is indexed");
860
- for row in reached {
861
- if row.value.get(field) != Some(&value) {
862
- failures.push(format!(
863
- "{} was reached through the {field} bucket for {value} but does not hold it",
864
- row.key,
865
- ));
866
- }
867
- }
868
- }
869
-
870
- let paired = indexed_query(&db, &[("status", json!("paired"))]).expect("indexed");
871
- let mut halves: BTreeMap<String, usize> = BTreeMap::new();
872
- for key in &paired {
873
- let sibling = key
874
- .strip_suffix("-tx-a")
875
- .or_else(|| key.strip_suffix("-tx-b"))
876
- .unwrap_or(key)
877
- .to_string();
878
- *halves.entry(sibling).or_default() += 1;
879
- }
880
- // A pair is written by one transaction. Seeing exactly one half would
881
- // mean a partially committed transaction became queryable.
882
- for (sibling, count) in halves {
883
- if count != 2 {
884
- failures.push(format!(
885
- "transaction pair {sibling} was observed {count} times, not 2"
886
- ));
887
- }
888
- }
889
- observations += 1;
890
- }
891
-
892
- running.store(false, Ordering::Relaxed);
893
- let cycles: usize = writers
894
- .into_iter()
895
- .map(|handle| handle.join().expect("writer"))
896
- .sum();
897
-
898
- assert!(cycles > 0, "the writers actually mutated the collection");
899
- assert!(observations > 0, "the reader actually queried");
900
- assert_eq!(failures, Vec::<String>::new());
901
- assert_index_matches_state(&db, "concurrent mutation and query");
902
- }
903
-
904
- /// Indexed and scan execution must still agree once the churn has stopped, on a
905
- /// state that concurrency produced rather than a test wrote by hand.
906
- #[test]
907
- fn indexed_and_scan_execution_agree_on_a_concurrently_built_state() {
908
- use std::sync::Arc;
909
-
910
- let (_directory, db) = indexed();
911
- let mut writers = Vec::new();
912
- for worker in 0..4 {
913
- let db = Arc::new(db.clone());
914
- writers.push(std::thread::spawn(move || {
915
- for step in 0..150 {
916
- let id = format!("items:w{worker}-{step:03}");
917
- db.insert(
918
- &id,
919
- &json!({ "status": if step % 3 == 0 { "active" } else { "archived" }, "tenantId": format!("tenant-{}", step % 5) }),
920
- )
921
- .expect("insert");
922
- if step % 7 == 0 {
923
- db.delete(&id).expect("delete");
924
- }
925
- }
926
- }));
927
- }
928
- for writer in writers {
929
- writer.join().expect("writer");
930
- }
931
-
932
- assert_index_matches_state(&db, "four concurrent writers");
933
- for conditions in [
934
- vec![("status", json!("active"))],
935
- vec![("tenantId", json!("tenant-3"))],
936
- vec![
937
- ("status", json!("archived")),
938
- ("tenantId", json!("tenant-2")),
939
- ],
940
- ] {
941
- assert_eq!(
942
- indexed_query(&db, &conditions).expect("indexed"),
943
- scan(&db, &conditions),
944
- "executions disagreed on {conditions:?} after concurrent writes",
945
- );
946
- }
947
- }