agentevolve-optimizer 0.5.0__py3-none-any.whl

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 (414) hide show
  1. agent_evolve/__init__.py +722 -0
  2. agent_evolve/agentic.py +2800 -0
  3. agent_evolve/api.py +767 -0
  4. agent_evolve/application/__init__.py +1580 -0
  5. agent_evolve/application/action_allocation.py +744 -0
  6. agent_evolve/application/action_allocation_frame.py +347 -0
  7. agent_evolve/application/action_allocation_frame_commit.py +185 -0
  8. agent_evolve/application/action_allocation_frame_commit_v3.py +184 -0
  9. agent_evolve/application/action_allocation_frame_v3.py +338 -0
  10. agent_evolve/application/action_archive_value.py +497 -0
  11. agent_evolve/application/action_evidence_consistency.py +455 -0
  12. agent_evolve/application/action_forecast_partitioning.py +1471 -0
  13. agent_evolve/application/action_metric_projection.py +211 -0
  14. agent_evolve/application/action_role_value.py +680 -0
  15. agent_evolve/application/action_score_authorities.py +363 -0
  16. agent_evolve/application/action_structural_signature.py +116 -0
  17. agent_evolve/application/action_target_realization.py +402 -0
  18. agent_evolve/application/agentic_evolution.py +7734 -0
  19. agent_evolve/application/agentic_portfolio_residual_expert.py +835 -0
  20. agent_evolve/application/anchor_residual_identification.py +463 -0
  21. agent_evolve/application/archive_conditioned_action_target.py +208 -0
  22. agent_evolve/application/artifact_journal.py +246 -0
  23. agent_evolve/application/artifact_replay.py +347 -0
  24. agent_evolve/application/budgeted_optimizer.py +1828 -0
  25. agent_evolve/application/calibrated_campaign.py +485 -0
  26. agent_evolve/application/calibrated_current_prefix_forecast_opportunity.py +322 -0
  27. agent_evolve/application/calibrated_positive_gain_opportunity.py +1581 -0
  28. agent_evolve/application/campaign_capacity_recourse.py +254 -0
  29. agent_evolve/application/campaign_contextual_outcomes.py +119 -0
  30. agent_evolve/application/campaign_diagnostic_blocks.py +930 -0
  31. agent_evolve/application/campaign_evidence_registry.py +262 -0
  32. agent_evolve/application/campaign_execution.py +2537 -0
  33. agent_evolve/application/campaign_generation_audit.py +942 -0
  34. agent_evolve/application/campaign_learning.py +1812 -0
  35. agent_evolve/application/campaign_learning_runtime.py +1977 -0
  36. agent_evolve/application/campaign_search_phase.py +227 -0
  37. agent_evolve/application/campaign_selector_context_extension.py +220 -0
  38. agent_evolve/application/campaign_variation_envelope.py +649 -0
  39. agent_evolve/application/campaign_variation_trace.py +451 -0
  40. agent_evolve/application/candidate_archive_consequence.py +128 -0
  41. agent_evolve/application/causal_opportunity_portfolio_gate.py +385 -0
  42. agent_evolve/application/composite_outcome_updater.py +145 -0
  43. agent_evolve/application/composition_portfolio_selection.py +363 -0
  44. agent_evolve/application/concurrent_stage.py +144 -0
  45. agent_evolve/application/contextual_action_allocation.py +181 -0
  46. agent_evolve/application/contextual_campaign_outcomes.py +267 -0
  47. agent_evolve/application/contextual_campaign_planning.py +1366 -0
  48. agent_evolve/application/contextual_delayed_credit.py +651 -0
  49. agent_evolve/application/contextual_search_controller.py +2374 -0
  50. agent_evolve/application/current_prefix_forecast_opportunity.py +714 -0
  51. agent_evolve/application/decision_metric_projection.py +112 -0
  52. agent_evolve/application/derived_action_semantics.py +129 -0
  53. agent_evolve/application/detailed_evaluation.py +449 -0
  54. agent_evolve/application/earned_lineage.py +1011 -0
  55. agent_evolve/application/effective_choice_audit.py +484 -0
  56. agent_evolve/application/empirical_consequence_calibration.py +908 -0
  57. agent_evolve/application/evaluation_accounting.py +325 -0
  58. agent_evolve/application/evaluation_cache.py +199 -0
  59. agent_evolve/application/evaluation_escrow.py +547 -0
  60. agent_evolve/application/evaluation_recourse.py +253 -0
  61. agent_evolve/application/event_recorder.py +151 -0
  62. agent_evolve/application/evolution_campaign.py +1840 -0
  63. agent_evolve/application/executable_hypothesis.py +323 -0
  64. agent_evolve/application/factorial_branch_pilot.py +772 -0
  65. agent_evolve/application/finite_acquisition_capacity_recourse.py +672 -0
  66. agent_evolve/application/finite_acquisition_residual_expert.py +373 -0
  67. agent_evolve/application/finite_acquisition_variation_envelope.py +802 -0
  68. agent_evolve/application/finite_action_hypothesis_semantics.py +446 -0
  69. agent_evolve/application/finite_action_selection.py +188 -0
  70. agent_evolve/application/finite_action_set.py +306 -0
  71. agent_evolve/application/finite_action_transition.py +537 -0
  72. agent_evolve/application/finite_variation_eligibility.py +296 -0
  73. agent_evolve/application/forecast_geometry_portfolio.py +799 -0
  74. agent_evolve/application/forecast_opportunity_shadow_calibration.py +316 -0
  75. agent_evolve/application/front_proximity_admission.py +311 -0
  76. agent_evolve/application/front_proximity_parent_basis.py +458 -0
  77. agent_evolve/application/frozen_hurdle_score.py +659 -0
  78. agent_evolve/application/g3_causal_screen.py +2257 -0
  79. agent_evolve/application/g3_causal_validation.py +1046 -0
  80. agent_evolve/application/g3_postseal_curation.py +818 -0
  81. agent_evolve/application/gated_agentic_generator.py +205 -0
  82. agent_evolve/application/generation_feedback.py +293 -0
  83. agent_evolve/application/generative_proposal_journal.py +185 -0
  84. agent_evolve/application/geometry_conditional_elasticity.py +453 -0
  85. agent_evolve/application/global_wave_action_allocation.py +1151 -0
  86. agent_evolve/application/head_mass_conditional_seat.py +268 -0
  87. agent_evolve/application/identifiable_reflection_evidence.py +1147 -0
  88. agent_evolve/application/identifiable_reflection_learning.py +395 -0
  89. agent_evolve/application/identifiable_reflection_request.py +364 -0
  90. agent_evolve/application/in_memory_residual_archive.py +341 -0
  91. agent_evolve/application/insight_memory.py +1804 -0
  92. agent_evolve/application/live_runtime_manifest.py +758 -0
  93. agent_evolve/application/llm_task_queue.py +769 -0
  94. agent_evolve/application/matched_finite_action_block.py +409 -0
  95. agent_evolve/application/materialized_action_broker.py +2328 -0
  96. agent_evolve/application/materialized_action_constraints.py +83 -0
  97. agent_evolve/application/materialized_variation.py +211 -0
  98. agent_evolve/application/multi_option_evolution.py +1536 -0
  99. agent_evolve/application/outcome_adaptive_action_racing.py +2827 -0
  100. agent_evolve/application/outcome_adaptive_residual_campaign_runtime.py +580 -0
  101. agent_evolve/application/outcome_adaptive_residual_portfolio_evolution.py +3671 -0
  102. agent_evolve/application/outcome_conditioned_portfolio_selection.py +1374 -0
  103. agent_evolve/application/outcome_relation.py +193 -0
  104. agent_evolve/application/paired_allocation_comparison.py +241 -0
  105. agent_evolve/application/paired_block_schedule.py +127 -0
  106. agent_evolve/application/parent_measurement.py +226 -0
  107. agent_evolve/application/pareto_archive.py +811 -0
  108. agent_evolve/application/portfolio_campaign_runtime.py +4739 -0
  109. agent_evolve/application/portfolio_evolution.py +2950 -0
  110. agent_evolve/application/portfolio_hypothesis_observations.py +814 -0
  111. agent_evolve/application/portfolio_memory_attribution.py +581 -0
  112. agent_evolve/application/portfolio_memory_dose.py +788 -0
  113. agent_evolve/application/portfolio_memory_matched_control.py +938 -0
  114. agent_evolve/application/portfolio_memory_transfer.py +297 -0
  115. agent_evolve/application/portfolio_optimization_memory.py +363 -0
  116. agent_evolve/application/portfolio_outcome_feedback.py +1613 -0
  117. agent_evolve/application/portfolio_projection.py +335 -0
  118. agent_evolve/application/portfolio_recombination.py +2032 -0
  119. agent_evolve/application/post_evolution_reflection.py +834 -0
  120. agent_evolve/application/postcommit_rank_authority.py +245 -0
  121. agent_evolve/application/precommitted_portfolio_racing.py +2762 -0
  122. agent_evolve/application/prequential_archive_opportunity_calibration.py +1154 -0
  123. agent_evolve/application/prequential_residual_exploration.py +343 -0
  124. agent_evolve/application/prequential_score_portfolio.py +954 -0
  125. agent_evolve/application/projections.py +292 -0
  126. agent_evolve/application/protected_action_committee.py +1027 -0
  127. agent_evolve/application/protected_branch_pilot.py +376 -0
  128. agent_evolve/application/protected_current_prefix_forecast_opportunity.py +552 -0
  129. agent_evolve/application/provider_replay.py +910 -0
  130. agent_evolve/application/rank_balanced_causal_pilot.py +1372 -0
  131. agent_evolve/application/recombination_residual_expert.py +403 -0
  132. agent_evolve/application/reflection_workflow.py +571 -0
  133. agent_evolve/application/region_conditional_credit.py +911 -0
  134. agent_evolve/application/residual_campaign_runtime.py +531 -0
  135. agent_evolve/application/residual_headroom_campaign_runtime.py +459 -0
  136. agent_evolve/application/residual_headroom_ledger.py +1544 -0
  137. agent_evolve/application/residual_learning_transaction.py +396 -0
  138. agent_evolve/application/residual_portfolio_evolution.py +1228 -0
  139. agent_evolve/application/residual_reachability.py +749 -0
  140. agent_evolve/application/residual_stage_credit.py +499 -0
  141. agent_evolve/application/same_prefix_paired_audit.py +1580 -0
  142. agent_evolve/application/semantic_coverage_score_portfolio.py +838 -0
  143. agent_evolve/application/sequential_lineage_allocation.py +1017 -0
  144. agent_evolve/application/sequential_market_replay.py +1395 -0
  145. agent_evolve/application/sequential_residual_campaign_runtime.py +305 -0
  146. agent_evolve/application/sequential_residual_portfolio_evolution.py +940 -0
  147. agent_evolve/application/single_score_action_allocation.py +299 -0
  148. agent_evolve/application/source_exposure_allocation.py +906 -0
  149. agent_evolve/application/staged_memory.py +210 -0
  150. agent_evolve/application/stratified_cold_start_allocation.py +732 -0
  151. agent_evolve/application/support_guarded_hurdle_score.py +549 -0
  152. agent_evolve/application/target_conditioned_action_forecast.py +595 -0
  153. agent_evolve/application/target_conditioned_campaign.py +566 -0
  154. agent_evolve/application/treatment_assignment.py +201 -0
  155. agent_evolve/application/trusted_objective_evidence.py +217 -0
  156. agent_evolve/application/two_stage_action_evolution.py +1131 -0
  157. agent_evolve/application/v8lite_allocation_policy.py +1083 -0
  158. agent_evolve/application/v9_candidate_policy.py +1303 -0
  159. agent_evolve/bootstrap.py +108 -0
  160. agent_evolve/campaign_presets.py +517 -0
  161. agent_evolve/campaign_profiles.py +452 -0
  162. agent_evolve/campaign_variation_topology.py +288 -0
  163. agent_evolve/campaign_workload.py +950 -0
  164. agent_evolve/cli.py +797 -0
  165. agent_evolve/contract.py +241 -0
  166. agent_evolve/core/__init__.py +91 -0
  167. agent_evolve/core/action_semantics.py +411 -0
  168. agent_evolve/core/authored.py +105 -0
  169. agent_evolve/core/formatting.py +286 -0
  170. agent_evolve/core/optimization_semantics.py +324 -0
  171. agent_evolve/core/problem.py +167 -0
  172. agent_evolve/core/results.py +323 -0
  173. agent_evolve/core/stats.py +70 -0
  174. agent_evolve/core/telemetry.py +100 -0
  175. agent_evolve/domain/__init__.py +89 -0
  176. agent_evolve/domain/artifact.py +162 -0
  177. agent_evolve/domain/durable_text.py +68 -0
  178. agent_evolve/domain/event.py +1454 -0
  179. agent_evolve/domain/finite_action_set.py +426 -0
  180. agent_evolve/domain/finite_variation.py +526 -0
  181. agent_evolve/domain/generative_emission.py +559 -0
  182. agent_evolve/domain/ids.py +163 -0
  183. agent_evolve/domain/inline_text.py +106 -0
  184. agent_evolve/domain/insight.py +27 -0
  185. agent_evolve/domain/lineage.py +737 -0
  186. agent_evolve/domain/llm_task_queue.py +960 -0
  187. agent_evolve/domain/outcome.py +96 -0
  188. agent_evolve/domain/patch.py +854 -0
  189. agent_evolve/domain/typed_json.py +542 -0
  190. agent_evolve/domain/variation_space.py +158 -0
  191. agent_evolve/driver.py +1014 -0
  192. agent_evolve/harness/__init__.py +29 -0
  193. agent_evolve/harness/base.py +242 -0
  194. agent_evolve/harness/directives.py +163 -0
  195. agent_evolve/harness/generative_seal.py +479 -0
  196. agent_evolve/harness/registry.py +41 -0
  197. agent_evolve/infrastructure/__init__.py +39 -0
  198. agent_evolve/infrastructure/artifacts/__init__.py +6 -0
  199. agent_evolve/infrastructure/artifacts/_verification.py +67 -0
  200. agent_evolve/infrastructure/artifacts/filesystem.py +343 -0
  201. agent_evolve/infrastructure/artifacts/in_memory.py +73 -0
  202. agent_evolve/infrastructure/asyncio_runtime.py +109 -0
  203. agent_evolve/infrastructure/authored_runtime.py +188 -0
  204. agent_evolve/infrastructure/authored_worker.py +171 -0
  205. agent_evolve/infrastructure/clock.py +53 -0
  206. agent_evolve/infrastructure/events/__init__.py +6 -0
  207. agent_evolve/infrastructure/events/_validation.py +89 -0
  208. agent_evolve/infrastructure/events/in_memory.py +56 -0
  209. agent_evolve/infrastructure/events/jsonl.py +193 -0
  210. agent_evolve/infrastructure/exception_provenance.py +215 -0
  211. agent_evolve/infrastructure/ids.py +118 -0
  212. agent_evolve/infrastructure/lineage_codec.py +1836 -0
  213. agent_evolve/infrastructure/outcome_adaptive_phase_journal.py +170 -0
  214. agent_evolve/infrastructure/residual_headroom_journal.py +221 -0
  215. agent_evolve/infrastructure/resource_lease.py +370 -0
  216. agent_evolve/infrastructure/sanitization/__init__.py +8 -0
  217. agent_evolve/infrastructure/sanitization/strict_json.py +484 -0
  218. agent_evolve/infrastructure/sequential_phase_journal.py +170 -0
  219. agent_evolve/infrastructure/stream_liveness.py +383 -0
  220. agent_evolve/infrastructure/subprocess_boundary.py +136 -0
  221. agent_evolve/integrations/__init__.py +1 -0
  222. agent_evolve/integrations/botorch/__init__.py +28 -0
  223. agent_evolve/integrations/botorch/finite_qlognehvi.py +190 -0
  224. agent_evolve/integrations/botorch/finite_qlognehvi_batch.py +155 -0
  225. agent_evolve/integrations/botorch/finite_qlognehvi_batch_identity.py +20 -0
  226. agent_evolve/integrations/botorch/finite_qlognehvi_batch_worker.py +55 -0
  227. agent_evolve/integrations/botorch/finite_qlognehvi_identity.py +22 -0
  228. agent_evolve/integrations/botorch/finite_qlognehvi_worker.py +55 -0
  229. agent_evolve/integrations/botorch/subprocess_qlognehvi.py +261 -0
  230. agent_evolve/integrations/botorch/subprocess_qlognehvi_batch.py +273 -0
  231. agent_evolve/integrations/completion.py +242 -0
  232. agent_evolve/integrations/pydantic_ai/__init__.py +441 -0
  233. agent_evolve/integrations/pydantic_ai/action_forecast.py +1068 -0
  234. agent_evolve/integrations/pydantic_ai/agentic_generator.py +2308 -0
  235. agent_evolve/integrations/pydantic_ai/async_generator.py +1604 -0
  236. agent_evolve/integrations/pydantic_ai/boundary_codec.py +1526 -0
  237. agent_evolve/integrations/pydantic_ai/calibrated_portfolio_campaign.py +756 -0
  238. agent_evolve/integrations/pydantic_ai/calibrated_portfolio_selection.py +7537 -0
  239. agent_evolve/integrations/pydantic_ai/campaign_acquisition.py +609 -0
  240. agent_evolve/integrations/pydantic_ai/execution_binding.py +138 -0
  241. agent_evolve/integrations/pydantic_ai/forecast_geometry_action_committee.py +217 -0
  242. agent_evolve/integrations/pydantic_ai/harness.py +159 -0
  243. agent_evolve/integrations/pydantic_ai/heterogeneous_model_execution.py +306 -0
  244. agent_evolve/integrations/pydantic_ai/hierarchical_residual_adaptive_semantic_view.py +179 -0
  245. agent_evolve/integrations/pydantic_ai/json_schema_dialect.py +108 -0
  246. agent_evolve/integrations/pydantic_ai/materialized_hierarchical_residual_expert.py +952 -0
  247. agent_evolve/integrations/pydantic_ai/materialized_portfolio_judge.py +520 -0
  248. agent_evolve/integrations/pydantic_ai/model_execution_profile.py +659 -0
  249. agent_evolve/integrations/pydantic_ai/outbound_request_manifest.py +1170 -0
  250. agent_evolve/integrations/pydantic_ai/portable_residual_consequence_features.py +575 -0
  251. agent_evolve/integrations/pydantic_ai/portfolio_selection.py +422 -0
  252. agent_evolve/integrations/pydantic_ai/progress_aware_openrouter.py +416 -0
  253. agent_evolve/integrations/pydantic_ai/provider_attempt_join.py +1523 -0
  254. agent_evolve/integrations/pydantic_ai/provider_free_calibrated_runner.py +607 -0
  255. agent_evolve/integrations/pydantic_ai/queued_runner.py +2634 -0
  256. agent_evolve/integrations/pydantic_ai/reconciled_residual_reachability.py +1417 -0
  257. agent_evolve/integrations/pydantic_ai/residual_forecast_geometry.py +445 -0
  258. agent_evolve/integrations/pydantic_ai/residual_reachability.py +674 -0
  259. agent_evolve/integrations/pydantic_ai/residual_semantic_cells.py +239 -0
  260. agent_evolve/integrations/pydantic_ai/sealed_output_replay.py +1068 -0
  261. agent_evolve/integrations/pydantic_ai/semantic_coverage_residual_portfolio.py +770 -0
  262. agent_evolve/integrations/pydantic_ai/semantic_decision_replay.py +383 -0
  263. agent_evolve/integrations/pydantic_ai/support_adaptive_residual_portfolio.py +135 -0
  264. agent_evolve/integrations/pydantic_ai/trusted_residual_prompt_context.py +143 -0
  265. agent_evolve/integrations/pydantic_ai/validated_openrouter_model.py +107 -0
  266. agent_evolve/integrations/pymoo_adapter.py +242 -0
  267. agent_evolve/policies/__init__.py +17 -0
  268. agent_evolve/policies/check.py +469 -0
  269. agent_evolve/policies/emit_scaffold.py +451 -0
  270. agent_evolve/policies/feedback/__init__.py +37 -0
  271. agent_evolve/policies/feedback/held_out_asn.py +1325 -0
  272. agent_evolve/policies/genetic.py +607 -0
  273. agent_evolve/policies/llm_backoff.py +183 -0
  274. agent_evolve/policies/llm_chooser.py +226 -0
  275. agent_evolve/policies/llm_generator.py +1760 -0
  276. agent_evolve/policies/llm_init.py +267 -0
  277. agent_evolve/policies/llm_operator.py +109 -0
  278. agent_evolve/policies/llm_prior.py +194 -0
  279. agent_evolve/policies/llm_surrogate.py +334 -0
  280. agent_evolve/policies/measurement_evidence.py +704 -0
  281. agent_evolve/policies/memory/__init__.py +223 -0
  282. agent_evolve/policies/memory/balanced_subset_blocks.py +707 -0
  283. agent_evolve/policies/memory/compatibility_matching.py +593 -0
  284. agent_evolve/policies/memory/global_falsification.py +1841 -0
  285. agent_evolve/policies/memory/prompt_shape.py +503 -0
  286. agent_evolve/policies/memory/randomized_subset.py +714 -0
  287. agent_evolve/policies/memory/staged_causal.py +1270 -0
  288. agent_evolve/policies/memory/treatment_compliance.py +759 -0
  289. agent_evolve/policies/objective_resolution/__init__.py +17 -0
  290. agent_evolve/policies/objective_resolution/fixed_grid.py +364 -0
  291. agent_evolve/policies/operator_portfolio.py +407 -0
  292. agent_evolve/policies/reguidance.py +1133 -0
  293. agent_evolve/policies/reward/__init__.py +83 -0
  294. agent_evolve/policies/reward/affine_candidate_consequence.py +156 -0
  295. agent_evolve/policies/reward/affine_candidate_consequence_3d.py +159 -0
  296. agent_evolve/policies/reward/affine_hypervolume.py +490 -0
  297. agent_evolve/policies/reward/affine_hypervolume_3d.py +567 -0
  298. agent_evolve/policies/reward/contextual_marginal_utility.py +318 -0
  299. agent_evolve/policies/reward/frozen_archive.py +360 -0
  300. agent_evolve/policies/reward/frozen_wave_archive.py +368 -0
  301. agent_evolve/policies/search_state.py +208 -0
  302. agent_evolve/policies/selection/__init__.py +345 -0
  303. agent_evolve/policies/selection/acquisition_certified_slate.py +684 -0
  304. agent_evolve/policies/selection/affine_frontier_context.py +330 -0
  305. agent_evolve/policies/selection/affine_frontier_target.py +473 -0
  306. agent_evolve/policies/selection/archive_elite.py +1346 -0
  307. agent_evolve/policies/selection/calibrated_portfolio_binding.py +640 -0
  308. agent_evolve/policies/selection/calibrated_slate.py +1394 -0
  309. agent_evolve/policies/selection/calibrated_slate_codec.py +579 -0
  310. agent_evolve/policies/selection/common_candidate_pool.py +685 -0
  311. agent_evolve/policies/selection/diagnostic_sampling.py +319 -0
  312. agent_evolve/policies/selection/disjoint_pairs.py +479 -0
  313. agent_evolve/policies/selection/elite_explorer.py +719 -0
  314. agent_evolve/policies/selection/finite_action.py +187 -0
  315. agent_evolve/policies/selection/finite_option_prompt_projection.py +377 -0
  316. agent_evolve/policies/selection/finite_palette_evidence.py +247 -0
  317. agent_evolve/policies/selection/forecast_calibration.py +922 -0
  318. agent_evolve/policies/selection/frontier_probe_slate.py +814 -0
  319. agent_evolve/policies/selection/frozen_archive_pairs.py +762 -0
  320. agent_evolve/policies/selection/full_support_slate.py +91 -0
  321. agent_evolve/policies/selection/meaningful_direction.py +240 -0
  322. agent_evolve/policies/selection/memory_dose_feasibility.py +259 -0
  323. agent_evolve/policies/selection/model_anchored_slate.py +826 -0
  324. agent_evolve/policies/selection/phenotype_recourse.py +979 -0
  325. agent_evolve/policies/selection/proposal_support.py +368 -0
  326. agent_evolve/policies/selection/random_portfolio.py +254 -0
  327. agent_evolve/policies/selection/regret_bounded_slate.py +1084 -0
  328. agent_evolve/policies/selection/residual_frontier.py +463 -0
  329. agent_evolve/policies/selection/residual_frontier_target.py +605 -0
  330. agent_evolve/policies/selection/structural_posterior_slate.py +1571 -0
  331. agent_evolve/policies/selection/target_conditioned_allocator.py +648 -0
  332. agent_evolve/policies/selection/target_conditioned_features.py +812 -0
  333. agent_evolve/policies/selection/target_conditioned_prequential.py +1527 -0
  334. agent_evolve/policies/selection/task_keyed_palette.py +906 -0
  335. agent_evolve/policies/semantics.py +147 -0
  336. agent_evolve/policies/structure.py +362 -0
  337. agent_evolve/policies/structured_output_budget.py +62 -0
  338. agent_evolve/policies/surrogate.py +696 -0
  339. agent_evolve/policies/variation/__init__.py +1 -0
  340. agent_evolve/policies/variation/compositional_finite_catalog.py +426 -0
  341. agent_evolve/policies/variation/crossover_inheritance.py +575 -0
  342. agent_evolve/policies/variation/disjoint_recombination.py +611 -0
  343. agent_evolve/policies/variation/exact_composition_capacity.py +214 -0
  344. agent_evolve/policies/variation/exact_parent_crossover.py +950 -0
  345. agent_evolve/policies/variation/multiscale_restart_catalog.py +372 -0
  346. agent_evolve/policies/variation/source_union_finite_catalog.py +403 -0
  347. agent_evolve/policies/variation/typed_patch.py +1981 -0
  348. agent_evolve/policies/weighted_prior.py +394 -0
  349. agent_evolve/ports/__init__.py +383 -0
  350. agent_evolve/ports/action_allocation.py +733 -0
  351. agent_evolve/ports/action_allocation_frame.py +1153 -0
  352. agent_evolve/ports/action_allocation_frame_commit.py +294 -0
  353. agent_evolve/ports/action_allocation_frame_commit_v3.py +432 -0
  354. agent_evolve/ports/action_allocation_frame_v3.py +995 -0
  355. agent_evolve/ports/action_forecast.py +1568 -0
  356. agent_evolve/ports/action_metric_projection.py +165 -0
  357. agent_evolve/ports/agentic_generator.py +1561 -0
  358. agent_evolve/ports/archive_context.py +136 -0
  359. agent_evolve/ports/artifact_sanitizer.py +44 -0
  360. agent_evolve/ports/artifact_store.py +225 -0
  361. agent_evolve/ports/clock.py +13 -0
  362. agent_evolve/ports/contextual_search_allocation.py +827 -0
  363. agent_evolve/ports/decision_metric_projection.py +258 -0
  364. agent_evolve/ports/event_store.py +55 -0
  365. agent_evolve/ports/executable_hypothesis.py +557 -0
  366. agent_evolve/ports/finite_acquisition.py +377 -0
  367. agent_evolve/ports/finite_acquisition_batch.py +296 -0
  368. agent_evolve/ports/finite_acquisition_batch_json.py +164 -0
  369. agent_evolve/ports/finite_acquisition_json.py +247 -0
  370. agent_evolve/ports/finite_acquisition_space.py +168 -0
  371. agent_evolve/ports/finite_action_selection.py +348 -0
  372. agent_evolve/ports/finite_action_set.py +256 -0
  373. agent_evolve/ports/frontier_target.py +396 -0
  374. agent_evolve/ports/generation_failure.py +43 -0
  375. agent_evolve/ports/hard_feasibility.py +233 -0
  376. agent_evolve/ports/id_factory.py +34 -0
  377. agent_evolve/ports/llm_task_queue.py +93 -0
  378. agent_evolve/ports/objective_resolution.py +419 -0
  379. agent_evolve/ports/paired_allocation_comparison.py +401 -0
  380. agent_evolve/ports/paired_block_schedule.py +475 -0
  381. agent_evolve/ports/parent_measurement.py +336 -0
  382. agent_evolve/ports/portfolio_memory_dose.py +643 -0
  383. agent_evolve/ports/portfolio_selection.py +3169 -0
  384. agent_evolve/ports/postcommit_rank_authority.py +467 -0
  385. agent_evolve/ports/presented_action_evidence.py +794 -0
  386. agent_evolve/ports/resource_lease.py +162 -0
  387. agent_evolve/ports/structured_generator.py +734 -0
  388. agent_evolve/ports/structured_output_budget.py +120 -0
  389. agent_evolve/ports/subprocess_boundary.py +138 -0
  390. agent_evolve/ports/treatment_assignment.py +466 -0
  391. agent_evolve/ports/variation_catalog.py +76 -0
  392. agent_evolve/ports/variation_source.py +226 -0
  393. agent_evolve/proposal_mode.py +157 -0
  394. agent_evolve/proposers/__init__.py +10 -0
  395. agent_evolve/proposers/random_proposer.py +188 -0
  396. agent_evolve/provider_accounting.py +163 -0
  397. agent_evolve/py.typed +0 -0
  398. agent_evolve/reference_method.py +1570 -0
  399. agent_evolve/session/__init__.py +11 -0
  400. agent_evolve/session/authorship.py +864 -0
  401. agent_evolve/session/evaluate.py +236 -0
  402. agent_evolve/session/fidelity.py +237 -0
  403. agent_evolve/session/genetic_loop.py +742 -0
  404. agent_evolve/session/loop.py +803 -0
  405. agent_evolve/session/screening.py +671 -0
  406. agent_evolve/settings.py +376 -0
  407. agent_evolve/workload_kit.py +368 -0
  408. agent_evolve/workload_prompt.py +398 -0
  409. agentevolve_optimizer-0.5.0.dist-info/METADATA +599 -0
  410. agentevolve_optimizer-0.5.0.dist-info/RECORD +414 -0
  411. agentevolve_optimizer-0.5.0.dist-info/WHEEL +5 -0
  412. agentevolve_optimizer-0.5.0.dist-info/entry_points.txt +2 -0
  413. agentevolve_optimizer-0.5.0.dist-info/licenses/LICENSE +21 -0
  414. agentevolve_optimizer-0.5.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1153 @@
1
+ """Authenticated forecast frames and audited allocation-surface ports.
2
+
3
+ The original allocation port deliberately accepts only a complete resolved
4
+ forecast batch. A partition block must not be relabelled as such a batch just
5
+ to reach an allocator. This module provides a parallel, provider-neutral
6
+ boundary whose source remains an exact complete batch, partition block, or
7
+ authenticated subset of one partition block.
8
+
9
+ No benchmark metric or provider representation is interpreted here. A bound
10
+ diagnostic policy supplies the single domain-dependent judgement used by the
11
+ surface gate: whether one candidate score is at a benchmark-defined boundary
12
+ or extreme. Counts, ties, gaps, and score-multiset identities remain trusted
13
+ generic arithmetic.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import hashlib
19
+ import json
20
+ import math
21
+ import re
22
+ from dataclasses import dataclass, field
23
+ from enum import Enum
24
+ from typing import Protocol, runtime_checkable
25
+
26
+ from agent_evolve.domain.patch import require_sha256
27
+ from agent_evolve.ports.action_allocation import (
28
+ AllocatedActionMember,
29
+ ForecastPortfolioUtilityBinding,
30
+ PortfolioAllocationScore,
31
+ )
32
+ from agent_evolve.ports.action_forecast import (
33
+ ActionForecastBlockRequest,
34
+ ActionForecastRequest,
35
+ ResolvedActionForecast,
36
+ ResolvedActionForecastBatch,
37
+ ResolvedActionForecastBlock,
38
+ validate_resolved_action_forecast_block,
39
+ validate_resolved_action_forecasts,
40
+ )
41
+
42
+
43
+ _TOKEN = re.compile(r"^[a-z][a-z0-9_.-]{0,95}$")
44
+ _OPTION_ID = re.compile(r"^[a-z][a-z0-9_.-]{0,255}$")
45
+ _CANDIDATE_LABEL = re.compile(r"^row_[0-9]{8}$")
46
+ _FRAME_DOMAIN = b"agent-evolve:resolved-action-allocation-frame:v1\x00"
47
+ _SUBSET_POLICY_DOMAIN = b"agent-evolve:allocation-frame-subset-policy:v1\x00"
48
+ _ELIGIBLE_DOMAIN = b"agent-evolve:frame-allocation-eligible-set:v1\x00"
49
+ _REQUEST_DOMAIN = b"agent-evolve:frame-action-allocation-request:v1\x00"
50
+ _DECISION_DOMAIN = b"agent-evolve:frame-action-portfolio-decision:v1\x00"
51
+ _DIAGNOSTIC_POLICY_DOMAIN = (
52
+ b"agent-evolve:allocation-score-diagnostic-policy:v1\x00"
53
+ )
54
+ _GATE_POLICY_DOMAIN = b"agent-evolve:allocation-surface-gate-policy:v1\x00"
55
+ _SCORE_MULTISET_DOMAIN = b"agent-evolve:allocation-score-multiset:v1\x00"
56
+ _STEP_AUDIT_DOMAIN = b"agent-evolve:allocation-surface-step-audit:v1\x00"
57
+ _SURFACE_AUDIT_DOMAIN = b"agent-evolve:allocation-surface-audit:v1\x00"
58
+ _RESULT_DOMAIN = b"agent-evolve:audited-frame-allocation-result:v1\x00"
59
+
60
+
61
+ def _canonical_json(value: object) -> bytes:
62
+ return json.dumps(
63
+ value,
64
+ allow_nan=False,
65
+ ensure_ascii=True,
66
+ separators=(",", ":"),
67
+ sort_keys=True,
68
+ ).encode("ascii")
69
+
70
+
71
+ def _hash(domain: bytes, value: object) -> str:
72
+ return hashlib.sha256(domain + _canonical_json(value)).hexdigest()
73
+
74
+
75
+ def _finite_float(value: object, name: str) -> float:
76
+ if type(value) is not float or not math.isfinite(value):
77
+ raise TypeError(f"{name} must be a finite canonical float")
78
+ return value
79
+
80
+
81
+ def _token(value: object, name: str) -> str:
82
+ if type(value) is not str or _TOKEN.fullmatch(value) is None:
83
+ raise ValueError(f"{name} must use the closed token grammar")
84
+ return value
85
+
86
+
87
+ def _canonical_parent_receipts(values: tuple[str, ...]) -> None:
88
+ if type(values) is not tuple or any(type(value) is not str for value in values):
89
+ raise TypeError("parent_receipt_sha256s must be an exact digest tuple")
90
+ for index, value in enumerate(values):
91
+ require_sha256(value, f"parent_receipt_sha256s[{index}]")
92
+ if values != tuple(sorted(set(values))):
93
+ raise ValueError("parent receipts must be unique and canonically sorted")
94
+
95
+
96
+ class ActionForecastAllocationFrameKind(str, Enum):
97
+ COMPLETE = "complete"
98
+ PARTITION_BLOCK = "partition_block"
99
+ PARTITION_BLOCK_SUBSET = "partition_block_subset"
100
+
101
+
102
+ @dataclass(frozen=True, slots=True, eq=False)
103
+ class ActionAllocationFrameSubsetPolicyBinding:
104
+ """Identified rule selecting allocation rows from one resolved block."""
105
+
106
+ policy_id: str
107
+ policy_version: int
108
+ policy_definition_sha256: str
109
+
110
+ def __post_init__(self) -> None:
111
+ _token(self.policy_id, "policy_id")
112
+ if type(self.policy_version) is not int or self.policy_version <= 0:
113
+ raise ValueError("policy_version must be a positive exact integer")
114
+ require_sha256(self.policy_definition_sha256, "policy_definition_sha256")
115
+
116
+ def _unsigned_record(self) -> dict[str, object]:
117
+ self.__post_init__()
118
+ return {
119
+ "schema_version": 1,
120
+ "policy_id": self.policy_id,
121
+ "policy_version": self.policy_version,
122
+ "policy_definition_sha256": self.policy_definition_sha256,
123
+ }
124
+
125
+ @property
126
+ def binding_sha256(self) -> str:
127
+ return _hash(_SUBSET_POLICY_DOMAIN, self._unsigned_record())
128
+
129
+ def to_record(self) -> dict[str, object]:
130
+ return {**self._unsigned_record(), "binding_sha256": self.binding_sha256}
131
+
132
+ def __eq__(self, other: object) -> bool:
133
+ return (
134
+ type(self) is ActionAllocationFrameSubsetPolicyBinding
135
+ and type(other) is ActionAllocationFrameSubsetPolicyBinding
136
+ and self.binding_sha256 == other.binding_sha256
137
+ )
138
+
139
+ __hash__ = None
140
+
141
+
142
+ @dataclass(frozen=True, slots=True, eq=False)
143
+ class ResolvedActionForecastAllocationFrame:
144
+ """Exact allocation view over a complete batch, block, or block subset.
145
+
146
+ Source objects are retained so public validation can authenticate forecast
147
+ values rather than trusting copied IDs. The durable record stores only the
148
+ source receipt and exact ordered row/option identities.
149
+ """
150
+
151
+ request: ActionForecastRequest = field(repr=False, compare=False)
152
+ frame_kind: ActionForecastAllocationFrameKind
153
+ global_row_indices: tuple[int, ...]
154
+ parent_receipt_sha256s: tuple[str, ...]
155
+ complete_batch: ResolvedActionForecastBatch | None = field(
156
+ default=None,
157
+ repr=False,
158
+ compare=False,
159
+ )
160
+ block_request: ActionForecastBlockRequest | None = field(
161
+ default=None,
162
+ repr=False,
163
+ compare=False,
164
+ )
165
+ resolved_block: ResolvedActionForecastBlock | None = field(
166
+ default=None,
167
+ repr=False,
168
+ compare=False,
169
+ )
170
+ subset_policy: ActionAllocationFrameSubsetPolicyBinding | None = None
171
+ _validated_receipt_sha256: str | None = field(
172
+ default=None,
173
+ init=False,
174
+ repr=False,
175
+ compare=False,
176
+ )
177
+
178
+ def __post_init__(self) -> None:
179
+ if self._validated_receipt_sha256 is not None:
180
+ require_sha256(
181
+ self._validated_receipt_sha256,
182
+ "_validated_receipt_sha256",
183
+ )
184
+ return
185
+ if type(self.request) is not ActionForecastRequest:
186
+ raise TypeError("request must be an exact ActionForecastRequest")
187
+ self.request.__post_init__()
188
+ if type(self.frame_kind) is not ActionForecastAllocationFrameKind:
189
+ raise TypeError("frame_kind must be an exact allocation-frame kind")
190
+ if type(self.global_row_indices) is not tuple or not self.global_row_indices:
191
+ raise ValueError("global_row_indices must be a non-empty exact tuple")
192
+ if any(type(value) is not int for value in self.global_row_indices):
193
+ raise TypeError("global_row_indices must contain exact integers")
194
+ if self.global_row_indices != tuple(sorted(set(self.global_row_indices))):
195
+ raise ValueError("global rows must be unique and in global order")
196
+ contract = self.request.finite_variation_contract
197
+ if any(value < 0 or value >= len(contract.options) for value in self.global_row_indices):
198
+ raise ValueError("an allocation-frame row is outside the finite contract")
199
+ _canonical_parent_receipts(self.parent_receipt_sha256s)
200
+
201
+ if self.frame_kind is ActionForecastAllocationFrameKind.COMPLETE:
202
+ if type(self.complete_batch) is not ResolvedActionForecastBatch:
203
+ raise TypeError("complete frames require an exact resolved batch")
204
+ if self.block_request is not None or self.resolved_block is not None:
205
+ raise ValueError("complete frames forbid partition-block sources")
206
+ if self.subset_policy is not None:
207
+ raise ValueError("complete frames forbid a subset policy")
208
+ validate_resolved_action_forecasts(self.request, self.complete_batch)
209
+ if self.global_row_indices != tuple(range(len(contract.options))):
210
+ raise ValueError("complete frames must cover every global row")
211
+ else:
212
+ if self.complete_batch is not None:
213
+ raise ValueError("partition frames forbid a complete batch")
214
+ if type(self.block_request) is not ActionForecastBlockRequest:
215
+ raise TypeError("partition frames require an exact block request")
216
+ if type(self.resolved_block) is not ResolvedActionForecastBlock:
217
+ raise TypeError("partition frames require an exact resolved block")
218
+ if self.block_request.request != self.request:
219
+ raise ValueError("block request carries another forecast request")
220
+ validate_resolved_action_forecast_block(
221
+ self.block_request,
222
+ self.resolved_block,
223
+ )
224
+ spec = self.block_request.block
225
+ full_indices = tuple(range(spec.global_row_start, spec.global_row_stop))
226
+ if self.frame_kind is ActionForecastAllocationFrameKind.PARTITION_BLOCK:
227
+ if self.global_row_indices != full_indices:
228
+ raise ValueError("full block frame differs from its block rows")
229
+ if self.subset_policy is not None:
230
+ raise ValueError("full block frames forbid a subset policy")
231
+ else:
232
+ if type(self.subset_policy) is not (
233
+ ActionAllocationFrameSubsetPolicyBinding
234
+ ):
235
+ raise TypeError("block-subset frames require a subset policy")
236
+ self.subset_policy.__post_init__()
237
+ if not self.parent_receipt_sha256s:
238
+ raise ValueError(
239
+ "block-subset frames require an authenticated parent receipt"
240
+ )
241
+ if not set(self.global_row_indices).issubset(full_indices):
242
+ raise ValueError("block-subset rows escape the resolved block")
243
+
244
+ expected = tuple(contract.options[index] for index in self.global_row_indices)
245
+ observed = self.forecasts
246
+ if len(observed) != len(expected):
247
+ raise ValueError("frame forecasts differ from its exact row count")
248
+ for option, forecast in zip(expected, observed, strict=True):
249
+ if (
250
+ forecast.option_id != option.option_id
251
+ or forecast.option_identity_sha256 != option.identity_sha256
252
+ or forecast.child_configuration_sha256
253
+ != option.child_configuration_sha256
254
+ or forecast.family != option.family
255
+ ):
256
+ raise ValueError("allocation frame changed a finite option identity")
257
+ object.__setattr__(
258
+ self,
259
+ "_validated_receipt_sha256",
260
+ _hash(_FRAME_DOMAIN, self._record_from_validated_values()),
261
+ )
262
+
263
+ @property
264
+ def forecasts(self) -> tuple[ResolvedActionForecast, ...]:
265
+ if self.frame_kind is ActionForecastAllocationFrameKind.COMPLETE:
266
+ assert self.complete_batch is not None
267
+ return self.complete_batch.forecasts
268
+ assert self.block_request is not None
269
+ assert self.resolved_block is not None
270
+ start = self.block_request.block.global_row_start
271
+ return tuple(
272
+ self.resolved_block.forecasts[index - start]
273
+ for index in self.global_row_indices
274
+ )
275
+
276
+ @property
277
+ def source_forecast_receipt_sha256(self) -> str:
278
+ if self.frame_kind is ActionForecastAllocationFrameKind.COMPLETE:
279
+ assert self.complete_batch is not None
280
+ return self.complete_batch.receipt_sha256
281
+ assert self.resolved_block is not None
282
+ return self.resolved_block.receipt_sha256
283
+
284
+ @property
285
+ def policy_identity(self) -> tuple[str, int, str]:
286
+ source = self.complete_batch or self.resolved_block
287
+ assert source is not None
288
+ return (
289
+ source.policy_id,
290
+ source.policy_version,
291
+ source.policy_definition_sha256,
292
+ )
293
+
294
+ def _record_from_validated_values(self) -> dict[str, object]:
295
+ contract = self.request.finite_variation_contract
296
+ policy_id, policy_version, policy_definition_sha256 = self.policy_identity
297
+ return {
298
+ "schema_version": 1,
299
+ "frame_kind": self.frame_kind.value,
300
+ "request_sha256": self.request.request_sha256,
301
+ "context_sha256": self.request.context_sha256,
302
+ "finite_contract_identity_sha256": contract.identity_sha256,
303
+ "source_forecast_receipt_sha256": (
304
+ self.source_forecast_receipt_sha256
305
+ ),
306
+ "parent_receipt_sha256s": list(self.parent_receipt_sha256s),
307
+ "global_row_indices": list(self.global_row_indices),
308
+ "options": [
309
+ {
310
+ "global_row_index": index,
311
+ "option_id": option.option_id,
312
+ "option_identity_sha256": option.identity_sha256,
313
+ "child_configuration_sha256": (
314
+ option.child_configuration_sha256
315
+ ),
316
+ "family": option.family,
317
+ }
318
+ for index, option in zip(
319
+ self.global_row_indices,
320
+ (contract.options[value] for value in self.global_row_indices),
321
+ strict=True,
322
+ )
323
+ ],
324
+ "forecast_policy": {
325
+ "policy_id": policy_id,
326
+ "policy_version": policy_version,
327
+ "policy_definition_sha256": policy_definition_sha256,
328
+ },
329
+ "partition": (
330
+ None
331
+ if self.block_request is None
332
+ else {
333
+ "layout_sha256": self.block_request.layout.layout_sha256,
334
+ "block_request_sha256": (
335
+ self.block_request.block_request_sha256
336
+ ),
337
+ "block_spec_sha256": (
338
+ self.block_request.block.block_spec_sha256
339
+ ),
340
+ "block_index": self.block_request.block.block_index,
341
+ "global_row_start": (
342
+ self.block_request.block.global_row_start
343
+ ),
344
+ "global_row_stop": self.block_request.block.global_row_stop,
345
+ }
346
+ ),
347
+ "subset_policy": (
348
+ None
349
+ if self.subset_policy is None
350
+ else self.subset_policy.to_record()
351
+ ),
352
+ }
353
+
354
+ def _unsigned_record(self) -> dict[str, object]:
355
+ self.__post_init__()
356
+ return self._record_from_validated_values()
357
+
358
+ @property
359
+ def receipt_sha256(self) -> str:
360
+ self.__post_init__()
361
+ assert self._validated_receipt_sha256 is not None
362
+ return self._validated_receipt_sha256
363
+
364
+ def to_record(self) -> dict[str, object]:
365
+ return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
366
+
367
+ def __eq__(self, other: object) -> bool:
368
+ return (
369
+ type(self) is ResolvedActionForecastAllocationFrame
370
+ and type(other) is ResolvedActionForecastAllocationFrame
371
+ and self.receipt_sha256 == other.receipt_sha256
372
+ )
373
+
374
+ __hash__ = None
375
+
376
+
377
+ def bind_complete_action_forecast_allocation_frame(
378
+ request: ActionForecastRequest,
379
+ forecasts: ResolvedActionForecastBatch,
380
+ *,
381
+ parent_receipt_sha256s: tuple[str, ...] = (),
382
+ ) -> ResolvedActionForecastAllocationFrame:
383
+ return ResolvedActionForecastAllocationFrame(
384
+ request=request,
385
+ frame_kind=ActionForecastAllocationFrameKind.COMPLETE,
386
+ global_row_indices=tuple(range(len(request.finite_variation_contract.options))),
387
+ parent_receipt_sha256s=parent_receipt_sha256s,
388
+ complete_batch=forecasts,
389
+ )
390
+
391
+
392
+ def bind_action_forecast_block_allocation_frame(
393
+ block_request: ActionForecastBlockRequest,
394
+ forecasts: ResolvedActionForecastBlock,
395
+ *,
396
+ parent_receipt_sha256s: tuple[str, ...] = (),
397
+ ) -> ResolvedActionForecastAllocationFrame:
398
+ spec = block_request.block
399
+ return ResolvedActionForecastAllocationFrame(
400
+ request=block_request.request,
401
+ frame_kind=ActionForecastAllocationFrameKind.PARTITION_BLOCK,
402
+ global_row_indices=tuple(range(spec.global_row_start, spec.global_row_stop)),
403
+ parent_receipt_sha256s=parent_receipt_sha256s,
404
+ block_request=block_request,
405
+ resolved_block=forecasts,
406
+ )
407
+
408
+
409
+ def bind_action_forecast_block_subset_allocation_frame(
410
+ block_request: ActionForecastBlockRequest,
411
+ forecasts: ResolvedActionForecastBlock,
412
+ *,
413
+ included_global_row_indices: tuple[int, ...],
414
+ subset_policy: ActionAllocationFrameSubsetPolicyBinding,
415
+ parent_receipt_sha256s: tuple[str, ...],
416
+ ) -> ResolvedActionForecastAllocationFrame:
417
+ return ResolvedActionForecastAllocationFrame(
418
+ request=block_request.request,
419
+ frame_kind=ActionForecastAllocationFrameKind.PARTITION_BLOCK_SUBSET,
420
+ global_row_indices=included_global_row_indices,
421
+ parent_receipt_sha256s=parent_receipt_sha256s,
422
+ block_request=block_request,
423
+ resolved_block=forecasts,
424
+ subset_policy=subset_policy,
425
+ )
426
+
427
+
428
+ @dataclass(frozen=True, slots=True, eq=False)
429
+ class FrameActionAllocationRequest:
430
+ frame: ResolvedActionForecastAllocationFrame
431
+ eligible_option_ids: tuple[str, ...]
432
+ portfolio_size: int
433
+ utility: ForecastPortfolioUtilityBinding
434
+ _eligible_options_sha256_cache: str | None = field(
435
+ default=None,
436
+ init=False,
437
+ repr=False,
438
+ compare=False,
439
+ )
440
+ _request_sha256_cache: str | None = field(
441
+ default=None,
442
+ init=False,
443
+ repr=False,
444
+ compare=False,
445
+ )
446
+
447
+ def __post_init__(self) -> None:
448
+ if (
449
+ self._eligible_options_sha256_cache is not None
450
+ and self._request_sha256_cache is not None
451
+ ):
452
+ require_sha256(
453
+ self._eligible_options_sha256_cache,
454
+ "_eligible_options_sha256_cache",
455
+ )
456
+ require_sha256(
457
+ self._request_sha256_cache,
458
+ "_request_sha256_cache",
459
+ )
460
+ return
461
+ if type(self.frame) is not ResolvedActionForecastAllocationFrame:
462
+ raise TypeError("frame must be an exact allocation frame")
463
+ self.frame.__post_init__()
464
+ if type(self.eligible_option_ids) is not tuple or any(
465
+ type(value) is not str or _OPTION_ID.fullmatch(value) is None
466
+ for value in self.eligible_option_ids
467
+ ):
468
+ raise TypeError("eligible_option_ids must be an exact option-ID tuple")
469
+ if not self.eligible_option_ids:
470
+ raise ValueError("eligible_option_ids must be non-empty")
471
+ if self.eligible_option_ids != tuple(sorted(set(self.eligible_option_ids))):
472
+ raise ValueError("eligible option IDs must be unique and canonical")
473
+ available = {value.option_id for value in self.frame.forecasts}
474
+ if not set(self.eligible_option_ids).issubset(available):
475
+ raise ValueError("eligible option IDs escape the authenticated frame")
476
+ if type(self.portfolio_size) is not int or self.portfolio_size <= 0:
477
+ raise ValueError("portfolio_size must be a positive exact integer")
478
+ if self.portfolio_size > len(self.eligible_option_ids):
479
+ raise ValueError("portfolio_size exceeds the eligible frame")
480
+ if type(self.utility) is not ForecastPortfolioUtilityBinding:
481
+ raise TypeError("utility must be an exact identified binding")
482
+ self.utility.__post_init__()
483
+ eligible_options_sha256 = self._eligible_digest_from_validated_values()
484
+ object.__setattr__(
485
+ self,
486
+ "_eligible_options_sha256_cache",
487
+ eligible_options_sha256,
488
+ )
489
+ object.__setattr__(
490
+ self,
491
+ "_request_sha256_cache",
492
+ _hash(
493
+ _REQUEST_DOMAIN,
494
+ self._record_from_validated_values(eligible_options_sha256),
495
+ ),
496
+ )
497
+
498
+ def _eligible_digest_from_validated_values(self) -> str:
499
+ by_id = {value.option_id: value for value in self.frame.forecasts}
500
+ return _hash(
501
+ _ELIGIBLE_DOMAIN,
502
+ {
503
+ "frame_receipt_sha256": self.frame.receipt_sha256,
504
+ "options": [
505
+ {
506
+ "option_id": option_id,
507
+ "option_identity_sha256": by_id[
508
+ option_id
509
+ ].option_identity_sha256,
510
+ }
511
+ for option_id in self.eligible_option_ids
512
+ ],
513
+ },
514
+ )
515
+
516
+ @property
517
+ def eligible_options_sha256(self) -> str:
518
+ self.__post_init__()
519
+ assert self._eligible_options_sha256_cache is not None
520
+ return self._eligible_options_sha256_cache
521
+
522
+ def _record_from_validated_values(
523
+ self,
524
+ eligible_options_sha256: str,
525
+ ) -> dict[str, object]:
526
+ by_id = {value.option_id: value for value in self.frame.forecasts}
527
+ return {
528
+ "schema_version": 1,
529
+ "frame_receipt_sha256": self.frame.receipt_sha256,
530
+ "source_forecast_receipt_sha256": (
531
+ self.frame.source_forecast_receipt_sha256
532
+ ),
533
+ "eligible_options": [
534
+ {
535
+ "option_id": option_id,
536
+ "option_identity_sha256": by_id[
537
+ option_id
538
+ ].option_identity_sha256,
539
+ }
540
+ for option_id in self.eligible_option_ids
541
+ ],
542
+ "eligible_options_sha256": eligible_options_sha256,
543
+ "portfolio_size": self.portfolio_size,
544
+ "utility": self.utility.to_record(),
545
+ }
546
+
547
+ def _unsigned_record(self) -> dict[str, object]:
548
+ self.__post_init__()
549
+ assert self._eligible_options_sha256_cache is not None
550
+ return self._record_from_validated_values(
551
+ self._eligible_options_sha256_cache
552
+ )
553
+
554
+ @property
555
+ def request_sha256(self) -> str:
556
+ self.__post_init__()
557
+ assert self._request_sha256_cache is not None
558
+ return self._request_sha256_cache
559
+
560
+ def to_record(self) -> dict[str, object]:
561
+ return {**self._unsigned_record(), "request_sha256": self.request_sha256}
562
+
563
+ def __eq__(self, other: object) -> bool:
564
+ return (
565
+ type(self) is FrameActionAllocationRequest
566
+ and type(other) is FrameActionAllocationRequest
567
+ and self.request_sha256 == other.request_sha256
568
+ )
569
+
570
+ __hash__ = None
571
+
572
+
573
+ @dataclass(frozen=True, slots=True, eq=False)
574
+ class FrameActionPortfolioDecision:
575
+ allocation_request_sha256: str
576
+ frame_receipt_sha256: str
577
+ source_forecast_receipt_sha256: str
578
+ eligible_options_sha256: str
579
+ members: tuple[AllocatedActionMember, ...]
580
+ final_score: PortfolioAllocationScore
581
+ candidate_evaluations: int
582
+ utility_policy_id: str
583
+ utility_policy_version: int
584
+ utility_definition_sha256: str
585
+ allocator_policy_id: str
586
+ allocator_policy_version: int
587
+ allocator_definition_sha256: str
588
+ allocator_configuration_sha256: str
589
+
590
+ def __post_init__(self) -> None:
591
+ for name in (
592
+ "allocation_request_sha256",
593
+ "frame_receipt_sha256",
594
+ "source_forecast_receipt_sha256",
595
+ "eligible_options_sha256",
596
+ "utility_definition_sha256",
597
+ "allocator_definition_sha256",
598
+ "allocator_configuration_sha256",
599
+ ):
600
+ require_sha256(getattr(self, name), name)
601
+ if type(self.members) is not tuple or not self.members or any(
602
+ type(value) is not AllocatedActionMember for value in self.members
603
+ ):
604
+ raise ValueError("members must be a non-empty exact allocated tuple")
605
+ for value in self.members:
606
+ value.__post_init__()
607
+ if tuple(value.rank for value in self.members) != tuple(
608
+ range(1, len(self.members) + 1)
609
+ ):
610
+ raise ValueError("member ranks must be contiguous and ordered")
611
+ if len({value.option_id for value in self.members}) != len(self.members):
612
+ raise ValueError("a frame decision cannot repeat an option")
613
+ if type(self.final_score) is not PortfolioAllocationScore:
614
+ raise TypeError("final_score must be exact PortfolioAllocationScore")
615
+ self.final_score.__post_init__()
616
+ if self.final_score != self.members[-1].greedy_step_score:
617
+ raise ValueError("final_score must equal the last greedy score")
618
+ if type(self.candidate_evaluations) is not int or self.candidate_evaluations <= 0:
619
+ raise ValueError("candidate_evaluations must be positive")
620
+ for prefix in ("utility", "allocator"):
621
+ _token(getattr(self, f"{prefix}_policy_id"), f"{prefix}_policy_id")
622
+ version = getattr(self, f"{prefix}_policy_version")
623
+ if type(version) is not int or version <= 0:
624
+ raise ValueError(f"{prefix}_policy_version must be positive")
625
+
626
+ def _unsigned_record(self) -> dict[str, object]:
627
+ self.__post_init__()
628
+ return {
629
+ "schema_version": 1,
630
+ "allocation_request_sha256": self.allocation_request_sha256,
631
+ "frame_receipt_sha256": self.frame_receipt_sha256,
632
+ "source_forecast_receipt_sha256": self.source_forecast_receipt_sha256,
633
+ "eligible_options_sha256": self.eligible_options_sha256,
634
+ "members": [value.to_record() for value in self.members],
635
+ "final_score": self.final_score.to_record(),
636
+ "candidate_evaluations": self.candidate_evaluations,
637
+ "utility_policy": {
638
+ "policy_id": self.utility_policy_id,
639
+ "policy_version": self.utility_policy_version,
640
+ "definition_sha256": self.utility_definition_sha256,
641
+ },
642
+ "allocator_policy": {
643
+ "policy_id": self.allocator_policy_id,
644
+ "policy_version": self.allocator_policy_version,
645
+ "definition_sha256": self.allocator_definition_sha256,
646
+ "configuration_sha256": self.allocator_configuration_sha256,
647
+ },
648
+ }
649
+
650
+ @property
651
+ def receipt_sha256(self) -> str:
652
+ return _hash(_DECISION_DOMAIN, self._unsigned_record())
653
+
654
+ def to_record(self) -> dict[str, object]:
655
+ return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
656
+
657
+ def __eq__(self, other: object) -> bool:
658
+ return (
659
+ type(self) is FrameActionPortfolioDecision
660
+ and type(other) is FrameActionPortfolioDecision
661
+ and self.receipt_sha256 == other.receipt_sha256
662
+ )
663
+
664
+ __hash__ = None
665
+
666
+
667
+ def validate_frame_action_portfolio_decision(
668
+ request: FrameActionAllocationRequest,
669
+ decision: FrameActionPortfolioDecision,
670
+ ) -> None:
671
+ if type(request) is not FrameActionAllocationRequest:
672
+ raise TypeError("request must be an exact frame allocation request")
673
+ request.__post_init__()
674
+ if type(decision) is not FrameActionPortfolioDecision:
675
+ raise TypeError("decision must be an exact frame portfolio decision")
676
+ decision.__post_init__()
677
+ if (
678
+ decision.allocation_request_sha256 != request.request_sha256
679
+ or decision.frame_receipt_sha256 != request.frame.receipt_sha256
680
+ or decision.source_forecast_receipt_sha256
681
+ != request.frame.source_forecast_receipt_sha256
682
+ or decision.eligible_options_sha256 != request.eligible_options_sha256
683
+ ):
684
+ raise ValueError("frame decision is bound to another request")
685
+ if len(decision.members) != request.portfolio_size:
686
+ raise ValueError("frame decision member count differs from portfolio_size")
687
+ if (
688
+ decision.utility_policy_id != request.utility.policy_id
689
+ or decision.utility_policy_version != request.utility.policy_version
690
+ or decision.utility_definition_sha256 != request.utility.definition_sha256
691
+ ):
692
+ raise ValueError("frame decision names another utility policy")
693
+ forecasts = {value.option_id: value for value in request.frame.forecasts}
694
+ for member in decision.members:
695
+ if member.option_id not in request.eligible_option_ids:
696
+ raise ValueError("frame decision selected an ineligible option")
697
+ forecast = forecasts[member.option_id]
698
+ if (
699
+ member.option_identity_sha256 != forecast.option_identity_sha256
700
+ or member.child_configuration_sha256
701
+ != forecast.child_configuration_sha256
702
+ or member.family != forecast.family
703
+ ):
704
+ raise ValueError("frame decision member differs from its forecast")
705
+
706
+
707
+ @dataclass(frozen=True, slots=True)
708
+ class AllocationCandidateScoreDiagnosticInput:
709
+ allocation_request_sha256: str
710
+ step: int
711
+ candidate_label: str
712
+ members: tuple[ResolvedActionForecast, ...]
713
+ score: PortfolioAllocationScore
714
+ marginal_total_utility: float
715
+
716
+ def __post_init__(self) -> None:
717
+ require_sha256(self.allocation_request_sha256, "allocation_request_sha256")
718
+ if type(self.step) is not int or self.step <= 0:
719
+ raise ValueError("step must be a positive exact integer")
720
+ if type(self.candidate_label) is not str or _CANDIDATE_LABEL.fullmatch(
721
+ self.candidate_label
722
+ ) is None:
723
+ raise ValueError("candidate_label must be an opaque global-row label")
724
+ if type(self.members) is not tuple or not self.members or any(
725
+ type(value) is not ResolvedActionForecast for value in self.members
726
+ ):
727
+ raise ValueError("members must be a non-empty resolved forecast tuple")
728
+ for value in self.members:
729
+ value.__post_init__()
730
+ if type(self.score) is not PortfolioAllocationScore:
731
+ raise TypeError("score must be exact PortfolioAllocationScore")
732
+ self.score.__post_init__()
733
+ _finite_float(self.marginal_total_utility, "marginal_total_utility")
734
+
735
+
736
+ @dataclass(frozen=True, slots=True)
737
+ class AllocationCandidateScoreDiagnostic:
738
+ boundary_or_extreme: bool
739
+
740
+ def __post_init__(self) -> None:
741
+ if type(self.boundary_or_extreme) is not bool:
742
+ raise TypeError("boundary_or_extreme must be an exact bool")
743
+
744
+
745
+ @runtime_checkable
746
+ class AllocationScoreDiagnostic(Protocol):
747
+ def __call__(
748
+ self,
749
+ request: AllocationCandidateScoreDiagnosticInput,
750
+ ) -> AllocationCandidateScoreDiagnostic: ...
751
+
752
+
753
+ @dataclass(frozen=True, slots=True)
754
+ class AllocationScoreDiagnosticBinding:
755
+ diagnostic: AllocationScoreDiagnostic = field(repr=False, compare=False)
756
+ policy_id: str
757
+ policy_version: int
758
+ policy_definition_sha256: str
759
+
760
+ def __post_init__(self) -> None:
761
+ if not callable(self.diagnostic):
762
+ raise TypeError("diagnostic must be callable")
763
+ _token(self.policy_id, "policy_id")
764
+ if type(self.policy_version) is not int or self.policy_version <= 0:
765
+ raise ValueError("policy_version must be a positive exact integer")
766
+ require_sha256(self.policy_definition_sha256, "policy_definition_sha256")
767
+
768
+ def _unsigned_record(self) -> dict[str, object]:
769
+ self.__post_init__()
770
+ return {
771
+ "schema_version": 1,
772
+ "policy_id": self.policy_id,
773
+ "policy_version": self.policy_version,
774
+ "policy_definition_sha256": self.policy_definition_sha256,
775
+ }
776
+
777
+ @property
778
+ def binding_sha256(self) -> str:
779
+ return _hash(_DIAGNOSTIC_POLICY_DOMAIN, self._unsigned_record())
780
+
781
+ def to_record(self) -> dict[str, object]:
782
+ return {**self._unsigned_record(), "binding_sha256": self.binding_sha256}
783
+
784
+
785
+ @dataclass(frozen=True, slots=True, eq=False)
786
+ class AllocationSurfaceGatePolicyBinding:
787
+ """Domain-neutral thresholds applied before evaluator authority opens."""
788
+
789
+ policy_id: str
790
+ policy_version: int
791
+ policy_definition_sha256: str
792
+ minimum_distinct_finite_scores: int
793
+ maximum_top_tie_share: float
794
+ maximum_boundary_or_extreme_share: float
795
+ minimum_winner_runner_gap: float
796
+
797
+ def __post_init__(self) -> None:
798
+ _token(self.policy_id, "policy_id")
799
+ if type(self.policy_version) is not int or self.policy_version <= 0:
800
+ raise ValueError("policy_version must be a positive exact integer")
801
+ require_sha256(self.policy_definition_sha256, "policy_definition_sha256")
802
+ if (
803
+ type(self.minimum_distinct_finite_scores) is not int
804
+ or self.minimum_distinct_finite_scores <= 0
805
+ ):
806
+ raise ValueError("minimum_distinct_finite_scores must be positive")
807
+ for name in (
808
+ "maximum_top_tie_share",
809
+ "maximum_boundary_or_extreme_share",
810
+ ):
811
+ value = _finite_float(getattr(self, name), name)
812
+ if not 0.0 <= value <= 1.0:
813
+ raise ValueError(f"{name} must lie in [0,1]")
814
+ _finite_float(self.minimum_winner_runner_gap, "minimum_winner_runner_gap")
815
+ if self.minimum_winner_runner_gap < 0.0:
816
+ raise ValueError("minimum_winner_runner_gap cannot be negative")
817
+
818
+ def _unsigned_record(self) -> dict[str, object]:
819
+ self.__post_init__()
820
+ return {
821
+ "schema_version": 1,
822
+ "policy_id": self.policy_id,
823
+ "policy_version": self.policy_version,
824
+ "policy_definition_sha256": self.policy_definition_sha256,
825
+ "minimum_distinct_finite_scores": (
826
+ self.minimum_distinct_finite_scores
827
+ ),
828
+ "maximum_top_tie_share_hex": self.maximum_top_tie_share.hex(),
829
+ "maximum_boundary_or_extreme_share_hex": (
830
+ self.maximum_boundary_or_extreme_share.hex()
831
+ ),
832
+ "minimum_winner_runner_gap_hex": (
833
+ self.minimum_winner_runner_gap.hex()
834
+ ),
835
+ }
836
+
837
+ @property
838
+ def binding_sha256(self) -> str:
839
+ return _hash(_GATE_POLICY_DOMAIN, self._unsigned_record())
840
+
841
+ def to_record(self) -> dict[str, object]:
842
+ return {**self._unsigned_record(), "binding_sha256": self.binding_sha256}
843
+
844
+ def __eq__(self, other: object) -> bool:
845
+ return (
846
+ type(self) is AllocationSurfaceGatePolicyBinding
847
+ and type(other) is AllocationSurfaceGatePolicyBinding
848
+ and self.binding_sha256 == other.binding_sha256
849
+ )
850
+
851
+ __hash__ = None
852
+
853
+
854
+ def allocation_score_multiset_sha256(scores: tuple[float, ...]) -> str:
855
+ if type(scores) is not tuple or not scores:
856
+ raise ValueError("scores must be a non-empty exact tuple")
857
+ for index, score in enumerate(scores):
858
+ _finite_float(score, f"scores[{index}]")
859
+ return _hash(
860
+ _SCORE_MULTISET_DOMAIN,
861
+ {"score_hex_multiset": sorted(value.hex() for value in scores)},
862
+ )
863
+
864
+
865
+ @dataclass(frozen=True, slots=True, eq=False)
866
+ class AllocationSurfaceStepAudit:
867
+ step: int
868
+ candidate_count: int
869
+ distinct_finite_score_count: int
870
+ top_tie_count: int
871
+ winner_runner_gap: float
872
+ boundary_or_extreme_count: int
873
+ boundary_or_extreme_share: float
874
+ score_multiset_sha256: str
875
+ winner_candidate_label: str
876
+ tie_break_used: bool
877
+ failure_codes: tuple[str, ...]
878
+ passes: bool
879
+
880
+ def __post_init__(self) -> None:
881
+ if type(self.step) is not int or self.step <= 0:
882
+ raise ValueError("step must be a positive exact integer")
883
+ for name in (
884
+ "candidate_count",
885
+ "distinct_finite_score_count",
886
+ "top_tie_count",
887
+ ):
888
+ value = getattr(self, name)
889
+ if type(value) is not int or value <= 0:
890
+ raise ValueError(f"{name} must be a positive exact integer")
891
+ if value > self.candidate_count:
892
+ raise ValueError(f"{name} cannot exceed candidate_count")
893
+ if (
894
+ type(self.boundary_or_extreme_count) is not int
895
+ or not 0 <= self.boundary_or_extreme_count <= self.candidate_count
896
+ ):
897
+ raise ValueError("boundary_or_extreme_count is outside candidate_count")
898
+ _finite_float(self.winner_runner_gap, "winner_runner_gap")
899
+ if self.winner_runner_gap < 0.0:
900
+ raise ValueError("winner_runner_gap cannot be negative")
901
+ _finite_float(
902
+ self.boundary_or_extreme_share,
903
+ "boundary_or_extreme_share",
904
+ )
905
+ if not 0.0 <= self.boundary_or_extreme_share <= 1.0:
906
+ raise ValueError("boundary_or_extreme_share must lie in [0,1]")
907
+ expected_share = self.boundary_or_extreme_count / self.candidate_count
908
+ if self.boundary_or_extreme_share != expected_share:
909
+ raise ValueError("boundary/extreme share differs from its count")
910
+ require_sha256(self.score_multiset_sha256, "score_multiset_sha256")
911
+ if type(self.winner_candidate_label) is not str or _CANDIDATE_LABEL.fullmatch(
912
+ self.winner_candidate_label
913
+ ) is None:
914
+ raise ValueError("winner_candidate_label is not an opaque row label")
915
+ if type(self.tie_break_used) is not bool or type(self.passes) is not bool:
916
+ raise TypeError("tie_break_used and passes must be exact booleans")
917
+ if self.tie_break_used is not (self.top_tie_count > 1):
918
+ raise ValueError("tie_break_used differs from top_tie_count")
919
+ if type(self.failure_codes) is not tuple or any(
920
+ type(value) is not str or _TOKEN.fullmatch(value) is None
921
+ for value in self.failure_codes
922
+ ):
923
+ raise TypeError("failure_codes must be an exact token tuple")
924
+ if self.failure_codes != tuple(sorted(set(self.failure_codes))):
925
+ raise ValueError("failure_codes must be unique and canonical")
926
+ if self.passes is not (not self.failure_codes):
927
+ raise ValueError("passes differs from failure_codes")
928
+
929
+ @property
930
+ def top_tie_share(self) -> float:
931
+ return self.top_tie_count / self.candidate_count
932
+
933
+ def _unsigned_record(self) -> dict[str, object]:
934
+ self.__post_init__()
935
+ return {
936
+ "schema_version": 1,
937
+ "step": self.step,
938
+ "candidate_count": self.candidate_count,
939
+ "score_kind": "marginal_total_utility",
940
+ "distinct_finite_score_count": self.distinct_finite_score_count,
941
+ "top_tie_count": self.top_tie_count,
942
+ "top_tie_share_hex": self.top_tie_share.hex(),
943
+ "winner_runner_gap_hex": self.winner_runner_gap.hex(),
944
+ "boundary_or_extreme_count": self.boundary_or_extreme_count,
945
+ "boundary_or_extreme_share_hex": (
946
+ self.boundary_or_extreme_share.hex()
947
+ ),
948
+ "score_multiset_sha256": self.score_multiset_sha256,
949
+ "winner_candidate_label": self.winner_candidate_label,
950
+ "tie_break_used": self.tie_break_used,
951
+ "failure_codes": list(self.failure_codes),
952
+ "passes": self.passes,
953
+ }
954
+
955
+ @property
956
+ def receipt_sha256(self) -> str:
957
+ return _hash(_STEP_AUDIT_DOMAIN, self._unsigned_record())
958
+
959
+ def to_record(self) -> dict[str, object]:
960
+ return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
961
+
962
+ def __eq__(self, other: object) -> bool:
963
+ return (
964
+ type(self) is AllocationSurfaceStepAudit
965
+ and type(other) is AllocationSurfaceStepAudit
966
+ and self.receipt_sha256 == other.receipt_sha256
967
+ )
968
+
969
+ __hash__ = None
970
+
971
+
972
+ def allocation_surface_failure_codes(
973
+ *,
974
+ policy: AllocationSurfaceGatePolicyBinding,
975
+ candidate_count: int,
976
+ distinct_finite_score_count: int,
977
+ top_tie_count: int,
978
+ winner_runner_gap: float,
979
+ boundary_or_extreme_share: float,
980
+ ) -> tuple[str, ...]:
981
+ policy.__post_init__()
982
+ failures: list[str] = []
983
+ if distinct_finite_score_count < policy.minimum_distinct_finite_scores:
984
+ failures.append("insufficient_distinct_scores")
985
+ if top_tie_count / candidate_count > policy.maximum_top_tie_share:
986
+ failures.append("top_tie_concentration")
987
+ if (
988
+ boundary_or_extreme_share
989
+ > policy.maximum_boundary_or_extreme_share
990
+ ):
991
+ failures.append("boundary_extreme_concentration")
992
+ if candidate_count > 1 and winner_runner_gap < policy.minimum_winner_runner_gap:
993
+ failures.append("winner_runner_gap_too_small")
994
+ return tuple(sorted(failures))
995
+
996
+
997
+ @dataclass(frozen=True, slots=True, eq=False)
998
+ class ActionAllocationSurfaceAudit:
999
+ allocation_request_sha256: str
1000
+ decision_receipt_sha256: str
1001
+ frame_receipt_sha256: str
1002
+ score_diagnostic: AllocationScoreDiagnosticBinding
1003
+ gate_policy: AllocationSurfaceGatePolicyBinding
1004
+ steps: tuple[AllocationSurfaceStepAudit, ...]
1005
+ candidate_score_count: int
1006
+ passes: bool
1007
+
1008
+ def __post_init__(self) -> None:
1009
+ for name in (
1010
+ "allocation_request_sha256",
1011
+ "decision_receipt_sha256",
1012
+ "frame_receipt_sha256",
1013
+ ):
1014
+ require_sha256(getattr(self, name), name)
1015
+ if type(self.score_diagnostic) is not AllocationScoreDiagnosticBinding:
1016
+ raise TypeError("score_diagnostic must be an exact binding")
1017
+ self.score_diagnostic.__post_init__()
1018
+ if type(self.gate_policy) is not AllocationSurfaceGatePolicyBinding:
1019
+ raise TypeError("gate_policy must be an exact binding")
1020
+ self.gate_policy.__post_init__()
1021
+ if type(self.steps) is not tuple or not self.steps or any(
1022
+ type(value) is not AllocationSurfaceStepAudit for value in self.steps
1023
+ ):
1024
+ raise ValueError("steps must be a non-empty exact audit tuple")
1025
+ for value in self.steps:
1026
+ value.__post_init__()
1027
+ if tuple(value.step for value in self.steps) != tuple(
1028
+ range(1, len(self.steps) + 1)
1029
+ ):
1030
+ raise ValueError("audit steps must be contiguous and ordered")
1031
+ for step in self.steps:
1032
+ expected_failures = allocation_surface_failure_codes(
1033
+ policy=self.gate_policy,
1034
+ candidate_count=step.candidate_count,
1035
+ distinct_finite_score_count=step.distinct_finite_score_count,
1036
+ top_tie_count=step.top_tie_count,
1037
+ winner_runner_gap=step.winner_runner_gap,
1038
+ boundary_or_extreme_share=step.boundary_or_extreme_share,
1039
+ )
1040
+ if step.failure_codes != expected_failures:
1041
+ raise ValueError("step failure codes differ from the gate policy")
1042
+ expected_count = sum(value.candidate_count for value in self.steps)
1043
+ if (
1044
+ type(self.candidate_score_count) is not int
1045
+ or self.candidate_score_count != expected_count
1046
+ ):
1047
+ raise ValueError("candidate_score_count differs from audit steps")
1048
+ if type(self.passes) is not bool:
1049
+ raise TypeError("passes must be an exact bool")
1050
+ if self.passes is not all(value.passes for value in self.steps):
1051
+ raise ValueError("audit passes differs from its step gates")
1052
+
1053
+ def _unsigned_record(self) -> dict[str, object]:
1054
+ self.__post_init__()
1055
+ return {
1056
+ "schema_version": 1,
1057
+ "allocation_request_sha256": self.allocation_request_sha256,
1058
+ "decision_receipt_sha256": self.decision_receipt_sha256,
1059
+ "frame_receipt_sha256": self.frame_receipt_sha256,
1060
+ "score_diagnostic": self.score_diagnostic.to_record(),
1061
+ "gate_policy": self.gate_policy.to_record(),
1062
+ "steps": [value.to_record() for value in self.steps],
1063
+ "candidate_score_count": self.candidate_score_count,
1064
+ "passes": self.passes,
1065
+ }
1066
+
1067
+ @property
1068
+ def receipt_sha256(self) -> str:
1069
+ return _hash(_SURFACE_AUDIT_DOMAIN, self._unsigned_record())
1070
+
1071
+ def to_record(self) -> dict[str, object]:
1072
+ return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
1073
+
1074
+ def __eq__(self, other: object) -> bool:
1075
+ return (
1076
+ type(self) is ActionAllocationSurfaceAudit
1077
+ and type(other) is ActionAllocationSurfaceAudit
1078
+ and self.receipt_sha256 == other.receipt_sha256
1079
+ )
1080
+
1081
+ __hash__ = None
1082
+
1083
+
1084
+ @dataclass(frozen=True, slots=True, eq=False)
1085
+ class AuditedFrameActionAllocationResult:
1086
+ decision: FrameActionPortfolioDecision
1087
+ audit: ActionAllocationSurfaceAudit
1088
+
1089
+ def __post_init__(self) -> None:
1090
+ if type(self.decision) is not FrameActionPortfolioDecision:
1091
+ raise TypeError("decision must be an exact frame decision")
1092
+ self.decision.__post_init__()
1093
+ if type(self.audit) is not ActionAllocationSurfaceAudit:
1094
+ raise TypeError("audit must be an exact allocation-surface audit")
1095
+ self.audit.__post_init__()
1096
+ if self.audit.decision_receipt_sha256 != self.decision.receipt_sha256:
1097
+ raise ValueError("audit is bound to another decision")
1098
+ if self.audit.allocation_request_sha256 != (
1099
+ self.decision.allocation_request_sha256
1100
+ ):
1101
+ raise ValueError("audit and decision name different requests")
1102
+ if self.audit.frame_receipt_sha256 != self.decision.frame_receipt_sha256:
1103
+ raise ValueError("audit and decision name different frames")
1104
+ if self.audit.candidate_score_count != self.decision.candidate_evaluations:
1105
+ raise ValueError("audit and decision candidate counts differ")
1106
+
1107
+ def _unsigned_record(self) -> dict[str, object]:
1108
+ self.__post_init__()
1109
+ return {
1110
+ "schema_version": 1,
1111
+ "decision_receipt_sha256": self.decision.receipt_sha256,
1112
+ "audit_receipt_sha256": self.audit.receipt_sha256,
1113
+ "authorized": self.audit.passes,
1114
+ }
1115
+
1116
+ @property
1117
+ def receipt_sha256(self) -> str:
1118
+ return _hash(_RESULT_DOMAIN, self._unsigned_record())
1119
+
1120
+ def to_record(self) -> dict[str, object]:
1121
+ return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
1122
+
1123
+ def __eq__(self, other: object) -> bool:
1124
+ return (
1125
+ type(self) is AuditedFrameActionAllocationResult
1126
+ and type(other) is AuditedFrameActionAllocationResult
1127
+ and self.receipt_sha256 == other.receipt_sha256
1128
+ )
1129
+
1130
+ __hash__ = None
1131
+
1132
+
1133
+ __all__ = [
1134
+ "ActionAllocationFrameSubsetPolicyBinding",
1135
+ "ActionAllocationSurfaceAudit",
1136
+ "ActionForecastAllocationFrameKind",
1137
+ "AllocationCandidateScoreDiagnostic",
1138
+ "AllocationCandidateScoreDiagnosticInput",
1139
+ "AllocationScoreDiagnostic",
1140
+ "AllocationScoreDiagnosticBinding",
1141
+ "AllocationSurfaceGatePolicyBinding",
1142
+ "AllocationSurfaceStepAudit",
1143
+ "AuditedFrameActionAllocationResult",
1144
+ "FrameActionAllocationRequest",
1145
+ "FrameActionPortfolioDecision",
1146
+ "ResolvedActionForecastAllocationFrame",
1147
+ "allocation_score_multiset_sha256",
1148
+ "allocation_surface_failure_codes",
1149
+ "bind_action_forecast_block_allocation_frame",
1150
+ "bind_action_forecast_block_subset_allocation_frame",
1151
+ "bind_complete_action_forecast_allocation_frame",
1152
+ "validate_frame_action_portfolio_decision",
1153
+ ]