@feltdb/core 0.15.0 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/dist/agent-state.d.ts +20 -0
  2. package/dist/agent-state.d.ts.map +1 -1
  3. package/dist/agent-state.js +36 -0
  4. package/dist/build-identity.json +12 -4
  5. package/dist/cli/commands.js +187 -4
  6. package/dist/cli/publish-engine.js +116 -16
  7. package/dist/collection.d.ts.map +1 -1
  8. package/dist/collection.js +20 -2
  9. package/dist/create/build-identity.json +12 -4
  10. package/dist/create/managed-account.js +93 -35
  11. package/dist/create/package-versions.js +1 -1
  12. package/dist/create/server-source/Cargo.lock +6 -509
  13. package/dist/create/server-source/Cargo.toml +2 -13
  14. package/dist/create/server-source/crates/feltdb-client/Cargo.toml +1 -1
  15. package/dist/create/server-source/crates/feltdb-server/Cargo.toml +1 -1
  16. package/dist/create/server-source/crates/feltdb-server/src/main.rs +356 -24
  17. package/dist/create/server-source/crates/feltdb-server/tests/rust_client.rs +10 -1
  18. package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +1 -1
  19. package/dist/db.d.ts +11 -1
  20. package/dist/db.d.ts.map +1 -1
  21. package/dist/db.js +15 -2
  22. package/dist/error-codes.d.ts +5 -0
  23. package/dist/error-codes.d.ts.map +1 -1
  24. package/dist/error-codes.js +9 -1
  25. package/dist/http-db.d.ts +16 -0
  26. package/dist/http-db.d.ts.map +1 -1
  27. package/dist/http-db.js +36 -6
  28. package/dist/managed-recovery.d.ts +6 -0
  29. package/dist/managed-recovery.d.ts.map +1 -1
  30. package/dist/managed-recovery.js +9 -0
  31. package/dist/studio-app/assets/{feltdb_wasm-BjM1ylL3.js → feltdb_wasm-BUIxCuTR.js} +1 -1
  32. package/dist/studio-app/assets/feltdb_wasm_bg-C3D6y6DK.wasm +0 -0
  33. package/dist/studio-app/assets/index-BhXYAhgS.js +37 -0
  34. package/dist/studio-app/index.html +1 -1
  35. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  36. package/dist/wasm/package.json +1 -1
  37. package/dist/workload.d.ts +55 -3
  38. package/dist/workload.d.ts.map +1 -1
  39. package/dist/workload.js +23 -0
  40. package/dist/workspace/workspace-connection.d.ts.map +1 -1
  41. package/dist/workspace/workspace-connection.js +14 -3
  42. package/package.json +1 -1
  43. package/dist/create/server-source/crates/feltdb/Cargo.lock +0 -175
  44. package/dist/create/server-source/crates/feltdb/Cargo.toml +0 -102
  45. package/dist/create/server-source/crates/feltdb/LICENSE +0 -21
  46. package/dist/create/server-source/crates/feltdb/README.md +0 -75
  47. package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +0 -370
  48. package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +0 -589
  49. package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +0 -247
  50. package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +0 -440
  51. package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +0 -275
  52. package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +0 -381
  53. package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +0 -467
  54. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +0 -489
  55. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +0 -281
  56. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +0 -606
  57. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +0 -531
  58. package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +0 -462
  59. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +0 -505
  60. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +0 -484
  61. package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +0 -758
  62. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +0 -421
  63. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +0 -329
  64. package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +0 -1705
  65. package/dist/create/server-source/crates/feltdb/src/acquisition.rs +0 -286
  66. package/dist/create/server-source/crates/feltdb/src/admission.rs +0 -185
  67. package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +0 -507
  68. package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +0 -539
  69. package/dist/create/server-source/crates/feltdb/src/agent_state.rs +0 -320
  70. package/dist/create/server-source/crates/feltdb/src/agent_transition_contract.rs +0 -1545
  71. package/dist/create/server-source/crates/feltdb/src/analytics.rs +0 -521
  72. package/dist/create/server-source/crates/feltdb/src/application.rs +0 -2674
  73. package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +0 -1070
  74. package/dist/create/server-source/crates/feltdb/src/authority.rs +0 -479
  75. package/dist/create/server-source/crates/feltdb/src/authority_failover.rs +0 -847
  76. package/dist/create/server-source/crates/feltdb/src/authorization.rs +0 -1030
  77. package/dist/create/server-source/crates/feltdb/src/authorization_security_tests.rs +0 -1122
  78. package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority-client.rs +0 -32
  79. package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority.rs +0 -19
  80. package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +0 -1474
  81. package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +0 -8
  82. package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +0 -418
  83. package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +0 -482
  84. package/dist/create/server-source/crates/feltdb/src/capability.rs +0 -903
  85. package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +0 -263
  86. package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +0 -77
  87. package/dist/create/server-source/crates/feltdb/src/causal_backlog_bound.rs +0 -496
  88. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +0 -2574
  89. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +0 -212
  90. package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +0 -422
  91. package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +0 -445
  92. package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +0 -465
  93. package/dist/create/server-source/crates/feltdb/src/convergence.rs +0 -634
  94. package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +0 -427
  95. package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +0 -376
  96. package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +0 -365
  97. package/dist/create/server-source/crates/feltdb/src/cron.rs +0 -294
  98. package/dist/create/server-source/crates/feltdb/src/dedup_bound_investigation.rs +0 -483
  99. package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +0 -477
  100. package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +0 -1003
  101. package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +0 -1334
  102. package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +0 -368
  103. package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +0 -231
  104. package/dist/create/server-source/crates/feltdb/src/durable_operation_identity.rs +0 -482
  105. package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +0 -203
  106. package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +0 -317
  107. package/dist/create/server-source/crates/feltdb/src/equality_index.rs +0 -595
  108. package/dist/create/server-source/crates/feltdb/src/execution.rs +0 -426
  109. package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +0 -214
  110. package/dist/create/server-source/crates/feltdb/src/indexing.rs +0 -776
  111. package/dist/create/server-source/crates/feltdb/src/lib.rs +0 -7365
  112. package/dist/create/server-source/crates/feltdb/src/managed_cas_tests.rs +0 -234
  113. package/dist/create/server-source/crates/feltdb/src/materialization.rs +0 -184
  114. package/dist/create/server-source/crates/feltdb/src/metrics.rs +0 -266
  115. package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +0 -310
  116. package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +0 -736
  117. package/dist/create/server-source/crates/feltdb/src/observability.rs +0 -379
  118. package/dist/create/server-source/crates/feltdb/src/operation.rs +0 -290
  119. package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +0 -439
  120. package/dist/create/server-source/crates/feltdb/src/operation_log.rs +0 -349
  121. package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +0 -332
  122. package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +0 -718
  123. package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +0 -487
  124. package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +0 -166
  125. package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +0 -289
  126. package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +0 -566
  127. package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +0 -3391
  128. package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +0 -203
  129. package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +0 -261
  130. package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +0 -502
  131. package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +0 -216
  132. package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +0 -303
  133. package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +0 -323
  134. package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +0 -531
  135. package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +0 -406
  136. package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +0 -436
  137. package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +0 -387
  138. package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +0 -378
  139. package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +0 -359
  140. package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +0 -372
  141. package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +0 -404
  142. package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +0 -376
  143. package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +0 -461
  144. package/dist/create/server-source/crates/feltdb/src/policy_evaluation.rs +0 -2125
  145. package/dist/create/server-source/crates/feltdb/src/production_api.rs +0 -418
  146. package/dist/create/server-source/crates/feltdb/src/provenance.rs +0 -207
  147. package/dist/create/server-source/crates/feltdb/src/query_execution_diagnostics.rs +0 -126
  148. package/dist/create/server-source/crates/feltdb/src/query_performance.rs +0 -218
  149. package/dist/create/server-source/crates/feltdb/src/read_basis.rs +0 -257
  150. package/dist/create/server-source/crates/feltdb/src/references.rs +0 -404
  151. package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +0 -401
  152. package/dist/create/server-source/crates/feltdb/src/replica_acknowledgements.rs +0 -501
  153. package/dist/create/server-source/crates/feltdb/src/replica_membership.rs +0 -680
  154. package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +0 -163
  155. package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +0 -133
  156. package/dist/create/server-source/crates/feltdb/src/routing.rs +0 -409
  157. package/dist/create/server-source/crates/feltdb/src/semantic_decision.rs +0 -2029
  158. package/dist/create/server-source/crates/feltdb/src/semantic_query.rs +0 -599
  159. package/dist/create/server-source/crates/feltdb/src/sharding.rs +0 -549
  160. package/dist/create/server-source/crates/feltdb/src/state_conflict_contract.rs +0 -516
  161. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +0 -6003
  162. package/dist/create/server-source/crates/feltdb/src/state_diff_contract.rs +0 -222
  163. package/dist/create/server-source/crates/feltdb/src/state_facade.rs +0 -320
  164. package/dist/create/server-source/crates/feltdb/src/state_hash.rs +0 -291
  165. package/dist/create/server-source/crates/feltdb/src/state_model.rs +0 -4158
  166. package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +0 -379
  167. package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +0 -673
  168. package/dist/create/server-source/crates/feltdb/src/storage.rs +0 -273
  169. package/dist/create/server-source/crates/feltdb/src/submission.rs +0 -471
  170. package/dist/create/server-source/crates/feltdb/src/sync.rs +0 -495
  171. package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +0 -822
  172. package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +0 -433
  173. package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +0 -462
  174. package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +0 -965
  175. package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +0 -1120
  176. package/dist/create/server-source/crates/feltdb/src/transactions.rs +0 -643
  177. package/dist/create/server-source/crates/feltdb/src/trigger.rs +0 -390
  178. package/dist/create/server-source/crates/feltdb/src/trigger_contract.rs +0 -749
  179. package/dist/create/server-source/crates/feltdb/src/versioned_object_substrate.rs +0 -708
  180. package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +0 -1269
  181. package/dist/create/server-source/crates/feltdb/src/workflow.rs +0 -713
  182. package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +0 -510
  183. package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +0 -354
  184. package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +0 -594
  185. package/dist/create/server-source/crates/feltdb/src/workload.rs +0 -4222
  186. package/dist/create/server-source/crates/feltdb/src/workload_diagnostics.rs +0 -456
  187. package/dist/create/server-source/crates/feltdb/tests/agent_state_native.rs +0 -1188
  188. package/dist/create/server-source/crates/feltdb/tests/agent_transition_contract_conformance.rs +0 -2884
  189. package/dist/create/server-source/crates/feltdb/tests/authority_process.rs +0 -297
  190. package/dist/create/server-source/crates/feltdb/tests/authority_transaction_identity.rs +0 -534
  191. package/dist/create/server-source/crates/feltdb/tests/bounded_read_contract.rs +0 -132
  192. package/dist/create/server-source/crates/feltdb/tests/branching_evidence.rs +0 -299
  193. package/dist/create/server-source/crates/feltdb/tests/common/mod.rs +0 -96
  194. package/dist/create/server-source/crates/feltdb/tests/compaction_stall_contract.rs +0 -272
  195. package/dist/create/server-source/crates/feltdb/tests/crash_durability_contract.rs +0 -467
  196. package/dist/create/server-source/crates/feltdb/tests/current_revision_authority_evidence.rs +0 -309
  197. package/dist/create/server-source/crates/feltdb/tests/durable_backup_contract.rs +0 -445
  198. package/dist/create/server-source/crates/feltdb/tests/durable_corruption_contract.rs +0 -518
  199. package/dist/create/server-source/crates/feltdb/tests/durable_format_compatibility.rs +0 -392
  200. package/dist/create/server-source/crates/feltdb/tests/feltdb_state_boundary_tests.rs +0 -850
  201. package/dist/create/server-source/crates/feltdb/tests/fixtures/agent_transition_corpus.json +0 -23340
  202. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_conflict_contract_corpus.json +0 -1916
  203. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_diff_contract_corpus.json +0 -1878
  204. package/dist/create/server-source/crates/feltdb/tests/fixtures/trigger_contract_corpus.json +0 -1862
  205. package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_evidence.rs +0 -123
  206. package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_substrate_completion.rs +0 -299
  207. package/dist/create/server-source/crates/feltdb/tests/local_process_ownership.rs +0 -69
  208. package/dist/create/server-source/crates/feltdb/tests/managed_incident_regression.rs +0 -191
  209. package/dist/create/server-source/crates/feltdb/tests/operational_health_contract.rs +0 -278
  210. package/dist/create/server-source/crates/feltdb/tests/pr34_query_collection.rs +0 -234
  211. package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +0 -947
  212. package/dist/create/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +0 -404
  213. package/dist/create/server-source/crates/feltdb/tests/pr8_vocabulary_assessment.rs +0 -916
  214. package/dist/create/server-source/crates/feltdb/tests/pr9_phase2_boundary_tests.rs +0 -1045
  215. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3a_path_a_tests.rs +0 -347
  216. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_authorized_mutations.rs +0 -361
  217. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_role_based_authorization.rs +0 -330
  218. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_simple_auth_delete.rs +0 -93
  219. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_team_delete_role_authorization.rs +0 -622
  220. package/dist/create/server-source/crates/feltdb/tests/pr9_teams_role_based_access.rs +0 -517
  221. package/dist/create/server-source/crates/feltdb/tests/production_certification.rs +0 -1153
  222. package/dist/create/server-source/crates/feltdb/tests/production_contract.rs +0 -771
  223. package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +0 -1383
  224. package/dist/create/server-source/crates/feltdb/tests/reconciliation_application.rs +0 -868
  225. package/dist/create/server-source/crates/feltdb/tests/reconciliation_wire_format_evidence.rs +0 -221
  226. package/dist/create/server-source/crates/feltdb/tests/replicated_history_contract.rs +0 -417
  227. package/dist/create/server-source/crates/feltdb/tests/resource_scoped_revisions.rs +0 -338
  228. package/dist/create/server-source/crates/feltdb/tests/revision_identity_contract.rs +0 -1039
  229. package/dist/create/server-source/crates/feltdb/tests/revision_model_decision.rs +0 -739
  230. package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +0 -452
  231. package/dist/create/server-source/crates/feltdb/tests/saas_authorization_integration.rs +0 -81
  232. package/dist/create/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +0 -438
  233. package/dist/create/server-source/crates/feltdb/tests/semantic_decision.rs +0 -1165
  234. package/dist/create/server-source/crates/feltdb/tests/semantic_query.rs +0 -767
  235. package/dist/create/server-source/crates/feltdb/tests/semantic_query_contract.rs +0 -363
  236. package/dist/create/server-source/crates/feltdb/tests/state_conflict_contract_conformance.rs +0 -1799
  237. package/dist/create/server-source/crates/feltdb/tests/state_diff_contract_conformance.rs +0 -1316
  238. package/dist/create/server-source/crates/feltdb/tests/state_model_integration.rs +0 -358
  239. package/dist/create/server-source/crates/feltdb/tests/state_persistence_integration.rs +0 -365
  240. package/dist/create/server-source/crates/feltdb/tests/state_ref_authority.rs +0 -918
  241. package/dist/create/server-source/crates/feltdb/tests/state_store_boundary_evidence.rs +0 -299
  242. package/dist/create/server-source/crates/feltdb/tests/storage_independent_versioned_object_substrate.rs +0 -328
  243. package/dist/create/server-source/crates/feltdb/tests/sync_divergence_evidence.rs +0 -255
  244. package/dist/create/server-source/crates/feltdb/tests/three_way_input_boundary_evidence.rs +0 -249
  245. package/dist/create/server-source/crates/feltdb/tests/trigger_contract_conformance.rs +0 -994
  246. package/dist/create/server-source/crates/feltdb/tests/workload_envelope_contract.rs +0 -484
  247. package/dist/studio-app/assets/feltdb_wasm_bg-TseJn2Ax.wasm +0 -0
  248. package/dist/studio-app/assets/index-1wd0uKua.js +0 -30
@@ -1,767 +0,0 @@
1
- //! Semantic query composition — conformance to `docs/reference/semantic-query.md`.
2
- //!
3
- //! Every test here exercises `compose_semantic_page` over a candidate page the
4
- //! deterministic stage would have produced, with a runtime that counts what
5
- //! reaches a provider. The section numbers name the contract clause each test
6
- //! pins.
7
-
8
- use feltdb::semantic_decision::{
9
- DecisionDefinition, DecisionError, DecisionRequest, DecisionResult, DecisionRuntime,
10
- DecisionRuntimeCapabilities, DecisionRuntimeMetadata, DecisionStatus,
11
- DecisionTransportAuthorization, DEFAULT_DECISION_COLLECTION,
12
- };
13
- use feltdb::semantic_query::{
14
- compose_semantic_page, semantic_clause_hash, validate_semantic_clause, SemanticCandidate,
15
- SemanticOrderDirection, SemanticQueryClause, SemanticQueryFilter, SemanticQueryMode,
16
- SemanticQueryOrder, SemanticUnevaluatedReason,
17
- };
18
- use feltdb::FeltDb;
19
- use serde_json::{json, Value};
20
- use std::sync::atomic::{AtomicUsize, Ordering};
21
- use std::sync::{Arc, Mutex};
22
-
23
- fn memory_db(name: &str) -> FeltDb {
24
- let path = std::env::temp_dir().join(format!(
25
- "feltdb-semantic-query-{name}-{}.log",
26
- std::time::SystemTime::now()
27
- .duration_since(std::time::UNIX_EPOCH)
28
- .expect("time")
29
- .as_nanos()
30
- ));
31
- FeltDb::open(path).expect("db")
32
- }
33
-
34
- /// Answers from the record's own fields and counts every provider call.
35
- #[derive(Clone)]
36
- struct CountingRuntime {
37
- invocations: Arc<AtomicUsize>,
38
- seen: Arc<Mutex<Vec<String>>>,
39
- fail: bool,
40
- malformed: bool,
41
- capabilities: DecisionRuntimeCapabilities,
42
- }
43
-
44
- impl CountingRuntime {
45
- fn new() -> Self {
46
- Self {
47
- invocations: Arc::new(AtomicUsize::new(0)),
48
- seen: Arc::new(Mutex::new(Vec::new())),
49
- fail: false,
50
- malformed: false,
51
- capabilities: DecisionRuntimeCapabilities::default(),
52
- }
53
- }
54
-
55
- fn invocations(&self) -> usize {
56
- self.invocations.load(Ordering::SeqCst)
57
- }
58
-
59
- fn seen_contexts(&self) -> Vec<String> {
60
- self.seen.lock().expect("seen").clone()
61
- }
62
- }
63
-
64
- impl DecisionRuntime for CountingRuntime {
65
- fn metadata(&self) -> DecisionRuntimeMetadata {
66
- DecisionRuntimeMetadata {
67
- runtime: "deterministic".into(),
68
- model: Some("deterministic-runtime".into()),
69
- model_revision: Some("deterministic-v1".into()),
70
- prompt_revision: Some("prompt-v1".into()),
71
- execution_method: Some(feltdb::DecisionExecutionMethod::Logit),
72
- decision_schema_revision: Some("semantic-decision-v1".into()),
73
- }
74
- }
75
-
76
- fn capabilities(&self) -> DecisionRuntimeCapabilities {
77
- self.capabilities.clone()
78
- }
79
-
80
- fn decide(&self, request: &DecisionRequest) -> Result<DecisionResult, DecisionError> {
81
- self.invocations.fetch_add(1, Ordering::SeqCst);
82
- self.seen
83
- .lock()
84
- .expect("seen")
85
- .push(request.context().to_string());
86
- if self.fail {
87
- return Err(DecisionError::new("PROVIDER_DOWN", "connection refused"));
88
- }
89
- let context = request.context();
90
- Ok(match request {
91
- DecisionRequest::Binary { .. } => DecisionResult::Binary {
92
- decision: context.get("employmentType").and_then(Value::as_str) == Some("remote"),
93
- option_mass: 0.9,
94
- supporting_fields: vec!["employmentType".into()],
95
- },
96
- DecisionRequest::Choice { options, .. } => {
97
- let selected = context
98
- .get("actionHint")
99
- .and_then(Value::as_str)
100
- .unwrap_or("review")
101
- .to_string();
102
- DecisionResult::Choice {
103
- selected: if self.malformed {
104
- "not-an-option".into()
105
- } else {
106
- selected.clone()
107
- },
108
- options: options
109
- .iter()
110
- .map(|option| feltdb::WeightedOption {
111
- value: option.clone(),
112
- probability: if *option == selected { 0.9 } else { 0.05 },
113
- })
114
- .collect(),
115
- option_mass: 0.9,
116
- supporting_fields: vec!["actionHint".into()],
117
- }
118
- }
119
- DecisionRequest::Score { levels, .. } => {
120
- let selected = context
121
- .get("urgency")
122
- .and_then(Value::as_str)
123
- .unwrap_or("low")
124
- .to_string();
125
- DecisionResult::Score {
126
- selected: selected.clone(),
127
- levels: levels
128
- .iter()
129
- .map(|level| feltdb::WeightedOption {
130
- value: level.clone(),
131
- probability: if *level == selected { 0.9 } else { 0.05 },
132
- })
133
- .collect(),
134
- option_mass: 0.9,
135
- supporting_fields: vec!["urgency".into()],
136
- }
137
- }
138
- })
139
- }
140
- }
141
-
142
- fn urgency() -> DecisionDefinition {
143
- DecisionDefinition::Score {
144
- question: "What is the urgency?".into(),
145
- levels: vec!["low".into(), "medium".into(), "high".into()],
146
- }
147
- }
148
-
149
- fn remote() -> DecisionDefinition {
150
- DecisionDefinition::Binary {
151
- predicate: "could work from home".into(),
152
- }
153
- }
154
-
155
- fn action() -> DecisionDefinition {
156
- DecisionDefinition::Choice {
157
- question: "Which action should be taken?".into(),
158
- options: vec!["approve".into(), "reject".into(), "review".into()],
159
- }
160
- }
161
-
162
- fn clause(decision: DecisionDefinition) -> SemanticQueryClause {
163
- SemanticQueryClause {
164
- decision,
165
- mode: SemanticQueryMode::Evaluate,
166
- filter: None,
167
- order_by: None,
168
- fields: None,
169
- schema_version: None,
170
- authorization: None,
171
- }
172
- }
173
-
174
- /// Seeds `count` tickets and returns them as the deterministic page, in
175
- /// ascending `seq` order with the record id as tie-breaker.
176
- fn seed_page(db: &FeltDb, count: usize) -> Vec<SemanticCandidate> {
177
- (0..count)
178
- .map(|index| {
179
- let urgency = ["low", "medium", "high"][index % 3];
180
- let employment = if index % 2 == 0 { "remote" } else { "office" };
181
- let hint = ["approve", "reject", "review"][index % 3];
182
- let record = json!({
183
- "id": format!("t-{index}"),
184
- "__version": 1,
185
- "seq": index,
186
- "urgency": urgency,
187
- "employmentType": employment,
188
- "actionHint": hint,
189
- });
190
- db.insert(&format!("tickets:t-{index}"), record.clone())
191
- .expect("insert");
192
- SemanticCandidate {
193
- record_id: format!("t-{index}"),
194
- record,
195
- }
196
- })
197
- .collect()
198
- }
199
-
200
- fn ids(records: &[Value]) -> Vec<String> {
201
- records
202
- .iter()
203
- .map(|record| record["id"].as_str().unwrap().to_string())
204
- .collect()
205
- }
206
-
207
- /// §3 / §13 — the stage evaluates exactly the page it is given: one provider
208
- /// call per candidate, none for the records outside the page, and a second
209
- /// request over the same page reuses every judgment.
210
- #[test]
211
- fn evaluates_exactly_the_candidate_page_and_reuses_judgments() {
212
- let db = memory_db("page");
213
- let all = seed_page(&db, 9);
214
- let page = all[..4].to_vec();
215
- let runtime = CountingRuntime::new();
216
-
217
- let first = compose_semantic_page(
218
- &db,
219
- "tickets",
220
- page.clone(),
221
- &clause(urgency()),
222
- Some(&runtime),
223
- None,
224
- )
225
- .expect("page");
226
- assert_eq!(runtime.invocations(), 4);
227
- assert_eq!(ids(&first.records), ["t-0", "t-1", "t-2", "t-3"]);
228
- assert!(first.complete);
229
- assert_eq!(first.stage.candidates, 4);
230
- assert_eq!(first.stage.evaluated, 4);
231
- assert_eq!(first.stage.reused, 0);
232
- assert!(first
233
- .annotations
234
- .iter()
235
- .all(|annotation| annotation.as_ref().is_some_and(|a| !a.cached)));
236
-
237
- let second = compose_semantic_page(
238
- &db,
239
- "tickets",
240
- page,
241
- &clause(urgency()),
242
- Some(&runtime),
243
- None,
244
- )
245
- .expect("page");
246
- assert_eq!(
247
- runtime.invocations(),
248
- 4,
249
- "no provider call for a reusable judgment"
250
- );
251
- assert_eq!(second.stage.reused, 4);
252
- assert_eq!(second.stage.evaluated, 0);
253
- assert!(second.annotations.iter().all(|annotation| annotation
254
- .as_ref()
255
- .is_some_and(|a| a.cached && a.provenance.source == "cache")));
256
-
257
- let judgments: Vec<feltdb::DecisionJudgmentRecord> = db
258
- .capability(DEFAULT_DECISION_COLLECTION)
259
- .find(|_: &feltdb::DecisionJudgmentRecord| true)
260
- .expect("judgments");
261
- assert_eq!(judgments.len(), 4, "judgments exist only for page records");
262
- assert!(judgments
263
- .iter()
264
- .all(|judgment| judgment.status == DecisionStatus::Active));
265
- }
266
-
267
- /// §7 — binary and choice filters keep the deterministic order of the page
268
- /// and never refill it: a page whose filter removes candidates is shorter.
269
- #[test]
270
- fn binary_and_choice_filters_reduce_the_page_in_deterministic_order() {
271
- let db = memory_db("filter");
272
- let page = seed_page(&db, 6);
273
- let runtime = CountingRuntime::new();
274
-
275
- let mut remote_only = clause(remote());
276
- remote_only.filter = Some(SemanticQueryFilter {
277
- is: Some(true),
278
- ..Default::default()
279
- });
280
- let filtered = compose_semantic_page(
281
- &db,
282
- "tickets",
283
- page.clone(),
284
- &remote_only,
285
- Some(&runtime),
286
- None,
287
- )
288
- .expect("page");
289
- assert_eq!(ids(&filtered.records), ["t-0", "t-2", "t-4"]);
290
- assert_eq!(filtered.stage.filtered, 3);
291
- assert_eq!(filtered.stage.candidates, 6);
292
- assert_eq!(
293
- runtime.invocations(),
294
- 6,
295
- "every candidate was evaluated once"
296
- );
297
-
298
- let mut reviews = clause(action());
299
- reviews.filter = Some(SemanticQueryFilter {
300
- within: Some(vec!["review".into(), "reject".into()]),
301
- ..Default::default()
302
- });
303
- let filtered =
304
- compose_semantic_page(&db, "tickets", page, &reviews, Some(&runtime), None).expect("page");
305
- assert_eq!(ids(&filtered.records), ["t-1", "t-2", "t-4", "t-5"]);
306
- assert_eq!(filtered.annotations.len(), filtered.records.len());
307
- }
308
-
309
- /// §8 — score ordering is by declared-level ordinal within the page, with the
310
- /// deterministic position as tie-breaker; `desc` reverses the ordinal only.
311
- #[test]
312
- fn score_orders_within_the_page_by_level_ordinal_with_deterministic_ties() {
313
- let db = memory_db("order");
314
- let page = seed_page(&db, 7); // low, medium, high, low, medium, high, low
315
- let runtime = CountingRuntime::new();
316
-
317
- let mut ranked = clause(urgency());
318
- ranked.order_by = Some(SemanticQueryOrder {
319
- direction: SemanticOrderDirection::Desc,
320
- });
321
- let composed =
322
- compose_semantic_page(&db, "tickets", page.clone(), &ranked, Some(&runtime), None)
323
- .expect("page");
324
- assert_eq!(
325
- ids(&composed.records),
326
- ["t-2", "t-5", "t-1", "t-4", "t-0", "t-3", "t-6"],
327
- "ordinal descending, then the deterministic page order"
328
- );
329
- let selected: Vec<_> = composed
330
- .annotations
331
- .iter()
332
- .map(|annotation| match &annotation.as_ref().unwrap().result {
333
- DecisionResult::Score { selected, .. } => selected.clone(),
334
- other => panic!("unexpected {other:?}"),
335
- })
336
- .collect();
337
- assert_eq!(
338
- selected,
339
- ["high", "high", "medium", "medium", "low", "low", "low"]
340
- );
341
-
342
- let mut at_least_medium = ranked.clone();
343
- at_least_medium.order_by = Some(SemanticQueryOrder {
344
- direction: SemanticOrderDirection::Asc,
345
- });
346
- at_least_medium.filter = Some(SemanticQueryFilter {
347
- at_least: Some("medium".into()),
348
- at_most: Some("high".into()),
349
- ..Default::default()
350
- });
351
- let composed =
352
- compose_semantic_page(&db, "tickets", page, &at_least_medium, Some(&runtime), None)
353
- .expect("page");
354
- assert_eq!(ids(&composed.records), ["t-1", "t-4", "t-2", "t-5"]);
355
- assert_eq!(composed.stage.filtered, 3);
356
- }
357
-
358
- /// §5 / §8.3 / §11.3 — `reuse-only` never reaches the provider; candidates
359
- /// without a judgment are reported, sort last, and mark the page incomplete.
360
- #[test]
361
- fn reuse_only_never_reaches_the_provider_and_reports_what_it_could_not_answer() {
362
- let db = memory_db("reuse-only");
363
- let page = seed_page(&db, 5);
364
- let runtime = CountingRuntime::new();
365
-
366
- // Judge two of the five ahead of time, through the ordinary primitive.
367
- for id in ["t-2", "t-4"] {
368
- feltdb::decide_record(
369
- &db,
370
- &feltdb::DecisionRecordRef {
371
- collection: "tickets".into(),
372
- record_id: id.into(),
373
- },
374
- &urgency(),
375
- &runtime,
376
- &feltdb::DecisionOptions::default(),
377
- None,
378
- )
379
- .expect("judgment");
380
- }
381
- assert_eq!(runtime.invocations(), 2);
382
-
383
- let mut reuse = clause(urgency());
384
- reuse.mode = SemanticQueryMode::ReuseOnly;
385
- reuse.order_by = Some(SemanticQueryOrder {
386
- direction: SemanticOrderDirection::Desc,
387
- });
388
- let composed =
389
- compose_semantic_page(&db, "tickets", page.clone(), &reuse, Some(&runtime), None)
390
- .expect("page");
391
- assert_eq!(runtime.invocations(), 2, "reuse-only made no provider call");
392
- assert!(!composed.complete);
393
- assert_eq!(
394
- ids(&composed.records),
395
- ["t-2", "t-4", "t-0", "t-1", "t-3"],
396
- "evaluated first by ordinal, then unevaluated in deterministic order"
397
- );
398
- assert!(composed.annotations[0].is_some() && composed.annotations[1].is_some());
399
- assert!(composed.annotations[2..].iter().all(Option::is_none));
400
- assert_eq!(composed.stage.reused, 2);
401
- assert_eq!(composed.stage.unevaluated, 3);
402
- assert_eq!(
403
- composed
404
- .unevaluated
405
- .iter()
406
- .map(|entry| (entry.record_id.as_str(), entry.reason))
407
- .collect::<Vec<_>>(),
408
- [
409
- ("t-0", SemanticUnevaluatedReason::NoJudgment),
410
- ("t-1", SemanticUnevaluatedReason::NoJudgment),
411
- ("t-3", SemanticUnevaluatedReason::NoJudgment),
412
- ]
413
- );
414
-
415
- // With a filter, an unevaluated candidate cannot match and is not guessed.
416
- reuse.filter = Some(SemanticQueryFilter {
417
- at_least: Some("low".into()),
418
- ..Default::default()
419
- });
420
- let composed =
421
- compose_semantic_page(&db, "tickets", page, &reuse, Some(&runtime), None).expect("page");
422
- assert_eq!(ids(&composed.records), ["t-2", "t-4"]);
423
- assert_eq!(composed.stage.filtered, 3);
424
- assert!(!composed.complete);
425
- assert_eq!(runtime.invocations(), 2);
426
- }
427
-
428
- /// §4 — the provider sees only the authorized packet; a candidate whose
429
- /// authorized context is empty is unevaluated, not evaluated over nothing,
430
- /// and the returned record keeps every field the deterministic stage gave it.
431
- #[test]
432
- fn authorization_narrows_the_provider_context_and_never_the_record() {
433
- let db = memory_db("authorization");
434
- let page = seed_page(&db, 2);
435
- let runtime = CountingRuntime::new();
436
-
437
- let mut narrowed = clause(urgency());
438
- narrowed.authorization = Some(DecisionTransportAuthorization {
439
- allowed_fields: vec!["urgency".into()],
440
- principal: None,
441
- });
442
- let principal = feltdb::DecisionPrincipal {
443
- subject: feltdb::authorization::Subject::Service("query-principal".into()),
444
- tenant_id: "tenant".into(),
445
- application_id: "app".into(),
446
- capability: "state:read".into(),
447
- };
448
- let composed = compose_semantic_page(
449
- &db,
450
- "tickets",
451
- page.clone(),
452
- &narrowed,
453
- Some(&runtime),
454
- Some(principal.clone()),
455
- )
456
- .expect("page");
457
- assert!(composed.complete);
458
- for context in runtime.seen_contexts() {
459
- let context: Value = serde_json::from_str(&context).unwrap();
460
- assert_eq!(
461
- context.as_object().unwrap().keys().collect::<Vec<_>>(),
462
- ["urgency"],
463
- "only the authorized field reached the provider"
464
- );
465
- }
466
- assert!(
467
- composed.records[0].get("employmentType").is_some(),
468
- "the record itself is untouched"
469
- );
470
- let judgments: Vec<feltdb::DecisionJudgmentRecord> = db
471
- .capability(DEFAULT_DECISION_COLLECTION)
472
- .find(|_: &feltdb::DecisionJudgmentRecord| true)
473
- .expect("judgments");
474
- assert!(judgments.iter().all(|judgment| judgment
475
- .state
476
- .withheld_fields
477
- .contains(&"employmentType".to_string())));
478
- assert!(judgments
479
- .iter()
480
- .all(|judgment| judgment.state.reference.starts_with("flow://app/tickets/")));
481
-
482
- let mut nothing = narrowed.clone();
483
- nothing.authorization = Some(DecisionTransportAuthorization {
484
- allowed_fields: vec![],
485
- principal: None,
486
- });
487
- let before = runtime.invocations();
488
- let composed = compose_semantic_page(
489
- &db,
490
- "tickets",
491
- page,
492
- &nothing,
493
- Some(&runtime),
494
- Some(principal),
495
- )
496
- .expect("page");
497
- assert_eq!(
498
- runtime.invocations(),
499
- before,
500
- "an empty authorized context never reaches the provider"
501
- );
502
- assert!(!composed.complete);
503
- assert_eq!(
504
- composed.records.len(),
505
- 2,
506
- "unevaluated candidates stay in an unfiltered page"
507
- );
508
- assert!(composed.annotations.iter().all(Option::is_none));
509
- assert!(composed
510
- .unevaluated
511
- .iter()
512
- .all(|entry| entry.reason == SemanticUnevaluatedReason::Unauthorized));
513
- }
514
-
515
- /// §11 — the semantic stage fails as a unit. A provider failure is
516
- /// `SEMANTIC_PROVIDER_UNAVAILABLE`, a malformed provider result is
517
- /// `SEMANTIC_INVALID_RESULT`, a missing binding is unavailable in either
518
- /// mode, and an unsupported kind is refused before any candidate is
519
- /// evaluated. In none of these cases is the deterministic page returned.
520
- #[test]
521
- fn failures_never_degrade_into_the_deterministic_page() {
522
- let db = memory_db("failure");
523
- let page = seed_page(&db, 3);
524
-
525
- let down = CountingRuntime {
526
- fail: true,
527
- ..CountingRuntime::new()
528
- };
529
- let error = compose_semantic_page(
530
- &db,
531
- "tickets",
532
- page.clone(),
533
- &clause(urgency()),
534
- Some(&down),
535
- None,
536
- )
537
- .expect_err("provider down");
538
- assert_eq!(error.code, "SEMANTIC_PROVIDER_UNAVAILABLE");
539
- assert!(error.message.contains("connection refused"));
540
-
541
- let malformed = CountingRuntime {
542
- malformed: true,
543
- ..CountingRuntime::new()
544
- };
545
- let error = compose_semantic_page(
546
- &db,
547
- "tickets",
548
- page.clone(),
549
- &clause(action()),
550
- Some(&malformed),
551
- None,
552
- )
553
- .expect_err("malformed result");
554
- assert_eq!(error.code, "SEMANTIC_INVALID_RESULT");
555
-
556
- let error = compose_semantic_page(&db, "tickets", page.clone(), &clause(urgency()), None, None)
557
- .expect_err("no binding");
558
- assert_eq!(error.code, "SEMANTIC_PROVIDER_UNAVAILABLE");
559
- let mut reuse = clause(urgency());
560
- reuse.mode = SemanticQueryMode::ReuseOnly;
561
- let error = compose_semantic_page(&db, "tickets", page.clone(), &reuse, None, None)
562
- .expect_err("no binding, reuse-only");
563
- assert_eq!(error.code, "SEMANTIC_PROVIDER_UNAVAILABLE");
564
-
565
- let no_score = CountingRuntime {
566
- capabilities: DecisionRuntimeCapabilities {
567
- score: false,
568
- ..DecisionRuntimeCapabilities::default()
569
- },
570
- ..CountingRuntime::new()
571
- };
572
- let error = compose_semantic_page(
573
- &db,
574
- "tickets",
575
- page,
576
- &clause(urgency()),
577
- Some(&no_score),
578
- None,
579
- )
580
- .expect_err("unsupported kind");
581
- assert_eq!(error.code, "SEMANTIC_UNSUPPORTED");
582
- assert_eq!(no_score.invocations(), 0);
583
- }
584
-
585
- /// §7 / §11.5 — an invalid clause is `INVALID_QUERY` before anything runs:
586
- /// undeclared options or levels, an operator for the wrong kind, ordering on
587
- /// a non-score decision, an empty filter, and unknown keys.
588
- #[test]
589
- fn invalid_clauses_are_refused_before_evaluation() {
590
- let invalid = |clause: &SemanticQueryClause, expect: &str| {
591
- let error = validate_semantic_clause(clause).expect_err(expect);
592
- assert_eq!(error.code, "INVALID_QUERY", "{expect}: {error}");
593
- };
594
- let mut undeclared = clause(urgency());
595
- undeclared.filter = Some(SemanticQueryFilter {
596
- at_least: Some("critical".into()),
597
- ..Default::default()
598
- });
599
- invalid(&undeclared, "undeclared level");
600
-
601
- let mut wrong_operator = clause(remote());
602
- wrong_operator.filter = Some(SemanticQueryFilter {
603
- within: Some(vec!["true".into()]),
604
- ..Default::default()
605
- });
606
- invalid(&wrong_operator, "binary takes `is`");
607
-
608
- let mut ordered_choice = clause(action());
609
- ordered_choice.order_by = Some(SemanticQueryOrder {
610
- direction: SemanticOrderDirection::Asc,
611
- });
612
- invalid(&ordered_choice, "only score orders");
613
-
614
- let mut empty = clause(urgency());
615
- empty.filter = Some(SemanticQueryFilter::default());
616
- invalid(&empty, "empty filter");
617
-
618
- let mut bad_definition = clause(DecisionDefinition::Choice {
619
- question: "?".into(),
620
- options: vec!["a".into(), "a".into()],
621
- });
622
- bad_definition.filter = None;
623
- invalid(&bad_definition, "duplicate option");
624
-
625
- let unknown_key: Result<SemanticQueryClause, _> = serde_json::from_value(json!({
626
- "decision": { "kind": "binary", "predicate": "is urgent" },
627
- "refill": true,
628
- }));
629
- assert!(
630
- unknown_key.is_err(),
631
- "unknown clause keys are refused, never ignored"
632
- );
633
- let unknown_filter: Result<SemanticQueryClause, _> = serde_json::from_value(json!({
634
- "decision": { "kind": "binary", "predicate": "is urgent" },
635
- "filter": { "massAtLeast": 0.5 },
636
- }));
637
- assert!(
638
- unknown_filter.is_err(),
639
- "option_mass is never a filter operand"
640
- );
641
-
642
- let error = compose_semantic_page(
643
- &memory_db("too-many"),
644
- "tickets",
645
- (0..1_001)
646
- .map(|index| SemanticCandidate {
647
- record_id: format!("t-{index}"),
648
- record: json!({}),
649
- })
650
- .collect(),
651
- &clause(urgency()),
652
- Some(&CountingRuntime::new()),
653
- None,
654
- )
655
- .expect_err("over the page bound");
656
- assert_eq!(error.code, "INVALID_QUERY");
657
- }
658
-
659
- /// §10 — the clause hash that binds a cursor changes with the clause and
660
- /// with nothing else; results, judgments, and providers never enter it.
661
- #[test]
662
- fn clause_hash_binds_the_clause_and_not_its_results() {
663
- let base = clause(urgency());
664
- let same = clause(urgency());
665
- assert_eq!(semantic_clause_hash(&base), semantic_clause_hash(&same));
666
-
667
- let mut filtered = clause(urgency());
668
- filtered.filter = Some(SemanticQueryFilter {
669
- at_least: Some("medium".into()),
670
- ..Default::default()
671
- });
672
- assert_ne!(semantic_clause_hash(&base), semantic_clause_hash(&filtered));
673
-
674
- let mut reuse = clause(urgency());
675
- reuse.mode = SemanticQueryMode::ReuseOnly;
676
- assert_ne!(semantic_clause_hash(&base), semantic_clause_hash(&reuse));
677
-
678
- // Evaluating a page does not change the hash of the clause that ran it.
679
- let db = memory_db("hash");
680
- let page = seed_page(&db, 2);
681
- compose_semantic_page(
682
- &db,
683
- "tickets",
684
- page,
685
- &base,
686
- Some(&CountingRuntime::new()),
687
- None,
688
- )
689
- .expect("page");
690
- assert_eq!(semantic_clause_hash(&base), semantic_clause_hash(&same));
691
- }
692
-
693
- /// §6 — a semantic query writes judgments and nothing else; the judged records
694
- /// are byte-for-byte what they were, and a state change supersedes the
695
- /// judgment through the primitive's own lifecycle.
696
- #[test]
697
- fn judgments_are_derived_state_that_state_changes_supersede() {
698
- let db = memory_db("derived");
699
- let page = seed_page(&db, 2);
700
- let runtime = CountingRuntime::new();
701
- let before: Vec<Value> = page
702
- .iter()
703
- .map(|candidate| {
704
- db.get(&format!("tickets:{}", candidate.record_id))
705
- .unwrap()
706
- .unwrap()
707
- })
708
- .collect();
709
-
710
- compose_semantic_page(
711
- &db,
712
- "tickets",
713
- page.clone(),
714
- &clause(urgency()),
715
- Some(&runtime),
716
- None,
717
- )
718
- .expect("page");
719
- let after: Vec<Value> = page
720
- .iter()
721
- .map(|candidate| {
722
- db.get(&format!("tickets:{}", candidate.record_id))
723
- .unwrap()
724
- .unwrap()
725
- })
726
- .collect();
727
- assert_eq!(before, after);
728
-
729
- let mut changed = before[0].clone();
730
- changed["urgency"] = json!("high");
731
- db.update("tickets:t-0", changed.clone()).expect("update");
732
- let page = vec![
733
- SemanticCandidate {
734
- record_id: "t-0".into(),
735
- record: changed,
736
- },
737
- page[1].clone(),
738
- ];
739
- let composed = compose_semantic_page(
740
- &db,
741
- "tickets",
742
- page,
743
- &clause(urgency()),
744
- Some(&runtime),
745
- None,
746
- )
747
- .expect("page");
748
- assert_eq!(
749
- runtime.invocations(),
750
- 3,
751
- "only the changed record was re-evaluated"
752
- );
753
- assert_eq!(composed.stage.evaluated, 1);
754
- assert_eq!(composed.stage.reused, 1);
755
- let judgments: Vec<feltdb::DecisionJudgmentRecord> = db
756
- .capability(DEFAULT_DECISION_COLLECTION)
757
- .find(|row: &feltdb::DecisionJudgmentRecord| row.record_id == "t-0")
758
- .expect("judgments");
759
- assert_eq!(judgments.len(), 2);
760
- assert_eq!(
761
- judgments
762
- .iter()
763
- .filter(|row| row.status == DecisionStatus::Superseded)
764
- .count(),
765
- 1
766
- );
767
- }