@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,1474 +0,0 @@
1
- //! A FeltDB replication node, as an operating-system process.
2
- //!
3
- //! This exists so replication can be proved across a real process and network
4
- //! boundary rather than between executors sharing a heap. It is a harness, not
5
- //! a product surface: it speaks a line protocol on stdin/stdout so a test can
6
- //! drive it, and it implements no replication semantics of its own. Every
7
- //! decision about ordering, deduplication, durability, and convergence is made
8
- //! by `DistributedTransactionExecutor`; this binary only moves bytes between
9
- //! processes and reports what happened.
10
- //!
11
- //! ## Links are directed, one TCP connection each
12
- //!
13
- //! `TcpTransport` holds a single stream, and its `receive()` holds the stream
14
- //! lock while awaiting the next message. A bidirectional link would therefore
15
- //! deadlock the first `send()` behind the receive that is parked on the socket.
16
- //!
17
- //! So each direction gets its own connection: for a link X -> Y, Y binds a port
18
- //! dedicated to X and only ever receives on it, and X connects to that port and
19
- //! only ever sends. Three nodes in a full mesh is six directed links.
20
- //!
21
- //! That is a topology choice made here in the harness. It changes nothing about
22
- //! the transport or the protocol.
23
- //!
24
- //! ## Usage
25
- //!
26
- //! ```text
27
- //! feltdb_node --node-id A --data DIR --recv B=127.0.0.1:9111 --send B=127.0.0.1:9121
28
- //! ```
29
- //!
30
- //! `--recv PEER=ADDR` binds ADDR and accepts the connection PEER makes to it.
31
- //! `--send PEER=ADDR` connects to the address PEER is listening on for us.
32
- //!
33
- //! Commands on stdin, one per line:
34
- //!
35
- //! ```text
36
- //! execute-tx RECORD commit a local transaction and replicate it to every peer
37
- //! resend-last put the identical envelope back on the wire, to test dedup
38
- //! query-state the local replica's state hash and applied-operation count
39
- //! metrics per-link byte and message counters, straight from the transport
40
- //! causal-state applied frontier, and what is durable but held back
41
- //! hold-inbound PEER park messages from PEER instead of delivering them
42
- //! release-inbound PEER [reverse]
43
- //! deliver what was parked, optionally newest-first
44
- //! peers per-link connection state
45
- //! shutdown exit cleanly
46
- //! ```
47
-
48
- use feltdb::authority_failover::{AuthorityStore, ClaimResult, FencingToken, Heartbeat};
49
- use feltdb::convergence::VectorClock;
50
- use feltdb::distributed_transactions::{
51
- CausalCapacity, DistributedTransactionExecutor, ReceiveOutcome, ReplicationMessage,
52
- SubmitOutcome,
53
- };
54
- use feltdb::replica_acknowledgements::{
55
- AcknowledgementStore, ReclamationEvidence, ReplicaAcknowledgement,
56
- };
57
- use feltdb::replica_membership::MembershipStore;
58
- use feltdb::replication_protocol::ProtocolTransport;
59
- use feltdb::state_hash::StateHash;
60
- use feltdb::tcp_transport::TcpTransport;
61
- use feltdb::transactions::{
62
- ConsistencyContract, Operation, OperationCommand, OperationId, StateVersion,
63
- };
64
- use serde_json::json;
65
- use std::collections::{HashMap, HashSet};
66
- use std::io::{self, BufRead, Write};
67
- use std::path::PathBuf;
68
- use std::sync::atomic::{AtomicBool, Ordering};
69
- use std::sync::Arc;
70
- use tokio::sync::{mpsc, Mutex};
71
- use tokio::time::{sleep, Duration};
72
-
73
- /// One outbound connection to a peer, used only for sending.
74
- ///
75
- /// `control` exists because `connect` needs `&mut self`. Everything else goes
76
- /// through `io`, a clone: `TcpTransport` holds its stream and metrics behind
77
- /// `Arc`s, so a clone is the same connection, and reading counters or sending
78
- /// does not queue behind whatever else holds the mutex.
79
- #[derive(Clone)]
80
- struct SendLink {
81
- peer: String,
82
- addr: String,
83
- control: Arc<Mutex<TcpTransport>>,
84
- io: TcpTransport,
85
- }
86
-
87
- /// One inbound connection from a peer, used only for receiving.
88
- ///
89
- /// The distinction matters more here: `receive()` parks on the socket holding
90
- /// the transport's internal stream lock, so anything sharing an outer mutex
91
- /// with it would block until a message happened to arrive.
92
- #[derive(Clone)]
93
- struct RecvLink {
94
- peer: String,
95
- #[allow(dead_code)]
96
- addr: String,
97
- control: Arc<Mutex<TcpTransport>>,
98
- io: TcpTransport,
99
- }
100
-
101
- /// Hand one replicated message to the executor and record what it decided.
102
- ///
103
- /// The harness makes no ordering decision of its own here. It calls the same
104
- /// `receive_replicated` the shipping receive path calls, and reports the typed
105
- /// outcome back so a test can assert on "applied" versus "held back" rather
106
- /// than inferring it from state.
107
- fn deliver(
108
- executor: &mut DistributedTransactionExecutor,
109
- version: &mut StateVersion,
110
- message: ReplicationMessage,
111
- ) -> serde_json::Value {
112
- let origin = message.envelope.originating_node.clone();
113
- let key = format!(
114
- "{}:{}",
115
- message.envelope.envelope_id.originating_node, message.envelope.envelope_id.sequence
116
- );
117
- let parent = version.clone();
118
- match executor.receive_replicated(message, parent) {
119
- Ok(ReceiveOutcome::Applied { state_hash }) => {
120
- // Only a real application advances the chained version, so a held
121
- // or duplicated message cannot make the node look like it moved.
122
- version.state_hash = state_hash.to_string();
123
- version.vector_clock.increment(&origin);
124
- json!({ "envelope": key, "outcome": "applied" })
125
- }
126
- Ok(ReceiveOutcome::PendingDependencies { missing }) => {
127
- json!({ "envelope": key, "outcome": "pending", "missing": missing })
128
- }
129
- Ok(ReceiveOutcome::AlreadyKnown) => {
130
- json!({ "envelope": key, "outcome": "already_known" })
131
- }
132
- Ok(ReceiveOutcome::CapacityDeferred {
133
- missing,
134
- pending_entries,
135
- pending_bytes,
136
- }) => {
137
- // Durable, but not buffered. The caller's obligation is to stop
138
- // reading from this peer; the envelope is admitted from the log
139
- // once the backlog drains.
140
- json!({
141
- "envelope": key,
142
- "outcome": "capacity_deferred",
143
- "missing": missing,
144
- "pending_entries": pending_entries,
145
- "pending_bytes": pending_bytes,
146
- })
147
- }
148
- Err(error) => json!({ "envelope": key, "outcome": "error", "error": error }),
149
- }
150
- }
151
-
152
- fn parse_link(value: &str) -> Result<(String, String), String> {
153
- let (peer, addr) = value
154
- .split_once('=')
155
- .ok_or_else(|| format!("expected PEER=ADDR, got {value}"))?;
156
- if peer.is_empty() || addr.is_empty() {
157
- return Err(format!("expected PEER=ADDR, got {value}"));
158
- }
159
- Ok((peer.to_string(), addr.to_string()))
160
- }
161
-
162
- /// Connect with retries: the peer's listener may not be bound yet, and after a
163
- /// peer restarts its old socket is gone and this is how the link comes back.
164
- async fn connect_with_retry(link: &SendLink, attempts: u32) -> bool {
165
- for attempt in 0..attempts {
166
- let mut control = link.control.lock().await;
167
- if control.connect(&link.addr).await.is_ok() {
168
- return true;
169
- }
170
- drop(control);
171
- sleep(Duration::from_millis(25 * (attempt as u64 + 1))).await;
172
- }
173
- false
174
- }
175
-
176
- #[tokio::main]
177
- async fn main() -> Result<(), Box<dyn std::error::Error>> {
178
- let args: Vec<String> = std::env::args().collect();
179
-
180
- let mut node_id = String::new();
181
- let mut data_dir: Option<PathBuf> = None;
182
- let mut send_specs: Vec<(String, String)> = Vec::new();
183
- let mut recv_specs: Vec<(String, String)> = Vec::new();
184
- let mut max_pending_entries: Option<usize> = None;
185
- let mut max_pending_bytes: Option<usize> = None;
186
- let mut authority_priority: u32 = 0;
187
- let mut heartbeat_interval_ms: u64 = 1_000;
188
- let mut heartbeat_failure_threshold: u32 = 5;
189
-
190
- let mut index = 1;
191
- while index < args.len() {
192
- match args[index].as_str() {
193
- "--node-id" => {
194
- index += 1;
195
- node_id = args.get(index).ok_or("--node-id requires a value")?.clone();
196
- }
197
- "--data" => {
198
- index += 1;
199
- data_dir = Some(PathBuf::from(
200
- args.get(index).ok_or("--data requires a value")?,
201
- ));
202
- }
203
- "--send" => {
204
- index += 1;
205
- send_specs.push(parse_link(
206
- args.get(index).ok_or("--send requires PEER=ADDR")?,
207
- )?);
208
- }
209
- "--recv" => {
210
- index += 1;
211
- recv_specs.push(parse_link(
212
- args.get(index).ok_or("--recv requires PEER=ADDR")?,
213
- )?);
214
- }
215
- // The bound is settable so an acceptance test can reach it through
216
- // the production path. Filling the shipping default would mean
217
- // sending ten thousand blocked envelopes over TCP; a test that
218
- // instead constructed a barrier and filled it directly would be
219
- // measuring a component nothing runs, which is the defect this
220
- // whole line of work exists to stop repeating.
221
- "--max-pending-entries" => {
222
- index += 1;
223
- max_pending_entries = Some(
224
- args.get(index)
225
- .ok_or("--max-pending-entries requires a value")?
226
- .parse::<usize>()
227
- .map_err(|e| format!("--max-pending-entries: {e}"))?,
228
- );
229
- }
230
- "--max-pending-bytes" => {
231
- index += 1;
232
- max_pending_bytes = Some(
233
- args.get(index)
234
- .ok_or("--max-pending-bytes requires a value")?
235
- .parse::<usize>()
236
- .map_err(|e| format!("--max-pending-bytes: {e}"))?,
237
- );
238
- }
239
- // Static priority for authority election. Higher priority nodes are
240
- // preferred during failover when multiple candidates have equal state.
241
- "--authority-priority" => {
242
- index += 1;
243
- authority_priority = args
244
- .get(index)
245
- .ok_or("--authority-priority requires a value")?
246
- .parse::<u32>()
247
- .map_err(|e| format!("--authority-priority: {e}"))?;
248
- }
249
- "--heartbeat-interval-ms" => {
250
- index += 1;
251
- heartbeat_interval_ms = args
252
- .get(index)
253
- .ok_or("--heartbeat-interval-ms requires a value")?
254
- .parse::<u64>()
255
- .map_err(|e| format!("--heartbeat-interval-ms: {e}"))?;
256
- }
257
- "--heartbeat-failure-threshold" => {
258
- index += 1;
259
- heartbeat_failure_threshold = args
260
- .get(index)
261
- .ok_or("--heartbeat-failure-threshold requires a value")?
262
- .parse::<u32>()
263
- .map_err(|e| format!("--heartbeat-failure-threshold: {e}"))?;
264
- }
265
- other => return Err(format!("unknown argument {other}").into()),
266
- }
267
- index += 1;
268
- }
269
-
270
- if node_id.is_empty() {
271
- return Err("--node-id is required".into());
272
- }
273
-
274
- // Durability is the executor's, not this binary's. The log path is per
275
- // node, so each process owns its own directory on disk.
276
- let log_path = data_dir.as_ref().map(|dir| {
277
- std::fs::create_dir_all(dir).ok();
278
- dir.join("operations.jsonl")
279
- });
280
-
281
- // Membership lives beside the log and is loaded from disk, never rebuilt
282
- // from --send/--recv. A node's connection flags say who it talks to; they
283
- // say nothing about who is owed history, and conflating the two is what
284
- // this store exists to stop.
285
- let mut membership = MembershipStore::open(
286
- data_dir
287
- .as_ref()
288
- .map(|dir| dir.join("membership.json"))
289
- .unwrap_or_else(|| PathBuf::from("membership.json")),
290
- )
291
- .map_err(|e| e.to_string())?;
292
-
293
- // Acknowledgements are durable and shared with the receive tasks, because
294
- // they arrive on the socket rather than through a command.
295
- let acknowledgements = Arc::new(Mutex::new(
296
- AcknowledgementStore::open(
297
- data_dir
298
- .as_ref()
299
- .map(|dir| dir.join("acknowledgements.json"))
300
- .unwrap_or_else(|| PathBuf::from("acknowledgements.json")),
301
- )
302
- .map_err(|e| e.to_string())?,
303
- ));
304
-
305
- // Authority state tracks who holds write authority in the cluster. The store
306
- // is durable and survives restarts, so a node that crashes can rejoin and
307
- // recover its previous role. The cluster ID is set by the first `authority-claim`
308
- // command, so it starts as the node's own ID.
309
- let authority = Arc::new(Mutex::new(
310
- AuthorityStore::open_with_failure_detector(
311
- data_dir
312
- .as_ref()
313
- .map(|dir| dir.join("authority.json"))
314
- .unwrap_or_else(|| PathBuf::from("authority.json")),
315
- &node_id, // cluster_id defaults to node_id until set
316
- &node_id,
317
- authority_priority,
318
- heartbeat_interval_ms,
319
- heartbeat_failure_threshold,
320
- )
321
- .map_err(|e| e.to_string())?,
322
- ));
323
-
324
- let initial_state = StateHash::from_hex("0".repeat(64));
325
- let mut executor = DistributedTransactionExecutor::with_log(
326
- node_id.clone(),
327
- initial_state.clone(),
328
- log_path.clone(),
329
- )?;
330
-
331
- // The bound is set before recovery, not after, because recovery restores
332
- // the backlog and has to respect the same limit the live path does.
333
- // Otherwise a node could restart into a backlog it would never have
334
- // accepted while running, and the bound would hold only until the first
335
- // crash.
336
- let default_capacity = CausalCapacity::default();
337
- executor.set_causal_capacity(CausalCapacity::new(
338
- max_pending_entries.unwrap_or(default_capacity.max_pending_entries),
339
- max_pending_bytes.unwrap_or(default_capacity.max_pending_bytes),
340
- ));
341
-
342
- // Replaying the log restores the deduplication set, so a restarted node
343
- // does not re-apply operations it already durably recorded.
344
- let mut replayed = 0usize;
345
- if let Some(path) = &log_path {
346
- if path.exists() {
347
- replayed = executor.load_from_disk(path.clone())?;
348
- }
349
- }
350
-
351
- for (peer, _) in send_specs.iter().chain(recv_specs.iter()) {
352
- if peer != &node_id {
353
- executor.register_replica(peer.clone(), initial_state.clone());
354
- }
355
- }
356
-
357
- // The running state version, advanced by every applied transaction.
358
- //
359
- // This matters more than it looks. `execute_transaction` derives the next
360
- // hash as H(parent_hash, command), so passing a fresh zero-hash parent each
361
- // time — as this binary and the old in-process tests both did — makes the
362
- // resulting "state hash" a function of the last command alone, carrying no
363
- // history. Two nodes that applied completely different sets of operations
364
- // then report identical hashes as long as the most recent one matches, and
365
- // a convergence check built on that comparison cannot fail.
366
- //
367
- // Chaining it makes the hash mean what its name says, and makes a node that
368
- // missed an operation observably divergent. It lives beside the executor
369
- // under one lock so a local commit and an arriving replication cannot
370
- // interleave and chain onto a parent that is already stale.
371
- let recovered_version = executor
372
- .get_replica_state(&node_id)
373
- .map(|replica| StateVersion::new(replica.vector_clock, replica.state_hash.to_string()))
374
- .unwrap_or_else(|| StateVersion::new(VectorClock::new(), "0".repeat(64)));
375
- let core = Arc::new(Mutex::new((executor, recovered_version)));
376
- let should_exit = Arc::new(AtomicBool::new(false));
377
- // Per-peer delivery hold.
378
- //
379
- // `Some(queue)` means messages arriving from that peer are received off the
380
- // socket as normal and parked instead of being handed to the executor.
381
- // This is how a test produces out-of-causal-order arrival without faking
382
- // the network: the bytes really do cross TCP, and only the order in which
383
- // the receiver is offered them is controlled. A real network reorders
384
- // delivery this way; the harness just does it on demand.
385
- let held: Arc<Mutex<HashMap<String, Vec<ReplicationMessage>>>> =
386
- Arc::new(Mutex::new(HashMap::new()));
387
- // Peers this node has stopped reading from because the causal backlog is
388
- // at its bound.
389
- //
390
- // This is the backpressure the bounding decision commits to, and not
391
- // reading is the whole mechanism: the socket's receive buffer fills, the
392
- // TCP window closes, and the sender blocks. Nothing is dropped and nothing
393
- // is refused, so no retransmission protocol is needed -- which is good,
394
- // because there isn't one.
395
- let backpressured: Arc<Mutex<HashSet<String>>> = Arc::new(Mutex::new(HashSet::new()));
396
- // Kept so `resend-last` can put the identical envelope back on the wire.
397
- let last_envelope: Arc<Mutex<Option<feltdb::distributed_transactions::TransactionEnvelope>>> =
398
- Arc::new(Mutex::new(None));
399
-
400
- let send_links: Vec<SendLink> = send_specs
401
- .into_iter()
402
- .map(|(peer, addr)| {
403
- let transport = TcpTransport::new("127.0.0.1:0".to_string());
404
- SendLink {
405
- peer,
406
- addr,
407
- control: Arc::new(Mutex::new(transport.clone())),
408
- io: transport,
409
- }
410
- })
411
- .collect();
412
-
413
- let recv_links: Vec<RecvLink> = recv_specs
414
- .into_iter()
415
- .map(|(peer, addr)| {
416
- let transport = TcpTransport::new(addr.clone());
417
- RecvLink {
418
- peer,
419
- addr,
420
- control: Arc::new(Mutex::new(transport.clone())),
421
- io: transport,
422
- }
423
- })
424
- .collect();
425
-
426
- // Accept, serve, and accept again. One task per link, because each parks
427
- // on its own socket and must not block the others.
428
- //
429
- // The re-listen is what makes a peer restart survivable. `TcpTransport`'s
430
- // `listen()` binds, accepts exactly one connection, and drops the listener,
431
- // so after that first accept the port is closed and a peer that reconnects
432
- // is refused. Looping back to `listen()` once the connection ends rebinds
433
- // the port for the next one. That is a harness-level fix: the transport
434
- // keeps its single-connection shape and the protocol is untouched.
435
- for link in &recv_links {
436
- let control = link.control.clone();
437
- let transport = link.io.clone();
438
- let core = core.clone();
439
- let exit = should_exit.clone();
440
- let peer = link.peer.clone();
441
- let held = held.clone();
442
- let backpressured = backpressured.clone();
443
- let acknowledgements = acknowledgements.clone();
444
- tokio::spawn(async move {
445
- while !exit.load(Ordering::Relaxed) {
446
- {
447
- let mut control = control.lock().await;
448
- if let Err(error) = control.listen().await {
449
- eprintln!("listen for {peer} failed: {error}");
450
- drop(control);
451
- sleep(Duration::from_millis(100)).await;
452
- continue;
453
- }
454
- }
455
-
456
- // Serve this connection until it ends, then rebind for the next.
457
- loop {
458
- if exit.load(Ordering::Relaxed) {
459
- return;
460
- }
461
-
462
- // Do not read from a backpressured peer. While paused, keep
463
- // trying to drain: the work that frees capacity may arrive
464
- // on another connection, and a deferred envelope is already
465
- // durable, so draining needs nothing from this socket.
466
- while backpressured.lock().await.contains(&peer) {
467
- if exit.load(Ordering::Relaxed) {
468
- return;
469
- }
470
- {
471
- let mut guard = core.lock().await;
472
- let (executor, version) = &mut *guard;
473
- let parent = version.clone();
474
- let _ = executor.drain_backlog(parent);
475
- if executor.pending_causal_count()
476
- < executor.causal_capacity().max_pending_entries
477
- {
478
- backpressured.lock().await.remove(&peer);
479
- }
480
- }
481
- sleep(Duration::from_millis(25)).await;
482
- }
483
-
484
- match transport.receive().await {
485
- Ok(messages) => {
486
- // Acknowledgements arrive interleaved with
487
- // envelopes on the same connection. They are
488
- // durable before they count as evidence.
489
- for ack in transport.take_acknowledgements().await {
490
- let mut store = acknowledgements.lock().await;
491
- if let Err(error) = store.record(ack) {
492
- eprintln!("acknowledgement not recorded: {error}");
493
- }
494
- }
495
- for message in messages {
496
- {
497
- let mut hold = held.lock().await;
498
- if let Some(queue) = hold.get_mut(&peer) {
499
- queue.push(message);
500
- continue;
501
- }
502
- }
503
- let outcome = {
504
- let mut guard = core.lock().await;
505
- let (executor, version) = &mut *guard;
506
- deliver(executor, version, message)
507
- };
508
- if outcome.get("outcome").and_then(|value| value.as_str())
509
- == Some("capacity_deferred")
510
- {
511
- backpressured.lock().await.insert(peer.clone());
512
- }
513
- }
514
- }
515
- Err(_) => break,
516
- }
517
- }
518
- }
519
- });
520
- }
521
-
522
- // Establish outbound links before announcing readiness, so a test that
523
- // writes immediately is not racing the topology.
524
- for link in &send_links {
525
- connect_with_retry(link, 20).await;
526
- }
527
-
528
- println!(
529
- "STARTED {}",
530
- json!({
531
- "node": node_id,
532
- "replayed": replayed,
533
- "send": send_links.iter().map(|l| l.peer.clone()).collect::<Vec<_>>(),
534
- "recv": recv_links.iter().map(|l| l.peer.clone()).collect::<Vec<_>>(),
535
- })
536
- );
537
- io::stdout().flush().ok();
538
-
539
- let (commands, mut incoming) = mpsc::channel::<String>(100);
540
- tokio::task::spawn_blocking(move || {
541
- for line in io::stdin().lock().lines().map_while(Result::ok) {
542
- let line = line.trim().to_string();
543
- if !line.is_empty() && commands.blocking_send(line).is_err() {
544
- break;
545
- }
546
- }
547
- });
548
-
549
- while let Some(command) = incoming.recv().await {
550
- let (verb, argument) = match command.split_once(' ') {
551
- Some((verb, rest)) => (verb, rest.trim().to_string()),
552
- None => (command.as_str(), String::new()),
553
- };
554
-
555
- match verb {
556
- "execute-tx" | "authority-write" => {
557
- let record = if verb == "authority-write" {
558
- let mut parts = argument.splitn(2, ' ');
559
- let token = parts.next().and_then(|value| value.parse::<u64>().ok());
560
- let record = parts.next().unwrap_or("item").to_string();
561
- let validation = match token {
562
- Some(token) => authority
563
- .lock()
564
- .await
565
- .validate_quorum_write(FencingToken(token), 2),
566
- None => Err(feltdb::authority_failover::AuthorityError::StaleToken {
567
- provided: FencingToken(0),
568
- current: authority.lock().await.state().current_token(),
569
- }),
570
- };
571
- if let Err(error) = validation {
572
- println!(
573
- "AUTHORITY_WRITE_REJECTED {}",
574
- json!({ "record": record, "reason": error.to_string() })
575
- );
576
- io::stdout().flush().ok();
577
- continue;
578
- }
579
- record
580
- } else if argument.is_empty() {
581
- "item".to_string()
582
- } else {
583
- argument
584
- };
585
-
586
- let mut fields = HashMap::new();
587
- fields.insert("record".to_string(), json!(record.clone()));
588
-
589
- // The identity comes from the executor, which derives it from the
590
- // durable log. This binary used to keep its own counter,
591
- // starting at 1 on every launch, so a restarted node re-issued
592
- // identities its own earlier operations already held.
593
- let (parent, seq) = {
594
- let guard = core.lock().await;
595
- (guard.1.clone(), guard.0.next_origin_sequence())
596
- };
597
- let operation = Operation::new(
598
- OperationId::new(node_id.clone(), seq),
599
- parent.clone(),
600
- format!("tx_{record}"),
601
- OperationCommand {
602
- op_type: "set".to_string(),
603
- collection: "items".to_string(),
604
- record_id: record.clone(),
605
- fields,
606
- },
607
- node_id.clone(),
608
- );
609
-
610
- let envelope = {
611
- let mut guard = core.lock().await;
612
- let (executor, version) = &mut *guard;
613
- // The admitting entry point: local work is refused when the
614
- // causal backlog is at its bound, before anything is
615
- // executed or persisted, so a refusal leaves nothing to
616
- // undo.
617
- let result = executor.submit_local_transaction(
618
- format!("tx_{record}"),
619
- version.clone(),
620
- vec![operation],
621
- ConsistencyContract::local(),
622
- );
623
- if matches!(result, Ok(SubmitOutcome::Admitted(_))) {
624
- if let Some(replica) = executor.get_replica_state(&node_id) {
625
- version.state_hash = replica.state_hash.to_string();
626
- version.vector_clock.increment(&node_id);
627
- }
628
- }
629
- result
630
- };
631
-
632
- match envelope {
633
- Ok(SubmitOutcome::CapacityExceeded {
634
- pending_entries,
635
- pending_bytes,
636
- limit_entries,
637
- limit_bytes,
638
- }) => {
639
- // A distinct outcome, not an error: the caller should
640
- // retry, and nothing was admitted or lost.
641
- println!(
642
- "TX_REFUSED {}",
643
- json!({
644
- "record": record,
645
- "reason": "CAPACITY_EXCEEDED",
646
- "pending_entries": pending_entries,
647
- "pending_bytes": pending_bytes,
648
- "limit_entries": limit_entries,
649
- "limit_bytes": limit_bytes,
650
- })
651
- );
652
- }
653
- Ok(SubmitOutcome::Admitted(envelope)) => {
654
- *last_envelope.lock().await = Some(envelope.clone());
655
- let mut delivered = Vec::new();
656
- let mut failed = Vec::new();
657
- for link in &send_links {
658
- let message = ReplicationMessage::new(
659
- envelope.clone(),
660
- node_id.clone(),
661
- link.peer.clone(),
662
- seq,
663
- );
664
- let sent = link.io.send(message.clone()).await;
665
- match sent {
666
- Ok(()) => delivered.push(link.peer.clone()),
667
- Err(_) => {
668
- // The peer may have restarted, which drops
669
- // our end of the socket. Reconnect and try
670
- // once more before reporting a failure.
671
- if connect_with_retry(link, 3).await {
672
- if link.io.send(message).await.is_ok() {
673
- delivered.push(link.peer.clone());
674
- continue;
675
- }
676
- }
677
- failed.push(link.peer.clone());
678
- }
679
- }
680
- }
681
- println!(
682
- "TX_EXECUTED {}",
683
- json!({
684
- "record": record,
685
- // The identity the executor actually issued.
686
- "sequence": envelope.envelope_id.sequence,
687
- "delivered": delivered,
688
- "failed": failed,
689
- })
690
- );
691
- }
692
- Err(error) => println!("ERROR {}", json!({ "reason": error })),
693
- }
694
- io::stdout().flush().ok();
695
- }
696
-
697
- // Re-send the most recent envelope, unchanged, to every peer.
698
- //
699
- // Deduplication is a claimed property of the executor, and it is
700
- // only interesting if the duplicate actually crosses the wire.
701
- // Issuing another `execute-tx` would not test it: that produces a
702
- // new envelope id and is a different transaction.
703
- // `resend SEQUENCE` — put one specific envelope from this node's
704
- // durable log back on the wire.
705
- //
706
- // A peer re-sending an operation another node is missing is the
707
- // primitive catch-up is built from. This is not catch-up: nothing
708
- // decides *what* to send, a test does. It exists so the receiver's
709
- // behaviour can be exercised on a gap that a restart left behind.
710
- // `forward ORIGIN SEQUENCE` -- put an envelope this node holds, but
711
- // did not originate, back on the wire.
712
- //
713
- // `resend` deliberately only replays this node's own envelopes. A
714
- // peer relaying an operation that a *third* node is missing is a
715
- // different primitive, and it is the one a backpressured receiver
716
- // needs: when a node stops reading from the peer whose envelopes it
717
- // deferred, the dependency it is waiting for cannot arrive on that
718
- // link. It has to come from somewhere else, which is precisely the
719
- // case the read loop's keep-draining-while-paused behaviour exists
720
- // to serve.
721
- "forward" => {
722
- let mut parts = argument.split_whitespace();
723
- let origin = parts.next().unwrap_or_default().to_string();
724
- let wanted: Option<u64> = parts.next().and_then(|value| value.parse().ok());
725
- let envelope = {
726
- let guard = core.lock().await;
727
- let (executor, _) = &*guard;
728
- executor.operation_log.as_ref().and_then(|log| {
729
- log.load_all().ok().and_then(|envelopes| {
730
- envelopes.into_iter().find(|candidate| {
731
- candidate.envelope_id.originating_node == origin
732
- && Some(candidate.envelope_id.sequence) == wanted
733
- })
734
- })
735
- })
736
- };
737
- match envelope {
738
- Some(envelope) => {
739
- let mut delivered = Vec::new();
740
- for link in &send_links {
741
- let message = ReplicationMessage::new(
742
- envelope.clone(),
743
- node_id.clone(),
744
- link.peer.clone(),
745
- envelope.envelope_id.sequence,
746
- );
747
- // Forwarding, like re-sending, is an explicit
748
- // recovery operation. Prefer a fresh connection so
749
- // a peer restart cannot leave this write accepted
750
- // by a stale local socket and delivered nowhere.
751
- let _ = connect_with_retry(link, 3).await;
752
- if link.io.send(message).await.is_ok() {
753
- delivered.push(link.peer.clone());
754
- }
755
- }
756
- println!(
757
- "FORWARDED {}",
758
- json!({
759
- "origin": origin,
760
- "sequence": wanted,
761
- "delivered": delivered,
762
- })
763
- );
764
- }
765
- None => println!(
766
- "FORWARDED {}",
767
- json!({ "origin": origin, "sequence": wanted, "delivered": [], "found": false })
768
- ),
769
- }
770
- io::stdout().flush().ok();
771
- }
772
-
773
- "resend" => {
774
- let wanted: Option<u64> = argument.trim().parse().ok();
775
- let envelope = {
776
- let guard = core.lock().await;
777
- let (executor, _) = &*guard;
778
- executor.operation_log.as_ref().and_then(|log| {
779
- log.load_all().ok().and_then(|envelopes| {
780
- envelopes.into_iter().find(|candidate| {
781
- candidate.envelope_id.originating_node == node_id
782
- && Some(candidate.envelope_id.sequence) == wanted
783
- })
784
- })
785
- })
786
- };
787
- match envelope {
788
- Some(envelope) => {
789
- let mut delivered = Vec::new();
790
- for link in &send_links {
791
- let message = ReplicationMessage::new(
792
- envelope.clone(),
793
- node_id.clone(),
794
- link.peer.clone(),
795
- envelope.envelope_id.sequence,
796
- );
797
- // Re-sending is the recovery primitive used after a
798
- // peer restart. A write to the old TCP socket can
799
- // still return success after the peer has exited,
800
- // because that only confirms acceptance by the
801
- // local kernel. Try to establish a fresh connection
802
- // first. If the peer never restarted, the connect
803
- // attempt leaves the established stream untouched.
804
- let _ = connect_with_retry(link, 3).await;
805
- if link.io.send(message).await.is_ok() {
806
- delivered.push(link.peer.clone());
807
- }
808
- }
809
- println!(
810
- "RESENT {}",
811
- json!({ "envelope": envelope.envelope_id, "delivered": delivered })
812
- );
813
- }
814
- None => println!(
815
- "ERROR {}",
816
- json!({ "reason": format!("no envelope {argument} in this node's log") })
817
- ),
818
- }
819
- io::stdout().flush().ok();
820
- }
821
-
822
- "resend-last" => {
823
- let envelope = last_envelope.lock().await.clone();
824
- match envelope {
825
- Some(envelope) => {
826
- let mut delivered = Vec::new();
827
- for link in &send_links {
828
- let message = ReplicationMessage::new(
829
- envelope.clone(),
830
- node_id.clone(),
831
- link.peer.clone(),
832
- envelope.envelope_id.sequence,
833
- );
834
- if link.io.send(message).await.is_ok() {
835
- delivered.push(link.peer.clone());
836
- }
837
- }
838
- println!(
839
- "RESENT {}",
840
- json!({ "envelope": envelope.envelope_id, "delivered": delivered })
841
- );
842
- }
843
- None => println!("ERROR {}", json!({ "reason": "nothing sent yet" })),
844
- }
845
- io::stdout().flush().ok();
846
- }
847
-
848
- "query-state" => {
849
- let guard = core.lock().await;
850
- let (executor, _) = &*guard;
851
- let canonical = executor
852
- .canonical_state()
853
- .map(|state| state.hash().to_string())
854
- .unwrap_or_else(|error| format!("error: {error}"));
855
- match executor.get_replica_state(&node_id) {
856
- Some(replica) => println!(
857
- "STATE {}",
858
- json!({
859
- "hash": replica.state_hash.to_string(),
860
- "operations_applied": replica.operations_applied,
861
- // Order-independent, and therefore the one to
862
- // compare across replicas. The chained hash above
863
- // detects a missing operation in an ordered
864
- // history; it calls two nodes that applied the same
865
- // concurrent operations in different orders
866
- // divergent, which they are not.
867
- "canonical_hash": canonical,
868
- })
869
- ),
870
- None => println!("ERROR {}", json!({ "reason": "no local replica" })),
871
- }
872
- io::stdout().flush().ok();
873
- }
874
-
875
- // What the causal receiver is holding, and why.
876
- // Membership commands. Deliberately no compaction frontier and no
877
- // acknowledgement tracking: this establishes who exists and who is
878
- // required, and reclaims nothing.
879
- "cluster-create" => {
880
- let result = membership.create_cluster(argument.trim());
881
- println!(
882
- "CLUSTER {}",
883
- match result {
884
- Ok(()) => json!({ "cluster_id": argument.trim(), "created": true }),
885
- Err(error) => json!({ "created": false, "error": error.to_string() }),
886
- }
887
- );
888
- io::stdout().flush().ok();
889
- }
890
-
891
- "replica-join" | "replica-approve" | "replica-suspect" | "replica-seen"
892
- | "replica-remove" => {
893
- let mut parts = argument.split_whitespace();
894
- let replica = parts.next().unwrap_or_default().to_string();
895
- let result = match verb {
896
- "replica-join" => {
897
- let node = parts.next().unwrap_or(&replica).to_string();
898
- membership.join_replica(&replica, &node)
899
- }
900
- "replica-approve" => membership.approve_replica(&replica),
901
- "replica-suspect" => membership.suspect_replica(&replica),
902
- "replica-seen" => membership.mark_seen(&replica),
903
- _ => membership.remove_replica(&replica),
904
- };
905
- println!(
906
- "REPLICA {}",
907
- match result {
908
- Ok(()) => json!({ "replica_id": replica, "action": verb, "ok": true }),
909
- Err(error) =>
910
- json!({ "replica_id": replica, "action": verb, "ok": false, "error": error.to_string() }),
911
- }
912
- );
913
- io::stdout().flush().ok();
914
- }
915
-
916
- // Report what this node has durably applied, to every peer.
917
- //
918
- // The frontier is read from the executor, where it advances only in
919
- // record_applied -- after an fsynced envelope is applied. So the
920
- // statement "durably applied through this frontier" is true by
921
- // construction rather than by assertion.
922
- "ack-broadcast" => {
923
- let (frontier, marks) = {
924
- let guard = core.lock().await;
925
- let (executor, _) = &*guard;
926
- (
927
- executor.causal_frontier().clone(),
928
- executor.origin_sequence_high_water(),
929
- )
930
- };
931
- let view: Vec<String> = membership
932
- .get_members()
933
- .into_iter()
934
- .map(|replica| replica.replica_id)
935
- .collect();
936
- let replica_id = membership
937
- .get_members()
938
- .into_iter()
939
- .find(|replica| replica.node_id == node_id)
940
- .map(|replica| replica.replica_id)
941
- .unwrap_or_else(|| node_id.clone());
942
-
943
- let ack = ReplicaAcknowledgement::new(
944
- replica_id.clone(),
945
- node_id.clone(),
946
- frontier,
947
- marks,
948
- view,
949
- );
950
-
951
- // A node's own acknowledgement is evidence it holds first-hand:
952
- // it knows its own durable frontier without being told. Without
953
- // this a node would be permanently blocked on itself, since
954
- // nothing else can report on its behalf.
955
- {
956
- let mut store = acknowledgements.lock().await;
957
- let _ = store.record(ack.clone());
958
- }
959
-
960
- let mut delivered = Vec::new();
961
- for link in &send_links {
962
- // Acknowledgements are durable recovery evidence and are
963
- // commonly rebroadcast after a peer restart. Refresh the
964
- // connection first for the same reason as explicit
965
- // envelope replay.
966
- let _ = connect_with_retry(link, 3).await;
967
- if link.io.send_acknowledgement(ack.clone()).await.is_ok() {
968
- delivered.push(link.peer.clone());
969
- }
970
- }
971
- println!(
972
- "ACK_SENT {}",
973
- json!({
974
- "replica_id": replica_id,
975
- "frontier": ack.applied_frontier.clocks,
976
- "delivered": delivered,
977
- })
978
- );
979
- io::stdout().flush().ok();
980
- }
981
-
982
- "acknowledgements" => {
983
- let store = acknowledgements.lock().await;
984
- println!("ACKS {}", json!({ "acknowledgements": store.all() }));
985
- io::stdout().flush().ok();
986
- }
987
-
988
- // What this node's evidence permits. Computes; reclaims nothing.
989
- "reclamation-evidence" => {
990
- let local: Vec<String> = membership
991
- .get_required_replicas()
992
- .into_iter()
993
- .map(|replica| replica.replica_id)
994
- .collect();
995
- let store = acknowledgements.lock().await;
996
- let report = match store.evidence(&local) {
997
- ReclamationEvidence::Blocked { waiting_on } => json!({
998
- "state": "BLOCKED",
999
- "waiting_on": waiting_on,
1000
- "required_union": store.required_union(&local),
1001
- }),
1002
- ReclamationEvidence::SafeThrough { frontier, covered } => json!({
1003
- "state": "SAFE_THROUGH",
1004
- "frontier": frontier.clocks,
1005
- "covered": covered,
1006
- "required_union": store.required_union(&local),
1007
- }),
1008
- };
1009
- println!("EVIDENCE {}", report);
1010
- io::stdout().flush().ok();
1011
- }
1012
-
1013
- "members" => {
1014
- println!(
1015
- "MEMBERS {}",
1016
- json!({
1017
- "cluster_id": membership.cluster().map(|c| c.cluster_id.clone()),
1018
- "members": membership.get_members(),
1019
- "all": membership.all_replicas(),
1020
- })
1021
- );
1022
- io::stdout().flush().ok();
1023
- }
1024
-
1025
- "required-replicas" => {
1026
- println!(
1027
- "REQUIRED {}",
1028
- json!({ "required": membership.get_required_replicas() })
1029
- );
1030
- io::stdout().flush().ok();
1031
- }
1032
-
1033
- "causal-state" => {
1034
- let guard = core.lock().await;
1035
- let (executor, _) = &*guard;
1036
- let paused = {
1037
- let mut peers: Vec<String> =
1038
- backpressured.lock().await.iter().cloned().collect();
1039
- peers.sort();
1040
- peers
1041
- };
1042
- println!(
1043
- "CAUSAL {}",
1044
- json!({
1045
- "frontier": executor.causal_frontier().clocks,
1046
- "pending": executor.pending_causal_keys(),
1047
- "pending_count": executor.pending_causal_count(),
1048
- "pending_metadata_bytes": executor.pending_causal_metadata_bytes(),
1049
- // Occupancy against the bound that is now enforced.
1050
- // Current and maximum, for both dimensions, because a
1051
- // backlog that filled and drained between two reads is
1052
- // invisible to a current-value gauge.
1053
- "pending_payload_bytes": executor.pending_causal_payload_bytes(),
1054
- "deferred_count": executor.deferred_causal_count(),
1055
- "deferred": executor.deferred_causal_keys(),
1056
- "max_pending_entries_seen": executor.max_causal_occupancy().0,
1057
- "max_pending_payload_bytes_seen": executor.max_causal_occupancy().1,
1058
- "limit_entries": executor.causal_capacity().max_pending_entries,
1059
- "limit_bytes": executor.causal_capacity().max_pending_bytes,
1060
- // Peers this node has stopped reading from. This is the
1061
- // observable for backpressure: the pause lives in the
1062
- // socket read loop, so without it a test can only infer
1063
- // the behaviour from timing.
1064
- //
1065
- // Locked after `core`, matching the order the read loop
1066
- // takes them in.
1067
- "backpressured": paused,
1068
- })
1069
- );
1070
- io::stdout().flush().ok();
1071
- }
1072
-
1073
- // Park messages from a peer instead of delivering them, so a test
1074
- // can choose the order the receiver is offered them in.
1075
- "hold-inbound" => {
1076
- let mut hold = held.lock().await;
1077
- hold.entry(argument.clone()).or_default();
1078
- println!("HELD {}", json!({ "peer": argument }));
1079
- io::stdout().flush().ok();
1080
- }
1081
-
1082
- // Deliver what was parked, optionally newest-first, then resume
1083
- // normal delivery. Each message goes through the same production
1084
- // receiver as an unheld one.
1085
- // `release-inbound PEER [reverse] [COUNT]`
1086
- //
1087
- // COUNT releases only that many and keeps holding the rest, which
1088
- // is what lets a test offer an operation while deliberately
1089
- // withholding the dependency it needs.
1090
- "release-inbound" => {
1091
- let mut parts = argument.split_whitespace();
1092
- let peer = parts.next().unwrap_or_default().to_string();
1093
- let mut reverse = false;
1094
- let mut limit: Option<usize> = None;
1095
- for token in parts {
1096
- if token == "reverse" {
1097
- reverse = true;
1098
- } else if let Ok(value) = token.parse::<usize>() {
1099
- limit = Some(value);
1100
- }
1101
- }
1102
-
1103
- let mut queued = { held.lock().await.remove(&peer).unwrap_or_default() };
1104
- if reverse {
1105
- queued.reverse();
1106
- }
1107
- let take = limit.unwrap_or(queued.len()).min(queued.len());
1108
- let remainder = queued.split_off(take);
1109
-
1110
- let mut outcomes = Vec::new();
1111
- for message in queued {
1112
- let mut guard = core.lock().await;
1113
- let (executor, version) = &mut *guard;
1114
- outcomes.push(deliver(executor, version, message));
1115
- }
1116
-
1117
- // Anything not released stays held, so the hold is still in
1118
- // force for messages that arrive afterwards.
1119
- if !remainder.is_empty() || limit.is_some() {
1120
- held.lock().await.insert(peer.clone(), remainder);
1121
- }
1122
-
1123
- println!(
1124
- "RELEASED {}",
1125
- json!({ "peer": peer, "reverse": reverse, "outcomes": outcomes })
1126
- );
1127
- io::stdout().flush().ok();
1128
- }
1129
-
1130
- // The proof that the network carried the work. Counters come from
1131
- // the transport itself, so a test can assert bytes actually moved
1132
- // rather than inferring it from converged state.
1133
- "metrics" => {
1134
- let mut sent = json!({});
1135
- let mut received = json!({});
1136
- for link in &send_links {
1137
- let metrics = link.io.metrics();
1138
- sent[&link.peer] = json!({
1139
- "bytes": metrics.get_bytes_sent(),
1140
- "messages": metrics.get_messages_sent(),
1141
- "connection_changes": metrics.get_connection_state_changes(),
1142
- });
1143
- }
1144
- for link in &recv_links {
1145
- let metrics = link.io.metrics();
1146
- received[&link.peer] = json!({
1147
- "bytes": metrics.get_bytes_received(),
1148
- "messages": metrics.get_messages_received(),
1149
- "connection_changes": metrics.get_connection_state_changes(),
1150
- });
1151
- }
1152
- println!("METRICS {}", json!({ "sent": sent, "received": received }));
1153
- io::stdout().flush().ok();
1154
- }
1155
-
1156
- "list-operations" => {
1157
- let guard = core.lock().await;
1158
- let (executor, _) = &*guard;
1159
-
1160
- // Get all operations from the log
1161
- let mut all_ops = Vec::new();
1162
- let mut applied_ops = Vec::new();
1163
- let pending_ops = executor.pending_causal_keys();
1164
- let deferred_ops = executor.deferred_causal_keys();
1165
-
1166
- if let Some(ref log) = executor.operation_log {
1167
- if let Ok(envelopes) = log.load_all() {
1168
- for env in &envelopes {
1169
- let key = format!(
1170
- "{}:{}",
1171
- env.envelope_id.originating_node, env.envelope_id.sequence
1172
- );
1173
- all_ops.push(key.clone());
1174
- }
1175
- }
1176
- }
1177
-
1178
- // Get applied operations from the barrier
1179
- for key in executor.causal_barrier.applied_keys() {
1180
- applied_ops.push(key.to_string());
1181
- }
1182
-
1183
- println!(
1184
- "OPERATIONS {}",
1185
- json!({
1186
- "all": all_ops,
1187
- "applied": applied_ops,
1188
- "pending": pending_ops,
1189
- "deferred": deferred_ops,
1190
- "operations_applied": executor.get_replica_state(&node_id).map(|r| r.operations_applied).unwrap_or(0),
1191
- })
1192
- );
1193
- io::stdout().flush().ok();
1194
- }
1195
-
1196
- "operation-inventory" => {
1197
- let guard = core.lock().await;
1198
- let (executor, _) = &*guard;
1199
-
1200
- // Get all operations from the log
1201
- let mut operations = json!({
1202
- "applied": [],
1203
- "pending": [],
1204
- "deferred": [],
1205
- });
1206
-
1207
- if let Some(ref log) = executor.operation_log {
1208
- if let Ok(envelopes) = log.load_all() {
1209
- let applied_keys = executor.causal_barrier.applied_keys();
1210
- let pending_keys = executor.pending_causal_keys();
1211
- let deferred_keys = executor.deferred_causal_keys();
1212
-
1213
- for env in &envelopes {
1214
- let key = format!(
1215
- "{}:{}",
1216
- env.envelope_id.originating_node, env.envelope_id.sequence
1217
- );
1218
-
1219
- if applied_keys.contains(&key) {
1220
- operations["applied"].as_array_mut().unwrap().push(json!({
1221
- "id": key,
1222
- "origin": env.envelope_id.originating_node,
1223
- "sequence": env.envelope_id.sequence,
1224
- }));
1225
- } else if pending_keys.contains(&key) {
1226
- operations["pending"].as_array_mut().unwrap().push(json!({
1227
- "id": key,
1228
- "origin": env.envelope_id.originating_node,
1229
- "sequence": env.envelope_id.sequence,
1230
- }));
1231
- } else if deferred_keys.contains(&key) {
1232
- operations["deferred"].as_array_mut().unwrap().push(json!({
1233
- "id": key,
1234
- "origin": env.envelope_id.originating_node,
1235
- "sequence": env.envelope_id.sequence,
1236
- }));
1237
- }
1238
- }
1239
- }
1240
- }
1241
-
1242
- println!("INVENTORY {}", operations);
1243
- io::stdout().flush().ok();
1244
- }
1245
-
1246
- "peers" => {
1247
- let mut state = json!({});
1248
- for link in &send_links {
1249
- state[format!("send:{}", link.peer)] = json!(link.io.is_connected());
1250
- }
1251
- for link in &recv_links {
1252
- state[format!("recv:{}", link.peer)] = json!(link.io.is_connected());
1253
- }
1254
- println!("PEERS {state}");
1255
- io::stdout().flush().ok();
1256
- }
1257
-
1258
- // Authority failover commands.
1259
- //
1260
- // These commands implement the distributed authority protocol that prevents
1261
- // split-brain and allows automatic failover when the authority node fails.
1262
-
1263
- // Claim authority for this node. This generates a new fencing token that
1264
- // supersedes any previous authority. The claim succeeds only if no other
1265
- // node has a higher token and appears healthy.
1266
- "authority-claim" => {
1267
- let mut guard = authority.lock().await;
1268
- let result = guard.claim_authority();
1269
- match result {
1270
- Ok(ClaimResult::Granted { token }) => {
1271
- println!(
1272
- "AUTHORITY_CLAIMED {}",
1273
- json!({
1274
- "node": node_id,
1275
- "fencing_token": token.0,
1276
- "role": "AUTHORITY",
1277
- })
1278
- );
1279
- }
1280
- Ok(ClaimResult::Rejected {
1281
- current_authority,
1282
- current_token,
1283
- }) => {
1284
- println!(
1285
- "AUTHORITY_REJECTED {}",
1286
- json!({
1287
- "current_authority": current_authority,
1288
- "current_token": current_token.0,
1289
- "reason": "another authority is still active",
1290
- })
1291
- );
1292
- }
1293
- Ok(ClaimResult::Fenced { token }) => {
1294
- println!(
1295
- "AUTHORITY_FENCED {}",
1296
- json!({
1297
- "node": node_id,
1298
- "fencing_token": token.0,
1299
- "reason": "this node is fenced",
1300
- })
1301
- );
1302
- }
1303
- Err(e) => {
1304
- println!("ERROR {}", json!({ "reason": e.to_string() }));
1305
- }
1306
- }
1307
- io::stdout().flush().ok();
1308
- }
1309
-
1310
- // Accept a new authority announced by another node.
1311
- // Format: authority-accept NODE_ID TOKEN
1312
- "authority-accept" => {
1313
- let mut parts = argument.split_whitespace();
1314
- let authority_node = parts.next().unwrap_or_default().to_string();
1315
- let token_value: u64 = parts.next().unwrap_or("0").parse().unwrap_or(0);
1316
- let token = FencingToken(token_value);
1317
-
1318
- let mut guard = authority.lock().await;
1319
- let result = guard.accept_authority(&authority_node, token);
1320
- match result {
1321
- Ok(()) => {
1322
- let state = guard.state();
1323
- println!(
1324
- "AUTHORITY_ACCEPTED {}",
1325
- json!({
1326
- "authority_node": authority_node,
1327
- "fencing_token": token.0,
1328
- "local_role": format!("{:?}", state.local_role),
1329
- })
1330
- );
1331
- }
1332
- Err(e) => {
1333
- println!("ERROR {}", json!({ "reason": e.to_string() }));
1334
- }
1335
- }
1336
- io::stdout().flush().ok();
1337
- }
1338
-
1339
- // Query the current authority state.
1340
- "authority-state" => {
1341
- let guard = authority.lock().await;
1342
- let state = guard.state();
1343
- let operations_applied = {
1344
- let core_guard = core.lock().await;
1345
- let (executor, _) = &*core_guard;
1346
- executor
1347
- .get_replica_state(&node_id)
1348
- .map(|r| r.operations_applied)
1349
- .unwrap_or(0)
1350
- };
1351
- println!(
1352
- "AUTHORITY {}",
1353
- json!({
1354
- "cluster_id": state.cluster_id,
1355
- "local_node": state.local_node_id,
1356
- "local_role": format!("{:?}", state.local_role),
1357
- "is_authority": state.is_authority(),
1358
- "is_fenced": state.is_fenced(),
1359
- "authority_node": state.authority_node(),
1360
- "current_token": state.current_token().0,
1361
- "highest_seen_token": state.highest_seen_token.0,
1362
- "operations_applied": operations_applied,
1363
- })
1364
- );
1365
- io::stdout().flush().ok();
1366
- }
1367
-
1368
- // Record a heartbeat from a peer. This is used for failure detection.
1369
- // Format: heartbeat NODE_ID [TOKEN] [OPS_APPLIED] [IS_AUTHORITY]
1370
- "heartbeat" => {
1371
- let mut parts = argument.split_whitespace();
1372
- let from_node = parts.next().unwrap_or_default().to_string();
1373
-
1374
- if from_node.is_empty() {
1375
- println!(
1376
- "ERROR {}",
1377
- json!({ "reason": "heartbeat requires NODE_ID" })
1378
- );
1379
- } else {
1380
- let mut guard = authority.lock().await;
1381
- guard.heartbeat(&from_node);
1382
- println!(
1383
- "HEARTBEAT_RECORDED {}",
1384
- json!({
1385
- "from_node": from_node,
1386
- })
1387
- );
1388
- }
1389
- io::stdout().flush().ok();
1390
- }
1391
-
1392
- // Check if the current authority has failed based on heartbeat timeout.
1393
- "authority-failed" => {
1394
- let guard = authority.lock().await;
1395
- let failed = guard.authority_failed();
1396
- let failed_nodes = guard.failed_nodes();
1397
- let state = guard.state();
1398
- println!(
1399
- "AUTHORITY_CHECK {}",
1400
- json!({
1401
- "authority_failed": failed,
1402
- "current_authority": state.authority_node(),
1403
- "failed_nodes": failed_nodes,
1404
- })
1405
- );
1406
- io::stdout().flush().ok();
1407
- }
1408
-
1409
- // Step down from authority role voluntarily.
1410
- "authority-stepdown" => {
1411
- let mut guard = authority.lock().await;
1412
- let was_authority = guard.state().is_authority();
1413
- let result = guard.step_down();
1414
- match result {
1415
- Ok(()) => {
1416
- println!(
1417
- "AUTHORITY_STEPDOWN {}",
1418
- json!({
1419
- "node": node_id,
1420
- "was_authority": was_authority,
1421
- "now_role": format!("{:?}", guard.state().local_role),
1422
- })
1423
- );
1424
- }
1425
- Err(e) => {
1426
- println!("ERROR {}", json!({ "reason": e.to_string() }));
1427
- }
1428
- }
1429
- io::stdout().flush().ok();
1430
- }
1431
-
1432
- // Recover from fenced state after catching up.
1433
- "authority-recover" => {
1434
- let mut guard = authority.lock().await;
1435
- let was_fenced = guard.state().is_fenced();
1436
- let result = guard.recover_from_fenced();
1437
- match result {
1438
- Ok(()) => {
1439
- println!(
1440
- "AUTHORITY_RECOVERED {}",
1441
- json!({
1442
- "node": node_id,
1443
- "was_fenced": was_fenced,
1444
- "now_role": format!("{:?}", guard.state().local_role),
1445
- })
1446
- );
1447
- }
1448
- Err(e) => {
1449
- println!("ERROR {}", json!({ "reason": e.to_string() }));
1450
- }
1451
- }
1452
- io::stdout().flush().ok();
1453
- }
1454
-
1455
- "shutdown" => {
1456
- should_exit.store(true, Ordering::Relaxed);
1457
- println!("SHUTDOWN");
1458
- io::stdout().flush().ok();
1459
- sleep(Duration::from_millis(50)).await;
1460
- std::process::exit(0);
1461
- }
1462
-
1463
- _ => {
1464
- println!(
1465
- "ERROR {}",
1466
- json!({ "reason": format!("unknown command {verb}") })
1467
- );
1468
- io::stdout().flush().ok();
1469
- }
1470
- }
1471
- }
1472
-
1473
- Ok(())
1474
- }