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,2537 @@
1
+ """Live, workload-neutral execution ledger for prepared evolution campaigns.
2
+
3
+ Preparation and execution intentionally remain separate. This module consumes
4
+ an authenticated :class:`PreparedEvolutionCampaign`, but delegates benchmark
5
+ state, portfolio/recombination work, reflection, and lifecycle effects to
6
+ small async ports. The application layer owns only chronology, joins, budget
7
+ accounting, archive-utility cutoffs, reflection visibility, durable events,
8
+ and cleanup/finalization.
9
+
10
+ No provider call, model setting, objective, or workload action appears here.
11
+ Existing ``PortfolioEvolution`` and ``PortfolioRecombination`` services can be
12
+ bridged by one stateful ``CampaignStageRuntimePort`` implementation.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import asyncio
18
+ import base64
19
+ import hashlib
20
+ import json
21
+ import re
22
+ import zlib
23
+ from dataclasses import dataclass, field
24
+ from enum import Enum
25
+ from typing import Protocol, runtime_checkable
26
+
27
+ from agent_evolve.application.evolution_campaign import (
28
+ ArchiveUtilitySnapshot,
29
+ CampaignGenerationKind,
30
+ CampaignGenerationStep,
31
+ CampaignPolicies,
32
+ CampaignPromotionBarrier,
33
+ CampaignReflectionWave,
34
+ CampaignReflectionSupervisionPolicy,
35
+ PreparedEvolutionCampaign,
36
+ ReflectionFailureMode,
37
+ ReflectionVisibility,
38
+ freeze_archive_utility,
39
+ )
40
+ from agent_evolve.domain.patch import require_sha256
41
+ from agent_evolve.domain.typed_json import (
42
+ FrozenJsonObject,
43
+ freeze_json,
44
+ thaw_json,
45
+ typed_json_sha256,
46
+ )
47
+
48
+
49
+ _TOKEN = re.compile(r"^[a-z][a-z0-9_.:-]{0,127}$")
50
+ _STEP_DOMAIN = b"agent-evolve:campaign-execution-step:v1\x00"
51
+ _AUDIT_DOMAIN = b"agent-evolve:campaign-selector-audit:v1\x00"
52
+ _AUDIT_SET_DOMAIN = b"agent-evolve:campaign-selector-audit-set:v1\x00"
53
+ _START_DOMAIN = b"agent-evolve:campaign-execution-start:v1\x00"
54
+ _ARCHIVE_REQUEST_DOMAIN = b"agent-evolve:campaign-archive-cutoff-request:v1\x00"
55
+ _ARCHIVE_RECEIPT_DOMAIN = b"agent-evolve:campaign-archive-cutoff-receipt:v1\x00"
56
+ _STAGE_REQUEST_DOMAIN = b"agent-evolve:campaign-stage-request:v1\x00"
57
+ _STAGE_RECEIPT_DOMAIN = b"agent-evolve:campaign-stage-receipt:v1\x00"
58
+ _REFLECTION_REQUEST_DOMAIN = b"agent-evolve:campaign-reflection-request:v1\x00"
59
+ _REFLECTION_RECEIPT_DOMAIN = b"agent-evolve:campaign-reflection-receipt:v1\x00"
60
+ _REFLECTION_CANCELLATION_RECEIPT_DOMAIN = (
61
+ b"agent-evolve:campaign-reflection-cancellation-receipt:v1\x00"
62
+ )
63
+ _TEST_ADMISSION_REQUEST_DOMAIN = (
64
+ b"agent-evolve:campaign-reflection-test-admission-request:v1\x00"
65
+ )
66
+ _TEST_ADMISSION_RECEIPT_DOMAIN = (
67
+ b"agent-evolve:campaign-reflection-test-admission-receipt:v1\x00"
68
+ )
69
+ _TAIL_DRAIN_DOMAIN = b"agent-evolve:campaign-tail-drain:v1\x00"
70
+ _COUNTERS_DOMAIN = b"agent-evolve:campaign-execution-counters:v1\x00"
71
+ _FINALIZATION_REQUEST_DOMAIN = (
72
+ b"agent-evolve:campaign-execution-finalization-request:v1\x00"
73
+ )
74
+ _FINALIZATION_RECEIPT_DOMAIN = (
75
+ b"agent-evolve:campaign-execution-finalization-receipt:v1\x00"
76
+ )
77
+ _CLEANUP_REQUEST_DOMAIN = b"agent-evolve:campaign-runtime-cleanup-request:v1\x00"
78
+ _CLEANUP_RECEIPT_DOMAIN = b"agent-evolve:campaign-runtime-cleanup-receipt:v1\x00"
79
+ _EVENT_DOMAIN = b"agent-evolve:campaign-execution-event:v1\x00"
80
+ _RESULT_DOMAIN = b"agent-evolve:campaign-execution-result:v1\x00"
81
+ _AUDIT_TEXT_INLINE_UTF8_BYTES = 1_000_000
82
+ _AUDIT_TEXT_BASE64_CHUNK_CHARACTERS = 500_000
83
+ _AUDIT_TEXT_MAX_DECOMPRESSED_UTF8_BYTES = 64 * 1024 * 1024
84
+
85
+
86
+ class CampaignExecutionContractError(ValueError):
87
+ """A runtime receipt violated the prepared campaign contract."""
88
+
89
+
90
+ def _canonical_json(value: object) -> bytes:
91
+ return json.dumps(
92
+ value,
93
+ ensure_ascii=True,
94
+ allow_nan=False,
95
+ separators=(",", ":"),
96
+ sort_keys=True,
97
+ ).encode("ascii", errors="strict")
98
+
99
+
100
+ def _hash(domain: bytes, record: object) -> str:
101
+ return hashlib.sha256(domain + _canonical_json(record)).hexdigest()
102
+
103
+
104
+ def encode_selector_audit_text(name: str, value: str) -> dict[str, object]:
105
+ """Represent exact audit text without violating typed-JSON scalar limits.
106
+
107
+ Ordinary traces retain the original single-string representation byte for
108
+ byte. Large structured decisions are losslessly compressed and split into
109
+ bounded ASCII chunks; the receipt authenticates both compressed and
110
+ decompressed bytes. This is representation-only and has no optimizer or
111
+ model authority.
112
+ """
113
+
114
+ if name not in {"request_text", "response_text"}:
115
+ raise ValueError("selector audit text name is unsupported")
116
+ if type(value) is not str or not value:
117
+ raise ValueError("selector audit text must be a non-empty exact string")
118
+ raw = value.encode("utf-8", errors="strict")
119
+ if len(raw) <= _AUDIT_TEXT_INLINE_UTF8_BYTES:
120
+ return {name: value}
121
+ if len(raw) > _AUDIT_TEXT_MAX_DECOMPRESSED_UTF8_BYTES:
122
+ raise ValueError("selector audit text exceeds the lossless audit ceiling")
123
+ compressed = zlib.compress(raw, level=9)
124
+ encoded = base64.b64encode(compressed).decode("ascii")
125
+ chunks = [
126
+ encoded[offset : offset + _AUDIT_TEXT_BASE64_CHUNK_CHARACTERS]
127
+ for offset in range(0, len(encoded), _AUDIT_TEXT_BASE64_CHUNK_CHARACTERS)
128
+ ]
129
+ return {
130
+ f"{name}_encoding": "zlib_base64_chunks_v1",
131
+ f"{name}_base64_chunks": chunks,
132
+ f"{name}_utf8_bytes": len(raw),
133
+ f"{name}_sha256": hashlib.sha256(raw).hexdigest(),
134
+ f"{name}_compressed_bytes": len(compressed),
135
+ f"{name}_compressed_sha256": hashlib.sha256(compressed).hexdigest(),
136
+ }
137
+
138
+
139
+ def decode_selector_audit_text(audit: dict[str, object], name: str) -> str:
140
+ """Validate and recover one inline or losslessly chunked audit field."""
141
+
142
+ if type(audit) is not dict:
143
+ raise TypeError("selector audit must be an exact object")
144
+ if name not in {"request_text", "response_text"}:
145
+ raise ValueError("selector audit text name is unsupported")
146
+ inline = audit.get(name)
147
+ if inline is not None:
148
+ if type(inline) is not str or not inline:
149
+ raise ValueError(f"plaintext audit requires non-empty {name}")
150
+ encoded_keys = {
151
+ f"{name}_encoding",
152
+ f"{name}_base64_chunks",
153
+ f"{name}_utf8_bytes",
154
+ f"{name}_sha256",
155
+ f"{name}_compressed_bytes",
156
+ f"{name}_compressed_sha256",
157
+ }
158
+ if any(key in audit for key in encoded_keys):
159
+ raise ValueError("inline selector audit text cannot also be encoded")
160
+ return inline
161
+ if audit.get(f"{name}_encoding") != "zlib_base64_chunks_v1":
162
+ raise ValueError(f"plaintext audit requires non-empty {name}")
163
+ chunks = audit.get(f"{name}_base64_chunks")
164
+ if (
165
+ type(chunks) is not list
166
+ or not chunks
167
+ or any(type(value) is not str or not value for value in chunks)
168
+ ):
169
+ raise ValueError("selector audit chunks must be non-empty exact strings")
170
+ expected_utf8_bytes = audit.get(f"{name}_utf8_bytes")
171
+ expected_compressed_bytes = audit.get(f"{name}_compressed_bytes")
172
+ if (
173
+ type(expected_utf8_bytes) is not int
174
+ or not _AUDIT_TEXT_INLINE_UTF8_BYTES
175
+ < expected_utf8_bytes
176
+ <= _AUDIT_TEXT_MAX_DECOMPRESSED_UTF8_BYTES
177
+ or type(expected_compressed_bytes) is not int
178
+ or expected_compressed_bytes <= 0
179
+ ):
180
+ raise ValueError("selector audit encoded byte counts are invalid")
181
+ try:
182
+ compressed = base64.b64decode("".join(chunks), validate=True)
183
+ except (ValueError, UnicodeEncodeError) as error:
184
+ raise ValueError("selector audit chunks are not canonical base64") from error
185
+ if len(compressed) != expected_compressed_bytes:
186
+ raise ValueError("selector audit compressed byte count disagrees")
187
+ if hashlib.sha256(compressed).hexdigest() != audit.get(
188
+ f"{name}_compressed_sha256"
189
+ ):
190
+ raise ValueError("selector audit compressed digest disagrees")
191
+ decompressor = zlib.decompressobj()
192
+ try:
193
+ raw = decompressor.decompress(compressed, expected_utf8_bytes + 1)
194
+ raw += decompressor.flush()
195
+ except zlib.error as error:
196
+ raise ValueError("selector audit compressed text is invalid") from error
197
+ if (
198
+ not decompressor.eof
199
+ or decompressor.unused_data
200
+ or decompressor.unconsumed_tail
201
+ or len(raw) != expected_utf8_bytes
202
+ ):
203
+ raise ValueError("selector audit decompressed byte count disagrees")
204
+ if hashlib.sha256(raw).hexdigest() != audit.get(f"{name}_sha256"):
205
+ raise ValueError("selector audit decompressed digest disagrees")
206
+ try:
207
+ value = raw.decode("utf-8", errors="strict")
208
+ except UnicodeDecodeError as error:
209
+ raise ValueError("selector audit text is not valid UTF-8") from error
210
+ if not value:
211
+ raise ValueError(f"plaintext audit requires non-empty {name}")
212
+ return value
213
+
214
+
215
+ def _token(value: str, *, name: str) -> None:
216
+ if type(value) is not str or _TOKEN.fullmatch(value) is None:
217
+ raise ValueError(f"{name} must use the closed execution-token grammar")
218
+
219
+
220
+ def _frozen(value: object, *, name: str) -> FrozenJsonObject:
221
+ if type(value) is not FrozenJsonObject:
222
+ raise TypeError(f"{name} must be an exact FrozenJsonObject")
223
+ if freeze_json(value) is not value:
224
+ raise TypeError(f"{name} must already be frozen typed JSON")
225
+ return value
226
+
227
+
228
+ def _freeze_record(value: dict[str, object]) -> FrozenJsonObject:
229
+ frozen = freeze_json(value)
230
+ if type(frozen) is not FrozenJsonObject: # pragma: no cover - closed input.
231
+ raise AssertionError("record did not freeze to an object")
232
+ return frozen
233
+
234
+
235
+ def campaign_step_sha256(step: CampaignGenerationStep) -> str:
236
+ if type(step) is not CampaignGenerationStep:
237
+ raise TypeError("step must be an exact CampaignGenerationStep")
238
+ CampaignGenerationStep.__post_init__(step)
239
+ return _hash(_STEP_DOMAIN, step.to_record())
240
+
241
+
242
+ class SelectorAuditExecutionMode(str, Enum):
243
+ FRESH = "fresh"
244
+
245
+
246
+ @dataclass(frozen=True, slots=True)
247
+ class CampaignSelectorAuditReceipt:
248
+ """Fresh selector-call trace required for each portfolio parent slot."""
249
+
250
+ generation: int
251
+ parent_slot: int
252
+ selector_call_id: str
253
+ request_sha256: str
254
+ decision_sha256: str
255
+ trace_receipt_sha256: str
256
+ plaintext_audit: FrozenJsonObject
257
+ prior_audit_set_sha256: str
258
+ execution_mode: SelectorAuditExecutionMode
259
+
260
+ def __post_init__(self) -> None:
261
+ if type(self.generation) is not int or self.generation <= 0:
262
+ raise ValueError("selector audit generation must be positive")
263
+ if type(self.parent_slot) is not int or self.parent_slot < 0:
264
+ raise ValueError("selector audit parent_slot must be non-negative")
265
+ _token(self.selector_call_id, name="selector_call_id")
266
+ for name in (
267
+ "request_sha256",
268
+ "decision_sha256",
269
+ "trace_receipt_sha256",
270
+ "prior_audit_set_sha256",
271
+ ):
272
+ require_sha256(getattr(self, name), name)
273
+ _frozen(self.plaintext_audit, name="plaintext_audit")
274
+ if typed_json_sha256(self.plaintext_audit) != self.trace_receipt_sha256:
275
+ raise ValueError(
276
+ "trace_receipt_sha256 does not authenticate plaintext audit"
277
+ )
278
+ audit = thaw_json(self.plaintext_audit)
279
+ if type(audit) is not dict: # pragma: no cover - exact object above.
280
+ raise AssertionError("plaintext audit thawed to a non-object")
281
+ required = {
282
+ "selector_call_id": self.selector_call_id,
283
+ "request_sha256": self.request_sha256,
284
+ "decision_sha256": self.decision_sha256,
285
+ }
286
+ if any(audit.get(name) != value for name, value in required.items()):
287
+ raise ValueError(
288
+ "plaintext audit request/decision/call join is inconsistent"
289
+ )
290
+ for name in ("request_text", "response_text"):
291
+ decode_selector_audit_text(audit, name)
292
+ if self.execution_mode is not SelectorAuditExecutionMode.FRESH:
293
+ raise ValueError("portfolio selector audit must record fresh execution")
294
+
295
+ def _unsigned_record(self) -> dict[str, object]:
296
+ self.__post_init__()
297
+ return {
298
+ "generation": self.generation,
299
+ "parent_slot": self.parent_slot,
300
+ "selector_call_id": self.selector_call_id,
301
+ "request_sha256": self.request_sha256,
302
+ "decision_sha256": self.decision_sha256,
303
+ "trace_receipt_sha256": self.trace_receipt_sha256,
304
+ "plaintext_audit": thaw_json(self.plaintext_audit),
305
+ "prior_audit_set_sha256": self.prior_audit_set_sha256,
306
+ "execution_mode": self.execution_mode.value,
307
+ }
308
+
309
+ @property
310
+ def audit_sha256(self) -> str:
311
+ return _hash(_AUDIT_DOMAIN, self._unsigned_record())
312
+
313
+ def to_record(self) -> dict[str, object]:
314
+ return {**self._unsigned_record(), "audit_sha256": self.audit_sha256}
315
+
316
+
317
+ def selector_audit_set_sha256(
318
+ audits: tuple[CampaignSelectorAuditReceipt, ...],
319
+ ) -> str:
320
+ if type(audits) is not tuple or any(
321
+ type(value) is not CampaignSelectorAuditReceipt for value in audits
322
+ ):
323
+ raise TypeError("audits must contain exact selector audit receipts")
324
+ for value in audits:
325
+ CampaignSelectorAuditReceipt.__post_init__(value)
326
+ return _hash(
327
+ _AUDIT_SET_DOMAIN,
328
+ [value.audit_sha256 for value in audits],
329
+ )
330
+
331
+
332
+ @dataclass(frozen=True, slots=True)
333
+ class CampaignSeedExecutionReceipt:
334
+ """Exact seed admission/evaluation accounting produced during start."""
335
+
336
+ seed_id: str
337
+ configuration_sha256: str
338
+ evaluated: bool
339
+ unique_evaluation: bool
340
+ valid: bool
341
+ failure_type: str | None
342
+ evidence: FrozenJsonObject
343
+
344
+ def __post_init__(self) -> None:
345
+ _token(self.seed_id, name="seed_id")
346
+ require_sha256(self.configuration_sha256, "configuration_sha256")
347
+ for name in ("evaluated", "unique_evaluation", "valid"):
348
+ if type(getattr(self, name)) is not bool:
349
+ raise TypeError(f"{name} must be an exact bool")
350
+ if not self.evaluated and self.unique_evaluation:
351
+ raise ValueError("an unevaluated seed cannot be a unique evaluation")
352
+ if not self.evaluated and self.valid:
353
+ raise ValueError("an unevaluated seed cannot be valid")
354
+ if self.valid:
355
+ if not self.evaluated or self.failure_type is not None:
356
+ raise ValueError("valid seed accounting cannot carry a failure")
357
+ elif type(self.failure_type) is not str or not self.failure_type:
358
+ raise ValueError("failed or invalid seed accounting requires failure_type")
359
+ _frozen(self.evidence, name="evidence")
360
+
361
+ def to_record(self) -> dict[str, object]:
362
+ self.__post_init__()
363
+ return {
364
+ "seed_id": self.seed_id,
365
+ "configuration_sha256": self.configuration_sha256,
366
+ "evaluated": self.evaluated,
367
+ "unique_evaluation": self.unique_evaluation,
368
+ "valid": self.valid,
369
+ "failure_type": self.failure_type,
370
+ "evidence": thaw_json(self.evidence),
371
+ }
372
+
373
+
374
+ @dataclass(frozen=True, slots=True)
375
+ class CampaignExecutionStartReceipt:
376
+ preparation_sha256: str
377
+ runtime_preflight_receipt_sha256: str
378
+ runtime_session_id: str
379
+ seed_batch_sha256: str
380
+ seed_receipts: tuple[CampaignSeedExecutionReceipt, ...]
381
+ evidence: FrozenJsonObject
382
+
383
+ def __post_init__(self) -> None:
384
+ require_sha256(self.preparation_sha256, "preparation_sha256")
385
+ require_sha256(
386
+ self.runtime_preflight_receipt_sha256,
387
+ "runtime_preflight_receipt_sha256",
388
+ )
389
+ _token(self.runtime_session_id, name="runtime_session_id")
390
+ require_sha256(self.seed_batch_sha256, "seed_batch_sha256")
391
+ if type(self.seed_receipts) is not tuple or not self.seed_receipts:
392
+ raise ValueError("runtime start requires exact seed receipts")
393
+ if any(
394
+ type(value) is not CampaignSeedExecutionReceipt
395
+ for value in self.seed_receipts
396
+ ):
397
+ raise TypeError("seed_receipts must contain exact receipts")
398
+ for value in self.seed_receipts:
399
+ CampaignSeedExecutionReceipt.__post_init__(value)
400
+ if len({value.seed_id for value in self.seed_receipts}) != len(
401
+ self.seed_receipts
402
+ ):
403
+ raise ValueError("runtime start seed IDs must be unique")
404
+ _frozen(self.evidence, name="evidence")
405
+
406
+ @property
407
+ def seed_occurrence_count(self) -> int:
408
+ return len(self.seed_receipts)
409
+
410
+ @property
411
+ def seed_unique_evaluation_count(self) -> int:
412
+ return sum(value.unique_evaluation for value in self.seed_receipts)
413
+
414
+ @property
415
+ def seed_failure_count(self) -> int:
416
+ return sum(not value.valid for value in self.seed_receipts)
417
+
418
+ def _unsigned_record(self) -> dict[str, object]:
419
+ self.__post_init__()
420
+ return {
421
+ "preparation_sha256": self.preparation_sha256,
422
+ "runtime_preflight_receipt_sha256": (self.runtime_preflight_receipt_sha256),
423
+ "runtime_session_id": self.runtime_session_id,
424
+ "seed_batch_sha256": self.seed_batch_sha256,
425
+ "seed_receipts": [value.to_record() for value in self.seed_receipts],
426
+ "seed_accounting": {
427
+ "occurrences": self.seed_occurrence_count,
428
+ "unique_evaluations": self.seed_unique_evaluation_count,
429
+ "failures": self.seed_failure_count,
430
+ },
431
+ "evidence": thaw_json(self.evidence),
432
+ }
433
+
434
+ @property
435
+ def receipt_sha256(self) -> str:
436
+ return _hash(_START_DOMAIN, self._unsigned_record())
437
+
438
+ def to_record(self) -> dict[str, object]:
439
+ return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
440
+
441
+
442
+ @dataclass(frozen=True, slots=True)
443
+ class CampaignArchiveCutoffRequest:
444
+ preparation_sha256: str
445
+ runtime_start_receipt_sha256: str
446
+ generation: int
447
+ kind: CampaignGenerationKind
448
+ step_sha256: str
449
+ prior_stage_receipt_sha256: str | None
450
+
451
+ def __post_init__(self) -> None:
452
+ require_sha256(self.preparation_sha256, "preparation_sha256")
453
+ require_sha256(
454
+ self.runtime_start_receipt_sha256,
455
+ "runtime_start_receipt_sha256",
456
+ )
457
+ if type(self.generation) is not int or self.generation <= 0:
458
+ raise ValueError("archive cutoff generation must be positive")
459
+ if type(self.kind) is not CampaignGenerationKind:
460
+ raise TypeError("archive cutoff kind must be CampaignGenerationKind")
461
+ require_sha256(self.step_sha256, "step_sha256")
462
+ if self.prior_stage_receipt_sha256 is not None:
463
+ require_sha256(
464
+ self.prior_stage_receipt_sha256,
465
+ "prior_stage_receipt_sha256",
466
+ )
467
+
468
+ def to_record(self) -> dict[str, object]:
469
+ self.__post_init__()
470
+ return {
471
+ "preparation_sha256": self.preparation_sha256,
472
+ "runtime_start_receipt_sha256": self.runtime_start_receipt_sha256,
473
+ "generation": self.generation,
474
+ "kind": self.kind.value,
475
+ "step_sha256": self.step_sha256,
476
+ "prior_stage_receipt_sha256": self.prior_stage_receipt_sha256,
477
+ }
478
+
479
+ @property
480
+ def request_sha256(self) -> str:
481
+ return _hash(_ARCHIVE_REQUEST_DOMAIN, self.to_record())
482
+
483
+
484
+ @dataclass(frozen=True, slots=True)
485
+ class CampaignArchiveCutoffReceipt:
486
+ request_sha256: str
487
+ preparation_sha256: str
488
+ generation: int
489
+ archive: FrozenJsonObject
490
+ evidence: FrozenJsonObject
491
+
492
+ def __post_init__(self) -> None:
493
+ require_sha256(self.request_sha256, "request_sha256")
494
+ require_sha256(self.preparation_sha256, "preparation_sha256")
495
+ if type(self.generation) is not int or self.generation <= 0:
496
+ raise ValueError("archive cutoff generation must be positive")
497
+ _frozen(self.archive, name="archive")
498
+ _frozen(self.evidence, name="evidence")
499
+
500
+ def _unsigned_record(self) -> dict[str, object]:
501
+ self.__post_init__()
502
+ return {
503
+ "request_sha256": self.request_sha256,
504
+ "preparation_sha256": self.preparation_sha256,
505
+ "generation": self.generation,
506
+ "archive": thaw_json(self.archive),
507
+ "archive_sha256": typed_json_sha256(self.archive),
508
+ "evidence": thaw_json(self.evidence),
509
+ }
510
+
511
+ @property
512
+ def receipt_sha256(self) -> str:
513
+ return _hash(_ARCHIVE_RECEIPT_DOMAIN, self._unsigned_record())
514
+
515
+ def to_record(self) -> dict[str, object]:
516
+ return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
517
+
518
+
519
+ @dataclass(frozen=True, slots=True)
520
+ class CampaignStageReceipt:
521
+ """Opaque stage result plus exact accounting and selector trace evidence."""
522
+
523
+ request_sha256: str
524
+ preparation_sha256: str
525
+ generation: int
526
+ kind: CampaignGenerationKind
527
+ candidate_occurrence_count: int
528
+ unique_evaluation_count: int
529
+ selector_audits: tuple[CampaignSelectorAuditReceipt, ...]
530
+ result: FrozenJsonObject
531
+
532
+ def __post_init__(self) -> None:
533
+ require_sha256(self.request_sha256, "request_sha256")
534
+ require_sha256(self.preparation_sha256, "preparation_sha256")
535
+ if type(self.generation) is not int or self.generation <= 0:
536
+ raise ValueError("stage receipt generation must be positive")
537
+ if type(self.kind) is not CampaignGenerationKind:
538
+ raise TypeError("stage receipt kind must be CampaignGenerationKind")
539
+ if (
540
+ type(self.candidate_occurrence_count) is not int
541
+ or self.candidate_occurrence_count < 0
542
+ ):
543
+ raise ValueError("candidate_occurrence_count must be non-negative")
544
+ if (
545
+ type(self.unique_evaluation_count) is not int
546
+ or not 0 <= self.unique_evaluation_count <= self.candidate_occurrence_count
547
+ ):
548
+ raise ValueError(
549
+ "unique_evaluation_count must lie within candidate occurrences"
550
+ )
551
+ if type(self.selector_audits) is not tuple or any(
552
+ type(value) is not CampaignSelectorAuditReceipt
553
+ for value in self.selector_audits
554
+ ):
555
+ raise TypeError("selector_audits must contain exact audit receipts")
556
+ for value in self.selector_audits:
557
+ CampaignSelectorAuditReceipt.__post_init__(value)
558
+ _frozen(self.result, name="result")
559
+
560
+ def _unsigned_record(self) -> dict[str, object]:
561
+ self.__post_init__()
562
+ return {
563
+ "request_sha256": self.request_sha256,
564
+ "preparation_sha256": self.preparation_sha256,
565
+ "generation": self.generation,
566
+ "kind": self.kind.value,
567
+ "candidate_occurrence_count": self.candidate_occurrence_count,
568
+ "unique_evaluation_count": self.unique_evaluation_count,
569
+ "selector_audits": [value.to_record() for value in self.selector_audits],
570
+ "result": thaw_json(self.result),
571
+ }
572
+
573
+ @property
574
+ def receipt_sha256(self) -> str:
575
+ return _hash(_STAGE_RECEIPT_DOMAIN, self._unsigned_record())
576
+
577
+ def to_record(self) -> dict[str, object]:
578
+ return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
579
+
580
+
581
+ @dataclass(frozen=True, slots=True)
582
+ class CampaignStageRequest:
583
+ preparation_sha256: str
584
+ runtime_start_receipt_sha256: str
585
+ step: CampaignGenerationStep
586
+ archive_cutoff: CampaignArchiveCutoffReceipt
587
+ archive_utility: ArchiveUtilitySnapshot
588
+ source_portfolio: CampaignStageReceipt | None
589
+ test_eligible_reflection_receipt_sha256s: tuple[str, ...]
590
+ prior_selector_audit_set_sha256: str
591
+
592
+ def __post_init__(self) -> None:
593
+ require_sha256(self.preparation_sha256, "preparation_sha256")
594
+ require_sha256(
595
+ self.runtime_start_receipt_sha256,
596
+ "runtime_start_receipt_sha256",
597
+ )
598
+ if type(self.step) is not CampaignGenerationStep:
599
+ raise TypeError("step must be an exact CampaignGenerationStep")
600
+ CampaignGenerationStep.__post_init__(self.step)
601
+ if type(self.archive_cutoff) is not CampaignArchiveCutoffReceipt:
602
+ raise TypeError("archive_cutoff must be exact")
603
+ CampaignArchiveCutoffReceipt.__post_init__(self.archive_cutoff)
604
+ if type(self.archive_utility) is not ArchiveUtilitySnapshot:
605
+ raise TypeError("archive_utility must be exact")
606
+ ArchiveUtilitySnapshot.__post_init__(self.archive_utility)
607
+ if self.archive_cutoff.generation != self.step.generation:
608
+ raise ValueError("archive cutoff generation differs from stage")
609
+ if (
610
+ self.archive_utility.generation != self.step.generation
611
+ or self.archive_utility.archive_sha256
612
+ != typed_json_sha256(self.archive_cutoff.archive)
613
+ ):
614
+ raise ValueError("archive utility differs from the pre-stage cutoff")
615
+ if self.step.kind is CampaignGenerationKind.PORTFOLIO:
616
+ if self.source_portfolio is not None:
617
+ raise ValueError("portfolio stage cannot carry a source portfolio")
618
+ else:
619
+ if type(self.source_portfolio) is not CampaignStageReceipt:
620
+ raise ValueError("recombination requires its portfolio receipt")
621
+ CampaignStageReceipt.__post_init__(self.source_portfolio)
622
+ if (
623
+ self.source_portfolio.kind is not CampaignGenerationKind.PORTFOLIO
624
+ or self.source_portfolio.generation
625
+ != self.step.source_portfolio_generation
626
+ or self.source_portfolio.preparation_sha256 != self.preparation_sha256
627
+ ):
628
+ raise ValueError("recombination source portfolio is foreign")
629
+ if type(self.test_eligible_reflection_receipt_sha256s) is not tuple or any(
630
+ type(value) is not str
631
+ for value in self.test_eligible_reflection_receipt_sha256s
632
+ ):
633
+ raise TypeError(
634
+ "test-eligible reflection hashes must be an exact string tuple"
635
+ )
636
+ for value in self.test_eligible_reflection_receipt_sha256s:
637
+ require_sha256(value, "test_eligible_reflection_receipt_sha256")
638
+ if self.test_eligible_reflection_receipt_sha256s != tuple(
639
+ sorted(set(self.test_eligible_reflection_receipt_sha256s))
640
+ ):
641
+ raise ValueError(
642
+ "test-eligible reflection hashes must be unique and canonical"
643
+ )
644
+ require_sha256(
645
+ self.prior_selector_audit_set_sha256,
646
+ "prior_selector_audit_set_sha256",
647
+ )
648
+
649
+ def to_record(self) -> dict[str, object]:
650
+ self.__post_init__()
651
+ return {
652
+ "preparation_sha256": self.preparation_sha256,
653
+ "runtime_start_receipt_sha256": self.runtime_start_receipt_sha256,
654
+ "step": self.step.to_record(),
655
+ "step_sha256": campaign_step_sha256(self.step),
656
+ "archive_cutoff_receipt_sha256": self.archive_cutoff.receipt_sha256,
657
+ "archive_utility_snapshot_sha256": self.archive_utility.snapshot_sha256,
658
+ "source_portfolio_receipt_sha256": (
659
+ None
660
+ if self.source_portfolio is None
661
+ else self.source_portfolio.receipt_sha256
662
+ ),
663
+ "test_eligible_reflection_receipt_sha256s": list(
664
+ self.test_eligible_reflection_receipt_sha256s
665
+ ),
666
+ "prior_selector_audit_set_sha256": (self.prior_selector_audit_set_sha256),
667
+ }
668
+
669
+ @property
670
+ def request_sha256(self) -> str:
671
+ return _hash(_STAGE_REQUEST_DOMAIN, self.to_record())
672
+
673
+
674
+ @dataclass(frozen=True, slots=True)
675
+ class CampaignReflectionRequest:
676
+ """Reflection over one exact, already-sealed recombination cutoff.
677
+
678
+ ``source_stage`` is the complete evidence boundary exposed by this
679
+ application port. A delayed admission barrier may occur later, but no
680
+ later stage receipt is present in this request or passed to ``reflect``.
681
+ """
682
+
683
+ preparation_sha256: str
684
+ runtime_start_receipt_sha256: str
685
+ wave: CampaignReflectionWave
686
+ source_stage: CampaignStageReceipt
687
+
688
+ def __post_init__(self) -> None:
689
+ require_sha256(self.preparation_sha256, "preparation_sha256")
690
+ require_sha256(
691
+ self.runtime_start_receipt_sha256,
692
+ "runtime_start_receipt_sha256",
693
+ )
694
+ if type(self.wave) is not CampaignReflectionWave:
695
+ raise TypeError("wave must be an exact CampaignReflectionWave")
696
+ CampaignReflectionWave.__post_init__(self.wave)
697
+ if type(self.source_stage) is not CampaignStageReceipt:
698
+ raise TypeError("source_stage must be exact")
699
+ CampaignStageReceipt.__post_init__(self.source_stage)
700
+ if (
701
+ self.source_stage.preparation_sha256 != self.preparation_sha256
702
+ or self.source_stage.generation != self.wave.source_generation
703
+ or self.source_stage.kind is not CampaignGenerationKind.RECOMBINATION
704
+ ):
705
+ raise ValueError("reflection source stage is foreign")
706
+
707
+ def to_record(self) -> dict[str, object]:
708
+ self.__post_init__()
709
+ record: dict[str, object] = {
710
+ "preparation_sha256": self.preparation_sha256,
711
+ "runtime_start_receipt_sha256": self.runtime_start_receipt_sha256,
712
+ "wave": self.wave.to_record(),
713
+ "source_stage_receipt_sha256": self.source_stage.receipt_sha256,
714
+ }
715
+ barrier = self.wave.promotion_barrier_generation
716
+ if barrier is not None and barrier > self.wave.source_generation:
717
+ record["sealed_evidence_cutoff"] = {
718
+ "generation": self.source_stage.generation,
719
+ "stage_receipt_sha256": self.source_stage.receipt_sha256,
720
+ }
721
+ record["future_stage_evidence_permitted"] = False
722
+ return record
723
+
724
+ @property
725
+ def request_sha256(self) -> str:
726
+ return _hash(_REFLECTION_REQUEST_DOMAIN, self.to_record())
727
+
728
+
729
+ class CampaignReflectionStatus(str, Enum):
730
+ COMPLETED = "completed"
731
+ ABSTAINED = "abstained"
732
+ FAILED = "failed"
733
+
734
+
735
+ @dataclass(frozen=True, slots=True)
736
+ class CampaignReflectionReceipt:
737
+ request_sha256: str
738
+ preparation_sha256: str
739
+ source_generation: int
740
+ source_stage_receipt_sha256: str
741
+ logical_agent_calls: int
742
+ visibility: ReflectionVisibility
743
+ status: CampaignReflectionStatus
744
+ failure_type: str | None
745
+ quarantined_result: FrozenJsonObject
746
+
747
+ def __post_init__(self) -> None:
748
+ require_sha256(self.request_sha256, "request_sha256")
749
+ require_sha256(self.preparation_sha256, "preparation_sha256")
750
+ if type(self.source_generation) is not int or self.source_generation <= 0:
751
+ raise ValueError("reflection source_generation must be positive")
752
+ require_sha256(
753
+ self.source_stage_receipt_sha256,
754
+ "source_stage_receipt_sha256",
755
+ )
756
+ if type(self.logical_agent_calls) is not int or self.logical_agent_calls <= 0:
757
+ raise ValueError("logical_agent_calls must be positive")
758
+ if self.visibility is not ReflectionVisibility.QUARANTINED_UNTIL_BLOCK_CLOSE:
759
+ raise ValueError("reflection result must remain quarantined")
760
+ if type(self.status) is not CampaignReflectionStatus:
761
+ raise TypeError("status must be CampaignReflectionStatus")
762
+ if self.status is CampaignReflectionStatus.COMPLETED:
763
+ if self.failure_type is not None:
764
+ raise ValueError("completed reflection cannot carry failure_type")
765
+ elif self.status is CampaignReflectionStatus.ABSTAINED:
766
+ if self.failure_type is not None:
767
+ raise ValueError("abstained reflection cannot carry failure_type")
768
+ abstention = thaw_json(self.quarantined_result)
769
+ if (
770
+ abstention.get("status")
771
+ != "abstained_no_identifiable_mutation_evidence"
772
+ or abstention.get("evidence_tier") != "e0"
773
+ or abstention.get("provider_calls") != 0
774
+ or abstention.get("publishable_reflection_content") is not False
775
+ ):
776
+ raise ValueError("abstained reflection requires typed E0 evidence")
777
+ elif type(self.failure_type) is not str or not self.failure_type:
778
+ raise ValueError("failed reflection requires failure_type")
779
+ _frozen(self.quarantined_result, name="quarantined_result")
780
+
781
+ def _unsigned_record(self) -> dict[str, object]:
782
+ self.__post_init__()
783
+ return {
784
+ "request_sha256": self.request_sha256,
785
+ "preparation_sha256": self.preparation_sha256,
786
+ "source_generation": self.source_generation,
787
+ "source_stage_receipt_sha256": self.source_stage_receipt_sha256,
788
+ "logical_agent_calls": self.logical_agent_calls,
789
+ "visibility": self.visibility.value,
790
+ "status": self.status.value,
791
+ "failure_type": self.failure_type,
792
+ "quarantined_result": thaw_json(self.quarantined_result),
793
+ }
794
+
795
+ @property
796
+ def receipt_sha256(self) -> str:
797
+ return _hash(_REFLECTION_RECEIPT_DOMAIN, self._unsigned_record())
798
+
799
+ def to_record(self) -> dict[str, object]:
800
+ return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
801
+
802
+
803
+ @dataclass(frozen=True, slots=True)
804
+ class CampaignReflectionCancellationReceipt:
805
+ """Durable accounting for one scheduled reflection canceled on abort."""
806
+
807
+ request_sha256: str
808
+ preparation_sha256: str
809
+ source_generation: int
810
+ source_stage_receipt_sha256: str
811
+ logical_agent_calls_reserved: int
812
+ dispatched_to_runtime: bool
813
+ cancellation_reason: str
814
+
815
+ def __post_init__(self) -> None:
816
+ require_sha256(self.request_sha256, "request_sha256")
817
+ require_sha256(self.preparation_sha256, "preparation_sha256")
818
+ if type(self.source_generation) is not int or self.source_generation <= 0:
819
+ raise ValueError("source_generation must be a positive exact integer")
820
+ require_sha256(
821
+ self.source_stage_receipt_sha256,
822
+ "source_stage_receipt_sha256",
823
+ )
824
+ if (
825
+ type(self.logical_agent_calls_reserved) is not int
826
+ or self.logical_agent_calls_reserved <= 0
827
+ ):
828
+ raise ValueError("logical_agent_calls_reserved must be positive")
829
+ if type(self.dispatched_to_runtime) is not bool:
830
+ raise TypeError("dispatched_to_runtime must be an exact bool")
831
+ if (
832
+ type(self.cancellation_reason) is not str
833
+ or _TOKEN.fullmatch(self.cancellation_reason) is None
834
+ ):
835
+ raise ValueError("cancellation_reason must use the campaign token grammar")
836
+
837
+ @property
838
+ def logical_agent_calls_dispatched_to_runtime(self) -> int:
839
+ self.__post_init__()
840
+ return self.logical_agent_calls_reserved if self.dispatched_to_runtime else 0
841
+
842
+ def _unsigned_record(self) -> dict[str, object]:
843
+ self.__post_init__()
844
+ return {
845
+ "request_sha256": self.request_sha256,
846
+ "preparation_sha256": self.preparation_sha256,
847
+ "source_generation": self.source_generation,
848
+ "source_stage_receipt_sha256": self.source_stage_receipt_sha256,
849
+ "logical_agent_calls_reserved": self.logical_agent_calls_reserved,
850
+ "logical_agent_calls_dispatched_to_runtime": (
851
+ self.logical_agent_calls_dispatched_to_runtime
852
+ ),
853
+ "dispatched_to_runtime": self.dispatched_to_runtime,
854
+ "cancellation_reason": self.cancellation_reason,
855
+ "publishable_reflection_content": False,
856
+ }
857
+
858
+ @property
859
+ def receipt_sha256(self) -> str:
860
+ return _hash(
861
+ _REFLECTION_CANCELLATION_RECEIPT_DOMAIN,
862
+ self._unsigned_record(),
863
+ )
864
+
865
+ def to_record(self) -> dict[str, object]:
866
+ return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
867
+
868
+
869
+ @dataclass(frozen=True, slots=True)
870
+ class CampaignReflectionTestAdmissionRequest:
871
+ """Admit quarantined evidence only to later controlled testing.
872
+
873
+ For a delayed barrier, ``admission_cutoff_stage`` authenticates the later
874
+ stage seal available to an admission-time shadow screen. It is deliberately
875
+ absent from :class:`CampaignReflectionRequest`, so it cannot alter or leak
876
+ into the already-running reflection.
877
+ """
878
+
879
+ preparation_sha256: str
880
+ runtime_start_receipt_sha256: str
881
+ barrier: CampaignPromotionBarrier
882
+ reflections: tuple[CampaignReflectionReceipt, ...]
883
+ previously_test_eligible_reflection_receipt_sha256s: tuple[str, ...]
884
+ admission_cutoff_stage: CampaignStageReceipt | None = None
885
+
886
+ def __post_init__(self) -> None:
887
+ require_sha256(self.preparation_sha256, "preparation_sha256")
888
+ require_sha256(
889
+ self.runtime_start_receipt_sha256,
890
+ "runtime_start_receipt_sha256",
891
+ )
892
+ if type(self.barrier) is not CampaignPromotionBarrier:
893
+ raise TypeError("barrier must be an exact CampaignPromotionBarrier")
894
+ CampaignPromotionBarrier.__post_init__(self.barrier)
895
+ if type(self.reflections) is not tuple or any(
896
+ type(value) is not CampaignReflectionReceipt for value in self.reflections
897
+ ):
898
+ raise TypeError("reflections must contain exact receipts")
899
+ for value in self.reflections:
900
+ CampaignReflectionReceipt.__post_init__(value)
901
+ if tuple(value.source_generation for value in self.reflections) != (
902
+ self.barrier.reflection_source_generations
903
+ ):
904
+ raise ValueError("test-admission reflections differ from barrier sources")
905
+ if any(
906
+ value.preparation_sha256 != self.preparation_sha256
907
+ or value.status is not CampaignReflectionStatus.COMPLETED
908
+ for value in self.reflections
909
+ ):
910
+ raise ValueError("test admission requires completed reflections")
911
+ values = self.previously_test_eligible_reflection_receipt_sha256s
912
+ if type(values) is not tuple or any(type(value) is not str for value in values):
913
+ raise TypeError(
914
+ "previously test-eligible hashes must be an exact string tuple"
915
+ )
916
+ for value in values:
917
+ require_sha256(value, "previous_test_eligible_reflection_sha256")
918
+ if values != tuple(sorted(set(values))):
919
+ raise ValueError(
920
+ "previously test-eligible hashes must be unique and canonical"
921
+ )
922
+ delayed = (
923
+ self.barrier.generation
924
+ > self.barrier.reflection_source_generations[-1]
925
+ )
926
+ if delayed:
927
+ if type(self.admission_cutoff_stage) is not CampaignStageReceipt:
928
+ raise ValueError(
929
+ "delayed admission requires its exact barrier-stage cutoff"
930
+ )
931
+ CampaignStageReceipt.__post_init__(self.admission_cutoff_stage)
932
+ if (
933
+ self.admission_cutoff_stage.preparation_sha256
934
+ != self.preparation_sha256
935
+ or self.admission_cutoff_stage.generation
936
+ != self.barrier.generation
937
+ or self.admission_cutoff_stage.kind
938
+ is not CampaignGenerationKind.RECOMBINATION
939
+ ):
940
+ raise ValueError("delayed admission cutoff stage is foreign")
941
+ elif self.admission_cutoff_stage is not None:
942
+ raise ValueError(
943
+ "source-closing admission cannot carry a later-stage cutoff"
944
+ )
945
+
946
+ def to_record(self) -> dict[str, object]:
947
+ self.__post_init__()
948
+ record: dict[str, object] = {
949
+ "preparation_sha256": self.preparation_sha256,
950
+ "runtime_start_receipt_sha256": self.runtime_start_receipt_sha256,
951
+ "barrier": self.barrier.to_record(),
952
+ "reflection_receipt_sha256s": [
953
+ value.receipt_sha256 for value in self.reflections
954
+ ],
955
+ "previously_test_eligible_reflection_receipt_sha256s": list(
956
+ self.previously_test_eligible_reflection_receipt_sha256s
957
+ ),
958
+ "admission_scope": "controlled_future_testing_only",
959
+ "lifecycle_promotion": False,
960
+ }
961
+ if self.admission_cutoff_stage is not None:
962
+ record["admission_cutoff_stage_receipt_sha256"] = (
963
+ self.admission_cutoff_stage.receipt_sha256
964
+ )
965
+ record["reflection_cutoff_precedes_admission_cutoff"] = True
966
+ record["future_evidence_visible_to_reflection"] = False
967
+ return record
968
+
969
+ @property
970
+ def request_sha256(self) -> str:
971
+ return _hash(_TEST_ADMISSION_REQUEST_DOMAIN, self.to_record())
972
+
973
+
974
+ @dataclass(frozen=True, slots=True)
975
+ class CampaignReflectionTestAdmissionReceipt:
976
+ request_sha256: str
977
+ preparation_sha256: str
978
+ barrier_generation: int
979
+ admitted_reflection_receipt_sha256s: tuple[str, ...]
980
+ test_eligible_reflection_receipt_sha256s: tuple[str, ...]
981
+ lifecycle_promoted: bool
982
+ evidence: FrozenJsonObject
983
+
984
+ def __post_init__(self) -> None:
985
+ require_sha256(self.request_sha256, "request_sha256")
986
+ require_sha256(self.preparation_sha256, "preparation_sha256")
987
+ if type(self.barrier_generation) is not int or self.barrier_generation <= 0:
988
+ raise ValueError("barrier_generation must be positive")
989
+ for name in (
990
+ "admitted_reflection_receipt_sha256s",
991
+ "test_eligible_reflection_receipt_sha256s",
992
+ ):
993
+ values = getattr(self, name)
994
+ if type(values) is not tuple or any(
995
+ type(value) is not str for value in values
996
+ ):
997
+ raise TypeError(f"{name} must be an exact string tuple")
998
+ for value in values:
999
+ require_sha256(value, name[:-1])
1000
+ if values != tuple(sorted(set(values))):
1001
+ raise ValueError(f"{name} must be unique and canonical")
1002
+ if self.lifecycle_promoted is not False:
1003
+ raise ValueError(
1004
+ "reflection barrier cannot lifecycle-promote untested insights"
1005
+ )
1006
+ _frozen(self.evidence, name="evidence")
1007
+
1008
+ def _unsigned_record(self) -> dict[str, object]:
1009
+ self.__post_init__()
1010
+ return {
1011
+ "request_sha256": self.request_sha256,
1012
+ "preparation_sha256": self.preparation_sha256,
1013
+ "barrier_generation": self.barrier_generation,
1014
+ "admitted_reflection_receipt_sha256s": list(
1015
+ self.admitted_reflection_receipt_sha256s
1016
+ ),
1017
+ "test_eligible_reflection_receipt_sha256s": list(
1018
+ self.test_eligible_reflection_receipt_sha256s
1019
+ ),
1020
+ "lifecycle_promoted": False,
1021
+ "evidence": thaw_json(self.evidence),
1022
+ }
1023
+
1024
+ @property
1025
+ def receipt_sha256(self) -> str:
1026
+ return _hash(_TEST_ADMISSION_RECEIPT_DOMAIN, self._unsigned_record())
1027
+
1028
+ def to_record(self) -> dict[str, object]:
1029
+ return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
1030
+
1031
+
1032
+ @dataclass(frozen=True, slots=True)
1033
+ class CampaignTailDrainReceipt:
1034
+ preparation_sha256: str
1035
+ drained_reflections: tuple[tuple[int, str], ...]
1036
+ admitted_for_testing: bool = False
1037
+ lifecycle_promoted: bool = False
1038
+
1039
+ def __post_init__(self) -> None:
1040
+ require_sha256(self.preparation_sha256, "preparation_sha256")
1041
+ if type(self.drained_reflections) is not tuple or not self.drained_reflections:
1042
+ raise ValueError("tail drain requires reflection receipts")
1043
+ generations: list[int] = []
1044
+ for item in self.drained_reflections:
1045
+ if (
1046
+ type(item) is not tuple
1047
+ or len(item) != 2
1048
+ or type(item[0]) is not int
1049
+ or item[0] <= 0
1050
+ or type(item[1]) is not str
1051
+ ):
1052
+ raise TypeError(
1053
+ "drained_reflections must contain generation/hash pairs"
1054
+ )
1055
+ require_sha256(item[1], "reflection_receipt_sha256")
1056
+ generations.append(item[0])
1057
+ if generations != sorted(set(generations)):
1058
+ raise ValueError("tail drain generations must be unique and canonical")
1059
+ if (
1060
+ self.admitted_for_testing is not False
1061
+ or self.lifecycle_promoted is not False
1062
+ ):
1063
+ raise ValueError(
1064
+ "incomplete-tail reflections remain quarantined and unadmitted"
1065
+ )
1066
+
1067
+ def _unsigned_record(self) -> dict[str, object]:
1068
+ self.__post_init__()
1069
+ return {
1070
+ "preparation_sha256": self.preparation_sha256,
1071
+ "drained_reflections": [
1072
+ {"source_generation": generation, "receipt_sha256": receipt}
1073
+ for generation, receipt in self.drained_reflections
1074
+ ],
1075
+ "admitted_for_testing": False,
1076
+ "lifecycle_promoted": False,
1077
+ }
1078
+
1079
+ @property
1080
+ def receipt_sha256(self) -> str:
1081
+ return _hash(_TAIL_DRAIN_DOMAIN, self._unsigned_record())
1082
+
1083
+ def to_record(self) -> dict[str, object]:
1084
+ return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
1085
+
1086
+
1087
+ @dataclass(frozen=True, slots=True)
1088
+ class CampaignExecutionCounters:
1089
+ generations_completed: int
1090
+ candidate_occurrences: int
1091
+ unique_evaluations: int
1092
+ logical_agent_calls: int
1093
+ logical_agent_calls_dispatched_to_runtime: int = 0
1094
+ logical_agent_calls_succeeded: int = 0
1095
+ logical_agent_calls_abstained: int = 0
1096
+ logical_agent_calls_failed: int = 0
1097
+ logical_agent_calls_cancelled_before_dispatch: int = 0
1098
+ logical_agent_calls_cancelled_after_dispatch: int = 0
1099
+
1100
+ def __post_init__(self) -> None:
1101
+ for name in (
1102
+ "generations_completed",
1103
+ "candidate_occurrences",
1104
+ "unique_evaluations",
1105
+ "logical_agent_calls",
1106
+ "logical_agent_calls_dispatched_to_runtime",
1107
+ "logical_agent_calls_succeeded",
1108
+ "logical_agent_calls_abstained",
1109
+ "logical_agent_calls_failed",
1110
+ "logical_agent_calls_cancelled_before_dispatch",
1111
+ "logical_agent_calls_cancelled_after_dispatch",
1112
+ ):
1113
+ value = getattr(self, name)
1114
+ if type(value) is not int or value < 0:
1115
+ raise ValueError(f"{name} must be a non-negative exact integer")
1116
+ if self.logical_agent_calls_dispatched_to_runtime > self.logical_agent_calls:
1117
+ raise ValueError("dispatched logical calls exceed reserved calls")
1118
+ if (
1119
+ self.logical_agent_calls_dispatched_to_runtime
1120
+ + self.logical_agent_calls_cancelled_before_dispatch
1121
+ > self.logical_agent_calls
1122
+ ):
1123
+ raise ValueError(
1124
+ "dispatched and pre-dispatch-cancelled calls exceed reserve"
1125
+ )
1126
+ if (
1127
+ self.logical_agent_calls_succeeded
1128
+ + self.logical_agent_calls_abstained
1129
+ + self.logical_agent_calls_failed
1130
+ + self.logical_agent_calls_cancelled_after_dispatch
1131
+ > self.logical_agent_calls_dispatched_to_runtime
1132
+ ):
1133
+ raise ValueError("settled logical calls exceed dispatched calls")
1134
+
1135
+ def to_record(self) -> dict[str, int]:
1136
+ self.__post_init__()
1137
+ return {
1138
+ "generations_completed": self.generations_completed,
1139
+ "candidate_occurrences": self.candidate_occurrences,
1140
+ "unique_evaluations": self.unique_evaluations,
1141
+ "logical_agent_calls": self.logical_agent_calls,
1142
+ "logical_agent_calls_reserved": self.logical_agent_calls,
1143
+ "logical_agent_calls_dispatched_to_runtime": (
1144
+ self.logical_agent_calls_dispatched_to_runtime
1145
+ ),
1146
+ "logical_agent_calls_succeeded": self.logical_agent_calls_succeeded,
1147
+ "logical_agent_calls_abstained": self.logical_agent_calls_abstained,
1148
+ "logical_agent_calls_failed": self.logical_agent_calls_failed,
1149
+ "logical_agent_calls_cancelled_before_dispatch": (
1150
+ self.logical_agent_calls_cancelled_before_dispatch
1151
+ ),
1152
+ "logical_agent_calls_cancelled_after_dispatch": (
1153
+ self.logical_agent_calls_cancelled_after_dispatch
1154
+ ),
1155
+ }
1156
+
1157
+ @property
1158
+ def counters_sha256(self) -> str:
1159
+ return _hash(_COUNTERS_DOMAIN, self.to_record())
1160
+
1161
+
1162
+ class CampaignExecutionStatus(str, Enum):
1163
+ COMPLETED = "completed"
1164
+ DEGRADED = "degraded"
1165
+ FAILED = "failed"
1166
+
1167
+
1168
+ @dataclass(frozen=True, slots=True)
1169
+ class CampaignFinalizationRequest:
1170
+ preparation_sha256: str
1171
+ runtime_start_receipt_sha256: str
1172
+ status: CampaignExecutionStatus
1173
+ counters: CampaignExecutionCounters
1174
+ stage_receipt_sha256s: tuple[str, ...]
1175
+ reflection_receipt_sha256s: tuple[str, ...]
1176
+ reflection_cancellation_receipt_sha256s: tuple[str, ...]
1177
+ test_admission_receipt_sha256s: tuple[str, ...]
1178
+ tail_drain_receipt_sha256: str | None
1179
+ last_durable_event_sha256: str | None
1180
+ failure_type: str | None
1181
+
1182
+ def __post_init__(self) -> None:
1183
+ require_sha256(self.preparation_sha256, "preparation_sha256")
1184
+ require_sha256(
1185
+ self.runtime_start_receipt_sha256,
1186
+ "runtime_start_receipt_sha256",
1187
+ )
1188
+ if type(self.status) is not CampaignExecutionStatus:
1189
+ raise TypeError("status must be CampaignExecutionStatus")
1190
+ if type(self.counters) is not CampaignExecutionCounters:
1191
+ raise TypeError("counters must be exact")
1192
+ CampaignExecutionCounters.__post_init__(self.counters)
1193
+ for name in (
1194
+ "stage_receipt_sha256s",
1195
+ "reflection_receipt_sha256s",
1196
+ "reflection_cancellation_receipt_sha256s",
1197
+ "test_admission_receipt_sha256s",
1198
+ ):
1199
+ values = getattr(self, name)
1200
+ if type(values) is not tuple or any(
1201
+ type(value) is not str for value in values
1202
+ ):
1203
+ raise TypeError(f"{name} must be an exact string tuple")
1204
+ for value in values:
1205
+ require_sha256(value, name[:-1])
1206
+ for name in (
1207
+ "tail_drain_receipt_sha256",
1208
+ "last_durable_event_sha256",
1209
+ ):
1210
+ value = getattr(self, name)
1211
+ if value is not None:
1212
+ require_sha256(value, name)
1213
+ if self.status is CampaignExecutionStatus.COMPLETED:
1214
+ if self.failure_type is not None:
1215
+ raise ValueError("completed finalization cannot carry failure_type")
1216
+ elif type(self.failure_type) is not str or not self.failure_type:
1217
+ raise ValueError("failed finalization requires failure_type")
1218
+
1219
+ def to_record(self) -> dict[str, object]:
1220
+ self.__post_init__()
1221
+ return {
1222
+ "preparation_sha256": self.preparation_sha256,
1223
+ "runtime_start_receipt_sha256": self.runtime_start_receipt_sha256,
1224
+ "status": self.status.value,
1225
+ "counters": self.counters.to_record(),
1226
+ "stage_receipt_sha256s": list(self.stage_receipt_sha256s),
1227
+ "reflection_receipt_sha256s": list(self.reflection_receipt_sha256s),
1228
+ "reflection_cancellation_receipt_sha256s": list(
1229
+ self.reflection_cancellation_receipt_sha256s
1230
+ ),
1231
+ "test_admission_receipt_sha256s": list(self.test_admission_receipt_sha256s),
1232
+ "tail_drain_receipt_sha256": self.tail_drain_receipt_sha256,
1233
+ "last_durable_event_sha256": self.last_durable_event_sha256,
1234
+ "failure_type": self.failure_type,
1235
+ }
1236
+
1237
+ @property
1238
+ def request_sha256(self) -> str:
1239
+ return _hash(_FINALIZATION_REQUEST_DOMAIN, self.to_record())
1240
+
1241
+
1242
+ @dataclass(frozen=True, slots=True)
1243
+ class CampaignFinalizationReceipt:
1244
+ request_sha256: str
1245
+ preparation_sha256: str
1246
+ status: CampaignExecutionStatus
1247
+ evidence: FrozenJsonObject
1248
+
1249
+ def __post_init__(self) -> None:
1250
+ require_sha256(self.request_sha256, "request_sha256")
1251
+ require_sha256(self.preparation_sha256, "preparation_sha256")
1252
+ if type(self.status) is not CampaignExecutionStatus:
1253
+ raise TypeError("status must be CampaignExecutionStatus")
1254
+ _frozen(self.evidence, name="evidence")
1255
+
1256
+ def _unsigned_record(self) -> dict[str, object]:
1257
+ self.__post_init__()
1258
+ return {
1259
+ "request_sha256": self.request_sha256,
1260
+ "preparation_sha256": self.preparation_sha256,
1261
+ "status": self.status.value,
1262
+ "evidence": thaw_json(self.evidence),
1263
+ }
1264
+
1265
+ @property
1266
+ def receipt_sha256(self) -> str:
1267
+ return _hash(_FINALIZATION_RECEIPT_DOMAIN, self._unsigned_record())
1268
+
1269
+ def to_record(self) -> dict[str, object]:
1270
+ return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
1271
+
1272
+
1273
+ @dataclass(frozen=True, slots=True)
1274
+ class CampaignCleanupRequest:
1275
+ preparation_sha256: str
1276
+ runtime_start_receipt_sha256: str
1277
+ status: CampaignExecutionStatus
1278
+ finalization_receipt_sha256: str | None
1279
+ failure_type: str | None
1280
+
1281
+ def __post_init__(self) -> None:
1282
+ require_sha256(self.preparation_sha256, "preparation_sha256")
1283
+ require_sha256(
1284
+ self.runtime_start_receipt_sha256,
1285
+ "runtime_start_receipt_sha256",
1286
+ )
1287
+ if type(self.status) is not CampaignExecutionStatus:
1288
+ raise TypeError("status must be CampaignExecutionStatus")
1289
+ if self.finalization_receipt_sha256 is not None:
1290
+ require_sha256(
1291
+ self.finalization_receipt_sha256,
1292
+ "finalization_receipt_sha256",
1293
+ )
1294
+ if self.status is CampaignExecutionStatus.COMPLETED:
1295
+ if self.failure_type is not None:
1296
+ raise ValueError("completed cleanup cannot carry failure_type")
1297
+ elif type(self.failure_type) is not str or not self.failure_type:
1298
+ raise ValueError("failed cleanup requires failure_type")
1299
+
1300
+ def to_record(self) -> dict[str, object]:
1301
+ self.__post_init__()
1302
+ return {
1303
+ "preparation_sha256": self.preparation_sha256,
1304
+ "runtime_start_receipt_sha256": self.runtime_start_receipt_sha256,
1305
+ "status": self.status.value,
1306
+ "finalization_receipt_sha256": self.finalization_receipt_sha256,
1307
+ "failure_type": self.failure_type,
1308
+ }
1309
+
1310
+ @property
1311
+ def request_sha256(self) -> str:
1312
+ return _hash(_CLEANUP_REQUEST_DOMAIN, self.to_record())
1313
+
1314
+
1315
+ @dataclass(frozen=True, slots=True)
1316
+ class CampaignCleanupReceipt:
1317
+ request_sha256: str
1318
+ preparation_sha256: str
1319
+ released: bool
1320
+ evidence: FrozenJsonObject
1321
+
1322
+ def __post_init__(self) -> None:
1323
+ require_sha256(self.request_sha256, "request_sha256")
1324
+ require_sha256(self.preparation_sha256, "preparation_sha256")
1325
+ if type(self.released) is not bool:
1326
+ raise TypeError("released must be an exact bool")
1327
+ _frozen(self.evidence, name="evidence")
1328
+
1329
+ def _unsigned_record(self) -> dict[str, object]:
1330
+ self.__post_init__()
1331
+ return {
1332
+ "request_sha256": self.request_sha256,
1333
+ "preparation_sha256": self.preparation_sha256,
1334
+ "released": self.released,
1335
+ "evidence": thaw_json(self.evidence),
1336
+ }
1337
+
1338
+ @property
1339
+ def receipt_sha256(self) -> str:
1340
+ return _hash(_CLEANUP_RECEIPT_DOMAIN, self._unsigned_record())
1341
+
1342
+ def to_record(self) -> dict[str, object]:
1343
+ return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
1344
+
1345
+
1346
+ class CampaignExecutionEventKind(str, Enum):
1347
+ EXECUTION_STARTED = "execution_started"
1348
+ ARCHIVE_UTILITY_FROZEN = "archive_utility_frozen"
1349
+ STAGE_SEALED = "stage_sealed"
1350
+ REFLECTION_LAUNCHED = "reflection_launched"
1351
+ REFLECTION_COMPLETED = "reflection_completed"
1352
+ REFLECTION_ABSTAINED = "reflection_abstained"
1353
+ REFLECTION_FAILED = "reflection_failed"
1354
+ REFLECTION_CANCELLED = "reflection_cancelled"
1355
+ REFLECTION_ADMITTED_FOR_TESTING = "reflection_admitted_for_testing"
1356
+ REFLECTION_TAIL_DRAINED = "reflection_tail_drained"
1357
+ EXECUTION_DEGRADED = "execution_degraded"
1358
+ EXECUTION_FAILED = "execution_failed"
1359
+ EXECUTION_FINALIZED = "execution_finalized"
1360
+ RUNTIME_CLEANED = "runtime_cleaned"
1361
+
1362
+
1363
+ @dataclass(frozen=True, slots=True)
1364
+ class CampaignExecutionEvent:
1365
+ preparation_sha256: str
1366
+ sequence: int
1367
+ kind: CampaignExecutionEventKind
1368
+ previous_event_sha256: str | None
1369
+ payload: FrozenJsonObject
1370
+
1371
+ def __post_init__(self) -> None:
1372
+ require_sha256(self.preparation_sha256, "preparation_sha256")
1373
+ if type(self.sequence) is not int or self.sequence <= 0:
1374
+ raise ValueError("event sequence must be positive")
1375
+ if type(self.kind) is not CampaignExecutionEventKind:
1376
+ raise TypeError("event kind must be CampaignExecutionEventKind")
1377
+ if self.previous_event_sha256 is not None:
1378
+ require_sha256(self.previous_event_sha256, "previous_event_sha256")
1379
+ if self.sequence == 1 and self.previous_event_sha256 is not None:
1380
+ raise ValueError("first event cannot name a predecessor")
1381
+ if self.sequence > 1 and self.previous_event_sha256 is None:
1382
+ raise ValueError("later event requires a predecessor")
1383
+ _frozen(self.payload, name="payload")
1384
+
1385
+ def _unsigned_record(self) -> dict[str, object]:
1386
+ self.__post_init__()
1387
+ return {
1388
+ "preparation_sha256": self.preparation_sha256,
1389
+ "sequence": self.sequence,
1390
+ "kind": self.kind.value,
1391
+ "previous_event_sha256": self.previous_event_sha256,
1392
+ "payload": thaw_json(self.payload),
1393
+ }
1394
+
1395
+ @property
1396
+ def event_sha256(self) -> str:
1397
+ return _hash(_EVENT_DOMAIN, self._unsigned_record())
1398
+
1399
+ def to_record(self) -> dict[str, object]:
1400
+ return {**self._unsigned_record(), "event_sha256": self.event_sha256}
1401
+
1402
+
1403
+ @dataclass(frozen=True, slots=True)
1404
+ class CampaignJournalAck:
1405
+ event_sha256: str
1406
+ durable: bool
1407
+
1408
+ def __post_init__(self) -> None:
1409
+ require_sha256(self.event_sha256, "event_sha256")
1410
+ if self.durable is not True:
1411
+ raise ValueError("campaign journal acknowledgement must be durable")
1412
+
1413
+
1414
+ @runtime_checkable
1415
+ class CampaignStageRuntimePort(Protocol):
1416
+ async def snapshot_archive(
1417
+ self,
1418
+ request: CampaignArchiveCutoffRequest,
1419
+ ) -> CampaignArchiveCutoffReceipt: ...
1420
+
1421
+ async def execute_stage(
1422
+ self,
1423
+ request: CampaignStageRequest,
1424
+ ) -> CampaignStageReceipt: ...
1425
+
1426
+
1427
+ @runtime_checkable
1428
+ class CampaignReflectionRuntimePort(Protocol):
1429
+ async def reflect(
1430
+ self,
1431
+ request: CampaignReflectionRequest,
1432
+ ) -> CampaignReflectionReceipt: ...
1433
+
1434
+ async def admit_for_testing(
1435
+ self,
1436
+ request: CampaignReflectionTestAdmissionRequest,
1437
+ ) -> CampaignReflectionTestAdmissionReceipt: ...
1438
+
1439
+
1440
+ @runtime_checkable
1441
+ class CampaignExecutionLifecyclePort(Protocol):
1442
+ """Own runtime resources around one scheduler execution.
1443
+
1444
+ ``start`` is transaction-like: if it raises before returning a valid start
1445
+ receipt, it must release anything it acquired. After a valid receipt is
1446
+ returned, the scheduler calls ``finalize`` and ``cleanup`` on every success,
1447
+ stage failure, receipt rejection, cancellation, or durable-journal failure.
1448
+ ``cleanup`` must tolerate a failed ``finalize`` request.
1449
+ """
1450
+
1451
+ async def start(
1452
+ self,
1453
+ prepared: PreparedEvolutionCampaign,
1454
+ ) -> CampaignExecutionStartReceipt: ...
1455
+
1456
+ async def finalize(
1457
+ self,
1458
+ request: CampaignFinalizationRequest,
1459
+ ) -> CampaignFinalizationReceipt: ...
1460
+
1461
+ async def cleanup(
1462
+ self,
1463
+ request: CampaignCleanupRequest,
1464
+ ) -> CampaignCleanupReceipt: ...
1465
+
1466
+
1467
+ @runtime_checkable
1468
+ class CampaignExecutionJournalPort(Protocol):
1469
+ async def append(self, event: CampaignExecutionEvent) -> CampaignJournalAck: ...
1470
+
1471
+
1472
+ @dataclass(frozen=True, slots=True)
1473
+ class CampaignExecutionResult:
1474
+ preparation_sha256: str
1475
+ runtime_start_receipt_sha256: str
1476
+ stage_receipts: tuple[CampaignStageReceipt, ...]
1477
+ reflection_receipts: tuple[CampaignReflectionReceipt, ...]
1478
+ reflection_cancellation_receipts: tuple[CampaignReflectionCancellationReceipt, ...]
1479
+ test_admission_receipts: tuple[CampaignReflectionTestAdmissionReceipt, ...]
1480
+ tail_drain_receipt: CampaignTailDrainReceipt | None
1481
+ counters: CampaignExecutionCounters
1482
+ finalization_receipt: CampaignFinalizationReceipt
1483
+ cleanup_receipt: CampaignCleanupReceipt
1484
+ durable_event_sha256s: tuple[str, ...]
1485
+
1486
+ def __post_init__(self) -> None:
1487
+ require_sha256(self.preparation_sha256, "preparation_sha256")
1488
+ require_sha256(
1489
+ self.runtime_start_receipt_sha256,
1490
+ "runtime_start_receipt_sha256",
1491
+ )
1492
+ if type(self.stage_receipts) is not tuple or not self.stage_receipts:
1493
+ raise ValueError("execution result requires stage receipts")
1494
+ if any(
1495
+ type(value) is not CampaignStageReceipt for value in self.stage_receipts
1496
+ ):
1497
+ raise TypeError("stage_receipts must contain exact receipts")
1498
+ if type(self.reflection_receipts) is not tuple or any(
1499
+ type(value) is not CampaignReflectionReceipt
1500
+ for value in self.reflection_receipts
1501
+ ):
1502
+ raise TypeError("reflection_receipts must contain exact receipts")
1503
+ if type(self.reflection_cancellation_receipts) is not tuple or any(
1504
+ type(value) is not CampaignReflectionCancellationReceipt
1505
+ for value in self.reflection_cancellation_receipts
1506
+ ):
1507
+ raise TypeError(
1508
+ "reflection_cancellation_receipts must contain exact receipts"
1509
+ )
1510
+ if type(self.test_admission_receipts) is not tuple or any(
1511
+ type(value) is not CampaignReflectionTestAdmissionReceipt
1512
+ for value in self.test_admission_receipts
1513
+ ):
1514
+ raise TypeError("test_admission_receipts must contain exact receipts")
1515
+ if (
1516
+ self.tail_drain_receipt is not None
1517
+ and type(self.tail_drain_receipt) is not CampaignTailDrainReceipt
1518
+ ):
1519
+ raise TypeError("tail_drain_receipt must be exact or None")
1520
+ if type(self.counters) is not CampaignExecutionCounters:
1521
+ raise TypeError("counters must be exact")
1522
+ CampaignExecutionCounters.__post_init__(self.counters)
1523
+ if type(self.finalization_receipt) is not CampaignFinalizationReceipt:
1524
+ raise TypeError("finalization_receipt must be exact")
1525
+ if type(self.cleanup_receipt) is not CampaignCleanupReceipt:
1526
+ raise TypeError("cleanup_receipt must be exact")
1527
+ if not self.cleanup_receipt.released:
1528
+ raise ValueError("successful execution requires released runtime resources")
1529
+ if type(self.durable_event_sha256s) is not tuple or any(
1530
+ type(value) is not str for value in self.durable_event_sha256s
1531
+ ):
1532
+ raise TypeError("durable event hashes must be an exact string tuple")
1533
+ for value in self.durable_event_sha256s:
1534
+ require_sha256(value, "durable_event_sha256")
1535
+
1536
+ def _unsigned_record(self) -> dict[str, object]:
1537
+ self.__post_init__()
1538
+ return {
1539
+ "preparation_sha256": self.preparation_sha256,
1540
+ "runtime_start_receipt_sha256": self.runtime_start_receipt_sha256,
1541
+ "stage_receipt_sha256s": [
1542
+ value.receipt_sha256 for value in self.stage_receipts
1543
+ ],
1544
+ "reflection_receipt_sha256s": [
1545
+ value.receipt_sha256 for value in self.reflection_receipts
1546
+ ],
1547
+ "reflection_cancellation_receipt_sha256s": [
1548
+ value.receipt_sha256 for value in self.reflection_cancellation_receipts
1549
+ ],
1550
+ "test_admission_receipt_sha256s": [
1551
+ value.receipt_sha256 for value in self.test_admission_receipts
1552
+ ],
1553
+ "tail_drain_receipt_sha256": (
1554
+ None
1555
+ if self.tail_drain_receipt is None
1556
+ else self.tail_drain_receipt.receipt_sha256
1557
+ ),
1558
+ "counters": self.counters.to_record(),
1559
+ "finalization_receipt_sha256": self.finalization_receipt.receipt_sha256,
1560
+ "cleanup_receipt_sha256": self.cleanup_receipt.receipt_sha256,
1561
+ "durable_event_sha256s": list(self.durable_event_sha256s),
1562
+ }
1563
+
1564
+ @property
1565
+ def result_sha256(self) -> str:
1566
+ return _hash(_RESULT_DOMAIN, self._unsigned_record())
1567
+
1568
+ def to_record(self) -> dict[str, object]:
1569
+ return {**self._unsigned_record(), "result_sha256": self.result_sha256}
1570
+
1571
+
1572
+ @dataclass(slots=True)
1573
+ class EvolutionCampaignScheduler:
1574
+ """Execute a prepared schedule while owning its scientific ledger."""
1575
+
1576
+ prepared: PreparedEvolutionCampaign
1577
+ policies: CampaignPolicies
1578
+ stages: CampaignStageRuntimePort
1579
+ reflections: CampaignReflectionRuntimePort
1580
+ lifecycle: CampaignExecutionLifecyclePort
1581
+ journal: CampaignExecutionJournalPort
1582
+ _has_run: bool = field(init=False, default=False)
1583
+
1584
+ def __post_init__(self) -> None:
1585
+ if type(self.prepared) is not PreparedEvolutionCampaign:
1586
+ raise TypeError("prepared must be an exact PreparedEvolutionCampaign")
1587
+ PreparedEvolutionCampaign.__post_init__(self.prepared)
1588
+ if type(self.policies) is not CampaignPolicies:
1589
+ raise TypeError("policies must be an exact CampaignPolicies")
1590
+ CampaignPolicies.__post_init__(self.policies)
1591
+ if self.policies.policies_sha256 != self.prepared.policies_sha256:
1592
+ raise ValueError("execution policies differ from prepared policies")
1593
+ if not isinstance(self.stages, CampaignStageRuntimePort):
1594
+ raise TypeError("stages must implement CampaignStageRuntimePort")
1595
+ if not isinstance(self.reflections, CampaignReflectionRuntimePort):
1596
+ raise TypeError("reflections must implement CampaignReflectionRuntimePort")
1597
+ if not isinstance(self.lifecycle, CampaignExecutionLifecyclePort):
1598
+ raise TypeError("lifecycle must implement CampaignExecutionLifecyclePort")
1599
+ if not isinstance(self.journal, CampaignExecutionJournalPort):
1600
+ raise TypeError("journal must implement CampaignExecutionJournalPort")
1601
+
1602
+ async def run(self) -> CampaignExecutionResult:
1603
+ """Run every prepared generation and always finalize and clean up."""
1604
+
1605
+ self.__post_init__()
1606
+ if self._has_run:
1607
+ raise RuntimeError("campaign scheduler instances are one-shot")
1608
+ self._has_run = True
1609
+ preparation_sha256 = self.prepared.preparation_sha256
1610
+ start = await self.lifecycle.start(self.prepared)
1611
+ if type(start) is not CampaignExecutionStartReceipt:
1612
+ raise TypeError("lifecycle start must return CampaignExecutionStartReceipt")
1613
+ CampaignExecutionStartReceipt.__post_init__(start)
1614
+
1615
+ event_sequence = 0
1616
+ last_event_sha256: str | None = None
1617
+ durable_event_sha256s: list[str] = []
1618
+
1619
+ async def append_event(
1620
+ kind: CampaignExecutionEventKind,
1621
+ payload: dict[str, object],
1622
+ ) -> None:
1623
+ nonlocal event_sequence, last_event_sha256
1624
+ event = CampaignExecutionEvent(
1625
+ preparation_sha256=preparation_sha256,
1626
+ sequence=event_sequence + 1,
1627
+ kind=kind,
1628
+ previous_event_sha256=last_event_sha256,
1629
+ payload=_freeze_record(payload),
1630
+ )
1631
+ ack = await self.journal.append(event)
1632
+ if type(ack) is not CampaignJournalAck:
1633
+ raise TypeError("journal must return CampaignJournalAck")
1634
+ CampaignJournalAck.__post_init__(ack)
1635
+ if ack.event_sha256 != event.event_sha256:
1636
+ raise CampaignExecutionContractError(
1637
+ "journal acknowledged a foreign event"
1638
+ )
1639
+ event_sequence = event.sequence
1640
+ last_event_sha256 = event.event_sha256
1641
+ durable_event_sha256s.append(event.event_sha256)
1642
+
1643
+ counters = CampaignExecutionCounters(
1644
+ generations_completed=0,
1645
+ candidate_occurrences=0,
1646
+ unique_evaluations=0,
1647
+ logical_agent_calls=0,
1648
+ )
1649
+ stage_receipts: list[CampaignStageReceipt] = []
1650
+ reflection_receipts: dict[int, CampaignReflectionReceipt] = {}
1651
+ reflection_cancellations: list[CampaignReflectionCancellationReceipt] = []
1652
+ test_admission_receipts: list[CampaignReflectionTestAdmissionReceipt] = []
1653
+ tail_drain: CampaignTailDrainReceipt | None = None
1654
+ selector_audits: list[CampaignSelectorAuditReceipt] = []
1655
+ test_eligible_reflections: tuple[str, ...] = ()
1656
+ reflection_tasks: dict[int, asyncio.Task[CampaignReflectionReceipt]] = {}
1657
+ reflection_requests: dict[int, CampaignReflectionRequest] = {}
1658
+ reflection_runtime_dispatched: set[int] = set()
1659
+ degraded_reflection_sources: set[int] = set()
1660
+ abstained_reflection_sources: set[int] = set()
1661
+ finalization: CampaignFinalizationReceipt | None = None
1662
+ cleanup: CampaignCleanupReceipt | None = None
1663
+ primary_error: BaseException | None = None
1664
+
1665
+ supervision = self.policies.reflection_supervision
1666
+ if type(supervision) is not CampaignReflectionSupervisionPolicy:
1667
+ raise TypeError("campaign has a foreign reflection supervision policy")
1668
+ CampaignReflectionSupervisionPolicy.__post_init__(supervision)
1669
+
1670
+ def update_call_counters(
1671
+ *,
1672
+ reserved: int = 0,
1673
+ dispatched: int = 0,
1674
+ succeeded: int = 0,
1675
+ abstained: int = 0,
1676
+ failed: int = 0,
1677
+ cancelled_before_dispatch: int = 0,
1678
+ cancelled_after_dispatch: int = 0,
1679
+ ) -> None:
1680
+ nonlocal counters
1681
+ counters = CampaignExecutionCounters(
1682
+ generations_completed=counters.generations_completed,
1683
+ candidate_occurrences=counters.candidate_occurrences,
1684
+ unique_evaluations=counters.unique_evaluations,
1685
+ logical_agent_calls=counters.logical_agent_calls + reserved,
1686
+ logical_agent_calls_dispatched_to_runtime=(
1687
+ counters.logical_agent_calls_dispatched_to_runtime + dispatched
1688
+ ),
1689
+ logical_agent_calls_succeeded=(
1690
+ counters.logical_agent_calls_succeeded + succeeded
1691
+ ),
1692
+ logical_agent_calls_abstained=(
1693
+ counters.logical_agent_calls_abstained + abstained
1694
+ ),
1695
+ logical_agent_calls_failed=(
1696
+ counters.logical_agent_calls_failed + failed
1697
+ ),
1698
+ logical_agent_calls_cancelled_before_dispatch=(
1699
+ counters.logical_agent_calls_cancelled_before_dispatch
1700
+ + cancelled_before_dispatch
1701
+ ),
1702
+ logical_agent_calls_cancelled_after_dispatch=(
1703
+ counters.logical_agent_calls_cancelled_after_dispatch
1704
+ + cancelled_after_dispatch
1705
+ ),
1706
+ )
1707
+
1708
+ def failed_reflection_receipt(
1709
+ source_generation: int,
1710
+ error: BaseException,
1711
+ ) -> CampaignReflectionReceipt:
1712
+ request = reflection_requests[source_generation]
1713
+ failure_type = type(error).__name__
1714
+ failure_digest = hashlib.sha256(
1715
+ type(error).__qualname__.encode("utf-8", errors="strict")
1716
+ + b"\x00"
1717
+ + str(error).encode("utf-8", errors="replace")
1718
+ ).hexdigest()
1719
+ return CampaignReflectionReceipt(
1720
+ request_sha256=request.request_sha256,
1721
+ preparation_sha256=preparation_sha256,
1722
+ source_generation=source_generation,
1723
+ source_stage_receipt_sha256=(request.source_stage.receipt_sha256),
1724
+ logical_agent_calls=request.wave.call_count,
1725
+ visibility=request.wave.visibility,
1726
+ status=CampaignReflectionStatus.FAILED,
1727
+ failure_type=failure_type,
1728
+ quarantined_result=_freeze_record(
1729
+ {
1730
+ "schema_version": 1,
1731
+ "status": "failed",
1732
+ "failure_type": failure_type,
1733
+ "failure_digest_sha256": failure_digest,
1734
+ "approved_summary": (
1735
+ "reflection execution failed without publishable "
1736
+ "insight content"
1737
+ ),
1738
+ "publishable_reflection_content": False,
1739
+ }
1740
+ ),
1741
+ )
1742
+
1743
+ async def run_reflection(
1744
+ request: CampaignReflectionRequest,
1745
+ started: asyncio.Event,
1746
+ ) -> CampaignReflectionReceipt:
1747
+ source_generation = request.wave.source_generation
1748
+ reflection_runtime_dispatched.add(source_generation)
1749
+ started.set()
1750
+ try:
1751
+ return await self.reflections.reflect(request)
1752
+ except asyncio.CancelledError:
1753
+ raise
1754
+ except Exception as error:
1755
+ return failed_reflection_receipt(source_generation, error)
1756
+
1757
+ async def settle_reflection(
1758
+ source_generation: int,
1759
+ receipt: CampaignReflectionReceipt,
1760
+ ) -> CampaignReflectionReceipt:
1761
+ if source_generation in reflection_receipts:
1762
+ if reflection_receipts[source_generation] != receipt:
1763
+ raise CampaignExecutionContractError(
1764
+ "reflection source settled with two different receipts"
1765
+ )
1766
+ return receipt
1767
+ request = reflection_requests[source_generation]
1768
+ if type(receipt) is not CampaignReflectionReceipt:
1769
+ raise TypeError("reflection runtime must return exact receipt")
1770
+ CampaignReflectionReceipt.__post_init__(receipt)
1771
+ if (
1772
+ receipt.request_sha256 != request.request_sha256
1773
+ or receipt.preparation_sha256 != preparation_sha256
1774
+ or receipt.source_generation != source_generation
1775
+ or receipt.source_stage_receipt_sha256
1776
+ != request.source_stage.receipt_sha256
1777
+ or receipt.logical_agent_calls != request.wave.call_count
1778
+ ):
1779
+ raise CampaignExecutionContractError(
1780
+ "reflection receipt differs from its launch request"
1781
+ )
1782
+ reflection_receipts[source_generation] = receipt
1783
+ reflection_tasks.pop(source_generation, None)
1784
+ if receipt.status is CampaignReflectionStatus.COMPLETED:
1785
+ update_call_counters(succeeded=receipt.logical_agent_calls)
1786
+ kind = CampaignExecutionEventKind.REFLECTION_COMPLETED
1787
+ elif receipt.status is CampaignReflectionStatus.ABSTAINED:
1788
+ update_call_counters(abstained=receipt.logical_agent_calls)
1789
+ abstained_reflection_sources.add(source_generation)
1790
+ kind = CampaignExecutionEventKind.REFLECTION_ABSTAINED
1791
+ else:
1792
+ update_call_counters(failed=receipt.logical_agent_calls)
1793
+ kind = CampaignExecutionEventKind.REFLECTION_FAILED
1794
+ await append_event(
1795
+ kind,
1796
+ {"reflection_receipt": receipt.to_record()},
1797
+ )
1798
+ return receipt
1799
+
1800
+ async def record_reflection_cancellation(
1801
+ source_generation: int,
1802
+ *,
1803
+ reason: str,
1804
+ ) -> CampaignReflectionCancellationReceipt:
1805
+ request = reflection_requests[source_generation]
1806
+ dispatched = source_generation in reflection_runtime_dispatched
1807
+ receipt = CampaignReflectionCancellationReceipt(
1808
+ request_sha256=request.request_sha256,
1809
+ preparation_sha256=preparation_sha256,
1810
+ source_generation=source_generation,
1811
+ source_stage_receipt_sha256=request.source_stage.receipt_sha256,
1812
+ logical_agent_calls_reserved=request.wave.call_count,
1813
+ dispatched_to_runtime=dispatched,
1814
+ cancellation_reason=reason,
1815
+ )
1816
+ reflection_cancellations.append(receipt)
1817
+ if dispatched:
1818
+ update_call_counters(cancelled_after_dispatch=request.wave.call_count)
1819
+ else:
1820
+ update_call_counters(cancelled_before_dispatch=request.wave.call_count)
1821
+ await append_event(
1822
+ CampaignExecutionEventKind.REFLECTION_CANCELLED,
1823
+ {"reflection_cancellation_receipt": receipt.to_record()},
1824
+ )
1825
+ return receipt
1826
+
1827
+ async def settle_sources(
1828
+ sources: tuple[int, ...],
1829
+ *,
1830
+ wait_for_all: bool,
1831
+ ) -> tuple[CampaignReflectionReceipt, ...]:
1832
+ pending_sources = tuple(
1833
+ source
1834
+ for source in sources
1835
+ if source not in reflection_receipts
1836
+ and source in reflection_tasks
1837
+ and (wait_for_all or reflection_tasks[source].done())
1838
+ )
1839
+ if pending_sources:
1840
+ raw = await asyncio.gather(
1841
+ *(reflection_tasks[source] for source in pending_sources),
1842
+ return_exceptions=True,
1843
+ )
1844
+ for source, value in zip(pending_sources, raw, strict=True):
1845
+ if isinstance(value, asyncio.CancelledError):
1846
+ await record_reflection_cancellation(
1847
+ source,
1848
+ reason="scheduler_task_cancelled",
1849
+ )
1850
+ reflection_tasks.pop(source, None)
1851
+ elif isinstance(value, BaseException):
1852
+ await settle_reflection(
1853
+ source,
1854
+ failed_reflection_receipt(source, value),
1855
+ )
1856
+ else:
1857
+ await settle_reflection(source, value)
1858
+ return tuple(
1859
+ reflection_receipts[source]
1860
+ for source in sources
1861
+ if source in reflection_receipts
1862
+ )
1863
+
1864
+ def failed_sources(sources: tuple[int, ...]) -> tuple[int, ...]:
1865
+ return tuple(
1866
+ source
1867
+ for source in sources
1868
+ if source in reflection_receipts
1869
+ and reflection_receipts[source].status
1870
+ is CampaignReflectionStatus.FAILED
1871
+ )
1872
+
1873
+ def abstained_sources(sources: tuple[int, ...]) -> tuple[int, ...]:
1874
+ return tuple(
1875
+ source
1876
+ for source in sources
1877
+ if source in reflection_receipts
1878
+ and reflection_receipts[source].status
1879
+ is CampaignReflectionStatus.ABSTAINED
1880
+ )
1881
+
1882
+ async def settle_stage_boundary() -> None:
1883
+ # Give callbacks made runnable by the just-sealed stage one turn to
1884
+ # publish their terminal task state before the next expensive stage.
1885
+ await asyncio.sleep(0)
1886
+ sources = tuple(sorted(reflection_tasks))
1887
+ await settle_sources(sources, wait_for_all=False)
1888
+ failures = failed_sources(tuple(sorted(reflection_receipts)))
1889
+ if not failures:
1890
+ return
1891
+ if supervision.mode is ReflectionFailureMode.FAIL_AT_NEXT_STAGE_BOUNDARY:
1892
+ raise CampaignExecutionContractError(
1893
+ "reflection failed at the next durable stage boundary"
1894
+ )
1895
+ if supervision.mode is ReflectionFailureMode.BEST_EFFORT_DEGRADED:
1896
+ degraded_reflection_sources.update(failures)
1897
+
1898
+ async def join_barrier(barrier: CampaignPromotionBarrier) -> None:
1899
+ nonlocal test_eligible_reflections
1900
+ sources = barrier.reflection_source_generations
1901
+ if any(
1902
+ source not in reflection_tasks and source not in reflection_receipts
1903
+ for source in sources
1904
+ ):
1905
+ raise CampaignExecutionContractError(
1906
+ "test-admission barrier lacks a launched reflection"
1907
+ )
1908
+ await settle_sources(sources, wait_for_all=True)
1909
+ joined = tuple(reflection_receipts[source] for source in sources)
1910
+ failures = failed_sources(sources)
1911
+ if failures:
1912
+ for source in sources:
1913
+ reflection_tasks.pop(source, None)
1914
+ reflection_requests.pop(source, None)
1915
+ if supervision.mode is ReflectionFailureMode.BEST_EFFORT_DEGRADED:
1916
+ degraded_reflection_sources.update(failures)
1917
+ return
1918
+ raise CampaignExecutionContractError(
1919
+ "reflection block closed with one or more failed receipts"
1920
+ )
1921
+ abstentions = abstained_sources(sources)
1922
+ if abstentions:
1923
+ # A block containing E0 evidence has no publishable reflection
1924
+ # content. Preserve every receipt, but do not manufacture a
1925
+ # partial admission whose estimand differs from the prepared
1926
+ # barrier.
1927
+ for source in sources:
1928
+ reflection_tasks.pop(source, None)
1929
+ reflection_requests.pop(source, None)
1930
+ return
1931
+ request = CampaignReflectionTestAdmissionRequest(
1932
+ preparation_sha256=preparation_sha256,
1933
+ runtime_start_receipt_sha256=start.receipt_sha256,
1934
+ barrier=barrier,
1935
+ reflections=joined,
1936
+ previously_test_eligible_reflection_receipt_sha256s=(
1937
+ test_eligible_reflections
1938
+ ),
1939
+ admission_cutoff_stage=(
1940
+ stage_receipts[barrier.generation - 1]
1941
+ if barrier.generation
1942
+ > barrier.reflection_source_generations[-1]
1943
+ else None
1944
+ ),
1945
+ )
1946
+ receipt = await self.reflections.admit_for_testing(request)
1947
+ if type(receipt) is not CampaignReflectionTestAdmissionReceipt:
1948
+ raise TypeError("test admission runtime must return exact receipt")
1949
+ CampaignReflectionTestAdmissionReceipt.__post_init__(receipt)
1950
+ admitted = tuple(sorted(value.receipt_sha256 for value in joined))
1951
+ expected_eligible = tuple(sorted((*test_eligible_reflections, *admitted)))
1952
+ if (
1953
+ receipt.request_sha256 != request.request_sha256
1954
+ or receipt.preparation_sha256 != preparation_sha256
1955
+ or receipt.barrier_generation != barrier.generation
1956
+ or receipt.admitted_reflection_receipt_sha256s != admitted
1957
+ or receipt.test_eligible_reflection_receipt_sha256s != expected_eligible
1958
+ or receipt.lifecycle_promoted
1959
+ ):
1960
+ raise CampaignExecutionContractError(
1961
+ "test-admission receipt differs from its barrier"
1962
+ )
1963
+ test_admission_receipts.append(receipt)
1964
+ test_eligible_reflections = expected_eligible
1965
+ await append_event(
1966
+ CampaignExecutionEventKind.REFLECTION_ADMITTED_FOR_TESTING,
1967
+ {"test_admission_receipt": receipt.to_record()},
1968
+ )
1969
+ for source in sources:
1970
+ reflection_tasks.pop(source, None)
1971
+ reflection_requests.pop(source, None)
1972
+
1973
+ try:
1974
+ if (
1975
+ start.preparation_sha256 != preparation_sha256
1976
+ or start.runtime_preflight_receipt_sha256
1977
+ != self.prepared.runtime_receipt.receipt_sha256
1978
+ or start.seed_batch_sha256 != self.prepared.seeds.batch_sha256
1979
+ ):
1980
+ raise CampaignExecutionContractError(
1981
+ "runtime start receipt differs from preparation"
1982
+ )
1983
+ expected_seeds = tuple(
1984
+ (value.seed_id, value.configuration_sha256)
1985
+ for value in self.prepared.seeds.seeds
1986
+ )
1987
+ observed_seeds = tuple(
1988
+ (value.seed_id, value.configuration_sha256)
1989
+ for value in start.seed_receipts
1990
+ )
1991
+ if observed_seeds != expected_seeds:
1992
+ raise CampaignExecutionContractError(
1993
+ "runtime seed accounting differs from prepared seed batch"
1994
+ )
1995
+ counters = CampaignExecutionCounters(
1996
+ generations_completed=0,
1997
+ candidate_occurrences=start.seed_occurrence_count,
1998
+ unique_evaluations=start.seed_unique_evaluation_count,
1999
+ logical_agent_calls=0,
2000
+ )
2001
+ if (
2002
+ counters.unique_evaluations
2003
+ > self.prepared.budget.max_unique_evaluations
2004
+ ):
2005
+ raise CampaignExecutionContractError(
2006
+ "seed evaluations already exceed campaign budget"
2007
+ )
2008
+ await append_event(
2009
+ CampaignExecutionEventKind.EXECUTION_STARTED,
2010
+ {
2011
+ "start_receipt": start.to_record(),
2012
+ "initial_counters": counters.to_record(),
2013
+ },
2014
+ )
2015
+ if start.seed_failure_count or any(
2016
+ not value.evaluated or not value.valid for value in start.seed_receipts
2017
+ ):
2018
+ raise CampaignExecutionContractError(
2019
+ "runtime start reported failed or unevaluated seeds"
2020
+ )
2021
+ barrier_by_generation = {
2022
+ value.generation: value
2023
+ for value in self.prepared.schedule.promotion_barriers
2024
+ }
2025
+ wave_by_generation = {
2026
+ value.source_generation: value
2027
+ for value in self.prepared.schedule.reflection_waves
2028
+ }
2029
+ prior_stage: CampaignStageReceipt | None = None
2030
+
2031
+ for step in self.prepared.schedule.steps:
2032
+ if step.generation != counters.generations_completed + 1:
2033
+ raise CampaignExecutionContractError(
2034
+ "prepared schedule is not the next generation"
2035
+ )
2036
+ expected_stage_agent_calls = (
2037
+ step.parent_count
2038
+ if step.kind is CampaignGenerationKind.PORTFOLIO
2039
+ else 0
2040
+ )
2041
+ if (
2042
+ counters.logical_agent_calls + expected_stage_agent_calls
2043
+ > self.prepared.budget.max_logical_llm_calls
2044
+ ):
2045
+ raise CampaignExecutionContractError(
2046
+ "selector calls would exceed campaign budget"
2047
+ )
2048
+ cutoff_request = CampaignArchiveCutoffRequest(
2049
+ preparation_sha256=preparation_sha256,
2050
+ runtime_start_receipt_sha256=start.receipt_sha256,
2051
+ generation=step.generation,
2052
+ kind=step.kind,
2053
+ step_sha256=campaign_step_sha256(step),
2054
+ prior_stage_receipt_sha256=(
2055
+ None if prior_stage is None else prior_stage.receipt_sha256
2056
+ ),
2057
+ )
2058
+ cutoff = await self.stages.snapshot_archive(cutoff_request)
2059
+ if type(cutoff) is not CampaignArchiveCutoffReceipt:
2060
+ raise TypeError("stage runtime must return exact archive cutoff")
2061
+ CampaignArchiveCutoffReceipt.__post_init__(cutoff)
2062
+ if (
2063
+ cutoff.request_sha256 != cutoff_request.request_sha256
2064
+ or cutoff.preparation_sha256 != preparation_sha256
2065
+ or cutoff.generation != step.generation
2066
+ ):
2067
+ raise CampaignExecutionContractError(
2068
+ "archive cutoff receipt differs from its request"
2069
+ )
2070
+ utility = freeze_archive_utility(
2071
+ self.policies.archive_utility,
2072
+ benchmark=self.prepared.benchmark_session.benchmark,
2073
+ generation=step.generation,
2074
+ archive=cutoff.archive,
2075
+ )
2076
+ await append_event(
2077
+ CampaignExecutionEventKind.ARCHIVE_UTILITY_FROZEN,
2078
+ {
2079
+ "archive_cutoff": cutoff.to_record(),
2080
+ "archive_utility": utility.to_record(),
2081
+ },
2082
+ )
2083
+
2084
+ source_portfolio: CampaignStageReceipt | None = None
2085
+ if step.kind is CampaignGenerationKind.RECOMBINATION:
2086
+ if (
2087
+ prior_stage is None
2088
+ or prior_stage.kind is not CampaignGenerationKind.PORTFOLIO
2089
+ or prior_stage.generation != step.source_portfolio_generation
2090
+ ):
2091
+ raise CampaignExecutionContractError(
2092
+ "recombination lacks its exact portfolio predecessor"
2093
+ )
2094
+ source_portfolio = prior_stage
2095
+ stage_request = CampaignStageRequest(
2096
+ preparation_sha256=preparation_sha256,
2097
+ runtime_start_receipt_sha256=start.receipt_sha256,
2098
+ step=step,
2099
+ archive_cutoff=cutoff,
2100
+ archive_utility=utility,
2101
+ source_portfolio=source_portfolio,
2102
+ test_eligible_reflection_receipt_sha256s=(
2103
+ test_eligible_reflections
2104
+ ),
2105
+ prior_selector_audit_set_sha256=selector_audit_set_sha256(
2106
+ tuple(selector_audits)
2107
+ ),
2108
+ )
2109
+ stage = await self.stages.execute_stage(stage_request)
2110
+ if type(stage) is not CampaignStageReceipt:
2111
+ raise TypeError("stage runtime must return exact stage receipt")
2112
+ CampaignStageReceipt.__post_init__(stage)
2113
+ if (
2114
+ stage.request_sha256 != stage_request.request_sha256
2115
+ or stage.preparation_sha256 != preparation_sha256
2116
+ or stage.generation != step.generation
2117
+ or stage.kind is not step.kind
2118
+ ):
2119
+ raise CampaignExecutionContractError(
2120
+ "stage receipt is out-of-order or foreign"
2121
+ )
2122
+ maximum_occurrences = step.planned_candidate_evaluations
2123
+ if step.kind is CampaignGenerationKind.PORTFOLIO:
2124
+ if stage.candidate_occurrence_count != maximum_occurrences:
2125
+ raise CampaignExecutionContractError(
2126
+ "portfolio stage did not account for every planned child"
2127
+ )
2128
+ if len(stage.selector_audits) != step.parent_count:
2129
+ raise CampaignExecutionContractError(
2130
+ "portfolio stage lacks one fresh selector audit per parent"
2131
+ )
2132
+ expected_slots = tuple(range(step.parent_count))
2133
+ if (
2134
+ tuple(value.parent_slot for value in stage.selector_audits)
2135
+ != expected_slots
2136
+ ):
2137
+ raise CampaignExecutionContractError(
2138
+ "selector audits do not cover canonical parent slots"
2139
+ )
2140
+ prior_audit_hash = selector_audit_set_sha256(tuple(selector_audits))
2141
+ if any(
2142
+ value.generation != step.generation
2143
+ or value.prior_audit_set_sha256 != prior_audit_hash
2144
+ for value in stage.selector_audits
2145
+ ):
2146
+ raise CampaignExecutionContractError(
2147
+ "selector audit is stale or names another generation"
2148
+ )
2149
+ existing_calls = {
2150
+ value.selector_call_id for value in selector_audits
2151
+ }
2152
+ existing_requests = {
2153
+ value.request_sha256 for value in selector_audits
2154
+ }
2155
+ existing_traces = {
2156
+ value.trace_receipt_sha256 for value in selector_audits
2157
+ }
2158
+ if (
2159
+ len({value.selector_call_id for value in stage.selector_audits})
2160
+ != len(stage.selector_audits)
2161
+ or len(
2162
+ {value.request_sha256 for value in stage.selector_audits}
2163
+ )
2164
+ != len(stage.selector_audits)
2165
+ or len(
2166
+ {
2167
+ value.trace_receipt_sha256
2168
+ for value in stage.selector_audits
2169
+ }
2170
+ )
2171
+ != len(stage.selector_audits)
2172
+ or any(
2173
+ value.selector_call_id in existing_calls
2174
+ or value.request_sha256 in existing_requests
2175
+ or value.trace_receipt_sha256 in existing_traces
2176
+ for value in stage.selector_audits
2177
+ )
2178
+ ):
2179
+ raise CampaignExecutionContractError(
2180
+ "selector audit is duplicate rather than fresh"
2181
+ )
2182
+ selector_audits.extend(stage.selector_audits)
2183
+ else:
2184
+ if stage.candidate_occurrence_count > maximum_occurrences:
2185
+ raise CampaignExecutionContractError(
2186
+ "recombination exceeded its planned child envelope"
2187
+ )
2188
+ if stage.selector_audits:
2189
+ raise CampaignExecutionContractError(
2190
+ "recombination stage cannot publish selector audits"
2191
+ )
2192
+ next_unique = (
2193
+ counters.unique_evaluations + stage.unique_evaluation_count
2194
+ )
2195
+ if next_unique > self.prepared.budget.max_unique_evaluations:
2196
+ raise CampaignExecutionContractError(
2197
+ "stage evaluations exceed campaign budget"
2198
+ )
2199
+ counters = CampaignExecutionCounters(
2200
+ generations_completed=step.generation,
2201
+ candidate_occurrences=(
2202
+ counters.candidate_occurrences
2203
+ + stage.candidate_occurrence_count
2204
+ ),
2205
+ unique_evaluations=next_unique,
2206
+ logical_agent_calls=(
2207
+ counters.logical_agent_calls + expected_stage_agent_calls
2208
+ ),
2209
+ logical_agent_calls_dispatched_to_runtime=(
2210
+ counters.logical_agent_calls_dispatched_to_runtime
2211
+ + expected_stage_agent_calls
2212
+ ),
2213
+ logical_agent_calls_succeeded=(
2214
+ counters.logical_agent_calls_succeeded
2215
+ + expected_stage_agent_calls
2216
+ ),
2217
+ logical_agent_calls_abstained=(
2218
+ counters.logical_agent_calls_abstained
2219
+ ),
2220
+ logical_agent_calls_failed=(counters.logical_agent_calls_failed),
2221
+ logical_agent_calls_cancelled_before_dispatch=(
2222
+ counters.logical_agent_calls_cancelled_before_dispatch
2223
+ ),
2224
+ logical_agent_calls_cancelled_after_dispatch=(
2225
+ counters.logical_agent_calls_cancelled_after_dispatch
2226
+ ),
2227
+ )
2228
+ stage_receipts.append(stage)
2229
+ prior_stage = stage
2230
+ await append_event(
2231
+ CampaignExecutionEventKind.STAGE_SEALED,
2232
+ {
2233
+ "stage_receipt": stage.to_record(),
2234
+ "counters": counters.to_record(),
2235
+ },
2236
+ )
2237
+ await settle_stage_boundary()
2238
+
2239
+ wave = wave_by_generation.get(step.generation)
2240
+ if wave is not None:
2241
+ if step.kind is not CampaignGenerationKind.RECOMBINATION:
2242
+ raise CampaignExecutionContractError(
2243
+ "reflection wave follows a non-recombination stage"
2244
+ )
2245
+ if (
2246
+ counters.logical_agent_calls + wave.call_count
2247
+ > self.prepared.budget.max_logical_llm_calls
2248
+ ):
2249
+ raise CampaignExecutionContractError(
2250
+ "reflection calls would exceed campaign budget"
2251
+ )
2252
+ reflection_request = CampaignReflectionRequest(
2253
+ preparation_sha256=preparation_sha256,
2254
+ runtime_start_receipt_sha256=start.receipt_sha256,
2255
+ wave=wave,
2256
+ source_stage=stage,
2257
+ )
2258
+ await append_event(
2259
+ CampaignExecutionEventKind.REFLECTION_LAUNCHED,
2260
+ {"reflection_request": reflection_request.to_record()},
2261
+ )
2262
+ reflection_requests[step.generation] = reflection_request
2263
+ update_call_counters(reserved=wave.call_count)
2264
+ started = asyncio.Event()
2265
+ reflection_tasks[step.generation] = asyncio.create_task(
2266
+ run_reflection(reflection_request, started)
2267
+ )
2268
+ await started.wait()
2269
+ update_call_counters(dispatched=wave.call_count)
2270
+ await settle_stage_boundary()
2271
+
2272
+ barrier = barrier_by_generation.get(step.generation)
2273
+ if barrier is not None:
2274
+ await join_barrier(barrier)
2275
+
2276
+ if (
2277
+ counters.generations_completed != len(self.prepared.schedule.steps)
2278
+ or counters.generations_completed > self.prepared.budget.max_generations
2279
+ ):
2280
+ raise CampaignExecutionContractError(
2281
+ "execution did not close the exact prepared generation count"
2282
+ )
2283
+
2284
+ if reflection_requests:
2285
+ tail_sources = tuple(sorted(reflection_requests))
2286
+ if any(
2287
+ reflection_requests[source].wave.promotion_barrier_generation
2288
+ is not None
2289
+ for source in tail_sources
2290
+ ):
2291
+ raise CampaignExecutionContractError(
2292
+ "a declared promotion barrier was not joined"
2293
+ )
2294
+ await settle_sources(tail_sources, wait_for_all=True)
2295
+ drained = tuple(reflection_receipts[source] for source in tail_sources)
2296
+ failures = failed_sources(tail_sources)
2297
+ if failures and supervision.mode is not (
2298
+ ReflectionFailureMode.BEST_EFFORT_DEGRADED
2299
+ ):
2300
+ raise CampaignExecutionContractError(
2301
+ "reflection tail closed with one or more failed receipts"
2302
+ )
2303
+ degraded_reflection_sources.update(failures)
2304
+ tail_drain = CampaignTailDrainReceipt(
2305
+ preparation_sha256=preparation_sha256,
2306
+ drained_reflections=tuple(
2307
+ (value.source_generation, value.receipt_sha256)
2308
+ for value in drained
2309
+ ),
2310
+ )
2311
+ await append_event(
2312
+ CampaignExecutionEventKind.REFLECTION_TAIL_DRAINED,
2313
+ {"tail_drain_receipt": tail_drain.to_record()},
2314
+ )
2315
+ reflection_tasks.clear()
2316
+ reflection_requests.clear()
2317
+ except BaseException as exc:
2318
+ primary_error = exc
2319
+ finally:
2320
+ if reflection_tasks:
2321
+ pending_sources = tuple(sorted(reflection_tasks))
2322
+ for task in reflection_tasks.values():
2323
+ if not task.done():
2324
+ task.cancel()
2325
+ raw = await asyncio.gather(
2326
+ *(reflection_tasks[source] for source in pending_sources),
2327
+ return_exceptions=True,
2328
+ )
2329
+ for source, value in zip(pending_sources, raw, strict=True):
2330
+ try:
2331
+ if isinstance(value, asyncio.CancelledError):
2332
+ await record_reflection_cancellation(
2333
+ source,
2334
+ reason="campaign_abort",
2335
+ )
2336
+ elif isinstance(value, BaseException):
2337
+ await settle_reflection(
2338
+ source,
2339
+ failed_reflection_receipt(source, value),
2340
+ )
2341
+ else:
2342
+ await settle_reflection(source, value)
2343
+ except BaseException as settlement_error:
2344
+ if primary_error is None:
2345
+ primary_error = settlement_error
2346
+ reflection_tasks.clear()
2347
+ reflection_requests.clear()
2348
+
2349
+ if primary_error is not None:
2350
+ status = CampaignExecutionStatus.FAILED
2351
+ failure_type = type(primary_error).__name__
2352
+ elif degraded_reflection_sources:
2353
+ status = CampaignExecutionStatus.DEGRADED
2354
+ failure_type = "reflection_failure"
2355
+ try:
2356
+ await append_event(
2357
+ CampaignExecutionEventKind.EXECUTION_DEGRADED,
2358
+ {
2359
+ "approved_summary": (
2360
+ "campaign completed with quarantined reflection "
2361
+ "failures"
2362
+ ),
2363
+ "failed_reflection_sources": sorted(
2364
+ degraded_reflection_sources
2365
+ ),
2366
+ "counters": counters.to_record(),
2367
+ },
2368
+ )
2369
+ except BaseException as degraded_event_error:
2370
+ primary_error = degraded_event_error
2371
+ status = CampaignExecutionStatus.FAILED
2372
+ failure_type = type(degraded_event_error).__name__
2373
+ else:
2374
+ status = CampaignExecutionStatus.COMPLETED
2375
+ failure_type = None
2376
+ if primary_error is not None:
2377
+ failure_digest = hashlib.sha256(
2378
+ type(primary_error).__qualname__.encode("utf-8", errors="strict")
2379
+ + b"\x00"
2380
+ + str(primary_error).encode("utf-8", errors="replace")
2381
+ ).hexdigest()
2382
+ try:
2383
+ await append_event(
2384
+ CampaignExecutionEventKind.EXECUTION_FAILED,
2385
+ {
2386
+ "failure_type": failure_type,
2387
+ "failure_digest_sha256": failure_digest,
2388
+ "approved_summary": "campaign execution failed",
2389
+ "counters": counters.to_record(),
2390
+ },
2391
+ )
2392
+ except BaseException:
2393
+ pass
2394
+
2395
+ finalize_request = CampaignFinalizationRequest(
2396
+ preparation_sha256=preparation_sha256,
2397
+ runtime_start_receipt_sha256=start.receipt_sha256,
2398
+ status=status,
2399
+ counters=counters,
2400
+ stage_receipt_sha256s=tuple(
2401
+ value.receipt_sha256 for value in stage_receipts
2402
+ ),
2403
+ reflection_receipt_sha256s=tuple(
2404
+ reflection_receipts[source].receipt_sha256
2405
+ for source in sorted(reflection_receipts)
2406
+ ),
2407
+ reflection_cancellation_receipt_sha256s=tuple(
2408
+ value.receipt_sha256 for value in reflection_cancellations
2409
+ ),
2410
+ test_admission_receipt_sha256s=tuple(
2411
+ value.receipt_sha256 for value in test_admission_receipts
2412
+ ),
2413
+ tail_drain_receipt_sha256=(
2414
+ None if tail_drain is None else tail_drain.receipt_sha256
2415
+ ),
2416
+ last_durable_event_sha256=last_event_sha256,
2417
+ failure_type=failure_type,
2418
+ )
2419
+ try:
2420
+ candidate_finalization = await self.lifecycle.finalize(finalize_request)
2421
+ if type(candidate_finalization) is not CampaignFinalizationReceipt:
2422
+ raise TypeError(
2423
+ "lifecycle finalize must return CampaignFinalizationReceipt"
2424
+ )
2425
+ CampaignFinalizationReceipt.__post_init__(candidate_finalization)
2426
+ if (
2427
+ candidate_finalization.request_sha256
2428
+ != finalize_request.request_sha256
2429
+ or candidate_finalization.preparation_sha256 != preparation_sha256
2430
+ or candidate_finalization.status is not status
2431
+ ):
2432
+ raise CampaignExecutionContractError(
2433
+ "finalization receipt differs from request"
2434
+ )
2435
+ finalization = candidate_finalization
2436
+ await append_event(
2437
+ CampaignExecutionEventKind.EXECUTION_FINALIZED,
2438
+ {"finalization_receipt": finalization.to_record()},
2439
+ )
2440
+ except BaseException as exc:
2441
+ if primary_error is None:
2442
+ primary_error = exc
2443
+ status = CampaignExecutionStatus.FAILED
2444
+ failure_type = type(exc).__name__
2445
+
2446
+ cleanup_request = CampaignCleanupRequest(
2447
+ preparation_sha256=preparation_sha256,
2448
+ runtime_start_receipt_sha256=start.receipt_sha256,
2449
+ status=status,
2450
+ finalization_receipt_sha256=(
2451
+ None if finalization is None else finalization.receipt_sha256
2452
+ ),
2453
+ failure_type=failure_type,
2454
+ )
2455
+ try:
2456
+ cleanup = await self.lifecycle.cleanup(cleanup_request)
2457
+ if type(cleanup) is not CampaignCleanupReceipt:
2458
+ raise TypeError(
2459
+ "lifecycle cleanup must return CampaignCleanupReceipt"
2460
+ )
2461
+ CampaignCleanupReceipt.__post_init__(cleanup)
2462
+ if (
2463
+ cleanup.request_sha256 != cleanup_request.request_sha256
2464
+ or cleanup.preparation_sha256 != preparation_sha256
2465
+ or not cleanup.released
2466
+ ):
2467
+ raise CampaignExecutionContractError(
2468
+ "cleanup receipt differs from request or retained resources"
2469
+ )
2470
+ await append_event(
2471
+ CampaignExecutionEventKind.RUNTIME_CLEANED,
2472
+ {"cleanup_receipt": cleanup.to_record()},
2473
+ )
2474
+ except BaseException as exc:
2475
+ if primary_error is None:
2476
+ primary_error = exc
2477
+
2478
+ if primary_error is not None:
2479
+ raise primary_error
2480
+ if finalization is None or cleanup is None: # pragma: no cover - guarded above.
2481
+ raise AssertionError("successful execution lost lifecycle receipts")
2482
+ result = CampaignExecutionResult(
2483
+ preparation_sha256=preparation_sha256,
2484
+ runtime_start_receipt_sha256=start.receipt_sha256,
2485
+ stage_receipts=tuple(stage_receipts),
2486
+ reflection_receipts=tuple(
2487
+ reflection_receipts[source] for source in sorted(reflection_receipts)
2488
+ ),
2489
+ reflection_cancellation_receipts=tuple(reflection_cancellations),
2490
+ test_admission_receipts=tuple(test_admission_receipts),
2491
+ tail_drain_receipt=tail_drain,
2492
+ counters=counters,
2493
+ finalization_receipt=finalization,
2494
+ cleanup_receipt=cleanup,
2495
+ durable_event_sha256s=tuple(durable_event_sha256s),
2496
+ )
2497
+ CampaignExecutionResult.__post_init__(result)
2498
+ return result
2499
+
2500
+
2501
+ __all__ = [
2502
+ "CampaignArchiveCutoffReceipt",
2503
+ "CampaignArchiveCutoffRequest",
2504
+ "CampaignCleanupReceipt",
2505
+ "CampaignCleanupRequest",
2506
+ "CampaignExecutionContractError",
2507
+ "CampaignExecutionCounters",
2508
+ "CampaignExecutionEvent",
2509
+ "CampaignExecutionEventKind",
2510
+ "CampaignExecutionJournalPort",
2511
+ "CampaignExecutionLifecyclePort",
2512
+ "CampaignExecutionResult",
2513
+ "CampaignExecutionStartReceipt",
2514
+ "CampaignExecutionStatus",
2515
+ "CampaignFinalizationReceipt",
2516
+ "CampaignFinalizationRequest",
2517
+ "CampaignJournalAck",
2518
+ "CampaignReflectionReceipt",
2519
+ "CampaignReflectionCancellationReceipt",
2520
+ "CampaignReflectionRequest",
2521
+ "CampaignReflectionRuntimePort",
2522
+ "CampaignReflectionStatus",
2523
+ "CampaignReflectionTestAdmissionReceipt",
2524
+ "CampaignReflectionTestAdmissionRequest",
2525
+ "CampaignSeedExecutionReceipt",
2526
+ "CampaignSelectorAuditReceipt",
2527
+ "CampaignStageReceipt",
2528
+ "CampaignStageRequest",
2529
+ "CampaignStageRuntimePort",
2530
+ "CampaignTailDrainReceipt",
2531
+ "EvolutionCampaignScheduler",
2532
+ "SelectorAuditExecutionMode",
2533
+ "campaign_step_sha256",
2534
+ "decode_selector_audit_text",
2535
+ "encode_selector_audit_text",
2536
+ "selector_audit_set_sha256",
2537
+ ]