@feltdb/core 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) 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 +3 -0
  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 +255 -12
  17. package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +1 -1
  18. package/dist/db.d.ts +11 -1
  19. package/dist/db.d.ts.map +1 -1
  20. package/dist/db.js +15 -2
  21. package/dist/error-codes.d.ts +5 -0
  22. package/dist/error-codes.d.ts.map +1 -1
  23. package/dist/error-codes.js +9 -1
  24. package/dist/http-db.d.ts +16 -0
  25. package/dist/http-db.d.ts.map +1 -1
  26. package/dist/http-db.js +36 -6
  27. package/dist/managed-recovery.d.ts +6 -0
  28. package/dist/managed-recovery.d.ts.map +1 -1
  29. package/dist/managed-recovery.js +9 -0
  30. package/dist/studio-app/assets/{feltdb_wasm-BjM1ylL3.js → feltdb_wasm-DtybUY0q.js} +1 -1
  31. package/dist/studio-app/assets/feltdb_wasm_bg-DsgIUwaB.wasm +0 -0
  32. package/dist/studio-app/assets/index-C4OffsZl.js +37 -0
  33. package/dist/studio-app/index.html +1 -1
  34. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  35. package/dist/wasm/package.json +1 -1
  36. package/dist/workload.d.ts +55 -3
  37. package/dist/workload.d.ts.map +1 -1
  38. package/dist/workload.js +23 -0
  39. package/dist/workspace/workspace-connection.d.ts.map +1 -1
  40. package/dist/workspace/workspace-connection.js +14 -3
  41. package/package.json +1 -1
  42. package/dist/create/server-source/crates/feltdb/Cargo.lock +0 -175
  43. package/dist/create/server-source/crates/feltdb/Cargo.toml +0 -102
  44. package/dist/create/server-source/crates/feltdb/LICENSE +0 -21
  45. package/dist/create/server-source/crates/feltdb/README.md +0 -75
  46. package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +0 -370
  47. package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +0 -589
  48. package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +0 -247
  49. package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +0 -440
  50. package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +0 -275
  51. package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +0 -381
  52. package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +0 -467
  53. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +0 -489
  54. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +0 -281
  55. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +0 -606
  56. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +0 -531
  57. package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +0 -462
  58. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +0 -505
  59. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +0 -484
  60. package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +0 -758
  61. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +0 -421
  62. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +0 -329
  63. package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +0 -1705
  64. package/dist/create/server-source/crates/feltdb/src/acquisition.rs +0 -286
  65. package/dist/create/server-source/crates/feltdb/src/admission.rs +0 -185
  66. package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +0 -507
  67. package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +0 -539
  68. package/dist/create/server-source/crates/feltdb/src/agent_state.rs +0 -320
  69. package/dist/create/server-source/crates/feltdb/src/agent_transition_contract.rs +0 -1545
  70. package/dist/create/server-source/crates/feltdb/src/analytics.rs +0 -521
  71. package/dist/create/server-source/crates/feltdb/src/application.rs +0 -2674
  72. package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +0 -1070
  73. package/dist/create/server-source/crates/feltdb/src/authority.rs +0 -479
  74. package/dist/create/server-source/crates/feltdb/src/authority_failover.rs +0 -847
  75. package/dist/create/server-source/crates/feltdb/src/authorization.rs +0 -1030
  76. package/dist/create/server-source/crates/feltdb/src/authorization_security_tests.rs +0 -1122
  77. package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority-client.rs +0 -32
  78. package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority.rs +0 -19
  79. package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +0 -1474
  80. package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +0 -8
  81. package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +0 -418
  82. package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +0 -482
  83. package/dist/create/server-source/crates/feltdb/src/capability.rs +0 -903
  84. package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +0 -263
  85. package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +0 -77
  86. package/dist/create/server-source/crates/feltdb/src/causal_backlog_bound.rs +0 -496
  87. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +0 -2574
  88. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +0 -212
  89. package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +0 -422
  90. package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +0 -445
  91. package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +0 -465
  92. package/dist/create/server-source/crates/feltdb/src/convergence.rs +0 -634
  93. package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +0 -427
  94. package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +0 -376
  95. package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +0 -365
  96. package/dist/create/server-source/crates/feltdb/src/cron.rs +0 -294
  97. package/dist/create/server-source/crates/feltdb/src/dedup_bound_investigation.rs +0 -483
  98. package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +0 -477
  99. package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +0 -1003
  100. package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +0 -1334
  101. package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +0 -368
  102. package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +0 -231
  103. package/dist/create/server-source/crates/feltdb/src/durable_operation_identity.rs +0 -482
  104. package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +0 -203
  105. package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +0 -317
  106. package/dist/create/server-source/crates/feltdb/src/equality_index.rs +0 -595
  107. package/dist/create/server-source/crates/feltdb/src/execution.rs +0 -426
  108. package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +0 -214
  109. package/dist/create/server-source/crates/feltdb/src/indexing.rs +0 -776
  110. package/dist/create/server-source/crates/feltdb/src/lib.rs +0 -7365
  111. package/dist/create/server-source/crates/feltdb/src/managed_cas_tests.rs +0 -234
  112. package/dist/create/server-source/crates/feltdb/src/materialization.rs +0 -184
  113. package/dist/create/server-source/crates/feltdb/src/metrics.rs +0 -266
  114. package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +0 -310
  115. package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +0 -736
  116. package/dist/create/server-source/crates/feltdb/src/observability.rs +0 -379
  117. package/dist/create/server-source/crates/feltdb/src/operation.rs +0 -290
  118. package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +0 -439
  119. package/dist/create/server-source/crates/feltdb/src/operation_log.rs +0 -349
  120. package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +0 -332
  121. package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +0 -718
  122. package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +0 -487
  123. package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +0 -166
  124. package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +0 -289
  125. package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +0 -566
  126. package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +0 -3391
  127. package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +0 -203
  128. package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +0 -261
  129. package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +0 -502
  130. package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +0 -216
  131. package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +0 -303
  132. package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +0 -323
  133. package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +0 -531
  134. package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +0 -406
  135. package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +0 -436
  136. package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +0 -387
  137. package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +0 -378
  138. package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +0 -359
  139. package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +0 -372
  140. package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +0 -404
  141. package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +0 -376
  142. package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +0 -461
  143. package/dist/create/server-source/crates/feltdb/src/policy_evaluation.rs +0 -2125
  144. package/dist/create/server-source/crates/feltdb/src/production_api.rs +0 -418
  145. package/dist/create/server-source/crates/feltdb/src/provenance.rs +0 -207
  146. package/dist/create/server-source/crates/feltdb/src/query_execution_diagnostics.rs +0 -126
  147. package/dist/create/server-source/crates/feltdb/src/query_performance.rs +0 -218
  148. package/dist/create/server-source/crates/feltdb/src/read_basis.rs +0 -257
  149. package/dist/create/server-source/crates/feltdb/src/references.rs +0 -404
  150. package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +0 -401
  151. package/dist/create/server-source/crates/feltdb/src/replica_acknowledgements.rs +0 -501
  152. package/dist/create/server-source/crates/feltdb/src/replica_membership.rs +0 -680
  153. package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +0 -163
  154. package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +0 -133
  155. package/dist/create/server-source/crates/feltdb/src/routing.rs +0 -409
  156. package/dist/create/server-source/crates/feltdb/src/semantic_decision.rs +0 -2029
  157. package/dist/create/server-source/crates/feltdb/src/semantic_query.rs +0 -599
  158. package/dist/create/server-source/crates/feltdb/src/sharding.rs +0 -549
  159. package/dist/create/server-source/crates/feltdb/src/state_conflict_contract.rs +0 -516
  160. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +0 -6003
  161. package/dist/create/server-source/crates/feltdb/src/state_diff_contract.rs +0 -222
  162. package/dist/create/server-source/crates/feltdb/src/state_facade.rs +0 -320
  163. package/dist/create/server-source/crates/feltdb/src/state_hash.rs +0 -291
  164. package/dist/create/server-source/crates/feltdb/src/state_model.rs +0 -4158
  165. package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +0 -379
  166. package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +0 -673
  167. package/dist/create/server-source/crates/feltdb/src/storage.rs +0 -273
  168. package/dist/create/server-source/crates/feltdb/src/submission.rs +0 -471
  169. package/dist/create/server-source/crates/feltdb/src/sync.rs +0 -495
  170. package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +0 -822
  171. package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +0 -433
  172. package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +0 -462
  173. package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +0 -965
  174. package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +0 -1120
  175. package/dist/create/server-source/crates/feltdb/src/transactions.rs +0 -643
  176. package/dist/create/server-source/crates/feltdb/src/trigger.rs +0 -390
  177. package/dist/create/server-source/crates/feltdb/src/trigger_contract.rs +0 -749
  178. package/dist/create/server-source/crates/feltdb/src/versioned_object_substrate.rs +0 -708
  179. package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +0 -1269
  180. package/dist/create/server-source/crates/feltdb/src/workflow.rs +0 -713
  181. package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +0 -510
  182. package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +0 -354
  183. package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +0 -594
  184. package/dist/create/server-source/crates/feltdb/src/workload.rs +0 -4222
  185. package/dist/create/server-source/crates/feltdb/src/workload_diagnostics.rs +0 -456
  186. package/dist/create/server-source/crates/feltdb/tests/agent_state_native.rs +0 -1188
  187. package/dist/create/server-source/crates/feltdb/tests/agent_transition_contract_conformance.rs +0 -2884
  188. package/dist/create/server-source/crates/feltdb/tests/authority_process.rs +0 -297
  189. package/dist/create/server-source/crates/feltdb/tests/authority_transaction_identity.rs +0 -534
  190. package/dist/create/server-source/crates/feltdb/tests/bounded_read_contract.rs +0 -132
  191. package/dist/create/server-source/crates/feltdb/tests/branching_evidence.rs +0 -299
  192. package/dist/create/server-source/crates/feltdb/tests/common/mod.rs +0 -96
  193. package/dist/create/server-source/crates/feltdb/tests/compaction_stall_contract.rs +0 -272
  194. package/dist/create/server-source/crates/feltdb/tests/crash_durability_contract.rs +0 -467
  195. package/dist/create/server-source/crates/feltdb/tests/current_revision_authority_evidence.rs +0 -309
  196. package/dist/create/server-source/crates/feltdb/tests/durable_backup_contract.rs +0 -445
  197. package/dist/create/server-source/crates/feltdb/tests/durable_corruption_contract.rs +0 -518
  198. package/dist/create/server-source/crates/feltdb/tests/durable_format_compatibility.rs +0 -392
  199. package/dist/create/server-source/crates/feltdb/tests/feltdb_state_boundary_tests.rs +0 -850
  200. package/dist/create/server-source/crates/feltdb/tests/fixtures/agent_transition_corpus.json +0 -23340
  201. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_conflict_contract_corpus.json +0 -1916
  202. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_diff_contract_corpus.json +0 -1878
  203. package/dist/create/server-source/crates/feltdb/tests/fixtures/trigger_contract_corpus.json +0 -1862
  204. package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_evidence.rs +0 -123
  205. package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_substrate_completion.rs +0 -299
  206. package/dist/create/server-source/crates/feltdb/tests/local_process_ownership.rs +0 -69
  207. package/dist/create/server-source/crates/feltdb/tests/managed_incident_regression.rs +0 -191
  208. package/dist/create/server-source/crates/feltdb/tests/operational_health_contract.rs +0 -278
  209. package/dist/create/server-source/crates/feltdb/tests/pr34_query_collection.rs +0 -234
  210. package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +0 -947
  211. package/dist/create/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +0 -404
  212. package/dist/create/server-source/crates/feltdb/tests/pr8_vocabulary_assessment.rs +0 -916
  213. package/dist/create/server-source/crates/feltdb/tests/pr9_phase2_boundary_tests.rs +0 -1045
  214. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3a_path_a_tests.rs +0 -347
  215. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_authorized_mutations.rs +0 -361
  216. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_role_based_authorization.rs +0 -330
  217. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_simple_auth_delete.rs +0 -93
  218. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_team_delete_role_authorization.rs +0 -622
  219. package/dist/create/server-source/crates/feltdb/tests/pr9_teams_role_based_access.rs +0 -517
  220. package/dist/create/server-source/crates/feltdb/tests/production_certification.rs +0 -1153
  221. package/dist/create/server-source/crates/feltdb/tests/production_contract.rs +0 -771
  222. package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +0 -1383
  223. package/dist/create/server-source/crates/feltdb/tests/reconciliation_application.rs +0 -868
  224. package/dist/create/server-source/crates/feltdb/tests/reconciliation_wire_format_evidence.rs +0 -221
  225. package/dist/create/server-source/crates/feltdb/tests/replicated_history_contract.rs +0 -417
  226. package/dist/create/server-source/crates/feltdb/tests/resource_scoped_revisions.rs +0 -338
  227. package/dist/create/server-source/crates/feltdb/tests/revision_identity_contract.rs +0 -1039
  228. package/dist/create/server-source/crates/feltdb/tests/revision_model_decision.rs +0 -739
  229. package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +0 -452
  230. package/dist/create/server-source/crates/feltdb/tests/saas_authorization_integration.rs +0 -81
  231. package/dist/create/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +0 -438
  232. package/dist/create/server-source/crates/feltdb/tests/semantic_decision.rs +0 -1165
  233. package/dist/create/server-source/crates/feltdb/tests/semantic_query.rs +0 -767
  234. package/dist/create/server-source/crates/feltdb/tests/semantic_query_contract.rs +0 -363
  235. package/dist/create/server-source/crates/feltdb/tests/state_conflict_contract_conformance.rs +0 -1799
  236. package/dist/create/server-source/crates/feltdb/tests/state_diff_contract_conformance.rs +0 -1316
  237. package/dist/create/server-source/crates/feltdb/tests/state_model_integration.rs +0 -358
  238. package/dist/create/server-source/crates/feltdb/tests/state_persistence_integration.rs +0 -365
  239. package/dist/create/server-source/crates/feltdb/tests/state_ref_authority.rs +0 -918
  240. package/dist/create/server-source/crates/feltdb/tests/state_store_boundary_evidence.rs +0 -299
  241. package/dist/create/server-source/crates/feltdb/tests/storage_independent_versioned_object_substrate.rs +0 -328
  242. package/dist/create/server-source/crates/feltdb/tests/sync_divergence_evidence.rs +0 -255
  243. package/dist/create/server-source/crates/feltdb/tests/three_way_input_boundary_evidence.rs +0 -249
  244. package/dist/create/server-source/crates/feltdb/tests/trigger_contract_conformance.rs +0 -994
  245. package/dist/create/server-source/crates/feltdb/tests/workload_envelope_contract.rs +0 -484
  246. package/dist/studio-app/assets/feltdb_wasm_bg-TseJn2Ax.wasm +0 -0
  247. package/dist/studio-app/assets/index-1wd0uKua.js +0 -30
@@ -1,739 +0,0 @@
1
- //! The revision model decision experiment.
2
- //!
3
- //! This file **implements nothing in the product**. It builds three tiny probes
4
- //! of what a `StateRevision` could *be*, runs the same seven scenarios against
5
- //! each, and fills in a decision matrix by execution rather than by preference.
6
- //!
7
- //! It is the executable half of `docs/architecture/revision-model-decision.md`.
8
- //!
9
- //! # The question
10
- //!
11
- //! The [identity and ancestry contract](../../../docs/architecture/revision-identity-and-ancestry.md)
12
- //! established that content-only identity cannot survive automatic minting, and
13
- //! that `base` as "the previous state of resource X" is unanswerable because a
14
- //! `StateRevision` has no resource. So the decision is not which fix to apply.
15
- //! It is:
16
- //!
17
- //! > **What is a `StateRevision` a revision *of*?**
18
- //!
19
- //! Three answers are consistent with the evidence:
20
- //!
21
- //! | model | a revision is | identity |
22
- //! | --- | --- | --- |
23
- //! | **A — State** | a durable content | content alone; ancestry lives outside the revision |
24
- //! | **B — Historical transition** | a particular occurrence in history | content + parent + a discriminator |
25
- //! | **C — Resource transition** | a transition belonging to a resource | resource + content + parent + a discriminator |
26
- //!
27
- //! # Two invariants, asserted against every model
28
- //!
29
- //! These are not model choices. They are the boundary the T5 and T6 findings
30
- //! draw, and a model that cannot hold them is not a revision model:
31
- //!
32
- //! 1. **Once committed, a revision's identity and ancestry are immutable.** A
33
- //! record whose historical parent can change after commit is not a durable
34
- //! historical object; it is a mutable record that happens to contain
35
- //! historical fields.
36
- //! 2. **A `parent_id` is either a reference to an authoritative revision, or it
37
- //! is not a parent.** Parent existence is part of what makes a revision
38
- //! valid, not a lookup applied afterwards.
39
- //!
40
- //! The probes are deliberately small and their identities are readable strings
41
- //! rather than hashes: what is under test is the *structure* of identity, not
42
- //! its encoding.
43
- //!
44
- //! Storage cost, compaction economics and copy-on-write are **deliberately
45
- //! absent**. Measuring them before the object is defined would optimise
46
- //! something that does not yet exist.
47
-
48
- use feltdb::state_model::{StateRevision, StateStore};
49
- use feltdb::FeltDb;
50
- use std::collections::BTreeMap;
51
- use std::sync::Arc;
52
- use tempfile::TempDir;
53
-
54
- type Rid = String;
55
-
56
- #[derive(Debug, PartialEq, Eq)]
57
- enum MintError {
58
- /// Invariant 2: the named parent is not an authoritative revision.
59
- UnknownParent,
60
- }
61
-
62
- /// The model has no way to express the question, as opposed to answering "no".
63
- #[derive(Debug, PartialEq, Eq)]
64
- struct Unsupported(&'static str);
65
-
66
- /// What every candidate model must be able to do.
67
- trait Model {
68
- fn name(&self) -> &'static str;
69
-
70
- /// Mint a revision of `content` for `resource`, descending from `parent`.
71
- fn mint(
72
- &mut self,
73
- resource: &str,
74
- content: &str,
75
- parent: Option<&Rid>,
76
- ) -> Result<Rid, MintError>;
77
-
78
- /// The parent of `id` as seen from `resource`.
79
- fn parent_of(&self, resource: &str, id: &Rid) -> Option<Rid>;
80
-
81
- /// The revisions of one resource, oldest first.
82
- fn history_of(&self, resource: &str) -> Result<Vec<Rid>, Unsupported>;
83
-
84
- /// Retain only the newest `keep` revisions of one resource.
85
- fn retain_last(&mut self, resource: &str, keep: usize) -> Result<usize, Unsupported>;
86
-
87
- /// Every durable record, for immutability checking.
88
- fn records(&self) -> BTreeMap<Rid, String>;
89
- }
90
-
91
- // ---------------------------------------------------------------------------
92
- // Model A — a revision is a durable state
93
- // ---------------------------------------------------------------------------
94
-
95
- /// Identity is content. Ancestry is **not a field of the revision** — it is a
96
- /// separate edge set, so the same state recurring in history is one object
97
- /// referenced twice rather than one object rewritten twice.
98
- #[derive(Default)]
99
- struct StateModel {
100
- contents: BTreeMap<Rid, String>,
101
- edges: Vec<Edge>,
102
- }
103
-
104
- struct Edge {
105
- resource: String,
106
- from: Option<Rid>,
107
- to: Rid,
108
- }
109
-
110
- impl Model for StateModel {
111
- fn name(&self) -> &'static str {
112
- "A state"
113
- }
114
-
115
- fn mint(
116
- &mut self,
117
- resource: &str,
118
- content: &str,
119
- parent: Option<&Rid>,
120
- ) -> Result<Rid, MintError> {
121
- if let Some(parent) = parent {
122
- if !self.contents.contains_key(parent) {
123
- return Err(MintError::UnknownParent);
124
- }
125
- }
126
- let id = format!("state:{content}");
127
- // Idempotent by construction: the same content is the same bytes.
128
- self.contents.insert(id.clone(), content.to_string());
129
- self.edges.push(Edge {
130
- resource: resource.to_string(),
131
- from: parent.cloned(),
132
- to: id.clone(),
133
- });
134
- Ok(id)
135
- }
136
-
137
- fn parent_of(&self, resource: &str, id: &Rid) -> Option<Rid> {
138
- // The last time this state was reached on this resource.
139
- self.edges
140
- .iter()
141
- .rev()
142
- .find(|edge| edge.resource == resource && &edge.to == id)
143
- .and_then(|edge| edge.from.clone())
144
- }
145
-
146
- fn history_of(&self, resource: &str) -> Result<Vec<Rid>, Unsupported> {
147
- Ok(self
148
- .edges
149
- .iter()
150
- .filter(|edge| edge.resource == resource)
151
- .map(|edge| edge.to.clone())
152
- .collect())
153
- }
154
-
155
- fn retain_last(&mut self, resource: &str, keep: usize) -> Result<usize, Unsupported> {
156
- let mine: Vec<usize> = self
157
- .edges
158
- .iter()
159
- .enumerate()
160
- .filter(|(_, edge)| edge.resource == resource)
161
- .map(|(index, _)| index)
162
- .collect();
163
- let drop_count = mine.len().saturating_sub(keep);
164
- let dropped: Vec<usize> = mine.into_iter().take(drop_count).collect();
165
- self.edges = std::mem::take(&mut self.edges)
166
- .into_iter()
167
- .enumerate()
168
- .filter(|(index, _)| !dropped.contains(index))
169
- .map(|(_, edge)| edge)
170
- .collect();
171
- // Contents stay: they are shared, and another resource may reference them.
172
- Ok(drop_count)
173
- }
174
-
175
- fn records(&self) -> BTreeMap<Rid, String> {
176
- self.contents.clone()
177
- }
178
- }
179
-
180
- // ---------------------------------------------------------------------------
181
- // Model B — a revision is a historical transition
182
- // ---------------------------------------------------------------------------
183
-
184
- /// Identity is content plus parent plus a monotonic discriminator, so the same
185
- /// content occurring twice is two revisions. There is **no resource**: a
186
- /// revision is an occurrence in history, and history is not divided up.
187
- #[derive(Default)]
188
- struct TransitionModel {
189
- revisions: BTreeMap<Rid, (String, Option<Rid>)>,
190
- sequence: u64,
191
- }
192
-
193
- impl Model for TransitionModel {
194
- fn name(&self) -> &'static str {
195
- "B transition"
196
- }
197
-
198
- fn mint(
199
- &mut self,
200
- _resource: &str,
201
- content: &str,
202
- parent: Option<&Rid>,
203
- ) -> Result<Rid, MintError> {
204
- if let Some(parent) = parent {
205
- if !self.revisions.contains_key(parent) {
206
- return Err(MintError::UnknownParent);
207
- }
208
- }
209
- self.sequence += 1;
210
- let id = format!(
211
- "occurrence:{}:{}:{content}",
212
- self.sequence,
213
- parent.map(String::as_str).unwrap_or("root")
214
- );
215
- self.revisions
216
- .insert(id.clone(), (content.to_string(), parent.cloned()));
217
- Ok(id)
218
- }
219
-
220
- fn parent_of(&self, _resource: &str, id: &Rid) -> Option<Rid> {
221
- self.revisions
222
- .get(id)
223
- .and_then(|(_, parent)| parent.clone())
224
- }
225
-
226
- fn history_of(&self, _resource: &str) -> Result<Vec<Rid>, Unsupported> {
227
- Err(Unsupported(
228
- "a transition belongs to no resource, so a resource has no history",
229
- ))
230
- }
231
-
232
- fn retain_last(&mut self, _resource: &str, _keep: usize) -> Result<usize, Unsupported> {
233
- Err(Unsupported(
234
- "retention cannot be scoped to a resource the model cannot name",
235
- ))
236
- }
237
-
238
- fn records(&self) -> BTreeMap<Rid, String> {
239
- self.revisions
240
- .iter()
241
- .map(|(id, (content, parent))| (id.clone(), format!("{content}|{parent:?}")))
242
- .collect()
243
- }
244
- }
245
-
246
- // ---------------------------------------------------------------------------
247
- // Model C — a revision is a transition of a resource
248
- // ---------------------------------------------------------------------------
249
-
250
- /// Identity is resource plus content plus parent plus a discriminator. A
251
- /// revision knows what it is a revision *of*.
252
- #[derive(Default)]
253
- struct ResourceTransitionModel {
254
- revisions: BTreeMap<Rid, (String, String, Option<Rid>)>,
255
- sequence: u64,
256
- }
257
-
258
- impl Model for ResourceTransitionModel {
259
- fn name(&self) -> &'static str {
260
- "C resource transition"
261
- }
262
-
263
- fn mint(
264
- &mut self,
265
- resource: &str,
266
- content: &str,
267
- parent: Option<&Rid>,
268
- ) -> Result<Rid, MintError> {
269
- if let Some(parent) = parent {
270
- if !self.revisions.contains_key(parent) {
271
- return Err(MintError::UnknownParent);
272
- }
273
- }
274
- self.sequence += 1;
275
- let id = format!(
276
- "{resource}@{}:{}:{content}",
277
- self.sequence,
278
- parent.map(String::as_str).unwrap_or("root")
279
- );
280
- self.revisions.insert(
281
- id.clone(),
282
- (resource.to_string(), content.to_string(), parent.cloned()),
283
- );
284
- Ok(id)
285
- }
286
-
287
- fn parent_of(&self, _resource: &str, id: &Rid) -> Option<Rid> {
288
- self.revisions
289
- .get(id)
290
- .and_then(|(_, _, parent)| parent.clone())
291
- }
292
-
293
- fn history_of(&self, resource: &str) -> Result<Vec<Rid>, Unsupported> {
294
- Ok(self
295
- .revisions
296
- .iter()
297
- .filter(|(_, (owner, _, _))| owner == resource)
298
- .map(|(id, _)| id.clone())
299
- .collect())
300
- }
301
-
302
- fn retain_last(&mut self, resource: &str, keep: usize) -> Result<usize, Unsupported> {
303
- let mine = self.history_of(resource)?;
304
- let drop_count = mine.len().saturating_sub(keep);
305
- for id in mine.into_iter().take(drop_count) {
306
- self.revisions.remove(&id);
307
- }
308
- Ok(drop_count)
309
- }
310
-
311
- fn records(&self) -> BTreeMap<Rid, String> {
312
- self.revisions
313
- .iter()
314
- .map(|(id, (owner, content, parent))| {
315
- (id.clone(), format!("{owner}|{content}|{parent:?}"))
316
- })
317
- .collect()
318
- }
319
- }
320
-
321
- fn models() -> Vec<Box<dyn Model>> {
322
- vec![
323
- Box::<StateModel>::default(),
324
- Box::<TransitionModel>::default(),
325
- Box::<ResourceTransitionModel>::default(),
326
- ]
327
- }
328
-
329
- const X: &str = r#"{"title":"X"}"#;
330
- const Y: &str = r#"{"title":"Y"}"#;
331
-
332
- // ---------------------------------------------------------------------------
333
- // The two invariants, asserted against every model
334
- // ---------------------------------------------------------------------------
335
-
336
- /// **Invariant 1 — a committed revision's identity and ancestry are immutable.**
337
- ///
338
- /// Run every scenario that rewrote history under today's implementation, and
339
- /// assert that no already-stored record changed. All three models hold, by
340
- /// different means: A because a revision has no ancestry to rewrite, B and C
341
- /// because the rewrite would have a different identity.
342
- #[test]
343
- fn every_model_holds_committed_revisions_immutable() {
344
- for mut model in models() {
345
- let name = model.name();
346
- let first = model.mint("tasks:1", X, None).unwrap();
347
- let second = model.mint("tasks:1", Y, Some(&first)).unwrap();
348
- let before = model.records();
349
-
350
- // The three sequences that corrupt history today.
351
- model.mint("tasks:1", X, Some(&second)).unwrap(); // revert
352
- model.mint("tasks:1", Y, None).unwrap(); // re-mint a child as a root
353
- model.mint("tasks:2", X, None).unwrap(); // same content, other resource
354
-
355
- let after = model.records();
356
- for (id, record) in &before {
357
- assert_eq!(
358
- after.get(id),
359
- Some(record),
360
- "{name}: the record for {id} changed after commit"
361
- );
362
- }
363
- }
364
- }
365
-
366
- /// **Invariant 2 — a `parent_id` references an authoritative revision, or it is
367
- /// not a parent.**
368
- ///
369
- /// Model-independent: all three can enforce it at the mint, and it costs a
370
- /// containment check. This is the clause T5 should be discharged by — a rule in
371
- /// the definition of a valid revision, not a lookup bolted on afterwards.
372
- #[test]
373
- fn every_model_rejects_a_parent_it_does_not_hold() {
374
- for mut model in models() {
375
- let name = model.name();
376
- let foreign = "state:{\"never\":\"stored\"}".to_string();
377
- assert_eq!(
378
- model.mint("tasks:1", X, Some(&foreign)),
379
- Err(MintError::UnknownParent),
380
- "{name}: accepted a parent it does not hold"
381
- );
382
- }
383
- }
384
-
385
- /// **The real store now holds both — this is what the experiment decided.**
386
- ///
387
- /// This test asserted the opposite when the experiment was run: `StateStore`
388
- /// accepted an unheld parent, and re-minting a child's content as a root erased
389
- /// the parent that child had. Model C was adopted and implemented, and the same
390
- /// two sequences are now refused at the mint.
391
- ///
392
- /// It is kept in the experiment file rather than moved because the experiment's
393
- /// claim was that both invariants are *model-independent and cheap*. This is
394
- /// that claim, checked against the real store instead of a probe.
395
- #[test]
396
- fn the_real_store_now_holds_both_invariants() {
397
- let directory = TempDir::new().unwrap();
398
- let db = Arc::new(FeltDb::open(directory.path().join("today.log")).unwrap());
399
- let store = StateStore::with_feltdb(db).unwrap();
400
-
401
- let first = store
402
- .create("tasks:1", X.into(), "decision".into())
403
- .unwrap();
404
- let second = store.commit(Y.into(), &first, "decision".into()).unwrap();
405
- assert_eq!(
406
- store.get(&second.id).unwrap().parent_id,
407
- Some(first.id.clone())
408
- );
409
-
410
- // Invariant 1: beginning the resource again is refused, and a further
411
- // commit of the same content is a new revision rather than a rewrite.
412
- assert!(
413
- store
414
- .create("tasks:1", Y.into(), "decision".into())
415
- .is_err(),
416
- "a resource has one beginning"
417
- );
418
- let third = store.commit(Y.into(), &second, "decision".into()).unwrap();
419
- assert_ne!(
420
- third.id, second.id,
421
- "the same state again is a new revision"
422
- );
423
- assert_eq!(
424
- store.get(&second.id).unwrap().parent_id,
425
- Some(first.id),
426
- "the committed revision kept the parent it was committed with"
427
- );
428
-
429
- // Invariant 2: a parent the store does not hold is refused.
430
- let unheld = StateRevision::at(
431
- "tasks:1".into(),
432
- r#"{"never":"stored"}"#.into(),
433
- None,
434
- 0,
435
- "decision".into(),
436
- );
437
- assert!(
438
- store.commit(X.into(), &unheld, "decision".into()).is_err(),
439
- "a parent must resolve to a held revision"
440
- );
441
- }
442
-
443
- // ---------------------------------------------------------------------------
444
- // The seven scenarios
445
- // ---------------------------------------------------------------------------
446
-
447
- /// Whether the two mints produce one identity or two.
448
- fn same_content_twice(model: &mut dyn Model) -> bool {
449
- let first = model.mint("tasks:1", X, None).unwrap();
450
- let second = model.mint("tasks:1", X, None).unwrap();
451
- first == second
452
- }
453
-
454
- /// Whether a revert lands back on the identity it started from.
455
- fn revert_reuses_the_original_identity(model: &mut dyn Model) -> bool {
456
- let first = model.mint("tasks:1", X, None).unwrap();
457
- let second = model.mint("tasks:1", Y, Some(&first)).unwrap();
458
- let third = model.mint("tasks:1", X, Some(&second)).unwrap();
459
- first == third
460
- }
461
-
462
- /// Whether two resources holding the same content share one identity.
463
- fn two_resources_share_an_identity(model: &mut dyn Model) -> bool {
464
- let one = model.mint("tasks:1", X, None).unwrap();
465
- let two = model.mint("tasks:2", X, None).unwrap();
466
- one == two
467
- }
468
-
469
- /// Whether re-minting a child's content as a root can disturb the child.
470
- fn re_minting_a_child_as_a_root_disturbs_it(model: &mut dyn Model) -> bool {
471
- let first = model.mint("tasks:1", X, None).unwrap();
472
- let second = model.mint("tasks:1", Y, Some(&first)).unwrap();
473
- let before = model.parent_of("tasks:1", &second);
474
- model.mint("tasks:1", Y, None).unwrap();
475
- model.parent_of("tasks:1", &second) != before
476
- }
477
-
478
- /// Whether a foreign parent is refused.
479
- fn foreign_parent_refused(model: &mut dyn Model) -> bool {
480
- model.mint("tasks:1", X, Some(&"nothing:here".to_string())) == Err(MintError::UnknownParent)
481
- }
482
-
483
- /// Whether the history of one resource can be listed.
484
- fn resource_history_addressable(model: &mut dyn Model) -> bool {
485
- model.mint("tasks:1", X, None).unwrap();
486
- model.mint("tasks:2", Y, None).unwrap();
487
- matches!(model.history_of("tasks:1"), Ok(history) if history.len() == 1)
488
- }
489
-
490
- /// Whether a retention policy can be *stated* — "keep the last k of this
491
- /// resource" — as opposed to being applied to everything at once.
492
- fn retention_expressible(model: &mut dyn Model) -> bool {
493
- let mut previous = model.mint("tasks:1", X, None).unwrap();
494
- for n in 0..4 {
495
- previous = model
496
- .mint("tasks:1", &format!(r#"{{"n":{n}}}"#), Some(&previous))
497
- .unwrap();
498
- }
499
- model.retain_last("tasks:1", 2).is_ok()
500
- }
501
-
502
- /// Whether following parents from the newest revision terminates.
503
- fn topology_terminates(model: &mut dyn Model) -> bool {
504
- let first = model.mint("tasks:1", X, None).unwrap();
505
- let second = model.mint("tasks:1", Y, Some(&first)).unwrap();
506
- let third = model.mint("tasks:1", X, Some(&second)).unwrap();
507
-
508
- let mut seen: Vec<Rid> = Vec::new();
509
- let mut current = third;
510
- for _ in 0..32 {
511
- if seen.contains(&current) {
512
- return false;
513
- }
514
- seen.push(current.clone());
515
- match model.parent_of("tasks:1", &current) {
516
- Some(parent) => current = parent,
517
- None => return true,
518
- }
519
- }
520
- false
521
- }
522
-
523
- // ---------------------------------------------------------------------------
524
- // The decision matrix
525
- // ---------------------------------------------------------------------------
526
-
527
- /// One row of the matrix: a requirement, and what each model does about it.
528
- struct Row {
529
- requirement: &'static str,
530
- /// Evaluated per model, in the order A, B, C.
531
- evaluate: fn(&mut dyn Model) -> bool,
532
- expected: [bool; 3],
533
- }
534
-
535
- /// The matrix, filled by execution.
536
- ///
537
- /// `expected` is what the probes actually do. Changing a probe without changing
538
- /// this table fails the suite.
539
- const MATRIX: &[Row] = &[
540
- Row {
541
- requirement: "the same content can recur without reusing an identity",
542
- evaluate: |m| !same_content_twice(m),
543
- expected: [false, true, true],
544
- },
545
- Row {
546
- requirement: "a revert is distinguishable from the state it returns to",
547
- evaluate: |m| !revert_reuses_the_original_identity(m),
548
- expected: [false, true, true],
549
- },
550
- Row {
551
- requirement: "two resources at the same value are distinguishable",
552
- evaluate: |m| !two_resources_share_an_identity(m),
553
- expected: [false, true, true],
554
- },
555
- Row {
556
- requirement: "a committed revision's parent is undisturbed by later mints",
557
- evaluate: |m| !re_minting_a_child_as_a_root_disturbs_it(m),
558
- // A fails this for the same reason it deduplicates: parentage is not a
559
- // property of a revision there, so "the parent of R" has no stable
560
- // answer. See `model_a_is_sound_only_when_history_is_walked_over_edges`.
561
- expected: [false, true, true],
562
- },
563
- Row {
564
- requirement: "a parent must resolve to a held revision",
565
- evaluate: foreign_parent_refused,
566
- expected: [true, true, true],
567
- },
568
- Row {
569
- requirement: "the history of one resource is addressable",
570
- evaluate: resource_history_addressable,
571
- expected: [true, false, true],
572
- },
573
- Row {
574
- requirement: "a per-resource retention policy is expressible",
575
- evaluate: retention_expressible,
576
- expected: [true, false, true],
577
- },
578
- Row {
579
- requirement: "following parents terminates",
580
- evaluate: topology_terminates,
581
- // A cycles exactly as today's implementation does, and for the same
582
- // reason: resolving a parent by *identity* collapses the two occasions
583
- // one state was reached.
584
- expected: [false, true, true],
585
- },
586
- ];
587
-
588
- #[test]
589
- fn the_decision_matrix_is_exactly_this() {
590
- let mut drifted = Vec::new();
591
- for row in MATRIX {
592
- for (index, mut model) in models().into_iter().enumerate() {
593
- let actual = (row.evaluate)(model.as_mut());
594
- if actual != row.expected[index] {
595
- drifted.push(format!(
596
- " {} / {}: expected {}, observed {}",
597
- row.requirement,
598
- model.name(),
599
- row.expected[index],
600
- actual
601
- ));
602
- }
603
- }
604
- }
605
- assert!(
606
- drifted.is_empty(),
607
- "the decision matrix has drifted from the probes:\n{}\n\n\
608
- Update docs/architecture/revision-model-decision.md and this table together.",
609
- drifted.join("\n")
610
- );
611
- }
612
-
613
- /// **What the matrix forces.**
614
- ///
615
- /// A passes 3 of 8, B passes 6 of 8, C passes 8 of 8.
616
- ///
617
- /// B fails exactly the two rows that need a resource, and no discriminator added
618
- /// to its identity can fix that: those rows are unanswerable because the model
619
- /// has no resource to name, not because its identity is too coarse. Giving B a
620
- /// resource dimension **is** model C.
621
- ///
622
- /// A fails the three identity rows by design — a recurring state is one object
623
- /// referenced twice — and then fails two more as a consequence, because once
624
- /// identity is shared, "the parent of this revision" stops having one answer.
625
- /// That is not a defect in the probe; it is what moving ancestry out of the
626
- /// revision costs, and it is only recoverable under the discipline the next
627
- /// test describes.
628
- #[test]
629
- fn the_matrix_scores_a_three_b_six_c_eight() {
630
- let mut scores = [0usize; 3];
631
- for row in MATRIX {
632
- for (index, mut model) in models().into_iter().enumerate() {
633
- if (row.evaluate)(model.as_mut()) {
634
- scores[index] += 1;
635
- }
636
- }
637
- }
638
- assert_eq!(scores, [3, 6, 8], "A, B, C");
639
- }
640
-
641
- /// **B's failures are the two rows that need a resource, and only those.**
642
- #[test]
643
- fn model_b_fails_exactly_the_resource_rows() {
644
- let failed: Vec<&str> = MATRIX
645
- .iter()
646
- .filter(|row| !row.expected[1])
647
- .map(|row| row.requirement)
648
- .collect();
649
- assert_eq!(
650
- failed,
651
- vec![
652
- "the history of one resource is addressable",
653
- "a per-resource retention policy is expressible",
654
- ]
655
- );
656
- }
657
-
658
- /// **Model A is sound — but only if the addressable unit of history is the
659
- /// edge, not the revision.**
660
- ///
661
- /// The matrix shows A cycling and losing parentage when ancestry is resolved by
662
- /// revision identity. That is the tempting reading of "ancestry lives outside
663
- /// the revision", and it does not work: two occasions of one state are one
664
- /// identity, so the question has two answers and the walk closes a loop.
665
- ///
666
- /// Walked over the *edge list* instead — where each edge is a distinct,
667
- /// ordered occurrence — A is well behaved: the walk strictly decreases in index
668
- /// and therefore terminates, and no edge is ever rewritten.
669
- ///
670
- /// So A is not disqualified. It is conditional, and the condition is a real
671
- /// constraint on every consumer: `base` in model A is an edge lookup, and
672
- /// `revision.parent` does not exist.
673
- #[test]
674
- fn model_a_is_sound_only_when_history_is_walked_over_edges() {
675
- let mut model = StateModel::default();
676
- let first = model.mint("tasks:1", X, None).unwrap();
677
- let second = model.mint("tasks:1", Y, Some(&first)).unwrap();
678
- let third = model.mint("tasks:1", X, Some(&second)).unwrap();
679
- assert_eq!(first, third, "one object, reached twice");
680
-
681
- // Walk backwards over the edges of this resource, by position.
682
- let positions: Vec<usize> = model
683
- .edges
684
- .iter()
685
- .enumerate()
686
- .filter(|(_, edge)| edge.resource == "tasks:1")
687
- .map(|(index, _)| index)
688
- .collect();
689
-
690
- let mut walked = Vec::new();
691
- let mut cursor = Some(*positions.last().unwrap());
692
- while let Some(position) = cursor {
693
- walked.push(model.edges[position].to.clone());
694
- let from = model.edges[position].from.clone();
695
- cursor = match from {
696
- None => None,
697
- Some(parent) => positions
698
- .iter()
699
- .rev()
700
- .find(|candidate| **candidate < position && model.edges[**candidate].to == parent)
701
- .copied(),
702
- };
703
- assert!(walked.len() <= 8, "the edge walk must terminate");
704
- }
705
-
706
- assert_eq!(
707
- walked,
708
- vec![third, second, first],
709
- "three occasions, in order, from one deduplicated pair of states"
710
- );
711
- }
712
-
713
- /// **Why A behaves that way**, stated where it cannot be missed.
714
- ///
715
- /// A keeps deduplication — the only thing currently bounding storage growth —
716
- /// but it pays for it by moving ancestry out of the revision. A revision can no
717
- /// longer be asked for its parent: the question only has an answer relative to a
718
- /// resource, and if a state recurs on one resource it has more than one answer.
719
- #[test]
720
- fn model_a_has_no_well_formed_parent_for_a_revision() {
721
- let mut model = StateModel::default();
722
- let first = model.mint("tasks:1", X, None).unwrap();
723
- let second = model.mint("tasks:1", Y, Some(&first)).unwrap();
724
- let third = model.mint("tasks:1", X, Some(&second)).unwrap();
725
- assert_eq!(first, third, "one object, reached twice");
726
-
727
- // The same identity has two parents on one resource, depending on when.
728
- let parents: Vec<Option<Rid>> = model
729
- .edges
730
- .iter()
731
- .filter(|edge| edge.to == first)
732
- .map(|edge| edge.from.clone())
733
- .collect();
734
- assert_eq!(
735
- parents,
736
- vec![None, Some(second)],
737
- "'the parent of this revision' is not a well-formed question in model A"
738
- );
739
- }