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,3169 @@
1
+ """Framework-neutral contracts for one-call ranked action portfolios.
2
+
3
+ The selector sees immutable prompt views of versioned insight cards and one
4
+ parent-bound finite variation contract. It returns an ordered portfolio of
5
+ opaque option IDs; trusted application code resolves every ID back to the
6
+ sealed option before a decision can be published. The port deliberately does
7
+ not extend :class:`AgenticGenerator`: portfolio selection is an independently
8
+ replaceable policy, not another candidate-authoring mode.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import hashlib
14
+ import json
15
+ import math
16
+ import re
17
+ from collections import OrderedDict
18
+ from dataclasses import dataclass, field
19
+ from enum import Enum
20
+ from itertools import combinations, product
21
+ from threading import RLock
22
+ from typing import Protocol, runtime_checkable
23
+
24
+ from agent_evolve.domain.finite_variation import (
25
+ FiniteActionEvidenceBinding,
26
+ FiniteVariationContract,
27
+ ValidatedFiniteVariationIdentityIndex,
28
+ validated_finite_variation_identity_index,
29
+ )
30
+ from agent_evolve.domain.ids import CandidateId, LLMCallId
31
+ from agent_evolve.domain.insight import InsightRef
32
+ from agent_evolve.domain.patch import JsonPath, require_sha256
33
+ from agent_evolve.domain.typed_json import (
34
+ FrozenJsonArray,
35
+ FrozenJsonObject,
36
+ FrozenJsonValue,
37
+ freeze_json,
38
+ thaw_json,
39
+ typed_json_sha256,
40
+ )
41
+ from agent_evolve.ports.agentic_generator import (
42
+ AgenticCallTelemetry,
43
+ MetricEffectPrediction,
44
+ )
45
+ from agent_evolve.ports.portfolio_memory_dose import (
46
+ BoundedPortfolioMemoryDoseContract,
47
+ PortfolioMemoryDoseAssessment,
48
+ PortfolioMemoryDoseMember,
49
+ PortfolioMemoryDoseStage,
50
+ )
51
+ from agent_evolve.ports.structured_generator import MAX_OUTPUT_TOKENS
52
+ from agent_evolve.policies.variation.typed_patch import derive_patch
53
+
54
+
55
+ _TOKEN = re.compile(r"^[a-z][a-z0-9_.-]{0,95}$")
56
+ _OPTION_ID = re.compile(r"^[a-z][a-z0-9_.-]{0,255}$")
57
+ _METRIC_ID = re.compile(r"^[a-z][a-z0-9_.:-]{0,191}$")
58
+ _REQUEST_DOMAIN = b"agent-evolve:portfolio-selection-request:v1\x00"
59
+ _CARD_SNAPSHOT_DOMAIN = b"agent-evolve:portfolio-card-snapshot:v1\x00"
60
+ _DECISION_DOMAIN = b"agent-evolve:ranked-portfolio-decision:v1\x00"
61
+ _SUPPLEMENTAL_AUDIT_DOMAIN = (
62
+ b"agent-evolve:portfolio-selection-supplemental-audit:v1\x00"
63
+ )
64
+ _TRANSFER_REQUEST_DOMAIN = b"agent-evolve:card-transfer-request:v1\x00"
65
+ _TRANSFER_RECEIPT_DOMAIN = b"agent-evolve:card-transfer-score-receipt:v1\x00"
66
+ _CARD_SCORE_STATE_DOMAIN = b"agent-evolve:portfolio-card-score-state:v1\x00"
67
+ _CARD_ACTION_EVIDENCE_STATE_DOMAIN = (
68
+ b"agent-evolve:portfolio-card-action-evidence-state:v1\x00"
69
+ )
70
+ _CARD_SOURCE_BINDING_DOMAIN = b"agent-evolve:portfolio-card-source-binding:v1\x00"
71
+ _CARD_DERIVED_VIEW_DOMAIN = b"agent-evolve:portfolio-card-derived-view:v1\x00"
72
+ _CARD_SOURCE_REGISTRY_DOMAIN = b"agent-evolve:portfolio-card-source-registry:v1\x00"
73
+ _EXPERIMENTAL_VIEW_RECEIPT_DOMAIN = (
74
+ b"agent-evolve:portfolio-experimental-view-receipt:v1\x00"
75
+ )
76
+ _REDACTED_EVIDENCE_DOMAIN = b"agent-evolve:portfolio-card-redacted-evidence:v1\x00"
77
+ _PARENT_PATCH_CERTIFICATE_DOMAIN = (
78
+ b"agent-evolve:parent-patch-feasibility-certificate:v1\x00"
79
+ )
80
+ _PARENT_PATCH_WITNESS_ORDER_DOMAIN = (
81
+ b"agent-evolve:parent-patch-feasibility-witness-order:v1\x00"
82
+ )
83
+ _MAX_PARENT_PATCH_CERTIFICATES = 64
84
+ _MAX_PARENT_PATCH_CACHED_PATHS = 65_536
85
+ _MAX_PARENT_PATCH_FEASIBILITY_RESULTS = 512
86
+
87
+
88
+ # Scientific neutral controls use one public, immutable representation. An
89
+ # empty typed object contains no benchmark vocabulary and therefore cannot
90
+ # accidentally reveal an option through a nominally "neutral" sham card.
91
+ CANONICAL_NEUTRAL_PORTFOLIO_PROMPT_PAYLOAD = FrozenJsonObject(())
92
+ CANONICAL_REDACTED_PORTFOLIO_EVIDENCE_SHA256 = hashlib.sha256(
93
+ _REDACTED_EVIDENCE_DOMAIN
94
+ ).hexdigest()
95
+
96
+
97
+ def _canonical_json(value: object) -> bytes:
98
+ return json.dumps(
99
+ value,
100
+ allow_nan=False,
101
+ ensure_ascii=True,
102
+ separators=(",", ":"),
103
+ sort_keys=True,
104
+ ).encode("ascii")
105
+
106
+
107
+ def _hash(domain: bytes, value: object) -> str:
108
+ return hashlib.sha256(domain + _canonical_json(value)).hexdigest()
109
+
110
+
111
+ def _reference_record(reference: InsightRef) -> dict[str, object]:
112
+ if type(reference) is not InsightRef:
113
+ raise TypeError("reference must be an exact InsightRef")
114
+ InsightRef.__post_init__(reference)
115
+ return {
116
+ "insight_id": reference.insight_id.value,
117
+ "version": reference.version,
118
+ }
119
+
120
+
121
+ def _canonical_metric_ids(values: tuple[str, ...]) -> None:
122
+ if type(values) is not tuple or any(
123
+ type(value) is not str or _METRIC_ID.fullmatch(value) is None
124
+ for value in values
125
+ ):
126
+ raise TypeError(
127
+ "required_metric_ids must be an exact tuple of metric identifiers"
128
+ )
129
+ if not values:
130
+ raise ValueError("required_metric_ids must be non-empty")
131
+ if values != tuple(sorted(set(values))):
132
+ raise ValueError("required_metric_ids must be unique and canonical")
133
+
134
+
135
+ @dataclass(frozen=True, slots=True)
136
+ class _ParentPatchFeasibilityCertificate:
137
+ """One content-bound derivation of all parent-relative option paths."""
138
+
139
+ contract_identity_sha256: str
140
+ option_ids: tuple[str, ...]
141
+ option_identity_sha256s: tuple[str, ...]
142
+ option_families: tuple[str, ...]
143
+ paths_by_option: tuple[tuple[JsonPath, ...], ...]
144
+ certificate_sha256: str = field(init=False)
145
+
146
+ def __post_init__(self) -> None:
147
+ require_sha256(self.contract_identity_sha256, "contract_identity_sha256")
148
+ count = len(self.option_ids)
149
+ if (
150
+ count == 0
151
+ or type(self.option_ids) is not tuple
152
+ or any(
153
+ type(value) is not str or _OPTION_ID.fullmatch(value) is None
154
+ for value in self.option_ids
155
+ )
156
+ or len(set(self.option_ids)) != count
157
+ ):
158
+ raise ValueError("certificate option IDs must be distinct finite IDs")
159
+ if (
160
+ type(self.option_identity_sha256s) is not tuple
161
+ or len(self.option_identity_sha256s) != count
162
+ ):
163
+ raise ValueError("certificate identities must cover every option")
164
+ for value in self.option_identity_sha256s:
165
+ require_sha256(value, "certificate option identity")
166
+ if (
167
+ type(self.option_families) is not tuple
168
+ or len(self.option_families) != count
169
+ or any(
170
+ type(value) is not str or _TOKEN.fullmatch(value) is None
171
+ for value in self.option_families
172
+ )
173
+ ):
174
+ raise ValueError("certificate families must cover every option")
175
+ if (
176
+ type(self.paths_by_option) is not tuple
177
+ or len(self.paths_by_option) != count
178
+ or any(
179
+ type(paths) is not tuple
180
+ or not paths
181
+ or any(type(path) is not JsonPath for path in paths)
182
+ for paths in self.paths_by_option
183
+ )
184
+ ):
185
+ raise ValueError("certificate paths must cover every option")
186
+ object.__setattr__(
187
+ self,
188
+ "certificate_sha256",
189
+ _hash(
190
+ _PARENT_PATCH_CERTIFICATE_DOMAIN,
191
+ {
192
+ "contract_identity_sha256": self.contract_identity_sha256,
193
+ "options": [
194
+ {
195
+ "option_id": option_id,
196
+ "option_identity_sha256": option_identity_sha256,
197
+ "family": family,
198
+ "path_schema_sha256s": [
199
+ path.schema_identity for path in paths
200
+ ],
201
+ }
202
+ for option_id, option_identity_sha256, family, paths in zip(
203
+ self.option_ids,
204
+ self.option_identity_sha256s,
205
+ self.option_families,
206
+ self.paths_by_option,
207
+ strict=True,
208
+ )
209
+ ],
210
+ },
211
+ ),
212
+ )
213
+
214
+
215
+ _PARENT_PATCH_CACHE_LOCK = RLock()
216
+ _PARENT_PATCH_CERTIFICATES: OrderedDict[str, _ParentPatchFeasibilityCertificate] = (
217
+ OrderedDict()
218
+ )
219
+ _PARENT_PATCH_CACHED_PATH_COUNT = 0
220
+ _PARENT_PATCH_FEASIBILITY_RESULTS: OrderedDict[
221
+ tuple[str, tuple[str, ...], int, int | None], bool
222
+ ] = OrderedDict()
223
+
224
+
225
+ def _parent_patch_certificate(
226
+ contract: FiniteVariationContract,
227
+ ) -> _ParentPatchFeasibilityCertificate:
228
+ """Return a bounded-cache certificate after exact content validation."""
229
+
230
+ index = validated_finite_variation_identity_index(contract)
231
+ if type(index) is not ValidatedFiniteVariationIdentityIndex:
232
+ raise TypeError("identity_index must be exact or None")
233
+ index.__post_init__()
234
+ key = index.contract_identity_sha256
235
+ with _PARENT_PATCH_CACHE_LOCK:
236
+ cached = _PARENT_PATCH_CERTIFICATES.get(key)
237
+ if cached is not None:
238
+ if (
239
+ cached.option_ids != index.option_ids
240
+ or cached.option_identity_sha256s != index.option_identity_sha256s
241
+ ):
242
+ raise RuntimeError("parent-patch certificate identity collision")
243
+ _PARENT_PATCH_CERTIFICATES.move_to_end(key)
244
+ return cached
245
+
246
+ base_id = CandidateId("candidate_portfolio_disjoint_parent")
247
+ target_id = CandidateId("candidate_portfolio_disjoint_child")
248
+ paths: list[tuple[JsonPath, ...]] = []
249
+ for option in contract.options:
250
+ patch = derive_patch(
251
+ contract.parent_configuration,
252
+ option.child_configuration,
253
+ base_candidate_id=base_id,
254
+ target_candidate_id=target_id,
255
+ )
256
+ if not patch.operations:
257
+ raise ValueError("finite option has no parent-relative patch")
258
+ paths.append(tuple(operation.path for operation in patch.operations))
259
+ certificate = _ParentPatchFeasibilityCertificate(
260
+ contract_identity_sha256=key,
261
+ option_ids=index.option_ids,
262
+ option_identity_sha256s=index.option_identity_sha256s,
263
+ option_families=tuple(option.family for option in contract.options),
264
+ paths_by_option=tuple(paths),
265
+ )
266
+ certificate_path_count = sum(len(value) for value in certificate.paths_by_option)
267
+ with _PARENT_PATCH_CACHE_LOCK:
268
+ global _PARENT_PATCH_CACHED_PATH_COUNT
269
+ prior = _PARENT_PATCH_CERTIFICATES.get(key)
270
+ if prior is not None:
271
+ if prior != certificate:
272
+ raise RuntimeError("parent-patch certificate identity collision")
273
+ _PARENT_PATCH_CERTIFICATES.move_to_end(key)
274
+ return prior
275
+ if certificate_path_count > _MAX_PARENT_PATCH_CACHED_PATHS:
276
+ return certificate
277
+ while _PARENT_PATCH_CERTIFICATES and (
278
+ len(_PARENT_PATCH_CERTIFICATES) >= _MAX_PARENT_PATCH_CERTIFICATES
279
+ or _PARENT_PATCH_CACHED_PATH_COUNT + certificate_path_count
280
+ > _MAX_PARENT_PATCH_CACHED_PATHS
281
+ ):
282
+ _, evicted = _PARENT_PATCH_CERTIFICATES.popitem(last=False)
283
+ _PARENT_PATCH_CACHED_PATH_COUNT -= sum(
284
+ len(value) for value in evicted.paths_by_option
285
+ )
286
+ _PARENT_PATCH_CERTIFICATES[key] = certificate
287
+ _PARENT_PATCH_CACHED_PATH_COUNT += certificate_path_count
288
+ return certificate
289
+
290
+
291
+ def _parent_patch_paths_by_option(
292
+ contract: FiniteVariationContract,
293
+ option_ids: tuple[str, ...],
294
+ ) -> dict[str, tuple[JsonPath, ...]]:
295
+ """Resolve paths from one content-bound contract certificate."""
296
+
297
+ if type(option_ids) is not tuple or any(
298
+ type(value) is not str or _OPTION_ID.fullmatch(value) is None
299
+ for value in option_ids
300
+ ):
301
+ raise TypeError("option_ids must be an exact tuple of finite option IDs")
302
+ if len(set(option_ids)) != len(option_ids):
303
+ raise ValueError("option_ids cannot repeat")
304
+ certificate = _parent_patch_certificate(contract)
305
+ all_paths = dict(
306
+ zip(
307
+ certificate.option_ids,
308
+ certificate.paths_by_option,
309
+ strict=True,
310
+ )
311
+ )
312
+ if any(option_id not in all_paths for option_id in option_ids):
313
+ raise ValueError("option_id is outside the sealed finite contract")
314
+ return {option_id: all_paths[option_id] for option_id in option_ids}
315
+
316
+
317
+ def _path_sets_are_disjoint(
318
+ left: tuple[JsonPath, ...],
319
+ right: tuple[JsonPath, ...],
320
+ ) -> bool:
321
+ return not any(
322
+ left_path.is_prefix_of(right_path) or right_path.is_prefix_of(left_path)
323
+ for left_path in left
324
+ for right_path in right
325
+ )
326
+
327
+
328
+ def pairwise_disjoint_parent_patch_pairs(
329
+ contract: FiniteVariationContract,
330
+ option_ids: tuple[str, ...],
331
+ ) -> tuple[tuple[str, str], ...]:
332
+ """Return every canonical option pair with non-overlapping parent patches."""
333
+
334
+ paths = _parent_patch_paths_by_option(contract, option_ids)
335
+ ordered = tuple(sorted(option_ids))
336
+ return tuple(
337
+ (left, right)
338
+ for left_index, left in enumerate(ordered)
339
+ for right in ordered[left_index + 1 :]
340
+ if _path_sets_are_disjoint(paths[left], paths[right])
341
+ )
342
+
343
+
344
+ def single_path_parent_patch_option_ids(
345
+ contract: FiniteVariationContract,
346
+ option_ids: tuple[str, ...],
347
+ ) -> tuple[str, ...]:
348
+ """Return canonical options that change exactly one parent-relative path.
349
+
350
+ This is structural evidence derived from the sealed finite variation
351
+ contract. It carries no objective, workload, model, or provider
352
+ semantics and is therefore safe for generic trusted allocation code.
353
+ """
354
+
355
+ paths = _parent_patch_paths_by_option(contract, option_ids)
356
+ return tuple(
357
+ option_id for option_id in sorted(option_ids) if len(paths[option_id]) == 1
358
+ )
359
+
360
+
361
+ def _validate_family_exposure_bounds(
362
+ values: tuple[tuple[str, int, int], ...],
363
+ *,
364
+ portfolio_size: int,
365
+ ) -> None:
366
+ if type(values) is not tuple:
367
+ raise TypeError("family_exposure_bounds must be an exact tuple")
368
+ if len(values) > portfolio_size:
369
+ raise ValueError("family exposure bound count exceeds the portfolio size")
370
+ families: list[str] = []
371
+ minimum_total = 0
372
+ for value in values:
373
+ if type(value) is not tuple or len(value) != 3:
374
+ raise TypeError("family exposure bounds must be exact three-tuples")
375
+ family, minimum, maximum = value
376
+ if type(family) is not str or not family:
377
+ raise ValueError("family exposure family must be a non-empty string")
378
+ if type(minimum) is not int or not 0 <= minimum <= portfolio_size:
379
+ raise ValueError("family exposure minimum is outside the portfolio")
380
+ if type(maximum) is not int or not 0 <= maximum <= portfolio_size:
381
+ raise ValueError("family exposure maximum is outside the portfolio")
382
+ if minimum > maximum:
383
+ raise ValueError("family exposure minimum cannot exceed its maximum")
384
+ families.append(family)
385
+ minimum_total += minimum
386
+ if tuple(families) != tuple(sorted(set(families))):
387
+ raise ValueError("family exposure bounds must be unique and canonical")
388
+ if minimum_total > portfolio_size:
389
+ raise ValueError("family exposure minima exceed the portfolio size")
390
+
391
+
392
+ def _distinct_family_minimum_remains_reachable(
393
+ *,
394
+ chosen: tuple[str, ...],
395
+ remaining: tuple[str, ...],
396
+ portfolio_size: int,
397
+ min_distinct_families: int | None,
398
+ family_exposure_bounds: tuple[tuple[str, int, int], ...],
399
+ family_by_id: dict[str, str],
400
+ ) -> bool:
401
+ """Return a cheap necessary condition for the distinct-family target.
402
+
403
+ A family minimum greater than one consumes duplicate-family slots. The
404
+ former feasibility search ignored that interaction and could enumerate a
405
+ large fraction of the K-subsets before proving, for example, that exact
406
+ two members from one family is incompatible with four distinct families
407
+ in a K=4 portfolio. This bound accounts for mandatory remaining slots and
408
+ then gives every free slot the optimistic benefit of introducing a new
409
+ family. It is therefore safe pruning: ``False`` proves infeasibility,
410
+ while ``True`` leaves the exact patch-disjoint search unchanged.
411
+ """
412
+
413
+ if min_distinct_families is None:
414
+ return True
415
+ needed = portfolio_size - len(chosen)
416
+ chosen_counts: dict[str, int] = {}
417
+ for option_id in chosen:
418
+ family = family_by_id[option_id]
419
+ chosen_counts[family] = chosen_counts.get(family, 0) + 1
420
+ remaining_counts: dict[str, int] = {}
421
+ for option_id in remaining:
422
+ family = family_by_id[option_id]
423
+ remaining_counts[family] = remaining_counts.get(family, 0) + 1
424
+
425
+ maximum_by_family = {
426
+ family: maximum for family, _, maximum in family_exposure_bounds
427
+ }
428
+ mandatory_slots = 0
429
+ mandatory_new_families: set[str] = set()
430
+ for family, minimum, _ in family_exposure_bounds:
431
+ outstanding = max(minimum - chosen_counts.get(family, 0), 0)
432
+ if outstanding > remaining_counts.get(family, 0):
433
+ return False
434
+ mandatory_slots += outstanding
435
+ if outstanding and family not in chosen_counts:
436
+ mandatory_new_families.add(family)
437
+ if mandatory_slots > needed:
438
+ return False
439
+
440
+ optional_new_families = {
441
+ family
442
+ for family in remaining_counts
443
+ if family not in chosen_counts
444
+ and family not in mandatory_new_families
445
+ and maximum_by_family.get(family, portfolio_size) > 0
446
+ }
447
+ optimistic_distinct = (
448
+ len(chosen_counts)
449
+ + len(mandatory_new_families)
450
+ + min(needed - mandatory_slots, len(optional_new_families))
451
+ )
452
+ return optimistic_distinct >= min_distinct_families
453
+
454
+
455
+ def finite_option_ids_have_pairwise_disjoint_parent_patch_subset(
456
+ contract: FiniteVariationContract,
457
+ option_ids: tuple[str, ...],
458
+ *,
459
+ portfolio_size: int,
460
+ min_distinct_families: int | None = None,
461
+ family_exposure_bounds: tuple[tuple[str, int, int], ...] = (),
462
+ ) -> bool:
463
+ """Decide exact K-subset feasibility with branch pruning and early exit.
464
+
465
+ Exact K-clique feasibility is exponential in the worst case. The public
466
+ finite contract is bounded to 1,024 options and portfolio sizes are small;
467
+ this search stops on its first witness and prunes every undersized suffix.
468
+ """
469
+
470
+ if type(portfolio_size) is not int or portfolio_size <= 0:
471
+ raise ValueError("portfolio_size must be a positive exact integer")
472
+ if min_distinct_families is not None and (
473
+ type(min_distinct_families) is not int
474
+ or not 1 <= min_distinct_families <= portfolio_size
475
+ ):
476
+ raise ValueError(
477
+ "min_distinct_families must lie within the requested portfolio"
478
+ )
479
+ _validate_family_exposure_bounds(
480
+ family_exposure_bounds,
481
+ portfolio_size=portfolio_size,
482
+ )
483
+ if type(option_ids) is not tuple or any(
484
+ type(value) is not str or _OPTION_ID.fullmatch(value) is None
485
+ for value in option_ids
486
+ ):
487
+ raise TypeError("option_ids must be an exact tuple of finite option IDs")
488
+ if len(set(option_ids)) != len(option_ids):
489
+ raise ValueError("option_ids cannot repeat")
490
+ if portfolio_size > len(option_ids):
491
+ return False
492
+ certificate = _parent_patch_certificate(contract)
493
+ path_by_id = dict(
494
+ zip(certificate.option_ids, certificate.paths_by_option, strict=True)
495
+ )
496
+ family_by_id = dict(
497
+ zip(certificate.option_ids, certificate.option_families, strict=True)
498
+ )
499
+ if any(option_id not in path_by_id for option_id in option_ids):
500
+ raise ValueError("option_id is outside the sealed finite contract")
501
+ ordered_option_ids = tuple(sorted(option_ids))
502
+ cache_key = (
503
+ certificate.certificate_sha256,
504
+ ordered_option_ids,
505
+ portfolio_size,
506
+ min_distinct_families,
507
+ family_exposure_bounds,
508
+ )
509
+ with _PARENT_PATCH_CACHE_LOCK:
510
+ cached = _PARENT_PATCH_FEASIBILITY_RESULTS.get(cache_key)
511
+ if cached is not None:
512
+ _PARENT_PATCH_FEASIBILITY_RESULTS.move_to_end(cache_key)
513
+ return cached
514
+
515
+ def search(remaining: tuple[str, ...], chosen: tuple[str, ...]) -> bool:
516
+ needed = portfolio_size - len(chosen)
517
+ if needed == 0:
518
+ if (
519
+ min_distinct_families is not None
520
+ and len({family_by_id[value] for value in chosen})
521
+ < min_distinct_families
522
+ ):
523
+ return False
524
+ return all(
525
+ minimum
526
+ <= sum(family_by_id[value] == family for value in chosen)
527
+ <= maximum
528
+ for family, minimum, maximum in family_exposure_bounds
529
+ )
530
+ if len(remaining) < needed:
531
+ return False
532
+ for family, minimum, maximum in family_exposure_bounds:
533
+ chosen_count = sum(family_by_id[value] == family for value in chosen)
534
+ if chosen_count > maximum:
535
+ return False
536
+ remaining_count = sum(
537
+ family_by_id[value] == family for value in remaining
538
+ )
539
+ if chosen_count + min(needed, remaining_count) < minimum:
540
+ return False
541
+ if not _distinct_family_minimum_remains_reachable(
542
+ chosen=chosen,
543
+ remaining=remaining,
544
+ portfolio_size=portfolio_size,
545
+ min_distinct_families=min_distinct_families,
546
+ family_exposure_bounds=family_exposure_bounds,
547
+ family_by_id=family_by_id,
548
+ ):
549
+ return False
550
+ for index, option_id in enumerate(remaining):
551
+ if any(
552
+ family_by_id[option_id] == family
553
+ and sum(family_by_id[value] == family for value in chosen)
554
+ >= maximum
555
+ for family, _, maximum in family_exposure_bounds
556
+ ):
557
+ continue
558
+ if all(
559
+ _path_sets_are_disjoint(path_by_id[option_id], path_by_id[prior])
560
+ for prior in chosen
561
+ ) and search(remaining[index + 1 :], (*chosen, option_id)):
562
+ return True
563
+ return False
564
+
565
+ result = search(ordered_option_ids, ())
566
+ with _PARENT_PATCH_CACHE_LOCK:
567
+ _PARENT_PATCH_FEASIBILITY_RESULTS[cache_key] = result
568
+ _PARENT_PATCH_FEASIBILITY_RESULTS.move_to_end(cache_key)
569
+ while (
570
+ len(_PARENT_PATCH_FEASIBILITY_RESULTS)
571
+ > _MAX_PARENT_PATCH_FEASIBILITY_RESULTS
572
+ ):
573
+ _PARENT_PATCH_FEASIBILITY_RESULTS.popitem(last=False)
574
+ return result
575
+
576
+
577
+ def pairwise_disjoint_parent_patch_witness(
578
+ contract: FiniteVariationContract,
579
+ option_ids: tuple[str, ...],
580
+ *,
581
+ portfolio_size: int,
582
+ min_distinct_families: int | None = None,
583
+ family_exposure_bounds: tuple[tuple[str, int, int], ...] = (),
584
+ ordering_key_sha256: str | None = None,
585
+ preferred_option_ids: tuple[str, ...] = (),
586
+ required_option_ids: tuple[str, ...] = (),
587
+ ) -> tuple[str, ...] | None:
588
+ """Return one deterministic feasibility witness, or ``None`` if absent.
589
+
590
+ The witness is derived only from the sealed parent-relative patches and
591
+ option families. It is therefore safe to expose before evaluation as a
592
+ structural decoding aid: it proves how to satisfy the hard combinatorial
593
+ contract without ranking an option by objective quality. The default
594
+ preserves canonical option-ID order. An optional authenticated SHA-256
595
+ key gives independent requests deterministic, domain-separated orderings
596
+ without consulting outcomes, option prose, or workload semantics. When
597
+ ``required_option_ids`` is a hard membership constraint with no ranking
598
+ semantics. When ``preferred_option_ids`` is non-empty, the witness first
599
+ includes every required option, then maximizes the number of retained
600
+ preferred options, and finally prefers the lexicographically earliest
601
+ preferred-rank combination. Canonical/keyed order breaks ties only among
602
+ nonpreferred completions. This supports an outcome-blind protected-source
603
+ floor alongside minimum-intervention semantic projection.
604
+ """
605
+
606
+ if type(portfolio_size) is not int or portfolio_size <= 0:
607
+ raise ValueError("portfolio_size must be a positive exact integer")
608
+ if min_distinct_families is not None and (
609
+ type(min_distinct_families) is not int
610
+ or not 1 <= min_distinct_families <= portfolio_size
611
+ ):
612
+ raise ValueError(
613
+ "min_distinct_families must lie within the requested portfolio"
614
+ )
615
+ _validate_family_exposure_bounds(
616
+ family_exposure_bounds,
617
+ portfolio_size=portfolio_size,
618
+ )
619
+ if type(option_ids) is not tuple or any(
620
+ type(value) is not str or _OPTION_ID.fullmatch(value) is None
621
+ for value in option_ids
622
+ ):
623
+ raise TypeError("option_ids must be an exact tuple of finite option IDs")
624
+ if len(set(option_ids)) != len(option_ids):
625
+ raise ValueError("option_ids cannot repeat")
626
+ if type(preferred_option_ids) is not tuple or any(
627
+ type(value) is not str or _OPTION_ID.fullmatch(value) is None
628
+ for value in preferred_option_ids
629
+ ):
630
+ raise TypeError(
631
+ "preferred_option_ids must be an exact tuple of finite option IDs"
632
+ )
633
+ if len(set(preferred_option_ids)) != len(preferred_option_ids):
634
+ raise ValueError("preferred_option_ids cannot repeat")
635
+ if not set(preferred_option_ids).issubset(option_ids):
636
+ raise ValueError("preferred_option_ids must be drawn from option_ids")
637
+ if type(required_option_ids) is not tuple or any(
638
+ type(value) is not str or _OPTION_ID.fullmatch(value) is None
639
+ for value in required_option_ids
640
+ ):
641
+ raise TypeError(
642
+ "required_option_ids must be an exact tuple of finite option IDs"
643
+ )
644
+ if required_option_ids != tuple(sorted(set(required_option_ids))):
645
+ raise ValueError("required_option_ids must be unique and canonical")
646
+ if not set(required_option_ids).issubset(option_ids):
647
+ raise ValueError("required_option_ids must be drawn from option_ids")
648
+ if len(required_option_ids) > portfolio_size:
649
+ return None
650
+ if portfolio_size > len(option_ids):
651
+ return None
652
+ if ordering_key_sha256 is not None:
653
+ require_sha256(ordering_key_sha256, "ordering_key_sha256")
654
+
655
+ certificate = _parent_patch_certificate(contract)
656
+ path_by_id = dict(
657
+ zip(certificate.option_ids, certificate.paths_by_option, strict=True)
658
+ )
659
+ family_by_id = dict(
660
+ zip(certificate.option_ids, certificate.option_families, strict=True)
661
+ )
662
+ if any(option_id not in path_by_id for option_id in option_ids):
663
+ raise ValueError("option_id is outside the sealed finite contract")
664
+ if ordering_key_sha256 is None:
665
+ ordered_option_ids = tuple(sorted(option_ids))
666
+ else:
667
+ ordering_key = bytes.fromhex(ordering_key_sha256)
668
+ ordered_option_ids = tuple(
669
+ sorted(
670
+ option_ids,
671
+ key=lambda option_id: (
672
+ hashlib.sha256(
673
+ _PARENT_PATCH_WITNESS_ORDER_DOMAIN
674
+ + ordering_key
675
+ + option_id.encode("ascii", errors="strict")
676
+ ).digest(),
677
+ option_id,
678
+ ),
679
+ )
680
+ )
681
+
682
+ def search(
683
+ remaining: tuple[str, ...],
684
+ chosen: tuple[str, ...],
685
+ ) -> tuple[str, ...] | None:
686
+ needed = portfolio_size - len(chosen)
687
+ if needed == 0:
688
+ if (
689
+ min_distinct_families is not None
690
+ and len({family_by_id[value] for value in chosen})
691
+ < min_distinct_families
692
+ ):
693
+ return None
694
+ if any(
695
+ not minimum
696
+ <= sum(family_by_id[value] == family for value in chosen)
697
+ <= maximum
698
+ for family, minimum, maximum in family_exposure_bounds
699
+ ):
700
+ return None
701
+ return chosen
702
+ if len(remaining) < needed:
703
+ return None
704
+ for family, minimum, maximum in family_exposure_bounds:
705
+ chosen_count = sum(family_by_id[value] == family for value in chosen)
706
+ if chosen_count > maximum:
707
+ return None
708
+ remaining_count = sum(
709
+ family_by_id[value] == family for value in remaining
710
+ )
711
+ if chosen_count + min(needed, remaining_count) < minimum:
712
+ return None
713
+ if not _distinct_family_minimum_remains_reachable(
714
+ chosen=chosen,
715
+ remaining=remaining,
716
+ portfolio_size=portfolio_size,
717
+ min_distinct_families=min_distinct_families,
718
+ family_exposure_bounds=family_exposure_bounds,
719
+ family_by_id=family_by_id,
720
+ ):
721
+ return None
722
+ for index, option_id in enumerate(remaining):
723
+ if any(
724
+ family_by_id[option_id] == family
725
+ and sum(family_by_id[value] == family for value in chosen)
726
+ >= maximum
727
+ for family, _, maximum in family_exposure_bounds
728
+ ):
729
+ continue
730
+ if not all(
731
+ _path_sets_are_disjoint(path_by_id[option_id], path_by_id[prior])
732
+ for prior in chosen
733
+ ):
734
+ continue
735
+ witness = search(remaining[index + 1 :], (*chosen, option_id))
736
+ if witness is not None:
737
+ return witness
738
+ return None
739
+
740
+ required_set = set(required_option_ids)
741
+ if any(
742
+ not _path_sets_are_disjoint(path_by_id[left], path_by_id[right])
743
+ for index, left in enumerate(required_option_ids)
744
+ for right in required_option_ids[index + 1 :]
745
+ ):
746
+ return None
747
+ if any(
748
+ sum(family_by_id[value] == family for value in required_option_ids)
749
+ > maximum
750
+ for family, _, maximum in family_exposure_bounds
751
+ ):
752
+ return None
753
+ completion_option_ids = tuple(
754
+ value for value in ordered_option_ids if value not in required_set
755
+ )
756
+ remaining_preferred = tuple(
757
+ value for value in preferred_option_ids if value not in required_set
758
+ )
759
+ if not remaining_preferred:
760
+ return search(completion_option_ids, required_option_ids)
761
+
762
+ preferred_set = set(remaining_preferred)
763
+ nonpreferred_option_ids = tuple(
764
+ value for value in completion_option_ids if value not in preferred_set
765
+ )
766
+ maximum_retained = min(
767
+ portfolio_size - len(required_option_ids),
768
+ len(remaining_preferred),
769
+ )
770
+ for retained_count in range(maximum_retained, -1, -1):
771
+ for preferred_subset in combinations(
772
+ remaining_preferred,
773
+ retained_count,
774
+ ):
775
+ if any(
776
+ not _path_sets_are_disjoint(
777
+ path_by_id[left],
778
+ path_by_id[right],
779
+ )
780
+ for index, left in enumerate(preferred_subset)
781
+ for right in preferred_subset[index + 1 :]
782
+ ):
783
+ continue
784
+ if any(
785
+ not _path_sets_are_disjoint(
786
+ path_by_id[required],
787
+ path_by_id[preferred],
788
+ )
789
+ for required in required_option_ids
790
+ for preferred in preferred_subset
791
+ ):
792
+ continue
793
+ if any(
794
+ sum(
795
+ family_by_id[value] == family
796
+ for value in (*required_option_ids, *preferred_subset)
797
+ )
798
+ > maximum
799
+ for family, _, maximum in family_exposure_bounds
800
+ ):
801
+ continue
802
+ witness = search(
803
+ nonpreferred_option_ids,
804
+ (*required_option_ids, *preferred_subset),
805
+ )
806
+ if witness is not None:
807
+ return witness
808
+ return None
809
+
810
+
811
+ def project_family_exposure_bounds_to_pairwise_disjoint_feasibility(
812
+ contract: FiniteVariationContract,
813
+ option_ids: tuple[str, ...],
814
+ *,
815
+ portfolio_size: int,
816
+ min_distinct_families: int | None = None,
817
+ requested_bounds: tuple[tuple[str, int, int], ...],
818
+ ) -> tuple[tuple[str, int, int], ...]:
819
+ """Project requested family bounds to the closest structural K-feasibility.
820
+
821
+ The projection is outcome-blind. It first preserves the requested intervals
822
+ exactly when any pairwise-disjoint K-subset satisfies them. Otherwise it
823
+ chooses the feasible exact count vector with minimum L1 interval violation,
824
+ using canonical count order only to break ties.
825
+ """
826
+
827
+ _validate_family_exposure_bounds(
828
+ requested_bounds,
829
+ portfolio_size=portfolio_size,
830
+ )
831
+ if not requested_bounds:
832
+ return ()
833
+ if finite_option_ids_have_pairwise_disjoint_parent_patch_subset(
834
+ contract,
835
+ option_ids,
836
+ portfolio_size=portfolio_size,
837
+ min_distinct_families=min_distinct_families,
838
+ family_exposure_bounds=requested_bounds,
839
+ ):
840
+ return requested_bounds
841
+ count_vectors = tuple(
842
+ sorted(
843
+ product(range(portfolio_size + 1), repeat=len(requested_bounds)),
844
+ key=lambda counts: (
845
+ sum(
846
+ max(minimum - count, 0, count - maximum)
847
+ for (_, minimum, maximum), count in zip(
848
+ requested_bounds,
849
+ counts,
850
+ strict=True,
851
+ )
852
+ ),
853
+ counts,
854
+ ),
855
+ )
856
+ )
857
+ for counts in count_vectors:
858
+ # Action families are mutually exclusive labels on each selected
859
+ # option. Exact counts whose sum exceeds K cannot describe any
860
+ # portfolio and are outside the validated bound domain.
861
+ if sum(counts) > portfolio_size:
862
+ continue
863
+ exact = tuple(
864
+ (family, count, count)
865
+ for (family, _, _), count in zip(
866
+ requested_bounds,
867
+ counts,
868
+ strict=True,
869
+ )
870
+ )
871
+ if not finite_option_ids_have_pairwise_disjoint_parent_patch_subset(
872
+ contract,
873
+ option_ids,
874
+ portfolio_size=portfolio_size,
875
+ min_distinct_families=min_distinct_families,
876
+ family_exposure_bounds=exact,
877
+ ):
878
+ continue
879
+ return exact
880
+ raise ValueError("finite option universe has no feasible evaluation portfolio")
881
+
882
+
883
+ def finite_portfolio_has_pairwise_disjoint_parent_patches(
884
+ contract: FiniteVariationContract,
885
+ *,
886
+ portfolio_size: int,
887
+ min_distinct_families: int | None = None,
888
+ ) -> bool:
889
+ """Decide whether a finite contract contains one disjoint K-portfolio."""
890
+
891
+ return finite_option_ids_have_pairwise_disjoint_parent_patch_subset(
892
+ contract,
893
+ tuple(option.option_id for option in contract.options),
894
+ portfolio_size=portfolio_size,
895
+ min_distinct_families=min_distinct_families,
896
+ )
897
+
898
+
899
+ def validate_pairwise_disjoint_parent_patch_selection(
900
+ contract: FiniteVariationContract,
901
+ option_ids: tuple[str, ...],
902
+ ) -> None:
903
+ """Fail closed unless every selected sealed option has a disjoint patch."""
904
+
905
+ paths = _parent_patch_paths_by_option(contract, option_ids)
906
+ for left_index, left in enumerate(option_ids):
907
+ for right in option_ids[left_index + 1 :]:
908
+ if not _path_sets_are_disjoint(paths[left], paths[right]):
909
+ raise ValueError(
910
+ "selected finite options have overlapping parent-relative patches"
911
+ )
912
+
913
+
914
+ @dataclass(frozen=True, slots=True)
915
+ class CardScoreComponent:
916
+ """One interpretable scalar in a card's selector-visible score bundle.
917
+
918
+ Components retain their scoring definition and evidence lineage so a
919
+ benchmark can rotate, redact, or independently adjudicate complete score
920
+ bundles without collapsing distinct semantics into an opaque global
921
+ score. ``evidence_count`` may exceed the number of receipts because one
922
+ sealed receipt can summarize multiple evidence rows.
923
+ """
924
+
925
+ score_id: str
926
+ value: float
927
+ definition_sha256: str
928
+ evidence_count: int
929
+ receipt_sha256s: tuple[str, ...]
930
+
931
+ def __post_init__(self) -> None:
932
+ if (
933
+ type(self.score_id) is not str
934
+ or _METRIC_ID.fullmatch(self.score_id) is None
935
+ ):
936
+ raise ValueError("score_id must use the closed metric-token grammar")
937
+ if type(self.value) is not float or not math.isfinite(self.value):
938
+ raise TypeError("value must be a finite canonical float")
939
+ require_sha256(self.definition_sha256, "definition_sha256")
940
+ if type(self.evidence_count) is not int or self.evidence_count < 0:
941
+ raise ValueError("evidence_count must be a non-negative exact integer")
942
+ if type(self.receipt_sha256s) is not tuple or any(
943
+ type(value) is not str for value in self.receipt_sha256s
944
+ ):
945
+ raise TypeError("receipt_sha256s must be an exact tuple of strings")
946
+ for value in self.receipt_sha256s:
947
+ require_sha256(value, "receipt_sha256")
948
+ if self.receipt_sha256s != tuple(sorted(set(self.receipt_sha256s))):
949
+ raise ValueError("receipt_sha256s must be unique and canonical")
950
+
951
+ def to_record(self) -> dict[str, object]:
952
+ self.__post_init__()
953
+ return {
954
+ "score_id": self.score_id,
955
+ "value_hex": self.value.hex(),
956
+ "definition_sha256": self.definition_sha256,
957
+ "evidence_count": self.evidence_count,
958
+ "receipt_sha256s": list(self.receipt_sha256s),
959
+ }
960
+
961
+ def prompt_record(self) -> dict[str, object]:
962
+ self.__post_init__()
963
+ return {
964
+ "score_id": self.score_id,
965
+ "value": self.value,
966
+ "definition_sha256": self.definition_sha256,
967
+ "evidence_count": self.evidence_count,
968
+ "receipt_sha256s": list(self.receipt_sha256s),
969
+ }
970
+
971
+
972
+ def _validate_finite_action_evidence(
973
+ values: tuple[FiniteActionEvidenceBinding, ...],
974
+ *,
975
+ name: str,
976
+ ) -> None:
977
+ if type(values) is not tuple or any(
978
+ type(binding) is not FiniteActionEvidenceBinding for binding in values
979
+ ):
980
+ raise TypeError(
981
+ f"{name} must be an exact tuple of FiniteActionEvidenceBinding values"
982
+ )
983
+ for binding in values:
984
+ FiniteActionEvidenceBinding.__post_init__(binding)
985
+ contrast_ids = tuple(binding.contrast_id for binding in values)
986
+ if contrast_ids != tuple(sorted(set(contrast_ids))):
987
+ raise ValueError(f"{name} must have unique canonical contrast order")
988
+
989
+
990
+ @dataclass(frozen=True, slots=True)
991
+ class PortfolioCardPromptPayload:
992
+ """Typed scientific prompt boundary for one portfolio card.
993
+
994
+ Free-form benchmark prose is confined to ``action_neutral_payload``.
995
+ Exact option attribution is carried only by the structured finite-action
996
+ bindings. This type does not itself know the request's complete option
997
+ catalog; :func:`validate_portfolio_experimental_view` performs that
998
+ catalog-relative leak check before a scientific request is admitted.
999
+ """
1000
+
1001
+ action_neutral_payload: FrozenJsonObject
1002
+ finite_action_evidence: tuple[FiniteActionEvidenceBinding, ...]
1003
+
1004
+ def __post_init__(self) -> None:
1005
+ if type(self.action_neutral_payload) is not FrozenJsonObject:
1006
+ raise TypeError("action_neutral_payload must be an exact FrozenJsonObject")
1007
+ if freeze_json(self.action_neutral_payload) is not self.action_neutral_payload:
1008
+ raise TypeError("action_neutral_payload must already be frozen typed JSON")
1009
+ _validate_finite_action_evidence(
1010
+ self.finite_action_evidence,
1011
+ name="finite_action_evidence",
1012
+ )
1013
+
1014
+ @property
1015
+ def action_neutral_payload_sha256(self) -> str:
1016
+ self.__post_init__()
1017
+ return typed_json_sha256(self.action_neutral_payload)
1018
+
1019
+ def prompt_record(self) -> dict[str, object]:
1020
+ """Render action-neutral prose and exact attribution separately."""
1021
+
1022
+ self.__post_init__()
1023
+ return {
1024
+ "action_neutral_payload": thaw_json(self.action_neutral_payload),
1025
+ "finite_action_evidence": [
1026
+ binding.to_record() for binding in self.finite_action_evidence
1027
+ ],
1028
+ }
1029
+
1030
+ def to_record(self) -> dict[str, object]:
1031
+ """Return a canonical evidence record without duplicating neutral prose."""
1032
+
1033
+ self.__post_init__()
1034
+ return {
1035
+ "schema_version": 1,
1036
+ "action_neutral_payload_sha256": self.action_neutral_payload_sha256,
1037
+ "finite_action_evidence": [
1038
+ binding.to_record() for binding in self.finite_action_evidence
1039
+ ],
1040
+ }
1041
+
1042
+
1043
+ def _typed_json_text_values(
1044
+ value: FrozenJsonValue,
1045
+ ) -> tuple[tuple[str, str], ...]:
1046
+ """Return paths and every key/string in an already-frozen JSON tree."""
1047
+
1048
+ found: list[tuple[str, str]] = []
1049
+
1050
+ def visit(node: FrozenJsonValue, path: str) -> None:
1051
+ if type(node) is str:
1052
+ found.append((path, node))
1053
+ return
1054
+ if type(node) is FrozenJsonArray:
1055
+ for index, item in enumerate(node.items):
1056
+ visit(item, f"{path}[{index}]")
1057
+ return
1058
+ if type(node) is FrozenJsonObject:
1059
+ for key, item in node.items:
1060
+ found.append((f"{path}.<key>", key))
1061
+ visit(item, f"{path}.{key}")
1062
+
1063
+ visit(value, "$")
1064
+ return tuple(found)
1065
+
1066
+
1067
+ def _contains_explicit_option_id(text: str, option_id: str) -> bool:
1068
+ """Detect catalog option identity anywhere in supposedly neutral text.
1069
+
1070
+ A token-boundary check is unsafe because ``.`` is both legal inside an
1071
+ option ID and ordinary sentence punctuation: ``"choose alpha.x1."`` would
1072
+ otherwise evade the guard. Scientific neutral payloads have no legitimate
1073
+ reason to contain a catalog option ID even as a substring, so the stricter
1074
+ containment rule is intentional.
1075
+ """
1076
+
1077
+ return option_id in text.casefold()
1078
+
1079
+
1080
+ def _validate_action_neutral_payload(
1081
+ payload: PortfolioCardPromptPayload,
1082
+ *,
1083
+ identity_index: ValidatedFiniteVariationIdentityIndex,
1084
+ ) -> None:
1085
+ """Fail closed when neutral prose carries exact finite-option identity."""
1086
+
1087
+ if type(payload) is not PortfolioCardPromptPayload:
1088
+ raise TypeError("payload must be an exact PortfolioCardPromptPayload")
1089
+ payload.__post_init__()
1090
+ if type(identity_index) is not ValidatedFiniteVariationIdentityIndex:
1091
+ raise TypeError("identity_index must be an exact validated identity index")
1092
+ identity_index.__post_init__()
1093
+
1094
+ source_option_ids = {
1095
+ binding.option_id for binding in payload.finite_action_evidence
1096
+ }
1097
+ forbidden_hashes = {
1098
+ identity_index.contract_identity_sha256,
1099
+ *identity_index.option_identity_sha256s,
1100
+ *identity_index.child_configuration_sha256s,
1101
+ *(binding.contrast_id for binding in payload.finite_action_evidence),
1102
+ *(
1103
+ binding.option_identity_sha256
1104
+ for binding in payload.finite_action_evidence
1105
+ ),
1106
+ *(
1107
+ binding.contract_identity_sha256
1108
+ for binding in payload.finite_action_evidence
1109
+ ),
1110
+ *(binding.identity_sha256 for binding in payload.finite_action_evidence),
1111
+ }
1112
+ for path, text in _typed_json_text_values(payload.action_neutral_payload):
1113
+ folded = text.casefold()
1114
+ for option_id in identity_index.option_ids:
1115
+ if _contains_explicit_option_id(folded, option_id):
1116
+ raise ValueError(
1117
+ "scientific action-neutral payload contains explicit "
1118
+ f"option_id at {path}"
1119
+ )
1120
+ for option_id in source_option_ids:
1121
+ if _contains_explicit_option_id(folded, option_id):
1122
+ raise ValueError(
1123
+ "scientific action-neutral payload contains source "
1124
+ f"option_id at {path}"
1125
+ )
1126
+ if any(value in folded for value in forbidden_hashes):
1127
+ raise ValueError(
1128
+ "scientific action-neutral payload contains exact option or "
1129
+ f"contract attribution at {path}"
1130
+ )
1131
+
1132
+
1133
+ def portfolio_card_action_evidence_sha256(
1134
+ values: tuple[FiniteActionEvidenceBinding, ...],
1135
+ ) -> str:
1136
+ """Hash-bind the exact prompt-visible action-attribution view."""
1137
+
1138
+ _validate_finite_action_evidence(values, name="finite_action_evidence")
1139
+ return _hash(
1140
+ _CARD_ACTION_EVIDENCE_STATE_DOMAIN,
1141
+ {
1142
+ "schema_version": 1,
1143
+ "finite_action_evidence": [value.to_record() for value in values],
1144
+ },
1145
+ )
1146
+
1147
+
1148
+ def _card_score_state_record(
1149
+ score_components: tuple[CardScoreComponent, ...],
1150
+ assigned_score: float | None,
1151
+ ) -> dict[str, object]:
1152
+ if type(score_components) is not tuple or any(
1153
+ type(component) is not CardScoreComponent for component in score_components
1154
+ ):
1155
+ raise TypeError(
1156
+ "score_components must be an exact tuple of CardScoreComponent values"
1157
+ )
1158
+ for component in score_components:
1159
+ component.__post_init__()
1160
+ score_ids = tuple(component.score_id for component in score_components)
1161
+ if score_ids != tuple(sorted(set(score_ids))):
1162
+ raise ValueError("score_components must use unique canonical score_id order")
1163
+ if assigned_score is not None and (
1164
+ type(assigned_score) is not float or not math.isfinite(assigned_score)
1165
+ ):
1166
+ raise TypeError("assigned_score must be a finite canonical float or None")
1167
+ return {
1168
+ "score_components": [component.to_record() for component in score_components],
1169
+ "assigned_score_hex": (
1170
+ None if assigned_score is None else assigned_score.hex()
1171
+ ),
1172
+ }
1173
+
1174
+
1175
+ def portfolio_card_score_state_sha256(
1176
+ score_components: tuple[CardScoreComponent, ...],
1177
+ assigned_score: float | None,
1178
+ ) -> str:
1179
+ """Hash-bind the exact score projection carried by one card view."""
1180
+
1181
+ return _hash(
1182
+ _CARD_SCORE_STATE_DOMAIN,
1183
+ _card_score_state_record(score_components, assigned_score),
1184
+ )
1185
+
1186
+
1187
+ @dataclass(frozen=True, slots=True)
1188
+ class PortfolioCardSourceBinding:
1189
+ """Integrity-bound source provenance for one insight-to-card projection.
1190
+
1191
+ This receipt binds the immutable insight version and evidence lineage to the
1192
+ exact source view constructed by trusted application code. A later
1193
+ experimental view may redact or permute that view, but it must retain this
1194
+ receipt unchanged and publish a separate :class:`PortfolioCardViewReceipt`.
1195
+ """
1196
+
1197
+ reference: InsightRef
1198
+ content_sha256: str
1199
+ evidence_lineage_identity_sha256: str
1200
+ finite_action_evidence: tuple[FiniteActionEvidenceBinding, ...]
1201
+ source_prompt_view_sha256: str
1202
+ source_evidence_sha256: str
1203
+ source_score_state_sha256: str
1204
+ source_receipt_sha256: str
1205
+
1206
+ def __post_init__(self) -> None:
1207
+ _reference_record(self.reference)
1208
+ for name in (
1209
+ "content_sha256",
1210
+ "evidence_lineage_identity_sha256",
1211
+ "source_prompt_view_sha256",
1212
+ "source_evidence_sha256",
1213
+ "source_score_state_sha256",
1214
+ "source_receipt_sha256",
1215
+ ):
1216
+ require_sha256(getattr(self, name), name)
1217
+ _validate_finite_action_evidence(
1218
+ self.finite_action_evidence,
1219
+ name="finite_action_evidence",
1220
+ )
1221
+
1222
+ def _unsigned_record(self) -> dict[str, object]:
1223
+ self.__post_init__()
1224
+ return {
1225
+ "schema_version": 1,
1226
+ "reference": _reference_record(self.reference),
1227
+ "content_sha256": self.content_sha256,
1228
+ "evidence_lineage_identity_sha256": (self.evidence_lineage_identity_sha256),
1229
+ "finite_action_evidence": [
1230
+ binding.to_record() for binding in self.finite_action_evidence
1231
+ ],
1232
+ "source_prompt_view_sha256": self.source_prompt_view_sha256,
1233
+ "source_evidence_sha256": self.source_evidence_sha256,
1234
+ "source_score_state_sha256": self.source_score_state_sha256,
1235
+ "source_receipt_sha256": self.source_receipt_sha256,
1236
+ }
1237
+
1238
+ @property
1239
+ def binding_sha256(self) -> str:
1240
+ return _hash(_CARD_SOURCE_BINDING_DOMAIN, self._unsigned_record())
1241
+
1242
+ def to_record(self) -> dict[str, object]:
1243
+ return {**self._unsigned_record(), "binding_sha256": self.binding_sha256}
1244
+
1245
+
1246
+ def _bind_portfolio_card_source(
1247
+ *,
1248
+ reference: InsightRef,
1249
+ content_sha256: str,
1250
+ evidence_lineage_identity_sha256: str,
1251
+ finite_action_evidence: tuple[FiniteActionEvidenceBinding, ...],
1252
+ prompt_payload: FrozenJsonObject,
1253
+ evidence_sha256: str,
1254
+ score_components: tuple[CardScoreComponent, ...],
1255
+ assigned_score: float | None,
1256
+ source_receipt_sha256: str,
1257
+ ) -> PortfolioCardSourceBinding:
1258
+ """Issue a source binding from exact framework-neutral projection values."""
1259
+
1260
+ if type(prompt_payload) is not FrozenJsonObject:
1261
+ raise TypeError("prompt_payload must be an exact FrozenJsonObject")
1262
+ if freeze_json(prompt_payload) is not prompt_payload:
1263
+ raise TypeError("prompt_payload must already be frozen typed JSON")
1264
+ return PortfolioCardSourceBinding(
1265
+ reference=reference,
1266
+ content_sha256=content_sha256,
1267
+ evidence_lineage_identity_sha256=evidence_lineage_identity_sha256,
1268
+ finite_action_evidence=finite_action_evidence,
1269
+ source_prompt_view_sha256=typed_json_sha256(prompt_payload),
1270
+ source_evidence_sha256=evidence_sha256,
1271
+ source_score_state_sha256=portfolio_card_score_state_sha256(
1272
+ score_components,
1273
+ assigned_score,
1274
+ ),
1275
+ source_receipt_sha256=source_receipt_sha256,
1276
+ )
1277
+
1278
+
1279
+ @dataclass(frozen=True, slots=True, init=False)
1280
+ class PortfolioCardSourceRegistry:
1281
+ """Application-admitted source bindings for one scientific request.
1282
+
1283
+ The registry is a hash-bound integrity and admission receipt, not a
1284
+ cryptographic signature. Its public constructor is closed: trusted
1285
+ application code issues it only after joining every binding to an exact
1286
+ supplied memory entry. Request validation then prevents a source binding
1287
+ synthesized directly by an adapter from entering without that admission.
1288
+ """
1289
+
1290
+ source_bindings: tuple[PortfolioCardSourceBinding, ...]
1291
+
1292
+ def __init__(self, *args: object, **kwargs: object) -> None:
1293
+ del args, kwargs
1294
+ raise TypeError(
1295
+ "PortfolioCardSourceRegistry is issued by trusted application code"
1296
+ )
1297
+
1298
+ def __post_init__(self) -> None:
1299
+ if type(self.source_bindings) is not tuple or not self.source_bindings:
1300
+ raise ValueError("source_bindings must be a non-empty exact tuple")
1301
+ if any(
1302
+ type(binding) is not PortfolioCardSourceBinding
1303
+ for binding in self.source_bindings
1304
+ ):
1305
+ raise TypeError(
1306
+ "source_bindings must contain exact PortfolioCardSourceBinding values"
1307
+ )
1308
+ for binding in self.source_bindings:
1309
+ binding.__post_init__()
1310
+ identities = tuple(binding.binding_sha256 for binding in self.source_bindings)
1311
+ if identities != tuple(sorted(set(identities))):
1312
+ raise ValueError("source_bindings must use unique canonical binding order")
1313
+ references = tuple(binding.reference for binding in self.source_bindings)
1314
+ if len(set(references)) != len(references):
1315
+ raise ValueError("source_bindings cannot repeat an insight reference")
1316
+
1317
+ def _unsigned_record(self) -> dict[str, object]:
1318
+ self.__post_init__()
1319
+ return {
1320
+ "schema_version": 1,
1321
+ "source_bindings": [
1322
+ binding.to_record() for binding in self.source_bindings
1323
+ ],
1324
+ }
1325
+
1326
+ @property
1327
+ def registry_sha256(self) -> str:
1328
+ return _hash(_CARD_SOURCE_REGISTRY_DOMAIN, self._unsigned_record())
1329
+
1330
+ def to_record(self) -> dict[str, object]:
1331
+ return {
1332
+ **self._unsigned_record(),
1333
+ "registry_sha256": self.registry_sha256,
1334
+ }
1335
+
1336
+
1337
+ def _issue_portfolio_card_source_registry(
1338
+ source_bindings: tuple[PortfolioCardSourceBinding, ...],
1339
+ ) -> PortfolioCardSourceRegistry:
1340
+ """Internal issuer used only after the application validates source joins."""
1341
+
1342
+ registry = object.__new__(PortfolioCardSourceRegistry)
1343
+ object.__setattr__(registry, "source_bindings", source_bindings)
1344
+ registry.__post_init__()
1345
+ return registry
1346
+
1347
+
1348
+ class PortfolioCardViewTransform(str, Enum):
1349
+ """Closed declarations for an experiment-controlled derived card view."""
1350
+
1351
+ EVIDENCE_PERMUTATION = "evidence_permutation"
1352
+ EVIDENCE_REDACTION = "evidence_redaction"
1353
+ PROMPT_PERMUTATION = "prompt_permutation"
1354
+ PROMPT_PROJECTION = "prompt_projection"
1355
+ PROMPT_REDACTION = "prompt_redaction"
1356
+ SCORE_PERMUTATION = "score_permutation"
1357
+ SCORE_REDACTION = "score_redaction"
1358
+ ACTION_EVIDENCE_PERMUTATION = "action_evidence_permutation"
1359
+ ACTION_EVIDENCE_REDACTION = "action_evidence_redaction"
1360
+
1361
+
1362
+ @dataclass(frozen=True, slots=True)
1363
+ class PortfolioCardViewReceipt:
1364
+ """Receipt for a derived view that never replaces its source provenance."""
1365
+
1366
+ source_binding_sha256: str
1367
+ transforms: tuple[PortfolioCardViewTransform, ...]
1368
+ derived_prompt_view_sha256: str
1369
+ derived_evidence_sha256: str
1370
+ derived_score_state_sha256: str
1371
+ derived_action_evidence_sha256: str
1372
+ prompt_source_binding_sha256: str | None
1373
+ evidence_source_binding_sha256: str | None
1374
+ score_source_binding_sha256: str | None
1375
+ action_evidence_source_binding_sha256: str | None
1376
+ policy_id: str
1377
+ policy_version: int
1378
+ policy_definition_sha256: str
1379
+
1380
+ def __post_init__(self) -> None:
1381
+ require_sha256(self.source_binding_sha256, "source_binding_sha256")
1382
+ if (
1383
+ type(self.transforms) is not tuple
1384
+ or not self.transforms
1385
+ or any(
1386
+ type(value) is not PortfolioCardViewTransform
1387
+ for value in self.transforms
1388
+ )
1389
+ ):
1390
+ raise TypeError(
1391
+ "transforms must be a non-empty exact tuple of "
1392
+ "PortfolioCardViewTransform values"
1393
+ )
1394
+ canonical = tuple(sorted(set(self.transforms), key=lambda item: item.value))
1395
+ if self.transforms != canonical:
1396
+ raise ValueError("transforms must be unique and canonically ordered")
1397
+ for name in (
1398
+ "derived_prompt_view_sha256",
1399
+ "derived_evidence_sha256",
1400
+ "derived_score_state_sha256",
1401
+ "derived_action_evidence_sha256",
1402
+ "policy_definition_sha256",
1403
+ ):
1404
+ require_sha256(getattr(self, name), name)
1405
+ for name in (
1406
+ "prompt_source_binding_sha256",
1407
+ "evidence_source_binding_sha256",
1408
+ "score_source_binding_sha256",
1409
+ "action_evidence_source_binding_sha256",
1410
+ ):
1411
+ value = getattr(self, name)
1412
+ if value is not None:
1413
+ require_sha256(value, name)
1414
+ if type(self.policy_id) is not str or _TOKEN.fullmatch(self.policy_id) is None:
1415
+ raise ValueError("policy_id must use the closed lowercase token grammar")
1416
+ if type(self.policy_version) is not int or self.policy_version <= 0:
1417
+ raise ValueError("policy_version must be a positive exact integer")
1418
+ transform_set = set(self.transforms)
1419
+ prompt_permutation = (
1420
+ PortfolioCardViewTransform.PROMPT_PERMUTATION in transform_set
1421
+ )
1422
+ evidence_permutation = (
1423
+ PortfolioCardViewTransform.EVIDENCE_PERMUTATION in transform_set
1424
+ )
1425
+ score_permutation = (
1426
+ PortfolioCardViewTransform.SCORE_PERMUTATION in transform_set
1427
+ )
1428
+ action_evidence_permutation = (
1429
+ PortfolioCardViewTransform.ACTION_EVIDENCE_PERMUTATION in transform_set
1430
+ )
1431
+ if prompt_permutation != (self.prompt_source_binding_sha256 is not None):
1432
+ raise ValueError(
1433
+ "prompt permutation requires exactly one prompt source binding"
1434
+ )
1435
+ if evidence_permutation != (self.evidence_source_binding_sha256 is not None):
1436
+ raise ValueError(
1437
+ "evidence permutation requires exactly one evidence source binding"
1438
+ )
1439
+ if score_permutation != (self.score_source_binding_sha256 is not None):
1440
+ raise ValueError(
1441
+ "score permutation requires exactly one score source binding"
1442
+ )
1443
+ if action_evidence_permutation != (
1444
+ self.action_evidence_source_binding_sha256 is not None
1445
+ ):
1446
+ raise ValueError(
1447
+ "action-evidence permutation requires exactly one source binding"
1448
+ )
1449
+ for value in (
1450
+ self.prompt_source_binding_sha256,
1451
+ self.evidence_source_binding_sha256,
1452
+ self.score_source_binding_sha256,
1453
+ self.action_evidence_source_binding_sha256,
1454
+ ):
1455
+ if value is not None and value == self.source_binding_sha256:
1456
+ raise ValueError("a permuted view must name a different source binding")
1457
+
1458
+ def _unsigned_record(self) -> dict[str, object]:
1459
+ self.__post_init__()
1460
+ return {
1461
+ "schema_version": 1,
1462
+ "source_binding_sha256": self.source_binding_sha256,
1463
+ "transforms": [value.value for value in self.transforms],
1464
+ "derived_prompt_view_sha256": self.derived_prompt_view_sha256,
1465
+ "derived_evidence_sha256": self.derived_evidence_sha256,
1466
+ "derived_score_state_sha256": self.derived_score_state_sha256,
1467
+ "derived_action_evidence_sha256": (self.derived_action_evidence_sha256),
1468
+ "prompt_source_binding_sha256": (self.prompt_source_binding_sha256),
1469
+ "evidence_source_binding_sha256": (self.evidence_source_binding_sha256),
1470
+ "score_source_binding_sha256": self.score_source_binding_sha256,
1471
+ "action_evidence_source_binding_sha256": (
1472
+ self.action_evidence_source_binding_sha256
1473
+ ),
1474
+ "policy_id": self.policy_id,
1475
+ "policy_version": self.policy_version,
1476
+ "policy_definition_sha256": self.policy_definition_sha256,
1477
+ }
1478
+
1479
+ @property
1480
+ def receipt_sha256(self) -> str:
1481
+ return _hash(_CARD_DERIVED_VIEW_DOMAIN, self._unsigned_record())
1482
+
1483
+ def to_record(self) -> dict[str, object]:
1484
+ return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
1485
+
1486
+
1487
+ @dataclass(frozen=True, slots=True)
1488
+ class CardTransferAdjudicationRequest:
1489
+ """Benchmark-owned prediction/outcome projection presented for scoring."""
1490
+
1491
+ card_key: str
1492
+ reference: InsightRef
1493
+ prediction: FrozenJsonObject
1494
+ outcome: FrozenJsonObject
1495
+ source_receipt_sha256s: tuple[str, ...]
1496
+
1497
+ def __post_init__(self) -> None:
1498
+ if type(self.card_key) is not str or _TOKEN.fullmatch(self.card_key) is None:
1499
+ raise ValueError("card_key must use the closed lowercase token grammar")
1500
+ _reference_record(self.reference)
1501
+ for name in ("prediction", "outcome"):
1502
+ value = getattr(self, name)
1503
+ if type(value) is not FrozenJsonObject:
1504
+ raise TypeError(f"{name} must be an exact FrozenJsonObject")
1505
+ if freeze_json(value) is not value:
1506
+ raise TypeError(f"{name} must already be frozen typed JSON")
1507
+ if type(self.source_receipt_sha256s) is not tuple or any(
1508
+ type(value) is not str for value in self.source_receipt_sha256s
1509
+ ):
1510
+ raise TypeError("source_receipt_sha256s must be an exact tuple of strings")
1511
+ for value in self.source_receipt_sha256s:
1512
+ require_sha256(value, "source_receipt_sha256")
1513
+ if self.source_receipt_sha256s != tuple(
1514
+ sorted(set(self.source_receipt_sha256s))
1515
+ ):
1516
+ raise ValueError("source_receipt_sha256s must be unique and canonical")
1517
+
1518
+ def to_record(self) -> dict[str, object]:
1519
+ self.__post_init__()
1520
+ return {
1521
+ "schema_version": 1,
1522
+ "card_key": self.card_key,
1523
+ "reference": _reference_record(self.reference),
1524
+ "prediction_sha256": typed_json_sha256(self.prediction),
1525
+ "outcome_sha256": typed_json_sha256(self.outcome),
1526
+ "source_receipt_sha256s": list(self.source_receipt_sha256s),
1527
+ }
1528
+
1529
+ @property
1530
+ def request_sha256(self) -> str:
1531
+ return _hash(_TRANSFER_REQUEST_DOMAIN, self.to_record())
1532
+
1533
+
1534
+ @dataclass(frozen=True, slots=True, eq=False)
1535
+ class CardTransferScoreReceipt:
1536
+ """Sealed adjudicator output for one exact transfer observation."""
1537
+
1538
+ request_sha256: str
1539
+ score_component: CardScoreComponent
1540
+ adjudicator_id: str
1541
+ adjudicator_version: int
1542
+ adjudicator_definition_sha256: str
1543
+
1544
+ def __post_init__(self) -> None:
1545
+ require_sha256(self.request_sha256, "request_sha256")
1546
+ if type(self.score_component) is not CardScoreComponent:
1547
+ raise TypeError("score_component must be an exact CardScoreComponent")
1548
+ self.score_component.__post_init__()
1549
+ if (
1550
+ type(self.adjudicator_id) is not str
1551
+ or _TOKEN.fullmatch(self.adjudicator_id) is None
1552
+ ):
1553
+ raise ValueError(
1554
+ "adjudicator_id must use the closed lowercase token grammar"
1555
+ )
1556
+ if type(self.adjudicator_version) is not int or self.adjudicator_version <= 0:
1557
+ raise ValueError("adjudicator_version must be a positive exact integer")
1558
+ require_sha256(
1559
+ self.adjudicator_definition_sha256,
1560
+ "adjudicator_definition_sha256",
1561
+ )
1562
+
1563
+ def _unsigned_record(self) -> dict[str, object]:
1564
+ self.__post_init__()
1565
+ return {
1566
+ "schema_version": 1,
1567
+ "request_sha256": self.request_sha256,
1568
+ "score_component": self.score_component.to_record(),
1569
+ "adjudicator_id": self.adjudicator_id,
1570
+ "adjudicator_version": self.adjudicator_version,
1571
+ "adjudicator_definition_sha256": self.adjudicator_definition_sha256,
1572
+ }
1573
+
1574
+ @property
1575
+ def receipt_sha256(self) -> str:
1576
+ return _hash(_TRANSFER_RECEIPT_DOMAIN, self._unsigned_record())
1577
+
1578
+ def to_record(self) -> dict[str, object]:
1579
+ record = self._unsigned_record()
1580
+ return {**record, "receipt_sha256": self.receipt_sha256}
1581
+
1582
+ def __eq__(self, other: object) -> bool:
1583
+ return (
1584
+ type(self) is CardTransferScoreReceipt
1585
+ and type(other) is CardTransferScoreReceipt
1586
+ and self.receipt_sha256 == other.receipt_sha256
1587
+ )
1588
+
1589
+ __hash__ = None
1590
+
1591
+
1592
+ def validate_card_transfer_score_receipt(
1593
+ request: CardTransferAdjudicationRequest,
1594
+ receipt: CardTransferScoreReceipt,
1595
+ ) -> None:
1596
+ """Reject a score receipt detached from its projection or lineage."""
1597
+
1598
+ if type(request) is not CardTransferAdjudicationRequest:
1599
+ raise TypeError("request must be an exact CardTransferAdjudicationRequest")
1600
+ request.__post_init__()
1601
+ if type(receipt) is not CardTransferScoreReceipt:
1602
+ raise TypeError("receipt must be an exact CardTransferScoreReceipt")
1603
+ receipt.__post_init__()
1604
+ if receipt.request_sha256 != request.request_sha256:
1605
+ raise ValueError("score receipt is bound to a different transfer request")
1606
+ if receipt.score_component.receipt_sha256s != request.source_receipt_sha256s:
1607
+ raise ValueError("score component lineage differs from the transfer request")
1608
+
1609
+
1610
+ @runtime_checkable
1611
+ class CardTransferAdjudicator(Protocol):
1612
+ """Benchmark-injected policy for scoring one card transfer observation."""
1613
+
1614
+ def adjudicate(
1615
+ self,
1616
+ request: CardTransferAdjudicationRequest,
1617
+ ) -> CardTransferScoreReceipt: ...
1618
+
1619
+
1620
+ @dataclass(frozen=True, slots=True)
1621
+ class PortfolioCard:
1622
+ """One prompt-visible card view bound to immutable source evidence.
1623
+
1624
+ ``prompt_payload`` is the action-neutral, experiment-controlled view.
1625
+ Adaptive, score-permuted, names-only, and redacted arms may project views
1626
+ while retaining the same source content/evidence hashes. The selector
1627
+ addresses cards only by the opaque task-local ``card_key``.
1628
+ ``finite_action_evidence`` is the prompt-visible action-attribution view.
1629
+ On a source card it equals the immutable action lineage in
1630
+ ``source_binding``. A derived experimental view may permute or redact it
1631
+ only through an explicit donor-bound view receipt; source provenance stays
1632
+ unchanged. Empty evidence preserves legacy card records and prompt bytes.
1633
+ """
1634
+
1635
+ card_key: str
1636
+ reference: InsightRef
1637
+ content_sha256: str
1638
+ evidence_sha256: str
1639
+ prompt_payload: FrozenJsonObject
1640
+ score_components: tuple[CardScoreComponent, ...] = ()
1641
+ assigned_score: float | None = None
1642
+ finite_action_evidence: tuple[FiniteActionEvidenceBinding, ...] = ()
1643
+ source_binding: PortfolioCardSourceBinding | None = None
1644
+ derived_view_receipt: PortfolioCardViewReceipt | None = None
1645
+
1646
+ def __post_init__(self) -> None:
1647
+ if type(self.card_key) is not str or _TOKEN.fullmatch(self.card_key) is None:
1648
+ raise ValueError("card_key must use the closed lowercase token grammar")
1649
+ _reference_record(self.reference)
1650
+ require_sha256(self.content_sha256, "content_sha256")
1651
+ require_sha256(self.evidence_sha256, "evidence_sha256")
1652
+ if type(self.prompt_payload) is not FrozenJsonObject:
1653
+ raise TypeError("prompt_payload must be an exact FrozenJsonObject")
1654
+ if freeze_json(self.prompt_payload) is not self.prompt_payload:
1655
+ raise TypeError("prompt_payload must already be frozen typed JSON")
1656
+ prompt_view_sha256 = typed_json_sha256(self.prompt_payload)
1657
+ score_state_sha256 = portfolio_card_score_state_sha256(
1658
+ self.score_components,
1659
+ self.assigned_score,
1660
+ )
1661
+ action_evidence_sha256 = portfolio_card_action_evidence_sha256(
1662
+ self.finite_action_evidence
1663
+ )
1664
+ _validate_finite_action_evidence(
1665
+ self.finite_action_evidence,
1666
+ name="finite_action_evidence",
1667
+ )
1668
+ if self.source_binding is None:
1669
+ if self.finite_action_evidence:
1670
+ raise ValueError(
1671
+ "non-empty finite_action_evidence requires a source binding"
1672
+ )
1673
+ if self.derived_view_receipt is not None:
1674
+ raise ValueError("a derived view receipt requires a source binding")
1675
+ return
1676
+ if type(self.source_binding) is not PortfolioCardSourceBinding:
1677
+ raise TypeError(
1678
+ "source_binding must be an exact PortfolioCardSourceBinding or None"
1679
+ )
1680
+ self.source_binding.__post_init__()
1681
+ if (
1682
+ self.reference != self.source_binding.reference
1683
+ or self.content_sha256 != self.source_binding.content_sha256
1684
+ ):
1685
+ raise ValueError("card identity differs from its source binding")
1686
+ if self.derived_view_receipt is None:
1687
+ if (
1688
+ prompt_view_sha256 != self.source_binding.source_prompt_view_sha256
1689
+ or self.evidence_sha256 != self.source_binding.source_evidence_sha256
1690
+ or score_state_sha256 != self.source_binding.source_score_state_sha256
1691
+ or self.finite_action_evidence
1692
+ != self.source_binding.finite_action_evidence
1693
+ ):
1694
+ raise ValueError(
1695
+ "card view differs from its source without a derived view receipt"
1696
+ )
1697
+ return
1698
+ if type(self.derived_view_receipt) is not PortfolioCardViewReceipt:
1699
+ raise TypeError(
1700
+ "derived_view_receipt must be an exact PortfolioCardViewReceipt or None"
1701
+ )
1702
+ receipt = self.derived_view_receipt
1703
+ receipt.__post_init__()
1704
+ if receipt.source_binding_sha256 != self.source_binding.binding_sha256:
1705
+ raise ValueError("derived view receipt names a different source binding")
1706
+ if (
1707
+ receipt.derived_prompt_view_sha256 != prompt_view_sha256
1708
+ or receipt.derived_evidence_sha256 != self.evidence_sha256
1709
+ or receipt.derived_score_state_sha256 != score_state_sha256
1710
+ or receipt.derived_action_evidence_sha256 != action_evidence_sha256
1711
+ ):
1712
+ raise ValueError("derived view receipt differs from the exact card view")
1713
+ transforms = set(receipt.transforms)
1714
+ prompt_changed = (
1715
+ prompt_view_sha256 != self.source_binding.source_prompt_view_sha256
1716
+ )
1717
+ evidence_changed = (
1718
+ self.evidence_sha256 != self.source_binding.source_evidence_sha256
1719
+ )
1720
+ score_changed = (
1721
+ score_state_sha256 != self.source_binding.source_score_state_sha256
1722
+ )
1723
+ action_evidence_changed = (
1724
+ self.finite_action_evidence != self.source_binding.finite_action_evidence
1725
+ )
1726
+ prompt_transforms = transforms & {
1727
+ PortfolioCardViewTransform.PROMPT_PERMUTATION,
1728
+ PortfolioCardViewTransform.PROMPT_PROJECTION,
1729
+ PortfolioCardViewTransform.PROMPT_REDACTION,
1730
+ }
1731
+ if len(prompt_transforms) > 1 or (prompt_changed and not prompt_transforms):
1732
+ raise ValueError("prompt transform declaration does not match the view")
1733
+ if (
1734
+ not prompt_changed
1735
+ and PortfolioCardViewTransform.PROMPT_PROJECTION in prompt_transforms
1736
+ ):
1737
+ raise ValueError("prompt projection must change the exact prompt view")
1738
+ evidence_transforms = transforms & {
1739
+ PortfolioCardViewTransform.EVIDENCE_PERMUTATION,
1740
+ PortfolioCardViewTransform.EVIDENCE_REDACTION,
1741
+ }
1742
+ if len(evidence_transforms) > 1 or (
1743
+ evidence_changed and not evidence_transforms
1744
+ ):
1745
+ raise ValueError("evidence transform declaration does not match the view")
1746
+ if (
1747
+ PortfolioCardViewTransform.PROMPT_REDACTION in prompt_transforms
1748
+ and self.prompt_payload != CANONICAL_NEUTRAL_PORTFOLIO_PROMPT_PAYLOAD
1749
+ ):
1750
+ raise ValueError(
1751
+ "redacted prompt payload must use the canonical neutral value"
1752
+ )
1753
+ if (
1754
+ PortfolioCardViewTransform.EVIDENCE_REDACTION in evidence_transforms
1755
+ and self.evidence_sha256 != CANONICAL_REDACTED_PORTFOLIO_EVIDENCE_SHA256
1756
+ ):
1757
+ raise ValueError("redacted evidence must use the canonical sentinel")
1758
+ score_transforms = transforms & {
1759
+ PortfolioCardViewTransform.SCORE_PERMUTATION,
1760
+ PortfolioCardViewTransform.SCORE_REDACTION,
1761
+ }
1762
+ if len(score_transforms) > 1 or (score_changed and not score_transforms):
1763
+ raise ValueError("score transform declaration does not match the view")
1764
+ if PortfolioCardViewTransform.SCORE_REDACTION in score_transforms and (
1765
+ self.score_components or self.assigned_score is not None
1766
+ ):
1767
+ raise ValueError("redacted score state must be empty")
1768
+ action_evidence_transforms = transforms & {
1769
+ PortfolioCardViewTransform.ACTION_EVIDENCE_PERMUTATION,
1770
+ PortfolioCardViewTransform.ACTION_EVIDENCE_REDACTION,
1771
+ }
1772
+ if len(action_evidence_transforms) > 1 or (
1773
+ action_evidence_changed and not action_evidence_transforms
1774
+ ):
1775
+ raise ValueError(
1776
+ "action-evidence transform declaration does not match the view"
1777
+ )
1778
+ if (
1779
+ PortfolioCardViewTransform.ACTION_EVIDENCE_REDACTION
1780
+ in action_evidence_transforms
1781
+ and self.finite_action_evidence
1782
+ ):
1783
+ raise ValueError("redacted action evidence must be empty")
1784
+
1785
+ @property
1786
+ def prompt_view_sha256(self) -> str:
1787
+ self.__post_init__()
1788
+ return typed_json_sha256(self.prompt_payload)
1789
+
1790
+ @property
1791
+ def typed_prompt_payload(self) -> PortfolioCardPromptPayload:
1792
+ """Return the scientific split between neutral prose and attribution."""
1793
+
1794
+ self.__post_init__()
1795
+ return PortfolioCardPromptPayload(
1796
+ action_neutral_payload=self.prompt_payload,
1797
+ finite_action_evidence=self.finite_action_evidence,
1798
+ )
1799
+
1800
+ def to_record(self) -> dict[str, object]:
1801
+ self.__post_init__()
1802
+ return {
1803
+ "card_key": self.card_key,
1804
+ "reference": _reference_record(self.reference),
1805
+ "content_sha256": self.content_sha256,
1806
+ "evidence_sha256": self.evidence_sha256,
1807
+ "prompt_view_sha256": self.prompt_view_sha256,
1808
+ "score_components": [
1809
+ component.to_record() for component in self.score_components
1810
+ ],
1811
+ "assigned_score_hex": (
1812
+ None if self.assigned_score is None else self.assigned_score.hex()
1813
+ ),
1814
+ **(
1815
+ {}
1816
+ if not self.finite_action_evidence
1817
+ else {
1818
+ "finite_action_evidence": [
1819
+ binding.to_record() for binding in self.finite_action_evidence
1820
+ ]
1821
+ }
1822
+ ),
1823
+ **(
1824
+ {}
1825
+ if self.source_binding is None
1826
+ else {"source_binding": self.source_binding.to_record()}
1827
+ ),
1828
+ **(
1829
+ {}
1830
+ if self.derived_view_receipt is None
1831
+ else {"derived_view_receipt": self.derived_view_receipt.to_record()}
1832
+ ),
1833
+ }
1834
+
1835
+ def prompt_record(self) -> dict[str, object]:
1836
+ """Return the exact detached view rendered for the selector."""
1837
+
1838
+ self.__post_init__()
1839
+ return {
1840
+ "card_key": self.card_key,
1841
+ "insight_id": self.reference.insight_id.value,
1842
+ "insight_version": self.reference.version,
1843
+ "content_sha256": self.content_sha256,
1844
+ "evidence_sha256": self.evidence_sha256,
1845
+ "score_components": [
1846
+ component.prompt_record() for component in self.score_components
1847
+ ],
1848
+ "assigned_score": self.assigned_score,
1849
+ "payload": thaw_json(self.prompt_payload),
1850
+ **(
1851
+ {}
1852
+ if not self.finite_action_evidence
1853
+ else {
1854
+ "finite_action_evidence": [
1855
+ binding.to_record() for binding in self.finite_action_evidence
1856
+ ]
1857
+ }
1858
+ ),
1859
+ }
1860
+
1861
+
1862
+ def portfolio_card_snapshot_sha256(cards: tuple[PortfolioCard, ...]) -> str:
1863
+ """Bind the complete source/view/score state supplied to one selector."""
1864
+
1865
+ if type(cards) is not tuple or any(
1866
+ type(card) is not PortfolioCard for card in cards
1867
+ ):
1868
+ raise TypeError("cards must be an exact tuple of PortfolioCard values")
1869
+ for card in cards:
1870
+ card.__post_init__()
1871
+ return _hash(
1872
+ _CARD_SNAPSHOT_DOMAIN,
1873
+ {"cards": [card.to_record() for card in cards]},
1874
+ )
1875
+
1876
+
1877
+ def derive_portfolio_card_view(
1878
+ source_card: PortfolioCard,
1879
+ *,
1880
+ prompt_payload: FrozenJsonObject,
1881
+ evidence_sha256: str,
1882
+ score_components: tuple[CardScoreComponent, ...],
1883
+ assigned_score: float | None,
1884
+ transforms: tuple[PortfolioCardViewTransform, ...],
1885
+ policy_id: str,
1886
+ policy_version: int,
1887
+ policy_definition_sha256: str,
1888
+ prompt_source_card: PortfolioCard | None = None,
1889
+ evidence_source_card: PortfolioCard | None = None,
1890
+ score_source_card: PortfolioCard | None = None,
1891
+ finite_action_evidence: tuple[FiniteActionEvidenceBinding, ...] | None = None,
1892
+ action_evidence_source_card: PortfolioCard | None = None,
1893
+ ) -> PortfolioCard:
1894
+ """Derive an explicit experimental view without changing source lineage."""
1895
+
1896
+ if type(source_card) is not PortfolioCard:
1897
+ raise TypeError("source_card must be an exact PortfolioCard")
1898
+ source_card.__post_init__()
1899
+ if source_card.source_binding is None:
1900
+ raise ValueError("derived views require a source-bound card")
1901
+ if source_card.derived_view_receipt is not None:
1902
+ raise ValueError(
1903
+ "derived views must start from the integrity-bound source view"
1904
+ )
1905
+ if type(prompt_payload) is not FrozenJsonObject:
1906
+ raise TypeError("prompt_payload must be an exact FrozenJsonObject")
1907
+ if freeze_json(prompt_payload) is not prompt_payload:
1908
+ raise TypeError("prompt_payload must already be frozen typed JSON")
1909
+ prompt_source_binding_sha256: str | None = None
1910
+ if prompt_source_card is not None:
1911
+ if type(prompt_source_card) is not PortfolioCard:
1912
+ raise TypeError("prompt_source_card must be an exact PortfolioCard")
1913
+ prompt_source_card.__post_init__()
1914
+ if prompt_source_card.source_binding is None:
1915
+ raise ValueError("prompt source card lacks integrity-bound provenance")
1916
+ prompt_source_binding_sha256 = prompt_source_card.source_binding.binding_sha256
1917
+ if (
1918
+ PortfolioCardViewTransform.PROMPT_PERMUTATION in transforms
1919
+ and typed_json_sha256(prompt_payload)
1920
+ != prompt_source_card.source_binding.source_prompt_view_sha256
1921
+ ):
1922
+ raise ValueError("permuted prompt must equal the named source prompt view")
1923
+ evidence_source_binding_sha256: str | None = None
1924
+ if evidence_source_card is not None:
1925
+ if type(evidence_source_card) is not PortfolioCard:
1926
+ raise TypeError("evidence_source_card must be an exact PortfolioCard")
1927
+ evidence_source_card.__post_init__()
1928
+ if evidence_source_card.source_binding is None:
1929
+ raise ValueError("evidence source card lacks integrity-bound provenance")
1930
+ evidence_source_binding_sha256 = (
1931
+ evidence_source_card.source_binding.binding_sha256
1932
+ )
1933
+ if (
1934
+ PortfolioCardViewTransform.EVIDENCE_PERMUTATION in transforms
1935
+ and evidence_sha256
1936
+ != evidence_source_card.source_binding.source_evidence_sha256
1937
+ ):
1938
+ raise ValueError("permuted evidence must equal the named source evidence")
1939
+ score_source_binding_sha256: str | None = None
1940
+ if score_source_card is not None:
1941
+ if type(score_source_card) is not PortfolioCard:
1942
+ raise TypeError("score_source_card must be an exact PortfolioCard")
1943
+ score_source_card.__post_init__()
1944
+ if score_source_card.source_binding is None:
1945
+ raise ValueError("score source card lacks integrity-bound provenance")
1946
+ score_source_binding_sha256 = score_source_card.source_binding.binding_sha256
1947
+ if (
1948
+ PortfolioCardViewTransform.SCORE_PERMUTATION in transforms
1949
+ and portfolio_card_score_state_sha256(
1950
+ score_components,
1951
+ assigned_score,
1952
+ )
1953
+ != score_source_card.source_binding.source_score_state_sha256
1954
+ ):
1955
+ raise ValueError(
1956
+ "permuted score state must equal the named source score state"
1957
+ )
1958
+ projected_action_evidence = (
1959
+ source_card.source_binding.finite_action_evidence
1960
+ if finite_action_evidence is None
1961
+ else finite_action_evidence
1962
+ )
1963
+ _validate_finite_action_evidence(
1964
+ projected_action_evidence,
1965
+ name="finite_action_evidence",
1966
+ )
1967
+ action_evidence_source_binding_sha256: str | None = None
1968
+ if action_evidence_source_card is not None:
1969
+ if type(action_evidence_source_card) is not PortfolioCard:
1970
+ raise TypeError(
1971
+ "action_evidence_source_card must be an exact PortfolioCard"
1972
+ )
1973
+ action_evidence_source_card.__post_init__()
1974
+ if action_evidence_source_card.source_binding is None:
1975
+ raise ValueError(
1976
+ "action-evidence source card lacks integrity-bound provenance"
1977
+ )
1978
+ action_evidence_source_binding_sha256 = (
1979
+ action_evidence_source_card.source_binding.binding_sha256
1980
+ )
1981
+ if (
1982
+ PortfolioCardViewTransform.ACTION_EVIDENCE_PERMUTATION in transforms
1983
+ and projected_action_evidence
1984
+ != action_evidence_source_card.source_binding.finite_action_evidence
1985
+ ):
1986
+ raise ValueError(
1987
+ "permuted action evidence must equal the named source action evidence"
1988
+ )
1989
+ if (
1990
+ PortfolioCardViewTransform.ACTION_EVIDENCE_REDACTION in transforms
1991
+ and projected_action_evidence
1992
+ ):
1993
+ raise ValueError("redacted action evidence must be empty")
1994
+ receipt = PortfolioCardViewReceipt(
1995
+ source_binding_sha256=source_card.source_binding.binding_sha256,
1996
+ transforms=transforms,
1997
+ derived_prompt_view_sha256=typed_json_sha256(prompt_payload),
1998
+ derived_evidence_sha256=evidence_sha256,
1999
+ derived_score_state_sha256=portfolio_card_score_state_sha256(
2000
+ score_components,
2001
+ assigned_score,
2002
+ ),
2003
+ derived_action_evidence_sha256=(
2004
+ portfolio_card_action_evidence_sha256(projected_action_evidence)
2005
+ ),
2006
+ prompt_source_binding_sha256=prompt_source_binding_sha256,
2007
+ evidence_source_binding_sha256=evidence_source_binding_sha256,
2008
+ score_source_binding_sha256=score_source_binding_sha256,
2009
+ action_evidence_source_binding_sha256=(action_evidence_source_binding_sha256),
2010
+ policy_id=policy_id,
2011
+ policy_version=policy_version,
2012
+ policy_definition_sha256=policy_definition_sha256,
2013
+ )
2014
+ return PortfolioCard(
2015
+ card_key=source_card.card_key,
2016
+ reference=source_card.reference,
2017
+ content_sha256=source_card.content_sha256,
2018
+ evidence_sha256=evidence_sha256,
2019
+ prompt_payload=prompt_payload,
2020
+ score_components=score_components,
2021
+ assigned_score=assigned_score,
2022
+ finite_action_evidence=projected_action_evidence,
2023
+ source_binding=source_card.source_binding,
2024
+ derived_view_receipt=receipt,
2025
+ )
2026
+
2027
+
2028
+ class PortfolioExperimentalArm(str, Enum):
2029
+ """Closed scientific M/P/N portfolio-view arms."""
2030
+
2031
+ MEMORY = "m"
2032
+ PERMUTED_PLACEBO = "p"
2033
+ NEUTRAL = "n"
2034
+
2035
+
2036
+ @dataclass(frozen=True, slots=True)
2037
+ class PortfolioExperimentalViewReceipt:
2038
+ """Request-level commitment to one complete scientific card view.
2039
+
2040
+ Per-card receipts bind compartment bytes. This receipt additionally binds
2041
+ the arm and the population-level permutation invariant, which cannot be
2042
+ established by validating cards independently.
2043
+ """
2044
+
2045
+ arm: PortfolioExperimentalArm
2046
+ source_registry_sha256: str
2047
+ card_snapshot_sha256: str
2048
+ source_donor_binding_pairs: tuple[tuple[str, str], ...]
2049
+ policy_id: str
2050
+ policy_version: int
2051
+ policy_definition_sha256: str
2052
+
2053
+ def __post_init__(self) -> None:
2054
+ if type(self.arm) is not PortfolioExperimentalArm:
2055
+ raise TypeError("arm must be an exact PortfolioExperimentalArm")
2056
+ require_sha256(self.source_registry_sha256, "source_registry_sha256")
2057
+ require_sha256(self.card_snapshot_sha256, "card_snapshot_sha256")
2058
+ if type(self.source_donor_binding_pairs) is not tuple or any(
2059
+ type(pair) is not tuple
2060
+ or len(pair) != 2
2061
+ or any(type(value) is not str for value in pair)
2062
+ for pair in self.source_donor_binding_pairs
2063
+ ):
2064
+ raise TypeError(
2065
+ "source_donor_binding_pairs must be an exact tuple of SHA pairs"
2066
+ )
2067
+ for source_sha256, donor_sha256 in self.source_donor_binding_pairs:
2068
+ require_sha256(source_sha256, "source_binding_sha256")
2069
+ require_sha256(donor_sha256, "donor_binding_sha256")
2070
+ if self.source_donor_binding_pairs != tuple(
2071
+ sorted(self.source_donor_binding_pairs)
2072
+ ):
2073
+ raise ValueError("source-donor pairs must use canonical source order")
2074
+ sources = tuple(pair[0] for pair in self.source_donor_binding_pairs)
2075
+ donors = tuple(pair[1] for pair in self.source_donor_binding_pairs)
2076
+ if len(set(sources)) != len(sources):
2077
+ raise ValueError("source-donor pairs cannot repeat a source")
2078
+ if self.arm is PortfolioExperimentalArm.PERMUTED_PLACEBO:
2079
+ if not sources:
2080
+ raise ValueError("P requires a non-empty donor permutation")
2081
+ if set(sources) != set(donors) or len(set(donors)) != len(donors):
2082
+ raise ValueError("P donors must be a bijection over source cards")
2083
+ if any(
2084
+ source == donor for source, donor in self.source_donor_binding_pairs
2085
+ ):
2086
+ raise ValueError("P donor permutation must be a derangement")
2087
+ elif self.source_donor_binding_pairs:
2088
+ raise ValueError("only P may carry source-donor pairs")
2089
+ if type(self.policy_id) is not str or _TOKEN.fullmatch(self.policy_id) is None:
2090
+ raise ValueError("policy_id must use the closed lowercase token grammar")
2091
+ if type(self.policy_version) is not int or self.policy_version <= 0:
2092
+ raise ValueError("policy_version must be a positive exact integer")
2093
+ require_sha256(self.policy_definition_sha256, "policy_definition_sha256")
2094
+
2095
+ def _unsigned_record(self) -> dict[str, object]:
2096
+ self.__post_init__()
2097
+ return {
2098
+ "schema_version": 1,
2099
+ "arm": self.arm.value,
2100
+ "source_registry_sha256": self.source_registry_sha256,
2101
+ "card_snapshot_sha256": self.card_snapshot_sha256,
2102
+ "source_donor_binding_pairs": [
2103
+ {"source_binding_sha256": source, "donor_binding_sha256": donor}
2104
+ for source, donor in self.source_donor_binding_pairs
2105
+ ],
2106
+ "policy_id": self.policy_id,
2107
+ "policy_version": self.policy_version,
2108
+ "policy_definition_sha256": self.policy_definition_sha256,
2109
+ }
2110
+
2111
+ @property
2112
+ def receipt_sha256(self) -> str:
2113
+ return _hash(_EXPERIMENTAL_VIEW_RECEIPT_DOMAIN, self._unsigned_record())
2114
+
2115
+ def to_record(self) -> dict[str, object]:
2116
+ return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
2117
+
2118
+
2119
+ def validate_portfolio_experimental_view(
2120
+ *,
2121
+ cards: tuple[PortfolioCard, ...],
2122
+ finite_variation_contract: FiniteVariationContract,
2123
+ source_registry: PortfolioCardSourceRegistry,
2124
+ receipt: PortfolioExperimentalViewReceipt,
2125
+ ) -> None:
2126
+ """Validate a complete provider-neutral M/P/N scientific prompt view."""
2127
+
2128
+ if (
2129
+ type(cards) is not tuple
2130
+ or not cards
2131
+ or any(type(card) is not PortfolioCard for card in cards)
2132
+ ):
2133
+ raise TypeError("cards must be a non-empty exact tuple of PortfolioCard values")
2134
+ identity_index = validated_finite_variation_identity_index(
2135
+ finite_variation_contract
2136
+ )
2137
+ if type(source_registry) is not PortfolioCardSourceRegistry:
2138
+ raise TypeError("source_registry must be an exact PortfolioCardSourceRegistry")
2139
+ source_registry.__post_init__()
2140
+ if type(receipt) is not PortfolioExperimentalViewReceipt:
2141
+ raise TypeError("receipt must be an exact PortfolioExperimentalViewReceipt")
2142
+ receipt.__post_init__()
2143
+ if receipt.source_registry_sha256 != source_registry.registry_sha256:
2144
+ raise ValueError("experimental receipt names a different source registry")
2145
+ if receipt.card_snapshot_sha256 != portfolio_card_snapshot_sha256(cards):
2146
+ raise ValueError("experimental receipt names a different card snapshot")
2147
+
2148
+ sources = {
2149
+ binding.binding_sha256: binding for binding in source_registry.source_bindings
2150
+ }
2151
+ cards_by_source: dict[str, PortfolioCard] = {}
2152
+ for card in cards:
2153
+ card.__post_init__()
2154
+ if card.source_binding is None:
2155
+ raise ValueError("scientific views reject unbound legacy cards")
2156
+ source_sha256 = card.source_binding.binding_sha256
2157
+ if source_sha256 in cards_by_source:
2158
+ raise ValueError("scientific views cannot repeat a source card")
2159
+ cards_by_source[source_sha256] = card
2160
+ admitted = sources.get(source_sha256)
2161
+ if admitted is None or admitted != card.source_binding:
2162
+ raise ValueError(
2163
+ "scientific card source differs from the admitted registry"
2164
+ )
2165
+ _validate_action_neutral_payload(
2166
+ card.typed_prompt_payload,
2167
+ identity_index=identity_index,
2168
+ )
2169
+ if cards_by_source.keys() != sources.keys():
2170
+ raise ValueError("scientific cards differ from the complete source registry")
2171
+
2172
+ if receipt.arm is PortfolioExperimentalArm.MEMORY:
2173
+ if any(card.derived_view_receipt is not None for card in cards):
2174
+ raise ValueError("M requires pristine coherent source cards")
2175
+ return
2176
+
2177
+ if receipt.arm is PortfolioExperimentalArm.PERMUTED_PLACEBO:
2178
+ required_transforms = tuple(
2179
+ sorted(
2180
+ (
2181
+ PortfolioCardViewTransform.EVIDENCE_PERMUTATION,
2182
+ PortfolioCardViewTransform.PROMPT_PERMUTATION,
2183
+ PortfolioCardViewTransform.SCORE_PERMUTATION,
2184
+ ),
2185
+ key=lambda value: value.value,
2186
+ )
2187
+ )
2188
+ inferred_pairs: list[tuple[str, str]] = []
2189
+ for source_sha256, card in cards_by_source.items():
2190
+ view = card.derived_view_receipt
2191
+ if view is None or view.transforms != required_transforms:
2192
+ raise ValueError(
2193
+ "P requires exact prompt/evidence/score permutation while "
2194
+ "retaining source action evidence"
2195
+ )
2196
+ donors = (
2197
+ view.prompt_source_binding_sha256,
2198
+ view.evidence_source_binding_sha256,
2199
+ view.score_source_binding_sha256,
2200
+ )
2201
+ if any(donor is None for donor in donors) or len(set(donors)) != 1:
2202
+ raise ValueError(
2203
+ "P requires one donor across prompt/evidence/score compartments"
2204
+ )
2205
+ donor_sha256 = donors[0]
2206
+ assert donor_sha256 is not None
2207
+ donor = sources.get(donor_sha256)
2208
+ if donor is None:
2209
+ raise ValueError("P donor is outside the admitted source registry")
2210
+ if (
2211
+ view.derived_prompt_view_sha256 != donor.source_prompt_view_sha256
2212
+ or view.derived_evidence_sha256 != donor.source_evidence_sha256
2213
+ or view.derived_score_state_sha256 != donor.source_score_state_sha256
2214
+ ):
2215
+ raise ValueError(
2216
+ "P prompt/evidence/score compartment differs from its donor"
2217
+ )
2218
+ if (
2219
+ view.action_evidence_source_binding_sha256 is not None
2220
+ or card.finite_action_evidence
2221
+ != card.source_binding.finite_action_evidence
2222
+ or view.derived_action_evidence_sha256
2223
+ != portfolio_card_action_evidence_sha256(
2224
+ card.source_binding.finite_action_evidence
2225
+ )
2226
+ ):
2227
+ raise ValueError(
2228
+ "P must retain the source card's finite-action evidence"
2229
+ )
2230
+ inferred_pairs.append((source_sha256, donor_sha256))
2231
+ if tuple(sorted(inferred_pairs)) != receipt.source_donor_binding_pairs:
2232
+ raise ValueError("P receipt differs from the exact donor permutation")
2233
+ return
2234
+
2235
+ required_transforms = tuple(
2236
+ sorted(
2237
+ (
2238
+ PortfolioCardViewTransform.ACTION_EVIDENCE_REDACTION,
2239
+ PortfolioCardViewTransform.EVIDENCE_REDACTION,
2240
+ PortfolioCardViewTransform.PROMPT_REDACTION,
2241
+ PortfolioCardViewTransform.SCORE_REDACTION,
2242
+ ),
2243
+ key=lambda value: value.value,
2244
+ )
2245
+ )
2246
+ for card in cards:
2247
+ view = card.derived_view_receipt
2248
+ if view is None or view.transforms != required_transforms:
2249
+ raise ValueError("N requires exact canonical compartment redaction")
2250
+ if (
2251
+ card.prompt_payload != CANONICAL_NEUTRAL_PORTFOLIO_PROMPT_PAYLOAD
2252
+ or card.evidence_sha256 != CANONICAL_REDACTED_PORTFOLIO_EVIDENCE_SHA256
2253
+ or card.score_components
2254
+ or card.assigned_score is not None
2255
+ or card.finite_action_evidence
2256
+ ):
2257
+ raise ValueError("N card compartments are not canonically neutral")
2258
+
2259
+
2260
+ @dataclass(frozen=True, slots=True)
2261
+ class _PortfolioSelectionRequestValidationReceipt:
2262
+ finite_identity_index: ValidatedFiniteVariationIdentityIndex
2263
+ context_sha256: str
2264
+ card_snapshot_sha256: str
2265
+
2266
+ def __post_init__(self) -> None:
2267
+ if (
2268
+ type(self.finite_identity_index)
2269
+ is not ValidatedFiniteVariationIdentityIndex
2270
+ ):
2271
+ raise TypeError("finite_identity_index must be exact")
2272
+ self.finite_identity_index.__post_init__()
2273
+ require_sha256(self.context_sha256, "context_sha256")
2274
+ require_sha256(self.card_snapshot_sha256, "card_snapshot_sha256")
2275
+
2276
+
2277
+ @dataclass(frozen=True, slots=True)
2278
+ class PortfolioSelectionRequest:
2279
+ """One exact logical call over a sealed action and card snapshot."""
2280
+
2281
+ call_id: LLMCallId
2282
+ operation: str
2283
+ instruction: str
2284
+ context: FrozenJsonObject
2285
+ finite_variation_contract: FiniteVariationContract
2286
+ cards: tuple[PortfolioCard, ...]
2287
+ portfolio_size: int
2288
+ required_metric_ids: tuple[str, ...]
2289
+ min_distinct_families: int | None = None
2290
+ require_supporting_cards: bool = True
2291
+ require_pairwise_disjoint_parent_patches: bool = False
2292
+ max_output_tokens: int = 2_048
2293
+ temperature: float | None = None
2294
+ source_registry: PortfolioCardSourceRegistry | None = None
2295
+ experimental_view_receipt: PortfolioExperimentalViewReceipt | None = None
2296
+ memory_dose_contract: BoundedPortfolioMemoryDoseContract | None = None
2297
+ candidate_pool_required_option_ids: tuple[str, ...] = ()
2298
+ _validation_receipt: _PortfolioSelectionRequestValidationReceipt = field(
2299
+ init=False,
2300
+ repr=False,
2301
+ compare=False,
2302
+ )
2303
+
2304
+ def __post_init__(self) -> None:
2305
+ if type(self.call_id) is not LLMCallId:
2306
+ raise TypeError("call_id must be an exact LLMCallId")
2307
+ LLMCallId.__post_init__(self.call_id)
2308
+ if type(self.operation) is not str or _TOKEN.fullmatch(self.operation) is None:
2309
+ raise ValueError("operation must use the closed lowercase token grammar")
2310
+ if (
2311
+ type(self.instruction) is not str
2312
+ or not self.instruction.strip()
2313
+ or self.instruction != self.instruction.strip()
2314
+ ):
2315
+ raise ValueError("instruction must be canonical non-empty text")
2316
+ if type(self.context) is not FrozenJsonObject:
2317
+ raise TypeError("context must be an exact FrozenJsonObject")
2318
+ if freeze_json(self.context) is not self.context:
2319
+ raise TypeError("context must already be frozen typed JSON")
2320
+ finite_identity_index = validated_finite_variation_identity_index(
2321
+ self.finite_variation_contract
2322
+ )
2323
+ if type(self.cards) is not tuple or not self.cards:
2324
+ raise ValueError("cards must be a non-empty exact tuple")
2325
+ if any(type(card) is not PortfolioCard for card in self.cards):
2326
+ raise TypeError("cards must contain exact PortfolioCard values")
2327
+ for card in self.cards:
2328
+ card.__post_init__()
2329
+ card_keys = tuple(card.card_key for card in self.cards)
2330
+ if card_keys != tuple(sorted(set(card_keys))):
2331
+ raise ValueError("cards must use unique canonical card_key order")
2332
+ references = tuple(card.reference for card in self.cards)
2333
+ if len(set(references)) != len(references):
2334
+ raise ValueError("cards cannot repeat an exact insight reference")
2335
+ source_bindings = {
2336
+ card.source_binding.binding_sha256: card.source_binding
2337
+ for card in self.cards
2338
+ if card.source_binding is not None
2339
+ }
2340
+ if source_bindings:
2341
+ if len(source_bindings) != len(self.cards):
2342
+ raise ValueError(
2343
+ "a source-admitted request cannot mix bound and legacy cards"
2344
+ )
2345
+ if type(self.source_registry) is not PortfolioCardSourceRegistry:
2346
+ raise ValueError(
2347
+ "source-bound cards require trusted application admission"
2348
+ )
2349
+ self.source_registry.__post_init__()
2350
+ admitted = {
2351
+ binding.binding_sha256: binding
2352
+ for binding in self.source_registry.source_bindings
2353
+ }
2354
+ if admitted.keys() != source_bindings.keys():
2355
+ raise ValueError(
2356
+ "source registry differs from the request card source set"
2357
+ )
2358
+ for binding_sha256, binding in source_bindings.items():
2359
+ if admitted[binding_sha256] != binding:
2360
+ raise ValueError(
2361
+ "source registry binding differs from the request card"
2362
+ )
2363
+ elif self.source_registry is not None:
2364
+ raise ValueError("legacy unbound cards cannot carry a source registry")
2365
+ for card in self.cards:
2366
+ receipt = card.derived_view_receipt
2367
+ if receipt is None:
2368
+ continue
2369
+ prompt_source_sha256 = receipt.prompt_source_binding_sha256
2370
+ if prompt_source_sha256 is not None:
2371
+ prompt_source = source_bindings.get(prompt_source_sha256)
2372
+ if prompt_source is None:
2373
+ raise ValueError(
2374
+ "derived card view names a prompt source outside the request"
2375
+ )
2376
+ if (
2377
+ receipt.derived_prompt_view_sha256
2378
+ != prompt_source.source_prompt_view_sha256
2379
+ ):
2380
+ raise ValueError(
2381
+ "permuted prompt differs from the named source view"
2382
+ )
2383
+ evidence_source_sha256 = receipt.evidence_source_binding_sha256
2384
+ if evidence_source_sha256 is not None:
2385
+ evidence_source = source_bindings.get(evidence_source_sha256)
2386
+ if evidence_source is None:
2387
+ raise ValueError(
2388
+ "derived card view names an evidence source outside the request"
2389
+ )
2390
+ if (
2391
+ receipt.derived_evidence_sha256
2392
+ != evidence_source.source_evidence_sha256
2393
+ ):
2394
+ raise ValueError(
2395
+ "permuted evidence differs from the named source view"
2396
+ )
2397
+ score_source_sha256 = receipt.score_source_binding_sha256
2398
+ if score_source_sha256 is not None:
2399
+ score_source = source_bindings.get(score_source_sha256)
2400
+ if score_source is None:
2401
+ raise ValueError(
2402
+ "derived card view names a score source outside the request"
2403
+ )
2404
+ if (
2405
+ receipt.derived_score_state_sha256
2406
+ != score_source.source_score_state_sha256
2407
+ ):
2408
+ raise ValueError(
2409
+ "permuted score state differs from the named source view"
2410
+ )
2411
+ action_source_sha256 = receipt.action_evidence_source_binding_sha256
2412
+ if action_source_sha256 is not None:
2413
+ action_source = source_bindings.get(action_source_sha256)
2414
+ if action_source is None:
2415
+ raise ValueError(
2416
+ "derived card view names an action-evidence source "
2417
+ "outside the request"
2418
+ )
2419
+ if (
2420
+ receipt.derived_action_evidence_sha256
2421
+ != portfolio_card_action_evidence_sha256(
2422
+ action_source.finite_action_evidence
2423
+ )
2424
+ ):
2425
+ raise ValueError(
2426
+ "permuted action evidence differs from the named source view"
2427
+ )
2428
+ if self.experimental_view_receipt is not None:
2429
+ if (
2430
+ type(self.experimental_view_receipt)
2431
+ is not PortfolioExperimentalViewReceipt
2432
+ ):
2433
+ raise TypeError(
2434
+ "experimental_view_receipt must be an exact "
2435
+ "PortfolioExperimentalViewReceipt or None"
2436
+ )
2437
+ if type(self.source_registry) is not PortfolioCardSourceRegistry:
2438
+ raise ValueError(
2439
+ "a scientific experimental view requires source admission"
2440
+ )
2441
+ validate_portfolio_experimental_view(
2442
+ cards=self.cards,
2443
+ finite_variation_contract=self.finite_variation_contract,
2444
+ source_registry=self.source_registry,
2445
+ receipt=self.experimental_view_receipt,
2446
+ )
2447
+ if type(self.portfolio_size) is not int or self.portfolio_size <= 0:
2448
+ raise ValueError("portfolio_size must be a positive exact integer")
2449
+ if self.portfolio_size > len(self.finite_variation_contract.options):
2450
+ raise ValueError("portfolio_size exceeds the finite option count")
2451
+ _canonical_metric_ids(self.required_metric_ids)
2452
+ available_families = {
2453
+ option.family for option in self.finite_variation_contract.options
2454
+ }
2455
+ if self.min_distinct_families is not None:
2456
+ if (
2457
+ type(self.min_distinct_families) is not int
2458
+ or self.min_distinct_families <= 0
2459
+ ):
2460
+ raise ValueError(
2461
+ "min_distinct_families must be a positive exact integer or None"
2462
+ )
2463
+ if self.min_distinct_families > self.portfolio_size:
2464
+ raise ValueError("min_distinct_families cannot exceed portfolio_size")
2465
+ if self.min_distinct_families > len(available_families):
2466
+ raise ValueError("finite contract cannot satisfy min_distinct_families")
2467
+ if type(self.require_supporting_cards) is not bool:
2468
+ raise TypeError("require_supporting_cards must be an exact bool")
2469
+ if self.memory_dose_contract is not None:
2470
+ if type(self.memory_dose_contract) is not (
2471
+ BoundedPortfolioMemoryDoseContract
2472
+ ):
2473
+ raise TypeError(
2474
+ "memory_dose_contract must be an exact bounded contract or None"
2475
+ )
2476
+ self.memory_dose_contract.__post_init__()
2477
+ if self.require_supporting_cards:
2478
+ raise ValueError(
2479
+ "bounded memory dose requires unattributed exploration slots"
2480
+ )
2481
+ if (
2482
+ self.memory_dose_contract.finite_contract_identity_sha256
2483
+ != finite_identity_index.contract_identity_sha256
2484
+ ):
2485
+ raise ValueError("memory-dose contract names a foreign finite contract")
2486
+ cards_by_key = {value.card_key: value for value in self.cards}
2487
+ if not set(self.memory_dose_contract.assigned_card_keys).issubset(
2488
+ cards_by_key
2489
+ ):
2490
+ raise ValueError(
2491
+ "memory-dose contract names a card outside the request"
2492
+ )
2493
+ if any(
2494
+ cards_by_key[support.card_key].content_sha256
2495
+ != support.card_content_sha256
2496
+ for support in self.memory_dose_contract.card_supports
2497
+ ):
2498
+ raise ValueError(
2499
+ "memory-dose support differs from the request card content"
2500
+ )
2501
+ if (
2502
+ type(self.candidate_pool_required_option_ids) is not tuple
2503
+ or any(
2504
+ type(value) is not str or _OPTION_ID.fullmatch(value) is None
2505
+ for value in self.candidate_pool_required_option_ids
2506
+ )
2507
+ ):
2508
+ raise TypeError(
2509
+ "candidate_pool_required_option_ids must be an exact option-ID tuple"
2510
+ )
2511
+ if self.candidate_pool_required_option_ids != tuple(
2512
+ sorted(set(self.candidate_pool_required_option_ids))
2513
+ ):
2514
+ raise ValueError(
2515
+ "candidate_pool_required_option_ids must be unique and canonical"
2516
+ )
2517
+ if not set(self.candidate_pool_required_option_ids).issubset(
2518
+ finite_identity_index.option_ids
2519
+ ):
2520
+ raise ValueError(
2521
+ "candidate_pool_required_option_ids escapes the finite contract"
2522
+ )
2523
+ if type(self.require_pairwise_disjoint_parent_patches) is not bool:
2524
+ raise TypeError(
2525
+ "require_pairwise_disjoint_parent_patches must be an exact bool"
2526
+ )
2527
+ if self.require_pairwise_disjoint_parent_patches and not (
2528
+ finite_portfolio_has_pairwise_disjoint_parent_patches(
2529
+ self.finite_variation_contract,
2530
+ portfolio_size=self.portfolio_size,
2531
+ min_distinct_families=self.min_distinct_families,
2532
+ )
2533
+ ):
2534
+ raise ValueError(
2535
+ "finite contract has no feasible pairwise-disjoint portfolio"
2536
+ )
2537
+ if (
2538
+ type(self.max_output_tokens) is not int
2539
+ or not 1 <= self.max_output_tokens <= MAX_OUTPUT_TOKENS
2540
+ ):
2541
+ raise ValueError(f"max_output_tokens must lie in [1, {MAX_OUTPUT_TOKENS}]")
2542
+ if self.temperature is not None and (
2543
+ isinstance(self.temperature, bool)
2544
+ or not isinstance(self.temperature, (int, float))
2545
+ or not math.isfinite(float(self.temperature))
2546
+ or not 0 <= float(self.temperature) <= 2
2547
+ ):
2548
+ raise ValueError("temperature must be finite in [0,2] or None")
2549
+ object.__setattr__(
2550
+ self,
2551
+ "_validation_receipt",
2552
+ _PortfolioSelectionRequestValidationReceipt(
2553
+ finite_identity_index=finite_identity_index,
2554
+ context_sha256=typed_json_sha256(self.context),
2555
+ card_snapshot_sha256=portfolio_card_snapshot_sha256(self.cards),
2556
+ ),
2557
+ )
2558
+
2559
+ @property
2560
+ def context_sha256(self) -> str:
2561
+ if type(self.context) is not FrozenJsonObject:
2562
+ raise TypeError("context must be an exact FrozenJsonObject")
2563
+ if freeze_json(self.context) is not self.context:
2564
+ raise TypeError("context must already be frozen typed JSON")
2565
+ return typed_json_sha256(self.context)
2566
+
2567
+ @property
2568
+ def card_snapshot_sha256(self) -> str:
2569
+ return portfolio_card_snapshot_sha256(self.cards)
2570
+
2571
+ def to_record(self) -> dict[str, object]:
2572
+ self.__post_init__()
2573
+ receipt = self._validation_receipt
2574
+ return {
2575
+ "schema_version": 1,
2576
+ "call_id": self.call_id.value,
2577
+ "operation": self.operation,
2578
+ "instruction_sha256": hashlib.sha256(
2579
+ self.instruction.encode("utf-8", errors="strict")
2580
+ ).hexdigest(),
2581
+ "context_sha256": receipt.context_sha256,
2582
+ "finite_contract_identity_sha256": (
2583
+ receipt.finite_identity_index.contract_identity_sha256
2584
+ ),
2585
+ "card_snapshot_sha256": receipt.card_snapshot_sha256,
2586
+ "portfolio_size": self.portfolio_size,
2587
+ "required_metric_ids": list(self.required_metric_ids),
2588
+ **(
2589
+ {}
2590
+ if self.source_registry is None
2591
+ else {"source_registry_sha256": self.source_registry.registry_sha256}
2592
+ ),
2593
+ **(
2594
+ {}
2595
+ if self.experimental_view_receipt is None
2596
+ else {
2597
+ "experimental_view_receipt_sha256": (
2598
+ self.experimental_view_receipt.receipt_sha256
2599
+ )
2600
+ }
2601
+ ),
2602
+ "min_distinct_families": self.min_distinct_families,
2603
+ "require_supporting_cards": self.require_supporting_cards,
2604
+ **(
2605
+ {}
2606
+ if self.memory_dose_contract is None
2607
+ else {"memory_dose_contract": (self.memory_dose_contract.to_record())}
2608
+ ),
2609
+ **(
2610
+ {}
2611
+ if not self.candidate_pool_required_option_ids
2612
+ else {
2613
+ "candidate_pool_required_option_ids": list(
2614
+ self.candidate_pool_required_option_ids
2615
+ )
2616
+ }
2617
+ ),
2618
+ **(
2619
+ {
2620
+ "require_pairwise_disjoint_parent_patches": True,
2621
+ }
2622
+ if self.require_pairwise_disjoint_parent_patches
2623
+ else {}
2624
+ ),
2625
+ "max_output_tokens": self.max_output_tokens,
2626
+ "temperature_hex": (
2627
+ None if self.temperature is None else float(self.temperature).hex()
2628
+ ),
2629
+ }
2630
+
2631
+ @property
2632
+ def request_sha256(self) -> str:
2633
+ return _hash(_REQUEST_DOMAIN, self.to_record())
2634
+
2635
+
2636
+ @dataclass(frozen=True, slots=True)
2637
+ class PortfolioMemberDraft:
2638
+ """Framework-neutral structured output before trusted option resolution."""
2639
+
2640
+ option_id: str
2641
+ supporting_card_keys: tuple[str, ...]
2642
+ effect_predictions: tuple[MetricEffectPrediction, ...]
2643
+ design_rationale: str
2644
+
2645
+ def __post_init__(self) -> None:
2646
+ if (
2647
+ type(self.option_id) is not str
2648
+ or _OPTION_ID.fullmatch(self.option_id) is None
2649
+ ):
2650
+ raise ValueError("option_id must use the closed option grammar")
2651
+ if type(self.supporting_card_keys) is not tuple or any(
2652
+ type(value) is not str or _TOKEN.fullmatch(value) is None
2653
+ for value in self.supporting_card_keys
2654
+ ):
2655
+ raise TypeError(
2656
+ "supporting_card_keys must be an exact tuple of card tokens"
2657
+ )
2658
+ if self.supporting_card_keys != tuple(sorted(set(self.supporting_card_keys))):
2659
+ raise ValueError("supporting_card_keys must be unique and canonical")
2660
+ if type(self.effect_predictions) is not tuple or any(
2661
+ type(value) is not MetricEffectPrediction
2662
+ for value in self.effect_predictions
2663
+ ):
2664
+ raise TypeError(
2665
+ "effect_predictions must contain exact MetricEffectPrediction values"
2666
+ )
2667
+ for prediction in self.effect_predictions:
2668
+ MetricEffectPrediction.__post_init__(prediction)
2669
+ metric_ids = tuple(value.metric_id for value in self.effect_predictions)
2670
+ if metric_ids != tuple(sorted(set(metric_ids))):
2671
+ raise ValueError("effect_predictions must be unique and metric-ordered")
2672
+ if (
2673
+ type(self.design_rationale) is not str
2674
+ or not self.design_rationale.strip()
2675
+ or self.design_rationale != self.design_rationale.strip()
2676
+ ):
2677
+ raise ValueError("design_rationale must be canonical non-empty text")
2678
+
2679
+
2680
+ @dataclass(frozen=True, slots=True)
2681
+ class RankedPortfolioMember:
2682
+ """One trusted ranked action resolved against the sealed finite contract."""
2683
+
2684
+ rank: int
2685
+ option_id: str
2686
+ option_identity_sha256: str
2687
+ child_configuration_sha256: str
2688
+ family: str
2689
+ supporting_card_keys: tuple[str, ...]
2690
+ effect_predictions: tuple[MetricEffectPrediction, ...]
2691
+ design_rationale: str
2692
+
2693
+ def __post_init__(self) -> None:
2694
+ if type(self.rank) is not int or self.rank <= 0:
2695
+ raise ValueError("rank must be a positive exact integer")
2696
+ PortfolioMemberDraft(
2697
+ option_id=self.option_id,
2698
+ supporting_card_keys=self.supporting_card_keys,
2699
+ effect_predictions=self.effect_predictions,
2700
+ design_rationale=self.design_rationale,
2701
+ )
2702
+ require_sha256(self.option_identity_sha256, "option_identity_sha256")
2703
+ require_sha256(
2704
+ self.child_configuration_sha256,
2705
+ "child_configuration_sha256",
2706
+ )
2707
+ if type(self.family) is not str or _TOKEN.fullmatch(self.family) is None:
2708
+ raise ValueError("family must use the closed lowercase token grammar")
2709
+
2710
+ def to_record(self) -> dict[str, object]:
2711
+ self.__post_init__()
2712
+ return {
2713
+ "rank": self.rank,
2714
+ "option_id": self.option_id,
2715
+ "option_identity_sha256": self.option_identity_sha256,
2716
+ "child_configuration_sha256": self.child_configuration_sha256,
2717
+ "family": self.family,
2718
+ "supporting_card_keys": list(self.supporting_card_keys),
2719
+ "effect_predictions": [
2720
+ {
2721
+ "metric_id": prediction.metric_id,
2722
+ "direction": prediction.direction.value,
2723
+ }
2724
+ for prediction in self.effect_predictions
2725
+ ],
2726
+ "design_rationale_sha256": hashlib.sha256(
2727
+ self.design_rationale.encode("utf-8", errors="strict")
2728
+ ).hexdigest(),
2729
+ }
2730
+
2731
+ def to_audit_record(self) -> dict[str, object]:
2732
+ """Return the committed member record with its rationale plaintext.
2733
+
2734
+ ``to_record`` remains the stable hash boundary: the rationale is bound
2735
+ there by digest only. This projection makes model-authored reasoning
2736
+ available to trace analysis while retaining that committed digest for
2737
+ direct verification.
2738
+ """
2739
+
2740
+ return {**self.to_record(), "design_rationale": self.design_rationale}
2741
+
2742
+
2743
+ @dataclass(frozen=True, slots=True, eq=False)
2744
+ class RankedPortfolioDecision:
2745
+ """All-or-nothing ranked selection bound to one exact request snapshot."""
2746
+
2747
+ request_sha256: str
2748
+ context_sha256: str
2749
+ finite_contract_identity_sha256: str
2750
+ card_snapshot_sha256: str
2751
+ members: tuple[RankedPortfolioMember, ...]
2752
+ policy_id: str
2753
+ policy_version: int
2754
+ policy_definition_sha256: str
2755
+ memory_dose_assessment: PortfolioMemoryDoseAssessment | None = None
2756
+
2757
+ def __post_init__(self) -> None:
2758
+ for name in (
2759
+ "request_sha256",
2760
+ "context_sha256",
2761
+ "finite_contract_identity_sha256",
2762
+ "card_snapshot_sha256",
2763
+ "policy_definition_sha256",
2764
+ ):
2765
+ require_sha256(getattr(self, name), name)
2766
+ if type(self.members) is not tuple or not self.members:
2767
+ raise ValueError("members must be a non-empty exact tuple")
2768
+ if any(type(member) is not RankedPortfolioMember for member in self.members):
2769
+ raise TypeError("members must contain exact RankedPortfolioMember values")
2770
+ for member in self.members:
2771
+ member.__post_init__()
2772
+ if tuple(member.rank for member in self.members) != tuple(
2773
+ range(1, len(self.members) + 1)
2774
+ ):
2775
+ raise ValueError("member ranks must be contiguous and tuple-ordered")
2776
+ option_ids = tuple(member.option_id for member in self.members)
2777
+ if len(set(option_ids)) != len(option_ids):
2778
+ raise ValueError("a ranked portfolio cannot repeat an option")
2779
+ if type(self.policy_id) is not str or _TOKEN.fullmatch(self.policy_id) is None:
2780
+ raise ValueError("policy_id must use the closed lowercase token grammar")
2781
+ if type(self.policy_version) is not int or self.policy_version <= 0:
2782
+ raise ValueError("policy_version must be a positive exact integer")
2783
+ if self.memory_dose_assessment is not None:
2784
+ if type(self.memory_dose_assessment) is not (PortfolioMemoryDoseAssessment):
2785
+ raise TypeError("memory_dose_assessment must be exact or None")
2786
+ self.memory_dose_assessment.__post_init__()
2787
+ if (
2788
+ self.memory_dose_assessment.stage
2789
+ is not PortfolioMemoryDoseStage.EVALUATED_PORTFOLIO
2790
+ or not self.memory_dose_assessment.passed
2791
+ ):
2792
+ raise ValueError(
2793
+ "ranked decision memory dose must be a passing evaluation"
2794
+ )
2795
+
2796
+ def _unsigned_record(self) -> dict[str, object]:
2797
+ self.__post_init__()
2798
+ return {
2799
+ "schema_version": 1,
2800
+ "request_sha256": self.request_sha256,
2801
+ "context_sha256": self.context_sha256,
2802
+ "finite_contract_identity_sha256": (self.finite_contract_identity_sha256),
2803
+ "card_snapshot_sha256": self.card_snapshot_sha256,
2804
+ "members": [member.to_record() for member in self.members],
2805
+ "policy_id": self.policy_id,
2806
+ "policy_version": self.policy_version,
2807
+ "policy_definition_sha256": self.policy_definition_sha256,
2808
+ **(
2809
+ {}
2810
+ if self.memory_dose_assessment is None
2811
+ else {
2812
+ "memory_dose_assessment": (self.memory_dose_assessment.to_record())
2813
+ }
2814
+ ),
2815
+ }
2816
+
2817
+ @property
2818
+ def decision_sha256(self) -> str:
2819
+ return _hash(_DECISION_DOMAIN, self._unsigned_record())
2820
+
2821
+ def to_record(self) -> dict[str, object]:
2822
+ record = self._unsigned_record()
2823
+ return {**record, "decision_sha256": self.decision_sha256}
2824
+
2825
+ def to_audit_record(self) -> dict[str, object]:
2826
+ """Return the decision commitment plus auditable member plaintext."""
2827
+
2828
+ record = self.to_record()
2829
+ return {
2830
+ **record,
2831
+ "members": [member.to_audit_record() for member in self.members],
2832
+ }
2833
+
2834
+ def __eq__(self, other: object) -> bool:
2835
+ return (
2836
+ type(self) is RankedPortfolioDecision
2837
+ and type(other) is RankedPortfolioDecision
2838
+ and self.decision_sha256 == other.decision_sha256
2839
+ )
2840
+
2841
+ __hash__ = None
2842
+
2843
+
2844
+ def resolve_ranked_portfolio_decision(
2845
+ request: PortfolioSelectionRequest,
2846
+ drafts: tuple[PortfolioMemberDraft, ...],
2847
+ *,
2848
+ policy_id: str,
2849
+ policy_version: int,
2850
+ policy_definition_sha256: str,
2851
+ memory_dose_assessment: PortfolioMemoryDoseAssessment | None = None,
2852
+ ) -> RankedPortfolioDecision:
2853
+ """Resolve the complete structured output or publish no decision."""
2854
+
2855
+ if type(request) is not PortfolioSelectionRequest:
2856
+ raise TypeError("request must be an exact PortfolioSelectionRequest")
2857
+ request.__post_init__()
2858
+ if type(drafts) is not tuple or any(
2859
+ type(draft) is not PortfolioMemberDraft for draft in drafts
2860
+ ):
2861
+ raise TypeError("drafts must be an exact tuple of PortfolioMemberDraft values")
2862
+ if len(drafts) != request.portfolio_size:
2863
+ raise ValueError(
2864
+ "selector output does not contain exactly portfolio_size members"
2865
+ )
2866
+ for draft in drafts:
2867
+ draft.__post_init__()
2868
+ option_ids = tuple(draft.option_id for draft in drafts)
2869
+ if len(set(option_ids)) != len(option_ids):
2870
+ raise ValueError("selector output repeats a finite option")
2871
+ card_keys = {card.card_key for card in request.cards}
2872
+ for draft in drafts:
2873
+ if not set(draft.supporting_card_keys).issubset(card_keys):
2874
+ raise ValueError(
2875
+ "selector output cites a card outside the request snapshot"
2876
+ )
2877
+ if request.require_supporting_cards and not draft.supporting_card_keys:
2878
+ raise ValueError("every portfolio member must cite a supporting card")
2879
+ if tuple(value.metric_id for value in draft.effect_predictions) != (
2880
+ request.required_metric_ids
2881
+ ):
2882
+ raise ValueError(
2883
+ "every portfolio member must predict the exact required metrics"
2884
+ )
2885
+
2886
+ contract = request.finite_variation_contract
2887
+ options = tuple(contract.resolve(draft.option_id) for draft in drafts)
2888
+ if (
2889
+ request.min_distinct_families is not None
2890
+ and len({option.family for option in options}) < request.min_distinct_families
2891
+ ):
2892
+ raise ValueError("selector output violates min_distinct_families")
2893
+ if request.require_pairwise_disjoint_parent_patches:
2894
+ validate_pairwise_disjoint_parent_patch_selection(
2895
+ contract,
2896
+ option_ids,
2897
+ )
2898
+ decision = RankedPortfolioDecision(
2899
+ request_sha256=request.request_sha256,
2900
+ context_sha256=request.context_sha256,
2901
+ finite_contract_identity_sha256=contract.identity_sha256,
2902
+ card_snapshot_sha256=request.card_snapshot_sha256,
2903
+ members=tuple(
2904
+ RankedPortfolioMember(
2905
+ rank=index,
2906
+ option_id=option.option_id,
2907
+ option_identity_sha256=option.identity_sha256,
2908
+ child_configuration_sha256=option.child_configuration_sha256,
2909
+ family=option.family,
2910
+ supporting_card_keys=draft.supporting_card_keys,
2911
+ effect_predictions=draft.effect_predictions,
2912
+ design_rationale=draft.design_rationale,
2913
+ )
2914
+ for index, (draft, option) in enumerate(
2915
+ zip(drafts, options, strict=True),
2916
+ start=1,
2917
+ )
2918
+ ),
2919
+ policy_id=policy_id,
2920
+ policy_version=policy_version,
2921
+ policy_definition_sha256=policy_definition_sha256,
2922
+ memory_dose_assessment=memory_dose_assessment,
2923
+ )
2924
+ validate_ranked_portfolio_decision(request, decision)
2925
+ return decision
2926
+
2927
+
2928
+ def validate_ranked_portfolio_decision(
2929
+ request: PortfolioSelectionRequest,
2930
+ decision: RankedPortfolioDecision,
2931
+ ) -> None:
2932
+ """Revalidate a decision against the exact request at any trust boundary."""
2933
+
2934
+ if type(request) is not PortfolioSelectionRequest:
2935
+ raise TypeError("request must be an exact PortfolioSelectionRequest")
2936
+ request.__post_init__()
2937
+ if type(decision) is not RankedPortfolioDecision:
2938
+ raise TypeError("decision must be an exact RankedPortfolioDecision")
2939
+ decision.__post_init__()
2940
+ if (
2941
+ decision.request_sha256 != request.request_sha256
2942
+ or decision.context_sha256 != request.context_sha256
2943
+ or decision.finite_contract_identity_sha256
2944
+ != request.finite_variation_contract.identity_sha256
2945
+ or decision.card_snapshot_sha256 != request.card_snapshot_sha256
2946
+ ):
2947
+ raise ValueError("decision is bound to a different request snapshot")
2948
+ if len(decision.members) != request.portfolio_size:
2949
+ raise ValueError("decision member count differs from portfolio_size")
2950
+ cards = {card.card_key for card in request.cards}
2951
+ option_families: set[str] = set()
2952
+ for member in decision.members:
2953
+ option = request.finite_variation_contract.resolve(member.option_id)
2954
+ if (
2955
+ member.option_identity_sha256 != option.identity_sha256
2956
+ or member.child_configuration_sha256 != option.child_configuration_sha256
2957
+ or member.family != option.family
2958
+ ):
2959
+ raise ValueError("decision member differs from its sealed finite option")
2960
+ if not set(member.supporting_card_keys).issubset(cards):
2961
+ raise ValueError("decision member cites a foreign card")
2962
+ if request.require_supporting_cards and not member.supporting_card_keys:
2963
+ raise ValueError("decision member omitted required card attribution")
2964
+ if (
2965
+ tuple(prediction.metric_id for prediction in member.effect_predictions)
2966
+ != request.required_metric_ids
2967
+ ):
2968
+ raise ValueError("decision member metric predictions differ from request")
2969
+ option_families.add(option.family)
2970
+ dose = request.memory_dose_contract
2971
+ assessment = decision.memory_dose_assessment
2972
+ if dose is None:
2973
+ if assessment is not None:
2974
+ raise ValueError("decision supplied memory dose for an unbounded request")
2975
+ else:
2976
+ if assessment is None:
2977
+ raise ValueError(
2978
+ "bounded request requires an evaluated memory-dose receipt"
2979
+ )
2980
+ assessment.__post_init__()
2981
+ if (
2982
+ assessment.contract_sha256 != dose.contract_sha256
2983
+ or assessment.stage is not PortfolioMemoryDoseStage.EVALUATED_PORTFOLIO
2984
+ or not assessment.passed
2985
+ ):
2986
+ raise ValueError(
2987
+ "decision memory-dose receipt differs from the request contract"
2988
+ )
2989
+ expected_members = tuple(
2990
+ PortfolioMemoryDoseMember(
2991
+ rank=value.rank,
2992
+ option_id=value.option_id,
2993
+ option_identity_sha256=value.option_identity_sha256,
2994
+ supporting_card_keys=value.supporting_card_keys,
2995
+ )
2996
+ for value in decision.members
2997
+ )
2998
+ if assessment.member_content_binding_sha256s != tuple(
2999
+ value.content_binding_sha256 for value in expected_members
3000
+ ):
3001
+ raise ValueError(
3002
+ "decision memory-dose receipt differs from selected members"
3003
+ )
3004
+ if request.min_distinct_families is not None and len(option_families) < (
3005
+ request.min_distinct_families
3006
+ ):
3007
+ raise ValueError("decision violates min_distinct_families")
3008
+ if request.require_pairwise_disjoint_parent_patches:
3009
+ validate_pairwise_disjoint_parent_patch_selection(
3010
+ request.finite_variation_contract,
3011
+ tuple(member.option_id for member in decision.members),
3012
+ )
3013
+
3014
+
3015
+ @dataclass(frozen=True, slots=True, eq=False)
3016
+ class PortfolioSelectionSupplementalAudit:
3017
+ """Opaque, integrity-bound selector evidence carried beside a v1 decision.
3018
+
3019
+ The ranked decision and materialization receipt remain the stable evaluator
3020
+ boundary. Optional selectors may retain richer policy-specific evidence in
3021
+ ``payload`` without making the application layer depend on that policy.
3022
+ """
3023
+
3024
+ audit_kind: str
3025
+ request_sha256: str
3026
+ decision_sha256: str
3027
+ payload: FrozenJsonObject
3028
+
3029
+ def __post_init__(self) -> None:
3030
+ if (
3031
+ type(self.audit_kind) is not str
3032
+ or _TOKEN.fullmatch(self.audit_kind) is None
3033
+ ):
3034
+ raise ValueError("audit_kind must use the closed lowercase token grammar")
3035
+ require_sha256(self.request_sha256, "request_sha256")
3036
+ require_sha256(self.decision_sha256, "decision_sha256")
3037
+ if type(self.payload) is not FrozenJsonObject:
3038
+ raise TypeError("payload must be an exact FrozenJsonObject")
3039
+ if freeze_json(self.payload) is not self.payload:
3040
+ raise TypeError("payload must already be frozen typed JSON")
3041
+
3042
+ def _unsigned_record(self) -> dict[str, object]:
3043
+ self.__post_init__()
3044
+ return {
3045
+ "schema_version": 1,
3046
+ "audit_kind": self.audit_kind,
3047
+ "request_sha256": self.request_sha256,
3048
+ "decision_sha256": self.decision_sha256,
3049
+ "payload_sha256": typed_json_sha256(self.payload),
3050
+ }
3051
+
3052
+ @property
3053
+ def audit_sha256(self) -> str:
3054
+ return _hash(_SUPPLEMENTAL_AUDIT_DOMAIN, self._unsigned_record())
3055
+
3056
+ def to_record(self) -> dict[str, object]:
3057
+ return {
3058
+ **self._unsigned_record(),
3059
+ "payload": thaw_json(self.payload),
3060
+ "audit_sha256": self.audit_sha256,
3061
+ }
3062
+
3063
+ def __eq__(self, other: object) -> bool:
3064
+ return (
3065
+ type(other) is PortfolioSelectionSupplementalAudit
3066
+ and self.audit_sha256 == other.audit_sha256
3067
+ )
3068
+
3069
+ __hash__ = None
3070
+
3071
+
3072
+ @dataclass(frozen=True, slots=True)
3073
+ class PortfolioSelectionResult:
3074
+ """One selection outcome and the provider evidence that produced it.
3075
+
3076
+ ``telemetry`` and ``provider_free`` are mutually exclusive and exactly one
3077
+ must be present. A bare ``None`` telemetry with no assertion is rejected:
3078
+ absence must be asserted and then measured, never inferred from a missing
3079
+ field. A policy that reaches the provider supplies telemetry as before; a
3080
+ policy that does not must say so, and the runtime confirms it against the
3081
+ outbound journals rather than taking the claim on trust.
3082
+ """
3083
+
3084
+ decision: RankedPortfolioDecision
3085
+ telemetry: AgenticCallTelemetry | None
3086
+ supplemental_audit: PortfolioSelectionSupplementalAudit | None = None
3087
+ provider_free: bool = False
3088
+
3089
+ def __post_init__(self) -> None:
3090
+ if type(self.decision) is not RankedPortfolioDecision:
3091
+ raise TypeError("decision must be an exact RankedPortfolioDecision")
3092
+ self.decision.__post_init__()
3093
+ if type(self.provider_free) is not bool:
3094
+ raise TypeError("provider_free must be an exact bool")
3095
+ if self.provider_free and self.telemetry is not None:
3096
+ raise ValueError(
3097
+ "a provider-free selection cannot also carry call telemetry"
3098
+ )
3099
+ if not self.provider_free and self.telemetry is None:
3100
+ raise ValueError(
3101
+ "selection telemetry is absent and provider_free was not "
3102
+ "asserted; absence must be asserted, not inferred"
3103
+ )
3104
+ if self.telemetry is not None:
3105
+ if type(self.telemetry) is not AgenticCallTelemetry:
3106
+ raise TypeError("telemetry must be exact or None")
3107
+ AgenticCallTelemetry.__post_init__(self.telemetry)
3108
+ if self.supplemental_audit is not None:
3109
+ if type(self.supplemental_audit) is not PortfolioSelectionSupplementalAudit:
3110
+ raise TypeError("supplemental_audit must be exact or None")
3111
+ self.supplemental_audit.__post_init__()
3112
+ if (
3113
+ self.supplemental_audit.request_sha256 != self.decision.request_sha256
3114
+ or self.supplemental_audit.decision_sha256
3115
+ != self.decision.decision_sha256
3116
+ ):
3117
+ raise ValueError(
3118
+ "supplemental audit differs from the ranked decision identity"
3119
+ )
3120
+
3121
+
3122
+ @runtime_checkable
3123
+ class PortfolioSelectionPolicy(Protocol):
3124
+ """Select one complete ranked portfolio; partial results are not representable."""
3125
+
3126
+ async def select(
3127
+ self,
3128
+ request: PortfolioSelectionRequest,
3129
+ ) -> PortfolioSelectionResult: ...
3130
+
3131
+
3132
+ __all__ = [
3133
+ "CANONICAL_NEUTRAL_PORTFOLIO_PROMPT_PAYLOAD",
3134
+ "CANONICAL_REDACTED_PORTFOLIO_EVIDENCE_SHA256",
3135
+ "CardScoreComponent",
3136
+ "CardTransferAdjudicationRequest",
3137
+ "CardTransferAdjudicator",
3138
+ "CardTransferScoreReceipt",
3139
+ "PortfolioCard",
3140
+ "PortfolioCardPromptPayload",
3141
+ "PortfolioCardSourceBinding",
3142
+ "PortfolioCardSourceRegistry",
3143
+ "PortfolioCardViewReceipt",
3144
+ "PortfolioCardViewTransform",
3145
+ "PortfolioExperimentalArm",
3146
+ "PortfolioExperimentalViewReceipt",
3147
+ "PortfolioMemberDraft",
3148
+ "PortfolioSelectionPolicy",
3149
+ "PortfolioSelectionRequest",
3150
+ "PortfolioSelectionResult",
3151
+ "PortfolioSelectionSupplementalAudit",
3152
+ "RankedPortfolioDecision",
3153
+ "RankedPortfolioMember",
3154
+ "derive_portfolio_card_view",
3155
+ "portfolio_card_action_evidence_sha256",
3156
+ "portfolio_card_score_state_sha256",
3157
+ "portfolio_card_snapshot_sha256",
3158
+ "finite_portfolio_has_pairwise_disjoint_parent_patches",
3159
+ "finite_option_ids_have_pairwise_disjoint_parent_patch_subset",
3160
+ "pairwise_disjoint_parent_patch_witness",
3161
+ "pairwise_disjoint_parent_patch_pairs",
3162
+ "single_path_parent_patch_option_ids",
3163
+ "project_family_exposure_bounds_to_pairwise_disjoint_feasibility",
3164
+ "resolve_ranked_portfolio_decision",
3165
+ "validate_card_transfer_score_receipt",
3166
+ "validate_portfolio_experimental_view",
3167
+ "validate_pairwise_disjoint_parent_patch_selection",
3168
+ "validate_ranked_portfolio_decision",
3169
+ ]