@feltdb/core 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. package/dist/agent-state.d.ts +20 -0
  2. package/dist/agent-state.d.ts.map +1 -1
  3. package/dist/agent-state.js +36 -0
  4. package/dist/build-identity.json +12 -4
  5. package/dist/cli/commands.js +187 -4
  6. package/dist/cli/publish-engine.js +116 -16
  7. package/dist/collection.d.ts.map +1 -1
  8. package/dist/collection.js +3 -0
  9. package/dist/create/build-identity.json +12 -4
  10. package/dist/create/managed-account.js +93 -35
  11. package/dist/create/package-versions.js +1 -1
  12. package/dist/create/server-source/Cargo.lock +6 -509
  13. package/dist/create/server-source/Cargo.toml +2 -13
  14. package/dist/create/server-source/crates/feltdb-client/Cargo.toml +1 -1
  15. package/dist/create/server-source/crates/feltdb-server/Cargo.toml +1 -1
  16. package/dist/create/server-source/crates/feltdb-server/src/main.rs +255 -12
  17. package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +1 -1
  18. package/dist/db.d.ts +11 -1
  19. package/dist/db.d.ts.map +1 -1
  20. package/dist/db.js +15 -2
  21. package/dist/error-codes.d.ts +5 -0
  22. package/dist/error-codes.d.ts.map +1 -1
  23. package/dist/error-codes.js +9 -1
  24. package/dist/http-db.d.ts +16 -0
  25. package/dist/http-db.d.ts.map +1 -1
  26. package/dist/http-db.js +36 -6
  27. package/dist/managed-recovery.d.ts +6 -0
  28. package/dist/managed-recovery.d.ts.map +1 -1
  29. package/dist/managed-recovery.js +9 -0
  30. package/dist/studio-app/assets/{feltdb_wasm-BjM1ylL3.js → feltdb_wasm-DtybUY0q.js} +1 -1
  31. package/dist/studio-app/assets/feltdb_wasm_bg-DsgIUwaB.wasm +0 -0
  32. package/dist/studio-app/assets/index-C4OffsZl.js +37 -0
  33. package/dist/studio-app/index.html +1 -1
  34. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  35. package/dist/wasm/package.json +1 -1
  36. package/dist/workload.d.ts +55 -3
  37. package/dist/workload.d.ts.map +1 -1
  38. package/dist/workload.js +23 -0
  39. package/dist/workspace/workspace-connection.d.ts.map +1 -1
  40. package/dist/workspace/workspace-connection.js +14 -3
  41. package/package.json +1 -1
  42. package/dist/create/server-source/crates/feltdb/Cargo.lock +0 -175
  43. package/dist/create/server-source/crates/feltdb/Cargo.toml +0 -102
  44. package/dist/create/server-source/crates/feltdb/LICENSE +0 -21
  45. package/dist/create/server-source/crates/feltdb/README.md +0 -75
  46. package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +0 -370
  47. package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +0 -589
  48. package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +0 -247
  49. package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +0 -440
  50. package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +0 -275
  51. package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +0 -381
  52. package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +0 -467
  53. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +0 -489
  54. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +0 -281
  55. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +0 -606
  56. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +0 -531
  57. package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +0 -462
  58. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +0 -505
  59. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +0 -484
  60. package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +0 -758
  61. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +0 -421
  62. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +0 -329
  63. package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +0 -1705
  64. package/dist/create/server-source/crates/feltdb/src/acquisition.rs +0 -286
  65. package/dist/create/server-source/crates/feltdb/src/admission.rs +0 -185
  66. package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +0 -507
  67. package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +0 -539
  68. package/dist/create/server-source/crates/feltdb/src/agent_state.rs +0 -320
  69. package/dist/create/server-source/crates/feltdb/src/agent_transition_contract.rs +0 -1545
  70. package/dist/create/server-source/crates/feltdb/src/analytics.rs +0 -521
  71. package/dist/create/server-source/crates/feltdb/src/application.rs +0 -2674
  72. package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +0 -1070
  73. package/dist/create/server-source/crates/feltdb/src/authority.rs +0 -479
  74. package/dist/create/server-source/crates/feltdb/src/authority_failover.rs +0 -847
  75. package/dist/create/server-source/crates/feltdb/src/authorization.rs +0 -1030
  76. package/dist/create/server-source/crates/feltdb/src/authorization_security_tests.rs +0 -1122
  77. package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority-client.rs +0 -32
  78. package/dist/create/server-source/crates/feltdb/src/bin/feltdb-authority.rs +0 -19
  79. package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +0 -1474
  80. package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +0 -8
  81. package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +0 -418
  82. package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +0 -482
  83. package/dist/create/server-source/crates/feltdb/src/capability.rs +0 -903
  84. package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +0 -263
  85. package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +0 -77
  86. package/dist/create/server-source/crates/feltdb/src/causal_backlog_bound.rs +0 -496
  87. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +0 -2574
  88. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +0 -212
  89. package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +0 -422
  90. package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +0 -445
  91. package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +0 -465
  92. package/dist/create/server-source/crates/feltdb/src/convergence.rs +0 -634
  93. package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +0 -427
  94. package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +0 -376
  95. package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +0 -365
  96. package/dist/create/server-source/crates/feltdb/src/cron.rs +0 -294
  97. package/dist/create/server-source/crates/feltdb/src/dedup_bound_investigation.rs +0 -483
  98. package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +0 -477
  99. package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +0 -1003
  100. package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +0 -1334
  101. package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +0 -368
  102. package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +0 -231
  103. package/dist/create/server-source/crates/feltdb/src/durable_operation_identity.rs +0 -482
  104. package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +0 -203
  105. package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +0 -317
  106. package/dist/create/server-source/crates/feltdb/src/equality_index.rs +0 -595
  107. package/dist/create/server-source/crates/feltdb/src/execution.rs +0 -426
  108. package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +0 -214
  109. package/dist/create/server-source/crates/feltdb/src/indexing.rs +0 -776
  110. package/dist/create/server-source/crates/feltdb/src/lib.rs +0 -7365
  111. package/dist/create/server-source/crates/feltdb/src/managed_cas_tests.rs +0 -234
  112. package/dist/create/server-source/crates/feltdb/src/materialization.rs +0 -184
  113. package/dist/create/server-source/crates/feltdb/src/metrics.rs +0 -266
  114. package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +0 -310
  115. package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +0 -736
  116. package/dist/create/server-source/crates/feltdb/src/observability.rs +0 -379
  117. package/dist/create/server-source/crates/feltdb/src/operation.rs +0 -290
  118. package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +0 -439
  119. package/dist/create/server-source/crates/feltdb/src/operation_log.rs +0 -349
  120. package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +0 -332
  121. package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +0 -718
  122. package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +0 -487
  123. package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +0 -166
  124. package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +0 -289
  125. package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +0 -566
  126. package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +0 -3391
  127. package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +0 -203
  128. package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +0 -261
  129. package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +0 -502
  130. package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +0 -216
  131. package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +0 -303
  132. package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +0 -323
  133. package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +0 -531
  134. package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +0 -406
  135. package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +0 -436
  136. package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +0 -387
  137. package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +0 -378
  138. package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +0 -359
  139. package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +0 -372
  140. package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +0 -404
  141. package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +0 -376
  142. package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +0 -461
  143. package/dist/create/server-source/crates/feltdb/src/policy_evaluation.rs +0 -2125
  144. package/dist/create/server-source/crates/feltdb/src/production_api.rs +0 -418
  145. package/dist/create/server-source/crates/feltdb/src/provenance.rs +0 -207
  146. package/dist/create/server-source/crates/feltdb/src/query_execution_diagnostics.rs +0 -126
  147. package/dist/create/server-source/crates/feltdb/src/query_performance.rs +0 -218
  148. package/dist/create/server-source/crates/feltdb/src/read_basis.rs +0 -257
  149. package/dist/create/server-source/crates/feltdb/src/references.rs +0 -404
  150. package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +0 -401
  151. package/dist/create/server-source/crates/feltdb/src/replica_acknowledgements.rs +0 -501
  152. package/dist/create/server-source/crates/feltdb/src/replica_membership.rs +0 -680
  153. package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +0 -163
  154. package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +0 -133
  155. package/dist/create/server-source/crates/feltdb/src/routing.rs +0 -409
  156. package/dist/create/server-source/crates/feltdb/src/semantic_decision.rs +0 -2029
  157. package/dist/create/server-source/crates/feltdb/src/semantic_query.rs +0 -599
  158. package/dist/create/server-source/crates/feltdb/src/sharding.rs +0 -549
  159. package/dist/create/server-source/crates/feltdb/src/state_conflict_contract.rs +0 -516
  160. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +0 -6003
  161. package/dist/create/server-source/crates/feltdb/src/state_diff_contract.rs +0 -222
  162. package/dist/create/server-source/crates/feltdb/src/state_facade.rs +0 -320
  163. package/dist/create/server-source/crates/feltdb/src/state_hash.rs +0 -291
  164. package/dist/create/server-source/crates/feltdb/src/state_model.rs +0 -4158
  165. package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +0 -379
  166. package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +0 -673
  167. package/dist/create/server-source/crates/feltdb/src/storage.rs +0 -273
  168. package/dist/create/server-source/crates/feltdb/src/submission.rs +0 -471
  169. package/dist/create/server-source/crates/feltdb/src/sync.rs +0 -495
  170. package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +0 -822
  171. package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +0 -433
  172. package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +0 -462
  173. package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +0 -965
  174. package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +0 -1120
  175. package/dist/create/server-source/crates/feltdb/src/transactions.rs +0 -643
  176. package/dist/create/server-source/crates/feltdb/src/trigger.rs +0 -390
  177. package/dist/create/server-source/crates/feltdb/src/trigger_contract.rs +0 -749
  178. package/dist/create/server-source/crates/feltdb/src/versioned_object_substrate.rs +0 -708
  179. package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +0 -1269
  180. package/dist/create/server-source/crates/feltdb/src/workflow.rs +0 -713
  181. package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +0 -510
  182. package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +0 -354
  183. package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +0 -594
  184. package/dist/create/server-source/crates/feltdb/src/workload.rs +0 -4222
  185. package/dist/create/server-source/crates/feltdb/src/workload_diagnostics.rs +0 -456
  186. package/dist/create/server-source/crates/feltdb/tests/agent_state_native.rs +0 -1188
  187. package/dist/create/server-source/crates/feltdb/tests/agent_transition_contract_conformance.rs +0 -2884
  188. package/dist/create/server-source/crates/feltdb/tests/authority_process.rs +0 -297
  189. package/dist/create/server-source/crates/feltdb/tests/authority_transaction_identity.rs +0 -534
  190. package/dist/create/server-source/crates/feltdb/tests/bounded_read_contract.rs +0 -132
  191. package/dist/create/server-source/crates/feltdb/tests/branching_evidence.rs +0 -299
  192. package/dist/create/server-source/crates/feltdb/tests/common/mod.rs +0 -96
  193. package/dist/create/server-source/crates/feltdb/tests/compaction_stall_contract.rs +0 -272
  194. package/dist/create/server-source/crates/feltdb/tests/crash_durability_contract.rs +0 -467
  195. package/dist/create/server-source/crates/feltdb/tests/current_revision_authority_evidence.rs +0 -309
  196. package/dist/create/server-source/crates/feltdb/tests/durable_backup_contract.rs +0 -445
  197. package/dist/create/server-source/crates/feltdb/tests/durable_corruption_contract.rs +0 -518
  198. package/dist/create/server-source/crates/feltdb/tests/durable_format_compatibility.rs +0 -392
  199. package/dist/create/server-source/crates/feltdb/tests/feltdb_state_boundary_tests.rs +0 -850
  200. package/dist/create/server-source/crates/feltdb/tests/fixtures/agent_transition_corpus.json +0 -23340
  201. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_conflict_contract_corpus.json +0 -1916
  202. package/dist/create/server-source/crates/feltdb/tests/fixtures/state_diff_contract_corpus.json +0 -1878
  203. package/dist/create/server-source/crates/feltdb/tests/fixtures/trigger_contract_corpus.json +0 -1862
  204. package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_evidence.rs +0 -123
  205. package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_substrate_completion.rs +0 -299
  206. package/dist/create/server-source/crates/feltdb/tests/local_process_ownership.rs +0 -69
  207. package/dist/create/server-source/crates/feltdb/tests/managed_incident_regression.rs +0 -191
  208. package/dist/create/server-source/crates/feltdb/tests/operational_health_contract.rs +0 -278
  209. package/dist/create/server-source/crates/feltdb/tests/pr34_query_collection.rs +0 -234
  210. package/dist/create/server-source/crates/feltdb/tests/pr35_equality_index.rs +0 -947
  211. package/dist/create/server-source/crates/feltdb/tests/pr7_self_authorization_proof.rs +0 -404
  212. package/dist/create/server-source/crates/feltdb/tests/pr8_vocabulary_assessment.rs +0 -916
  213. package/dist/create/server-source/crates/feltdb/tests/pr9_phase2_boundary_tests.rs +0 -1045
  214. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3a_path_a_tests.rs +0 -347
  215. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_authorized_mutations.rs +0 -361
  216. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_role_based_authorization.rs +0 -330
  217. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_simple_auth_delete.rs +0 -93
  218. package/dist/create/server-source/crates/feltdb/tests/pr9_phase3c_team_delete_role_authorization.rs +0 -622
  219. package/dist/create/server-source/crates/feltdb/tests/pr9_teams_role_based_access.rs +0 -517
  220. package/dist/create/server-source/crates/feltdb/tests/production_certification.rs +0 -1153
  221. package/dist/create/server-source/crates/feltdb/tests/production_contract.rs +0 -771
  222. package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +0 -1383
  223. package/dist/create/server-source/crates/feltdb/tests/reconciliation_application.rs +0 -868
  224. package/dist/create/server-source/crates/feltdb/tests/reconciliation_wire_format_evidence.rs +0 -221
  225. package/dist/create/server-source/crates/feltdb/tests/replicated_history_contract.rs +0 -417
  226. package/dist/create/server-source/crates/feltdb/tests/resource_scoped_revisions.rs +0 -338
  227. package/dist/create/server-source/crates/feltdb/tests/revision_identity_contract.rs +0 -1039
  228. package/dist/create/server-source/crates/feltdb/tests/revision_model_decision.rs +0 -739
  229. package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +0 -452
  230. package/dist/create/server-source/crates/feltdb/tests/saas_authorization_integration.rs +0 -81
  231. package/dist/create/server-source/crates/feltdb/tests/saas_invitation_lifecycle.rs +0 -438
  232. package/dist/create/server-source/crates/feltdb/tests/semantic_decision.rs +0 -1165
  233. package/dist/create/server-source/crates/feltdb/tests/semantic_query.rs +0 -767
  234. package/dist/create/server-source/crates/feltdb/tests/semantic_query_contract.rs +0 -363
  235. package/dist/create/server-source/crates/feltdb/tests/state_conflict_contract_conformance.rs +0 -1799
  236. package/dist/create/server-source/crates/feltdb/tests/state_diff_contract_conformance.rs +0 -1316
  237. package/dist/create/server-source/crates/feltdb/tests/state_model_integration.rs +0 -358
  238. package/dist/create/server-source/crates/feltdb/tests/state_persistence_integration.rs +0 -365
  239. package/dist/create/server-source/crates/feltdb/tests/state_ref_authority.rs +0 -918
  240. package/dist/create/server-source/crates/feltdb/tests/state_store_boundary_evidence.rs +0 -299
  241. package/dist/create/server-source/crates/feltdb/tests/storage_independent_versioned_object_substrate.rs +0 -328
  242. package/dist/create/server-source/crates/feltdb/tests/sync_divergence_evidence.rs +0 -255
  243. package/dist/create/server-source/crates/feltdb/tests/three_way_input_boundary_evidence.rs +0 -249
  244. package/dist/create/server-source/crates/feltdb/tests/trigger_contract_conformance.rs +0 -994
  245. package/dist/create/server-source/crates/feltdb/tests/workload_envelope_contract.rs +0 -484
  246. package/dist/studio-app/assets/feltdb_wasm_bg-TseJn2Ax.wasm +0 -0
  247. package/dist/studio-app/assets/index-1wd0uKua.js +0 -30
@@ -1,1165 +0,0 @@
1
- use feltdb::authorization::{AuthorizationRequest, Subject};
2
- use feltdb::semantic_decision::{
3
- authorize_context, decide_record, deterministic_request_key, execute_decision_batch,
4
- execute_decision_transport, jev, run_choice_diagnostics, run_decision_benchmark,
5
- DecisionAuthorizationConfig, DecisionAuthorizationDecision, DecisionAuthorizationGate,
6
- DecisionBatch, DecisionBatchDecision, DecisionDefinition, DecisionError, DecisionOptions,
7
- DecisionRecordRef, DecisionRequest, DecisionResult, DecisionRuntime, DecisionRuntimeMetadata,
8
- DecisionStatus, DecisionTransportAuthorization, DecisionTransportExecutionRequest,
9
- DecisionTransportRuntime, DeterministicDecisionRuntime, WeightedOption,
10
- DEFAULT_DECISION_COLLECTION,
11
- };
12
- use feltdb::FeltDb;
13
- use serde_json::{json, Value};
14
- use std::collections::{BTreeMap, BTreeSet};
15
- use std::sync::atomic::{AtomicUsize, Ordering};
16
- use std::sync::{Arc, Mutex};
17
- use tempfile::TempDir;
18
-
19
- #[derive(Clone)]
20
- struct RecordingRuntime {
21
- metadata: DecisionRuntimeMetadata,
22
- calls: Arc<Mutex<Vec<DecisionRequest>>>,
23
- invocations: Arc<AtomicUsize>,
24
- }
25
-
26
- impl RecordingRuntime {
27
- fn new(model_revision: &str, prompt_revision: &str) -> Self {
28
- Self {
29
- metadata: DecisionRuntimeMetadata {
30
- runtime: "deterministic".into(),
31
- model: Some("deterministic-runtime".into()),
32
- model_revision: Some(model_revision.into()),
33
- prompt_revision: Some(prompt_revision.into()),
34
- execution_method: Some(feltdb::DecisionExecutionMethod::Logit),
35
- decision_schema_revision: Some("semantic-decision-v1".into()),
36
- },
37
- calls: Arc::new(Mutex::new(Vec::new())),
38
- invocations: Arc::new(AtomicUsize::new(0)),
39
- }
40
- }
41
-
42
- fn calls(&self) -> Vec<DecisionRequest> {
43
- self.calls.lock().expect("calls").clone()
44
- }
45
-
46
- fn invocations(&self) -> usize {
47
- self.invocations.load(Ordering::SeqCst)
48
- }
49
- }
50
-
51
- impl DecisionRuntime for RecordingRuntime {
52
- fn metadata(&self) -> DecisionRuntimeMetadata {
53
- self.metadata.clone()
54
- }
55
-
56
- fn decide(&self, request: &DecisionRequest) -> Result<DecisionResult, DecisionError> {
57
- self.invocations.fetch_add(1, Ordering::SeqCst);
58
- self.calls.lock().expect("calls").push(request.clone());
59
- let context = request.context().as_object().cloned().unwrap_or_default();
60
- Ok(match request {
61
- DecisionRequest::Binary { .. } => {
62
- let decision = context
63
- .get("employmentType")
64
- .and_then(Value::as_str)
65
- .is_some_and(|value| matches!(value, "remote" | "hybrid"));
66
- DecisionResult::Binary {
67
- decision,
68
- option_mass: if decision { 0.9 } else { 0.8 },
69
- supporting_fields: vec!["employmentType".into()],
70
- }
71
- }
72
- DecisionRequest::Choice { options, .. } => {
73
- let selected = context
74
- .get("actionHint")
75
- .and_then(Value::as_str)
76
- .unwrap_or(options.first().map(String::as_str).unwrap_or("approve"))
77
- .to_string();
78
- DecisionResult::Choice {
79
- option_mass: 0.85,
80
- selected: selected.clone(),
81
- options: options
82
- .iter()
83
- .map(|value| WeightedOption {
84
- value: value.clone(),
85
- probability: if value == &selected {
86
- 0.85
87
- } else {
88
- 0.15 / ((options.len() - 1).max(1) as f64)
89
- },
90
- })
91
- .collect(),
92
- supporting_fields: vec!["actionHint".into()],
93
- }
94
- }
95
- DecisionRequest::Score { levels, .. } => {
96
- let selected = context
97
- .get("urgency")
98
- .and_then(Value::as_str)
99
- .unwrap_or(levels.first().map(String::as_str).unwrap_or("low"))
100
- .to_string();
101
- DecisionResult::Score {
102
- option_mass: 0.88,
103
- selected: selected.clone(),
104
- levels: levels
105
- .iter()
106
- .map(|value| WeightedOption {
107
- value: value.clone(),
108
- probability: if value == &selected {
109
- 0.88
110
- } else {
111
- 0.12 / ((levels.len() - 1).max(1) as f64)
112
- },
113
- })
114
- .collect(),
115
- supporting_fields: vec!["urgency".into()],
116
- }
117
- }
118
- })
119
- }
120
- }
121
-
122
- struct StaticGate {
123
- allowed: BTreeSet<String>,
124
- requests: Arc<Mutex<Vec<AuthorizationRequest>>>,
125
- }
126
-
127
- impl StaticGate {
128
- fn new(fields: &[&str]) -> Self {
129
- Self {
130
- allowed: fields.iter().map(|field| (*field).to_string()).collect(),
131
- requests: Arc::new(Mutex::new(Vec::new())),
132
- }
133
- }
134
- }
135
-
136
- impl DecisionAuthorizationGate for StaticGate {
137
- fn check(
138
- &self,
139
- request: &AuthorizationRequest,
140
- ) -> Result<DecisionAuthorizationDecision, DecisionError> {
141
- self.requests
142
- .lock()
143
- .expect("requests")
144
- .push(request.clone());
145
- let field = request
146
- .resource
147
- .uri
148
- .split('/')
149
- .next_back()
150
- .unwrap_or_default()
151
- .to_string();
152
- Ok(DecisionAuthorizationDecision {
153
- allowed: self.allowed.contains(&field),
154
- decision_id: Some(format!("decision-{field}")),
155
- })
156
- }
157
- }
158
-
159
- fn authorization_config() -> DecisionAuthorizationConfig {
160
- DecisionAuthorizationConfig {
161
- principal: Some(feltdb::DecisionPrincipal {
162
- subject: Subject::Service("reporting-service".into()),
163
- tenant_id: "tenant-a".into(),
164
- application_id: "people-directory".into(),
165
- capability: "state:read".into(),
166
- }),
167
- }
168
- }
169
-
170
- fn memory_db(name: &str) -> FeltDb {
171
- let path = std::env::temp_dir().join(format!(
172
- "feltdb-semantic-decision-{name}-{}.log",
173
- std::time::SystemTime::now()
174
- .duration_since(std::time::UNIX_EPOCH)
175
- .expect("time")
176
- .as_nanos()
177
- ));
178
- FeltDb::open(path).expect("db")
179
- }
180
-
181
- fn insert_person(db: &FeltDb, id: &str, record: Value) {
182
- db.insert(&format!("people:{id}"), record).expect("insert");
183
- }
184
-
185
- fn update_person(db: &FeltDb, id: &str, record: Value) {
186
- db.update(&format!("people:{id}"), record).expect("update");
187
- }
188
-
189
- fn judgment_rows(db: &FeltDb) -> Vec<feltdb::DecisionJudgmentRecord> {
190
- db.capability(DEFAULT_DECISION_COLLECTION)
191
- .find(|_: &feltdb::DecisionJudgmentRecord| true)
192
- .expect("judgments")
193
- }
194
-
195
- #[test]
196
- fn binary_choice_and_score_decisions_are_valid_and_authoritative() {
197
- let db = memory_db("valid");
198
- insert_person(
199
- &db,
200
- "p-1",
201
- json!({
202
- "id": "p-1",
203
- "__version": 1,
204
- "publicRole": "Staff Backend Engineer",
205
- "employmentType": "remote",
206
- "actionHint": "review",
207
- "urgency": "high",
208
- }),
209
- );
210
- let runtime = RecordingRuntime::new("deterministic-v1", "prompt-v1");
211
- let target = DecisionRecordRef {
212
- collection: "people".into(),
213
- record_id: "p-1".into(),
214
- };
215
-
216
- let binary = jev(
217
- &db,
218
- &target,
219
- "could work from home",
220
- &runtime,
221
- &DecisionOptions::default(),
222
- None,
223
- )
224
- .expect("binary");
225
- let choice = decide_record(
226
- &db,
227
- &target,
228
- &DecisionDefinition::Choice {
229
- question: "Which action should be taken?".into(),
230
- options: vec!["approve".into(), "reject".into(), "review".into()],
231
- },
232
- &runtime,
233
- &DecisionOptions::default(),
234
- None,
235
- )
236
- .expect("choice");
237
- let score = decide_record(
238
- &db,
239
- &target,
240
- &DecisionDefinition::Score {
241
- question: "What is the urgency?".into(),
242
- levels: vec!["low".into(), "medium".into(), "high".into()],
243
- },
244
- &runtime,
245
- &DecisionOptions::default(),
246
- None,
247
- )
248
- .expect("score");
249
-
250
- assert!(matches!(
251
- &binary.result,
252
- DecisionResult::Binary { decision: true, .. }
253
- ));
254
- assert!(
255
- matches!(choice.result, DecisionResult::Choice { ref selected, .. } if selected == "review")
256
- );
257
- assert!(
258
- matches!(score.result, DecisionResult::Score { ref selected, .. } if selected == "high")
259
- );
260
- assert_eq!(runtime.invocations(), 3);
261
- assert_eq!(judgment_rows(&db).len(), 3);
262
- assert_eq!(
263
- binary.provenance.execution_method,
264
- Some(feltdb::DecisionExecutionMethod::Logit)
265
- );
266
- assert_eq!(
267
- binary.provenance.decision_schema_revision.as_deref(),
268
- Some("semantic-decision-v1")
269
- );
270
- }
271
-
272
- #[test]
273
- fn invalid_runtime_results_fail_closed() {
274
- let db = memory_db("invalid-results");
275
- insert_person(
276
- &db,
277
- "p-1",
278
- json!({"id":"p-1","__version":1,"employmentType":"remote","actionHint":"review","urgency":"medium"}),
279
- );
280
- let target = DecisionRecordRef {
281
- collection: "people".into(),
282
- record_id: "p-1".into(),
283
- };
284
-
285
- struct InvalidRuntime(&'static str);
286
- impl DecisionRuntime for InvalidRuntime {
287
- fn metadata(&self) -> DecisionRuntimeMetadata {
288
- DecisionRuntimeMetadata {
289
- runtime: "broken".into(),
290
- model: Some("broken".into()),
291
- model_revision: Some("broken-v1".into()),
292
- prompt_revision: Some("broken-prompt".into()),
293
- execution_method: None,
294
- decision_schema_revision: None,
295
- }
296
- }
297
-
298
- fn decide(&self, _request: &DecisionRequest) -> Result<DecisionResult, DecisionError> {
299
- Ok(match self.0 {
300
- "option" => DecisionResult::Choice {
301
- selected: "maybe".into(),
302
- options: vec![
303
- WeightedOption {
304
- value: "approve".into(),
305
- probability: 0.5,
306
- },
307
- WeightedOption {
308
- value: "reject".into(),
309
- probability: 0.5,
310
- },
311
- ],
312
- option_mass: 0.5,
313
- supporting_fields: vec![],
314
- },
315
- _ => DecisionResult::Score {
316
- selected: "high".into(),
317
- levels: vec![
318
- WeightedOption {
319
- value: "low".into(),
320
- probability: 0.4,
321
- },
322
- WeightedOption {
323
- value: "medium".into(),
324
- probability: 0.4,
325
- },
326
- WeightedOption {
327
- value: "high".into(),
328
- probability: 0.4,
329
- },
330
- ],
331
- option_mass: 0.4,
332
- supporting_fields: vec![],
333
- },
334
- })
335
- }
336
- }
337
-
338
- let invalid_option = decide_record(
339
- &db,
340
- &target,
341
- &DecisionDefinition::Choice {
342
- question: "Which action should be taken?".into(),
343
- options: vec!["approve".into(), "reject".into(), "review".into()],
344
- },
345
- &InvalidRuntime("option"),
346
- &DecisionOptions::default(),
347
- None,
348
- )
349
- .expect_err("invalid option");
350
- assert_eq!(invalid_option.code, "DECISION_INVALID_OPTION");
351
-
352
- let invalid_distribution = decide_record(
353
- &db,
354
- &target,
355
- &DecisionDefinition::Score {
356
- question: "What is the urgency?".into(),
357
- levels: vec!["low".into(), "medium".into(), "high".into()],
358
- },
359
- &InvalidRuntime("distribution"),
360
- &DecisionOptions::default(),
361
- None,
362
- )
363
- .expect_err("invalid distribution");
364
- assert_eq!(invalid_distribution.code, "DECISION_INVALID_DISTRIBUTION");
365
- }
366
-
367
- #[test]
368
- fn authorization_filters_context_before_runtime_and_evidence() {
369
- let db = memory_db("authorization");
370
- let secret = "SENTINEL-remote-every-day";
371
- insert_person(
372
- &db,
373
- "p-1",
374
- json!({
375
- "id":"p-1",
376
- "__version":1,
377
- "publicRole":"Staff Backend Engineer",
378
- "employmentType":"office",
379
- "privateNotes": secret
380
- }),
381
- );
382
- let runtime = RecordingRuntime::new("deterministic-v1", "prompt-v1");
383
- let gate = StaticGate::new(&["publicRole", "employmentType"]);
384
- let result = jev(
385
- &db,
386
- &DecisionRecordRef {
387
- collection: "people".into(),
388
- record_id: "p-1".into(),
389
- },
390
- "could work from home",
391
- &runtime,
392
- &DecisionOptions::default(),
393
- Some((&gate, &authorization_config())),
394
- )
395
- .expect("jev");
396
-
397
- let calls = runtime.calls();
398
- assert_eq!(calls.len(), 1);
399
- let payload = serde_json::to_string(&calls).expect("serialize calls");
400
- assert!(!payload.contains(secret));
401
- assert!(!payload.contains("privateNotes"));
402
- let request_uris: Vec<String> = gate
403
- .requests
404
- .lock()
405
- .expect("requests")
406
- .iter()
407
- .map(|request| request.resource.uri.clone())
408
- .collect();
409
- assert_eq!(
410
- request_uris,
411
- vec![
412
- "flow://people-directory/people/p-1/employmentType".to_string(),
413
- "flow://people-directory/people/p-1/privateNotes".to_string(),
414
- "flow://people-directory/people/p-1/publicRole".to_string(),
415
- ]
416
- );
417
- assert_eq!(
418
- result.evidence[0].state.withheld_fields,
419
- vec!["privateNotes".to_string()]
420
- );
421
- }
422
-
423
- #[test]
424
- fn cache_reuse_invalidation_and_supersession_follow_authorized_state() {
425
- let db = memory_db("cache");
426
- insert_person(
427
- &db,
428
- "p-1",
429
- json!({
430
- "id":"p-1",
431
- "__version":1,
432
- "publicRole":"Staff Backend Engineer",
433
- "employmentType":"office",
434
- "lastSeenAt":"2026-01-01"
435
- }),
436
- );
437
- let runtime = RecordingRuntime::new("deterministic-v1", "prompt-v1");
438
- let target = DecisionRecordRef {
439
- collection: "people".into(),
440
- record_id: "p-1".into(),
441
- };
442
- let options = DecisionOptions {
443
- fields: Some(vec!["publicRole".into(), "employmentType".into()]),
444
- ..DecisionOptions::default()
445
- };
446
-
447
- let first = jev(
448
- &db,
449
- &target,
450
- "could work from home",
451
- &runtime,
452
- &options,
453
- None,
454
- )
455
- .expect("first");
456
- let second = jev(
457
- &db,
458
- &target,
459
- "could work from home",
460
- &runtime,
461
- &options,
462
- None,
463
- )
464
- .expect("second");
465
- assert!(!first.cached);
466
- assert!(second.cached);
467
- assert_eq!(runtime.invocations(), 1);
468
-
469
- update_person(
470
- &db,
471
- "p-1",
472
- json!({
473
- "id":"p-1",
474
- "__version":2,
475
- "publicRole":"Staff Backend Engineer",
476
- "employmentType":"office",
477
- "lastSeenAt":"2026-09-18"
478
- }),
479
- );
480
- let after_irrelevant = jev(
481
- &db,
482
- &target,
483
- "could work from home",
484
- &runtime,
485
- &options,
486
- None,
487
- )
488
- .expect("irrelevant");
489
- assert!(after_irrelevant.cached);
490
- assert_eq!(runtime.invocations(), 1);
491
-
492
- update_person(
493
- &db,
494
- "p-1",
495
- json!({
496
- "id":"p-1",
497
- "__version":3,
498
- "publicRole":"Staff Backend Engineer",
499
- "employmentType":"remote",
500
- "lastSeenAt":"2026-09-18"
501
- }),
502
- );
503
- let after_relevant = jev(
504
- &db,
505
- &target,
506
- "could work from home",
507
- &runtime,
508
- &options,
509
- None,
510
- )
511
- .expect("relevant");
512
- assert!(!after_relevant.cached);
513
- assert_eq!(runtime.invocations(), 2);
514
- let judgments = judgment_rows(&db);
515
- assert_eq!(judgments.len(), 2);
516
- assert!(judgments
517
- .iter()
518
- .any(|row| row.status == DecisionStatus::Superseded));
519
- assert!(judgments
520
- .iter()
521
- .any(|row| row.status == DecisionStatus::Active));
522
- }
523
-
524
- #[test]
525
- fn decision_schema_and_runtime_revisions_invalidate_cache_identity() {
526
- let db = memory_db("identity");
527
- insert_person(
528
- &db,
529
- "p-1",
530
- json!({"id":"p-1","__version":1,"actionHint":"approve","urgency":"medium"}),
531
- );
532
- let target = DecisionRecordRef {
533
- collection: "people".into(),
534
- record_id: "p-1".into(),
535
- };
536
- let runtime_a = RecordingRuntime::new("deterministic-v1", "prompt-v1");
537
- let runtime_b = RecordingRuntime::new("deterministic-v2", "prompt-v1");
538
-
539
- let first = decide_record(
540
- &db,
541
- &target,
542
- &DecisionDefinition::Choice {
543
- question: "Which action should be taken?".into(),
544
- options: vec!["approve".into(), "reject".into()],
545
- },
546
- &runtime_a,
547
- &DecisionOptions::default(),
548
- None,
549
- )
550
- .expect("first");
551
- let changed_options = decide_record(
552
- &db,
553
- &target,
554
- &DecisionDefinition::Choice {
555
- question: "Which action should be taken?".into(),
556
- options: vec!["approve".into(), "reject".into(), "review".into()],
557
- },
558
- &runtime_a,
559
- &DecisionOptions::default(),
560
- None,
561
- )
562
- .expect("changed options");
563
- let changed_runtime = decide_record(
564
- &db,
565
- &target,
566
- &DecisionDefinition::Choice {
567
- question: "Which action should be taken?".into(),
568
- options: vec!["approve".into(), "reject".into()],
569
- },
570
- &runtime_b,
571
- &DecisionOptions::default(),
572
- None,
573
- )
574
- .expect("changed runtime");
575
-
576
- assert_ne!(
577
- first.provenance.cache_key,
578
- changed_options.provenance.cache_key
579
- );
580
- assert_ne!(
581
- first.provenance.cache_key,
582
- changed_runtime.provenance.cache_key
583
- );
584
- }
585
-
586
- #[test]
587
- fn deterministic_runtime_and_file_backed_reopen_reuse_durable_judgments() {
588
- let tempdir = TempDir::new().expect("tempdir");
589
- let path = tempdir.path().join("decision.log");
590
-
591
- let runtime = {
592
- let request = DecisionRequest::Binary {
593
- predicate: "could work from home".into(),
594
- context: json!({"employmentType":"remote"}),
595
- };
596
- let mut responses = BTreeMap::new();
597
- responses.insert(
598
- deterministic_request_key(&request).expect("request key"),
599
- DecisionResult::Binary {
600
- decision: true,
601
- option_mass: 0.9,
602
- supporting_fields: vec!["employmentType".into()],
603
- },
604
- );
605
- DeterministicDecisionRuntime::new(
606
- DecisionRuntimeMetadata {
607
- runtime: "deterministic".into(),
608
- model: Some("deterministic-runtime".into()),
609
- model_revision: Some("deterministic-v1".into()),
610
- prompt_revision: Some("prompt-v1".into()),
611
- execution_method: None,
612
- decision_schema_revision: None,
613
- },
614
- responses,
615
- )
616
- };
617
-
618
- {
619
- let db = FeltDb::open(&path).expect("open writer");
620
- insert_person(
621
- &db,
622
- "p-1",
623
- json!({"id":"p-1","__version":1,"employmentType":"remote"}),
624
- );
625
- let first = jev(
626
- &db,
627
- &DecisionRecordRef {
628
- collection: "people".into(),
629
- record_id: "p-1".into(),
630
- },
631
- "could work from home",
632
- &runtime,
633
- &DecisionOptions {
634
- fields: Some(vec!["employmentType".into()]),
635
- ..DecisionOptions::default()
636
- },
637
- None,
638
- )
639
- .expect("first");
640
- assert!(!first.cached);
641
- }
642
-
643
- let db = FeltDb::open(&path).expect("open reader");
644
- let second = jev(
645
- &db,
646
- &DecisionRecordRef {
647
- collection: "people".into(),
648
- record_id: "p-1".into(),
649
- },
650
- "could work from home",
651
- &runtime,
652
- &DecisionOptions {
653
- fields: Some(vec!["employmentType".into()]),
654
- ..DecisionOptions::default()
655
- },
656
- None,
657
- )
658
- .expect("second");
659
- assert!(second.cached);
660
- }
661
-
662
- #[test]
663
- fn benchmark_reports_twenty_cases_for_each_primitive_and_reuses_cache() {
664
- let db = memory_db("benchmark");
665
- struct BenchmarkRuntime(Arc<AtomicUsize>);
666
- impl DecisionRuntime for BenchmarkRuntime {
667
- fn metadata(&self) -> DecisionRuntimeMetadata {
668
- DecisionRuntimeMetadata {
669
- runtime: "deterministic".into(),
670
- model: Some("benchmark-runtime".into()),
671
- model_revision: Some("benchmark-v1".into()),
672
- prompt_revision: Some("prompt-v1".into()),
673
- execution_method: None,
674
- decision_schema_revision: None,
675
- }
676
- }
677
-
678
- fn decide(&self, request: &DecisionRequest) -> Result<DecisionResult, DecisionError> {
679
- self.0.fetch_add(1, Ordering::SeqCst);
680
- let context = request.context().as_object().cloned().unwrap_or_default();
681
- Ok(match request {
682
- DecisionRequest::Binary { .. } => DecisionResult::Binary {
683
- decision: context
684
- .get("employmentType")
685
- .and_then(Value::as_str)
686
- .is_some_and(|value| value == "remote"),
687
- option_mass: 0.75,
688
- supporting_fields: vec!["employmentType".into()],
689
- },
690
- DecisionRequest::Choice { options, .. } => DecisionResult::Choice {
691
- selected: if context
692
- .get("publicRole")
693
- .and_then(Value::as_str)
694
- .is_some_and(|value| value.contains("Engineer"))
695
- {
696
- "approve".into()
697
- } else {
698
- "review".into()
699
- },
700
- options: options
701
- .iter()
702
- .map(|value| WeightedOption {
703
- value: value.clone(),
704
- probability: if value == "reject" {
705
- 0.05
706
- } else if value == "review" {
707
- 0.15
708
- } else {
709
- 0.8
710
- },
711
- })
712
- .collect(),
713
- option_mass: 0.8,
714
- supporting_fields: vec!["publicRole".into()],
715
- },
716
- DecisionRequest::Score { levels, .. } => {
717
- let selected = context
718
- .get("urgency")
719
- .and_then(Value::as_str)
720
- .unwrap_or("low")
721
- .to_string();
722
- DecisionResult::Score {
723
- selected: selected.clone(),
724
- levels: levels
725
- .iter()
726
- .map(|value| WeightedOption {
727
- value: value.clone(),
728
- probability: if value == &selected { 0.8 } else { 0.1 },
729
- })
730
- .collect(),
731
- option_mass: 0.8,
732
- supporting_fields: vec!["urgency".into()],
733
- }
734
- }
735
- })
736
- }
737
- }
738
-
739
- let counter = Arc::new(AtomicUsize::new(0));
740
- let report = run_decision_benchmark(
741
- &db,
742
- &BenchmarkRuntime(counter.clone()),
743
- &DecisionOptions::default(),
744
- )
745
- .expect("benchmark");
746
- assert_eq!(report.binary, 20);
747
- assert_eq!(report.choice, 20);
748
- assert_eq!(report.score, 20);
749
- assert_eq!(report.runtime_invocations_first_pass, 60);
750
- assert_eq!(report.runtime_invocations_second_pass, 0);
751
- assert_eq!(report.cache_hits_second_pass, 60);
752
- assert_eq!(counter.load(Ordering::SeqCst), 60);
753
- }
754
-
755
- #[test]
756
- fn transport_execution_matches_direct_rust_semantics() {
757
- fn seeded(name: &str) -> FeltDb {
758
- let db = memory_db(name);
759
- insert_person(
760
- &db,
761
- "p-1",
762
- json!({
763
- "id":"p-1",
764
- "__version":1,
765
- "employmentType":"remote",
766
- "actionHint":"review",
767
- "urgency":"high"
768
- }),
769
- );
770
- db
771
- }
772
-
773
- let metadata = DecisionRuntimeMetadata {
774
- runtime: "deterministic".into(),
775
- model: Some("deterministic-runtime".into()),
776
- model_revision: Some("deterministic-v1".into()),
777
- prompt_revision: Some("prompt-v1".into()),
778
- execution_method: None,
779
- decision_schema_revision: None,
780
- };
781
- let context = json!({
782
- "actionHint":"review",
783
- "employmentType":"remote",
784
- "urgency":"high"
785
- });
786
- let binary_request = DecisionRequest::Binary {
787
- predicate: "could work from home".into(),
788
- context: context.clone(),
789
- };
790
- let choice_request = DecisionRequest::Choice {
791
- question: "Which action should be taken?".into(),
792
- options: vec!["approve".into(), "reject".into(), "review".into()],
793
- context: context.clone(),
794
- };
795
- let score_request = DecisionRequest::Score {
796
- question: "What is the urgency?".into(),
797
- levels: vec!["low".into(), "medium".into(), "high".into()],
798
- context,
799
- };
800
- let mut responses = BTreeMap::new();
801
- responses.insert(
802
- deterministic_request_key(&binary_request).expect("binary key"),
803
- DecisionResult::Binary {
804
- decision: true,
805
- option_mass: 0.9,
806
- supporting_fields: vec!["employmentType".into()],
807
- },
808
- );
809
- responses.insert(
810
- deterministic_request_key(&choice_request).expect("choice key"),
811
- DecisionResult::Choice {
812
- selected: "review".into(),
813
- options: vec![
814
- WeightedOption {
815
- value: "approve".into(),
816
- probability: 0.05,
817
- },
818
- WeightedOption {
819
- value: "reject".into(),
820
- probability: 0.05,
821
- },
822
- WeightedOption {
823
- value: "review".into(),
824
- probability: 0.9,
825
- },
826
- ],
827
- option_mass: 0.9,
828
- supporting_fields: vec!["actionHint".into()],
829
- },
830
- );
831
- responses.insert(
832
- deterministic_request_key(&score_request).expect("score key"),
833
- DecisionResult::Score {
834
- selected: "high".into(),
835
- levels: vec![
836
- WeightedOption {
837
- value: "low".into(),
838
- probability: 0.05,
839
- },
840
- WeightedOption {
841
- value: "medium".into(),
842
- probability: 0.05,
843
- },
844
- WeightedOption {
845
- value: "high".into(),
846
- probability: 0.9,
847
- },
848
- ],
849
- option_mass: 0.9,
850
- supporting_fields: vec!["urgency".into()],
851
- },
852
- );
853
-
854
- let target = DecisionRecordRef {
855
- collection: "people".into(),
856
- record_id: "p-1".into(),
857
- };
858
- let definitions = vec![
859
- DecisionDefinition::Binary {
860
- predicate: "could work from home".into(),
861
- },
862
- DecisionDefinition::Choice {
863
- question: "Which action should be taken?".into(),
864
- options: vec!["approve".into(), "reject".into(), "review".into()],
865
- },
866
- DecisionDefinition::Score {
867
- question: "What is the urgency?".into(),
868
- levels: vec!["low".into(), "medium".into(), "high".into()],
869
- },
870
- ];
871
-
872
- for definition in definitions {
873
- let direct_db = seeded("direct-transport");
874
- let transport_db = seeded("transport");
875
- let direct = decide_record(
876
- &direct_db,
877
- &target,
878
- &definition,
879
- &DeterministicDecisionRuntime::new(metadata.clone(), responses.clone()),
880
- &DecisionOptions::default(),
881
- None,
882
- )
883
- .expect("direct");
884
- let via_transport = execute_decision_transport(
885
- &transport_db,
886
- &DecisionTransportExecutionRequest {
887
- target: Some(target.clone()),
888
- context: None,
889
- definition: definition.clone(),
890
- options: DecisionOptions::default(),
891
- authorization: None,
892
- runtime: DecisionTransportRuntime::Deterministic {
893
- metadata: metadata.clone(),
894
- responses: responses.clone(),
895
- },
896
- },
897
- )
898
- .expect("transport");
899
- assert_eq!(
900
- via_transport.runtime_requests,
901
- Vec::<DecisionRequest>::new()
902
- );
903
- assert_eq!(via_transport.evaluation.result, direct.result);
904
- assert_eq!(via_transport.evaluation.evidence, direct.evidence);
905
- assert_eq!(
906
- via_transport.evaluation.provenance.context_hash,
907
- direct.provenance.context_hash
908
- );
909
- assert_eq!(
910
- via_transport.evaluation.provenance.relevant_state_version,
911
- direct.provenance.relevant_state_version
912
- );
913
- assert_eq!(
914
- via_transport.evaluation.provenance.cache_key,
915
- direct.provenance.cache_key
916
- );
917
- }
918
- }
919
-
920
- #[test]
921
- fn transport_rejects_ambiguous_target_and_inline_context() {
922
- let db = memory_db("transport-ambiguous");
923
- insert_person(
924
- &db,
925
- "p-1",
926
- json!({"id":"p-1","__version":1,"employmentType":"remote"}),
927
- );
928
- let error = execute_decision_transport(
929
- &db,
930
- &DecisionTransportExecutionRequest {
931
- target: Some(DecisionRecordRef {
932
- collection: "people".into(),
933
- record_id: "p-1".into(),
934
- }),
935
- context: Some(json!({"employmentType":"remote"})),
936
- definition: DecisionDefinition::Binary {
937
- predicate: "could work from home".into(),
938
- },
939
- options: DecisionOptions::default(),
940
- authorization: None,
941
- runtime: DecisionTransportRuntime::Recording {
942
- metadata: DecisionRuntimeMetadata {
943
- runtime: "recording".into(),
944
- model: Some("recording-runtime".into()),
945
- model_revision: Some("recording-v1".into()),
946
- prompt_revision: Some("prompt-v1".into()),
947
- execution_method: None,
948
- decision_schema_revision: None,
949
- },
950
- result: DecisionResult::Binary {
951
- decision: true,
952
- option_mass: 0.9,
953
- supporting_fields: vec!["employmentType".into()],
954
- },
955
- },
956
- },
957
- )
958
- .expect_err("ambiguous request should fail");
959
- assert_eq!(error.code, "DECISION_INVALID_RESULT");
960
- }
961
-
962
- #[test]
963
- fn transport_authorization_filters_runtime_input_before_execution() {
964
- let db = memory_db("transport-auth");
965
- let secret = "SENTINEL-secret-field";
966
- insert_person(
967
- &db,
968
- "p-1",
969
- json!({
970
- "id":"p-1",
971
- "__version":1,
972
- "public_field":"visible",
973
- "secret_field": secret
974
- }),
975
- );
976
- let response = execute_decision_transport(
977
- &db,
978
- &DecisionTransportExecutionRequest {
979
- target: Some(DecisionRecordRef {
980
- collection: "people".into(),
981
- record_id: "p-1".into(),
982
- }),
983
- context: None,
984
- definition: DecisionDefinition::Binary {
985
- predicate: "is visible".into(),
986
- },
987
- options: DecisionOptions::default(),
988
- authorization: Some(DecisionTransportAuthorization {
989
- allowed_fields: vec!["public_field".into()],
990
- principal: Some(feltdb::DecisionPrincipal {
991
- subject: Subject::Service("reporting-service".into()),
992
- tenant_id: "tenant-a".into(),
993
- application_id: "people-directory".into(),
994
- capability: "state:read".into(),
995
- }),
996
- }),
997
- runtime: DecisionTransportRuntime::Recording {
998
- metadata: DecisionRuntimeMetadata {
999
- runtime: "recording".into(),
1000
- model: Some("recording-runtime".into()),
1001
- model_revision: Some("recording-v1".into()),
1002
- prompt_revision: Some("prompt-v1".into()),
1003
- execution_method: None,
1004
- decision_schema_revision: None,
1005
- },
1006
- result: DecisionResult::Binary {
1007
- decision: true,
1008
- option_mass: 0.8,
1009
- supporting_fields: vec!["public_field".into()],
1010
- },
1011
- },
1012
- },
1013
- )
1014
- .expect("transport authorization");
1015
- assert_eq!(response.runtime_requests.len(), 1);
1016
- let payload = serde_json::to_string(&response.runtime_requests).expect("serialize");
1017
- assert!(!payload.contains(secret));
1018
- assert!(!payload.contains("secret_field"));
1019
- assert!(payload.contains("public_field"));
1020
- assert_eq!(
1021
- response.evaluation.evidence[0].state.withheld_fields,
1022
- vec!["secret_field".to_string()]
1023
- );
1024
- }
1025
-
1026
- #[test]
1027
- fn decision_batch_shares_authorized_context_without_cross_decision_leakage() {
1028
- let db = memory_db("decision-batch");
1029
- insert_person(
1030
- &db,
1031
- "p-1",
1032
- json!({
1033
- "id":"p-1",
1034
- "__version":1,
1035
- "employmentType":"remote",
1036
- "public_field":"visible"
1037
- }),
1038
- );
1039
- let runtime = RecordingRuntime::new("deterministic-v1", "prompt-v1");
1040
- let context = authorize_context(
1041
- "people",
1042
- "p-1",
1043
- &json!({
1044
- "id":"p-1",
1045
- "__version":1,
1046
- "employmentType":"remote",
1047
- "public_field":"visible"
1048
- }),
1049
- &DecisionOptions::default(),
1050
- None,
1051
- )
1052
- .expect("context");
1053
- let evaluations = execute_decision_batch(
1054
- &context,
1055
- &DecisionBatch {
1056
- context: json!({ "shared_fact":"shared" }),
1057
- decisions: vec![
1058
- DecisionBatchDecision {
1059
- id: "decision-a".into(),
1060
- definition: DecisionDefinition::Binary {
1061
- predicate: "could work from home".into(),
1062
- },
1063
- context: json!({ "decision_only":"a" }),
1064
- },
1065
- DecisionBatchDecision {
1066
- id: "decision-b".into(),
1067
- definition: DecisionDefinition::Binary {
1068
- predicate: "could work from home".into(),
1069
- },
1070
- context: json!({}),
1071
- },
1072
- ],
1073
- },
1074
- &runtime,
1075
- )
1076
- .expect("batch");
1077
- assert_eq!(evaluations.len(), 2);
1078
- let calls = runtime.calls();
1079
- assert_eq!(calls.len(), 2);
1080
- let first = serde_json::to_value(&calls[0]).expect("first");
1081
- let second = serde_json::to_value(&calls[1]).expect("second");
1082
- assert_eq!(first["context"]["shared_fact"], "shared");
1083
- assert_eq!(second["context"]["shared_fact"], "shared");
1084
- assert_eq!(first["context"]["decision_only"], "a");
1085
- assert!(second["context"].get("decision_only").is_none());
1086
- }
1087
-
1088
- #[test]
1089
- fn choice_diagnostics_measure_permutations_and_option_expansion() {
1090
- struct ChoiceRuntime;
1091
- impl DecisionRuntime for ChoiceRuntime {
1092
- fn metadata(&self) -> DecisionRuntimeMetadata {
1093
- DecisionRuntimeMetadata {
1094
- runtime: "diagnostic".into(),
1095
- model: Some("diagnostic-runtime".into()),
1096
- model_revision: Some("diagnostic-v1".into()),
1097
- prompt_revision: Some("prompt-v1".into()),
1098
- execution_method: None,
1099
- decision_schema_revision: None,
1100
- }
1101
- }
1102
-
1103
- fn decide(&self, request: &DecisionRequest) -> Result<DecisionResult, DecisionError> {
1104
- match request {
1105
- DecisionRequest::Choice { options, .. } => Ok(DecisionResult::Choice {
1106
- selected: options.first().cloned().unwrap_or_default(),
1107
- options: options
1108
- .iter()
1109
- .enumerate()
1110
- .map(|(index, value)| WeightedOption {
1111
- value: value.clone(),
1112
- probability: if index == 0 {
1113
- 0.6
1114
- } else if index == 1 {
1115
- 0.3
1116
- } else {
1117
- 0.1 / ((options.len() - 2).max(1) as f64)
1118
- },
1119
- })
1120
- .collect(),
1121
- option_mass: 0.6,
1122
- supporting_fields: vec!["employmentType".into()],
1123
- }),
1124
- _ => Err(DecisionError::new(
1125
- "DECISION_INVALID_RESULT",
1126
- "expected choice request",
1127
- )),
1128
- }
1129
- }
1130
- }
1131
-
1132
- let context = authorize_context(
1133
- "people",
1134
- "p-1",
1135
- &json!({
1136
- "id":"p-1",
1137
- "__version":1,
1138
- "employmentType":"remote"
1139
- }),
1140
- &DecisionOptions::default(),
1141
- None,
1142
- )
1143
- .expect("context");
1144
- let report = run_choice_diagnostics(
1145
- &context,
1146
- "Which option should be selected?",
1147
- &["A".into(), "B".into(), "C".into()],
1148
- Some("D"),
1149
- &ChoiceRuntime,
1150
- )
1151
- .expect("choice diagnostics");
1152
- assert_eq!(report.permutations.len(), 6);
1153
- assert!(report
1154
- .permutations
1155
- .iter()
1156
- .any(|entry| entry.order == vec!["A", "B", "C"] && entry.selected == "A"));
1157
- assert!(report
1158
- .permutations
1159
- .iter()
1160
- .any(|entry| entry.order == vec!["C", "B", "A"] && entry.selected == "C"));
1161
- let expansion = report.expansion.expect("expansion");
1162
- assert_eq!(expansion.baseline.order, vec!["A", "B", "C"]);
1163
- assert_eq!(expansion.expanded.order, vec!["A", "B", "C", "D"]);
1164
- assert_eq!(expansion.expanded.rank, 1);
1165
- }