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,979 @@
|
|
|
1
|
+
"""Objective-blind phenotype identity and bounded evaluation recourse.
|
|
2
|
+
|
|
3
|
+
Candidate occurrences are causal units: two independently assigned trials remain
|
|
4
|
+
distinct even when they materialize the same evaluation phenotype. Physical
|
|
5
|
+
evaluation identity is a separate, injected policy. This module joins those two
|
|
6
|
+
views without importing an evaluator, reward, archive, model provider, engine, or
|
|
7
|
+
optimizer.
|
|
8
|
+
|
|
9
|
+
Recourse is deliberately narrow. A pool is sealed before primary outcomes are
|
|
10
|
+
known, only successful *primary* collisions create credit, and successful
|
|
11
|
+
recourse outcomes can never create more credit. Infrastructure/system failures
|
|
12
|
+
in the primary wave invalidate the block; model and candidate failures are valid
|
|
13
|
+
zero-yield causal outcomes. The deterministic slot cap is::
|
|
14
|
+
|
|
15
|
+
min(max_recourse,
|
|
16
|
+
successful_primary_collision_credit,
|
|
17
|
+
unique_budget_after_reservations_and_recombination_protection,
|
|
18
|
+
eligible_presealed_pool_size)
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import hashlib
|
|
24
|
+
import json
|
|
25
|
+
import re
|
|
26
|
+
from collections.abc import Callable, Sequence
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
from enum import Enum
|
|
29
|
+
from itertools import combinations
|
|
30
|
+
from typing import ClassVar, Protocol
|
|
31
|
+
|
|
32
|
+
from agent_evolve.domain.ids import CandidateId, OperatorInvocationId
|
|
33
|
+
from agent_evolve.domain.patch import require_sha256
|
|
34
|
+
from agent_evolve.domain.typed_json import (
|
|
35
|
+
FrozenJsonValue,
|
|
36
|
+
freeze_json,
|
|
37
|
+
is_frozen_json_value,
|
|
38
|
+
typed_json_sha256,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
IDENTITY_POLICY_ID = "typed_configuration_phenotype"
|
|
43
|
+
IDENTITY_POLICY_VERSION = 1
|
|
44
|
+
RECOURSE_POLICY_ID = "bounded_collision_evaluation_recourse"
|
|
45
|
+
RECOURSE_POLICY_VERSION = 1
|
|
46
|
+
|
|
47
|
+
_IDENTITY_DOMAIN = b"agent-evolve:phenotype-identity:v1\x00"
|
|
48
|
+
_LEDGER_DOMAIN = b"agent-evolve:phenotype-occurrence-ledger:v1\x00"
|
|
49
|
+
_POOL_DOMAIN = b"agent-evolve:presealed-recourse-pool:v1\x00"
|
|
50
|
+
_DECISION_DOMAIN = b"agent-evolve:evaluation-recourse-decision:v1\x00"
|
|
51
|
+
_TOKEN = re.compile(r"^[a-z][a-z0-9_.-]{0,127}$")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _token(value: str, *, name: str) -> None:
|
|
55
|
+
if type(value) is not str or _TOKEN.fullmatch(value) is None:
|
|
56
|
+
raise ValueError(f"{name} must use the closed lowercase token grammar")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _positive(value: int, *, name: str) -> None:
|
|
60
|
+
if type(value) is not int or value <= 0:
|
|
61
|
+
raise ValueError(f"{name} must be a positive integer")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _nonnegative(value: int, *, name: str) -> None:
|
|
65
|
+
if type(value) is not int or value < 0:
|
|
66
|
+
raise ValueError(f"{name} must be a non-negative integer")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _canonical_json(value: object) -> bytes:
|
|
70
|
+
return json.dumps(
|
|
71
|
+
value,
|
|
72
|
+
allow_nan=False,
|
|
73
|
+
ensure_ascii=True,
|
|
74
|
+
separators=(",", ":"),
|
|
75
|
+
sort_keys=True,
|
|
76
|
+
).encode("ascii")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _domain_hash(domain: bytes, value: object) -> str:
|
|
80
|
+
return hashlib.sha256(domain + _canonical_json(value)).hexdigest()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass(frozen=True, slots=True)
|
|
84
|
+
class PhenotypeIdentity:
|
|
85
|
+
"""A policy-bound digest used to coalesce physical evaluations."""
|
|
86
|
+
|
|
87
|
+
policy_id: str
|
|
88
|
+
policy_version: int
|
|
89
|
+
value_sha256: str
|
|
90
|
+
|
|
91
|
+
def __post_init__(self) -> None:
|
|
92
|
+
_token(self.policy_id, name="identity policy_id")
|
|
93
|
+
_positive(self.policy_version, name="identity policy_version")
|
|
94
|
+
require_sha256(self.value_sha256, "phenotype value_sha256")
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def identity_sha256(self) -> str:
|
|
98
|
+
return _domain_hash(_IDENTITY_DOMAIN, self.to_trace_record())
|
|
99
|
+
|
|
100
|
+
def to_trace_record(self) -> dict[str, object]:
|
|
101
|
+
return {
|
|
102
|
+
"policy_id": self.policy_id,
|
|
103
|
+
"policy_version": self.policy_version,
|
|
104
|
+
"value_sha256": self.value_sha256,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class PhenotypeIdentityPolicy(Protocol):
|
|
109
|
+
"""Injected boundary between a typed configuration and evaluator identity."""
|
|
110
|
+
|
|
111
|
+
policy_id: str
|
|
112
|
+
policy_version: int
|
|
113
|
+
|
|
114
|
+
def identify(self, configuration: object) -> PhenotypeIdentity: ...
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass(frozen=True, slots=True)
|
|
118
|
+
class TypedConfigurationPhenotypeIdentityPolicy:
|
|
119
|
+
"""Default identity: the exact, type-sensitive configuration tree."""
|
|
120
|
+
|
|
121
|
+
policy_id: ClassVar[str] = IDENTITY_POLICY_ID
|
|
122
|
+
policy_version: ClassVar[int] = IDENTITY_POLICY_VERSION
|
|
123
|
+
|
|
124
|
+
def identify(self, configuration: object) -> PhenotypeIdentity:
|
|
125
|
+
return PhenotypeIdentity(
|
|
126
|
+
policy_id=self.policy_id,
|
|
127
|
+
policy_version=self.policy_version,
|
|
128
|
+
value_sha256=typed_json_sha256(freeze_json(configuration)),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
SemanticProjection = Callable[[FrozenJsonValue], object]
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@dataclass(frozen=True, slots=True)
|
|
136
|
+
class SemanticProjectionPhenotypeIdentityPolicy:
|
|
137
|
+
"""Hash an injected deterministic semantic projection of a configuration.
|
|
138
|
+
|
|
139
|
+
The projector receives an immutable typed-JSON tree and must return another
|
|
140
|
+
typed-JSON value. A problem adapter can therefore erase evaluator-inert
|
|
141
|
+
syntax (aliases, ordering, decoded no-ops) without weakening the default
|
|
142
|
+
type-sensitive identity used by domains that have no such equivalence.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
policy_id: str
|
|
146
|
+
policy_version: int
|
|
147
|
+
projector: SemanticProjection
|
|
148
|
+
|
|
149
|
+
def __post_init__(self) -> None:
|
|
150
|
+
_token(self.policy_id, name="semantic identity policy_id")
|
|
151
|
+
_positive(self.policy_version, name="semantic identity policy_version")
|
|
152
|
+
if not callable(self.projector):
|
|
153
|
+
raise TypeError("projector must be callable")
|
|
154
|
+
|
|
155
|
+
def identify(self, configuration: object) -> PhenotypeIdentity:
|
|
156
|
+
immutable_configuration = freeze_json(configuration)
|
|
157
|
+
projected = freeze_json(self.projector(immutable_configuration))
|
|
158
|
+
return PhenotypeIdentity(
|
|
159
|
+
policy_id=self.policy_id,
|
|
160
|
+
policy_version=self.policy_version,
|
|
161
|
+
value_sha256=typed_json_sha256(projected),
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class EvaluationOccurrenceRole(str, Enum):
|
|
166
|
+
PRIMARY = "primary"
|
|
167
|
+
RECOURSE = "recourse"
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class EvaluationOccurrenceStatus(str, Enum):
|
|
171
|
+
"""Terminal status with explicit causal and block-validity semantics."""
|
|
172
|
+
|
|
173
|
+
SUCCESS = "success"
|
|
174
|
+
CANDIDATE_FAILURE = "candidate_failure"
|
|
175
|
+
MODEL_FAILURE = "model_failure"
|
|
176
|
+
INFRASTRUCTURE_FAILURE = "infrastructure_failure"
|
|
177
|
+
SYSTEM_FAILURE = "system_failure"
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def invalidates_primary_block(self) -> bool:
|
|
181
|
+
return self in (
|
|
182
|
+
EvaluationOccurrenceStatus.INFRASTRUCTURE_FAILURE,
|
|
183
|
+
EvaluationOccurrenceStatus.SYSTEM_FAILURE,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@dataclass(frozen=True, slots=True)
|
|
188
|
+
class PhenotypeOccurrence:
|
|
189
|
+
"""One causal trial occurrence, independent of physical cache identity."""
|
|
190
|
+
|
|
191
|
+
trial_id: OperatorInvocationId
|
|
192
|
+
role: EvaluationOccurrenceRole
|
|
193
|
+
status: EvaluationOccurrenceStatus
|
|
194
|
+
candidate_id: CandidateId | None
|
|
195
|
+
phenotype: PhenotypeIdentity | None
|
|
196
|
+
|
|
197
|
+
def __post_init__(self) -> None:
|
|
198
|
+
if type(self.trial_id) is not OperatorInvocationId:
|
|
199
|
+
raise TypeError("trial_id must be an exact OperatorInvocationId")
|
|
200
|
+
OperatorInvocationId.__post_init__(self.trial_id)
|
|
201
|
+
if type(self.role) is not EvaluationOccurrenceRole:
|
|
202
|
+
raise TypeError("role must be an EvaluationOccurrenceRole")
|
|
203
|
+
if type(self.status) is not EvaluationOccurrenceStatus:
|
|
204
|
+
raise TypeError("status must be an EvaluationOccurrenceStatus")
|
|
205
|
+
if self.candidate_id is not None:
|
|
206
|
+
if type(self.candidate_id) is not CandidateId:
|
|
207
|
+
raise TypeError("candidate_id must be an exact CandidateId or None")
|
|
208
|
+
CandidateId.__post_init__(self.candidate_id)
|
|
209
|
+
if self.phenotype is not None:
|
|
210
|
+
if type(self.phenotype) is not PhenotypeIdentity:
|
|
211
|
+
raise TypeError("phenotype must be an exact PhenotypeIdentity or None")
|
|
212
|
+
PhenotypeIdentity.__post_init__(self.phenotype)
|
|
213
|
+
|
|
214
|
+
has_candidate = self.candidate_id is not None
|
|
215
|
+
has_phenotype = self.phenotype is not None
|
|
216
|
+
if has_candidate != has_phenotype:
|
|
217
|
+
raise ValueError("candidate_id and phenotype must be present together")
|
|
218
|
+
if self.status is EvaluationOccurrenceStatus.SUCCESS and not has_candidate:
|
|
219
|
+
raise ValueError("success requires candidate and phenotype")
|
|
220
|
+
if self.status is EvaluationOccurrenceStatus.MODEL_FAILURE and has_candidate:
|
|
221
|
+
raise ValueError("model_failure cannot carry candidate or phenotype")
|
|
222
|
+
if (
|
|
223
|
+
self.role is EvaluationOccurrenceRole.RECOURSE
|
|
224
|
+
and self.status is EvaluationOccurrenceStatus.MODEL_FAILURE
|
|
225
|
+
):
|
|
226
|
+
raise ValueError("engine-owned recourse cannot have model_failure status")
|
|
227
|
+
|
|
228
|
+
def to_trace_record(self) -> dict[str, object]:
|
|
229
|
+
return {
|
|
230
|
+
"trial_id": self.trial_id.value,
|
|
231
|
+
"role": self.role.value,
|
|
232
|
+
"status": self.status.value,
|
|
233
|
+
"candidate_id": (
|
|
234
|
+
None if self.candidate_id is None else self.candidate_id.value
|
|
235
|
+
),
|
|
236
|
+
"phenotype_identity_sha256": (
|
|
237
|
+
None if self.phenotype is None else self.phenotype.identity_sha256
|
|
238
|
+
),
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _occurrence_key(occurrence: PhenotypeOccurrence) -> str:
|
|
243
|
+
return occurrence.trial_id.value
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@dataclass(frozen=True, slots=True)
|
|
247
|
+
class PhenotypeCluster:
|
|
248
|
+
"""All causal occurrences sharing one physical evaluation phenotype."""
|
|
249
|
+
|
|
250
|
+
phenotype: PhenotypeIdentity
|
|
251
|
+
occurrences: tuple[PhenotypeOccurrence, ...]
|
|
252
|
+
|
|
253
|
+
def __post_init__(self) -> None:
|
|
254
|
+
if type(self.phenotype) is not PhenotypeIdentity:
|
|
255
|
+
raise TypeError("phenotype must be an exact PhenotypeIdentity")
|
|
256
|
+
PhenotypeIdentity.__post_init__(self.phenotype)
|
|
257
|
+
if type(self.occurrences) is not tuple or not self.occurrences:
|
|
258
|
+
raise ValueError("cluster occurrences must be a non-empty exact tuple")
|
|
259
|
+
if any(type(item) is not PhenotypeOccurrence for item in self.occurrences):
|
|
260
|
+
raise TypeError("cluster must contain exact PhenotypeOccurrence values")
|
|
261
|
+
for occurrence in self.occurrences:
|
|
262
|
+
PhenotypeOccurrence.__post_init__(occurrence)
|
|
263
|
+
if occurrence.phenotype != self.phenotype:
|
|
264
|
+
raise ValueError("cluster contains a different phenotype")
|
|
265
|
+
expected = tuple(sorted(self.occurrences, key=_occurrence_key))
|
|
266
|
+
if self.occurrences != expected:
|
|
267
|
+
raise ValueError("cluster occurrences must be canonically ordered")
|
|
268
|
+
if len({item.trial_id for item in self.occurrences}) != len(self.occurrences):
|
|
269
|
+
raise ValueError("cluster trial IDs must be unique")
|
|
270
|
+
|
|
271
|
+
@property
|
|
272
|
+
def successful_primary_occurrences(self) -> tuple[PhenotypeOccurrence, ...]:
|
|
273
|
+
return tuple(
|
|
274
|
+
item
|
|
275
|
+
for item in self.occurrences
|
|
276
|
+
if item.role is EvaluationOccurrenceRole.PRIMARY
|
|
277
|
+
and item.status is EvaluationOccurrenceStatus.SUCCESS
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
@property
|
|
281
|
+
def successful_primary_collision_credit(self) -> int:
|
|
282
|
+
return max(0, len(self.successful_primary_occurrences) - 1)
|
|
283
|
+
|
|
284
|
+
@property
|
|
285
|
+
def zero_identity_contrast_pairs(
|
|
286
|
+
self,
|
|
287
|
+
) -> tuple[tuple[OperatorInvocationId, OperatorInvocationId], ...]:
|
|
288
|
+
"""Successful primary pairs whose evaluation contrast is zero by identity."""
|
|
289
|
+
|
|
290
|
+
return tuple(
|
|
291
|
+
(left.trial_id, right.trial_id)
|
|
292
|
+
for left, right in combinations(self.successful_primary_occurrences, 2)
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
def to_trace_record(self) -> dict[str, object]:
|
|
296
|
+
return {
|
|
297
|
+
"phenotype": self.phenotype.to_trace_record(),
|
|
298
|
+
"phenotype_identity_sha256": self.phenotype.identity_sha256,
|
|
299
|
+
"occurrences": [item.to_trace_record() for item in self.occurrences],
|
|
300
|
+
"successful_primary_collision_credit": (
|
|
301
|
+
self.successful_primary_collision_credit
|
|
302
|
+
),
|
|
303
|
+
"zero_identity_contrast_pairs": [
|
|
304
|
+
[left.value, right.value]
|
|
305
|
+
for left, right in self.zero_identity_contrast_pairs
|
|
306
|
+
],
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
311
|
+
class PhenotypeOccurrenceLedger:
|
|
312
|
+
"""Canonical occurrence history and its phenotype clusters."""
|
|
313
|
+
|
|
314
|
+
identity_policy_id: str
|
|
315
|
+
identity_policy_version: int
|
|
316
|
+
occurrences: tuple[PhenotypeOccurrence, ...]
|
|
317
|
+
|
|
318
|
+
def __post_init__(self) -> None:
|
|
319
|
+
_token(self.identity_policy_id, name="ledger identity_policy_id")
|
|
320
|
+
_positive(
|
|
321
|
+
self.identity_policy_version,
|
|
322
|
+
name="ledger identity_policy_version",
|
|
323
|
+
)
|
|
324
|
+
if type(self.occurrences) is not tuple or any(
|
|
325
|
+
type(item) is not PhenotypeOccurrence for item in self.occurrences
|
|
326
|
+
):
|
|
327
|
+
raise TypeError("occurrences must contain exact PhenotypeOccurrence values")
|
|
328
|
+
for occurrence in self.occurrences:
|
|
329
|
+
PhenotypeOccurrence.__post_init__(occurrence)
|
|
330
|
+
phenotype = occurrence.phenotype
|
|
331
|
+
if phenotype is not None and (
|
|
332
|
+
phenotype.policy_id != self.identity_policy_id
|
|
333
|
+
or phenotype.policy_version != self.identity_policy_version
|
|
334
|
+
):
|
|
335
|
+
raise ValueError(
|
|
336
|
+
"occurrence phenotype uses a different identity policy"
|
|
337
|
+
)
|
|
338
|
+
expected = tuple(sorted(self.occurrences, key=_occurrence_key))
|
|
339
|
+
if self.occurrences != expected:
|
|
340
|
+
raise ValueError("occurrences must be canonically ordered by trial ID")
|
|
341
|
+
if len({item.trial_id for item in self.occurrences}) != len(self.occurrences):
|
|
342
|
+
raise ValueError("occurrence trial IDs must be unique")
|
|
343
|
+
candidate_ids = tuple(
|
|
344
|
+
item.candidate_id
|
|
345
|
+
for item in self.occurrences
|
|
346
|
+
if item.candidate_id is not None
|
|
347
|
+
)
|
|
348
|
+
if len(set(candidate_ids)) != len(candidate_ids):
|
|
349
|
+
raise ValueError("occurrence candidate IDs must be unique")
|
|
350
|
+
|
|
351
|
+
@classmethod
|
|
352
|
+
def build(
|
|
353
|
+
cls,
|
|
354
|
+
occurrences: Sequence[PhenotypeOccurrence],
|
|
355
|
+
*,
|
|
356
|
+
identity_policy: PhenotypeIdentityPolicy,
|
|
357
|
+
) -> "PhenotypeOccurrenceLedger":
|
|
358
|
+
if isinstance(occurrences, (str, bytes)):
|
|
359
|
+
raise TypeError("occurrences must be a finite occurrence sequence")
|
|
360
|
+
if not callable(getattr(identity_policy, "identify", None)):
|
|
361
|
+
raise TypeError("identity_policy must implement identify")
|
|
362
|
+
policy_id = getattr(identity_policy, "policy_id", None)
|
|
363
|
+
policy_version = getattr(identity_policy, "policy_version", None)
|
|
364
|
+
_token(policy_id, name="identity policy_id")
|
|
365
|
+
_positive(policy_version, name="identity policy_version")
|
|
366
|
+
values = tuple(occurrences)
|
|
367
|
+
for value in values:
|
|
368
|
+
if type(value) is not PhenotypeOccurrence:
|
|
369
|
+
raise TypeError(
|
|
370
|
+
"occurrences must contain exact PhenotypeOccurrence values"
|
|
371
|
+
)
|
|
372
|
+
return cls(
|
|
373
|
+
identity_policy_id=policy_id,
|
|
374
|
+
identity_policy_version=policy_version,
|
|
375
|
+
occurrences=tuple(sorted(values, key=_occurrence_key)),
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
@property
|
|
379
|
+
def clusters(self) -> tuple[PhenotypeCluster, ...]:
|
|
380
|
+
grouped: dict[str, list[PhenotypeOccurrence]] = {}
|
|
381
|
+
identities: dict[str, PhenotypeIdentity] = {}
|
|
382
|
+
for occurrence in self.occurrences:
|
|
383
|
+
phenotype = occurrence.phenotype
|
|
384
|
+
if phenotype is None:
|
|
385
|
+
continue
|
|
386
|
+
key = phenotype.identity_sha256
|
|
387
|
+
identities[key] = phenotype
|
|
388
|
+
grouped.setdefault(key, []).append(occurrence)
|
|
389
|
+
return tuple(
|
|
390
|
+
PhenotypeCluster(identities[key], tuple(grouped[key]))
|
|
391
|
+
for key in sorted(grouped)
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
@property
|
|
395
|
+
def successful_primary_collision_credit(self) -> int:
|
|
396
|
+
return sum(
|
|
397
|
+
cluster.successful_primary_collision_credit for cluster in self.clusters
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
@property
|
|
401
|
+
def primary_occurrences(self) -> tuple[PhenotypeOccurrence, ...]:
|
|
402
|
+
return tuple(
|
|
403
|
+
item
|
|
404
|
+
for item in self.occurrences
|
|
405
|
+
if item.role is EvaluationOccurrenceRole.PRIMARY
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
@property
|
|
409
|
+
def primary_phenotypes(self) -> frozenset[PhenotypeIdentity]:
|
|
410
|
+
return frozenset(
|
|
411
|
+
item.phenotype
|
|
412
|
+
for item in self.primary_occurrences
|
|
413
|
+
if item.phenotype is not None
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
@property
|
|
417
|
+
def invalidating_primary_trial_ids(self) -> tuple[OperatorInvocationId, ...]:
|
|
418
|
+
return tuple(
|
|
419
|
+
item.trial_id
|
|
420
|
+
for item in self.primary_occurrences
|
|
421
|
+
if item.status.invalidates_primary_block
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
@property
|
|
425
|
+
def invalidating_trial_ids(self) -> tuple[OperatorInvocationId, ...]:
|
|
426
|
+
"""Every infrastructure/system failure, including post-decision recourse."""
|
|
427
|
+
|
|
428
|
+
return tuple(
|
|
429
|
+
item.trial_id
|
|
430
|
+
for item in self.occurrences
|
|
431
|
+
if item.status.invalidates_primary_block
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
@property
|
|
435
|
+
def primary_block_valid(self) -> bool:
|
|
436
|
+
return not self.invalidating_primary_trial_ids
|
|
437
|
+
|
|
438
|
+
@property
|
|
439
|
+
def experiment_block_valid(self) -> bool:
|
|
440
|
+
"""False if infrastructure/system failure occurred at any wave stage."""
|
|
441
|
+
|
|
442
|
+
return not self.invalidating_trial_ids
|
|
443
|
+
|
|
444
|
+
@property
|
|
445
|
+
def ignored_recourse_trial_ids(self) -> tuple[OperatorInvocationId, ...]:
|
|
446
|
+
return tuple(
|
|
447
|
+
item.trial_id
|
|
448
|
+
for item in self.occurrences
|
|
449
|
+
if item.role is EvaluationOccurrenceRole.RECOURSE
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
def is_zero_identity_contrast(
|
|
453
|
+
self,
|
|
454
|
+
left_trial_id: OperatorInvocationId,
|
|
455
|
+
right_trial_id: OperatorInvocationId,
|
|
456
|
+
) -> bool:
|
|
457
|
+
"""Whether two successful primary trials map to one phenotype."""
|
|
458
|
+
|
|
459
|
+
for value, name in (
|
|
460
|
+
(left_trial_id, "left_trial_id"),
|
|
461
|
+
(right_trial_id, "right_trial_id"),
|
|
462
|
+
):
|
|
463
|
+
if type(value) is not OperatorInvocationId:
|
|
464
|
+
raise TypeError(f"{name} must be an exact OperatorInvocationId")
|
|
465
|
+
if left_trial_id == right_trial_id:
|
|
466
|
+
raise ValueError("contrast trial IDs must be distinct")
|
|
467
|
+
by_id = {item.trial_id: item for item in self.occurrences}
|
|
468
|
+
if left_trial_id not in by_id or right_trial_id not in by_id:
|
|
469
|
+
raise KeyError("contrast trial ID is absent from the ledger")
|
|
470
|
+
left = by_id[left_trial_id]
|
|
471
|
+
right = by_id[right_trial_id]
|
|
472
|
+
return (
|
|
473
|
+
left.role is EvaluationOccurrenceRole.PRIMARY
|
|
474
|
+
and right.role is EvaluationOccurrenceRole.PRIMARY
|
|
475
|
+
and left.status is EvaluationOccurrenceStatus.SUCCESS
|
|
476
|
+
and right.status is EvaluationOccurrenceStatus.SUCCESS
|
|
477
|
+
and left.phenotype is not None
|
|
478
|
+
and left.phenotype == right.phenotype
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
def _trace_payload(self) -> dict[str, object]:
|
|
482
|
+
return {
|
|
483
|
+
"identity_policy_id": self.identity_policy_id,
|
|
484
|
+
"identity_policy_version": self.identity_policy_version,
|
|
485
|
+
"occurrences": [item.to_trace_record() for item in self.occurrences],
|
|
486
|
+
"clusters": [cluster.to_trace_record() for cluster in self.clusters],
|
|
487
|
+
"successful_primary_collision_credit": (
|
|
488
|
+
self.successful_primary_collision_credit
|
|
489
|
+
),
|
|
490
|
+
"invalidating_primary_trial_ids": [
|
|
491
|
+
item.value for item in self.invalidating_primary_trial_ids
|
|
492
|
+
],
|
|
493
|
+
"invalidating_trial_ids": [
|
|
494
|
+
item.value for item in self.invalidating_trial_ids
|
|
495
|
+
],
|
|
496
|
+
"primary_block_valid": self.primary_block_valid,
|
|
497
|
+
"experiment_block_valid": self.experiment_block_valid,
|
|
498
|
+
"ignored_recourse_trial_ids": [
|
|
499
|
+
item.value for item in self.ignored_recourse_trial_ids
|
|
500
|
+
],
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
@property
|
|
504
|
+
def ledger_sha256(self) -> str:
|
|
505
|
+
return _domain_hash(_LEDGER_DOMAIN, self._trace_payload())
|
|
506
|
+
|
|
507
|
+
def to_trace_record(self) -> dict[str, object]:
|
|
508
|
+
return {**self._trace_payload(), "ledger_sha256": self.ledger_sha256}
|
|
509
|
+
|
|
510
|
+
def __eq__(self, other: object) -> bool:
|
|
511
|
+
return (
|
|
512
|
+
type(self) is PhenotypeOccurrenceLedger
|
|
513
|
+
and type(other) is PhenotypeOccurrenceLedger
|
|
514
|
+
and self.ledger_sha256 == other.ledger_sha256
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
__hash__ = None
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
@dataclass(frozen=True, slots=True)
|
|
521
|
+
class RecoursePoolCandidate:
|
|
522
|
+
"""An engine-authored recourse candidate before identity materialization."""
|
|
523
|
+
|
|
524
|
+
entry_id: str
|
|
525
|
+
configuration: FrozenJsonValue
|
|
526
|
+
|
|
527
|
+
def __post_init__(self) -> None:
|
|
528
|
+
_token(self.entry_id, name="recourse entry_id")
|
|
529
|
+
if not is_frozen_json_value(self.configuration):
|
|
530
|
+
raise TypeError("recourse configuration must already be frozen typed JSON")
|
|
531
|
+
freeze_json(self.configuration)
|
|
532
|
+
|
|
533
|
+
@classmethod
|
|
534
|
+
def freeze(cls, entry_id: str, configuration: object) -> "RecoursePoolCandidate":
|
|
535
|
+
return cls(entry_id=entry_id, configuration=freeze_json(configuration))
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
@dataclass(frozen=True, slots=True)
|
|
539
|
+
class RecoursePoolEntry:
|
|
540
|
+
"""One ordered, identity-bound candidate in a pre-outcome pool."""
|
|
541
|
+
|
|
542
|
+
ordinal: int
|
|
543
|
+
candidate: RecoursePoolCandidate
|
|
544
|
+
phenotype: PhenotypeIdentity
|
|
545
|
+
|
|
546
|
+
def __post_init__(self) -> None:
|
|
547
|
+
_nonnegative(self.ordinal, name="recourse entry ordinal")
|
|
548
|
+
if type(self.candidate) is not RecoursePoolCandidate:
|
|
549
|
+
raise TypeError("candidate must be an exact RecoursePoolCandidate")
|
|
550
|
+
RecoursePoolCandidate.__post_init__(self.candidate)
|
|
551
|
+
if type(self.phenotype) is not PhenotypeIdentity:
|
|
552
|
+
raise TypeError("phenotype must be an exact PhenotypeIdentity")
|
|
553
|
+
PhenotypeIdentity.__post_init__(self.phenotype)
|
|
554
|
+
|
|
555
|
+
@property
|
|
556
|
+
def entry_id(self) -> str:
|
|
557
|
+
return self.candidate.entry_id
|
|
558
|
+
|
|
559
|
+
def to_trace_record(self) -> dict[str, object]:
|
|
560
|
+
return {
|
|
561
|
+
"ordinal": self.ordinal,
|
|
562
|
+
"entry_id": self.entry_id,
|
|
563
|
+
"typed_configuration_sha256": typed_json_sha256(
|
|
564
|
+
self.candidate.configuration
|
|
565
|
+
),
|
|
566
|
+
"phenotype_identity_sha256": self.phenotype.identity_sha256,
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
571
|
+
class PresealedRecoursePool:
|
|
572
|
+
"""An ordered recourse pool cryptographically bound before outcomes."""
|
|
573
|
+
|
|
574
|
+
pool_id: str
|
|
575
|
+
seal_context_sha256: str
|
|
576
|
+
identity_policy_id: str
|
|
577
|
+
identity_policy_version: int
|
|
578
|
+
entries: tuple[RecoursePoolEntry, ...]
|
|
579
|
+
|
|
580
|
+
def __post_init__(self) -> None:
|
|
581
|
+
_token(self.pool_id, name="recourse pool_id")
|
|
582
|
+
require_sha256(self.seal_context_sha256, "seal_context_sha256")
|
|
583
|
+
_token(self.identity_policy_id, name="pool identity_policy_id")
|
|
584
|
+
_positive(
|
|
585
|
+
self.identity_policy_version,
|
|
586
|
+
name="pool identity_policy_version",
|
|
587
|
+
)
|
|
588
|
+
if type(self.entries) is not tuple or any(
|
|
589
|
+
type(item) is not RecoursePoolEntry for item in self.entries
|
|
590
|
+
):
|
|
591
|
+
raise TypeError("entries must contain exact RecoursePoolEntry values")
|
|
592
|
+
for entry in self.entries:
|
|
593
|
+
RecoursePoolEntry.__post_init__(entry)
|
|
594
|
+
if (
|
|
595
|
+
entry.phenotype.policy_id != self.identity_policy_id
|
|
596
|
+
or entry.phenotype.policy_version != self.identity_policy_version
|
|
597
|
+
):
|
|
598
|
+
raise ValueError("pool entry uses a different identity policy")
|
|
599
|
+
if tuple(entry.ordinal for entry in self.entries) != tuple(
|
|
600
|
+
range(len(self.entries))
|
|
601
|
+
):
|
|
602
|
+
raise ValueError("pool ordinals must be contiguous and ordered from zero")
|
|
603
|
+
if len({entry.entry_id for entry in self.entries}) != len(self.entries):
|
|
604
|
+
raise ValueError("recourse pool entry IDs must be unique")
|
|
605
|
+
identities = tuple(entry.phenotype for entry in self.entries)
|
|
606
|
+
if len(set(identities)) != len(identities):
|
|
607
|
+
raise ValueError("recourse pool phenotypes must be unique")
|
|
608
|
+
|
|
609
|
+
@classmethod
|
|
610
|
+
def seal(
|
|
611
|
+
cls,
|
|
612
|
+
*,
|
|
613
|
+
pool_id: str,
|
|
614
|
+
seal_context_sha256: str,
|
|
615
|
+
candidates: Sequence[RecoursePoolCandidate],
|
|
616
|
+
identity_policy: PhenotypeIdentityPolicy,
|
|
617
|
+
) -> "PresealedRecoursePool":
|
|
618
|
+
if isinstance(candidates, (str, bytes)):
|
|
619
|
+
raise TypeError("candidates must be a finite candidate sequence")
|
|
620
|
+
if not callable(getattr(identity_policy, "identify", None)):
|
|
621
|
+
raise TypeError("identity_policy must implement identify")
|
|
622
|
+
policy_id = getattr(identity_policy, "policy_id", None)
|
|
623
|
+
policy_version = getattr(identity_policy, "policy_version", None)
|
|
624
|
+
_token(policy_id, name="identity policy_id")
|
|
625
|
+
_positive(policy_version, name="identity policy_version")
|
|
626
|
+
values = tuple(candidates)
|
|
627
|
+
entries: list[RecoursePoolEntry] = []
|
|
628
|
+
for ordinal, candidate in enumerate(values):
|
|
629
|
+
if type(candidate) is not RecoursePoolCandidate:
|
|
630
|
+
raise TypeError(
|
|
631
|
+
"candidates must contain exact RecoursePoolCandidate values"
|
|
632
|
+
)
|
|
633
|
+
identity = identity_policy.identify(candidate.configuration)
|
|
634
|
+
if type(identity) is not PhenotypeIdentity:
|
|
635
|
+
raise TypeError("identity policy must return exact PhenotypeIdentity")
|
|
636
|
+
if (
|
|
637
|
+
identity.policy_id != policy_id
|
|
638
|
+
or identity.policy_version != policy_version
|
|
639
|
+
):
|
|
640
|
+
raise ValueError(
|
|
641
|
+
"identity policy returned inconsistent policy metadata"
|
|
642
|
+
)
|
|
643
|
+
entries.append(RecoursePoolEntry(ordinal, candidate, identity))
|
|
644
|
+
return cls(
|
|
645
|
+
pool_id=pool_id,
|
|
646
|
+
seal_context_sha256=seal_context_sha256,
|
|
647
|
+
identity_policy_id=policy_id,
|
|
648
|
+
identity_policy_version=policy_version,
|
|
649
|
+
entries=tuple(entries),
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
def _trace_payload(self) -> dict[str, object]:
|
|
653
|
+
return {
|
|
654
|
+
"pool_id": self.pool_id,
|
|
655
|
+
"seal_context_sha256": self.seal_context_sha256,
|
|
656
|
+
"identity_policy_id": self.identity_policy_id,
|
|
657
|
+
"identity_policy_version": self.identity_policy_version,
|
|
658
|
+
"entries": [entry.to_trace_record() for entry in self.entries],
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
@property
|
|
662
|
+
def pool_sha256(self) -> str:
|
|
663
|
+
return _domain_hash(_POOL_DOMAIN, self._trace_payload())
|
|
664
|
+
|
|
665
|
+
def to_trace_record(self) -> dict[str, object]:
|
|
666
|
+
return {**self._trace_payload(), "pool_sha256": self.pool_sha256}
|
|
667
|
+
|
|
668
|
+
def __eq__(self, other: object) -> bool:
|
|
669
|
+
return (
|
|
670
|
+
type(self) is PresealedRecoursePool
|
|
671
|
+
and type(other) is PresealedRecoursePool
|
|
672
|
+
and self.pool_sha256 == other.pool_sha256
|
|
673
|
+
)
|
|
674
|
+
|
|
675
|
+
__hash__ = None
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
@dataclass(frozen=True, slots=True)
|
|
679
|
+
class RecourseBudgetSnapshot:
|
|
680
|
+
"""Unique-evaluation facts visible to the objective-blind recourse policy."""
|
|
681
|
+
|
|
682
|
+
max_unique_evaluations: int
|
|
683
|
+
used_unique_evaluations: int
|
|
684
|
+
reserved_non_recourse_evaluations: int
|
|
685
|
+
protected_recombination_evaluations: int
|
|
686
|
+
|
|
687
|
+
def __post_init__(self) -> None:
|
|
688
|
+
_nonnegative(self.max_unique_evaluations, name="max_unique_evaluations")
|
|
689
|
+
_nonnegative(self.used_unique_evaluations, name="used_unique_evaluations")
|
|
690
|
+
_nonnegative(
|
|
691
|
+
self.reserved_non_recourse_evaluations,
|
|
692
|
+
name="reserved_non_recourse_evaluations",
|
|
693
|
+
)
|
|
694
|
+
_nonnegative(
|
|
695
|
+
self.protected_recombination_evaluations,
|
|
696
|
+
name="protected_recombination_evaluations",
|
|
697
|
+
)
|
|
698
|
+
if self.used_unique_evaluations > self.max_unique_evaluations:
|
|
699
|
+
raise ValueError("used_unique_evaluations exceeds the hard maximum")
|
|
700
|
+
if (
|
|
701
|
+
self.used_unique_evaluations
|
|
702
|
+
+ self.reserved_non_recourse_evaluations
|
|
703
|
+
+ self.protected_recombination_evaluations
|
|
704
|
+
> self.max_unique_evaluations
|
|
705
|
+
):
|
|
706
|
+
raise ValueError(
|
|
707
|
+
"used, reserved, and protected evaluations exceed the hard maximum"
|
|
708
|
+
)
|
|
709
|
+
|
|
710
|
+
@property
|
|
711
|
+
def available_for_recourse(self) -> int:
|
|
712
|
+
return max(
|
|
713
|
+
0,
|
|
714
|
+
self.max_unique_evaluations
|
|
715
|
+
- self.used_unique_evaluations
|
|
716
|
+
- self.reserved_non_recourse_evaluations
|
|
717
|
+
- self.protected_recombination_evaluations,
|
|
718
|
+
)
|
|
719
|
+
|
|
720
|
+
def to_trace_record(self) -> dict[str, int]:
|
|
721
|
+
return {
|
|
722
|
+
"max_unique_evaluations": self.max_unique_evaluations,
|
|
723
|
+
"used_unique_evaluations": self.used_unique_evaluations,
|
|
724
|
+
"reserved_non_recourse_evaluations": (
|
|
725
|
+
self.reserved_non_recourse_evaluations
|
|
726
|
+
),
|
|
727
|
+
"protected_recombination_evaluations": (
|
|
728
|
+
self.protected_recombination_evaluations
|
|
729
|
+
),
|
|
730
|
+
"available_for_recourse": self.available_for_recourse,
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
class RecourseDecisionReason(str, Enum):
|
|
735
|
+
SELECTED = "selected"
|
|
736
|
+
PRIMARY_BLOCK_INVALID = "primary_block_invalid"
|
|
737
|
+
MAX_RECOURSE_ZERO = "max_recourse_zero"
|
|
738
|
+
NO_SUCCESSFUL_PRIMARY_COLLISION = "no_successful_primary_collision"
|
|
739
|
+
NO_ELIGIBLE_PRESEALED_CANDIDATE = "no_eligible_presealed_candidate"
|
|
740
|
+
UNIQUE_BUDGET_PROTECTED = "unique_budget_protected"
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
def _eligible_pool_entries(
|
|
744
|
+
ledger: PhenotypeOccurrenceLedger,
|
|
745
|
+
pool: PresealedRecoursePool,
|
|
746
|
+
) -> tuple[RecoursePoolEntry, ...]:
|
|
747
|
+
# Recourse outcomes are intentionally absent: they cannot change this pool
|
|
748
|
+
# or trigger a second recourse wave. Every primary identity-bearing outcome
|
|
749
|
+
# is excluded because repeating it cannot require a new physical evaluation.
|
|
750
|
+
occupied = ledger.primary_phenotypes
|
|
751
|
+
return tuple(entry for entry in pool.entries if entry.phenotype not in occupied)
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
def _decision_facts(
|
|
755
|
+
*,
|
|
756
|
+
ledger: PhenotypeOccurrenceLedger,
|
|
757
|
+
pool: PresealedRecoursePool,
|
|
758
|
+
budget: RecourseBudgetSnapshot,
|
|
759
|
+
max_recourse: int,
|
|
760
|
+
) -> tuple[
|
|
761
|
+
tuple[RecoursePoolEntry, ...],
|
|
762
|
+
int,
|
|
763
|
+
RecourseDecisionReason,
|
|
764
|
+
]:
|
|
765
|
+
eligible = _eligible_pool_entries(ledger, pool)
|
|
766
|
+
invalidating = ledger.invalidating_primary_trial_ids
|
|
767
|
+
credit = ledger.successful_primary_collision_credit
|
|
768
|
+
if invalidating:
|
|
769
|
+
return eligible, 0, RecourseDecisionReason.PRIMARY_BLOCK_INVALID
|
|
770
|
+
|
|
771
|
+
slots = min(
|
|
772
|
+
max_recourse,
|
|
773
|
+
credit,
|
|
774
|
+
budget.available_for_recourse,
|
|
775
|
+
len(eligible),
|
|
776
|
+
)
|
|
777
|
+
if slots > 0:
|
|
778
|
+
return eligible, slots, RecourseDecisionReason.SELECTED
|
|
779
|
+
if max_recourse == 0:
|
|
780
|
+
reason = RecourseDecisionReason.MAX_RECOURSE_ZERO
|
|
781
|
+
elif credit == 0:
|
|
782
|
+
reason = RecourseDecisionReason.NO_SUCCESSFUL_PRIMARY_COLLISION
|
|
783
|
+
elif not eligible:
|
|
784
|
+
reason = RecourseDecisionReason.NO_ELIGIBLE_PRESEALED_CANDIDATE
|
|
785
|
+
else:
|
|
786
|
+
reason = RecourseDecisionReason.UNIQUE_BUDGET_PROTECTED
|
|
787
|
+
return eligible, 0, reason
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
791
|
+
class EvaluationRecourseDecision:
|
|
792
|
+
"""Complete, replayable, objective-free recourse decision."""
|
|
793
|
+
|
|
794
|
+
ledger: PhenotypeOccurrenceLedger
|
|
795
|
+
pool: PresealedRecoursePool
|
|
796
|
+
budget: RecourseBudgetSnapshot
|
|
797
|
+
max_recourse: int
|
|
798
|
+
selected_entry_ids: tuple[str, ...]
|
|
799
|
+
reason: RecourseDecisionReason
|
|
800
|
+
|
|
801
|
+
policy_id: ClassVar[str] = RECOURSE_POLICY_ID
|
|
802
|
+
policy_version: ClassVar[int] = RECOURSE_POLICY_VERSION
|
|
803
|
+
|
|
804
|
+
def __post_init__(self) -> None:
|
|
805
|
+
if type(self.ledger) is not PhenotypeOccurrenceLedger:
|
|
806
|
+
raise TypeError("ledger must be an exact PhenotypeOccurrenceLedger")
|
|
807
|
+
PhenotypeOccurrenceLedger.__post_init__(self.ledger)
|
|
808
|
+
if type(self.pool) is not PresealedRecoursePool:
|
|
809
|
+
raise TypeError("pool must be an exact PresealedRecoursePool")
|
|
810
|
+
PresealedRecoursePool.__post_init__(self.pool)
|
|
811
|
+
if type(self.budget) is not RecourseBudgetSnapshot:
|
|
812
|
+
raise TypeError("budget must be an exact RecourseBudgetSnapshot")
|
|
813
|
+
RecourseBudgetSnapshot.__post_init__(self.budget)
|
|
814
|
+
_nonnegative(self.max_recourse, name="max_recourse")
|
|
815
|
+
if (
|
|
816
|
+
self.ledger.identity_policy_id != self.pool.identity_policy_id
|
|
817
|
+
or self.ledger.identity_policy_version != self.pool.identity_policy_version
|
|
818
|
+
):
|
|
819
|
+
raise ValueError("ledger and pool identity policies differ")
|
|
820
|
+
if type(self.selected_entry_ids) is not tuple or any(
|
|
821
|
+
type(item) is not str for item in self.selected_entry_ids
|
|
822
|
+
):
|
|
823
|
+
raise TypeError("selected_entry_ids must be an exact string tuple")
|
|
824
|
+
if type(self.reason) is not RecourseDecisionReason:
|
|
825
|
+
raise TypeError("reason must be a RecourseDecisionReason")
|
|
826
|
+
eligible, slots, expected_reason = _decision_facts(
|
|
827
|
+
ledger=self.ledger,
|
|
828
|
+
pool=self.pool,
|
|
829
|
+
budget=self.budget,
|
|
830
|
+
max_recourse=self.max_recourse,
|
|
831
|
+
)
|
|
832
|
+
expected_ids = tuple(entry.entry_id for entry in eligible[:slots])
|
|
833
|
+
if self.selected_entry_ids != expected_ids:
|
|
834
|
+
raise ValueError("selected entries do not match the bounded prefix rule")
|
|
835
|
+
if self.reason is not expected_reason:
|
|
836
|
+
raise ValueError("decision reason does not match the bounded rule")
|
|
837
|
+
|
|
838
|
+
@property
|
|
839
|
+
def eligible_entries(self) -> tuple[RecoursePoolEntry, ...]:
|
|
840
|
+
return _eligible_pool_entries(self.ledger, self.pool)
|
|
841
|
+
|
|
842
|
+
@property
|
|
843
|
+
def selected_entries(self) -> tuple[RecoursePoolEntry, ...]:
|
|
844
|
+
by_id = {entry.entry_id: entry for entry in self.pool.entries}
|
|
845
|
+
return tuple(by_id[entry_id] for entry_id in self.selected_entry_ids)
|
|
846
|
+
|
|
847
|
+
@property
|
|
848
|
+
def slots(self) -> int:
|
|
849
|
+
return len(self.selected_entry_ids)
|
|
850
|
+
|
|
851
|
+
def _trace_payload(self) -> dict[str, object]:
|
|
852
|
+
return {
|
|
853
|
+
"event_type": "evaluation_recourse_decided",
|
|
854
|
+
"policy_id": self.policy_id,
|
|
855
|
+
"policy_version": self.policy_version,
|
|
856
|
+
"ledger_sha256": self.ledger.ledger_sha256,
|
|
857
|
+
"pool_sha256": self.pool.pool_sha256,
|
|
858
|
+
"budget": self.budget.to_trace_record(),
|
|
859
|
+
"max_recourse": self.max_recourse,
|
|
860
|
+
"successful_primary_collision_credit": (
|
|
861
|
+
self.ledger.successful_primary_collision_credit
|
|
862
|
+
),
|
|
863
|
+
"collision_credit_source": "successful_primary_occurrences_only",
|
|
864
|
+
"invalidating_primary_trial_ids": [
|
|
865
|
+
item.value for item in self.ledger.invalidating_primary_trial_ids
|
|
866
|
+
],
|
|
867
|
+
"ignored_recourse_trial_ids": [
|
|
868
|
+
item.value for item in self.ledger.ignored_recourse_trial_ids
|
|
869
|
+
],
|
|
870
|
+
"eligible_entry_ids": [entry.entry_id for entry in self.eligible_entries],
|
|
871
|
+
"selected_entry_ids": list(self.selected_entry_ids),
|
|
872
|
+
"slots": self.slots,
|
|
873
|
+
"reason": self.reason.value,
|
|
874
|
+
"slot_formula": (
|
|
875
|
+
"min(max_recourse, successful_primary_collision_credit, "
|
|
876
|
+
"available_unique_budget_after_reservations_and_recombination, "
|
|
877
|
+
"eligible_presealed_pool_size)"
|
|
878
|
+
),
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
@property
|
|
882
|
+
def decision_sha256(self) -> str:
|
|
883
|
+
return _domain_hash(_DECISION_DOMAIN, self._trace_payload())
|
|
884
|
+
|
|
885
|
+
def to_trace_record(self) -> dict[str, object]:
|
|
886
|
+
return {**self._trace_payload(), "decision_sha256": self.decision_sha256}
|
|
887
|
+
|
|
888
|
+
def __eq__(self, other: object) -> bool:
|
|
889
|
+
return (
|
|
890
|
+
type(self) is EvaluationRecourseDecision
|
|
891
|
+
and type(other) is EvaluationRecourseDecision
|
|
892
|
+
and self.decision_sha256 == other.decision_sha256
|
|
893
|
+
)
|
|
894
|
+
|
|
895
|
+
__hash__ = None
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
class EvaluationRecoursePolicy(Protocol):
|
|
899
|
+
"""Objective-blind policy over identities, statuses, and reservations only."""
|
|
900
|
+
|
|
901
|
+
def decide(
|
|
902
|
+
self,
|
|
903
|
+
*,
|
|
904
|
+
ledger: PhenotypeOccurrenceLedger,
|
|
905
|
+
pool: PresealedRecoursePool,
|
|
906
|
+
budget: RecourseBudgetSnapshot,
|
|
907
|
+
) -> EvaluationRecourseDecision: ...
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
@dataclass(frozen=True, slots=True)
|
|
911
|
+
class BoundedEvaluationRecoursePolicy:
|
|
912
|
+
"""Choose a deterministic prefix under collision and hard-budget caps."""
|
|
913
|
+
|
|
914
|
+
max_recourse: int
|
|
915
|
+
|
|
916
|
+
policy_id: ClassVar[str] = RECOURSE_POLICY_ID
|
|
917
|
+
policy_version: ClassVar[int] = RECOURSE_POLICY_VERSION
|
|
918
|
+
|
|
919
|
+
def __post_init__(self) -> None:
|
|
920
|
+
_nonnegative(self.max_recourse, name="max_recourse")
|
|
921
|
+
|
|
922
|
+
def decide(
|
|
923
|
+
self,
|
|
924
|
+
*,
|
|
925
|
+
ledger: PhenotypeOccurrenceLedger,
|
|
926
|
+
pool: PresealedRecoursePool,
|
|
927
|
+
budget: RecourseBudgetSnapshot,
|
|
928
|
+
) -> EvaluationRecourseDecision:
|
|
929
|
+
for value, expected, name in (
|
|
930
|
+
(ledger, PhenotypeOccurrenceLedger, "ledger"),
|
|
931
|
+
(pool, PresealedRecoursePool, "pool"),
|
|
932
|
+
(budget, RecourseBudgetSnapshot, "budget"),
|
|
933
|
+
):
|
|
934
|
+
if type(value) is not expected:
|
|
935
|
+
raise TypeError(f"{name} must be an exact {expected.__name__}")
|
|
936
|
+
if (
|
|
937
|
+
ledger.identity_policy_id != pool.identity_policy_id
|
|
938
|
+
or ledger.identity_policy_version != pool.identity_policy_version
|
|
939
|
+
):
|
|
940
|
+
raise ValueError("ledger and pool identity policies differ")
|
|
941
|
+
eligible, slots, reason = _decision_facts(
|
|
942
|
+
ledger=ledger,
|
|
943
|
+
pool=pool,
|
|
944
|
+
budget=budget,
|
|
945
|
+
max_recourse=self.max_recourse,
|
|
946
|
+
)
|
|
947
|
+
return EvaluationRecourseDecision(
|
|
948
|
+
ledger=ledger,
|
|
949
|
+
pool=pool,
|
|
950
|
+
budget=budget,
|
|
951
|
+
max_recourse=self.max_recourse,
|
|
952
|
+
selected_entry_ids=tuple(entry.entry_id for entry in eligible[:slots]),
|
|
953
|
+
reason=reason,
|
|
954
|
+
)
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
__all__ = [
|
|
958
|
+
"BoundedEvaluationRecoursePolicy",
|
|
959
|
+
"EvaluationOccurrenceRole",
|
|
960
|
+
"EvaluationOccurrenceStatus",
|
|
961
|
+
"EvaluationRecourseDecision",
|
|
962
|
+
"EvaluationRecoursePolicy",
|
|
963
|
+
"IDENTITY_POLICY_ID",
|
|
964
|
+
"IDENTITY_POLICY_VERSION",
|
|
965
|
+
"PhenotypeCluster",
|
|
966
|
+
"PhenotypeIdentity",
|
|
967
|
+
"PhenotypeIdentityPolicy",
|
|
968
|
+
"PhenotypeOccurrence",
|
|
969
|
+
"PhenotypeOccurrenceLedger",
|
|
970
|
+
"PresealedRecoursePool",
|
|
971
|
+
"RECOURSE_POLICY_ID",
|
|
972
|
+
"RECOURSE_POLICY_VERSION",
|
|
973
|
+
"RecourseBudgetSnapshot",
|
|
974
|
+
"RecourseDecisionReason",
|
|
975
|
+
"RecoursePoolCandidate",
|
|
976
|
+
"RecoursePoolEntry",
|
|
977
|
+
"SemanticProjectionPhenotypeIdentityPolicy",
|
|
978
|
+
"TypedConfigurationPhenotypeIdentityPolicy",
|
|
979
|
+
]
|