@feltdb/core 0.15.0 → 0.16.1

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 (248) hide show
  1. package/dist/agent-state.d.ts +20 -0
  2. package/dist/agent-state.d.ts.map +1 -1
  3. package/dist/agent-state.js +36 -0
  4. package/dist/build-identity.json +12 -4
  5. package/dist/cli/commands.js +187 -4
  6. package/dist/cli/publish-engine.js +116 -16
  7. package/dist/collection.d.ts.map +1 -1
  8. package/dist/collection.js +20 -2
  9. package/dist/create/build-identity.json +12 -4
  10. package/dist/create/managed-account.js +93 -35
  11. package/dist/create/package-versions.js +1 -1
  12. package/dist/create/server-source/Cargo.lock +6 -509
  13. package/dist/create/server-source/Cargo.toml +2 -13
  14. package/dist/create/server-source/crates/feltdb-client/Cargo.toml +1 -1
  15. package/dist/create/server-source/crates/feltdb-server/Cargo.toml +1 -1
  16. package/dist/create/server-source/crates/feltdb-server/src/main.rs +356 -24
  17. package/dist/create/server-source/crates/feltdb-server/tests/rust_client.rs +10 -1
  18. package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +1 -1
  19. package/dist/db.d.ts +11 -1
  20. package/dist/db.d.ts.map +1 -1
  21. package/dist/db.js +15 -2
  22. package/dist/error-codes.d.ts +5 -0
  23. package/dist/error-codes.d.ts.map +1 -1
  24. package/dist/error-codes.js +9 -1
  25. package/dist/http-db.d.ts +16 -0
  26. package/dist/http-db.d.ts.map +1 -1
  27. package/dist/http-db.js +36 -6
  28. package/dist/managed-recovery.d.ts +6 -0
  29. package/dist/managed-recovery.d.ts.map +1 -1
  30. package/dist/managed-recovery.js +9 -0
  31. package/dist/studio-app/assets/{feltdb_wasm-BjM1ylL3.js → feltdb_wasm-BUIxCuTR.js} +1 -1
  32. package/dist/studio-app/assets/feltdb_wasm_bg-C3D6y6DK.wasm +0 -0
  33. package/dist/studio-app/assets/index-BhXYAhgS.js +37 -0
  34. package/dist/studio-app/index.html +1 -1
  35. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  36. package/dist/wasm/package.json +1 -1
  37. package/dist/workload.d.ts +55 -3
  38. package/dist/workload.d.ts.map +1 -1
  39. package/dist/workload.js +23 -0
  40. package/dist/workspace/workspace-connection.d.ts.map +1 -1
  41. package/dist/workspace/workspace-connection.js +14 -3
  42. package/package.json +1 -1
  43. package/dist/create/server-source/crates/feltdb/Cargo.lock +0 -175
  44. package/dist/create/server-source/crates/feltdb/Cargo.toml +0 -102
  45. package/dist/create/server-source/crates/feltdb/LICENSE +0 -21
  46. package/dist/create/server-source/crates/feltdb/README.md +0 -75
  47. package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +0 -370
  48. package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +0 -589
  49. package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +0 -247
  50. package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +0 -440
  51. package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +0 -275
  52. package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +0 -381
  53. package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +0 -467
  54. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +0 -489
  55. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +0 -281
  56. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +0 -606
  57. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +0 -531
  58. package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +0 -462
  59. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +0 -505
  60. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +0 -484
  61. package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +0 -758
  62. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +0 -421
  63. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +0 -329
  64. package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +0 -1705
  65. package/dist/create/server-source/crates/feltdb/src/acquisition.rs +0 -286
  66. package/dist/create/server-source/crates/feltdb/src/admission.rs +0 -185
  67. package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +0 -507
  68. package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +0 -539
  69. package/dist/create/server-source/crates/feltdb/src/agent_state.rs +0 -320
  70. package/dist/create/server-source/crates/feltdb/src/agent_transition_contract.rs +0 -1545
  71. package/dist/create/server-source/crates/feltdb/src/analytics.rs +0 -521
  72. package/dist/create/server-source/crates/feltdb/src/application.rs +0 -2674
  73. package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +0 -1070
  74. package/dist/create/server-source/crates/feltdb/src/authority.rs +0 -479
  75. package/dist/create/server-source/crates/feltdb/src/authority_failover.rs +0 -847
  76. package/dist/create/server-source/crates/feltdb/src/authorization.rs +0 -1030
  77. package/dist/create/server-source/crates/feltdb/src/authorization_security_tests.rs +0 -1122
  78. package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority-client.rs +0 -32
  79. package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority.rs +0 -19
  80. package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +0 -1474
  81. package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +0 -8
  82. package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +0 -418
  83. package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +0 -482
  84. package/dist/create/server-source/crates/feltdb/src/capability.rs +0 -903
  85. package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +0 -263
  86. package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +0 -77
  87. package/dist/create/server-source/crates/feltdb/src/causal_backlog_bound.rs +0 -496
  88. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +0 -2574
  89. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +0 -212
  90. package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +0 -422
  91. package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +0 -445
  92. package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +0 -465
  93. package/dist/create/server-source/crates/feltdb/src/convergence.rs +0 -634
  94. package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +0 -427
  95. package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +0 -376
  96. package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +0 -365
  97. package/dist/create/server-source/crates/feltdb/src/cron.rs +0 -294
  98. package/dist/create/server-source/crates/feltdb/src/dedup_bound_investigation.rs +0 -483
  99. package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +0 -477
  100. package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +0 -1003
  101. package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +0 -1334
  102. package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +0 -368
  103. package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +0 -231
  104. package/dist/create/server-source/crates/feltdb/src/durable_operation_identity.rs +0 -482
  105. package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +0 -203
  106. package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +0 -317
  107. package/dist/create/server-source/crates/feltdb/src/equality_index.rs +0 -595
  108. package/dist/create/server-source/crates/feltdb/src/execution.rs +0 -426
  109. package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +0 -214
  110. package/dist/create/server-source/crates/feltdb/src/indexing.rs +0 -776
  111. package/dist/create/server-source/crates/feltdb/src/lib.rs +0 -7365
  112. package/dist/create/server-source/crates/feltdb/src/managed_cas_tests.rs +0 -234
  113. package/dist/create/server-source/crates/feltdb/src/materialization.rs +0 -184
  114. package/dist/create/server-source/crates/feltdb/src/metrics.rs +0 -266
  115. package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +0 -310
  116. package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +0 -736
  117. package/dist/create/server-source/crates/feltdb/src/observability.rs +0 -379
  118. package/dist/create/server-source/crates/feltdb/src/operation.rs +0 -290
  119. package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +0 -439
  120. package/dist/create/server-source/crates/feltdb/src/operation_log.rs +0 -349
  121. package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +0 -332
  122. package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +0 -718
  123. package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +0 -487
  124. package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +0 -166
  125. package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +0 -289
  126. package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +0 -566
  127. package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +0 -3391
  128. package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +0 -203
  129. package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +0 -261
  130. package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +0 -502
  131. package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +0 -216
  132. package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +0 -303
  133. package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +0 -323
  134. package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +0 -531
  135. package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +0 -406
  136. package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +0 -436
  137. package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +0 -387
  138. package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +0 -378
  139. package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +0 -359
  140. package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +0 -372
  141. package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +0 -404
  142. package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +0 -376
  143. package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +0 -461
  144. package/dist/create/server-source/crates/feltdb/src/policy_evaluation.rs +0 -2125
  145. package/dist/create/server-source/crates/feltdb/src/production_api.rs +0 -418
  146. package/dist/create/server-source/crates/feltdb/src/provenance.rs +0 -207
  147. package/dist/create/server-source/crates/feltdb/src/query_execution_diagnostics.rs +0 -126
  148. package/dist/create/server-source/crates/feltdb/src/query_performance.rs +0 -218
  149. package/dist/create/server-source/crates/feltdb/src/read_basis.rs +0 -257
  150. package/dist/create/server-source/crates/feltdb/src/references.rs +0 -404
  151. package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +0 -401
  152. package/dist/create/server-source/crates/feltdb/src/replica_acknowledgements.rs +0 -501
  153. package/dist/create/server-source/crates/feltdb/src/replica_membership.rs +0 -680
  154. package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +0 -163
  155. package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +0 -133
  156. package/dist/create/server-source/crates/feltdb/src/routing.rs +0 -409
  157. package/dist/create/server-source/crates/feltdb/src/semantic_decision.rs +0 -2029
  158. package/dist/create/server-source/crates/feltdb/src/semantic_query.rs +0 -599
  159. package/dist/create/server-source/crates/feltdb/src/sharding.rs +0 -549
  160. package/dist/create/server-source/crates/feltdb/src/state_conflict_contract.rs +0 -516
  161. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +0 -6003
  162. package/dist/create/server-source/crates/feltdb/src/state_diff_contract.rs +0 -222
  163. package/dist/create/server-source/crates/feltdb/src/state_facade.rs +0 -320
  164. package/dist/create/server-source/crates/feltdb/src/state_hash.rs +0 -291
  165. package/dist/create/server-source/crates/feltdb/src/state_model.rs +0 -4158
  166. package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +0 -379
  167. package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +0 -673
  168. package/dist/create/server-source/crates/feltdb/src/storage.rs +0 -273
  169. package/dist/create/server-source/crates/feltdb/src/submission.rs +0 -471
  170. package/dist/create/server-source/crates/feltdb/src/sync.rs +0 -495
  171. package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +0 -822
  172. package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +0 -433
  173. package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +0 -462
  174. package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +0 -965
  175. package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +0 -1120
  176. package/dist/create/server-source/crates/feltdb/src/transactions.rs +0 -643
  177. package/dist/create/server-source/crates/feltdb/src/trigger.rs +0 -390
  178. package/dist/create/server-source/crates/feltdb/src/trigger_contract.rs +0 -749
  179. package/dist/create/server-source/crates/feltdb/src/versioned_object_substrate.rs +0 -708
  180. package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +0 -1269
  181. package/dist/create/server-source/crates/feltdb/src/workflow.rs +0 -713
  182. package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +0 -510
  183. package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +0 -354
  184. package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +0 -594
  185. package/dist/create/server-source/crates/feltdb/src/workload.rs +0 -4222
  186. package/dist/create/server-source/crates/feltdb/src/workload_diagnostics.rs +0 -456
  187. package/dist/create/server-source/crates/feltdb/tests/agent_state_native.rs +0 -1188
  188. package/dist/create/server-source/crates/feltdb/tests/agent_transition_contract_conformance.rs +0 -2884
  189. package/dist/create/server-source/crates/feltdb/tests/authority_process.rs +0 -297
  190. package/dist/create/server-source/crates/feltdb/tests/authority_transaction_identity.rs +0 -534
  191. package/dist/create/server-source/crates/feltdb/tests/bounded_read_contract.rs +0 -132
  192. package/dist/create/server-source/crates/feltdb/tests/branching_evidence.rs +0 -299
  193. package/dist/create/server-source/crates/feltdb/tests/common/mod.rs +0 -96
  194. package/dist/create/server-source/crates/feltdb/tests/compaction_stall_contract.rs +0 -272
  195. package/dist/create/server-source/crates/feltdb/tests/crash_durability_contract.rs +0 -467
  196. package/dist/create/server-source/crates/feltdb/tests/current_revision_authority_evidence.rs +0 -309
  197. package/dist/create/server-source/crates/feltdb/tests/durable_backup_contract.rs +0 -445
  198. package/dist/create/server-source/crates/feltdb/tests/durable_corruption_contract.rs +0 -518
  199. package/dist/create/server-source/crates/feltdb/tests/durable_format_compatibility.rs +0 -392
  200. package/dist/create/server-source/crates/feltdb/tests/feltdb_state_boundary_tests.rs +0 -850
  201. package/dist/create/server-source/crates/feltdb/tests/fixtures/agent_transition_corpus.json +0 -23340
  202. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_conflict_contract_corpus.json +0 -1916
  203. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_diff_contract_corpus.json +0 -1878
  204. package/dist/create/server-source/crates/feltdb/tests/fixtures/trigger_contract_corpus.json +0 -1862
  205. package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_evidence.rs +0 -123
  206. package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_substrate_completion.rs +0 -299
  207. package/dist/create/server-source/crates/feltdb/tests/local_process_ownership.rs +0 -69
  208. package/dist/create/server-source/crates/feltdb/tests/managed_incident_regression.rs +0 -191
  209. package/dist/create/server-source/crates/feltdb/tests/operational_health_contract.rs +0 -278
  210. package/dist/create/server-source/crates/feltdb/tests/pr34_query_collection.rs +0 -234
  211. package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +0 -947
  212. package/dist/create/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +0 -404
  213. package/dist/create/server-source/crates/feltdb/tests/pr8_vocabulary_assessment.rs +0 -916
  214. package/dist/create/server-source/crates/feltdb/tests/pr9_phase2_boundary_tests.rs +0 -1045
  215. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3a_path_a_tests.rs +0 -347
  216. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_authorized_mutations.rs +0 -361
  217. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_role_based_authorization.rs +0 -330
  218. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_simple_auth_delete.rs +0 -93
  219. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_team_delete_role_authorization.rs +0 -622
  220. package/dist/create/server-source/crates/feltdb/tests/pr9_teams_role_based_access.rs +0 -517
  221. package/dist/create/server-source/crates/feltdb/tests/production_certification.rs +0 -1153
  222. package/dist/create/server-source/crates/feltdb/tests/production_contract.rs +0 -771
  223. package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +0 -1383
  224. package/dist/create/server-source/crates/feltdb/tests/reconciliation_application.rs +0 -868
  225. package/dist/create/server-source/crates/feltdb/tests/reconciliation_wire_format_evidence.rs +0 -221
  226. package/dist/create/server-source/crates/feltdb/tests/replicated_history_contract.rs +0 -417
  227. package/dist/create/server-source/crates/feltdb/tests/resource_scoped_revisions.rs +0 -338
  228. package/dist/create/server-source/crates/feltdb/tests/revision_identity_contract.rs +0 -1039
  229. package/dist/create/server-source/crates/feltdb/tests/revision_model_decision.rs +0 -739
  230. package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +0 -452
  231. package/dist/create/server-source/crates/feltdb/tests/saas_authorization_integration.rs +0 -81
  232. package/dist/create/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +0 -438
  233. package/dist/create/server-source/crates/feltdb/tests/semantic_decision.rs +0 -1165
  234. package/dist/create/server-source/crates/feltdb/tests/semantic_query.rs +0 -767
  235. package/dist/create/server-source/crates/feltdb/tests/semantic_query_contract.rs +0 -363
  236. package/dist/create/server-source/crates/feltdb/tests/state_conflict_contract_conformance.rs +0 -1799
  237. package/dist/create/server-source/crates/feltdb/tests/state_diff_contract_conformance.rs +0 -1316
  238. package/dist/create/server-source/crates/feltdb/tests/state_model_integration.rs +0 -358
  239. package/dist/create/server-source/crates/feltdb/tests/state_persistence_integration.rs +0 -365
  240. package/dist/create/server-source/crates/feltdb/tests/state_ref_authority.rs +0 -918
  241. package/dist/create/server-source/crates/feltdb/tests/state_store_boundary_evidence.rs +0 -299
  242. package/dist/create/server-source/crates/feltdb/tests/storage_independent_versioned_object_substrate.rs +0 -328
  243. package/dist/create/server-source/crates/feltdb/tests/sync_divergence_evidence.rs +0 -255
  244. package/dist/create/server-source/crates/feltdb/tests/three_way_input_boundary_evidence.rs +0 -249
  245. package/dist/create/server-source/crates/feltdb/tests/trigger_contract_conformance.rs +0 -994
  246. package/dist/create/server-source/crates/feltdb/tests/workload_envelope_contract.rs +0 -484
  247. package/dist/studio-app/assets/feltdb_wasm_bg-TseJn2Ax.wasm +0 -0
  248. package/dist/studio-app/assets/index-1wd0uKua.js +0 -30
@@ -1,1705 +0,0 @@
1
- /// Acceptance tests for local-first synchronization
2
- /// These tests verify that the FeltDB sync engine correctly handles
3
- /// multi-instance synchronization scenarios
4
-
5
- #[cfg(test)]
6
- mod sync_acceptance_tests {
7
- use crate::{ChangeLog, ConflictDetector, Operation, OperationType, SyncMessage, SyncState};
8
-
9
- /// Helper to create a test operation
10
- fn make_operation(
11
- op_id: u64,
12
- instance_id: &str,
13
- sequence: u64,
14
- key: &str,
15
- value: Option<serde_json::Value>,
16
- ) -> Operation {
17
- if let Some(val) = value {
18
- Operation::update(
19
- op_id,
20
- instance_id.to_string(),
21
- sequence,
22
- key.to_string(),
23
- val,
24
- "Task".to_string(),
25
- "tasks".to_string(),
26
- )
27
- } else {
28
- Operation::delete(
29
- op_id,
30
- instance_id.to_string(),
31
- sequence,
32
- key.to_string(),
33
- "Task".to_string(),
34
- "tasks".to_string(),
35
- )
36
- }
37
- }
38
-
39
- #[test]
40
- fn test_a_two_instances_a_writes_sync_b_sees_change() {
41
- // Scenario: FeltDB A creates a task, syncs to B, B observes it
42
- // This is the foundational sync test - operations flow from A to B
43
-
44
- // Simulate instance A creating a task
45
- let instance_a_id = "inst-a";
46
- let task_op = make_operation(
47
- 1,
48
- instance_a_id,
49
- 1,
50
- "tasks:123",
51
- Some(serde_json::json!({
52
- "id": "123",
53
- "title": "Buy groceries",
54
- "status": "active"
55
- })),
56
- );
57
-
58
- // Instance A's change log records the operation
59
- let mut changelog_a = ChangeLog::new();
60
- changelog_a.add_operation(task_op.clone());
61
-
62
- // B requests pending operations from A
63
- let pending = changelog_a.pending_operations();
64
- assert_eq!(pending.len(), 1);
65
- assert_eq!(pending[0].key, "tasks:123");
66
-
67
- // B applies the operation - verifying the state change
68
- assert_eq!(pending[0].op_type, OperationType::Update);
69
- assert!(pending[0].value.is_some());
70
-
71
- println!("✓ Test A passed: Operations flow A → B successfully");
72
- }
73
-
74
- #[test]
75
- fn test_b_reverse_direction_b_writes_a_sees_change() {
76
- // Scenario: FeltDB B modifies the task, syncs to A, A observes the change
77
- // Tests bidirectional sync
78
-
79
- // Instance B modifies the task
80
- let instance_b_id = "inst-b";
81
- let update_op = make_operation(
82
- 2,
83
- instance_b_id,
84
- 1,
85
- "tasks:123",
86
- Some(serde_json::json!({
87
- "id": "123",
88
- "title": "Buy groceries",
89
- "status": "completed"
90
- })),
91
- );
92
-
93
- // B's change log records the modification
94
- let mut changelog_b = ChangeLog::new();
95
- changelog_b.add_operation(update_op.clone());
96
-
97
- // A requests pending operations from B
98
- let pending = changelog_b.pending_operations();
99
- assert_eq!(pending.len(), 1);
100
- assert_eq!(pending[0].instance_id, "inst-b");
101
- assert_eq!(pending[0].sequence, 1);
102
-
103
- println!("✓ Test B passed: Reverse direction B → A works correctly");
104
- }
105
-
106
- #[test]
107
- fn test_c_offline_mode_with_100_mutations() {
108
- // Scenario: Instance A continues working offline, creates 100 mutations
109
- // When network returns, all 100 should be ready to sync
110
-
111
- let instance_a_id = "inst-a";
112
- let mut changelog_a = ChangeLog::new();
113
-
114
- // Simulate 100 mutations while offline
115
- for i in 1..=100 {
116
- let op = make_operation(
117
- i as u64,
118
- instance_a_id,
119
- i as u64,
120
- &format!("tasks:{}", i),
121
- Some(serde_json::json!({
122
- "id": format!("{}", i),
123
- "title": format!("Task {}", i),
124
- "status": "active"
125
- })),
126
- );
127
- changelog_a.add_operation(op);
128
- }
129
-
130
- // All 100 operations are pending
131
- let pending = changelog_a.pending_operations();
132
- assert_eq!(pending.len(), 100);
133
-
134
- // Verify all operations have correct sequence numbers
135
- for (idx, op) in pending.iter().enumerate() {
136
- assert_eq!(op.sequence, (idx + 1) as u64);
137
- }
138
-
139
- println!("✓ Test C passed: 100 offline mutations stored correctly");
140
- }
141
-
142
- #[test]
143
- fn test_d_restart_with_pending_operations() {
144
- // Scenario: Instance A has pending operations, closes, reopens
145
- // Pending operations should still exist and be ready to sync
146
- // Note: This test verifies the data structures; actual persistence
147
- // is handled by the storage layer
148
-
149
- let instance_a_id = "inst-a";
150
- let mut changelog_a = ChangeLog::new();
151
-
152
- // Add 5 operations before "restart"
153
- for i in 1..=5 {
154
- let op = make_operation(
155
- i as u64,
156
- instance_a_id,
157
- i as u64,
158
- &format!("tasks:{}", i),
159
- Some(serde_json::json!({"title": format!("Task {}", i)})),
160
- );
161
- changelog_a.add_operation(op);
162
- }
163
-
164
- let pending_before = changelog_a.pending_operations();
165
- assert_eq!(pending_before.len(), 5);
166
-
167
- // After "restart", pending operations are still there
168
- // (In a real scenario, these would be reloaded from storage)
169
- let pending_after = changelog_a.pending_operations();
170
- assert_eq!(pending_after.len(), 5);
171
- assert_eq!(pending_after[0].sequence, 1);
172
- assert_eq!(pending_after[4].sequence, 5);
173
-
174
- println!("✓ Test D passed: Pending operations survive restart");
175
- }
176
-
177
- #[test]
178
- fn test_e_duplicate_delivery_handling() {
179
- // Scenario: The same operation is delivered 3 times
180
- // Instance B must apply it only once
181
- // This requires idempotency tracking
182
-
183
- let instance_a_id = "inst-a";
184
- let task_op = make_operation(
185
- 1,
186
- instance_a_id,
187
- 1,
188
- "tasks:123",
189
- Some(serde_json::json!({"title": "Important task"})),
190
- );
191
-
192
- // B receives the same operation 3 times
193
- // B tracks by (instance_id, sequence) to detect duplicates
194
- let mut seen_ops: std::collections::HashMap<(String, u64), bool> =
195
- std::collections::HashMap::new();
196
-
197
- for _ in 0..3 {
198
- let key = (task_op.instance_id.clone(), task_op.sequence);
199
- if seen_ops.contains_key(&key) {
200
- // Duplicate detected and skipped
201
- continue;
202
- }
203
- seen_ops.insert(key, true);
204
- }
205
-
206
- // Only one unique operation was applied
207
- assert_eq!(seen_ops.len(), 1);
208
-
209
- println!("✓ Test E passed: Duplicate operations detected and skipped");
210
- }
211
-
212
- #[test]
213
- fn test_f_interrupted_sync_recovery() {
214
- // Scenario: Sync of 500 operations starts, connection dies at #237
215
- // When reconnecting, sync should resume at #238
216
- // This requires sync state tracking
217
-
218
- let mut changelog = ChangeLog::new();
219
-
220
- // Create 500 operations
221
- for i in 1..=500 {
222
- let op = make_operation(
223
- i as u64,
224
- "inst-a",
225
- i as u64,
226
- &format!("tasks:{}", i),
227
- Some(serde_json::json!({"title": format!("Task {}", i)})),
228
- );
229
- changelog.add_operation(op);
230
- }
231
-
232
- // Acknowledge operations up to sequence 237
233
- changelog.acknowledge_peer("peer-b".to_string(), 237);
234
-
235
- // Remaining operations are pending delivery
236
- let all_pending = changelog.pending_operations();
237
- let last_acked = changelog.last_peer_sequence("peer-b");
238
-
239
- assert_eq!(last_acked, 237);
240
- assert_eq!(all_pending.len(), 500);
241
-
242
- // Filter to get operations after 237
243
- let remaining: Vec<_> = all_pending
244
- .iter()
245
- .filter(|op| op.sequence > last_acked)
246
- .collect();
247
-
248
- assert_eq!(remaining.len(), 263);
249
- assert_eq!(remaining[0].sequence, 238);
250
-
251
- println!("✓ Test F passed: Interrupted sync resumes correctly");
252
- }
253
-
254
- #[test]
255
- fn test_g_reactive_propagation() {
256
- // Scenario: Operation is synced from A to B
257
- // B's reactive graph should update without explicit refetch
258
- // This test verifies that synced operations follow the same path
259
- // as local mutations
260
-
261
- let mut detector = ConflictDetector::new();
262
-
263
- // Local operation in instance A
264
- let local_op = make_operation(
265
- 1,
266
- "inst-a",
267
- 1,
268
- "tasks:123",
269
- Some(serde_json::json!({"status": "active"})),
270
- );
271
-
272
- // Synced operation in instance B (remote change)
273
- let synced_op = make_operation(
274
- 2,
275
- "inst-b",
276
- 1,
277
- "tasks:123",
278
- Some(serde_json::json!({"status": "completed"})),
279
- );
280
-
281
- // Conflict detected - both modified the same entity
282
- let conflict = detector.detect_conflict(&local_op, &synced_op);
283
- assert!(conflict.is_some());
284
-
285
- let detected = conflict.unwrap();
286
- assert_eq!(detected.entity_key, "tasks:123");
287
- assert_eq!(detected.local_op.instance_id, "inst-a");
288
- assert_eq!(detected.remote_op.instance_id, "inst-b");
289
-
290
- // The reactive graph would then:
291
- // 1. Receive the conflict information
292
- // 2. Emit change event for the entity
293
- // 3. UI subscribers receive update
294
- // 4. State is consistent across both instances
295
-
296
- println!("✓ Test G passed: Synced operations trigger reactive propagation");
297
- }
298
-
299
- #[test]
300
- fn test_sync_state_transitions() {
301
- // Verify sync state tracks connection properly
302
- let mut sync_state = SyncState::new("inst-a".to_string());
303
-
304
- assert!(!sync_state.is_connected);
305
- assert_eq!(sync_state.connected_peers.len(), 0);
306
-
307
- sync_state.add_peer("peer-b".to_string());
308
- assert!(sync_state.is_connected);
309
- assert_eq!(sync_state.connected_peers.len(), 1);
310
-
311
- sync_state.add_peer("peer-c".to_string());
312
- assert_eq!(sync_state.connected_peers.len(), 2);
313
-
314
- // Metrics tracking
315
- sync_state.increment_sent(100);
316
- assert_eq!(sync_state.operations_sent, 100);
317
-
318
- sync_state.increment_received(150);
319
- assert_eq!(sync_state.operations_received, 150);
320
-
321
- sync_state.increment_conflicts();
322
- assert_eq!(sync_state.conflicts_detected, 1);
323
-
324
- println!("✓ Sync state transitions working correctly");
325
- }
326
-
327
- #[test]
328
- fn test_serialization_round_trip() {
329
- // Verify operations can be serialized and deserialized
330
- // This is critical for network transmission
331
-
332
- let op = make_operation(
333
- 42,
334
- "inst-xyz",
335
- 7,
336
- "tasks:999",
337
- Some(serde_json::json!({"complex": {"nested": "value"}})),
338
- );
339
-
340
- let json = serde_json::to_string(&op).expect("serialize");
341
- let deserialized: Operation = serde_json::from_str(&json).expect("deserialize");
342
-
343
- assert_eq!(deserialized.op_id, 42);
344
- assert_eq!(deserialized.instance_id, "inst-xyz");
345
- assert_eq!(deserialized.sequence, 7);
346
- assert_eq!(deserialized.key, "tasks:999");
347
- assert_eq!(deserialized.op_type, OperationType::Update);
348
-
349
- println!("✓ Serialization round-trip successful");
350
- }
351
-
352
- #[test]
353
- fn test_sync_protocol_messages() {
354
- // Verify sync protocol messages can be created and serialized
355
-
356
- let msg1 = SyncMessage::Handshake {
357
- instance_id: "inst-a".to_string(),
358
- version: "0.1.0".to_string(),
359
- };
360
-
361
- let msg2 = SyncMessage::StateVersion {
362
- instance_id: "inst-b".to_string(),
363
- sequence: 100,
364
- };
365
-
366
- let msg3 = SyncMessage::MissingOperations {
367
- instance_id: "inst-a".to_string(),
368
- since_sequence: 50,
369
- };
370
-
371
- let msg4 = SyncMessage::Acknowledgement {
372
- instance_id: "inst-b".to_string(),
373
- up_to_sequence: 100,
374
- };
375
-
376
- // Verify all can be serialized
377
- assert!(serde_json::to_string(&msg1).is_ok());
378
- assert!(serde_json::to_string(&msg2).is_ok());
379
- assert!(serde_json::to_string(&msg3).is_ok());
380
- assert!(serde_json::to_string(&msg4).is_ok());
381
-
382
- println!("✓ All sync protocol messages serializable");
383
- }
384
- }
385
-
386
- /// Acceptance tests for event-driven execution runtime
387
- #[cfg(test)]
388
- mod execution_acceptance_tests {
389
- use crate::{
390
- CronSchedule, CronScheduler, Execution, ExecutionQueue, ExecutionStatus, Operation,
391
- Trigger, TriggerFilter, TriggerRegistry,
392
- };
393
-
394
- /// Helper to create a test order operation
395
- fn make_order_operation(
396
- op_id: u64,
397
- instance_id: &str,
398
- sequence: u64,
399
- order_id: &str,
400
- total: f64,
401
- ) -> Operation {
402
- Operation::insert(
403
- op_id,
404
- instance_id.to_string(),
405
- sequence,
406
- format!("orders:{}", order_id),
407
- serde_json::json!({
408
- "id": order_id,
409
- "total": total,
410
- "status": "pending"
411
- }),
412
- "Order".to_string(),
413
- "orders".to_string(),
414
- )
415
- }
416
-
417
- /// Test A: Mutation trigger
418
- /// insert Order → OrderCreated → capability executes
419
- #[test]
420
- fn test_a_mutation_trigger() {
421
- let mut registry = TriggerRegistry::new();
422
- let mut queue = ExecutionQueue::new();
423
-
424
- // Register trigger: when Order is inserted, execute "process_order"
425
- let trigger = Trigger::new(
426
- "trigger-order-created".to_string(),
427
- "process_order".to_string(),
428
- "OrderCreated".to_string(),
429
- "orders".to_string(),
430
- );
431
- registry.register(trigger);
432
-
433
- // Create order operation
434
- let op = make_order_operation(1, "inst-a", 1, "123", 100.0);
435
-
436
- // Find matching triggers
437
- let matching = registry.find_matching(&op);
438
- assert_eq!(matching.len(), 1);
439
- assert_eq!(matching[0].capability, "process_order");
440
-
441
- // Create execution
442
- let exec = Execution::new(
443
- "exec-1".to_string(),
444
- matching[0].trigger_id.clone(),
445
- op.clone(),
446
- matching[0].capability.clone(),
447
- );
448
- assert_eq!(exec.status, ExecutionStatus::Pending);
449
-
450
- queue.add_execution(exec).unwrap();
451
- let pending = queue.get_pending();
452
- assert_eq!(pending.len(), 1);
453
-
454
- println!("✓ Test A passed: Mutation trigger creates execution");
455
- }
456
-
457
- /// Test B: Filtered trigger
458
- /// Order $50 → nothing
459
- /// Order $500 → capability executes
460
- #[test]
461
- fn test_b_filtered_trigger() {
462
- let mut registry = TriggerRegistry::new();
463
-
464
- // Register trigger: when Order > $100, execute "send_discount"
465
- let filter = TriggerFilter {
466
- field: "total".to_string(),
467
- operator: ">".to_string(),
468
- value: serde_json::json!(100),
469
- };
470
-
471
- let trigger = Trigger::with_filter(
472
- "trigger-high-value".to_string(),
473
- "send_discount".to_string(),
474
- "OrderCreated".to_string(),
475
- "orders".to_string(),
476
- filter,
477
- );
478
- registry.register(trigger);
479
-
480
- // Order with total $50 - should NOT match
481
- let op1 = make_order_operation(1, "inst-a", 1, "123", 50.0);
482
- let matching1 = registry.find_matching(&op1);
483
- assert_eq!(matching1.len(), 0);
484
-
485
- // Order with total $500 - should match
486
- let op2 = make_order_operation(2, "inst-a", 2, "124", 500.0);
487
- let matching2 = registry.find_matching(&op2);
488
- assert_eq!(matching2.len(), 1);
489
-
490
- println!("✓ Test B passed: Filtered trigger applies correctly");
491
- }
492
-
493
- /// Test C: Cron
494
- /// scheduled time → CronEvent → capability executes
495
- #[test]
496
- fn test_c_cron_scheduling() {
497
- let mut scheduler = CronScheduler::new();
498
-
499
- // Register cron schedule: daily reports at midnight
500
- let schedule = CronSchedule::new(
501
- "cron-daily-reports".to_string(),
502
- "daily-reports".to_string(),
503
- "0 0 * * *".to_string(),
504
- "generate_reports".to_string(),
505
- );
506
- scheduler.register(schedule);
507
-
508
- // Simulate midnight timestamp
509
- let midnight_ms = 1704067200 * 1000;
510
-
511
- // Find executable schedules
512
- let executable = scheduler.find_executable(midnight_ms);
513
- assert_eq!(executable.len(), 1);
514
- assert_eq!(executable[0].capability, "generate_reports");
515
-
516
- // Create synthetic cron operation
517
- let op = CronScheduler::create_cron_operation(&executable[0], 1, 1, midnight_ms);
518
- assert_eq!(op.capability, "cron");
519
- assert_eq!(op.rust_type, "CronEvent");
520
-
521
- println!("✓ Test C passed: Cron produces execution events");
522
- }
523
-
524
- /// Test D: Persistence
525
- /// trigger → execution queued → FeltDB closes/reopens → execution resumes
526
- #[test]
527
- fn test_d_execution_persistence() {
528
- let mut queue1 = ExecutionQueue::new();
529
-
530
- // Create and queue execution
531
- let op = make_order_operation(1, "inst-a", 1, "123", 100.0);
532
- let exec = Execution::new(
533
- "exec-1".to_string(),
534
- "trigger-1".to_string(),
535
- op.clone(),
536
- "process_order".to_string(),
537
- );
538
-
539
- queue1.add_execution(exec).unwrap();
540
- assert_eq!(queue1.get_pending().len(), 1);
541
-
542
- // Serialize the queue
543
- let queue_json = serde_json::to_string(&queue1).expect("serialize queue");
544
-
545
- // Simulate database close/reopen by deserializing
546
- let queue2: ExecutionQueue = serde_json::from_str(&queue_json).expect("deserialize queue");
547
-
548
- // Execution should still be pending
549
- let pending = queue2.get_pending();
550
- assert_eq!(pending.len(), 1);
551
- assert_eq!(pending[0].execution_id, "exec-1");
552
- assert_eq!(pending[0].status, ExecutionStatus::Pending);
553
-
554
- println!("✓ Test D passed: Executions persist across restarts");
555
- }
556
-
557
- /// Test E: Idempotency
558
- /// same operation → trigger delivered 10 times → one execution
559
- #[test]
560
- fn test_e_execution_idempotency() {
561
- let mut queue = ExecutionQueue::new();
562
-
563
- let op = make_order_operation(1, "inst-a", 1, "123", 100.0);
564
-
565
- // Try to create 10 executions from the same operation
566
- for i in 1..=10 {
567
- let exec = Execution::new(
568
- format!("exec-{}", i),
569
- "trigger-1".to_string(),
570
- op.clone(),
571
- "process_order".to_string(),
572
- );
573
-
574
- queue.add_execution(exec).unwrap();
575
- }
576
-
577
- // Only one execution should exist (idempotency)
578
- assert_eq!(queue.all().len(), 1);
579
-
580
- // Verify idempotency map blocks duplicate operations
581
- assert!(queue.has_execution_for_operation("inst-a", 1));
582
- let exec_id = queue.get_execution_for_operation("inst-a", 1).unwrap();
583
- assert_eq!(exec_id, "exec-1");
584
-
585
- println!("✓ Test E passed: Duplicate operations prevented by idempotency");
586
- }
587
-
588
- /// Test F: Retry
589
- /// execution fails → retry → success
590
- #[test]
591
- fn test_f_execution_retry() {
592
- let op = make_order_operation(1, "inst-a", 1, "123", 100.0);
593
-
594
- let mut exec = Execution::new(
595
- "exec-1".to_string(),
596
- "trigger-1".to_string(),
597
- op.clone(),
598
- "process_order".to_string(),
599
- );
600
-
601
- // First attempt: running
602
- exec.mark_running("inst-a".to_string());
603
- assert_eq!(exec.status, ExecutionStatus::Running);
604
-
605
- // First attempt: fails
606
- exec.mark_failed("Network timeout".to_string());
607
- assert_eq!(exec.status, ExecutionStatus::Failed);
608
- assert!(exec.can_retry());
609
-
610
- // Schedule retry
611
- exec.mark_retry_scheduled();
612
- assert_eq!(exec.attempt, 2);
613
- assert_eq!(exec.status, ExecutionStatus::RetryScheduled);
614
-
615
- // Second attempt: succeeds
616
- exec.mark_running("inst-a".to_string());
617
- exec.mark_succeeded(serde_json::json!({"processed": true}));
618
- assert_eq!(exec.status, ExecutionStatus::Succeeded);
619
- assert!(exec.is_terminal());
620
-
621
- println!("✓ Test F passed: Retry mechanism works correctly");
622
- }
623
-
624
- /// Test G: Offline
625
- /// offline → mutation → trigger queued, network unavailable → state remains functional
626
- #[test]
627
- fn test_g_offline_execution() {
628
- let mut registry = TriggerRegistry::new();
629
-
630
- // Register trigger
631
- let trigger = Trigger::new(
632
- "trigger-1".to_string(),
633
- "process_order".to_string(),
634
- "OrderCreated".to_string(),
635
- "orders".to_string(),
636
- );
637
- registry.register(trigger);
638
-
639
- // Simulate offline: mutation occurs
640
- let op = make_order_operation(1, "inst-a", 1, "123", 100.0);
641
-
642
- // Even offline, triggers are matched
643
- let matching = registry.find_matching(&op);
644
- assert_eq!(matching.len(), 1);
645
-
646
- // Execution is queued even with no network
647
- let exec = Execution::new(
648
- "exec-1".to_string(),
649
- matching[0].trigger_id.clone(),
650
- op,
651
- matching[0].capability.clone(),
652
- );
653
- let mut queue = ExecutionQueue::new();
654
- queue.add_execution(exec).unwrap();
655
-
656
- // State is still functional
657
- let pending = queue.get_pending();
658
- assert_eq!(pending.len(), 1);
659
-
660
- println!("✓ Test G passed: Offline execution queueing works");
661
- }
662
-
663
- /// Test H: Sync + execution
664
- /// FeltDB A → mutation → sync → FeltDB B → trigger recognized → execution claimed once
665
- #[test]
666
- fn test_h_sync_execution_single_owner() {
667
- let mut registry = TriggerRegistry::new();
668
- let mut queue_a = ExecutionQueue::new();
669
- let mut queue_b = ExecutionQueue::new();
670
-
671
- // Both instances have same trigger registered
672
- let trigger = Trigger::new(
673
- "trigger-1".to_string(),
674
- "process_order".to_string(),
675
- "OrderCreated".to_string(),
676
- "orders".to_string(),
677
- );
678
- registry.register(trigger);
679
-
680
- // Instance A creates order
681
- let op = make_order_operation(1, "inst-a", 1, "123", 100.0);
682
-
683
- // Instance A creates execution with exec-1
684
- let matching = registry.find_matching(&op);
685
- let exec_a = Execution::new(
686
- "exec-1".to_string(),
687
- matching[0].trigger_id.clone(),
688
- op.clone(),
689
- matching[0].capability.clone(),
690
- );
691
- let id_a = queue_a.add_execution(exec_a).unwrap();
692
- assert_eq!(id_a, "exec-1");
693
-
694
- // Operation syncs to B
695
- // B recognizes same operation (by instance_id:sequence)
696
- // When B tries to add execution with different ID, it should get the FIRST ID back
697
- let matching_b = registry.find_matching(&op);
698
- let exec_b = Execution::new(
699
- "exec-2".to_string(), // Different ID than A
700
- matching_b[0].trigger_id.clone(),
701
- op.clone(),
702
- matching_b[0].capability.clone(),
703
- );
704
- let id_b = queue_b.add_execution(exec_b).unwrap();
705
-
706
- // Single-owner semantics: first execution ID wins
707
- // B should get the same operation, which maps to first execution
708
- assert_eq!(id_b, "exec-2"); // B creates its own, but...
709
-
710
- // The important part: within queue_b, trying to add SAME operation again returns exec-2
711
- assert!(queue_b.has_execution_for_operation("inst-a", 1));
712
- assert_eq!(
713
- queue_b.get_execution_for_operation("inst-a", 1).unwrap(),
714
- "exec-2"
715
- );
716
-
717
- // Similarly in queue_a
718
- assert!(queue_a.has_execution_for_operation("inst-a", 1));
719
- assert_eq!(
720
- queue_a.get_execution_for_operation("inst-a", 1).unwrap(),
721
- "exec-1"
722
- );
723
-
724
- println!("✓ Test H passed: Sync + execution single-owner semantics");
725
- }
726
-
727
- /// Test I: Reactive UI
728
- /// execution → state mutation → reactive graph → UI updates
729
- /// (This test verifies the capability is ready to update state)
730
- #[test]
731
- fn test_i_execution_state_mutation() {
732
- let mut exec = Execution::new(
733
- "exec-1".to_string(),
734
- "trigger-1".to_string(),
735
- make_order_operation(1, "inst-a", 1, "123", 100.0),
736
- "process_order".to_string(),
737
- );
738
-
739
- // Execution completes and produces a state mutation result
740
- exec.mark_running("inst-a".to_string());
741
-
742
- // Capability executes and updates the order state
743
- let result = serde_json::json!({
744
- "order_id": "123",
745
- "status": "processed",
746
- "processed_at_ms": 1704067200000u128,
747
- });
748
-
749
- exec.mark_succeeded(result.clone());
750
-
751
- // Result is available for reactive propagation
752
- assert_eq!(exec.result, Some(result));
753
- assert_eq!(exec.status, ExecutionStatus::Succeeded);
754
-
755
- // The reactive graph would:
756
- // 1. Detect the status change in result
757
- // 2. Emit a change event to subscribers
758
- // 3. UI subscribers get notified
759
-
760
- println!("✓ Test I passed: Execution results ready for reactive propagation");
761
- }
762
- }
763
-
764
- #[cfg(test)]
765
- mod capability_acceptance_tests {
766
- use crate::{
767
- capabilities::{SearchCapability, VectorCapability},
768
- capability::{CapabilityContext, FlowCapability},
769
- CapabilityQuery, Operation,
770
- };
771
- use serde_json::json;
772
-
773
- /// Test A: Search projection - insert document → search index → query → result
774
- #[test]
775
- fn test_a_search_projection() {
776
- // Scenario: Search index as derived state projection
777
- let mut search = SearchCapability::new();
778
- let context = CapabilityContext {
779
- capability_id: search.id(),
780
- instance_id: "instance-1".to_string(),
781
- sequence: 1,
782
- version: 1,
783
- };
784
-
785
- // Initialize capability
786
- search.initialize(&context).unwrap();
787
-
788
- // Canonical operation: user inserts a document
789
- let op = Operation::insert(
790
- 1,
791
- "instance-1".to_string(),
792
- 1,
793
- "docs:1".to_string(),
794
- json!("Reactive Rust Database"),
795
- "Document".to_string(),
796
- "docs".to_string(),
797
- );
798
-
799
- // Capability processes operation → maintains search index
800
- let result = search.on_operation(&op, &context).unwrap();
801
- assert!(result.success);
802
- assert!(result.produced_derived_state);
803
-
804
- // Query the search index
805
- let query = CapabilityQuery::new("search".to_string(), json!({"q": "Rust", "limit": 10}));
806
- let results = search.query(&query, &context).unwrap();
807
-
808
- // Search returns the document
809
- assert!(results.success);
810
- assert_eq!(results.results.len(), 1);
811
- assert!(results.results[0]
812
- .get("key")
813
- .unwrap()
814
- .as_str()
815
- .unwrap()
816
- .contains("docs:1"));
817
-
818
- println!("✓ Test A passed: Search projection works (insert → index → query → result)");
819
- }
820
-
821
- /// Test B: Reactive search - document changes → index updates → result updates
822
- #[test]
823
- fn test_b_reactive_search() {
824
- let mut search = SearchCapability::new();
825
- let context = CapabilityContext {
826
- capability_id: search.id(),
827
- instance_id: "instance-1".to_string(),
828
- sequence: 1,
829
- version: 1,
830
- };
831
-
832
- search.initialize(&context).unwrap();
833
-
834
- // Insert initial document
835
- let op1 = Operation::insert(
836
- 1,
837
- "instance-1".to_string(),
838
- 1,
839
- "docs:1".to_string(),
840
- json!("Original Content"),
841
- "Document".to_string(),
842
- "docs".to_string(),
843
- );
844
- search.on_operation(&op1, &context).unwrap();
845
-
846
- // Query for "Original"
847
- let query1 =
848
- CapabilityQuery::new("search".to_string(), json!({"q": "Original", "limit": 10}));
849
- let results1 = search.query(&query1, &context).unwrap();
850
- assert_eq!(results1.results.len(), 1);
851
-
852
- // Update document (reactive change)
853
- let op2 = Operation::update(
854
- 2,
855
- "instance-1".to_string(),
856
- 2,
857
- "docs:1".to_string(),
858
- json!("Updated Content"),
859
- "Document".to_string(),
860
- "docs".to_string(),
861
- );
862
- search.on_operation(&op2, &context).unwrap();
863
-
864
- // Query for "Original" - should not find it
865
- let results2 = search.query(&query1, &context).unwrap();
866
- assert_eq!(results2.results.len(), 0);
867
-
868
- // Query for "Updated" - should find it now
869
- let query2 =
870
- CapabilityQuery::new("search".to_string(), json!({"q": "Updated", "limit": 10}));
871
- let results3 = search.query(&query2, &context).unwrap();
872
- assert_eq!(results3.results.len(), 1);
873
-
874
- println!("✓ Test B passed: Reactive search (changes → index updates → results update)");
875
- }
876
-
877
- /// Test C: Delete - document deleted → search result disappears
878
- #[test]
879
- fn test_c_search_delete() {
880
- let mut search = SearchCapability::new();
881
- let context = CapabilityContext {
882
- capability_id: search.id(),
883
- instance_id: "instance-1".to_string(),
884
- sequence: 1,
885
- version: 1,
886
- };
887
-
888
- search.initialize(&context).unwrap();
889
-
890
- // Insert document
891
- let op_insert = Operation::insert(
892
- 1,
893
- "instance-1".to_string(),
894
- 1,
895
- "docs:1".to_string(),
896
- json!("Document to Delete"),
897
- "Document".to_string(),
898
- "docs".to_string(),
899
- );
900
- search.on_operation(&op_insert, &context).unwrap();
901
-
902
- // Verify it's found
903
- let query = CapabilityQuery::new("search".to_string(), json!({"q": "Delete", "limit": 10}));
904
- let results1 = search.query(&query, &context).unwrap();
905
- assert_eq!(results1.results.len(), 1);
906
-
907
- // Delete document
908
- let op_delete = Operation::delete(
909
- 2,
910
- "instance-1".to_string(),
911
- 2,
912
- "docs:1".to_string(),
913
- "Document".to_string(),
914
- "docs".to_string(),
915
- );
916
- search.on_operation(&op_delete, &context).unwrap();
917
-
918
- // Verify it's gone from search
919
- let results2 = search.query(&query, &context).unwrap();
920
- assert_eq!(results2.results.len(), 0);
921
-
922
- println!("✓ Test C passed: Delete (document deleted → search result disappears)");
923
- }
924
-
925
- /// Test D: Rebuild - destroy index → rebuild from canonical → identical results
926
- #[test]
927
- fn test_d_search_rebuild() {
928
- // Create and populate original capability
929
- let mut search1 = SearchCapability::new();
930
- let context = CapabilityContext {
931
- capability_id: search1.id(),
932
- instance_id: "instance-1".to_string(),
933
- sequence: 2,
934
- version: 1,
935
- };
936
- search1.initialize(&context).unwrap();
937
-
938
- let ops = vec![
939
- Operation::insert(
940
- 1,
941
- "instance-1".to_string(),
942
- 1,
943
- "docs:1".to_string(),
944
- json!("Rust Guide"),
945
- "Document".to_string(),
946
- "docs".to_string(),
947
- ),
948
- Operation::insert(
949
- 2,
950
- "instance-1".to_string(),
951
- 2,
952
- "docs:2".to_string(),
953
- json!("Go Tutorial"),
954
- "Document".to_string(),
955
- "docs".to_string(),
956
- ),
957
- ];
958
-
959
- for op in &ops {
960
- search1.on_operation(op, &context).unwrap();
961
- }
962
-
963
- let query = CapabilityQuery::new("search".to_string(), json!({"q": "Rust", "limit": 10}));
964
- let results1 = search1.query(&query, &context).unwrap();
965
- assert_eq!(results1.results.len(), 1);
966
-
967
- // Create new capability, rebuild from operations
968
- let mut search2 = SearchCapability::new();
969
- search2.initialize(&context).unwrap();
970
- search2.rebuild(&ops, &context).unwrap();
971
-
972
- // Query rebuilt index - should get same results
973
- let results2 = search2.query(&query, &context).unwrap();
974
- assert_eq!(results2.results.len(), 1);
975
- assert_eq!(results1.results, results2.results);
976
-
977
- println!("✓ Test D passed: Rebuild (destroy → replay from canonical → identical results)");
978
- }
979
-
980
- /// Test E: Resume - capability processed to seq 500 → crash → restart → resume at 501
981
- #[test]
982
- fn test_e_search_checkpoint_resume() {
983
- let mut search = SearchCapability::new();
984
- let context_500 = CapabilityContext {
985
- capability_id: search.id(),
986
- instance_id: "instance-1".to_string(),
987
- sequence: 500,
988
- version: 1,
989
- };
990
-
991
- search.initialize(&context_500).unwrap();
992
-
993
- // Simulate processing up to operation 500
994
- let op_500 = Operation::insert(
995
- 500,
996
- "instance-1".to_string(),
997
- 500,
998
- "docs:500".to_string(),
999
- json!("Document 500"),
1000
- "Document".to_string(),
1001
- "docs".to_string(),
1002
- );
1003
- search.on_operation(&op_500, &context_500).unwrap();
1004
-
1005
- // Checkpoint: save that we've processed up to 500
1006
- let _checkpoint = crate::CapabilityCheckpoint::new(search.id(), "instance-1".to_string());
1007
- // In real system, checkpoint would be saved to disk
1008
-
1009
- // Simulate restart at operation 501
1010
- let context_501 = CapabilityContext {
1011
- capability_id: search.id(),
1012
- instance_id: "instance-1".to_string(),
1013
- sequence: 501,
1014
- version: 1,
1015
- };
1016
-
1017
- // Instead of replaying from 1, we resume from checkpoint
1018
- // Only operation 501+ would be replayed
1019
- let op_501 = Operation::insert(
1020
- 501,
1021
- "instance-1".to_string(),
1022
- 501,
1023
- "docs:501".to_string(),
1024
- json!("Document 501"),
1025
- "Document".to_string(),
1026
- "docs".to_string(),
1027
- );
1028
- search.on_operation(&op_501, &context_501).unwrap();
1029
-
1030
- // Verify both operations are indexed
1031
- let query =
1032
- CapabilityQuery::new("search".to_string(), json!({"q": "Document", "limit": 10}));
1033
- let results = search.query(&query, &context_501).unwrap();
1034
- assert!(results.results.len() >= 2); // At least ops 500 and 501
1035
-
1036
- println!("✓ Test E passed: Resume (process 500 → crash → restart → resume at 501)");
1037
- }
1038
-
1039
- /// Test F: Vector projection - document → embedding → vector index → similarity query
1040
- #[test]
1041
- fn test_f_vector_projection() {
1042
- let mut vector = VectorCapability::with_default_provider();
1043
- let context = CapabilityContext {
1044
- capability_id: vector.id(),
1045
- instance_id: "instance-1".to_string(),
1046
- sequence: 1,
1047
- version: 1,
1048
- };
1049
-
1050
- vector.initialize(&context).unwrap();
1051
-
1052
- // Insert documents
1053
- let op1 = Operation::insert(
1054
- 1,
1055
- "instance-1".to_string(),
1056
- 1,
1057
- "docs:1".to_string(),
1058
- json!("Machine learning algorithms"),
1059
- "Document".to_string(),
1060
- "docs".to_string(),
1061
- );
1062
- vector.on_operation(&op1, &context).unwrap();
1063
-
1064
- let op2 = Operation::insert(
1065
- 2,
1066
- "instance-1".to_string(),
1067
- 2,
1068
- "docs:2".to_string(),
1069
- json!("Database optimization techniques"),
1070
- "Document".to_string(),
1071
- "docs".to_string(),
1072
- );
1073
- vector.on_operation(&op2, &context).unwrap();
1074
-
1075
- // Query for similar documents
1076
- let query = CapabilityQuery::new(
1077
- "vectorFind".to_string(),
1078
- json!({"query": "machine learning", "topK": 5}),
1079
- );
1080
- let results = vector.query(&query, &context).unwrap();
1081
-
1082
- assert!(results.success);
1083
- assert!(results.results.len() > 0);
1084
-
1085
- println!(
1086
- "✓ Test F passed: Vector projection (document → embedding → index → similarity query)"
1087
- );
1088
- }
1089
-
1090
- /// Test G: Remote operation - FeltDB A sync to B → B's projections update
1091
- #[test]
1092
- fn test_g_remote_operation_sync() {
1093
- // Two capability instances simulating sync between peers
1094
- let mut search_a = SearchCapability::new();
1095
- let mut search_b = SearchCapability::new();
1096
-
1097
- let context_a = CapabilityContext {
1098
- capability_id: search_a.id(),
1099
- instance_id: "instance-a".to_string(),
1100
- sequence: 1,
1101
- version: 1,
1102
- };
1103
-
1104
- let context_b = CapabilityContext {
1105
- capability_id: search_b.id(),
1106
- instance_id: "instance-b".to_string(),
1107
- sequence: 1,
1108
- version: 1,
1109
- };
1110
-
1111
- search_a.initialize(&context_a).unwrap();
1112
- search_b.initialize(&context_b).unwrap();
1113
-
1114
- // Instance A creates a document
1115
- let op_from_a = Operation::insert(
1116
- 100,
1117
- "instance-a".to_string(),
1118
- 1,
1119
- "docs:100".to_string(),
1120
- json!("Synchronized Content"),
1121
- "Document".to_string(),
1122
- "docs".to_string(),
1123
- );
1124
-
1125
- // A processes it locally
1126
- search_a.on_operation(&op_from_a, &context_a).unwrap();
1127
-
1128
- // Operation arrives at B through sync
1129
- search_b.on_operation(&op_from_a, &context_b).unwrap();
1130
-
1131
- // Both instances should find the document in search
1132
- let query = CapabilityQuery::new(
1133
- "search".to_string(),
1134
- json!({"q": "Synchronized", "limit": 10}),
1135
- );
1136
-
1137
- let results_a = search_a.query(&query, &context_a).unwrap();
1138
- let results_b = search_b.query(&query, &context_b).unwrap();
1139
-
1140
- assert_eq!(results_a.results.len(), 1);
1141
- assert_eq!(results_b.results.len(), 1);
1142
-
1143
- println!("✓ Test G passed: Remote operation (A sync → B → both projections updated)");
1144
- }
1145
-
1146
- /// Test H: No duplicate external execution
1147
- /// If operation arrives twice, execute only once
1148
- #[test]
1149
- fn test_h_no_duplicate_embedding_execution() {
1150
- let mut vector = VectorCapability::with_default_provider();
1151
- let context = CapabilityContext {
1152
- capability_id: vector.id(),
1153
- instance_id: "instance-1".to_string(),
1154
- sequence: 1,
1155
- version: 1,
1156
- };
1157
-
1158
- vector.initialize(&context).unwrap();
1159
-
1160
- // Operation received first time
1161
- let op = Operation::insert(
1162
- 100,
1163
- "instance-1".to_string(),
1164
- 100,
1165
- "docs:100".to_string(),
1166
- json!("Test Document"),
1167
- "Document".to_string(),
1168
- "docs".to_string(),
1169
- );
1170
-
1171
- vector.on_operation(&op, &context).unwrap();
1172
-
1173
- // Query to verify it's indexed
1174
- let query1 = CapabilityQuery::new(
1175
- "vectorFind".to_string(),
1176
- json!({"query": "Test", "topK": 5}),
1177
- );
1178
- let results1 = vector.query(&query1, &context).unwrap();
1179
- assert_eq!(results1.results.len(), 1);
1180
-
1181
- // Same operation arrives again (duplicate delivery)
1182
- // With idempotency, this should not create duplicate embedding executions
1183
- vector.on_operation(&op, &context).unwrap();
1184
-
1185
- // Query should still have only one result
1186
- let results2 = vector.query(&query1, &context).unwrap();
1187
- assert_eq!(results2.results.len(), 1);
1188
-
1189
- println!(
1190
- "✓ Test H passed: No duplicate external execution (op received twice → one embedding)"
1191
- );
1192
- }
1193
- }
1194
-
1195
- /// Acceptance tests for distributed content acquisition and materialization
1196
- #[cfg(test)]
1197
- mod content_acquisition_tests {
1198
- use crate::{
1199
- AcquisitionMetadata, AcquisitionPolicy, ContentAcquisitionManager, ContentAddressableStore,
1200
- ContentIdentity, FlowRef, PeerAdvertisement, PeerId, PeerRegistry,
1201
- };
1202
-
1203
- #[test]
1204
- fn test_a_remote_resolution() {
1205
- // Scenario: Node A owns a document, Node B has a reference
1206
- // B.resolve(ref) should return the document
1207
-
1208
- // Setup Node A's store with content
1209
- let node_a_store = ContentAddressableStore::new();
1210
- let node_a_manager = ContentAcquisitionManager::new(node_a_store);
1211
-
1212
- let flow_ref = FlowRef::new("docs", "doc-123");
1213
- let content = b"document content".to_vec();
1214
- let content_id = ContentIdentity::from_bytes(&content);
1215
-
1216
- // Node A materializes the document
1217
- node_a_manager
1218
- .materialize(&flow_ref, content.clone(), &content_id)
1219
- .unwrap();
1220
-
1221
- // Verify it exists on Node A
1222
- assert!(node_a_manager.has_local(&flow_ref).unwrap());
1223
- assert_eq!(
1224
- node_a_manager.get_local(&flow_ref).unwrap(),
1225
- Some(content.clone())
1226
- );
1227
-
1228
- println!("✓ Test A passed: Remote document accessible locally");
1229
- }
1230
-
1231
- #[test]
1232
- fn test_b_transparent_acquisition() {
1233
- // Scenario: B doesn't possess document initially
1234
- // resolve → automatic acquisition → document appears in B's local state
1235
-
1236
- let store = ContentAddressableStore::new();
1237
- let manager = ContentAcquisitionManager::new(store);
1238
-
1239
- let flow_ref = FlowRef::new("docs", "doc-456");
1240
-
1241
- // Initially, B doesn't have it
1242
- assert!(!manager.has_local(&flow_ref).unwrap());
1243
-
1244
- // B tries to resolve with RemoteAllowed policy
1245
- let result = manager
1246
- .resolve(&flow_ref, AcquisitionPolicy::RemoteAllowed)
1247
- .unwrap();
1248
-
1249
- // For now, without a network layer, it won't find it
1250
- // But the policy was respected
1251
- assert!(result.is_none());
1252
-
1253
- println!("✓ Test B passed: Acquisition policy respected");
1254
- }
1255
-
1256
- #[test]
1257
- fn test_c_hash_verification() {
1258
- // Scenario: Correct content → accepted
1259
- // Tampered content → rejected
1260
-
1261
- let store = ContentAddressableStore::new();
1262
- let manager = ContentAcquisitionManager::new(store);
1263
-
1264
- let flow_ref = FlowRef::new("docs", "doc-789");
1265
- let content = b"original content".to_vec();
1266
- let content_id = ContentIdentity::from_bytes(&content);
1267
-
1268
- // Correct content should be accepted
1269
- let result = manager.materialize(&flow_ref, content.clone(), &content_id);
1270
- assert!(result.is_ok());
1271
-
1272
- // Verify it was stored
1273
- assert_eq!(manager.get_local(&flow_ref).unwrap(), Some(content));
1274
-
1275
- // Tampered content should be rejected
1276
- let tampered = b"different content".to_vec();
1277
- let result = manager.materialize(&flow_ref, tampered, &content_id);
1278
- assert!(result.is_err()); // Hash mismatch
1279
-
1280
- println!("✓ Test C passed: Hash verification works");
1281
- }
1282
-
1283
- #[test]
1284
- fn test_d_multiple_providers() {
1285
- // Scenario: Document available on multiple peers
1286
- // Resolver should select an available peer
1287
-
1288
- let store = ContentAddressableStore::new();
1289
- let manager = ContentAcquisitionManager::new(store);
1290
-
1291
- // Register multiple peers
1292
- let peer_a = PeerAdvertisement::new(PeerId::new("peer-a")).with_namespace("docs");
1293
- let peer_b = PeerAdvertisement::new(PeerId::new("peer-b")).with_namespace("docs");
1294
- let peer_c = PeerAdvertisement::new(PeerId::new("peer-c")).with_namespace("docs");
1295
-
1296
- manager.register_peer(peer_a).unwrap();
1297
- manager.register_peer(peer_b).unwrap();
1298
- manager.register_peer(peer_c).unwrap();
1299
-
1300
- // Try to resolve from any available peer
1301
- let flow_ref = FlowRef::new("docs", "doc-abc");
1302
- let result = manager
1303
- .resolve(&flow_ref, AcquisitionPolicy::RemoteAllowed)
1304
- .unwrap();
1305
-
1306
- // Without network, we can't actually fetch, but provider selection is available
1307
- assert!(result.is_none()); // No local content
1308
-
1309
- println!("✓ Test D passed: Multiple providers registered");
1310
- }
1311
-
1312
- #[test]
1313
- fn test_e_deduplicated_acquisition() {
1314
- // Scenario: 100 concurrent resolves for same content
1315
- // Should result in: 1 network transfer, 1 materialization
1316
-
1317
- let store = ContentAddressableStore::new();
1318
- let manager = ContentAcquisitionManager::new(store);
1319
-
1320
- let flow_ref = FlowRef::new("docs", "doc-shared");
1321
- let content = b"shared content".to_vec();
1322
- let content_id = ContentIdentity::from_bytes(&content);
1323
-
1324
- // First acquisition
1325
- manager
1326
- .materialize(&flow_ref, content.clone(), &content_id)
1327
- .unwrap();
1328
-
1329
- // Subsequent acquisitions should see the same content
1330
- for _ in 0..100 {
1331
- let result = manager.get_local(&flow_ref).unwrap();
1332
- assert_eq!(result, Some(content.clone()));
1333
- }
1334
-
1335
- println!("✓ Test E passed: Deduplication works (all see same materialized content)");
1336
- }
1337
-
1338
- #[test]
1339
- fn test_f_offline_behavior() {
1340
- // Scenario: Remote reference, peer unavailable
1341
- // With LocalOnly policy: predictable unresolved state
1342
-
1343
- let store = ContentAddressableStore::new();
1344
- let manager = ContentAcquisitionManager::new(store);
1345
-
1346
- let flow_ref = FlowRef::new("docs", "doc-offline");
1347
-
1348
- // Try to resolve with LocalOnly (no remote fallback)
1349
- let result = manager
1350
- .resolve(&flow_ref, AcquisitionPolicy::LocalOnly)
1351
- .unwrap();
1352
- assert!(result.is_none()); // Stays unresolved
1353
-
1354
- // When peer returns and we acquire it
1355
- let content = b"acquired offline".to_vec();
1356
- let content_id = ContentIdentity::from_bytes(&content);
1357
- manager
1358
- .materialize(&flow_ref, content.clone(), &content_id)
1359
- .unwrap();
1360
-
1361
- // Now it's available
1362
- assert_eq!(manager.get_local(&flow_ref).unwrap(), Some(content));
1363
-
1364
- println!("✓ Test F passed: Offline behavior predictable");
1365
- }
1366
-
1367
- #[test]
1368
- fn test_g_acquisition_metadata() {
1369
- // Scenario: Object acquired from another peer
1370
- // FeltDB knows: source, timestamp, but not canonical state
1371
-
1372
- let peer_b = PeerId::new("peer-b");
1373
- let metadata = AcquisitionMetadata::new(peer_b.clone(), true);
1374
-
1375
- assert_eq!(metadata.source_peer, peer_b);
1376
- assert!(metadata.is_cached);
1377
- assert!(metadata.acquired_at_ms > 0);
1378
-
1379
- // Metadata is informational, not canonical
1380
- // Application code can query it but doesn't depend on it
1381
-
1382
- println!("✓ Test G passed: Acquisition metadata tracking");
1383
- }
1384
-
1385
- #[test]
1386
- fn test_h_projection_capability_discovery() {
1387
- // Scenario: Vector capability exists on Node B
1388
- // Node C discovers it and can route to it
1389
-
1390
- let registry = PeerRegistry::new();
1391
-
1392
- let peer_b = PeerId::new("node-b");
1393
- let ad = PeerAdvertisement::new(peer_b.clone())
1394
- .with_namespace("documents")
1395
- .with_capability("vector:embeddings");
1396
-
1397
- registry.register(ad).unwrap();
1398
-
1399
- // Node C discovers vector capability provider
1400
- let vector_providers = registry.peers_for_capability("vector:embeddings").unwrap();
1401
- assert_eq!(vector_providers.len(), 1);
1402
- assert_eq!(vector_providers[0].peer_id.id, "node-b");
1403
-
1404
- println!("✓ Test H passed: Projection routing via capabilities");
1405
- }
1406
-
1407
- #[test]
1408
- fn test_i_restart_persistence() {
1409
- // Scenario: Acquire → shutdown → restart
1410
- // Reference still resolves locally (persisted)
1411
-
1412
- let store = ContentAddressableStore::new();
1413
- let manager = ContentAcquisitionManager::new(store);
1414
-
1415
- let flow_ref = FlowRef::new("docs", "doc-persist");
1416
- let content = b"persistent content".to_vec();
1417
- let content_id = ContentIdentity::from_bytes(&content);
1418
-
1419
- // Acquire and materialize
1420
- manager
1421
- .materialize(&flow_ref, content.clone(), &content_id)
1422
- .unwrap();
1423
-
1424
- // "Restart" - create new manager from same store
1425
- // In real implementation, this would reload from disk
1426
-
1427
- // But we can verify the content is in the store
1428
- assert!(manager.has_local(&flow_ref).unwrap());
1429
-
1430
- println!("✓ Test I passed: Persistence across materialization");
1431
- }
1432
-
1433
- #[test]
1434
- fn test_j_node_disappearance() {
1435
- // Scenario: A owns content, B acquires it, A disappears
1436
- // B continues operating with acquired content
1437
-
1438
- let store = ContentAddressableStore::new();
1439
- let manager = ContentAcquisitionManager::new(store);
1440
-
1441
- let flow_ref = FlowRef::new("docs", "doc-orphan");
1442
- let content = b"orphaned content".to_vec();
1443
- let content_id = ContentIdentity::from_bytes(&content);
1444
-
1445
- // B acquires from A
1446
- manager
1447
- .materialize(&flow_ref, content.clone(), &content_id)
1448
- .unwrap();
1449
-
1450
- // A disappears - but B still has it locally
1451
- assert_eq!(manager.get_local(&flow_ref).unwrap(), Some(content));
1452
-
1453
- println!("✓ Test J passed: Local content survives peer disappearance");
1454
- }
1455
- }
1456
-
1457
- /// Acceptance tests for convergent replication across distributed instances
1458
- #[cfg(test)]
1459
- mod convergence_acceptance_tests {
1460
- use crate::FeltDb;
1461
- use serde::{Deserialize, Serialize};
1462
- use std::path::PathBuf;
1463
- use std::sync::atomic::{AtomicU64, Ordering};
1464
- use std::time::SystemTime;
1465
- use std::time::UNIX_EPOCH;
1466
-
1467
- static TEMP_FILE_SEQUENCE: AtomicU64 = AtomicU64::new(0);
1468
-
1469
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1470
- struct Task {
1471
- title: String,
1472
- status: String,
1473
- assignee: String,
1474
- }
1475
-
1476
- fn temp_file() -> PathBuf {
1477
- let nanos = SystemTime::now()
1478
- .duration_since(UNIX_EPOCH)
1479
- .expect("time")
1480
- .as_nanos();
1481
- let sequence = TEMP_FILE_SEQUENCE.fetch_add(1, Ordering::Relaxed);
1482
- std::env::temp_dir().join(format!(
1483
- "feltdb-convergence-test-{}-{nanos}-{sequence}.data",
1484
- std::process::id()
1485
- ))
1486
- }
1487
-
1488
- /// Test convergence: Two instances make concurrent mutations on same key
1489
- /// Both should resolve to the same state deterministically
1490
- #[test]
1491
- fn test_concurrent_mutations_deterministic_convergence() {
1492
- // Instance A and B both write to the same task concurrently
1493
- let path_a = temp_file();
1494
- let path_b = temp_file();
1495
-
1496
- let db_a = FeltDb::open(&path_a).expect("open db_a");
1497
- let db_b = FeltDb::open(&path_b).expect("open db_b");
1498
-
1499
- // Both instances write to the same key concurrently
1500
- let task_a = Task {
1501
- title: "Complete task".to_string(),
1502
- status: "completed".to_string(),
1503
- assignee: "Alice".to_string(),
1504
- };
1505
-
1506
- let task_b = Task {
1507
- title: "Complete task".to_string(),
1508
- status: "blocked".to_string(),
1509
- assignee: "Bob".to_string(),
1510
- };
1511
-
1512
- // A writes first
1513
- db_a.insert("tasks:1", &task_a).expect("insert_a");
1514
-
1515
- // B writes independently (concurrent)
1516
- db_b.insert("tasks:1", &task_b).expect("insert_b");
1517
-
1518
- // Both instances have operations with vector clocks
1519
- let ops_a = db_a
1520
- .get_pending_for_peer("peer-b", 0)
1521
- .expect("get_pending_a");
1522
- let ops_b = db_b
1523
- .get_pending_for_peer("peer-a", 0)
1524
- .expect("get_pending_b");
1525
-
1526
- assert_eq!(ops_a.len(), 1);
1527
- assert_eq!(ops_b.len(), 1);
1528
- assert!(ops_a[0].vector_clock.is_some());
1529
- assert!(ops_b[0].vector_clock.is_some());
1530
-
1531
- // Simulate sync: A applies B's operation
1532
- db_a.apply_remote_operation(ops_b[0].clone())
1533
- .expect("apply_remote_to_a");
1534
-
1535
- // Simulate sync: B applies A's operation
1536
- db_b.apply_remote_operation(ops_a[0].clone())
1537
- .expect("apply_remote_to_b");
1538
-
1539
- // Both should have converged to the same state
1540
- // (Conflict resolution will pick one deterministically)
1541
- let final_task_a: Option<Task> = db_a.get("tasks:1").expect("get_final_a");
1542
- let final_task_b: Option<Task> = db_b.get("tasks:1").expect("get_final_b");
1543
-
1544
- // Both should have the same final state (due to deterministic conflict resolution)
1545
- assert_eq!(final_task_a, final_task_b);
1546
-
1547
- let _ = std::fs::remove_file(path_a);
1548
- let _ = std::fs::remove_file(path_b);
1549
-
1550
- println!("✓ Convergence Test A passed: Concurrent mutations resolve deterministically");
1551
- }
1552
-
1553
- /// Test convergence: Vector clocks correctly track causality
1554
- #[test]
1555
- fn test_vector_clock_causal_tracking() {
1556
- let path = temp_file();
1557
- let db = FeltDb::open(&path).expect("open");
1558
-
1559
- // Create a chain of mutations
1560
- let task1 = Task {
1561
- title: "Task 1".to_string(),
1562
- status: "open".to_string(),
1563
- assignee: "Alice".to_string(),
1564
- };
1565
-
1566
- db.insert("tasks:1", &task1).expect("insert_1");
1567
-
1568
- let ops_1 = db.get_pending_for_peer("peer", 0).expect("get_pending_1");
1569
- assert_eq!(ops_1.len(), 1);
1570
- let vc_1 = ops_1[0].vector_clock.clone().expect("vc_1");
1571
-
1572
- // Update the same task
1573
- let task1_updated = Task {
1574
- title: "Task 1".to_string(),
1575
- status: "in_progress".to_string(),
1576
- assignee: "Bob".to_string(),
1577
- };
1578
-
1579
- db.update("tasks:1", &task1_updated).expect("update");
1580
-
1581
- let ops_2 = db.get_pending_for_peer("peer", 1).expect("get_pending_2");
1582
- assert_eq!(ops_2.len(), 1);
1583
- let vc_2 = ops_2[0].vector_clock.clone().expect("vc_2");
1584
-
1585
- // Vector clock should have incremented
1586
- assert!(
1587
- vc_2.get(&db.instance_id().expect("instance_id"))
1588
- > vc_1.get(&db.instance_id().expect("instance_id"))
1589
- );
1590
-
1591
- let _ = std::fs::remove_file(path);
1592
-
1593
- println!("✓ Convergence Test B passed: Vector clocks track causality correctly");
1594
- }
1595
-
1596
- /// Test convergence: Operations flow through sync and are applied idempotently
1597
- #[test]
1598
- fn test_sync_idempotency_with_vector_clocks() {
1599
- let path_a = temp_file();
1600
- let path_b = temp_file();
1601
-
1602
- let db_a = FeltDb::open(&path_a).expect("open db_a");
1603
- let db_b = FeltDb::open(&path_b).expect("open db_b");
1604
-
1605
- // A writes a task
1606
- let task = Task {
1607
- title: "Sync test".to_string(),
1608
- status: "open".to_string(),
1609
- assignee: "Charlie".to_string(),
1610
- };
1611
-
1612
- db_a.insert("tasks:100", &task).expect("insert");
1613
-
1614
- // Get the operation
1615
- let ops = db_a.get_pending_for_peer("peer-b", 0).expect("get_pending");
1616
- assert_eq!(ops.len(), 1);
1617
- let op = ops[0].clone();
1618
-
1619
- // B receives the operation
1620
- db_b.apply_remote_operation(op.clone()).expect("apply_1");
1621
-
1622
- // Check B has the data
1623
- let task_b: Option<Task> = db_b.get("tasks:100").expect("get");
1624
- assert_eq!(task_b, Some(task.clone()));
1625
-
1626
- // B receives the same operation again (duplicate delivery)
1627
- db_b.apply_remote_operation(op).expect("apply_2");
1628
-
1629
- // B should still have the same state (idempotent)
1630
- let task_b_after: Option<Task> = db_b.get("tasks:100").expect("get_after");
1631
- assert_eq!(task_b_after, Some(task));
1632
-
1633
- // B's vector clock should have been merged (not duplicated)
1634
- let vc_b = db_b.get_vector_clock().expect("get_vc").expect("vc");
1635
- assert!(vc_b.len() >= 1);
1636
-
1637
- let _ = std::fs::remove_file(path_a);
1638
- let _ = std::fs::remove_file(path_b);
1639
-
1640
- println!("✓ Convergence Test C passed: Operations are applied idempotently");
1641
- }
1642
-
1643
- /// Test convergence: Deterministic resolution with synchronized operations
1644
- /// When all instances see the same set of concurrent operations,
1645
- /// they will all apply the same resolution
1646
- #[test]
1647
- fn test_three_way_convergence() {
1648
- let path_a = temp_file();
1649
- let path_b = temp_file();
1650
- let path_c = temp_file();
1651
-
1652
- let db_a = FeltDb::open(&path_a).expect("open db_a");
1653
- let db_b = FeltDb::open(&path_b).expect("open db_b");
1654
- let db_c = FeltDb::open(&path_c).expect("open db_c");
1655
-
1656
- // All three instances make the same mutation independently
1657
- let task_all = Task {
1658
- title: "3-way test".to_string(),
1659
- status: "open".to_string(),
1660
- assignee: "All".to_string(),
1661
- };
1662
- db_a.insert("tasks:200", &task_all).expect("insert_a");
1663
- db_b.insert("tasks:200", &task_all).expect("insert_b");
1664
- db_c.insert("tasks:200", &task_all).expect("insert_c");
1665
-
1666
- // Get all operations
1667
- let ops_a = db_a.get_pending_for_peer("sync", 0).expect("ops_a");
1668
- let ops_b = db_b.get_pending_for_peer("sync", 0).expect("ops_b");
1669
- let ops_c = db_c.get_pending_for_peer("sync", 0).expect("ops_c");
1670
-
1671
- // Sync: Each instance receives all others' operations
1672
- // A receives B and C
1673
- db_a.apply_remote_operation(ops_b[0].clone())
1674
- .expect("apply_b_to_a");
1675
- db_a.apply_remote_operation(ops_c[0].clone())
1676
- .expect("apply_c_to_a");
1677
-
1678
- // B receives A and C
1679
- db_b.apply_remote_operation(ops_a[0].clone())
1680
- .expect("apply_a_to_b");
1681
- db_b.apply_remote_operation(ops_c[0].clone())
1682
- .expect("apply_c_to_b");
1683
-
1684
- // C receives A and B
1685
- db_c.apply_remote_operation(ops_a[0].clone())
1686
- .expect("apply_a_to_c");
1687
- db_c.apply_remote_operation(ops_b[0].clone())
1688
- .expect("apply_b_to_c");
1689
-
1690
- // All three should converge to same state (since they're writing same value)
1691
- let final_a: Option<Task> = db_a.get("tasks:200").expect("get_final_a");
1692
- let final_b: Option<Task> = db_b.get("tasks:200").expect("get_final_b");
1693
- let final_c: Option<Task> = db_c.get("tasks:200").expect("get_final_c");
1694
-
1695
- assert_eq!(final_a, final_b);
1696
- assert_eq!(final_b, final_c);
1697
- assert_eq!(final_a, Some(task_all));
1698
-
1699
- let _ = std::fs::remove_file(path_a);
1700
- let _ = std::fs::remove_file(path_b);
1701
- let _ = std::fs::remove_file(path_c);
1702
-
1703
- println!("✓ Convergence Test D passed: Three-way sync converges all instances");
1704
- }
1705
- }