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.
- agent_evolve/__init__.py +722 -0
- agent_evolve/agentic.py +2800 -0
- agent_evolve/api.py +767 -0
- agent_evolve/application/__init__.py +1580 -0
- agent_evolve/application/action_allocation.py +744 -0
- agent_evolve/application/action_allocation_frame.py +347 -0
- agent_evolve/application/action_allocation_frame_commit.py +185 -0
- agent_evolve/application/action_allocation_frame_commit_v3.py +184 -0
- agent_evolve/application/action_allocation_frame_v3.py +338 -0
- agent_evolve/application/action_archive_value.py +497 -0
- agent_evolve/application/action_evidence_consistency.py +455 -0
- agent_evolve/application/action_forecast_partitioning.py +1471 -0
- agent_evolve/application/action_metric_projection.py +211 -0
- agent_evolve/application/action_role_value.py +680 -0
- agent_evolve/application/action_score_authorities.py +363 -0
- agent_evolve/application/action_structural_signature.py +116 -0
- agent_evolve/application/action_target_realization.py +402 -0
- agent_evolve/application/agentic_evolution.py +7734 -0
- agent_evolve/application/agentic_portfolio_residual_expert.py +835 -0
- agent_evolve/application/anchor_residual_identification.py +463 -0
- agent_evolve/application/archive_conditioned_action_target.py +208 -0
- agent_evolve/application/artifact_journal.py +246 -0
- agent_evolve/application/artifact_replay.py +347 -0
- agent_evolve/application/budgeted_optimizer.py +1828 -0
- agent_evolve/application/calibrated_campaign.py +485 -0
- agent_evolve/application/calibrated_current_prefix_forecast_opportunity.py +322 -0
- agent_evolve/application/calibrated_positive_gain_opportunity.py +1581 -0
- agent_evolve/application/campaign_capacity_recourse.py +254 -0
- agent_evolve/application/campaign_contextual_outcomes.py +119 -0
- agent_evolve/application/campaign_diagnostic_blocks.py +930 -0
- agent_evolve/application/campaign_evidence_registry.py +262 -0
- agent_evolve/application/campaign_execution.py +2537 -0
- agent_evolve/application/campaign_generation_audit.py +942 -0
- agent_evolve/application/campaign_learning.py +1812 -0
- agent_evolve/application/campaign_learning_runtime.py +1977 -0
- agent_evolve/application/campaign_search_phase.py +227 -0
- agent_evolve/application/campaign_selector_context_extension.py +220 -0
- agent_evolve/application/campaign_variation_envelope.py +649 -0
- agent_evolve/application/campaign_variation_trace.py +451 -0
- agent_evolve/application/candidate_archive_consequence.py +128 -0
- agent_evolve/application/causal_opportunity_portfolio_gate.py +385 -0
- agent_evolve/application/composite_outcome_updater.py +145 -0
- agent_evolve/application/composition_portfolio_selection.py +363 -0
- agent_evolve/application/concurrent_stage.py +144 -0
- agent_evolve/application/contextual_action_allocation.py +181 -0
- agent_evolve/application/contextual_campaign_outcomes.py +267 -0
- agent_evolve/application/contextual_campaign_planning.py +1366 -0
- agent_evolve/application/contextual_delayed_credit.py +651 -0
- agent_evolve/application/contextual_search_controller.py +2374 -0
- agent_evolve/application/current_prefix_forecast_opportunity.py +714 -0
- agent_evolve/application/decision_metric_projection.py +112 -0
- agent_evolve/application/derived_action_semantics.py +129 -0
- agent_evolve/application/detailed_evaluation.py +449 -0
- agent_evolve/application/earned_lineage.py +1011 -0
- agent_evolve/application/effective_choice_audit.py +484 -0
- agent_evolve/application/empirical_consequence_calibration.py +908 -0
- agent_evolve/application/evaluation_accounting.py +325 -0
- agent_evolve/application/evaluation_cache.py +199 -0
- agent_evolve/application/evaluation_escrow.py +547 -0
- agent_evolve/application/evaluation_recourse.py +253 -0
- agent_evolve/application/event_recorder.py +151 -0
- agent_evolve/application/evolution_campaign.py +1840 -0
- agent_evolve/application/executable_hypothesis.py +323 -0
- agent_evolve/application/factorial_branch_pilot.py +772 -0
- agent_evolve/application/finite_acquisition_capacity_recourse.py +672 -0
- agent_evolve/application/finite_acquisition_residual_expert.py +373 -0
- agent_evolve/application/finite_acquisition_variation_envelope.py +802 -0
- agent_evolve/application/finite_action_hypothesis_semantics.py +446 -0
- agent_evolve/application/finite_action_selection.py +188 -0
- agent_evolve/application/finite_action_set.py +306 -0
- agent_evolve/application/finite_action_transition.py +537 -0
- agent_evolve/application/finite_variation_eligibility.py +296 -0
- agent_evolve/application/forecast_geometry_portfolio.py +799 -0
- agent_evolve/application/forecast_opportunity_shadow_calibration.py +316 -0
- agent_evolve/application/front_proximity_admission.py +311 -0
- agent_evolve/application/front_proximity_parent_basis.py +458 -0
- agent_evolve/application/frozen_hurdle_score.py +659 -0
- agent_evolve/application/g3_causal_screen.py +2257 -0
- agent_evolve/application/g3_causal_validation.py +1046 -0
- agent_evolve/application/g3_postseal_curation.py +818 -0
- agent_evolve/application/gated_agentic_generator.py +205 -0
- agent_evolve/application/generation_feedback.py +293 -0
- agent_evolve/application/generative_proposal_journal.py +185 -0
- agent_evolve/application/geometry_conditional_elasticity.py +453 -0
- agent_evolve/application/global_wave_action_allocation.py +1151 -0
- agent_evolve/application/head_mass_conditional_seat.py +268 -0
- agent_evolve/application/identifiable_reflection_evidence.py +1147 -0
- agent_evolve/application/identifiable_reflection_learning.py +395 -0
- agent_evolve/application/identifiable_reflection_request.py +364 -0
- agent_evolve/application/in_memory_residual_archive.py +341 -0
- agent_evolve/application/insight_memory.py +1804 -0
- agent_evolve/application/live_runtime_manifest.py +758 -0
- agent_evolve/application/llm_task_queue.py +769 -0
- agent_evolve/application/matched_finite_action_block.py +409 -0
- agent_evolve/application/materialized_action_broker.py +2328 -0
- agent_evolve/application/materialized_action_constraints.py +83 -0
- agent_evolve/application/materialized_variation.py +211 -0
- agent_evolve/application/multi_option_evolution.py +1536 -0
- agent_evolve/application/outcome_adaptive_action_racing.py +2827 -0
- agent_evolve/application/outcome_adaptive_residual_campaign_runtime.py +580 -0
- agent_evolve/application/outcome_adaptive_residual_portfolio_evolution.py +3671 -0
- agent_evolve/application/outcome_conditioned_portfolio_selection.py +1374 -0
- agent_evolve/application/outcome_relation.py +193 -0
- agent_evolve/application/paired_allocation_comparison.py +241 -0
- agent_evolve/application/paired_block_schedule.py +127 -0
- agent_evolve/application/parent_measurement.py +226 -0
- agent_evolve/application/pareto_archive.py +811 -0
- agent_evolve/application/portfolio_campaign_runtime.py +4739 -0
- agent_evolve/application/portfolio_evolution.py +2950 -0
- agent_evolve/application/portfolio_hypothesis_observations.py +814 -0
- agent_evolve/application/portfolio_memory_attribution.py +581 -0
- agent_evolve/application/portfolio_memory_dose.py +788 -0
- agent_evolve/application/portfolio_memory_matched_control.py +938 -0
- agent_evolve/application/portfolio_memory_transfer.py +297 -0
- agent_evolve/application/portfolio_optimization_memory.py +363 -0
- agent_evolve/application/portfolio_outcome_feedback.py +1613 -0
- agent_evolve/application/portfolio_projection.py +335 -0
- agent_evolve/application/portfolio_recombination.py +2032 -0
- agent_evolve/application/post_evolution_reflection.py +834 -0
- agent_evolve/application/postcommit_rank_authority.py +245 -0
- agent_evolve/application/precommitted_portfolio_racing.py +2762 -0
- agent_evolve/application/prequential_archive_opportunity_calibration.py +1154 -0
- agent_evolve/application/prequential_residual_exploration.py +343 -0
- agent_evolve/application/prequential_score_portfolio.py +954 -0
- agent_evolve/application/projections.py +292 -0
- agent_evolve/application/protected_action_committee.py +1027 -0
- agent_evolve/application/protected_branch_pilot.py +376 -0
- agent_evolve/application/protected_current_prefix_forecast_opportunity.py +552 -0
- agent_evolve/application/provider_replay.py +910 -0
- agent_evolve/application/rank_balanced_causal_pilot.py +1372 -0
- agent_evolve/application/recombination_residual_expert.py +403 -0
- agent_evolve/application/reflection_workflow.py +571 -0
- agent_evolve/application/region_conditional_credit.py +911 -0
- agent_evolve/application/residual_campaign_runtime.py +531 -0
- agent_evolve/application/residual_headroom_campaign_runtime.py +459 -0
- agent_evolve/application/residual_headroom_ledger.py +1544 -0
- agent_evolve/application/residual_learning_transaction.py +396 -0
- agent_evolve/application/residual_portfolio_evolution.py +1228 -0
- agent_evolve/application/residual_reachability.py +749 -0
- agent_evolve/application/residual_stage_credit.py +499 -0
- agent_evolve/application/same_prefix_paired_audit.py +1580 -0
- agent_evolve/application/semantic_coverage_score_portfolio.py +838 -0
- agent_evolve/application/sequential_lineage_allocation.py +1017 -0
- agent_evolve/application/sequential_market_replay.py +1395 -0
- agent_evolve/application/sequential_residual_campaign_runtime.py +305 -0
- agent_evolve/application/sequential_residual_portfolio_evolution.py +940 -0
- agent_evolve/application/single_score_action_allocation.py +299 -0
- agent_evolve/application/source_exposure_allocation.py +906 -0
- agent_evolve/application/staged_memory.py +210 -0
- agent_evolve/application/stratified_cold_start_allocation.py +732 -0
- agent_evolve/application/support_guarded_hurdle_score.py +549 -0
- agent_evolve/application/target_conditioned_action_forecast.py +595 -0
- agent_evolve/application/target_conditioned_campaign.py +566 -0
- agent_evolve/application/treatment_assignment.py +201 -0
- agent_evolve/application/trusted_objective_evidence.py +217 -0
- agent_evolve/application/two_stage_action_evolution.py +1131 -0
- agent_evolve/application/v8lite_allocation_policy.py +1083 -0
- agent_evolve/application/v9_candidate_policy.py +1303 -0
- agent_evolve/bootstrap.py +108 -0
- agent_evolve/campaign_presets.py +517 -0
- agent_evolve/campaign_profiles.py +452 -0
- agent_evolve/campaign_variation_topology.py +288 -0
- agent_evolve/campaign_workload.py +950 -0
- agent_evolve/cli.py +797 -0
- agent_evolve/contract.py +241 -0
- agent_evolve/core/__init__.py +91 -0
- agent_evolve/core/action_semantics.py +411 -0
- agent_evolve/core/authored.py +105 -0
- agent_evolve/core/formatting.py +286 -0
- agent_evolve/core/optimization_semantics.py +324 -0
- agent_evolve/core/problem.py +167 -0
- agent_evolve/core/results.py +323 -0
- agent_evolve/core/stats.py +70 -0
- agent_evolve/core/telemetry.py +100 -0
- agent_evolve/domain/__init__.py +89 -0
- agent_evolve/domain/artifact.py +162 -0
- agent_evolve/domain/durable_text.py +68 -0
- agent_evolve/domain/event.py +1454 -0
- agent_evolve/domain/finite_action_set.py +426 -0
- agent_evolve/domain/finite_variation.py +526 -0
- agent_evolve/domain/generative_emission.py +559 -0
- agent_evolve/domain/ids.py +163 -0
- agent_evolve/domain/inline_text.py +106 -0
- agent_evolve/domain/insight.py +27 -0
- agent_evolve/domain/lineage.py +737 -0
- agent_evolve/domain/llm_task_queue.py +960 -0
- agent_evolve/domain/outcome.py +96 -0
- agent_evolve/domain/patch.py +854 -0
- agent_evolve/domain/typed_json.py +542 -0
- agent_evolve/domain/variation_space.py +158 -0
- agent_evolve/driver.py +1014 -0
- agent_evolve/harness/__init__.py +29 -0
- agent_evolve/harness/base.py +242 -0
- agent_evolve/harness/directives.py +163 -0
- agent_evolve/harness/generative_seal.py +479 -0
- agent_evolve/harness/registry.py +41 -0
- agent_evolve/infrastructure/__init__.py +39 -0
- agent_evolve/infrastructure/artifacts/__init__.py +6 -0
- agent_evolve/infrastructure/artifacts/_verification.py +67 -0
- agent_evolve/infrastructure/artifacts/filesystem.py +343 -0
- agent_evolve/infrastructure/artifacts/in_memory.py +73 -0
- agent_evolve/infrastructure/asyncio_runtime.py +109 -0
- agent_evolve/infrastructure/authored_runtime.py +188 -0
- agent_evolve/infrastructure/authored_worker.py +171 -0
- agent_evolve/infrastructure/clock.py +53 -0
- agent_evolve/infrastructure/events/__init__.py +6 -0
- agent_evolve/infrastructure/events/_validation.py +89 -0
- agent_evolve/infrastructure/events/in_memory.py +56 -0
- agent_evolve/infrastructure/events/jsonl.py +193 -0
- agent_evolve/infrastructure/exception_provenance.py +215 -0
- agent_evolve/infrastructure/ids.py +118 -0
- agent_evolve/infrastructure/lineage_codec.py +1836 -0
- agent_evolve/infrastructure/outcome_adaptive_phase_journal.py +170 -0
- agent_evolve/infrastructure/residual_headroom_journal.py +221 -0
- agent_evolve/infrastructure/resource_lease.py +370 -0
- agent_evolve/infrastructure/sanitization/__init__.py +8 -0
- agent_evolve/infrastructure/sanitization/strict_json.py +484 -0
- agent_evolve/infrastructure/sequential_phase_journal.py +170 -0
- agent_evolve/infrastructure/stream_liveness.py +383 -0
- agent_evolve/infrastructure/subprocess_boundary.py +136 -0
- agent_evolve/integrations/__init__.py +1 -0
- agent_evolve/integrations/botorch/__init__.py +28 -0
- agent_evolve/integrations/botorch/finite_qlognehvi.py +190 -0
- agent_evolve/integrations/botorch/finite_qlognehvi_batch.py +155 -0
- agent_evolve/integrations/botorch/finite_qlognehvi_batch_identity.py +20 -0
- agent_evolve/integrations/botorch/finite_qlognehvi_batch_worker.py +55 -0
- agent_evolve/integrations/botorch/finite_qlognehvi_identity.py +22 -0
- agent_evolve/integrations/botorch/finite_qlognehvi_worker.py +55 -0
- agent_evolve/integrations/botorch/subprocess_qlognehvi.py +261 -0
- agent_evolve/integrations/botorch/subprocess_qlognehvi_batch.py +273 -0
- agent_evolve/integrations/completion.py +242 -0
- agent_evolve/integrations/pydantic_ai/__init__.py +441 -0
- agent_evolve/integrations/pydantic_ai/action_forecast.py +1068 -0
- agent_evolve/integrations/pydantic_ai/agentic_generator.py +2308 -0
- agent_evolve/integrations/pydantic_ai/async_generator.py +1604 -0
- agent_evolve/integrations/pydantic_ai/boundary_codec.py +1526 -0
- agent_evolve/integrations/pydantic_ai/calibrated_portfolio_campaign.py +756 -0
- agent_evolve/integrations/pydantic_ai/calibrated_portfolio_selection.py +7537 -0
- agent_evolve/integrations/pydantic_ai/campaign_acquisition.py +609 -0
- agent_evolve/integrations/pydantic_ai/execution_binding.py +138 -0
- agent_evolve/integrations/pydantic_ai/forecast_geometry_action_committee.py +217 -0
- agent_evolve/integrations/pydantic_ai/harness.py +159 -0
- agent_evolve/integrations/pydantic_ai/heterogeneous_model_execution.py +306 -0
- agent_evolve/integrations/pydantic_ai/hierarchical_residual_adaptive_semantic_view.py +179 -0
- agent_evolve/integrations/pydantic_ai/json_schema_dialect.py +108 -0
- agent_evolve/integrations/pydantic_ai/materialized_hierarchical_residual_expert.py +952 -0
- agent_evolve/integrations/pydantic_ai/materialized_portfolio_judge.py +520 -0
- agent_evolve/integrations/pydantic_ai/model_execution_profile.py +659 -0
- agent_evolve/integrations/pydantic_ai/outbound_request_manifest.py +1170 -0
- agent_evolve/integrations/pydantic_ai/portable_residual_consequence_features.py +575 -0
- agent_evolve/integrations/pydantic_ai/portfolio_selection.py +422 -0
- agent_evolve/integrations/pydantic_ai/progress_aware_openrouter.py +416 -0
- agent_evolve/integrations/pydantic_ai/provider_attempt_join.py +1523 -0
- agent_evolve/integrations/pydantic_ai/provider_free_calibrated_runner.py +607 -0
- agent_evolve/integrations/pydantic_ai/queued_runner.py +2634 -0
- agent_evolve/integrations/pydantic_ai/reconciled_residual_reachability.py +1417 -0
- agent_evolve/integrations/pydantic_ai/residual_forecast_geometry.py +445 -0
- agent_evolve/integrations/pydantic_ai/residual_reachability.py +674 -0
- agent_evolve/integrations/pydantic_ai/residual_semantic_cells.py +239 -0
- agent_evolve/integrations/pydantic_ai/sealed_output_replay.py +1068 -0
- agent_evolve/integrations/pydantic_ai/semantic_coverage_residual_portfolio.py +770 -0
- agent_evolve/integrations/pydantic_ai/semantic_decision_replay.py +383 -0
- agent_evolve/integrations/pydantic_ai/support_adaptive_residual_portfolio.py +135 -0
- agent_evolve/integrations/pydantic_ai/trusted_residual_prompt_context.py +143 -0
- agent_evolve/integrations/pydantic_ai/validated_openrouter_model.py +107 -0
- agent_evolve/integrations/pymoo_adapter.py +242 -0
- agent_evolve/policies/__init__.py +17 -0
- agent_evolve/policies/check.py +469 -0
- agent_evolve/policies/emit_scaffold.py +451 -0
- agent_evolve/policies/feedback/__init__.py +37 -0
- agent_evolve/policies/feedback/held_out_asn.py +1325 -0
- agent_evolve/policies/genetic.py +607 -0
- agent_evolve/policies/llm_backoff.py +183 -0
- agent_evolve/policies/llm_chooser.py +226 -0
- agent_evolve/policies/llm_generator.py +1760 -0
- agent_evolve/policies/llm_init.py +267 -0
- agent_evolve/policies/llm_operator.py +109 -0
- agent_evolve/policies/llm_prior.py +194 -0
- agent_evolve/policies/llm_surrogate.py +334 -0
- agent_evolve/policies/measurement_evidence.py +704 -0
- agent_evolve/policies/memory/__init__.py +223 -0
- agent_evolve/policies/memory/balanced_subset_blocks.py +707 -0
- agent_evolve/policies/memory/compatibility_matching.py +593 -0
- agent_evolve/policies/memory/global_falsification.py +1841 -0
- agent_evolve/policies/memory/prompt_shape.py +503 -0
- agent_evolve/policies/memory/randomized_subset.py +714 -0
- agent_evolve/policies/memory/staged_causal.py +1270 -0
- agent_evolve/policies/memory/treatment_compliance.py +759 -0
- agent_evolve/policies/objective_resolution/__init__.py +17 -0
- agent_evolve/policies/objective_resolution/fixed_grid.py +364 -0
- agent_evolve/policies/operator_portfolio.py +407 -0
- agent_evolve/policies/reguidance.py +1133 -0
- agent_evolve/policies/reward/__init__.py +83 -0
- agent_evolve/policies/reward/affine_candidate_consequence.py +156 -0
- agent_evolve/policies/reward/affine_candidate_consequence_3d.py +159 -0
- agent_evolve/policies/reward/affine_hypervolume.py +490 -0
- agent_evolve/policies/reward/affine_hypervolume_3d.py +567 -0
- agent_evolve/policies/reward/contextual_marginal_utility.py +318 -0
- agent_evolve/policies/reward/frozen_archive.py +360 -0
- agent_evolve/policies/reward/frozen_wave_archive.py +368 -0
- agent_evolve/policies/search_state.py +208 -0
- agent_evolve/policies/selection/__init__.py +345 -0
- agent_evolve/policies/selection/acquisition_certified_slate.py +684 -0
- agent_evolve/policies/selection/affine_frontier_context.py +330 -0
- agent_evolve/policies/selection/affine_frontier_target.py +473 -0
- agent_evolve/policies/selection/archive_elite.py +1346 -0
- agent_evolve/policies/selection/calibrated_portfolio_binding.py +640 -0
- agent_evolve/policies/selection/calibrated_slate.py +1394 -0
- agent_evolve/policies/selection/calibrated_slate_codec.py +579 -0
- agent_evolve/policies/selection/common_candidate_pool.py +685 -0
- agent_evolve/policies/selection/diagnostic_sampling.py +319 -0
- agent_evolve/policies/selection/disjoint_pairs.py +479 -0
- agent_evolve/policies/selection/elite_explorer.py +719 -0
- agent_evolve/policies/selection/finite_action.py +187 -0
- agent_evolve/policies/selection/finite_option_prompt_projection.py +377 -0
- agent_evolve/policies/selection/finite_palette_evidence.py +247 -0
- agent_evolve/policies/selection/forecast_calibration.py +922 -0
- agent_evolve/policies/selection/frontier_probe_slate.py +814 -0
- agent_evolve/policies/selection/frozen_archive_pairs.py +762 -0
- agent_evolve/policies/selection/full_support_slate.py +91 -0
- agent_evolve/policies/selection/meaningful_direction.py +240 -0
- agent_evolve/policies/selection/memory_dose_feasibility.py +259 -0
- agent_evolve/policies/selection/model_anchored_slate.py +826 -0
- agent_evolve/policies/selection/phenotype_recourse.py +979 -0
- agent_evolve/policies/selection/proposal_support.py +368 -0
- agent_evolve/policies/selection/random_portfolio.py +254 -0
- agent_evolve/policies/selection/regret_bounded_slate.py +1084 -0
- agent_evolve/policies/selection/residual_frontier.py +463 -0
- agent_evolve/policies/selection/residual_frontier_target.py +605 -0
- agent_evolve/policies/selection/structural_posterior_slate.py +1571 -0
- agent_evolve/policies/selection/target_conditioned_allocator.py +648 -0
- agent_evolve/policies/selection/target_conditioned_features.py +812 -0
- agent_evolve/policies/selection/target_conditioned_prequential.py +1527 -0
- agent_evolve/policies/selection/task_keyed_palette.py +906 -0
- agent_evolve/policies/semantics.py +147 -0
- agent_evolve/policies/structure.py +362 -0
- agent_evolve/policies/structured_output_budget.py +62 -0
- agent_evolve/policies/surrogate.py +696 -0
- agent_evolve/policies/variation/__init__.py +1 -0
- agent_evolve/policies/variation/compositional_finite_catalog.py +426 -0
- agent_evolve/policies/variation/crossover_inheritance.py +575 -0
- agent_evolve/policies/variation/disjoint_recombination.py +611 -0
- agent_evolve/policies/variation/exact_composition_capacity.py +214 -0
- agent_evolve/policies/variation/exact_parent_crossover.py +950 -0
- agent_evolve/policies/variation/multiscale_restart_catalog.py +372 -0
- agent_evolve/policies/variation/source_union_finite_catalog.py +403 -0
- agent_evolve/policies/variation/typed_patch.py +1981 -0
- agent_evolve/policies/weighted_prior.py +394 -0
- agent_evolve/ports/__init__.py +383 -0
- agent_evolve/ports/action_allocation.py +733 -0
- agent_evolve/ports/action_allocation_frame.py +1153 -0
- agent_evolve/ports/action_allocation_frame_commit.py +294 -0
- agent_evolve/ports/action_allocation_frame_commit_v3.py +432 -0
- agent_evolve/ports/action_allocation_frame_v3.py +995 -0
- agent_evolve/ports/action_forecast.py +1568 -0
- agent_evolve/ports/action_metric_projection.py +165 -0
- agent_evolve/ports/agentic_generator.py +1561 -0
- agent_evolve/ports/archive_context.py +136 -0
- agent_evolve/ports/artifact_sanitizer.py +44 -0
- agent_evolve/ports/artifact_store.py +225 -0
- agent_evolve/ports/clock.py +13 -0
- agent_evolve/ports/contextual_search_allocation.py +827 -0
- agent_evolve/ports/decision_metric_projection.py +258 -0
- agent_evolve/ports/event_store.py +55 -0
- agent_evolve/ports/executable_hypothesis.py +557 -0
- agent_evolve/ports/finite_acquisition.py +377 -0
- agent_evolve/ports/finite_acquisition_batch.py +296 -0
- agent_evolve/ports/finite_acquisition_batch_json.py +164 -0
- agent_evolve/ports/finite_acquisition_json.py +247 -0
- agent_evolve/ports/finite_acquisition_space.py +168 -0
- agent_evolve/ports/finite_action_selection.py +348 -0
- agent_evolve/ports/finite_action_set.py +256 -0
- agent_evolve/ports/frontier_target.py +396 -0
- agent_evolve/ports/generation_failure.py +43 -0
- agent_evolve/ports/hard_feasibility.py +233 -0
- agent_evolve/ports/id_factory.py +34 -0
- agent_evolve/ports/llm_task_queue.py +93 -0
- agent_evolve/ports/objective_resolution.py +419 -0
- agent_evolve/ports/paired_allocation_comparison.py +401 -0
- agent_evolve/ports/paired_block_schedule.py +475 -0
- agent_evolve/ports/parent_measurement.py +336 -0
- agent_evolve/ports/portfolio_memory_dose.py +643 -0
- agent_evolve/ports/portfolio_selection.py +3169 -0
- agent_evolve/ports/postcommit_rank_authority.py +467 -0
- agent_evolve/ports/presented_action_evidence.py +794 -0
- agent_evolve/ports/resource_lease.py +162 -0
- agent_evolve/ports/structured_generator.py +734 -0
- agent_evolve/ports/structured_output_budget.py +120 -0
- agent_evolve/ports/subprocess_boundary.py +138 -0
- agent_evolve/ports/treatment_assignment.py +466 -0
- agent_evolve/ports/variation_catalog.py +76 -0
- agent_evolve/ports/variation_source.py +226 -0
- agent_evolve/proposal_mode.py +157 -0
- agent_evolve/proposers/__init__.py +10 -0
- agent_evolve/proposers/random_proposer.py +188 -0
- agent_evolve/provider_accounting.py +163 -0
- agent_evolve/py.typed +0 -0
- agent_evolve/reference_method.py +1570 -0
- agent_evolve/session/__init__.py +11 -0
- agent_evolve/session/authorship.py +864 -0
- agent_evolve/session/evaluate.py +236 -0
- agent_evolve/session/fidelity.py +237 -0
- agent_evolve/session/genetic_loop.py +742 -0
- agent_evolve/session/loop.py +803 -0
- agent_evolve/session/screening.py +671 -0
- agent_evolve/settings.py +376 -0
- agent_evolve/workload_kit.py +368 -0
- agent_evolve/workload_prompt.py +398 -0
- agentevolve_optimizer-0.5.0.dist-info/METADATA +599 -0
- agentevolve_optimizer-0.5.0.dist-info/RECORD +414 -0
- agentevolve_optimizer-0.5.0.dist-info/WHEEL +5 -0
- agentevolve_optimizer-0.5.0.dist-info/entry_points.txt +2 -0
- agentevolve_optimizer-0.5.0.dist-info/licenses/LICENSE +21 -0
- agentevolve_optimizer-0.5.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1346 @@
|
|
|
1
|
+
"""Authenticated, benchmark-neutral parent selection for evolution planners.
|
|
2
|
+
|
|
3
|
+
The strict elite policy rotates only over the archive's exact current front.
|
|
4
|
+
The singleton-safe reservoir policy replays archive admissibility over complete
|
|
5
|
+
candidate history and ranks it from declared objective directions, without any
|
|
6
|
+
benchmark branches. Both produce deterministic, snapshot-bound receipts.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import hashlib
|
|
12
|
+
import json
|
|
13
|
+
import math
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from enum import Enum
|
|
16
|
+
from typing import Protocol
|
|
17
|
+
|
|
18
|
+
from agent_evolve.application.agentic_evolution import EvolutionCandidate
|
|
19
|
+
from agent_evolve.application.budgeted_optimizer import OptimizerState
|
|
20
|
+
from agent_evolve.application.pareto_archive import (
|
|
21
|
+
EvidenceAdmissionPolicy,
|
|
22
|
+
ParetoCandidateRef,
|
|
23
|
+
ParetoDecision,
|
|
24
|
+
ParetoDecisionReason,
|
|
25
|
+
pareto_candidate_hash,
|
|
26
|
+
)
|
|
27
|
+
from agent_evolve.core.problem import (
|
|
28
|
+
ObjectiveSpec,
|
|
29
|
+
ProblemContractError,
|
|
30
|
+
normalize_objective_values,
|
|
31
|
+
validate_objective_specs,
|
|
32
|
+
)
|
|
33
|
+
from agent_evolve.core.results import dominates
|
|
34
|
+
from agent_evolve.domain.patch import require_sha256
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
POLICY_ID = "task_keyed_archive_elite_parent"
|
|
38
|
+
POLICY_VERSION = 1
|
|
39
|
+
_SCHEMA_VERSION = 1
|
|
40
|
+
_MAX_ROTATION_INDEX = (1 << 63) - 1
|
|
41
|
+
_DEFINITION_DOMAIN = b"agent-evolve:archive-elite-parent:def:v1\x00"
|
|
42
|
+
_ELIGIBLE_FRONT_DOMAIN = b"agent-evolve:archive-elite-parent:front:v1\x00"
|
|
43
|
+
_ROTATION_DOMAIN = b"agent-evolve:archive-elite-parent:rotation:v1\x00"
|
|
44
|
+
_RECEIPT_DOMAIN = b"agent-evolve:archive-elite-parent:receipt:v1\x00"
|
|
45
|
+
_DEFINITION = {
|
|
46
|
+
"eligible_source": "complete OptimizerState.archive.front_candidates only",
|
|
47
|
+
"archive_authority": "exact ParetoArchiveSnapshot and snapshot hash",
|
|
48
|
+
"outcome_or_objective_access": False,
|
|
49
|
+
"selection": "task/archive/front-keyed cyclic rotation without replacement",
|
|
50
|
+
"stale_or_foreign_candidate_admission": False,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _canonical_json(value: object) -> bytes:
|
|
55
|
+
return json.dumps(
|
|
56
|
+
value,
|
|
57
|
+
ensure_ascii=True,
|
|
58
|
+
allow_nan=False,
|
|
59
|
+
separators=(",", ":"),
|
|
60
|
+
sort_keys=True,
|
|
61
|
+
).encode("ascii", errors="strict")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
POLICY_DEFINITION_SHA256 = hashlib.sha256(
|
|
65
|
+
_DEFINITION_DOMAIN + _canonical_json(_DEFINITION)
|
|
66
|
+
).hexdigest()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
RESERVOIR_POLICY_ID = "task_keyed_archive_ranked_reservoir_parent"
|
|
70
|
+
RESERVOIR_POLICY_VERSION = 1
|
|
71
|
+
_RESERVOIR_SCHEMA_VERSION = 1
|
|
72
|
+
_RESERVOIR_DEFINITION_DOMAIN = (
|
|
73
|
+
b"agent-evolve:archive-ranked-reservoir-parent:def:v1\x00"
|
|
74
|
+
)
|
|
75
|
+
_RESERVOIR_RANKING_DOMAIN = (
|
|
76
|
+
b"agent-evolve:archive-ranked-reservoir-parent:ranking:v1\x00"
|
|
77
|
+
)
|
|
78
|
+
_RESERVOIR_DOMAIN = b"agent-evolve:archive-ranked-reservoir-parent:reservoir:v1\x00"
|
|
79
|
+
_RESERVOIR_ROTATION_DOMAIN = (
|
|
80
|
+
b"agent-evolve:archive-ranked-reservoir-parent:rotation:v1\x00"
|
|
81
|
+
)
|
|
82
|
+
_RESERVOIR_RECEIPT_DOMAIN = (
|
|
83
|
+
b"agent-evolve:archive-ranked-reservoir-parent:receipt:v1\x00"
|
|
84
|
+
)
|
|
85
|
+
_RESERVOIR_DEFINITION = {
|
|
86
|
+
"eligible_source": (
|
|
87
|
+
"complete archive-authenticated, gate-passing unique-configuration history"
|
|
88
|
+
),
|
|
89
|
+
"ranking": (
|
|
90
|
+
"nondomination rank; multiobjective crowding when meaningful; "
|
|
91
|
+
"minimax dense objective rank; authenticated proposal-sequence recency; "
|
|
92
|
+
"canonical identity"
|
|
93
|
+
),
|
|
94
|
+
"reservoir": "first min(reservoir_limit, eligible_count) ranked candidates",
|
|
95
|
+
"selection": (
|
|
96
|
+
"task/archive/ranking-keyed cyclic rotation without replacement; "
|
|
97
|
+
"return min(requested_parent_count, reservoir_cardinality)"
|
|
98
|
+
),
|
|
99
|
+
"benchmark_branching": False,
|
|
100
|
+
"stale_or_foreign_candidate_admission": False,
|
|
101
|
+
}
|
|
102
|
+
RESERVOIR_POLICY_DEFINITION_SHA256 = hashlib.sha256(
|
|
103
|
+
_RESERVOIR_DEFINITION_DOMAIN + _canonical_json(_RESERVOIR_DEFINITION)
|
|
104
|
+
).hexdigest()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _reference_record(reference: ParetoCandidateRef) -> dict[str, str]:
|
|
108
|
+
if type(reference) is not ParetoCandidateRef:
|
|
109
|
+
raise TypeError("front references must be exact ParetoCandidateRef values")
|
|
110
|
+
ParetoCandidateRef.__post_init__(reference)
|
|
111
|
+
return reference.to_trace_record()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _reference_key(reference: ParetoCandidateRef) -> tuple[str, str, str]:
|
|
115
|
+
return (
|
|
116
|
+
reference.configuration_hash,
|
|
117
|
+
reference.candidate_id.value,
|
|
118
|
+
reference.candidate_hash,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _candidate_reference(candidate: EvolutionCandidate) -> ParetoCandidateRef:
|
|
123
|
+
if type(candidate) is not EvolutionCandidate:
|
|
124
|
+
raise TypeError("archive front must contain exact EvolutionCandidate values")
|
|
125
|
+
EvolutionCandidate.__post_init__(candidate)
|
|
126
|
+
return ParetoCandidateRef(
|
|
127
|
+
candidate_id=candidate.candidate_id,
|
|
128
|
+
candidate_hash=pareto_candidate_hash(candidate),
|
|
129
|
+
configuration_hash=candidate.occurrence.configuration_hash,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _eligible_front_sha256(front: tuple[ParetoCandidateRef, ...]) -> str:
|
|
134
|
+
if type(front) is not tuple or not front:
|
|
135
|
+
raise ValueError("eligible archive front must be a non-empty exact tuple")
|
|
136
|
+
return hashlib.sha256(
|
|
137
|
+
_ELIGIBLE_FRONT_DOMAIN
|
|
138
|
+
+ _canonical_json([_reference_record(reference) for reference in front])
|
|
139
|
+
).hexdigest()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _rotation_anchor(
|
|
143
|
+
*,
|
|
144
|
+
task_sha256: str,
|
|
145
|
+
archive_snapshot_hash: str,
|
|
146
|
+
eligible_front_sha256: str,
|
|
147
|
+
cardinality: int,
|
|
148
|
+
) -> int:
|
|
149
|
+
require_sha256(task_sha256, "task_sha256")
|
|
150
|
+
require_sha256(archive_snapshot_hash, "archive_snapshot_hash")
|
|
151
|
+
require_sha256(eligible_front_sha256, "eligible_front_sha256")
|
|
152
|
+
if type(cardinality) is not int or cardinality <= 0:
|
|
153
|
+
raise ValueError("archive front cardinality must be positive")
|
|
154
|
+
digest = hashlib.sha256(
|
|
155
|
+
_ROTATION_DOMAIN
|
|
156
|
+
+ bytes.fromhex(task_sha256)
|
|
157
|
+
+ bytes.fromhex(archive_snapshot_hash)
|
|
158
|
+
+ bytes.fromhex(eligible_front_sha256)
|
|
159
|
+
).digest()
|
|
160
|
+
return int.from_bytes(digest, "big", signed=False) % cardinality
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _validated_front(
|
|
164
|
+
state: OptimizerState,
|
|
165
|
+
) -> tuple[tuple[EvolutionCandidate, ...], tuple[ParetoCandidateRef, ...]]:
|
|
166
|
+
if type(state) is not OptimizerState:
|
|
167
|
+
raise TypeError("state must be an exact OptimizerState")
|
|
168
|
+
OptimizerState.__post_init__(state)
|
|
169
|
+
candidates = state.archive.front_candidates
|
|
170
|
+
references = state.archive.front_references
|
|
171
|
+
if type(candidates) is not tuple or type(references) is not tuple:
|
|
172
|
+
raise TypeError("archive front and references must be exact tuples")
|
|
173
|
+
if not candidates:
|
|
174
|
+
raise ValueError("archive elite parent selection requires a non-empty front")
|
|
175
|
+
if len(candidates) != len(references):
|
|
176
|
+
raise ValueError("archive front candidates and references have different sizes")
|
|
177
|
+
|
|
178
|
+
derived = tuple(_candidate_reference(candidate) for candidate in candidates)
|
|
179
|
+
for observed, expected in zip(references, derived, strict=True):
|
|
180
|
+
_reference_record(observed)
|
|
181
|
+
if observed != expected:
|
|
182
|
+
raise ValueError("archive front candidate does not match its reference")
|
|
183
|
+
if references != tuple(sorted(references, key=_reference_key)):
|
|
184
|
+
raise ValueError("archive front is not in its canonical archive order")
|
|
185
|
+
if len({reference.candidate_id for reference in references}) != len(references):
|
|
186
|
+
raise ValueError("archive front contains duplicate candidate IDs")
|
|
187
|
+
if len({reference.configuration_hash for reference in references}) != len(
|
|
188
|
+
references
|
|
189
|
+
):
|
|
190
|
+
raise ValueError("archive front contains duplicate configurations")
|
|
191
|
+
|
|
192
|
+
history_by_id = {
|
|
193
|
+
candidate.candidate_id: _candidate_reference(candidate)
|
|
194
|
+
for candidate in state.candidates
|
|
195
|
+
}
|
|
196
|
+
for reference in references:
|
|
197
|
+
if history_by_id.get(reference.candidate_id) != reference:
|
|
198
|
+
raise ValueError("archive front contains a foreign or stale candidate")
|
|
199
|
+
return candidates, references
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class ArchiveReservoirCrowdingKind(str, Enum):
|
|
203
|
+
"""Whether a replayed crowding score is applicable and finite."""
|
|
204
|
+
|
|
205
|
+
NOT_APPLICABLE = "not_applicable"
|
|
206
|
+
BOUNDARY = "boundary"
|
|
207
|
+
FINITE = "finite"
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@dataclass(frozen=True, slots=True)
|
|
211
|
+
class ArchiveReservoirRankedCandidate:
|
|
212
|
+
"""One fully ordered, archive-authenticated reservoir candidate."""
|
|
213
|
+
|
|
214
|
+
reference: ParetoCandidateRef
|
|
215
|
+
nondomination_rank: int
|
|
216
|
+
crowding_kind: ArchiveReservoirCrowdingKind
|
|
217
|
+
crowding_distance_hex: str | None
|
|
218
|
+
objective_worst_dense_rank: int
|
|
219
|
+
objective_dense_rank_sum: int
|
|
220
|
+
proposal_sequence: int
|
|
221
|
+
|
|
222
|
+
def __post_init__(self) -> None:
|
|
223
|
+
_reference_record(self.reference)
|
|
224
|
+
for name in (
|
|
225
|
+
"nondomination_rank",
|
|
226
|
+
"objective_worst_dense_rank",
|
|
227
|
+
"objective_dense_rank_sum",
|
|
228
|
+
):
|
|
229
|
+
value = getattr(self, name)
|
|
230
|
+
if type(value) is not int or value <= 0:
|
|
231
|
+
raise ValueError(f"{name} must be a positive exact integer")
|
|
232
|
+
if type(self.proposal_sequence) is not int or self.proposal_sequence < 0:
|
|
233
|
+
raise ValueError("proposal_sequence must be a non-negative exact integer")
|
|
234
|
+
if type(self.crowding_kind) is not ArchiveReservoirCrowdingKind:
|
|
235
|
+
raise TypeError("crowding_kind must be an ArchiveReservoirCrowdingKind")
|
|
236
|
+
if self.crowding_kind is ArchiveReservoirCrowdingKind.FINITE:
|
|
237
|
+
if type(self.crowding_distance_hex) is not str:
|
|
238
|
+
raise TypeError("finite crowding distance requires a hexadecimal float")
|
|
239
|
+
try:
|
|
240
|
+
distance = float.fromhex(self.crowding_distance_hex)
|
|
241
|
+
except ValueError as exc:
|
|
242
|
+
raise ValueError(
|
|
243
|
+
"crowding_distance_hex is not a hexadecimal float"
|
|
244
|
+
) from exc
|
|
245
|
+
if not math.isfinite(distance) or distance < 0.0:
|
|
246
|
+
raise ValueError(
|
|
247
|
+
"finite crowding distance must be finite and non-negative"
|
|
248
|
+
)
|
|
249
|
+
if distance.hex() != self.crowding_distance_hex:
|
|
250
|
+
raise ValueError("crowding_distance_hex must be canonical")
|
|
251
|
+
elif self.crowding_distance_hex is not None:
|
|
252
|
+
raise ValueError("non-finite crowding kinds cannot carry a distance")
|
|
253
|
+
|
|
254
|
+
def to_trace_record(self) -> dict[str, object]:
|
|
255
|
+
ArchiveReservoirRankedCandidate.__post_init__(self)
|
|
256
|
+
return {
|
|
257
|
+
"candidate": _reference_record(self.reference),
|
|
258
|
+
"nondomination_rank": self.nondomination_rank,
|
|
259
|
+
"crowding_kind": self.crowding_kind.value,
|
|
260
|
+
"crowding_distance_hex": self.crowding_distance_hex,
|
|
261
|
+
"objective_worst_dense_rank": self.objective_worst_dense_rank,
|
|
262
|
+
"objective_dense_rank_sum": self.objective_dense_rank_sum,
|
|
263
|
+
"proposal_sequence": self.proposal_sequence,
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _candidate_objective_map(
|
|
268
|
+
candidate: EvolutionCandidate,
|
|
269
|
+
objectives: tuple[ObjectiveSpec, ...],
|
|
270
|
+
) -> dict[str, float]:
|
|
271
|
+
if type(candidate.objectives) is not tuple:
|
|
272
|
+
raise ProblemContractError("candidate objectives must be an exact tuple")
|
|
273
|
+
raw: dict[str, object] = {}
|
|
274
|
+
for item in candidate.objectives:
|
|
275
|
+
if type(item) is not tuple or len(item) != 2:
|
|
276
|
+
raise ProblemContractError(
|
|
277
|
+
"candidate objectives must contain exact (name, value) tuples"
|
|
278
|
+
)
|
|
279
|
+
name, value = item
|
|
280
|
+
if type(name) is not str:
|
|
281
|
+
raise ProblemContractError("candidate objective names must be strings")
|
|
282
|
+
if name in raw:
|
|
283
|
+
raise ProblemContractError(f"duplicate candidate objective {name!r}")
|
|
284
|
+
raw[name] = value
|
|
285
|
+
return normalize_objective_values(raw, objectives)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _validated_eligible_history(
|
|
289
|
+
state: OptimizerState,
|
|
290
|
+
) -> tuple[
|
|
291
|
+
tuple[EvolutionCandidate, ...],
|
|
292
|
+
tuple[dict[str, float], ...],
|
|
293
|
+
tuple[ObjectiveSpec, ...],
|
|
294
|
+
]:
|
|
295
|
+
"""Rejoin complete candidate history to the snapshot's consideration ledger."""
|
|
296
|
+
|
|
297
|
+
_validated_front(state)
|
|
298
|
+
snapshot = state.archive
|
|
299
|
+
if not snapshot.objective_pareto_relation:
|
|
300
|
+
raise ValueError(
|
|
301
|
+
"ranked parent reservoirs require objective Pareto archive semantics"
|
|
302
|
+
)
|
|
303
|
+
objectives = snapshot.objectives
|
|
304
|
+
if type(objectives) is not tuple or any(
|
|
305
|
+
type(objective) is not ObjectiveSpec for objective in objectives
|
|
306
|
+
):
|
|
307
|
+
raise TypeError("archive objectives must contain exact ObjectiveSpec values")
|
|
308
|
+
validate_objective_specs(objectives)
|
|
309
|
+
if type(snapshot.evidence_admission_policy) is not EvidenceAdmissionPolicy:
|
|
310
|
+
raise TypeError(
|
|
311
|
+
"archive evidence_admission_policy must be an EvidenceAdmissionPolicy"
|
|
312
|
+
)
|
|
313
|
+
if (
|
|
314
|
+
type(snapshot.consideration_count) is not int
|
|
315
|
+
or snapshot.consideration_count < 0
|
|
316
|
+
):
|
|
317
|
+
raise ValueError("archive consideration_count must be non-negative")
|
|
318
|
+
if (
|
|
319
|
+
type(snapshot.eligible_configuration_count) is not int
|
|
320
|
+
or snapshot.eligible_configuration_count < 0
|
|
321
|
+
):
|
|
322
|
+
raise ValueError("archive eligible_configuration_count must be non-negative")
|
|
323
|
+
decisions = snapshot.decisions
|
|
324
|
+
if type(decisions) is not tuple:
|
|
325
|
+
raise TypeError("archive decisions must be an exact tuple")
|
|
326
|
+
|
|
327
|
+
grouped: dict[int, list[ParetoDecision]] = {}
|
|
328
|
+
previous_consideration = 0
|
|
329
|
+
for expected_sequence, decision in enumerate(decisions, start=1):
|
|
330
|
+
if type(decision) is not ParetoDecision:
|
|
331
|
+
raise TypeError(
|
|
332
|
+
"archive decisions must contain exact ParetoDecision values"
|
|
333
|
+
)
|
|
334
|
+
ParetoDecision.__post_init__(decision)
|
|
335
|
+
if decision.decision_sequence != expected_sequence:
|
|
336
|
+
raise ValueError(
|
|
337
|
+
"archive decision sequences must be contiguous and ordered"
|
|
338
|
+
)
|
|
339
|
+
if decision.consideration_sequence < previous_consideration:
|
|
340
|
+
raise ValueError("archive consideration decisions must remain contiguous")
|
|
341
|
+
if decision.consideration_sequence > snapshot.consideration_count:
|
|
342
|
+
raise ValueError("archive decision exceeds consideration_count")
|
|
343
|
+
previous_consideration = decision.consideration_sequence
|
|
344
|
+
grouped.setdefault(decision.consideration_sequence, []).append(decision)
|
|
345
|
+
if tuple(grouped) != tuple(range(1, snapshot.consideration_count + 1)):
|
|
346
|
+
raise ValueError("archive consideration ledger is incomplete")
|
|
347
|
+
|
|
348
|
+
history_by_id: dict[str, EvolutionCandidate] = {}
|
|
349
|
+
history_references: dict[str, ParetoCandidateRef] = {}
|
|
350
|
+
for candidate in state.candidates:
|
|
351
|
+
reference = _candidate_reference(candidate)
|
|
352
|
+
candidate_id = candidate.candidate_id.value
|
|
353
|
+
history_by_id[candidate_id] = candidate
|
|
354
|
+
history_references[candidate_id] = reference
|
|
355
|
+
if candidate.generation > state.generation:
|
|
356
|
+
raise ValueError("candidate history cannot be newer than optimizer state")
|
|
357
|
+
primary_ids = tuple(
|
|
358
|
+
grouped[index][0].candidate.candidate_id.value
|
|
359
|
+
for index in range(1, snapshot.consideration_count + 1)
|
|
360
|
+
)
|
|
361
|
+
if len(set(primary_ids)) != len(primary_ids):
|
|
362
|
+
raise ValueError("archive consideration ledger repeats a candidate ID")
|
|
363
|
+
if set(primary_ids) != set(history_by_id):
|
|
364
|
+
raise ValueError("candidate history differs from the complete archive ledger")
|
|
365
|
+
|
|
366
|
+
gate_reason_order = (
|
|
367
|
+
("valid", ParetoDecisionReason.REJECTED_INVALID),
|
|
368
|
+
(
|
|
369
|
+
"operator_compliant",
|
|
370
|
+
ParetoDecisionReason.REJECTED_OPERATOR_NONCOMPLIANT,
|
|
371
|
+
),
|
|
372
|
+
(
|
|
373
|
+
"evidence_compliant",
|
|
374
|
+
ParetoDecisionReason.REJECTED_EVIDENCE_NONCOMPLIANT,
|
|
375
|
+
),
|
|
376
|
+
)
|
|
377
|
+
eligible_reasons = {
|
|
378
|
+
ParetoDecisionReason.ADMITTED_NONDOMINATED,
|
|
379
|
+
ParetoDecisionReason.ADMITTED_TIE_BREAK_REPLACEMENT,
|
|
380
|
+
ParetoDecisionReason.REJECTED_DOMINATED,
|
|
381
|
+
ParetoDecisionReason.REJECTED_OBJECTIVE_TIE,
|
|
382
|
+
}
|
|
383
|
+
seen_configurations: set[str] = set()
|
|
384
|
+
eligible_candidates: list[EvolutionCandidate] = []
|
|
385
|
+
objective_maps: list[dict[str, float]] = []
|
|
386
|
+
for consideration in range(1, snapshot.consideration_count + 1):
|
|
387
|
+
primary = grouped[consideration][0]
|
|
388
|
+
candidate_id = primary.candidate.candidate_id.value
|
|
389
|
+
candidate = history_by_id[candidate_id]
|
|
390
|
+
if primary.candidate != history_references[candidate_id]:
|
|
391
|
+
raise ValueError("archive consideration references a foreign candidate")
|
|
392
|
+
|
|
393
|
+
expected_gate_reasons = tuple(
|
|
394
|
+
reason
|
|
395
|
+
for attribute, reason in gate_reason_order
|
|
396
|
+
if not getattr(candidate, attribute)
|
|
397
|
+
and (
|
|
398
|
+
attribute != "evidence_compliant"
|
|
399
|
+
or snapshot.evidence_admission_policy
|
|
400
|
+
is EvidenceAdmissionPolicy.REQUIRE_COMPLIANT
|
|
401
|
+
)
|
|
402
|
+
)
|
|
403
|
+
if expected_gate_reasons:
|
|
404
|
+
if primary.reasons != expected_gate_reasons:
|
|
405
|
+
raise ValueError(
|
|
406
|
+
"archive gate decision differs from candidate evidence"
|
|
407
|
+
)
|
|
408
|
+
continue
|
|
409
|
+
try:
|
|
410
|
+
objective_map = _candidate_objective_map(candidate, objectives)
|
|
411
|
+
except (OverflowError, ProblemContractError, TypeError, ValueError):
|
|
412
|
+
if primary.reasons != (ParetoDecisionReason.REJECTED_OBJECTIVE_CONTRACT,):
|
|
413
|
+
raise ValueError(
|
|
414
|
+
"archive objective-contract decision differs from candidate evidence"
|
|
415
|
+
)
|
|
416
|
+
continue
|
|
417
|
+
|
|
418
|
+
configuration_hash = candidate.occurrence.configuration_hash
|
|
419
|
+
if configuration_hash in seen_configurations:
|
|
420
|
+
if primary.reasons != (
|
|
421
|
+
ParetoDecisionReason.REJECTED_DUPLICATE_CONFIGURATION,
|
|
422
|
+
):
|
|
423
|
+
raise ValueError(
|
|
424
|
+
"archive duplicate decision differs from consideration history"
|
|
425
|
+
)
|
|
426
|
+
continue
|
|
427
|
+
seen_configurations.add(configuration_hash)
|
|
428
|
+
if len(primary.reasons) != 1 or primary.reasons[0] not in eligible_reasons:
|
|
429
|
+
raise ValueError("eligible archive candidate has a foreign decision reason")
|
|
430
|
+
eligible_candidates.append(candidate)
|
|
431
|
+
objective_maps.append(objective_map)
|
|
432
|
+
|
|
433
|
+
if len(eligible_candidates) != snapshot.eligible_configuration_count:
|
|
434
|
+
raise ValueError("eligible history differs from archive configuration count")
|
|
435
|
+
return tuple(eligible_candidates), tuple(objective_maps), objectives
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def _nondomination_ranks(
|
|
439
|
+
objective_maps: tuple[dict[str, float], ...],
|
|
440
|
+
objectives: tuple[ObjectiveSpec, ...],
|
|
441
|
+
) -> tuple[int, ...]:
|
|
442
|
+
cardinality = len(objective_maps)
|
|
443
|
+
dominated_sets: list[set[int]] = [set() for _ in range(cardinality)]
|
|
444
|
+
dominator_counts = [0] * cardinality
|
|
445
|
+
for left in range(cardinality):
|
|
446
|
+
for right in range(left + 1, cardinality):
|
|
447
|
+
if dominates(objective_maps[left], objective_maps[right], objectives):
|
|
448
|
+
dominated_sets[left].add(right)
|
|
449
|
+
dominator_counts[right] += 1
|
|
450
|
+
elif dominates(objective_maps[right], objective_maps[left], objectives):
|
|
451
|
+
dominated_sets[right].add(left)
|
|
452
|
+
dominator_counts[left] += 1
|
|
453
|
+
|
|
454
|
+
ranks = [0] * cardinality
|
|
455
|
+
current = [index for index, count in enumerate(dominator_counts) if count == 0]
|
|
456
|
+
rank = 1
|
|
457
|
+
ranked_count = 0
|
|
458
|
+
while current:
|
|
459
|
+
following: list[int] = []
|
|
460
|
+
for index in current:
|
|
461
|
+
ranks[index] = rank
|
|
462
|
+
ranked_count += 1
|
|
463
|
+
for dominated in dominated_sets[index]:
|
|
464
|
+
dominator_counts[dominated] -= 1
|
|
465
|
+
if dominator_counts[dominated] == 0:
|
|
466
|
+
following.append(dominated)
|
|
467
|
+
current = following
|
|
468
|
+
rank += 1
|
|
469
|
+
if ranked_count != cardinality:
|
|
470
|
+
raise RuntimeError("nondomination ranking did not cover eligible history")
|
|
471
|
+
return tuple(ranks)
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def _objective_dense_ranks(
|
|
475
|
+
candidates: tuple[EvolutionCandidate, ...],
|
|
476
|
+
objective_maps: tuple[dict[str, float], ...],
|
|
477
|
+
objectives: tuple[ObjectiveSpec, ...],
|
|
478
|
+
) -> tuple[tuple[int, ...], ...]:
|
|
479
|
+
ranks = [[0] * len(objectives) for _ in candidates]
|
|
480
|
+
references = tuple(_candidate_reference(candidate) for candidate in candidates)
|
|
481
|
+
for objective_index, objective in enumerate(objectives):
|
|
482
|
+
order = sorted(
|
|
483
|
+
range(len(candidates)),
|
|
484
|
+
key=lambda index: (
|
|
485
|
+
-objective_maps[index][objective.name]
|
|
486
|
+
if objective.goal == "max"
|
|
487
|
+
else objective_maps[index][objective.name],
|
|
488
|
+
_reference_key(references[index]),
|
|
489
|
+
),
|
|
490
|
+
)
|
|
491
|
+
dense_rank = 1
|
|
492
|
+
for position, index in enumerate(order):
|
|
493
|
+
if position and (
|
|
494
|
+
objective_maps[index][objective.name]
|
|
495
|
+
!= objective_maps[order[position - 1]][objective.name]
|
|
496
|
+
):
|
|
497
|
+
dense_rank += 1
|
|
498
|
+
ranks[index][objective_index] = dense_rank
|
|
499
|
+
return tuple(tuple(value) for value in ranks)
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def _crowding_values(
|
|
503
|
+
candidates: tuple[EvolutionCandidate, ...],
|
|
504
|
+
objective_maps: tuple[dict[str, float], ...],
|
|
505
|
+
objectives: tuple[ObjectiveSpec, ...],
|
|
506
|
+
nondomination_ranks: tuple[int, ...],
|
|
507
|
+
) -> tuple[tuple[ArchiveReservoirCrowdingKind, float | None], ...]:
|
|
508
|
+
result = [(ArchiveReservoirCrowdingKind.NOT_APPLICABLE, None) for _ in candidates]
|
|
509
|
+
for rank in sorted(set(nondomination_ranks)):
|
|
510
|
+
front = tuple(
|
|
511
|
+
index
|
|
512
|
+
for index, candidate_rank in enumerate(nondomination_ranks)
|
|
513
|
+
if candidate_rank == rank
|
|
514
|
+
)
|
|
515
|
+
if len(objectives) < 2 or len(front) < 3:
|
|
516
|
+
continue
|
|
517
|
+
boundary: set[int] = set()
|
|
518
|
+
distances = {index: 0.0 for index in front}
|
|
519
|
+
varying_objective = False
|
|
520
|
+
for objective in objectives:
|
|
521
|
+
values = {index: objective_maps[index][objective.name] for index in front}
|
|
522
|
+
distinct_values = sorted(set(values.values()))
|
|
523
|
+
if len(distinct_values) < 2:
|
|
524
|
+
continue
|
|
525
|
+
varying_objective = True
|
|
526
|
+
lower = distinct_values[0]
|
|
527
|
+
upper = distinct_values[-1]
|
|
528
|
+
scale = upper - lower
|
|
529
|
+
boundary.update(index for index in front if values[index] in {lower, upper})
|
|
530
|
+
position = {value: index for index, value in enumerate(distinct_values)}
|
|
531
|
+
for index in front:
|
|
532
|
+
value_position = position[values[index]]
|
|
533
|
+
if value_position in (0, len(distinct_values) - 1):
|
|
534
|
+
continue
|
|
535
|
+
gap = (
|
|
536
|
+
distinct_values[value_position + 1]
|
|
537
|
+
- distinct_values[value_position - 1]
|
|
538
|
+
) / scale
|
|
539
|
+
distances[index] += gap
|
|
540
|
+
if not varying_objective:
|
|
541
|
+
continue
|
|
542
|
+
for index in front:
|
|
543
|
+
result[index] = (
|
|
544
|
+
(ArchiveReservoirCrowdingKind.BOUNDARY, None)
|
|
545
|
+
if index in boundary
|
|
546
|
+
else (ArchiveReservoirCrowdingKind.FINITE, distances[index])
|
|
547
|
+
)
|
|
548
|
+
return tuple(result)
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def _reservoir_member_key(
|
|
552
|
+
member: ArchiveReservoirRankedCandidate,
|
|
553
|
+
) -> tuple[object, ...]:
|
|
554
|
+
ArchiveReservoirRankedCandidate.__post_init__(member)
|
|
555
|
+
crowding_order = {
|
|
556
|
+
ArchiveReservoirCrowdingKind.BOUNDARY: 0,
|
|
557
|
+
ArchiveReservoirCrowdingKind.FINITE: 1,
|
|
558
|
+
ArchiveReservoirCrowdingKind.NOT_APPLICABLE: 2,
|
|
559
|
+
}[member.crowding_kind]
|
|
560
|
+
crowding_distance = (
|
|
561
|
+
0.0
|
|
562
|
+
if member.crowding_distance_hex is None
|
|
563
|
+
else float.fromhex(member.crowding_distance_hex)
|
|
564
|
+
)
|
|
565
|
+
return (
|
|
566
|
+
member.nondomination_rank,
|
|
567
|
+
crowding_order,
|
|
568
|
+
-crowding_distance,
|
|
569
|
+
member.objective_worst_dense_rank,
|
|
570
|
+
member.objective_dense_rank_sum,
|
|
571
|
+
-member.proposal_sequence,
|
|
572
|
+
*_reference_key(member.reference),
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def _ranked_eligible_history(
|
|
577
|
+
state: OptimizerState,
|
|
578
|
+
) -> tuple[tuple[EvolutionCandidate, ...], tuple[ArchiveReservoirRankedCandidate, ...]]:
|
|
579
|
+
candidates, objective_maps, objectives = _validated_eligible_history(state)
|
|
580
|
+
if not candidates:
|
|
581
|
+
raise ValueError("ranked parent selection requires eligible candidate history")
|
|
582
|
+
nondomination_ranks = _nondomination_ranks(objective_maps, objectives)
|
|
583
|
+
dense_ranks = _objective_dense_ranks(candidates, objective_maps, objectives)
|
|
584
|
+
crowding = _crowding_values(
|
|
585
|
+
candidates,
|
|
586
|
+
objective_maps,
|
|
587
|
+
objectives,
|
|
588
|
+
nondomination_ranks,
|
|
589
|
+
)
|
|
590
|
+
members = tuple(
|
|
591
|
+
ArchiveReservoirRankedCandidate(
|
|
592
|
+
reference=_candidate_reference(candidate),
|
|
593
|
+
nondomination_rank=nondomination_ranks[index],
|
|
594
|
+
crowding_kind=crowding[index][0],
|
|
595
|
+
crowding_distance_hex=(
|
|
596
|
+
None if crowding[index][1] is None else crowding[index][1].hex()
|
|
597
|
+
),
|
|
598
|
+
objective_worst_dense_rank=max(dense_ranks[index]),
|
|
599
|
+
objective_dense_rank_sum=sum(dense_ranks[index]),
|
|
600
|
+
proposal_sequence=candidate.occurrence.proposal_sequence,
|
|
601
|
+
)
|
|
602
|
+
for index, candidate in enumerate(candidates)
|
|
603
|
+
)
|
|
604
|
+
ranked_members = tuple(sorted(members, key=_reservoir_member_key))
|
|
605
|
+
candidates_by_id = {candidate.candidate_id: candidate for candidate in candidates}
|
|
606
|
+
ranked_candidates = tuple(
|
|
607
|
+
candidates_by_id[member.reference.candidate_id] for member in ranked_members
|
|
608
|
+
)
|
|
609
|
+
return ranked_candidates, ranked_members
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
def _eligible_ranking_sha256(
|
|
613
|
+
ranking: tuple[ArchiveReservoirRankedCandidate, ...],
|
|
614
|
+
) -> str:
|
|
615
|
+
if type(ranking) is not tuple or not ranking:
|
|
616
|
+
raise ValueError("eligible ranking must be a non-empty exact tuple")
|
|
617
|
+
return hashlib.sha256(
|
|
618
|
+
_RESERVOIR_RANKING_DOMAIN
|
|
619
|
+
+ _canonical_json([member.to_trace_record() for member in ranking])
|
|
620
|
+
).hexdigest()
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def _reservoir_sha256(reservoir: tuple[ParetoCandidateRef, ...]) -> str:
|
|
624
|
+
if type(reservoir) is not tuple or not reservoir:
|
|
625
|
+
raise ValueError("parent reservoir must be a non-empty exact tuple")
|
|
626
|
+
return hashlib.sha256(
|
|
627
|
+
_RESERVOIR_DOMAIN
|
|
628
|
+
+ _canonical_json([_reference_record(reference) for reference in reservoir])
|
|
629
|
+
).hexdigest()
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
def _reservoir_rotation_anchor(
|
|
633
|
+
*,
|
|
634
|
+
task_sha256: str,
|
|
635
|
+
archive_snapshot_hash: str,
|
|
636
|
+
eligible_ranking_sha256: str,
|
|
637
|
+
reservoir_sha256: str,
|
|
638
|
+
cardinality: int,
|
|
639
|
+
) -> int:
|
|
640
|
+
require_sha256(task_sha256, "task_sha256")
|
|
641
|
+
require_sha256(archive_snapshot_hash, "archive_snapshot_hash")
|
|
642
|
+
require_sha256(eligible_ranking_sha256, "eligible_ranking_sha256")
|
|
643
|
+
require_sha256(reservoir_sha256, "reservoir_sha256")
|
|
644
|
+
if type(cardinality) is not int or cardinality <= 0:
|
|
645
|
+
raise ValueError("parent reservoir cardinality must be positive")
|
|
646
|
+
digest = hashlib.sha256(
|
|
647
|
+
_RESERVOIR_ROTATION_DOMAIN
|
|
648
|
+
+ bytes.fromhex(task_sha256)
|
|
649
|
+
+ bytes.fromhex(archive_snapshot_hash)
|
|
650
|
+
+ bytes.fromhex(eligible_ranking_sha256)
|
|
651
|
+
+ bytes.fromhex(reservoir_sha256)
|
|
652
|
+
).digest()
|
|
653
|
+
return int.from_bytes(digest, "big", signed=False) % cardinality
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
@dataclass(frozen=True, slots=True)
|
|
657
|
+
class ArchiveEliteParentSelectionReceipt:
|
|
658
|
+
"""Replayable binding of one parent choice to the complete archive front."""
|
|
659
|
+
|
|
660
|
+
task_sha256: str
|
|
661
|
+
optimizer_generation: int
|
|
662
|
+
archive_snapshot_hash: str
|
|
663
|
+
eligible_front: tuple[ParetoCandidateRef, ...]
|
|
664
|
+
requested_parent_count: int
|
|
665
|
+
rotation_index: int
|
|
666
|
+
rotation_anchor: int
|
|
667
|
+
selected_ordinals: tuple[int, ...]
|
|
668
|
+
selected_parents: tuple[ParetoCandidateRef, ...]
|
|
669
|
+
schema_version: int = field(init=False, default=_SCHEMA_VERSION)
|
|
670
|
+
policy_id: str = field(init=False, default=POLICY_ID)
|
|
671
|
+
policy_version: int = field(init=False, default=POLICY_VERSION)
|
|
672
|
+
policy_definition_sha256: str = field(
|
|
673
|
+
init=False,
|
|
674
|
+
default=POLICY_DEFINITION_SHA256,
|
|
675
|
+
)
|
|
676
|
+
eligible_front_sha256: str = field(init=False, default="")
|
|
677
|
+
receipt_sha256: str = field(init=False, default="")
|
|
678
|
+
|
|
679
|
+
def __post_init__(self) -> None:
|
|
680
|
+
if (
|
|
681
|
+
self.schema_version != _SCHEMA_VERSION
|
|
682
|
+
or self.policy_id != POLICY_ID
|
|
683
|
+
or self.policy_version != POLICY_VERSION
|
|
684
|
+
or self.policy_definition_sha256 != POLICY_DEFINITION_SHA256
|
|
685
|
+
):
|
|
686
|
+
raise ValueError("archive elite receipt uses a foreign policy identity")
|
|
687
|
+
require_sha256(self.task_sha256, "task_sha256")
|
|
688
|
+
require_sha256(self.archive_snapshot_hash, "archive_snapshot_hash")
|
|
689
|
+
if type(self.optimizer_generation) is not int or self.optimizer_generation < 0:
|
|
690
|
+
raise ValueError("optimizer_generation must be non-negative")
|
|
691
|
+
if type(self.eligible_front) is not tuple or not self.eligible_front:
|
|
692
|
+
raise ValueError("eligible_front must be a non-empty exact tuple")
|
|
693
|
+
for reference in self.eligible_front:
|
|
694
|
+
_reference_record(reference)
|
|
695
|
+
if self.eligible_front != tuple(
|
|
696
|
+
sorted(self.eligible_front, key=_reference_key)
|
|
697
|
+
):
|
|
698
|
+
raise ValueError("eligible_front must preserve canonical archive order")
|
|
699
|
+
if len({value.candidate_id for value in self.eligible_front}) != len(
|
|
700
|
+
self.eligible_front
|
|
701
|
+
):
|
|
702
|
+
raise ValueError("eligible_front contains duplicate candidate IDs")
|
|
703
|
+
if len({value.configuration_hash for value in self.eligible_front}) != len(
|
|
704
|
+
self.eligible_front
|
|
705
|
+
):
|
|
706
|
+
raise ValueError("eligible_front contains duplicate configurations")
|
|
707
|
+
|
|
708
|
+
cardinality = len(self.eligible_front)
|
|
709
|
+
if (
|
|
710
|
+
type(self.requested_parent_count) is not int
|
|
711
|
+
or not 1 <= self.requested_parent_count <= cardinality
|
|
712
|
+
):
|
|
713
|
+
raise ValueError("requested_parent_count must lie within the front")
|
|
714
|
+
if (
|
|
715
|
+
type(self.rotation_index) is not int
|
|
716
|
+
or not 0 <= self.rotation_index <= _MAX_ROTATION_INDEX
|
|
717
|
+
):
|
|
718
|
+
raise ValueError("rotation_index must be an exact non-negative int63")
|
|
719
|
+
|
|
720
|
+
computed_front_sha256 = _eligible_front_sha256(self.eligible_front)
|
|
721
|
+
if self.eligible_front_sha256 not in ("", computed_front_sha256):
|
|
722
|
+
raise ValueError("eligible_front_sha256 does not identify eligible_front")
|
|
723
|
+
object.__setattr__(
|
|
724
|
+
self,
|
|
725
|
+
"eligible_front_sha256",
|
|
726
|
+
computed_front_sha256,
|
|
727
|
+
)
|
|
728
|
+
expected_anchor = _rotation_anchor(
|
|
729
|
+
task_sha256=self.task_sha256,
|
|
730
|
+
archive_snapshot_hash=self.archive_snapshot_hash,
|
|
731
|
+
eligible_front_sha256=computed_front_sha256,
|
|
732
|
+
cardinality=cardinality,
|
|
733
|
+
)
|
|
734
|
+
if (
|
|
735
|
+
type(self.rotation_anchor) is not int
|
|
736
|
+
or self.rotation_anchor != expected_anchor
|
|
737
|
+
):
|
|
738
|
+
raise ValueError("rotation_anchor does not replay the task-keyed policy")
|
|
739
|
+
expected_ordinals = tuple(
|
|
740
|
+
(expected_anchor + self.rotation_index + offset) % cardinality
|
|
741
|
+
for offset in range(self.requested_parent_count)
|
|
742
|
+
)
|
|
743
|
+
if type(self.selected_ordinals) is not tuple:
|
|
744
|
+
raise TypeError("selected_ordinals must be an exact tuple")
|
|
745
|
+
if any(type(ordinal) is not int for ordinal in self.selected_ordinals):
|
|
746
|
+
raise TypeError("selected_ordinals must contain exact ints")
|
|
747
|
+
if type(self.selected_parents) is not tuple:
|
|
748
|
+
raise TypeError("selected_parents must be an exact tuple")
|
|
749
|
+
for reference in self.selected_parents:
|
|
750
|
+
_reference_record(reference)
|
|
751
|
+
if self.selected_ordinals != expected_ordinals:
|
|
752
|
+
raise ValueError("selected_ordinals do not replay the cyclic rotation")
|
|
753
|
+
if self.selected_parents != tuple(
|
|
754
|
+
self.eligible_front[ordinal] for ordinal in expected_ordinals
|
|
755
|
+
):
|
|
756
|
+
raise ValueError("selected_parents do not match selected_ordinals")
|
|
757
|
+
if len(set(self.selected_ordinals)) != len(self.selected_ordinals):
|
|
758
|
+
raise ValueError("parent selection must be without replacement")
|
|
759
|
+
|
|
760
|
+
computed_receipt_sha256 = hashlib.sha256(
|
|
761
|
+
_RECEIPT_DOMAIN + _canonical_json(self._record_without_hash())
|
|
762
|
+
).hexdigest()
|
|
763
|
+
if self.receipt_sha256 not in ("", computed_receipt_sha256):
|
|
764
|
+
raise ValueError("receipt_sha256 does not verify")
|
|
765
|
+
object.__setattr__(self, "receipt_sha256", computed_receipt_sha256)
|
|
766
|
+
|
|
767
|
+
def _record_without_hash(self) -> dict[str, object]:
|
|
768
|
+
return {
|
|
769
|
+
"schema_version": self.schema_version,
|
|
770
|
+
"policy_id": self.policy_id,
|
|
771
|
+
"policy_version": self.policy_version,
|
|
772
|
+
"policy_definition_sha256": self.policy_definition_sha256,
|
|
773
|
+
"task_sha256": self.task_sha256,
|
|
774
|
+
"optimizer_generation": self.optimizer_generation,
|
|
775
|
+
"archive_snapshot_hash": self.archive_snapshot_hash,
|
|
776
|
+
"eligible_front": [
|
|
777
|
+
_reference_record(reference) for reference in self.eligible_front
|
|
778
|
+
],
|
|
779
|
+
"eligible_front_sha256": self.eligible_front_sha256,
|
|
780
|
+
"requested_parent_count": self.requested_parent_count,
|
|
781
|
+
"rotation_index": self.rotation_index,
|
|
782
|
+
"rotation_anchor": self.rotation_anchor,
|
|
783
|
+
"selected_ordinals": list(self.selected_ordinals),
|
|
784
|
+
"selected_parents": [
|
|
785
|
+
_reference_record(reference) for reference in self.selected_parents
|
|
786
|
+
],
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
def revalidate(self) -> None:
|
|
790
|
+
if type(self) is not ArchiveEliteParentSelectionReceipt:
|
|
791
|
+
raise TypeError(
|
|
792
|
+
"receipt must be an exact ArchiveEliteParentSelectionReceipt"
|
|
793
|
+
)
|
|
794
|
+
ArchiveEliteParentSelectionReceipt.__post_init__(self)
|
|
795
|
+
|
|
796
|
+
def to_trace_record(self) -> dict[str, object]:
|
|
797
|
+
self.revalidate()
|
|
798
|
+
return {**self._record_without_hash(), "receipt_sha256": self.receipt_sha256}
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
@dataclass(frozen=True, slots=True)
|
|
802
|
+
class ArchiveEliteParentSelection:
|
|
803
|
+
"""Selected immutable parent values plus their audit receipt."""
|
|
804
|
+
|
|
805
|
+
parents: tuple[EvolutionCandidate, ...]
|
|
806
|
+
receipt: ArchiveEliteParentSelectionReceipt
|
|
807
|
+
|
|
808
|
+
def __post_init__(self) -> None:
|
|
809
|
+
if type(self.parents) is not tuple or not self.parents:
|
|
810
|
+
raise ValueError("parents must be a non-empty exact tuple")
|
|
811
|
+
if any(type(parent) is not EvolutionCandidate for parent in self.parents):
|
|
812
|
+
raise TypeError("parents must contain exact EvolutionCandidate values")
|
|
813
|
+
for parent in self.parents:
|
|
814
|
+
EvolutionCandidate.__post_init__(parent)
|
|
815
|
+
if type(self.receipt) is not ArchiveEliteParentSelectionReceipt:
|
|
816
|
+
raise TypeError("receipt must be an exact archive elite receipt")
|
|
817
|
+
self.receipt.revalidate()
|
|
818
|
+
if len(self.parents) != self.receipt.requested_parent_count:
|
|
819
|
+
raise ValueError("selected parent count differs from its receipt")
|
|
820
|
+
observed = tuple(_candidate_reference(parent) for parent in self.parents)
|
|
821
|
+
if observed != self.receipt.selected_parents:
|
|
822
|
+
raise ValueError("selected parent values differ from their receipt")
|
|
823
|
+
|
|
824
|
+
def revalidate(self) -> None:
|
|
825
|
+
if type(self) is not ArchiveEliteParentSelection:
|
|
826
|
+
raise TypeError("selection must be an exact ArchiveEliteParentSelection")
|
|
827
|
+
ArchiveEliteParentSelection.__post_init__(self)
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
def validate_archive_elite_parent_selection(
|
|
831
|
+
state: OptimizerState,
|
|
832
|
+
selection: ArchiveEliteParentSelection,
|
|
833
|
+
) -> None:
|
|
834
|
+
"""Reject a stale, foreign, or tampered selection at planner admission."""
|
|
835
|
+
|
|
836
|
+
candidates, references = _validated_front(state)
|
|
837
|
+
if type(selection) is not ArchiveEliteParentSelection:
|
|
838
|
+
raise TypeError("selection must be an exact ArchiveEliteParentSelection")
|
|
839
|
+
selection.revalidate()
|
|
840
|
+
receipt = selection.receipt
|
|
841
|
+
if receipt.optimizer_generation != state.generation:
|
|
842
|
+
raise ValueError("archive elite selection is stale for this generation")
|
|
843
|
+
if receipt.archive_snapshot_hash != state.archive_snapshot_hash:
|
|
844
|
+
raise ValueError("archive elite selection is stale for this archive snapshot")
|
|
845
|
+
if receipt.eligible_front != references:
|
|
846
|
+
raise ValueError(
|
|
847
|
+
"archive elite receipt does not bind the complete current front"
|
|
848
|
+
)
|
|
849
|
+
expected_parents = tuple(candidates[index] for index in receipt.selected_ordinals)
|
|
850
|
+
if selection.parents != expected_parents:
|
|
851
|
+
raise ValueError("archive elite parents are foreign to the current front")
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
class ArchiveEliteParentSelector(Protocol):
|
|
855
|
+
"""Inverted planner seam for objective-blind archive parent selection."""
|
|
856
|
+
|
|
857
|
+
policy_id: str
|
|
858
|
+
policy_version: int
|
|
859
|
+
definition_sha256: str
|
|
860
|
+
|
|
861
|
+
def select(
|
|
862
|
+
self,
|
|
863
|
+
state: OptimizerState,
|
|
864
|
+
*,
|
|
865
|
+
task_sha256: str,
|
|
866
|
+
expected_archive_snapshot_hash: str,
|
|
867
|
+
parent_count: int = 1,
|
|
868
|
+
rotation_index: int = 0,
|
|
869
|
+
) -> ArchiveEliteParentSelection: ...
|
|
870
|
+
|
|
871
|
+
def to_record(self) -> dict[str, object]: ...
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
@dataclass(frozen=True, slots=True)
|
|
875
|
+
class TaskKeyedArchiveEliteParentPolicy:
|
|
876
|
+
"""Select distinct elite parents without reading benchmark semantics."""
|
|
877
|
+
|
|
878
|
+
policy_id: str = field(init=False, default=POLICY_ID)
|
|
879
|
+
policy_version: int = field(init=False, default=POLICY_VERSION)
|
|
880
|
+
definition_sha256: str = field(
|
|
881
|
+
init=False,
|
|
882
|
+
default=POLICY_DEFINITION_SHA256,
|
|
883
|
+
)
|
|
884
|
+
|
|
885
|
+
def _validate_identity(self) -> None:
|
|
886
|
+
if type(self) is not TaskKeyedArchiveEliteParentPolicy:
|
|
887
|
+
raise TypeError("policy must be an exact TaskKeyedArchiveEliteParentPolicy")
|
|
888
|
+
if (
|
|
889
|
+
self.policy_id != POLICY_ID
|
|
890
|
+
or self.policy_version != POLICY_VERSION
|
|
891
|
+
or self.definition_sha256 != POLICY_DEFINITION_SHA256
|
|
892
|
+
):
|
|
893
|
+
raise ValueError("archive elite selector uses a foreign policy identity")
|
|
894
|
+
|
|
895
|
+
def select(
|
|
896
|
+
self,
|
|
897
|
+
state: OptimizerState,
|
|
898
|
+
*,
|
|
899
|
+
task_sha256: str,
|
|
900
|
+
expected_archive_snapshot_hash: str,
|
|
901
|
+
parent_count: int = 1,
|
|
902
|
+
rotation_index: int = 0,
|
|
903
|
+
) -> ArchiveEliteParentSelection:
|
|
904
|
+
self._validate_identity()
|
|
905
|
+
require_sha256(task_sha256, "task_sha256")
|
|
906
|
+
require_sha256(
|
|
907
|
+
expected_archive_snapshot_hash,
|
|
908
|
+
"expected_archive_snapshot_hash",
|
|
909
|
+
)
|
|
910
|
+
candidates, references = _validated_front(state)
|
|
911
|
+
if expected_archive_snapshot_hash != state.archive_snapshot_hash:
|
|
912
|
+
raise ValueError("expected archive snapshot is stale")
|
|
913
|
+
if type(parent_count) is not int or not 1 <= parent_count <= len(references):
|
|
914
|
+
raise ValueError("parent_count must lie within the archive front")
|
|
915
|
+
if (
|
|
916
|
+
type(rotation_index) is not int
|
|
917
|
+
or not 0 <= rotation_index <= _MAX_ROTATION_INDEX
|
|
918
|
+
):
|
|
919
|
+
raise ValueError("rotation_index must be an exact non-negative int63")
|
|
920
|
+
|
|
921
|
+
front_sha256 = _eligible_front_sha256(references)
|
|
922
|
+
anchor = _rotation_anchor(
|
|
923
|
+
task_sha256=task_sha256,
|
|
924
|
+
archive_snapshot_hash=state.archive_snapshot_hash,
|
|
925
|
+
eligible_front_sha256=front_sha256,
|
|
926
|
+
cardinality=len(references),
|
|
927
|
+
)
|
|
928
|
+
ordinals = tuple(
|
|
929
|
+
(anchor + rotation_index + offset) % len(references)
|
|
930
|
+
for offset in range(parent_count)
|
|
931
|
+
)
|
|
932
|
+
receipt = ArchiveEliteParentSelectionReceipt(
|
|
933
|
+
task_sha256=task_sha256,
|
|
934
|
+
optimizer_generation=state.generation,
|
|
935
|
+
archive_snapshot_hash=state.archive_snapshot_hash,
|
|
936
|
+
eligible_front=references,
|
|
937
|
+
requested_parent_count=parent_count,
|
|
938
|
+
rotation_index=rotation_index,
|
|
939
|
+
rotation_anchor=anchor,
|
|
940
|
+
selected_ordinals=ordinals,
|
|
941
|
+
selected_parents=tuple(references[index] for index in ordinals),
|
|
942
|
+
)
|
|
943
|
+
selection = ArchiveEliteParentSelection(
|
|
944
|
+
parents=tuple(candidates[index] for index in ordinals),
|
|
945
|
+
receipt=receipt,
|
|
946
|
+
)
|
|
947
|
+
validate_archive_elite_parent_selection(state, selection)
|
|
948
|
+
return selection
|
|
949
|
+
|
|
950
|
+
def to_record(self) -> dict[str, object]:
|
|
951
|
+
self._validate_identity()
|
|
952
|
+
return {
|
|
953
|
+
"policy_id": self.policy_id,
|
|
954
|
+
"policy_version": self.policy_version,
|
|
955
|
+
"definition_sha256": self.definition_sha256,
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
@dataclass(frozen=True, slots=True)
|
|
960
|
+
class ArchiveReservoirParentSelectionReceipt:
|
|
961
|
+
"""Replayable binding of a capped parent choice to complete ranked history."""
|
|
962
|
+
|
|
963
|
+
task_sha256: str
|
|
964
|
+
optimizer_generation: int
|
|
965
|
+
archive_snapshot_hash: str
|
|
966
|
+
eligible_ranking: tuple[ArchiveReservoirRankedCandidate, ...]
|
|
967
|
+
reservoir_limit: int
|
|
968
|
+
reservoir: tuple[ParetoCandidateRef, ...]
|
|
969
|
+
requested_parent_count: int
|
|
970
|
+
returned_parent_count: int
|
|
971
|
+
rotation_index: int
|
|
972
|
+
rotation_anchor: int
|
|
973
|
+
selected_ordinals: tuple[int, ...]
|
|
974
|
+
selected_parents: tuple[ParetoCandidateRef, ...]
|
|
975
|
+
schema_version: int = field(init=False, default=_RESERVOIR_SCHEMA_VERSION)
|
|
976
|
+
policy_id: str = field(init=False, default=RESERVOIR_POLICY_ID)
|
|
977
|
+
policy_version: int = field(init=False, default=RESERVOIR_POLICY_VERSION)
|
|
978
|
+
policy_definition_sha256: str = field(
|
|
979
|
+
init=False,
|
|
980
|
+
default=RESERVOIR_POLICY_DEFINITION_SHA256,
|
|
981
|
+
)
|
|
982
|
+
eligible_ranking_sha256: str = field(init=False, default="")
|
|
983
|
+
reservoir_sha256: str = field(init=False, default="")
|
|
984
|
+
receipt_sha256: str = field(init=False, default="")
|
|
985
|
+
|
|
986
|
+
def __post_init__(self) -> None:
|
|
987
|
+
if (
|
|
988
|
+
self.schema_version != _RESERVOIR_SCHEMA_VERSION
|
|
989
|
+
or self.policy_id != RESERVOIR_POLICY_ID
|
|
990
|
+
or self.policy_version != RESERVOIR_POLICY_VERSION
|
|
991
|
+
or self.policy_definition_sha256 != RESERVOIR_POLICY_DEFINITION_SHA256
|
|
992
|
+
):
|
|
993
|
+
raise ValueError("archive reservoir receipt uses a foreign policy identity")
|
|
994
|
+
require_sha256(self.task_sha256, "task_sha256")
|
|
995
|
+
require_sha256(self.archive_snapshot_hash, "archive_snapshot_hash")
|
|
996
|
+
if type(self.optimizer_generation) is not int or self.optimizer_generation < 0:
|
|
997
|
+
raise ValueError("optimizer_generation must be non-negative")
|
|
998
|
+
if type(self.eligible_ranking) is not tuple or not self.eligible_ranking:
|
|
999
|
+
raise ValueError("eligible_ranking must be a non-empty exact tuple")
|
|
1000
|
+
if any(
|
|
1001
|
+
type(member) is not ArchiveReservoirRankedCandidate
|
|
1002
|
+
for member in self.eligible_ranking
|
|
1003
|
+
):
|
|
1004
|
+
raise TypeError("eligible_ranking must contain exact ranked candidates")
|
|
1005
|
+
for member in self.eligible_ranking:
|
|
1006
|
+
ArchiveReservoirRankedCandidate.__post_init__(member)
|
|
1007
|
+
if self.eligible_ranking != tuple(
|
|
1008
|
+
sorted(self.eligible_ranking, key=_reservoir_member_key)
|
|
1009
|
+
):
|
|
1010
|
+
raise ValueError("eligible_ranking does not preserve policy order")
|
|
1011
|
+
references = tuple(member.reference for member in self.eligible_ranking)
|
|
1012
|
+
if len({value.candidate_id for value in references}) != len(references):
|
|
1013
|
+
raise ValueError("eligible_ranking contains duplicate candidate IDs")
|
|
1014
|
+
if len({value.configuration_hash for value in references}) != len(references):
|
|
1015
|
+
raise ValueError("eligible_ranking contains duplicate configurations")
|
|
1016
|
+
observed_ranks = {member.nondomination_rank for member in self.eligible_ranking}
|
|
1017
|
+
if observed_ranks != set(range(1, max(observed_ranks) + 1)):
|
|
1018
|
+
raise ValueError("eligible_ranking has non-contiguous nondomination ranks")
|
|
1019
|
+
|
|
1020
|
+
computed_ranking_sha256 = _eligible_ranking_sha256(self.eligible_ranking)
|
|
1021
|
+
if self.eligible_ranking_sha256 not in ("", computed_ranking_sha256):
|
|
1022
|
+
raise ValueError("eligible_ranking_sha256 does not identify ranking")
|
|
1023
|
+
object.__setattr__(
|
|
1024
|
+
self,
|
|
1025
|
+
"eligible_ranking_sha256",
|
|
1026
|
+
computed_ranking_sha256,
|
|
1027
|
+
)
|
|
1028
|
+
if type(self.reservoir_limit) is not int or self.reservoir_limit <= 0:
|
|
1029
|
+
raise ValueError("reservoir_limit must be a positive exact integer")
|
|
1030
|
+
expected_reservoir = references[: min(self.reservoir_limit, len(references))]
|
|
1031
|
+
if type(self.reservoir) is not tuple:
|
|
1032
|
+
raise TypeError("reservoir must be an exact tuple")
|
|
1033
|
+
for reference in self.reservoir:
|
|
1034
|
+
_reference_record(reference)
|
|
1035
|
+
if self.reservoir != expected_reservoir:
|
|
1036
|
+
raise ValueError("reservoir is not the ranked top-k eligible prefix")
|
|
1037
|
+
computed_reservoir_sha256 = _reservoir_sha256(self.reservoir)
|
|
1038
|
+
if self.reservoir_sha256 not in ("", computed_reservoir_sha256):
|
|
1039
|
+
raise ValueError("reservoir_sha256 does not identify reservoir")
|
|
1040
|
+
object.__setattr__(self, "reservoir_sha256", computed_reservoir_sha256)
|
|
1041
|
+
|
|
1042
|
+
if (
|
|
1043
|
+
type(self.requested_parent_count) is not int
|
|
1044
|
+
or self.requested_parent_count <= 0
|
|
1045
|
+
):
|
|
1046
|
+
raise ValueError("requested_parent_count must be a positive exact integer")
|
|
1047
|
+
expected_returned = min(self.requested_parent_count, len(self.reservoir))
|
|
1048
|
+
if (
|
|
1049
|
+
type(self.returned_parent_count) is not int
|
|
1050
|
+
or self.returned_parent_count != expected_returned
|
|
1051
|
+
):
|
|
1052
|
+
raise ValueError("returned_parent_count does not apply the singleton cap")
|
|
1053
|
+
if (
|
|
1054
|
+
type(self.rotation_index) is not int
|
|
1055
|
+
or not 0 <= self.rotation_index <= _MAX_ROTATION_INDEX
|
|
1056
|
+
):
|
|
1057
|
+
raise ValueError("rotation_index must be an exact non-negative int63")
|
|
1058
|
+
expected_anchor = _reservoir_rotation_anchor(
|
|
1059
|
+
task_sha256=self.task_sha256,
|
|
1060
|
+
archive_snapshot_hash=self.archive_snapshot_hash,
|
|
1061
|
+
eligible_ranking_sha256=computed_ranking_sha256,
|
|
1062
|
+
reservoir_sha256=computed_reservoir_sha256,
|
|
1063
|
+
cardinality=len(self.reservoir),
|
|
1064
|
+
)
|
|
1065
|
+
if (
|
|
1066
|
+
type(self.rotation_anchor) is not int
|
|
1067
|
+
or self.rotation_anchor != expected_anchor
|
|
1068
|
+
):
|
|
1069
|
+
raise ValueError("rotation_anchor does not replay the reservoir policy")
|
|
1070
|
+
expected_ordinals = tuple(
|
|
1071
|
+
(expected_anchor + self.rotation_index + offset) % len(self.reservoir)
|
|
1072
|
+
for offset in range(expected_returned)
|
|
1073
|
+
)
|
|
1074
|
+
if type(self.selected_ordinals) is not tuple or any(
|
|
1075
|
+
type(ordinal) is not int for ordinal in self.selected_ordinals
|
|
1076
|
+
):
|
|
1077
|
+
raise TypeError("selected_ordinals must be an exact tuple of exact ints")
|
|
1078
|
+
if self.selected_ordinals != expected_ordinals:
|
|
1079
|
+
raise ValueError("selected_ordinals do not replay the cyclic rotation")
|
|
1080
|
+
if len(set(self.selected_ordinals)) != len(self.selected_ordinals):
|
|
1081
|
+
raise ValueError("reservoir parent selection must be without replacement")
|
|
1082
|
+
if type(self.selected_parents) is not tuple:
|
|
1083
|
+
raise TypeError("selected_parents must be an exact tuple")
|
|
1084
|
+
for reference in self.selected_parents:
|
|
1085
|
+
_reference_record(reference)
|
|
1086
|
+
if self.selected_parents != tuple(
|
|
1087
|
+
self.reservoir[ordinal] for ordinal in expected_ordinals
|
|
1088
|
+
):
|
|
1089
|
+
raise ValueError("selected_parents do not match selected_ordinals")
|
|
1090
|
+
|
|
1091
|
+
computed_receipt_sha256 = hashlib.sha256(
|
|
1092
|
+
_RESERVOIR_RECEIPT_DOMAIN + _canonical_json(self._record_without_hash())
|
|
1093
|
+
).hexdigest()
|
|
1094
|
+
if self.receipt_sha256 not in ("", computed_receipt_sha256):
|
|
1095
|
+
raise ValueError("receipt_sha256 does not verify")
|
|
1096
|
+
object.__setattr__(self, "receipt_sha256", computed_receipt_sha256)
|
|
1097
|
+
|
|
1098
|
+
def _record_without_hash(self) -> dict[str, object]:
|
|
1099
|
+
return {
|
|
1100
|
+
"schema_version": self.schema_version,
|
|
1101
|
+
"policy_id": self.policy_id,
|
|
1102
|
+
"policy_version": self.policy_version,
|
|
1103
|
+
"policy_definition_sha256": self.policy_definition_sha256,
|
|
1104
|
+
"task_sha256": self.task_sha256,
|
|
1105
|
+
"optimizer_generation": self.optimizer_generation,
|
|
1106
|
+
"archive_snapshot_hash": self.archive_snapshot_hash,
|
|
1107
|
+
"eligible_ranking": [
|
|
1108
|
+
member.to_trace_record() for member in self.eligible_ranking
|
|
1109
|
+
],
|
|
1110
|
+
"eligible_ranking_sha256": self.eligible_ranking_sha256,
|
|
1111
|
+
"reservoir_limit": self.reservoir_limit,
|
|
1112
|
+
"reservoir": [_reference_record(reference) for reference in self.reservoir],
|
|
1113
|
+
"reservoir_sha256": self.reservoir_sha256,
|
|
1114
|
+
"requested_parent_count": self.requested_parent_count,
|
|
1115
|
+
"returned_parent_count": self.returned_parent_count,
|
|
1116
|
+
"rotation_index": self.rotation_index,
|
|
1117
|
+
"rotation_anchor": self.rotation_anchor,
|
|
1118
|
+
"selected_ordinals": list(self.selected_ordinals),
|
|
1119
|
+
"selected_parents": [
|
|
1120
|
+
_reference_record(reference) for reference in self.selected_parents
|
|
1121
|
+
],
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
def revalidate(self) -> None:
|
|
1125
|
+
if type(self) is not ArchiveReservoirParentSelectionReceipt:
|
|
1126
|
+
raise TypeError(
|
|
1127
|
+
"receipt must be an exact ArchiveReservoirParentSelectionReceipt"
|
|
1128
|
+
)
|
|
1129
|
+
ArchiveReservoirParentSelectionReceipt.__post_init__(self)
|
|
1130
|
+
|
|
1131
|
+
def to_trace_record(self) -> dict[str, object]:
|
|
1132
|
+
self.revalidate()
|
|
1133
|
+
return {**self._record_without_hash(), "receipt_sha256": self.receipt_sha256}
|
|
1134
|
+
|
|
1135
|
+
|
|
1136
|
+
@dataclass(frozen=True, slots=True)
|
|
1137
|
+
class ArchiveReservoirParentSelection:
|
|
1138
|
+
"""Up-to-K distinct parents plus their ranked-reservoir receipt."""
|
|
1139
|
+
|
|
1140
|
+
parents: tuple[EvolutionCandidate, ...]
|
|
1141
|
+
receipt: ArchiveReservoirParentSelectionReceipt
|
|
1142
|
+
|
|
1143
|
+
def __post_init__(self) -> None:
|
|
1144
|
+
if type(self.parents) is not tuple or not self.parents:
|
|
1145
|
+
raise ValueError("parents must be a non-empty exact tuple")
|
|
1146
|
+
if any(type(parent) is not EvolutionCandidate for parent in self.parents):
|
|
1147
|
+
raise TypeError("parents must contain exact EvolutionCandidate values")
|
|
1148
|
+
for parent in self.parents:
|
|
1149
|
+
EvolutionCandidate.__post_init__(parent)
|
|
1150
|
+
if type(self.receipt) is not ArchiveReservoirParentSelectionReceipt:
|
|
1151
|
+
raise TypeError("receipt must be an exact archive reservoir receipt")
|
|
1152
|
+
self.receipt.revalidate()
|
|
1153
|
+
if len(self.parents) != self.receipt.returned_parent_count:
|
|
1154
|
+
raise ValueError("selected parent count differs from its receipt")
|
|
1155
|
+
if len({parent.candidate_id for parent in self.parents}) != len(self.parents):
|
|
1156
|
+
raise ValueError("selected parent occurrences must be distinct")
|
|
1157
|
+
if len(
|
|
1158
|
+
{parent.occurrence.configuration_hash for parent in self.parents}
|
|
1159
|
+
) != len(self.parents):
|
|
1160
|
+
raise ValueError("selected parent configurations must be distinct")
|
|
1161
|
+
if tuple(_candidate_reference(parent) for parent in self.parents) != (
|
|
1162
|
+
self.receipt.selected_parents
|
|
1163
|
+
):
|
|
1164
|
+
raise ValueError("selected parent values differ from their receipt")
|
|
1165
|
+
|
|
1166
|
+
def revalidate(self) -> None:
|
|
1167
|
+
if type(self) is not ArchiveReservoirParentSelection:
|
|
1168
|
+
raise TypeError(
|
|
1169
|
+
"selection must be an exact ArchiveReservoirParentSelection"
|
|
1170
|
+
)
|
|
1171
|
+
ArchiveReservoirParentSelection.__post_init__(self)
|
|
1172
|
+
|
|
1173
|
+
|
|
1174
|
+
def validate_archive_reservoir_parent_selection(
|
|
1175
|
+
state: OptimizerState,
|
|
1176
|
+
selection: ArchiveReservoirParentSelection,
|
|
1177
|
+
) -> None:
|
|
1178
|
+
"""Reject stale, foreign, incompletely ranked, or tampered reservoir choices."""
|
|
1179
|
+
|
|
1180
|
+
ranked_candidates, ranked_members = _ranked_eligible_history(state)
|
|
1181
|
+
if type(selection) is not ArchiveReservoirParentSelection:
|
|
1182
|
+
raise TypeError("selection must be an exact ArchiveReservoirParentSelection")
|
|
1183
|
+
selection.revalidate()
|
|
1184
|
+
receipt = selection.receipt
|
|
1185
|
+
if receipt.optimizer_generation != state.generation:
|
|
1186
|
+
raise ValueError("archive reservoir selection is stale for this generation")
|
|
1187
|
+
if receipt.archive_snapshot_hash != state.archive_snapshot_hash:
|
|
1188
|
+
raise ValueError("archive reservoir selection is stale for this snapshot")
|
|
1189
|
+
if receipt.eligible_ranking != ranked_members:
|
|
1190
|
+
raise ValueError("receipt does not bind the complete current eligible ranking")
|
|
1191
|
+
expected_reservoir_members = ranked_members[
|
|
1192
|
+
: min(receipt.reservoir_limit, len(ranked_members))
|
|
1193
|
+
]
|
|
1194
|
+
if receipt.reservoir != tuple(
|
|
1195
|
+
member.reference for member in expected_reservoir_members
|
|
1196
|
+
):
|
|
1197
|
+
raise ValueError("receipt reservoir differs from the current ranked top-k")
|
|
1198
|
+
expected_parents = tuple(
|
|
1199
|
+
ranked_candidates[ordinal] for ordinal in receipt.selected_ordinals
|
|
1200
|
+
)
|
|
1201
|
+
if selection.parents != expected_parents:
|
|
1202
|
+
raise ValueError("archive reservoir parents are foreign to current history")
|
|
1203
|
+
|
|
1204
|
+
|
|
1205
|
+
class ArchiveReservoirParentSelector(Protocol):
|
|
1206
|
+
"""Inverted planner seam for singleton-safe ranked parent reservoirs."""
|
|
1207
|
+
|
|
1208
|
+
policy_id: str
|
|
1209
|
+
policy_version: int
|
|
1210
|
+
definition_sha256: str
|
|
1211
|
+
|
|
1212
|
+
def select(
|
|
1213
|
+
self,
|
|
1214
|
+
state: OptimizerState,
|
|
1215
|
+
*,
|
|
1216
|
+
task_sha256: str,
|
|
1217
|
+
expected_archive_snapshot_hash: str,
|
|
1218
|
+
reservoir_limit: int,
|
|
1219
|
+
parent_count: int = 1,
|
|
1220
|
+
rotation_index: int = 0,
|
|
1221
|
+
) -> ArchiveReservoirParentSelection: ...
|
|
1222
|
+
|
|
1223
|
+
def to_record(self) -> dict[str, object]: ...
|
|
1224
|
+
|
|
1225
|
+
|
|
1226
|
+
@dataclass(frozen=True, slots=True)
|
|
1227
|
+
class TaskKeyedArchiveReservoirParentPolicy:
|
|
1228
|
+
"""Rank eligible history generically, then rotate over its top-k reservoir."""
|
|
1229
|
+
|
|
1230
|
+
policy_id: str = field(init=False, default=RESERVOIR_POLICY_ID)
|
|
1231
|
+
policy_version: int = field(init=False, default=RESERVOIR_POLICY_VERSION)
|
|
1232
|
+
definition_sha256: str = field(
|
|
1233
|
+
init=False,
|
|
1234
|
+
default=RESERVOIR_POLICY_DEFINITION_SHA256,
|
|
1235
|
+
)
|
|
1236
|
+
|
|
1237
|
+
def _validate_identity(self) -> None:
|
|
1238
|
+
if type(self) is not TaskKeyedArchiveReservoirParentPolicy:
|
|
1239
|
+
raise TypeError(
|
|
1240
|
+
"policy must be an exact TaskKeyedArchiveReservoirParentPolicy"
|
|
1241
|
+
)
|
|
1242
|
+
if (
|
|
1243
|
+
self.policy_id != RESERVOIR_POLICY_ID
|
|
1244
|
+
or self.policy_version != RESERVOIR_POLICY_VERSION
|
|
1245
|
+
or self.definition_sha256 != RESERVOIR_POLICY_DEFINITION_SHA256
|
|
1246
|
+
):
|
|
1247
|
+
raise ValueError("archive reservoir selector uses a foreign identity")
|
|
1248
|
+
|
|
1249
|
+
def select(
|
|
1250
|
+
self,
|
|
1251
|
+
state: OptimizerState,
|
|
1252
|
+
*,
|
|
1253
|
+
task_sha256: str,
|
|
1254
|
+
expected_archive_snapshot_hash: str,
|
|
1255
|
+
reservoir_limit: int,
|
|
1256
|
+
parent_count: int = 1,
|
|
1257
|
+
rotation_index: int = 0,
|
|
1258
|
+
) -> ArchiveReservoirParentSelection:
|
|
1259
|
+
self._validate_identity()
|
|
1260
|
+
require_sha256(task_sha256, "task_sha256")
|
|
1261
|
+
require_sha256(
|
|
1262
|
+
expected_archive_snapshot_hash,
|
|
1263
|
+
"expected_archive_snapshot_hash",
|
|
1264
|
+
)
|
|
1265
|
+
if type(reservoir_limit) is not int or reservoir_limit <= 0:
|
|
1266
|
+
raise ValueError("reservoir_limit must be a positive exact integer")
|
|
1267
|
+
if type(parent_count) is not int or parent_count <= 0:
|
|
1268
|
+
raise ValueError("parent_count must be a positive exact integer")
|
|
1269
|
+
if (
|
|
1270
|
+
type(rotation_index) is not int
|
|
1271
|
+
or not 0 <= rotation_index <= _MAX_ROTATION_INDEX
|
|
1272
|
+
):
|
|
1273
|
+
raise ValueError("rotation_index must be an exact non-negative int63")
|
|
1274
|
+
ranked_candidates, ranking = _ranked_eligible_history(state)
|
|
1275
|
+
if expected_archive_snapshot_hash != state.archive_snapshot_hash:
|
|
1276
|
+
raise ValueError("expected archive snapshot is stale")
|
|
1277
|
+
|
|
1278
|
+
reservoir_cardinality = min(reservoir_limit, len(ranking))
|
|
1279
|
+
reservoir_candidates = ranked_candidates[:reservoir_cardinality]
|
|
1280
|
+
reservoir = tuple(
|
|
1281
|
+
member.reference for member in ranking[:reservoir_cardinality]
|
|
1282
|
+
)
|
|
1283
|
+
ranking_sha256 = _eligible_ranking_sha256(ranking)
|
|
1284
|
+
reservoir_hash = _reservoir_sha256(reservoir)
|
|
1285
|
+
anchor = _reservoir_rotation_anchor(
|
|
1286
|
+
task_sha256=task_sha256,
|
|
1287
|
+
archive_snapshot_hash=state.archive_snapshot_hash,
|
|
1288
|
+
eligible_ranking_sha256=ranking_sha256,
|
|
1289
|
+
reservoir_sha256=reservoir_hash,
|
|
1290
|
+
cardinality=reservoir_cardinality,
|
|
1291
|
+
)
|
|
1292
|
+
returned_parent_count = min(parent_count, reservoir_cardinality)
|
|
1293
|
+
ordinals = tuple(
|
|
1294
|
+
(anchor + rotation_index + offset) % reservoir_cardinality
|
|
1295
|
+
for offset in range(returned_parent_count)
|
|
1296
|
+
)
|
|
1297
|
+
receipt = ArchiveReservoirParentSelectionReceipt(
|
|
1298
|
+
task_sha256=task_sha256,
|
|
1299
|
+
optimizer_generation=state.generation,
|
|
1300
|
+
archive_snapshot_hash=state.archive_snapshot_hash,
|
|
1301
|
+
eligible_ranking=ranking,
|
|
1302
|
+
reservoir_limit=reservoir_limit,
|
|
1303
|
+
reservoir=reservoir,
|
|
1304
|
+
requested_parent_count=parent_count,
|
|
1305
|
+
returned_parent_count=returned_parent_count,
|
|
1306
|
+
rotation_index=rotation_index,
|
|
1307
|
+
rotation_anchor=anchor,
|
|
1308
|
+
selected_ordinals=ordinals,
|
|
1309
|
+
selected_parents=tuple(reservoir[index] for index in ordinals),
|
|
1310
|
+
)
|
|
1311
|
+
selection = ArchiveReservoirParentSelection(
|
|
1312
|
+
parents=tuple(reservoir_candidates[index] for index in ordinals),
|
|
1313
|
+
receipt=receipt,
|
|
1314
|
+
)
|
|
1315
|
+
validate_archive_reservoir_parent_selection(state, selection)
|
|
1316
|
+
return selection
|
|
1317
|
+
|
|
1318
|
+
def to_record(self) -> dict[str, object]:
|
|
1319
|
+
self._validate_identity()
|
|
1320
|
+
return {
|
|
1321
|
+
"policy_id": self.policy_id,
|
|
1322
|
+
"policy_version": self.policy_version,
|
|
1323
|
+
"definition_sha256": self.definition_sha256,
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
|
|
1327
|
+
__all__ = [
|
|
1328
|
+
"ArchiveEliteParentSelection",
|
|
1329
|
+
"ArchiveEliteParentSelectionReceipt",
|
|
1330
|
+
"ArchiveEliteParentSelector",
|
|
1331
|
+
"ArchiveReservoirCrowdingKind",
|
|
1332
|
+
"ArchiveReservoirParentSelection",
|
|
1333
|
+
"ArchiveReservoirParentSelectionReceipt",
|
|
1334
|
+
"ArchiveReservoirParentSelector",
|
|
1335
|
+
"ArchiveReservoirRankedCandidate",
|
|
1336
|
+
"POLICY_DEFINITION_SHA256",
|
|
1337
|
+
"POLICY_ID",
|
|
1338
|
+
"POLICY_VERSION",
|
|
1339
|
+
"RESERVOIR_POLICY_DEFINITION_SHA256",
|
|
1340
|
+
"RESERVOIR_POLICY_ID",
|
|
1341
|
+
"RESERVOIR_POLICY_VERSION",
|
|
1342
|
+
"TaskKeyedArchiveEliteParentPolicy",
|
|
1343
|
+
"TaskKeyedArchiveReservoirParentPolicy",
|
|
1344
|
+
"validate_archive_elite_parent_selection",
|
|
1345
|
+
"validate_archive_reservoir_parent_selection",
|
|
1346
|
+
]
|