@feltdb/core 0.13.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/dist/agent-runtime.d.ts +64 -2
  2. package/dist/agent-runtime.d.ts.map +1 -1
  3. package/dist/agent-runtime.js +121 -11
  4. package/dist/agent-state.d.ts +68 -0
  5. package/dist/agent-state.d.ts.map +1 -1
  6. package/dist/agent-state.js +210 -12
  7. package/dist/agent.d.ts +10 -1
  8. package/dist/agent.d.ts.map +1 -1
  9. package/dist/agent.js +10 -1
  10. package/dist/build-identity.json +12 -4
  11. package/dist/cli/commands.js +214 -10
  12. package/dist/cli/publish-engine.js +116 -16
  13. package/dist/collection.d.ts +3 -1
  14. package/dist/collection.d.ts.map +1 -1
  15. package/dist/collection.js +33 -8
  16. package/dist/create/build-identity.json +12 -4
  17. package/dist/create/cli.js +1 -1
  18. package/dist/create/create.js +4 -6
  19. package/dist/create/managed-account.js +93 -35
  20. package/dist/create/package-versions.js +1 -1
  21. package/dist/create/server-source/Cargo.lock +8 -511
  22. package/dist/create/server-source/Cargo.toml +2 -13
  23. package/dist/create/server-source/crates/feltdb-client/Cargo.toml +1 -1
  24. package/dist/create/server-source/crates/feltdb-server/Cargo.toml +1 -1
  25. package/dist/create/server-source/crates/feltdb-server/README.md +6 -1
  26. package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +9 -1
  27. package/dist/create/server-source/crates/feltdb-server/src/auth.rs +85 -25
  28. package/dist/create/server-source/crates/feltdb-server/src/backup.rs +4 -0
  29. package/dist/create/server-source/crates/feltdb-server/src/console_accounts.rs +1009 -0
  30. package/dist/create/server-source/crates/feltdb-server/src/control_authorization.rs +441 -0
  31. package/dist/create/server-source/crates/feltdb-server/src/identity.rs +68 -6
  32. package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +355 -23
  33. package/dist/create/server-source/crates/feltdb-server/src/lib.rs +3 -0
  34. package/dist/create/server-source/crates/feltdb-server/src/main.rs +1134 -96
  35. package/dist/create/server-source/crates/feltdb-server/src/redaction.rs +208 -0
  36. package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +198 -19
  37. package/dist/create/server-source/crates/feltdb-server/tests/transaction_precondition_contract.rs +403 -0
  38. package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +1 -1
  39. package/dist/db.d.ts +75 -10
  40. package/dist/db.d.ts.map +1 -1
  41. package/dist/db.js +168 -29
  42. package/dist/deployment.d.ts +19 -0
  43. package/dist/deployment.d.ts.map +1 -1
  44. package/dist/deployment.js +38 -2
  45. package/dist/embedded-query.d.ts.map +1 -1
  46. package/dist/embedded-query.js +5 -4
  47. package/dist/embedded-transaction.d.ts +8 -4
  48. package/dist/embedded-transaction.d.ts.map +1 -1
  49. package/dist/embedded-transaction.js +55 -19
  50. package/dist/error-codes.d.ts +36 -2
  51. package/dist/error-codes.d.ts.map +1 -1
  52. package/dist/error-codes.js +50 -3
  53. package/dist/feltdb.d.ts +7 -0
  54. package/dist/feltdb.d.ts.map +1 -1
  55. package/dist/file-db.d.ts +27 -0
  56. package/dist/file-db.d.ts.map +1 -1
  57. package/dist/file-db.js +145 -7
  58. package/dist/http-client.d.ts +11 -3
  59. package/dist/http-client.d.ts.map +1 -1
  60. package/dist/http-client.js +27 -14
  61. package/dist/http-db.d.ts +43 -1
  62. package/dist/http-db.d.ts.map +1 -1
  63. package/dist/http-db.js +287 -59
  64. package/dist/index.js +1 -1
  65. package/dist/local-state-lock.d.ts +34 -0
  66. package/dist/local-state-lock.d.ts.map +1 -0
  67. package/dist/local-state-lock.js +225 -0
  68. package/dist/managed-recovery.d.ts +6 -0
  69. package/dist/managed-recovery.d.ts.map +1 -1
  70. package/dist/managed-recovery.js +9 -0
  71. package/dist/reactive-graph.d.ts +0 -3
  72. package/dist/reactive-graph.d.ts.map +1 -1
  73. package/dist/reactive-graph.js +11 -1
  74. package/dist/runtime-registry.d.ts +7 -2
  75. package/dist/runtime-registry.d.ts.map +1 -1
  76. package/dist/runtime-registry.js +2 -2
  77. package/dist/studio/components/index.js +1 -1
  78. package/dist/studio/{components-BSGmHfif.js → components-CJmzntBi.js} +2 -1
  79. package/dist/studio/index.js +1 -1
  80. package/dist/studio-app/assets/{feltdb_wasm-3hDngl1h.js → feltdb_wasm-DtybUY0q.js} +1 -1
  81. package/dist/studio-app/assets/feltdb_wasm_bg-DsgIUwaB.wasm +0 -0
  82. package/dist/studio-app/assets/index-C4OffsZl.js +37 -0
  83. package/dist/studio-app/index.html +1 -1
  84. package/dist/transaction.d.ts +27 -11
  85. package/dist/transaction.d.ts.map +1 -1
  86. package/dist/transaction.js +20 -14
  87. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  88. package/dist/wasm/package.json +1 -1
  89. package/dist/workload.d.ts +86 -6
  90. package/dist/workload.d.ts.map +1 -1
  91. package/dist/workload.js +51 -3
  92. package/dist/workspace/workspace-connection.d.ts.map +1 -1
  93. package/dist/workspace/workspace-connection.js +14 -3
  94. package/package.json +1 -1
  95. package/dist/create/server-source/crates/feltdb/Cargo.lock +0 -175
  96. package/dist/create/server-source/crates/feltdb/Cargo.toml +0 -102
  97. package/dist/create/server-source/crates/feltdb/LICENSE +0 -21
  98. package/dist/create/server-source/crates/feltdb/README.md +0 -75
  99. package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +0 -370
  100. package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +0 -589
  101. package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +0 -247
  102. package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +0 -440
  103. package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +0 -275
  104. package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +0 -381
  105. package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +0 -467
  106. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +0 -489
  107. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +0 -281
  108. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +0 -606
  109. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +0 -531
  110. package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +0 -462
  111. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +0 -505
  112. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +0 -484
  113. package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +0 -758
  114. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +0 -421
  115. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +0 -329
  116. package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +0 -1705
  117. package/dist/create/server-source/crates/feltdb/src/acquisition.rs +0 -286
  118. package/dist/create/server-source/crates/feltdb/src/admission.rs +0 -185
  119. package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +0 -507
  120. package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +0 -539
  121. package/dist/create/server-source/crates/feltdb/src/agent_state.rs +0 -320
  122. package/dist/create/server-source/crates/feltdb/src/agent_transition_contract.rs +0 -1545
  123. package/dist/create/server-source/crates/feltdb/src/analytics.rs +0 -521
  124. package/dist/create/server-source/crates/feltdb/src/application.rs +0 -2674
  125. package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +0 -1070
  126. package/dist/create/server-source/crates/feltdb/src/authority.rs +0 -479
  127. package/dist/create/server-source/crates/feltdb/src/authority_failover.rs +0 -847
  128. package/dist/create/server-source/crates/feltdb/src/authorization.rs +0 -1030
  129. package/dist/create/server-source/crates/feltdb/src/authorization_security_tests.rs +0 -1122
  130. package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority-client.rs +0 -32
  131. package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority.rs +0 -19
  132. package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +0 -1474
  133. package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +0 -8
  134. package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +0 -418
  135. package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +0 -482
  136. package/dist/create/server-source/crates/feltdb/src/capability.rs +0 -903
  137. package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +0 -263
  138. package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +0 -77
  139. package/dist/create/server-source/crates/feltdb/src/causal_backlog_bound.rs +0 -496
  140. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +0 -2574
  141. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +0 -212
  142. package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +0 -422
  143. package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +0 -445
  144. package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +0 -465
  145. package/dist/create/server-source/crates/feltdb/src/convergence.rs +0 -634
  146. package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +0 -427
  147. package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +0 -376
  148. package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +0 -365
  149. package/dist/create/server-source/crates/feltdb/src/cron.rs +0 -294
  150. package/dist/create/server-source/crates/feltdb/src/dedup_bound_investigation.rs +0 -483
  151. package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +0 -477
  152. package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +0 -1003
  153. package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +0 -1334
  154. package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +0 -368
  155. package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +0 -231
  156. package/dist/create/server-source/crates/feltdb/src/durable_operation_identity.rs +0 -482
  157. package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +0 -203
  158. package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +0 -317
  159. package/dist/create/server-source/crates/feltdb/src/equality_index.rs +0 -595
  160. package/dist/create/server-source/crates/feltdb/src/execution.rs +0 -426
  161. package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +0 -214
  162. package/dist/create/server-source/crates/feltdb/src/indexing.rs +0 -776
  163. package/dist/create/server-source/crates/feltdb/src/lib.rs +0 -7274
  164. package/dist/create/server-source/crates/feltdb/src/managed_cas_tests.rs +0 -234
  165. package/dist/create/server-source/crates/feltdb/src/materialization.rs +0 -184
  166. package/dist/create/server-source/crates/feltdb/src/metrics.rs +0 -266
  167. package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +0 -310
  168. package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +0 -736
  169. package/dist/create/server-source/crates/feltdb/src/observability.rs +0 -379
  170. package/dist/create/server-source/crates/feltdb/src/operation.rs +0 -290
  171. package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +0 -439
  172. package/dist/create/server-source/crates/feltdb/src/operation_log.rs +0 -349
  173. package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +0 -332
  174. package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +0 -718
  175. package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +0 -487
  176. package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +0 -166
  177. package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +0 -289
  178. package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +0 -566
  179. package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +0 -3391
  180. package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +0 -203
  181. package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +0 -261
  182. package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +0 -502
  183. package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +0 -216
  184. package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +0 -303
  185. package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +0 -323
  186. package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +0 -531
  187. package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +0 -406
  188. package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +0 -436
  189. package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +0 -387
  190. package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +0 -378
  191. package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +0 -359
  192. package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +0 -372
  193. package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +0 -404
  194. package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +0 -376
  195. package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +0 -461
  196. package/dist/create/server-source/crates/feltdb/src/policy_evaluation.rs +0 -2125
  197. package/dist/create/server-source/crates/feltdb/src/production_api.rs +0 -418
  198. package/dist/create/server-source/crates/feltdb/src/provenance.rs +0 -207
  199. package/dist/create/server-source/crates/feltdb/src/query_execution_diagnostics.rs +0 -126
  200. package/dist/create/server-source/crates/feltdb/src/query_performance.rs +0 -218
  201. package/dist/create/server-source/crates/feltdb/src/read_basis.rs +0 -257
  202. package/dist/create/server-source/crates/feltdb/src/references.rs +0 -404
  203. package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +0 -401
  204. package/dist/create/server-source/crates/feltdb/src/replica_acknowledgements.rs +0 -501
  205. package/dist/create/server-source/crates/feltdb/src/replica_membership.rs +0 -680
  206. package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +0 -163
  207. package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +0 -133
  208. package/dist/create/server-source/crates/feltdb/src/routing.rs +0 -409
  209. package/dist/create/server-source/crates/feltdb/src/semantic_decision.rs +0 -2029
  210. package/dist/create/server-source/crates/feltdb/src/semantic_query.rs +0 -599
  211. package/dist/create/server-source/crates/feltdb/src/sharding.rs +0 -549
  212. package/dist/create/server-source/crates/feltdb/src/state_conflict_contract.rs +0 -516
  213. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +0 -5894
  214. package/dist/create/server-source/crates/feltdb/src/state_diff_contract.rs +0 -222
  215. package/dist/create/server-source/crates/feltdb/src/state_facade.rs +0 -320
  216. package/dist/create/server-source/crates/feltdb/src/state_hash.rs +0 -291
  217. package/dist/create/server-source/crates/feltdb/src/state_model.rs +0 -4158
  218. package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +0 -379
  219. package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +0 -673
  220. package/dist/create/server-source/crates/feltdb/src/storage.rs +0 -273
  221. package/dist/create/server-source/crates/feltdb/src/submission.rs +0 -471
  222. package/dist/create/server-source/crates/feltdb/src/sync.rs +0 -495
  223. package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +0 -822
  224. package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +0 -433
  225. package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +0 -462
  226. package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +0 -965
  227. package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +0 -1120
  228. package/dist/create/server-source/crates/feltdb/src/transactions.rs +0 -643
  229. package/dist/create/server-source/crates/feltdb/src/trigger.rs +0 -390
  230. package/dist/create/server-source/crates/feltdb/src/trigger_contract.rs +0 -749
  231. package/dist/create/server-source/crates/feltdb/src/versioned_object_substrate.rs +0 -708
  232. package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +0 -1133
  233. package/dist/create/server-source/crates/feltdb/src/workflow.rs +0 -713
  234. package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +0 -510
  235. package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +0 -354
  236. package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +0 -594
  237. package/dist/create/server-source/crates/feltdb/src/workload.rs +0 -2886
  238. package/dist/create/server-source/crates/feltdb/src/workload_diagnostics.rs +0 -456
  239. package/dist/create/server-source/crates/feltdb/tests/agent_state_native.rs +0 -1188
  240. package/dist/create/server-source/crates/feltdb/tests/agent_transition_contract_conformance.rs +0 -2884
  241. package/dist/create/server-source/crates/feltdb/tests/authority_process.rs +0 -297
  242. package/dist/create/server-source/crates/feltdb/tests/authority_transaction_identity.rs +0 -534
  243. package/dist/create/server-source/crates/feltdb/tests/bounded_read_contract.rs +0 -132
  244. package/dist/create/server-source/crates/feltdb/tests/branching_evidence.rs +0 -299
  245. package/dist/create/server-source/crates/feltdb/tests/common/mod.rs +0 -96
  246. package/dist/create/server-source/crates/feltdb/tests/compaction_stall_contract.rs +0 -272
  247. package/dist/create/server-source/crates/feltdb/tests/crash_durability_contract.rs +0 -467
  248. package/dist/create/server-source/crates/feltdb/tests/current_revision_authority_evidence.rs +0 -309
  249. package/dist/create/server-source/crates/feltdb/tests/durable_backup_contract.rs +0 -445
  250. package/dist/create/server-source/crates/feltdb/tests/durable_corruption_contract.rs +0 -518
  251. package/dist/create/server-source/crates/feltdb/tests/durable_format_compatibility.rs +0 -392
  252. package/dist/create/server-source/crates/feltdb/tests/feltdb_state_boundary_tests.rs +0 -850
  253. package/dist/create/server-source/crates/feltdb/tests/fixtures/agent_transition_corpus.json +0 -23340
  254. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_conflict_contract_corpus.json +0 -1916
  255. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_diff_contract_corpus.json +0 -1878
  256. package/dist/create/server-source/crates/feltdb/tests/fixtures/trigger_contract_corpus.json +0 -1862
  257. package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_evidence.rs +0 -123
  258. package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_substrate_completion.rs +0 -299
  259. package/dist/create/server-source/crates/feltdb/tests/local_process_ownership.rs +0 -69
  260. package/dist/create/server-source/crates/feltdb/tests/managed_incident_regression.rs +0 -191
  261. package/dist/create/server-source/crates/feltdb/tests/operational_health_contract.rs +0 -278
  262. package/dist/create/server-source/crates/feltdb/tests/pr34_query_collection.rs +0 -234
  263. package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +0 -947
  264. package/dist/create/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +0 -404
  265. package/dist/create/server-source/crates/feltdb/tests/pr8_vocabulary_assessment.rs +0 -916
  266. package/dist/create/server-source/crates/feltdb/tests/pr9_phase2_boundary_tests.rs +0 -1045
  267. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3a_path_a_tests.rs +0 -347
  268. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_authorized_mutations.rs +0 -361
  269. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_role_based_authorization.rs +0 -330
  270. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_simple_auth_delete.rs +0 -93
  271. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_team_delete_role_authorization.rs +0 -622
  272. package/dist/create/server-source/crates/feltdb/tests/pr9_teams_role_based_access.rs +0 -517
  273. package/dist/create/server-source/crates/feltdb/tests/production_certification.rs +0 -1153
  274. package/dist/create/server-source/crates/feltdb/tests/production_contract.rs +0 -771
  275. package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +0 -1383
  276. package/dist/create/server-source/crates/feltdb/tests/reconciliation_application.rs +0 -868
  277. package/dist/create/server-source/crates/feltdb/tests/reconciliation_wire_format_evidence.rs +0 -221
  278. package/dist/create/server-source/crates/feltdb/tests/replicated_history_contract.rs +0 -417
  279. package/dist/create/server-source/crates/feltdb/tests/resource_scoped_revisions.rs +0 -338
  280. package/dist/create/server-source/crates/feltdb/tests/revision_identity_contract.rs +0 -1039
  281. package/dist/create/server-source/crates/feltdb/tests/revision_model_decision.rs +0 -739
  282. package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +0 -452
  283. package/dist/create/server-source/crates/feltdb/tests/saas_authorization_integration.rs +0 -81
  284. package/dist/create/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +0 -438
  285. package/dist/create/server-source/crates/feltdb/tests/semantic_decision.rs +0 -1165
  286. package/dist/create/server-source/crates/feltdb/tests/semantic_query.rs +0 -767
  287. package/dist/create/server-source/crates/feltdb/tests/semantic_query_contract.rs +0 -363
  288. package/dist/create/server-source/crates/feltdb/tests/state_conflict_contract_conformance.rs +0 -1799
  289. package/dist/create/server-source/crates/feltdb/tests/state_diff_contract_conformance.rs +0 -1316
  290. package/dist/create/server-source/crates/feltdb/tests/state_model_integration.rs +0 -358
  291. package/dist/create/server-source/crates/feltdb/tests/state_persistence_integration.rs +0 -365
  292. package/dist/create/server-source/crates/feltdb/tests/state_ref_authority.rs +0 -918
  293. package/dist/create/server-source/crates/feltdb/tests/state_store_boundary_evidence.rs +0 -299
  294. package/dist/create/server-source/crates/feltdb/tests/storage_independent_versioned_object_substrate.rs +0 -328
  295. package/dist/create/server-source/crates/feltdb/tests/sync_divergence_evidence.rs +0 -255
  296. package/dist/create/server-source/crates/feltdb/tests/three_way_input_boundary_evidence.rs +0 -249
  297. package/dist/create/server-source/crates/feltdb/tests/trigger_contract_conformance.rs +0 -994
  298. package/dist/create/server-source/crates/feltdb/tests/workload_envelope_contract.rs +0 -484
  299. package/dist/studio-app/assets/feltdb_wasm_bg-CFQT4gCy.wasm +0 -0
  300. package/dist/studio-app/assets/index-Dql1hS5m.js +0 -30
@@ -1,2029 +0,0 @@
1
- use crate::authorization::{AuthorizationRequest, Subject};
2
- use crate::{FeltDb, FlowError};
3
- use serde::{Deserialize, Serialize};
4
- use serde_json::{json, Map, Value};
5
- use sha2::{Digest, Sha256};
6
- use std::collections::{BTreeMap, BTreeSet};
7
- use std::sync::{Arc, Mutex};
8
-
9
- pub const DEFAULT_DECISION_COLLECTION: &str = "semantic_decisions";
10
-
11
- #[cfg(target_arch = "wasm32")]
12
- fn semantic_now_ms() -> u128 {
13
- 0
14
- }
15
-
16
- #[cfg(not(target_arch = "wasm32"))]
17
- fn semantic_now_ms() -> u128 {
18
- crate::now_ms()
19
- }
20
-
21
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
22
- #[serde(rename_all = "snake_case")]
23
- pub enum DecisionStatus {
24
- Active,
25
- Superseded,
26
- }
27
-
28
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
29
- #[serde(rename_all = "snake_case")]
30
- pub enum DecisionKind {
31
- Binary,
32
- Choice,
33
- Score,
34
- }
35
-
36
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
37
- pub struct BinaryDecisionResult {
38
- pub kind: DecisionKind,
39
- pub decision: bool,
40
- pub option_mass: f64,
41
- #[serde(default)]
42
- pub supporting_fields: Vec<String>,
43
- }
44
-
45
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
46
- pub struct WeightedOption {
47
- pub value: String,
48
- pub probability: f64,
49
- }
50
-
51
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
52
- pub struct ChoiceDecisionResult {
53
- pub kind: DecisionKind,
54
- pub selected: String,
55
- pub options: Vec<WeightedOption>,
56
- pub option_mass: f64,
57
- #[serde(default)]
58
- pub supporting_fields: Vec<String>,
59
- }
60
-
61
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
62
- pub struct ScoreDecisionResult {
63
- pub kind: DecisionKind,
64
- pub selected: String,
65
- pub levels: Vec<WeightedOption>,
66
- pub option_mass: f64,
67
- #[serde(default)]
68
- pub supporting_fields: Vec<String>,
69
- }
70
-
71
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
72
- #[serde(tag = "kind", rename_all = "snake_case")]
73
- pub enum DecisionResult {
74
- Binary {
75
- decision: bool,
76
- option_mass: f64,
77
- #[serde(default)]
78
- supporting_fields: Vec<String>,
79
- },
80
- Choice {
81
- selected: String,
82
- options: Vec<WeightedOption>,
83
- option_mass: f64,
84
- #[serde(default)]
85
- supporting_fields: Vec<String>,
86
- },
87
- Score {
88
- selected: String,
89
- levels: Vec<WeightedOption>,
90
- option_mass: f64,
91
- #[serde(default)]
92
- supporting_fields: Vec<String>,
93
- },
94
- }
95
-
96
- impl DecisionResult {
97
- pub fn kind(&self) -> DecisionKind {
98
- match self {
99
- Self::Binary { .. } => DecisionKind::Binary,
100
- Self::Choice { .. } => DecisionKind::Choice,
101
- Self::Score { .. } => DecisionKind::Score,
102
- }
103
- }
104
-
105
- pub fn option_mass(&self) -> f64 {
106
- match self {
107
- Self::Binary { option_mass, .. }
108
- | Self::Choice { option_mass, .. }
109
- | Self::Score { option_mass, .. } => *option_mass,
110
- }
111
- }
112
-
113
- pub fn supporting_fields(&self) -> &[String] {
114
- match self {
115
- Self::Binary {
116
- supporting_fields, ..
117
- }
118
- | Self::Choice {
119
- supporting_fields, ..
120
- }
121
- | Self::Score {
122
- supporting_fields, ..
123
- } => supporting_fields.as_slice(),
124
- }
125
- }
126
- }
127
-
128
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
129
- #[serde(tag = "kind", rename_all = "snake_case")]
130
- pub enum DecisionDefinition {
131
- Binary {
132
- predicate: String,
133
- },
134
- Choice {
135
- question: String,
136
- options: Vec<String>,
137
- },
138
- Score {
139
- question: String,
140
- levels: Vec<String>,
141
- },
142
- }
143
-
144
- impl DecisionDefinition {
145
- pub fn kind(&self) -> DecisionKind {
146
- match self {
147
- Self::Binary { .. } => DecisionKind::Binary,
148
- Self::Choice { .. } => DecisionKind::Choice,
149
- Self::Score { .. } => DecisionKind::Score,
150
- }
151
- }
152
-
153
- pub fn prompt(&self) -> &str {
154
- match self {
155
- Self::Binary { predicate } => predicate,
156
- Self::Choice { question, .. } | Self::Score { question, .. } => question,
157
- }
158
- }
159
-
160
- pub fn schema_values(&self) -> &[String] {
161
- match self {
162
- Self::Binary { .. } => &[],
163
- Self::Choice { options, .. } => options,
164
- Self::Score { levels, .. } => levels,
165
- }
166
- }
167
- }
168
-
169
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
170
- #[serde(tag = "kind", rename_all = "snake_case")]
171
- pub enum DecisionRequest {
172
- Binary {
173
- predicate: String,
174
- context: Value,
175
- },
176
- Choice {
177
- question: String,
178
- options: Vec<String>,
179
- context: Value,
180
- },
181
- Score {
182
- question: String,
183
- levels: Vec<String>,
184
- context: Value,
185
- },
186
- }
187
-
188
- impl DecisionRequest {
189
- pub fn definition(&self) -> DecisionDefinition {
190
- match self {
191
- Self::Binary { predicate, .. } => DecisionDefinition::Binary {
192
- predicate: predicate.clone(),
193
- },
194
- Self::Choice {
195
- question, options, ..
196
- } => DecisionDefinition::Choice {
197
- question: question.clone(),
198
- options: options.clone(),
199
- },
200
- Self::Score {
201
- question, levels, ..
202
- } => DecisionDefinition::Score {
203
- question: question.clone(),
204
- levels: levels.clone(),
205
- },
206
- }
207
- }
208
-
209
- pub fn context(&self) -> &Value {
210
- match self {
211
- Self::Binary { context, .. }
212
- | Self::Choice { context, .. }
213
- | Self::Score { context, .. } => context,
214
- }
215
- }
216
- }
217
-
218
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
219
- pub struct DecisionRecordRef {
220
- pub collection: String,
221
- pub record_id: String,
222
- }
223
-
224
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
225
- pub struct DecisionPrincipal {
226
- pub subject: Subject,
227
- pub tenant_id: String,
228
- pub application_id: String,
229
- #[serde(default = "default_read_capability")]
230
- pub capability: String,
231
- }
232
-
233
- fn default_read_capability() -> String {
234
- "state:read".into()
235
- }
236
-
237
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
238
- pub struct DecisionAuthorizationDecision {
239
- pub allowed: bool,
240
- pub decision_id: Option<String>,
241
- }
242
-
243
- pub trait DecisionAuthorizationGate {
244
- fn check(
245
- &self,
246
- request: &AuthorizationRequest,
247
- ) -> Result<DecisionAuthorizationDecision, DecisionError>;
248
- }
249
-
250
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
251
- pub struct DecisionAuthorizationConfig {
252
- pub principal: Option<DecisionPrincipal>,
253
- }
254
-
255
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
256
- pub struct DecisionRuntimeMetadata {
257
- pub runtime: String,
258
- #[serde(default, skip_serializing_if = "Option::is_none")]
259
- pub model: Option<String>,
260
- #[serde(default, skip_serializing_if = "Option::is_none")]
261
- pub model_revision: Option<String>,
262
- #[serde(default, skip_serializing_if = "Option::is_none")]
263
- pub prompt_revision: Option<String>,
264
- #[serde(default, skip_serializing_if = "Option::is_none")]
265
- pub execution_method: Option<DecisionExecutionMethod>,
266
- #[serde(default, skip_serializing_if = "Option::is_none")]
267
- pub decision_schema_revision: Option<String>,
268
- }
269
-
270
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
271
- #[serde(rename_all = "snake_case")]
272
- pub enum DecisionExecutionMethod {
273
- Logit,
274
- Structured,
275
- Generated,
276
- Head,
277
- }
278
-
279
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
280
- pub struct DecisionRuntimeCapabilities {
281
- pub binary: bool,
282
- pub choice: bool,
283
- pub score: bool,
284
- pub batch: bool,
285
- pub probability_distribution: bool,
286
- pub execution_methods: BTreeSet<DecisionExecutionMethod>,
287
- }
288
-
289
- impl Default for DecisionRuntimeCapabilities {
290
- fn default() -> Self {
291
- Self {
292
- binary: true,
293
- choice: true,
294
- score: true,
295
- batch: true,
296
- probability_distribution: true,
297
- execution_methods: BTreeSet::new(),
298
- }
299
- }
300
- }
301
-
302
- pub trait DecisionRuntime {
303
- fn metadata(&self) -> DecisionRuntimeMetadata;
304
- fn decide(&self, request: &DecisionRequest) -> Result<DecisionResult, DecisionError>;
305
-
306
- fn capabilities(&self) -> DecisionRuntimeCapabilities {
307
- DecisionRuntimeCapabilities::default()
308
- }
309
-
310
- fn execute(&self, request: &DecisionRequest) -> Result<DecisionResult, DecisionError> {
311
- self.decide(request)
312
- }
313
-
314
- fn execute_batch(
315
- &self,
316
- requests: &[DecisionRequest],
317
- ) -> Result<Vec<DecisionResult>, DecisionError> {
318
- requests
319
- .iter()
320
- .map(|request| self.execute(request))
321
- .collect()
322
- }
323
- }
324
-
325
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
326
- pub struct DecisionProvenance {
327
- pub runtime: String,
328
- #[serde(default, skip_serializing_if = "Option::is_none")]
329
- pub model: Option<String>,
330
- #[serde(default, skip_serializing_if = "Option::is_none")]
331
- pub model_revision: Option<String>,
332
- #[serde(default, skip_serializing_if = "Option::is_none")]
333
- pub prompt_revision: Option<String>,
334
- #[serde(default, skip_serializing_if = "Option::is_none")]
335
- pub execution_method: Option<DecisionExecutionMethod>,
336
- #[serde(default, skip_serializing_if = "Option::is_none")]
337
- pub decision_schema_revision: Option<String>,
338
- pub context_hash: String,
339
- pub relevant_state_version: String,
340
- pub cache_key: String,
341
- pub source: String,
342
- pub evaluated_at: u128,
343
- pub served_at: u128,
344
- }
345
-
346
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
347
- pub struct DecisionEvidenceField {
348
- pub field: String,
349
- pub value: Value,
350
- }
351
-
352
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
353
- pub struct DecisionEvidence {
354
- pub decision_id: String,
355
- pub kind: DecisionKind,
356
- pub input: DecisionDefinition,
357
- pub result: DecisionResult,
358
- pub supporting_context: Vec<DecisionEvidenceField>,
359
- pub runtime: DecisionRuntimeMetadata,
360
- pub state: DecisionStateReference,
361
- }
362
-
363
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
364
- pub struct DecisionStateReference {
365
- pub collection: String,
366
- pub record_id: String,
367
- pub record_version: Option<u64>,
368
- pub reference: String,
369
- pub relevant_state_version: String,
370
- pub withheld_fields: Vec<String>,
371
- }
372
-
373
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
374
- pub struct DecisionEvaluation {
375
- pub result: DecisionResult,
376
- pub cached: bool,
377
- pub evidence: Vec<DecisionEvidence>,
378
- pub provenance: DecisionProvenance,
379
- }
380
-
381
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
382
- pub struct DecisionJudgmentRecord {
383
- pub id: String,
384
- pub lineage_key: String,
385
- pub kind: DecisionKind,
386
- pub collection: String,
387
- pub record_id: String,
388
- pub input: DecisionDefinition,
389
- pub schema_version: String,
390
- pub relevant_state_version: String,
391
- pub runtime: String,
392
- #[serde(default, skip_serializing_if = "Option::is_none")]
393
- pub model: Option<String>,
394
- #[serde(default, skip_serializing_if = "Option::is_none")]
395
- pub model_revision: Option<String>,
396
- #[serde(default, skip_serializing_if = "Option::is_none")]
397
- pub prompt_revision: Option<String>,
398
- #[serde(default, skip_serializing_if = "Option::is_none")]
399
- pub execution_method: Option<DecisionExecutionMethod>,
400
- #[serde(default, skip_serializing_if = "Option::is_none")]
401
- pub decision_schema_revision: Option<String>,
402
- pub result: DecisionResult,
403
- pub option_mass: f64,
404
- pub context_fields: Vec<String>,
405
- pub supporting_fields: Vec<String>,
406
- pub status: DecisionStatus,
407
- pub state: DecisionStateReference,
408
- pub evidence: DecisionEvidence,
409
- pub evaluated_at: u128,
410
- #[serde(default, skip_serializing_if = "Option::is_none")]
411
- pub superseded_at: Option<u128>,
412
- }
413
-
414
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
415
- pub struct DecisionOptions {
416
- #[serde(default)]
417
- pub fields: Option<Vec<String>>,
418
- #[serde(default)]
419
- pub application_id: Option<String>,
420
- #[serde(default)]
421
- pub decision_collection: Option<String>,
422
- #[serde(default)]
423
- pub schema_version: Option<String>,
424
- }
425
-
426
- impl Default for DecisionOptions {
427
- fn default() -> Self {
428
- Self {
429
- fields: None,
430
- application_id: None,
431
- decision_collection: None,
432
- schema_version: Some("semantic-decision-v1".into()),
433
- }
434
- }
435
- }
436
-
437
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
438
- pub struct DecisionTransportAuthorization {
439
- #[serde(default)]
440
- pub allowed_fields: Vec<String>,
441
- #[serde(default)]
442
- pub principal: Option<DecisionPrincipal>,
443
- }
444
-
445
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
446
- #[serde(tag = "kind", rename_all = "snake_case")]
447
- pub enum DecisionTransportRuntime {
448
- Deterministic {
449
- metadata: DecisionRuntimeMetadata,
450
- responses: BTreeMap<String, DecisionResult>,
451
- },
452
- Recording {
453
- metadata: DecisionRuntimeMetadata,
454
- result: DecisionResult,
455
- },
456
- }
457
-
458
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
459
- pub struct DecisionTransportExecutionRequest {
460
- #[serde(default)]
461
- pub target: Option<DecisionRecordRef>,
462
- #[serde(default, skip_serializing_if = "Option::is_none")]
463
- pub context: Option<Value>,
464
- pub definition: DecisionDefinition,
465
- #[serde(default)]
466
- pub options: DecisionOptions,
467
- #[serde(default)]
468
- pub authorization: Option<DecisionTransportAuthorization>,
469
- pub runtime: DecisionTransportRuntime,
470
- }
471
-
472
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
473
- pub struct DecisionTransportExecutionResponse {
474
- pub evaluation: DecisionEvaluation,
475
- #[serde(default)]
476
- pub runtime_requests: Vec<DecisionRequest>,
477
- }
478
-
479
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
480
- pub struct DecisionBatchDecision {
481
- pub id: String,
482
- pub definition: DecisionDefinition,
483
- #[serde(default = "empty_object")]
484
- pub context: Value,
485
- }
486
-
487
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
488
- pub struct DecisionBatch {
489
- #[serde(default = "empty_object")]
490
- pub context: Value,
491
- pub decisions: Vec<DecisionBatchDecision>,
492
- }
493
-
494
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
495
- pub struct DecisionBatchEvaluation {
496
- pub id: String,
497
- pub result: DecisionResult,
498
- }
499
-
500
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
501
- pub struct ChoicePermutationObservation {
502
- pub order: Vec<String>,
503
- pub selected: String,
504
- pub distribution: Vec<WeightedOption>,
505
- pub rank: usize,
506
- pub probability: f64,
507
- }
508
-
509
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
510
- pub struct ChoiceExpansionObservation {
511
- pub baseline: ChoicePermutationObservation,
512
- pub expanded: ChoicePermutationObservation,
513
- }
514
-
515
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
516
- pub struct ChoiceDiagnosticsReport {
517
- pub permutations: Vec<ChoicePermutationObservation>,
518
- #[serde(default, skip_serializing_if = "Option::is_none")]
519
- pub expansion: Option<ChoiceExpansionObservation>,
520
- }
521
-
522
- fn empty_object() -> Value {
523
- Value::Object(Map::new())
524
- }
525
-
526
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
527
- pub struct DecisionContextPacket {
528
- pub collection: String,
529
- pub record_id: String,
530
- pub record_version: Option<u64>,
531
- pub reference: String,
532
- pub fields: Vec<String>,
533
- pub values: BTreeMap<String, Value>,
534
- pub withheld_fields: Vec<String>,
535
- pub relevant_state_version: String,
536
- pub context_hash: String,
537
- pub authorization_decisions: Vec<String>,
538
- }
539
-
540
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
541
- pub struct DecisionBenchmarkCase {
542
- pub collection: String,
543
- pub record_id: String,
544
- pub input: DecisionDefinition,
545
- pub mutate: Option<Map<String, Value>>,
546
- }
547
-
548
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
549
- pub struct DecisionBenchmarkReport {
550
- pub binary: usize,
551
- pub choice: usize,
552
- pub score: usize,
553
- pub runtime_invocations_first_pass: usize,
554
- pub runtime_invocations_second_pass: usize,
555
- pub cache_hits_second_pass: usize,
556
- }
557
-
558
- #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
559
- pub struct DecisionError {
560
- pub code: String,
561
- pub message: String,
562
- }
563
-
564
- impl DecisionError {
565
- pub fn new(code: &str, message: impl Into<String>) -> Self {
566
- Self {
567
- code: code.into(),
568
- message: message.into(),
569
- }
570
- }
571
- }
572
-
573
- impl std::fmt::Display for DecisionError {
574
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
575
- write!(f, "{}: {}", self.code, self.message)
576
- }
577
- }
578
-
579
- impl std::error::Error for DecisionError {}
580
-
581
- pub(crate) struct TransportAuthorizationGate {
582
- allowed_fields: BTreeSet<String>,
583
- }
584
-
585
- impl TransportAuthorizationGate {
586
- pub(crate) fn new(allowed_fields: &[String]) -> Self {
587
- Self {
588
- allowed_fields: allowed_fields.iter().cloned().collect(),
589
- }
590
- }
591
- }
592
-
593
- impl DecisionAuthorizationGate for TransportAuthorizationGate {
594
- fn check(
595
- &self,
596
- request: &AuthorizationRequest,
597
- ) -> Result<DecisionAuthorizationDecision, DecisionError> {
598
- let field = request
599
- .resource
600
- .uri
601
- .split('/')
602
- .next_back()
603
- .unwrap_or_default()
604
- .to_string();
605
- Ok(DecisionAuthorizationDecision {
606
- allowed: self.allowed_fields.contains(&field),
607
- decision_id: Some(digest(&[
608
- "semantic-decision-authorization-v1".into(),
609
- request.application_id.clone(),
610
- request.resource.uri.clone(),
611
- field,
612
- ])),
613
- })
614
- }
615
- }
616
-
617
- struct RecordingDecisionRuntime {
618
- metadata: DecisionRuntimeMetadata,
619
- result: DecisionResult,
620
- requests: Mutex<Vec<DecisionRequest>>,
621
- }
622
-
623
- impl RecordingDecisionRuntime {
624
- fn new(metadata: DecisionRuntimeMetadata, result: DecisionResult) -> Self {
625
- Self {
626
- metadata,
627
- result,
628
- requests: Mutex::new(Vec::new()),
629
- }
630
- }
631
-
632
- fn recorded_requests(&self) -> Vec<DecisionRequest> {
633
- self.requests
634
- .lock()
635
- .expect("recording runtime mutex")
636
- .clone()
637
- }
638
- }
639
-
640
- impl DecisionRuntime for RecordingDecisionRuntime {
641
- fn metadata(&self) -> DecisionRuntimeMetadata {
642
- self.metadata.clone()
643
- }
644
-
645
- fn decide(&self, request: &DecisionRequest) -> Result<DecisionResult, DecisionError> {
646
- self.requests
647
- .lock()
648
- .expect("recording runtime mutex")
649
- .push(request.clone());
650
- Ok(self.result.clone())
651
- }
652
- }
653
-
654
- enum TransportRuntimeHandle {
655
- Deterministic(DeterministicDecisionRuntime),
656
- Recording(RecordingDecisionRuntime),
657
- }
658
-
659
- impl TransportRuntimeHandle {
660
- fn new(runtime: &DecisionTransportRuntime) -> Self {
661
- match runtime {
662
- DecisionTransportRuntime::Deterministic {
663
- metadata,
664
- responses,
665
- } => Self::Deterministic(DeterministicDecisionRuntime::new(
666
- metadata.clone(),
667
- responses.clone(),
668
- )),
669
- DecisionTransportRuntime::Recording { metadata, result } => Self::Recording(
670
- RecordingDecisionRuntime::new(metadata.clone(), result.clone()),
671
- ),
672
- }
673
- }
674
-
675
- fn recorded_requests(&self) -> Vec<DecisionRequest> {
676
- match self {
677
- Self::Deterministic(_) => Vec::new(),
678
- Self::Recording(runtime) => runtime.recorded_requests(),
679
- }
680
- }
681
- }
682
-
683
- impl DecisionRuntime for TransportRuntimeHandle {
684
- fn metadata(&self) -> DecisionRuntimeMetadata {
685
- match self {
686
- Self::Deterministic(runtime) => runtime.metadata(),
687
- Self::Recording(runtime) => runtime.metadata(),
688
- }
689
- }
690
-
691
- fn decide(&self, request: &DecisionRequest) -> Result<DecisionResult, DecisionError> {
692
- match self {
693
- Self::Deterministic(runtime) => runtime.execute(request),
694
- Self::Recording(runtime) => runtime.execute(request),
695
- }
696
- }
697
- }
698
-
699
- fn digest(parts: &[String]) -> String {
700
- let mut hasher = Sha256::new();
701
- for part in parts {
702
- hasher.update(part.as_bytes());
703
- hasher.update([0x1f]);
704
- }
705
- format!("sha256:{:x}", hasher.finalize())
706
- }
707
-
708
- fn canonical_text(text: &str) -> String {
709
- text.split_whitespace().collect::<Vec<_>>().join(" ")
710
- }
711
-
712
- fn require_object(value: &Value, message: &str) -> Result<Map<String, Value>, DecisionError> {
713
- value
714
- .as_object()
715
- .cloned()
716
- .ok_or_else(|| DecisionError::new("DECISION_INVALID_RESULT", message))
717
- }
718
-
719
- fn canonical_values(values: &[String]) -> Result<Vec<String>, DecisionError> {
720
- if values.is_empty() {
721
- return Err(DecisionError::new(
722
- "DECISION_INVALID_OPTION",
723
- "closed-set decisions must declare at least one value",
724
- ));
725
- }
726
- let mut seen = BTreeSet::new();
727
- let mut normalized = Vec::with_capacity(values.len());
728
- for value in values {
729
- let canonical = canonical_text(value);
730
- if canonical.is_empty() {
731
- return Err(DecisionError::new(
732
- "DECISION_INVALID_OPTION",
733
- "closed-set decisions cannot declare an empty value",
734
- ));
735
- }
736
- if !seen.insert(canonical.clone()) {
737
- return Err(DecisionError::new(
738
- "DECISION_INVALID_OPTION",
739
- format!("closed-set decision declared \"{canonical}\" more than once"),
740
- ));
741
- }
742
- normalized.push(canonical);
743
- }
744
- Ok(normalized)
745
- }
746
-
747
- pub fn validate_decision_definition(
748
- input: &DecisionDefinition,
749
- ) -> Result<DecisionDefinition, DecisionError> {
750
- Ok(match input {
751
- DecisionDefinition::Binary { predicate } => {
752
- let predicate = canonical_text(predicate);
753
- if predicate.is_empty() {
754
- return Err(DecisionError::new(
755
- "DECISION_INVALID_RESULT",
756
- "binary decisions require a non-empty predicate",
757
- ));
758
- }
759
- DecisionDefinition::Binary { predicate }
760
- }
761
- DecisionDefinition::Choice { question, options } => DecisionDefinition::Choice {
762
- question: {
763
- let question = canonical_text(question);
764
- if question.is_empty() {
765
- return Err(DecisionError::new(
766
- "DECISION_INVALID_RESULT",
767
- "choice decisions require a non-empty question",
768
- ));
769
- }
770
- question
771
- },
772
- options: canonical_values(options)?,
773
- },
774
- DecisionDefinition::Score { question, levels } => DecisionDefinition::Score {
775
- question: {
776
- let question = canonical_text(question);
777
- if question.is_empty() {
778
- return Err(DecisionError::new(
779
- "DECISION_INVALID_RESULT",
780
- "score decisions require a non-empty question",
781
- ));
782
- }
783
- question
784
- },
785
- levels: canonical_values(levels)?,
786
- },
787
- })
788
- }
789
-
790
- fn distribution_by_values(
791
- values: &[String],
792
- observed: &[WeightedOption],
793
- ) -> Result<Vec<WeightedOption>, DecisionError> {
794
- let mut probabilities = BTreeMap::new();
795
- let mut total = 0.0;
796
- for entry in observed {
797
- if !values.contains(&entry.value) {
798
- return Err(DecisionError::new(
799
- "DECISION_INVALID_OPTION",
800
- format!("runtime returned undeclared option \"{}\"", entry.value),
801
- ));
802
- }
803
- if entry.probability.is_nan()
804
- || !entry.probability.is_finite()
805
- || entry.probability < 0.0
806
- || entry.probability > 1.0
807
- {
808
- return Err(DecisionError::new(
809
- "DECISION_INVALID_DISTRIBUTION",
810
- format!(
811
- "runtime returned an invalid probability for \"{}\"",
812
- entry.value
813
- ),
814
- ));
815
- }
816
- if probabilities
817
- .insert(entry.value.clone(), entry.probability)
818
- .is_some()
819
- {
820
- return Err(DecisionError::new(
821
- "DECISION_INVALID_DISTRIBUTION",
822
- format!(
823
- "runtime returned duplicate probability for \"{}\"",
824
- entry.value
825
- ),
826
- ));
827
- }
828
- total += entry.probability;
829
- }
830
- if probabilities.len() != values.len() {
831
- return Err(DecisionError::new(
832
- "DECISION_INVALID_DISTRIBUTION",
833
- "runtime did not return probabilities for every declared value",
834
- ));
835
- }
836
- if (total - 1.0).abs() > 1e-6 {
837
- return Err(DecisionError::new(
838
- "DECISION_INVALID_DISTRIBUTION",
839
- "runtime returned a distribution that does not sum to 1",
840
- ));
841
- }
842
- Ok(values
843
- .iter()
844
- .map(|value| WeightedOption {
845
- value: value.clone(),
846
- probability: *probabilities.get(value).unwrap_or(&0.0),
847
- })
848
- .collect())
849
- }
850
-
851
- pub fn validate_decision_result(
852
- request: &DecisionRequest,
853
- result: &DecisionResult,
854
- ) -> Result<DecisionResult, DecisionError> {
855
- let supporting_fields = result.supporting_fields().to_vec();
856
- let option_mass = result.option_mass();
857
- if option_mass.is_nan() || !option_mass.is_finite() || !(0.0..=1.0).contains(&option_mass) {
858
- return Err(DecisionError::new(
859
- "DECISION_INVALID_RESULT",
860
- "runtime returned an optionMass outside [0, 1]",
861
- ));
862
- }
863
-
864
- Ok(match (request, result) {
865
- (DecisionRequest::Binary { .. }, DecisionResult::Binary { decision, .. }) => {
866
- DecisionResult::Binary {
867
- decision: *decision,
868
- option_mass,
869
- supporting_fields,
870
- }
871
- }
872
- (
873
- DecisionRequest::Choice { options, .. },
874
- DecisionResult::Choice {
875
- selected,
876
- options: returned,
877
- ..
878
- },
879
- ) => {
880
- if !options.contains(selected) {
881
- return Err(DecisionError::new(
882
- "DECISION_INVALID_OPTION",
883
- format!("runtime selected undeclared option \"{selected}\""),
884
- ));
885
- }
886
- DecisionResult::Choice {
887
- selected: selected.clone(),
888
- options: distribution_by_values(options, returned)?,
889
- option_mass,
890
- supporting_fields,
891
- }
892
- }
893
- (
894
- DecisionRequest::Score { levels, .. },
895
- DecisionResult::Score {
896
- selected,
897
- levels: returned,
898
- ..
899
- },
900
- ) => {
901
- if !levels.contains(selected) {
902
- return Err(DecisionError::new(
903
- "DECISION_INVALID_OPTION",
904
- format!("runtime selected undeclared level \"{selected}\""),
905
- ));
906
- }
907
- DecisionResult::Score {
908
- selected: selected.clone(),
909
- levels: distribution_by_values(levels, returned)?,
910
- option_mass,
911
- supporting_fields,
912
- }
913
- }
914
- _ => {
915
- return Err(DecisionError::new(
916
- "DECISION_INVALID_RESULT",
917
- "runtime returned a result for the wrong decision kind",
918
- ))
919
- }
920
- })
921
- }
922
-
923
- fn selected_fields(record: &Map<String, Value>, fields: &Option<Vec<String>>) -> Vec<String> {
924
- let mut readable: Vec<String> = record
925
- .keys()
926
- .filter(|key| !matches!(key.as_str(), "id" | "__version"))
927
- .cloned()
928
- .collect();
929
- readable.sort();
930
- match fields {
931
- None => readable,
932
- Some(fields) => {
933
- let selected: BTreeSet<_> = fields.iter().cloned().collect();
934
- readable
935
- .into_iter()
936
- .filter(|field| selected.contains(field))
937
- .collect()
938
- }
939
- }
940
- }
941
-
942
- fn state_reference(application_id: Option<&str>, collection: &str, record_id: &str) -> String {
943
- match application_id {
944
- Some(application_id) if !application_id.is_empty() => {
945
- format!("flow://{application_id}/{collection}/{record_id}")
946
- }
947
- _ => format!("{collection}:{record_id}"),
948
- }
949
- }
950
-
951
- pub fn authorize_context(
952
- collection: &str,
953
- record_id: &str,
954
- record: &Value,
955
- options: &DecisionOptions,
956
- authorization: Option<(&dyn DecisionAuthorizationGate, &DecisionAuthorizationConfig)>,
957
- ) -> Result<DecisionContextPacket, DecisionError> {
958
- let object = record.as_object().ok_or_else(|| {
959
- DecisionError::new(
960
- "DECISION_INVALID_RESULT",
961
- "semantic decisions require object-shaped records",
962
- )
963
- })?;
964
- let record_version = object.get("__version").and_then(Value::as_u64);
965
- let selected = selected_fields(object, &options.fields);
966
- let deselected: BTreeSet<_> = object
967
- .keys()
968
- .filter(|field| !matches!(field.as_str(), "id" | "__version"))
969
- .filter(|field| !selected.contains(field))
970
- .cloned()
971
- .collect();
972
- let application_id = options.application_id.as_deref().or_else(|| {
973
- authorization
974
- .and_then(|(_, config)| config.principal.as_ref())
975
- .map(|principal| principal.application_id.as_str())
976
- });
977
- let mut authorized = Vec::new();
978
- let mut withheld: BTreeSet<String> = deselected;
979
- let mut decisions = Vec::new();
980
-
981
- for field in &selected {
982
- let allowed = if let Some((gate, config)) = authorization {
983
- let principal = config.principal.as_ref().ok_or_else(|| {
984
- DecisionError::new(
985
- "DECISION_INVALID_RESULT",
986
- "authorization config requires a principal",
987
- )
988
- })?;
989
- let uri = format!(
990
- "flow://{}/{}/{}/{}",
991
- principal.application_id, collection, record_id, field
992
- );
993
- let decision = gate.check(&AuthorizationRequest {
994
- subject: principal.subject.clone(),
995
- tenant_id: principal.tenant_id.clone(),
996
- application_id: principal.application_id.clone(),
997
- resource: crate::authorization::Resource {
998
- uri,
999
- attributes: BTreeMap::new(),
1000
- },
1001
- capability: principal.capability.clone(),
1002
- context: crate::authorization::AuthorizationContext::default(),
1003
- grant_id: None,
1004
- subject_attributes: BTreeMap::new(),
1005
- })?;
1006
- if let Some(decision_id) = decision.decision_id {
1007
- decisions.push(decision_id);
1008
- }
1009
- decision.allowed
1010
- } else {
1011
- true
1012
- };
1013
-
1014
- if allowed {
1015
- authorized.push(field.clone());
1016
- } else {
1017
- withheld.insert(field.clone());
1018
- }
1019
- }
1020
-
1021
- let mut values = BTreeMap::new();
1022
- for field in &authorized {
1023
- if let Some(value) = object.get(field) {
1024
- values.insert(field.clone(), value.clone());
1025
- }
1026
- }
1027
- let projection = json!({ "fields": authorized, "values": values });
1028
- let projection_json = serde_json::to_string(&projection)
1029
- .map_err(|error| DecisionError::new("DECISION_INVALID_RESULT", error.to_string()))?;
1030
- Ok(DecisionContextPacket {
1031
- collection: collection.into(),
1032
- record_id: record_id.into(),
1033
- record_version,
1034
- reference: state_reference(application_id, collection, record_id),
1035
- fields: projection["fields"]
1036
- .as_array()
1037
- .into_iter()
1038
- .flatten()
1039
- .filter_map(|value| value.as_str().map(str::to_string))
1040
- .collect(),
1041
- values,
1042
- withheld_fields: withheld.into_iter().collect(),
1043
- relevant_state_version: digest(&[
1044
- "semantic-decision-state-v1".into(),
1045
- collection.into(),
1046
- record_id.into(),
1047
- projection_json.clone(),
1048
- ]),
1049
- context_hash: digest(&["semantic-decision-context-v1".into(), projection_json]),
1050
- authorization_decisions: decisions,
1051
- })
1052
- }
1053
-
1054
- fn request_with_context(
1055
- definition: &DecisionDefinition,
1056
- context: &DecisionContextPacket,
1057
- ) -> DecisionRequest {
1058
- request_with_values(definition, &context.values)
1059
- }
1060
-
1061
- fn request_with_values(
1062
- definition: &DecisionDefinition,
1063
- values: &BTreeMap<String, Value>,
1064
- ) -> DecisionRequest {
1065
- let context_value = Value::Object(
1066
- values
1067
- .iter()
1068
- .map(|(field, value)| (field.clone(), value.clone()))
1069
- .collect(),
1070
- );
1071
- match definition {
1072
- DecisionDefinition::Binary { predicate } => DecisionRequest::Binary {
1073
- predicate: predicate.clone(),
1074
- context: context_value,
1075
- },
1076
- DecisionDefinition::Choice { question, options } => DecisionRequest::Choice {
1077
- question: question.clone(),
1078
- options: options.clone(),
1079
- context: context_value,
1080
- },
1081
- DecisionDefinition::Score { question, levels } => DecisionRequest::Score {
1082
- question: question.clone(),
1083
- levels: levels.clone(),
1084
- context: context_value,
1085
- },
1086
- }
1087
- }
1088
-
1089
- fn require_runtime_capability(
1090
- runtime: &dyn DecisionRuntime,
1091
- kind: &DecisionKind,
1092
- ) -> Result<(), DecisionError> {
1093
- let capabilities = runtime.capabilities();
1094
- let supported = match kind {
1095
- DecisionKind::Binary => capabilities.binary,
1096
- DecisionKind::Choice => capabilities.choice,
1097
- DecisionKind::Score => capabilities.score,
1098
- };
1099
- if supported {
1100
- Ok(())
1101
- } else {
1102
- Err(DecisionError::new(
1103
- "DECISION_UNSUPPORTED",
1104
- format!("runtime does not support {:?} decisions", kind),
1105
- ))
1106
- }
1107
- }
1108
-
1109
- fn decision_collection<'a>(options: &'a DecisionOptions) -> &'a str {
1110
- options
1111
- .decision_collection
1112
- .as_deref()
1113
- .unwrap_or(DEFAULT_DECISION_COLLECTION)
1114
- }
1115
-
1116
- fn schema_version(options: &DecisionOptions) -> &str {
1117
- options
1118
- .schema_version
1119
- .as_deref()
1120
- .unwrap_or("semantic-decision-v1")
1121
- }
1122
-
1123
- fn lineage_key(
1124
- collection: &str,
1125
- record_id: &str,
1126
- definition: &DecisionDefinition,
1127
- runtime: &DecisionRuntimeMetadata,
1128
- schema_version: &str,
1129
- ) -> String {
1130
- digest(&[
1131
- "semantic-decision-lineage-v1".into(),
1132
- format!("{:?}", definition.kind()),
1133
- collection.into(),
1134
- record_id.into(),
1135
- canonical_text(definition.prompt()),
1136
- serde_json::to_string(definition.schema_values()).unwrap_or_default(),
1137
- runtime.runtime.clone(),
1138
- runtime
1139
- .model_revision
1140
- .clone()
1141
- .or(runtime.model.clone())
1142
- .unwrap_or_default(),
1143
- runtime.prompt_revision.clone().unwrap_or_default(),
1144
- serde_json::to_string(&runtime.execution_method).unwrap_or_default(),
1145
- runtime.decision_schema_revision.clone().unwrap_or_default(),
1146
- schema_version.into(),
1147
- ])
1148
- }
1149
-
1150
- pub fn decision_cache_key(
1151
- collection: &str,
1152
- record_id: &str,
1153
- definition: &DecisionDefinition,
1154
- context: &DecisionContextPacket,
1155
- runtime: &DecisionRuntimeMetadata,
1156
- schema_version: &str,
1157
- ) -> String {
1158
- digest(&[
1159
- "semantic-decision-cache-v1".into(),
1160
- format!("{:?}", definition.kind()),
1161
- collection.into(),
1162
- record_id.into(),
1163
- canonical_text(definition.prompt()),
1164
- serde_json::to_string(definition.schema_values()).unwrap_or_default(),
1165
- context.relevant_state_version.clone(),
1166
- runtime.runtime.clone(),
1167
- runtime
1168
- .model_revision
1169
- .clone()
1170
- .or(runtime.model.clone())
1171
- .unwrap_or_default(),
1172
- runtime.prompt_revision.clone().unwrap_or_default(),
1173
- serde_json::to_string(&runtime.execution_method).unwrap_or_default(),
1174
- runtime.decision_schema_revision.clone().unwrap_or_default(),
1175
- schema_version.into(),
1176
- ])
1177
- }
1178
-
1179
- fn valid_supporting_fields(
1180
- context: &DecisionContextPacket,
1181
- result: &DecisionResult,
1182
- ) -> Vec<String> {
1183
- result
1184
- .supporting_fields()
1185
- .iter()
1186
- .filter(|field| context.fields.contains(field))
1187
- .cloned()
1188
- .collect()
1189
- }
1190
-
1191
- fn supporting_context(
1192
- context: &DecisionContextPacket,
1193
- supporting_fields: &[String],
1194
- ) -> Vec<DecisionEvidenceField> {
1195
- let fields = if supporting_fields.is_empty() {
1196
- &context.fields
1197
- } else {
1198
- supporting_fields
1199
- };
1200
- fields
1201
- .iter()
1202
- .filter_map(|field| {
1203
- context
1204
- .values
1205
- .get(field)
1206
- .map(|value| DecisionEvidenceField {
1207
- field: field.clone(),
1208
- value: value.clone(),
1209
- })
1210
- })
1211
- .collect()
1212
- }
1213
-
1214
- fn load_judgment(
1215
- db: &FeltDb,
1216
- collection: &str,
1217
- key: &str,
1218
- ) -> Result<Option<DecisionJudgmentRecord>, DecisionError> {
1219
- let storage_key = format!("{collection}:{key}");
1220
- db.capability(collection)
1221
- .get(&storage_key)
1222
- .map_err(|error| DecisionError::new("DECISION_STORAGE_ERROR", error.to_string()))
1223
- }
1224
-
1225
- fn update_judgment(
1226
- db: &FeltDb,
1227
- collection: &str,
1228
- key: &str,
1229
- record: &DecisionJudgmentRecord,
1230
- ) -> Result<(), DecisionError> {
1231
- let storage_key = format!("{collection}:{key}");
1232
- db.capability(collection)
1233
- .update(&storage_key, record.clone())
1234
- .map_err(|error| DecisionError::new("DECISION_STORAGE_ERROR", error.to_string()))
1235
- }
1236
-
1237
- fn insert_judgment(
1238
- db: &FeltDb,
1239
- collection: &str,
1240
- key: &str,
1241
- record: &DecisionJudgmentRecord,
1242
- ) -> Result<(), DecisionError> {
1243
- let storage_key = format!("{collection}:{key}");
1244
- db.capability(collection)
1245
- .insert_with_key(&storage_key, record.clone())
1246
- .map_err(|error| DecisionError::new("DECISION_STORAGE_ERROR", error.to_string()))
1247
- }
1248
-
1249
- fn supersede_lineage(
1250
- db: &FeltDb,
1251
- decision_collection: &str,
1252
- lineage_key: &str,
1253
- keep: &str,
1254
- ) -> Result<(), DecisionError> {
1255
- let stale: Vec<DecisionJudgmentRecord> = db
1256
- .capability(decision_collection)
1257
- .find(|row: &DecisionJudgmentRecord| {
1258
- row.lineage_key == lineage_key && row.status == DecisionStatus::Active && row.id != keep
1259
- })
1260
- .map_err(|error| DecisionError::new("DECISION_STORAGE_ERROR", error.to_string()))?;
1261
- let superseded_at = semantic_now_ms();
1262
- for mut row in stale {
1263
- row.status = DecisionStatus::Superseded;
1264
- row.superseded_at = Some(superseded_at);
1265
- update_judgment(db, decision_collection, &row.id, &row)?;
1266
- }
1267
- Ok(())
1268
- }
1269
-
1270
- fn stored_to_evaluation(
1271
- stored: DecisionJudgmentRecord,
1272
- context: &DecisionContextPacket,
1273
- source: &str,
1274
- ) -> DecisionEvaluation {
1275
- DecisionEvaluation {
1276
- result: stored.result.clone(),
1277
- cached: source == "cache",
1278
- evidence: vec![DecisionEvidence {
1279
- decision_id: stored.id.clone(),
1280
- kind: stored.kind.clone(),
1281
- input: stored.input.clone(),
1282
- result: stored.result.clone(),
1283
- supporting_context: stored
1284
- .supporting_fields
1285
- .iter()
1286
- .filter_map(|field| {
1287
- context
1288
- .values
1289
- .get(field)
1290
- .map(|value| DecisionEvidenceField {
1291
- field: field.clone(),
1292
- value: value.clone(),
1293
- })
1294
- })
1295
- .collect(),
1296
- runtime: DecisionRuntimeMetadata {
1297
- runtime: stored.runtime.clone(),
1298
- model: stored.model.clone(),
1299
- model_revision: stored.model_revision.clone(),
1300
- prompt_revision: stored.prompt_revision.clone(),
1301
- execution_method: stored.execution_method.clone(),
1302
- decision_schema_revision: stored.decision_schema_revision.clone(),
1303
- },
1304
- state: stored.state.clone(),
1305
- }],
1306
- provenance: DecisionProvenance {
1307
- runtime: stored.runtime,
1308
- model: stored.model,
1309
- model_revision: stored.model_revision,
1310
- prompt_revision: stored.prompt_revision,
1311
- execution_method: stored.execution_method,
1312
- decision_schema_revision: stored.decision_schema_revision,
1313
- context_hash: context.context_hash.clone(),
1314
- relevant_state_version: context.relevant_state_version.clone(),
1315
- cache_key: stored.id,
1316
- source: source.into(),
1317
- evaluated_at: stored.evaluated_at,
1318
- served_at: semantic_now_ms(),
1319
- },
1320
- }
1321
- }
1322
-
1323
- /// The stored judgment whose identity matches `cache_key`, if one exists.
1324
- ///
1325
- /// A judgment superseded by an intermediate state change that has since been
1326
- /// reversed carries the same identity as the state it was derived from, so it
1327
- /// is reactivated rather than re-derived: the lineage's other judgments are
1328
- /// superseded and this one becomes active again.
1329
- fn reusable_judgment(
1330
- db: &FeltDb,
1331
- storage: &str,
1332
- cache_key: &str,
1333
- lineage: &str,
1334
- ) -> Result<Option<DecisionJudgmentRecord>, DecisionError> {
1335
- let Some(mut stored) = load_judgment(db, storage, cache_key)? else {
1336
- return Ok(None);
1337
- };
1338
- if stored.status != DecisionStatus::Active {
1339
- supersede_lineage(db, storage, lineage, &stored.id)?;
1340
- stored.status = DecisionStatus::Active;
1341
- stored.superseded_at = None;
1342
- update_judgment(db, storage, &stored.id, &stored)?;
1343
- }
1344
- Ok(Some(stored))
1345
- }
1346
-
1347
- /// Reuse a durable judgment for a record without invoking the runtime.
1348
- ///
1349
- /// This is the `reuse-only` path of semantic query composition: the record is
1350
- /// read and authorized exactly as `decide_record` reads and authorizes it, the
1351
- /// judgment identity is derived from that authorized state and the runtime's
1352
- /// metadata, and the stored judgment is returned when one with that identity
1353
- /// exists. `Ok(None)` means no reusable judgment exists; nothing is executed
1354
- /// and nothing is written except the reactivation described on
1355
- /// `reusable_judgment`.
1356
- pub fn reuse_record_judgment(
1357
- db: &FeltDb,
1358
- target: &DecisionRecordRef,
1359
- definition: &DecisionDefinition,
1360
- runtime_metadata: &DecisionRuntimeMetadata,
1361
- options: &DecisionOptions,
1362
- authorization: Option<(&dyn DecisionAuthorizationGate, &DecisionAuthorizationConfig)>,
1363
- ) -> Result<Option<DecisionEvaluation>, DecisionError> {
1364
- let definition = validate_decision_definition(definition)?;
1365
- let record: Value = db
1366
- .capability(&target.collection)
1367
- .get(&target.record_id)
1368
- .map_err(|error| DecisionError::new("DECISION_STORAGE_ERROR", error.to_string()))?
1369
- .ok_or_else(|| {
1370
- DecisionError::new(
1371
- "DECISION_RECORD_NOT_FOUND",
1372
- "decision target record not found",
1373
- )
1374
- })?;
1375
- let context = authorize_context(
1376
- &target.collection,
1377
- &target.record_id,
1378
- &record,
1379
- options,
1380
- authorization,
1381
- )?;
1382
- if context.fields.is_empty() {
1383
- return Err(DecisionError::new(
1384
- "DECISION_CONTEXT_EMPTY",
1385
- "decision refuses to evaluate an empty authorized context",
1386
- ));
1387
- }
1388
- let cache_key = decision_cache_key(
1389
- &target.collection,
1390
- &target.record_id,
1391
- &definition,
1392
- &context,
1393
- runtime_metadata,
1394
- schema_version(options),
1395
- );
1396
- let lineage = lineage_key(
1397
- &target.collection,
1398
- &target.record_id,
1399
- &definition,
1400
- runtime_metadata,
1401
- schema_version(options),
1402
- );
1403
- Ok(
1404
- reusable_judgment(db, decision_collection(options), &cache_key, &lineage)?
1405
- .map(|stored| stored_to_evaluation(stored, &context, "cache")),
1406
- )
1407
- }
1408
-
1409
- /// Build the runtime binding a FeltDB deployment configures for semantic
1410
- /// query composition.
1411
- ///
1412
- /// A query never names its runtime; the deployment does, and the binding's
1413
- /// identity becomes part of every judgment the query stage produces. Only the
1414
- /// deterministic runtime exists in Rust today, so this is the only binding a
1415
- /// deployment can configure; the recording runtime is a transport test double
1416
- /// that answers every request identically and is refused here.
1417
- pub fn configured_decision_runtime(
1418
- runtime: &DecisionTransportRuntime,
1419
- ) -> Result<Arc<dyn DecisionRuntime + Send + Sync>, DecisionError> {
1420
- match runtime {
1421
- DecisionTransportRuntime::Deterministic {
1422
- metadata,
1423
- responses,
1424
- } => Ok(Arc::new(DeterministicDecisionRuntime::new(
1425
- metadata.clone(),
1426
- responses.clone(),
1427
- ))),
1428
- DecisionTransportRuntime::Recording { .. } => Err(DecisionError::new(
1429
- "DECISION_UNSUPPORTED",
1430
- "the recording runtime is a transport test double, not a configurable runtime binding",
1431
- )),
1432
- }
1433
- }
1434
-
1435
- pub fn decide_record(
1436
- db: &FeltDb,
1437
- target: &DecisionRecordRef,
1438
- definition: &DecisionDefinition,
1439
- runtime: &dyn DecisionRuntime,
1440
- options: &DecisionOptions,
1441
- authorization: Option<(&dyn DecisionAuthorizationGate, &DecisionAuthorizationConfig)>,
1442
- ) -> Result<DecisionEvaluation, DecisionError> {
1443
- let definition = validate_decision_definition(definition)?;
1444
- require_runtime_capability(runtime, &definition.kind())?;
1445
- let record: Value = db
1446
- .capability(&target.collection)
1447
- .get(&target.record_id)
1448
- .map_err(|error| DecisionError::new("DECISION_STORAGE_ERROR", error.to_string()))?
1449
- .ok_or_else(|| {
1450
- DecisionError::new(
1451
- "DECISION_RECORD_NOT_FOUND",
1452
- "decision target record not found",
1453
- )
1454
- })?;
1455
- let context = authorize_context(
1456
- &target.collection,
1457
- &target.record_id,
1458
- &record,
1459
- options,
1460
- authorization,
1461
- )?;
1462
- if context.fields.is_empty() {
1463
- return Err(DecisionError::new(
1464
- "DECISION_CONTEXT_EMPTY",
1465
- "decision refuses to evaluate an empty authorized context",
1466
- ));
1467
- }
1468
- let runtime_metadata = runtime.metadata();
1469
- let cache_key = decision_cache_key(
1470
- &target.collection,
1471
- &target.record_id,
1472
- &definition,
1473
- &context,
1474
- &runtime_metadata,
1475
- schema_version(options),
1476
- );
1477
- let lineage = lineage_key(
1478
- &target.collection,
1479
- &target.record_id,
1480
- &definition,
1481
- &runtime_metadata,
1482
- schema_version(options),
1483
- );
1484
- let storage = decision_collection(options);
1485
- if let Some(stored) = reusable_judgment(db, storage, &cache_key, &lineage)? {
1486
- return Ok(stored_to_evaluation(stored, &context, "cache"));
1487
- }
1488
-
1489
- let request = request_with_context(&definition, &context);
1490
- let result = validate_decision_result(&request, &runtime.execute(&request)?)?;
1491
- let evaluated_at = semantic_now_ms();
1492
- let supporting_fields = valid_supporting_fields(&context, &result);
1493
- let supporting_context = supporting_context(&context, &supporting_fields);
1494
- let state = DecisionStateReference {
1495
- collection: target.collection.clone(),
1496
- record_id: target.record_id.clone(),
1497
- record_version: context.record_version,
1498
- reference: context.reference.clone(),
1499
- relevant_state_version: context.relevant_state_version.clone(),
1500
- withheld_fields: context.withheld_fields.clone(),
1501
- };
1502
- let evidence = DecisionEvidence {
1503
- decision_id: cache_key.clone(),
1504
- kind: definition.kind(),
1505
- input: definition.clone(),
1506
- result: result.clone(),
1507
- supporting_context,
1508
- runtime: runtime_metadata.clone(),
1509
- state: state.clone(),
1510
- };
1511
- let record = DecisionJudgmentRecord {
1512
- id: cache_key.clone(),
1513
- lineage_key: lineage.clone(),
1514
- kind: definition.kind(),
1515
- collection: target.collection.clone(),
1516
- record_id: target.record_id.clone(),
1517
- input: definition.clone(),
1518
- schema_version: schema_version(options).into(),
1519
- relevant_state_version: context.relevant_state_version.clone(),
1520
- runtime: runtime_metadata.runtime.clone(),
1521
- model: runtime_metadata.model.clone(),
1522
- model_revision: runtime_metadata.model_revision.clone(),
1523
- prompt_revision: runtime_metadata.prompt_revision.clone(),
1524
- execution_method: runtime_metadata.execution_method.clone(),
1525
- decision_schema_revision: runtime_metadata.decision_schema_revision.clone(),
1526
- option_mass: result.option_mass(),
1527
- context_fields: context.fields.clone(),
1528
- supporting_fields: if result.supporting_fields().is_empty() {
1529
- context.fields.clone()
1530
- } else {
1531
- supporting_fields.clone()
1532
- },
1533
- status: DecisionStatus::Active,
1534
- state,
1535
- evidence: evidence.clone(),
1536
- result: result.clone(),
1537
- evaluated_at,
1538
- superseded_at: None,
1539
- };
1540
- supersede_lineage(db, storage, &lineage, &cache_key)?;
1541
- insert_judgment(db, storage, &cache_key, &record)?;
1542
- Ok(DecisionEvaluation {
1543
- result,
1544
- cached: false,
1545
- evidence: vec![evidence],
1546
- provenance: DecisionProvenance {
1547
- runtime: runtime_metadata.runtime,
1548
- model: runtime_metadata.model,
1549
- model_revision: runtime_metadata.model_revision,
1550
- prompt_revision: runtime_metadata.prompt_revision,
1551
- execution_method: runtime_metadata.execution_method,
1552
- decision_schema_revision: runtime_metadata.decision_schema_revision,
1553
- context_hash: context.context_hash,
1554
- relevant_state_version: context.relevant_state_version,
1555
- cache_key,
1556
- source: "runtime".into(),
1557
- evaluated_at,
1558
- served_at: evaluated_at,
1559
- },
1560
- })
1561
- }
1562
-
1563
- fn evaluate_context_packet(
1564
- target: DecisionRecordRef,
1565
- definition: &DecisionDefinition,
1566
- context: &DecisionContextPacket,
1567
- runtime: &dyn DecisionRuntime,
1568
- options: &DecisionOptions,
1569
- ) -> Result<DecisionEvaluation, DecisionError> {
1570
- let definition = validate_decision_definition(definition)?;
1571
- require_runtime_capability(runtime, &definition.kind())?;
1572
- if context.fields.is_empty() {
1573
- return Err(DecisionError::new(
1574
- "DECISION_CONTEXT_EMPTY",
1575
- "decision refuses to evaluate an empty authorized context",
1576
- ));
1577
- }
1578
- let runtime_metadata = runtime.metadata();
1579
- let cache_key = decision_cache_key(
1580
- &target.collection,
1581
- &target.record_id,
1582
- &definition,
1583
- context,
1584
- &runtime_metadata,
1585
- schema_version(options),
1586
- );
1587
- let request = request_with_context(&definition, context);
1588
- let result = validate_decision_result(&request, &runtime.execute(&request)?)?;
1589
- let evaluated_at = semantic_now_ms();
1590
- let supporting_fields = valid_supporting_fields(context, &result);
1591
- let state = DecisionStateReference {
1592
- collection: target.collection,
1593
- record_id: target.record_id,
1594
- record_version: context.record_version,
1595
- reference: context.reference.clone(),
1596
- relevant_state_version: context.relevant_state_version.clone(),
1597
- withheld_fields: context.withheld_fields.clone(),
1598
- };
1599
- let evidence = DecisionEvidence {
1600
- decision_id: cache_key.clone(),
1601
- kind: definition.kind(),
1602
- input: definition,
1603
- result: result.clone(),
1604
- supporting_context: supporting_context(context, &supporting_fields),
1605
- runtime: runtime_metadata.clone(),
1606
- state,
1607
- };
1608
- Ok(DecisionEvaluation {
1609
- result,
1610
- cached: false,
1611
- evidence: vec![evidence],
1612
- provenance: DecisionProvenance {
1613
- runtime: runtime_metadata.runtime,
1614
- model: runtime_metadata.model,
1615
- model_revision: runtime_metadata.model_revision,
1616
- prompt_revision: runtime_metadata.prompt_revision,
1617
- execution_method: runtime_metadata.execution_method,
1618
- decision_schema_revision: runtime_metadata.decision_schema_revision,
1619
- context_hash: context.context_hash.clone(),
1620
- relevant_state_version: context.relevant_state_version.clone(),
1621
- cache_key,
1622
- source: "runtime".into(),
1623
- evaluated_at,
1624
- served_at: evaluated_at,
1625
- },
1626
- })
1627
- }
1628
-
1629
- fn inline_context_packet(
1630
- request: &DecisionTransportExecutionRequest,
1631
- authorization: Option<(&dyn DecisionAuthorizationGate, &DecisionAuthorizationConfig)>,
1632
- ) -> Result<DecisionContextPacket, DecisionError> {
1633
- let context = request.context.as_ref().ok_or_else(|| {
1634
- DecisionError::new(
1635
- "DECISION_INVALID_RESULT",
1636
- "inline decision transport requires a context object",
1637
- )
1638
- })?;
1639
- authorize_context(
1640
- "$context",
1641
- "inline",
1642
- context,
1643
- &request.options,
1644
- authorization,
1645
- )
1646
- }
1647
-
1648
- pub fn execute_inline_decision_transport(
1649
- request: &DecisionTransportExecutionRequest,
1650
- ) -> Result<DecisionTransportExecutionResponse, DecisionError> {
1651
- let runtime = TransportRuntimeHandle::new(&request.runtime);
1652
- let authorization_gate = request.authorization.as_ref().map(|authorization| {
1653
- (
1654
- TransportAuthorizationGate::new(&authorization.allowed_fields),
1655
- DecisionAuthorizationConfig {
1656
- principal: authorization.principal.clone(),
1657
- },
1658
- )
1659
- });
1660
- let authorization = authorization_gate
1661
- .as_ref()
1662
- .map(|(gate, config)| (gate as &dyn DecisionAuthorizationGate, config));
1663
- if request.target.is_some() {
1664
- return Err(DecisionError::new(
1665
- "DECISION_INVALID_RESULT",
1666
- "inline decision transport does not support durable target records",
1667
- ));
1668
- }
1669
- let evaluation = evaluate_context_packet(
1670
- DecisionRecordRef {
1671
- collection: "$context".into(),
1672
- record_id: "inline".into(),
1673
- },
1674
- &request.definition,
1675
- &inline_context_packet(request, authorization)?,
1676
- &runtime,
1677
- &request.options,
1678
- )?;
1679
- Ok(DecisionTransportExecutionResponse {
1680
- evaluation,
1681
- runtime_requests: runtime.recorded_requests(),
1682
- })
1683
- }
1684
-
1685
- pub fn execute_decision_transport(
1686
- db: &FeltDb,
1687
- request: &DecisionTransportExecutionRequest,
1688
- ) -> Result<DecisionTransportExecutionResponse, DecisionError> {
1689
- let runtime = TransportRuntimeHandle::new(&request.runtime);
1690
- let authorization_gate = request.authorization.as_ref().map(|authorization| {
1691
- (
1692
- TransportAuthorizationGate {
1693
- allowed_fields: authorization.allowed_fields.iter().cloned().collect(),
1694
- },
1695
- DecisionAuthorizationConfig {
1696
- principal: authorization.principal.clone(),
1697
- },
1698
- )
1699
- });
1700
- let authorization = authorization_gate
1701
- .as_ref()
1702
- .map(|(gate, config)| (gate as &dyn DecisionAuthorizationGate, config));
1703
- let evaluation = match (&request.target, &request.context) {
1704
- (Some(_), Some(_)) => return Err(DecisionError::new(
1705
- "DECISION_INVALID_RESULT",
1706
- "decision transport accepts either a target record or an inline context, but not both",
1707
- )),
1708
- (Some(target), None) => decide_record(
1709
- db,
1710
- target,
1711
- &request.definition,
1712
- &runtime,
1713
- &request.options,
1714
- authorization,
1715
- )?,
1716
- (None, Some(context)) => evaluate_context_packet(
1717
- DecisionRecordRef {
1718
- collection: "$context".into(),
1719
- record_id: "inline".into(),
1720
- },
1721
- &request.definition,
1722
- &authorize_context(
1723
- "$context",
1724
- "inline",
1725
- context,
1726
- &request.options,
1727
- authorization,
1728
- )?,
1729
- &runtime,
1730
- &request.options,
1731
- )?,
1732
- (None, None) => {
1733
- return Err(DecisionError::new(
1734
- "DECISION_INVALID_RESULT",
1735
- "decision transport requires either a target record or an inline context",
1736
- ))
1737
- }
1738
- };
1739
- Ok(DecisionTransportExecutionResponse {
1740
- evaluation,
1741
- runtime_requests: runtime.recorded_requests(),
1742
- })
1743
- }
1744
-
1745
- pub fn execute_decision_batch(
1746
- context: &DecisionContextPacket,
1747
- batch: &DecisionBatch,
1748
- runtime: &dyn DecisionRuntime,
1749
- ) -> Result<Vec<DecisionBatchEvaluation>, DecisionError> {
1750
- if batch.decisions.is_empty() {
1751
- return Err(DecisionError::new(
1752
- "DECISION_INVALID_RESULT",
1753
- "decision batches require at least one decision",
1754
- ));
1755
- }
1756
- if !runtime.capabilities().batch {
1757
- return Err(DecisionError::new(
1758
- "DECISION_UNSUPPORTED",
1759
- "runtime does not support decision batches",
1760
- ));
1761
- }
1762
- let mut ids = BTreeSet::new();
1763
- let shared = require_object(
1764
- &batch.context,
1765
- "decision batch shared context must be object-shaped",
1766
- )?;
1767
- let mut base = context.values.clone();
1768
- for (field, value) in shared {
1769
- base.insert(field, value);
1770
- }
1771
- let mut requests = Vec::with_capacity(batch.decisions.len());
1772
- let mut ids_in_order = Vec::with_capacity(batch.decisions.len());
1773
- for decision in &batch.decisions {
1774
- if canonical_text(&decision.id).is_empty() || !ids.insert(decision.id.clone()) {
1775
- return Err(DecisionError::new(
1776
- "DECISION_INVALID_RESULT",
1777
- "decision batches require unique non-empty decision ids",
1778
- ));
1779
- }
1780
- let local = require_object(
1781
- &decision.context,
1782
- "decision batch decision context must be object-shaped",
1783
- )?;
1784
- let definition = validate_decision_definition(&decision.definition)?;
1785
- let mut merged = base.clone();
1786
- for (field, value) in local {
1787
- merged.insert(field, value);
1788
- }
1789
- let request = request_with_values(&definition, &merged);
1790
- ids_in_order.push(decision.id.clone());
1791
- requests.push(request);
1792
- }
1793
- let results = runtime.execute_batch(&requests)?;
1794
- if results.len() != requests.len() {
1795
- return Err(DecisionError::new(
1796
- "DECISION_INVALID_RESULT",
1797
- "runtime returned a batch result count different from the request count",
1798
- ));
1799
- }
1800
- let mut evaluations = Vec::with_capacity(results.len());
1801
- for ((request, result), id) in requests.into_iter().zip(results).zip(ids_in_order) {
1802
- let result = validate_decision_result(&request, &result)?;
1803
- evaluations.push(DecisionBatchEvaluation { id, result });
1804
- }
1805
- Ok(evaluations)
1806
- }
1807
-
1808
- fn permutations(values: &[String]) -> Vec<Vec<String>> {
1809
- if values.len() <= 1 {
1810
- return vec![values.to_vec()];
1811
- }
1812
- let mut result = Vec::new();
1813
- for index in 0..values.len() {
1814
- let mut remaining = values.to_vec();
1815
- let head = remaining.remove(index);
1816
- for mut tail in permutations(&remaining) {
1817
- let mut permutation = vec![head.clone()];
1818
- permutation.append(&mut tail);
1819
- result.push(permutation);
1820
- }
1821
- }
1822
- result
1823
- }
1824
-
1825
- fn observe_choice(
1826
- context: &DecisionContextPacket,
1827
- question: &str,
1828
- options: Vec<String>,
1829
- runtime: &dyn DecisionRuntime,
1830
- ) -> Result<ChoicePermutationObservation, DecisionError> {
1831
- let definition = validate_decision_definition(&DecisionDefinition::Choice {
1832
- question: question.into(),
1833
- options: options.clone(),
1834
- })?;
1835
- let request = request_with_context(&definition, context);
1836
- let result = validate_decision_result(&request, &runtime.execute(&request)?)?;
1837
- match result {
1838
- DecisionResult::Choice {
1839
- selected,
1840
- options: distribution,
1841
- ..
1842
- } => {
1843
- let rank = distribution
1844
- .iter()
1845
- .enumerate()
1846
- .find_map(|(index, option)| (option.value == selected).then_some(index + 1))
1847
- .unwrap_or(distribution.len());
1848
- let probability = distribution
1849
- .iter()
1850
- .find_map(|option| (option.value == selected).then_some(option.probability))
1851
- .unwrap_or(0.0);
1852
- Ok(ChoicePermutationObservation {
1853
- order: options,
1854
- selected,
1855
- distribution,
1856
- rank,
1857
- probability,
1858
- })
1859
- }
1860
- _ => Err(DecisionError::new(
1861
- "DECISION_INVALID_RESULT",
1862
- "choice diagnostics require a choice decision",
1863
- )),
1864
- }
1865
- }
1866
-
1867
- pub fn run_choice_diagnostics(
1868
- context: &DecisionContextPacket,
1869
- question: &str,
1870
- options: &[String],
1871
- expansion_option: Option<&str>,
1872
- runtime: &dyn DecisionRuntime,
1873
- ) -> Result<ChoiceDiagnosticsReport, DecisionError> {
1874
- let canonical = validate_decision_definition(&DecisionDefinition::Choice {
1875
- question: question.into(),
1876
- options: options.to_vec(),
1877
- })?;
1878
- let base_options = canonical.schema_values().to_vec();
1879
- let mut permutations_observed = Vec::new();
1880
- for order in permutations(&base_options) {
1881
- permutations_observed.push(observe_choice(context, question, order, runtime)?);
1882
- }
1883
- let expansion = match expansion_option {
1884
- Some(option) => {
1885
- let baseline = observe_choice(context, question, base_options.clone(), runtime)?;
1886
- let mut expanded = base_options.clone();
1887
- expanded.push(option.into());
1888
- Some(ChoiceExpansionObservation {
1889
- baseline,
1890
- expanded: observe_choice(context, question, expanded, runtime)?,
1891
- })
1892
- }
1893
- None => None,
1894
- };
1895
- Ok(ChoiceDiagnosticsReport {
1896
- permutations: permutations_observed,
1897
- expansion,
1898
- })
1899
- }
1900
-
1901
- pub fn jev(
1902
- db: &FeltDb,
1903
- target: &DecisionRecordRef,
1904
- predicate: &str,
1905
- runtime: &dyn DecisionRuntime,
1906
- options: &DecisionOptions,
1907
- authorization: Option<(&dyn DecisionAuthorizationGate, &DecisionAuthorizationConfig)>,
1908
- ) -> Result<DecisionEvaluation, DecisionError> {
1909
- decide_record(
1910
- db,
1911
- target,
1912
- &DecisionDefinition::Binary {
1913
- predicate: predicate.into(),
1914
- },
1915
- runtime,
1916
- options,
1917
- authorization,
1918
- )
1919
- }
1920
-
1921
- #[derive(Debug, Clone)]
1922
- pub struct DeterministicDecisionRuntime {
1923
- metadata: DecisionRuntimeMetadata,
1924
- responses: BTreeMap<String, DecisionResult>,
1925
- }
1926
-
1927
- impl DeterministicDecisionRuntime {
1928
- pub fn new(
1929
- metadata: DecisionRuntimeMetadata,
1930
- responses: BTreeMap<String, DecisionResult>,
1931
- ) -> Self {
1932
- Self {
1933
- metadata,
1934
- responses,
1935
- }
1936
- }
1937
- }
1938
-
1939
- impl DecisionRuntime for DeterministicDecisionRuntime {
1940
- fn metadata(&self) -> DecisionRuntimeMetadata {
1941
- self.metadata.clone()
1942
- }
1943
-
1944
- fn decide(&self, request: &DecisionRequest) -> Result<DecisionResult, DecisionError> {
1945
- let key = serde_json::to_string(request)
1946
- .map_err(|error| DecisionError::new("DECISION_INVALID_RESULT", error.to_string()))?;
1947
- self.responses.get(&key).cloned().ok_or_else(|| {
1948
- DecisionError::new(
1949
- "DECISION_INVALID_RESULT",
1950
- "deterministic runtime has no configured response for this request",
1951
- )
1952
- })
1953
- }
1954
- }
1955
-
1956
- pub fn deterministic_request_key(request: &DecisionRequest) -> Result<String, DecisionError> {
1957
- serde_json::to_string(request)
1958
- .map_err(|error| DecisionError::new("DECISION_INVALID_RESULT", error.to_string()))
1959
- }
1960
-
1961
- pub fn run_decision_benchmark(
1962
- db: &FeltDb,
1963
- runtime: &dyn DecisionRuntime,
1964
- options: &DecisionOptions,
1965
- ) -> Result<DecisionBenchmarkReport, DecisionError> {
1966
- let collection = db.capability("benchmark_people");
1967
- for index in 0..20 {
1968
- collection
1969
- .insert_with_key(
1970
- &format!("b-{index}"),
1971
- json!({
1972
- "id": format!("b-{index}"),
1973
- "__version": 1,
1974
- "employmentType": if index % 2 == 0 { "remote" } else { "office" },
1975
- "publicRole": if index % 3 == 0 { "Staff Backend Engineer" } else { "Lab Technician" },
1976
- "urgency": if index % 3 == 0 { "high" } else if index % 3 == 1 { "medium" } else { "low" },
1977
- "notes": if index % 2 == 0 { "Remote-first and distributed systems ownership." } else { "On-site inventory and lab work." }
1978
- }),
1979
- )
1980
- .map_err(|error| DecisionError::new("DECISION_STORAGE_ERROR", error.to_string()))?;
1981
- }
1982
- let mut first = 0usize;
1983
- let mut second_cache_hits = 0usize;
1984
- let mut second_runtime = 0usize;
1985
- for index in 0..20 {
1986
- let target = DecisionRecordRef {
1987
- collection: "benchmark_people".into(),
1988
- record_id: format!("b-{index}"),
1989
- };
1990
- for definition in [
1991
- DecisionDefinition::Binary {
1992
- predicate: "could work from home".into(),
1993
- },
1994
- DecisionDefinition::Choice {
1995
- question: "Which reviewer should handle this?".into(),
1996
- options: vec!["approve".into(), "reject".into(), "review".into()],
1997
- },
1998
- DecisionDefinition::Score {
1999
- question: "What is the urgency?".into(),
2000
- levels: vec!["low".into(), "medium".into(), "high".into()],
2001
- },
2002
- ] {
2003
- let first_eval = decide_record(db, &target, &definition, runtime, options, None)?;
2004
- if !first_eval.cached {
2005
- first += 1;
2006
- }
2007
- let second_eval = decide_record(db, &target, &definition, runtime, options, None)?;
2008
- if second_eval.cached {
2009
- second_cache_hits += 1;
2010
- } else {
2011
- second_runtime += 1;
2012
- }
2013
- }
2014
- }
2015
- Ok(DecisionBenchmarkReport {
2016
- binary: 20,
2017
- choice: 20,
2018
- score: 20,
2019
- runtime_invocations_first_pass: first,
2020
- runtime_invocations_second_pass: second_runtime,
2021
- cache_hits_second_pass: second_cache_hits,
2022
- })
2023
- }
2024
-
2025
- impl From<DecisionError> for FlowError {
2026
- fn from(error: DecisionError) -> Self {
2027
- FlowError::CapabilityError(format!("{}: {}", error.code, error.message))
2028
- }
2029
- }