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,1011 @@
|
|
|
1
|
+
"""Workload-neutral conserved-backbone and earned-lineage control contracts.
|
|
2
|
+
|
|
3
|
+
This module deliberately contains no candidate schema, objective name,
|
|
4
|
+
workload identity, model identity, or provider field. Adapters authenticate
|
|
5
|
+
where a materialized candidate came from and an archive-utility policy supplies
|
|
6
|
+
conserved per-stage credit. The controller turns positive challenger credit
|
|
7
|
+
into expiring, one-use parent-exposure tickets.
|
|
8
|
+
|
|
9
|
+
Tickets reserve *proposal opportunities*, not evaluations. Descendants still
|
|
10
|
+
pass through the global feasibility, deduplication, and allocation broker.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import hashlib
|
|
16
|
+
import json
|
|
17
|
+
import math
|
|
18
|
+
import re
|
|
19
|
+
from collections.abc import Iterable
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from enum import Enum
|
|
22
|
+
from fractions import Fraction
|
|
23
|
+
|
|
24
|
+
from agent_evolve.domain.ids import CandidateId
|
|
25
|
+
from agent_evolve.domain.patch import require_sha256
|
|
26
|
+
|
|
27
|
+
_TOKEN = re.compile(r"^[a-z][a-z0-9_.:-]{0,255}$")
|
|
28
|
+
_PROVENANCE_DOMAIN = b"agent-evolve:candidate-proposal-provenance:v1\x00"
|
|
29
|
+
_STAGE_CREDIT_DOMAIN = b"agent-evolve:conserved-stage-credit:v1\x00"
|
|
30
|
+
_POLICY_DOMAIN = b"agent-evolve:earned-lineage-policy:v1\x00"
|
|
31
|
+
_TICKET_DOMAIN = b"agent-evolve:earned-reproduction-ticket:v1\x00"
|
|
32
|
+
_ISSUANCE_DOMAIN = b"agent-evolve:earned-reproduction-issuance:v1\x00"
|
|
33
|
+
_RESERVATION_DOMAIN = b"agent-evolve:earned-reproduction-reservation:v1\x00"
|
|
34
|
+
_COMMIT_DOMAIN = b"agent-evolve:earned-reproduction-commit:v1\x00"
|
|
35
|
+
_PARENT_ALLOCATION_DOMAIN = b"agent-evolve:earned-parent-allocation:v1\x00"
|
|
36
|
+
_CEILING_DOMAIN = b"agent-evolve:conserved-backbone-ceiling:v1\x00"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _canonical_bytes(value: object) -> bytes:
|
|
40
|
+
return json.dumps(
|
|
41
|
+
value,
|
|
42
|
+
allow_nan=False,
|
|
43
|
+
ensure_ascii=True,
|
|
44
|
+
separators=(",", ":"),
|
|
45
|
+
sort_keys=True,
|
|
46
|
+
).encode("ascii", errors="strict")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _hash(domain: bytes, value: object) -> str:
|
|
50
|
+
return hashlib.sha256(domain + _canonical_bytes(value)).hexdigest()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _token(value: str, name: str) -> None:
|
|
54
|
+
if type(value) is not str or _TOKEN.fullmatch(value) is None:
|
|
55
|
+
raise ValueError(f"{name} must use the closed token grammar")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _canonical_float(value: float, name: str, *, nonnegative: bool = True) -> None:
|
|
59
|
+
if type(value) is not float or not math.isfinite(value):
|
|
60
|
+
raise TypeError(f"{name} must be a finite exact float")
|
|
61
|
+
if nonnegative and value < 0.0:
|
|
62
|
+
raise ValueError(f"{name} must be non-negative")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _candidate_ids(
|
|
66
|
+
values: tuple[CandidateId, ...],
|
|
67
|
+
name: str,
|
|
68
|
+
*,
|
|
69
|
+
allow_empty: bool,
|
|
70
|
+
) -> None:
|
|
71
|
+
if type(values) is not tuple or (not allow_empty and not values):
|
|
72
|
+
raise ValueError(f"{name} must be an exact non-empty tuple")
|
|
73
|
+
if any(type(value) is not CandidateId for value in values):
|
|
74
|
+
raise TypeError(f"{name} must contain exact CandidateId values")
|
|
75
|
+
for value in values:
|
|
76
|
+
CandidateId.__post_init__(value)
|
|
77
|
+
if len(set(values)) != len(values):
|
|
78
|
+
raise ValueError(f"{name} cannot contain duplicates")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ProposalLineageRole(str, Enum):
|
|
82
|
+
"""Scientific role of a proposal, independent of its implementation."""
|
|
83
|
+
|
|
84
|
+
BACKBONE = "backbone"
|
|
85
|
+
CHALLENGER = "challenger"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass(frozen=True, slots=True)
|
|
89
|
+
class CandidateProposalProvenance:
|
|
90
|
+
"""Immutable source identity for one fully materialized candidate."""
|
|
91
|
+
|
|
92
|
+
candidate_id: CandidateId
|
|
93
|
+
configuration_sha256: str
|
|
94
|
+
generation: int
|
|
95
|
+
source_role: ProposalLineageRole
|
|
96
|
+
proposal_expert_id: str
|
|
97
|
+
proposal_expert_version: int
|
|
98
|
+
proposal_expert_definition_sha256: str
|
|
99
|
+
operator_id: str
|
|
100
|
+
parent_candidate_ids: tuple[CandidateId, ...]
|
|
101
|
+
decision_cutoff_sha256: str
|
|
102
|
+
source_receipt_sha256: str
|
|
103
|
+
|
|
104
|
+
def __post_init__(self) -> None:
|
|
105
|
+
if type(self.candidate_id) is not CandidateId:
|
|
106
|
+
raise TypeError("candidate_id must be an exact CandidateId")
|
|
107
|
+
CandidateId.__post_init__(self.candidate_id)
|
|
108
|
+
require_sha256(self.configuration_sha256, "configuration_sha256")
|
|
109
|
+
if type(self.generation) is not int or self.generation < 0:
|
|
110
|
+
raise ValueError("generation must be a non-negative exact integer")
|
|
111
|
+
if type(self.source_role) is not ProposalLineageRole:
|
|
112
|
+
raise TypeError("source_role must be an exact ProposalLineageRole")
|
|
113
|
+
_token(self.proposal_expert_id, "proposal_expert_id")
|
|
114
|
+
if (
|
|
115
|
+
type(self.proposal_expert_version) is not int
|
|
116
|
+
or self.proposal_expert_version <= 0
|
|
117
|
+
):
|
|
118
|
+
raise ValueError("proposal_expert_version must be positive")
|
|
119
|
+
require_sha256(
|
|
120
|
+
self.proposal_expert_definition_sha256,
|
|
121
|
+
"proposal_expert_definition_sha256",
|
|
122
|
+
)
|
|
123
|
+
_token(self.operator_id, "operator_id")
|
|
124
|
+
_candidate_ids(
|
|
125
|
+
self.parent_candidate_ids,
|
|
126
|
+
"parent_candidate_ids",
|
|
127
|
+
allow_empty=True,
|
|
128
|
+
)
|
|
129
|
+
if self.candidate_id in self.parent_candidate_ids:
|
|
130
|
+
raise ValueError("a proposal cannot be its own parent")
|
|
131
|
+
require_sha256(self.decision_cutoff_sha256, "decision_cutoff_sha256")
|
|
132
|
+
require_sha256(self.source_receipt_sha256, "source_receipt_sha256")
|
|
133
|
+
|
|
134
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
135
|
+
self.__post_init__()
|
|
136
|
+
return {
|
|
137
|
+
"schema_version": 1,
|
|
138
|
+
"candidate_id": self.candidate_id.value,
|
|
139
|
+
"configuration_sha256": self.configuration_sha256,
|
|
140
|
+
"generation": self.generation,
|
|
141
|
+
"source_role": self.source_role.value,
|
|
142
|
+
"proposal_expert": {
|
|
143
|
+
"expert_id": self.proposal_expert_id,
|
|
144
|
+
"expert_version": self.proposal_expert_version,
|
|
145
|
+
"definition_sha256": self.proposal_expert_definition_sha256,
|
|
146
|
+
},
|
|
147
|
+
"operator_id": self.operator_id,
|
|
148
|
+
"parent_candidate_ids": [
|
|
149
|
+
value.value for value in self.parent_candidate_ids
|
|
150
|
+
],
|
|
151
|
+
"decision_cutoff_sha256": self.decision_cutoff_sha256,
|
|
152
|
+
"source_receipt_sha256": self.source_receipt_sha256,
|
|
153
|
+
"workload_model_provider_fields_present": False,
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def provenance_sha256(self) -> str:
|
|
158
|
+
return _hash(_PROVENANCE_DOMAIN, self._unsigned_record())
|
|
159
|
+
|
|
160
|
+
def to_record(self) -> dict[str, object]:
|
|
161
|
+
return {
|
|
162
|
+
**self._unsigned_record(),
|
|
163
|
+
"provenance_sha256": self.provenance_sha256,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@dataclass(frozen=True, slots=True)
|
|
168
|
+
class CandidateConservedCredit:
|
|
169
|
+
"""One candidate's non-negative share of a stage archive gain."""
|
|
170
|
+
|
|
171
|
+
candidate_id: CandidateId
|
|
172
|
+
contribution: float
|
|
173
|
+
admitted_to_archive: bool
|
|
174
|
+
outcome_receipt_sha256: str
|
|
175
|
+
|
|
176
|
+
def __post_init__(self) -> None:
|
|
177
|
+
if type(self.candidate_id) is not CandidateId:
|
|
178
|
+
raise TypeError("candidate_id must be an exact CandidateId")
|
|
179
|
+
CandidateId.__post_init__(self.candidate_id)
|
|
180
|
+
_canonical_float(self.contribution, "contribution")
|
|
181
|
+
if type(self.admitted_to_archive) is not bool:
|
|
182
|
+
raise TypeError("admitted_to_archive must be an exact bool")
|
|
183
|
+
if self.contribution > 0.0 and not self.admitted_to_archive:
|
|
184
|
+
raise ValueError("positive archive credit requires archive admission")
|
|
185
|
+
require_sha256(self.outcome_receipt_sha256, "outcome_receipt_sha256")
|
|
186
|
+
|
|
187
|
+
def to_record(self) -> dict[str, object]:
|
|
188
|
+
self.__post_init__()
|
|
189
|
+
return {
|
|
190
|
+
"candidate_id": self.candidate_id.value,
|
|
191
|
+
"contribution_hex": self.contribution.hex(),
|
|
192
|
+
"admitted_to_archive": self.admitted_to_archive,
|
|
193
|
+
"outcome_receipt_sha256": self.outcome_receipt_sha256,
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@dataclass(frozen=True, slots=True)
|
|
198
|
+
class ConservedStageCreditReceipt:
|
|
199
|
+
"""Candidate credits that close to one authenticated archive transition."""
|
|
200
|
+
|
|
201
|
+
generation: int
|
|
202
|
+
utility_id: str
|
|
203
|
+
utility_version: int
|
|
204
|
+
utility_definition_sha256: str
|
|
205
|
+
pre_archive_sha256: str
|
|
206
|
+
post_archive_sha256: str
|
|
207
|
+
pre_utility: float
|
|
208
|
+
post_utility: float
|
|
209
|
+
contribution_policy_id: str
|
|
210
|
+
contribution_policy_version: int
|
|
211
|
+
contribution_policy_definition_sha256: str
|
|
212
|
+
candidate_credits: tuple[CandidateConservedCredit, ...]
|
|
213
|
+
|
|
214
|
+
def __post_init__(self) -> None:
|
|
215
|
+
if type(self.generation) is not int or self.generation <= 0:
|
|
216
|
+
raise ValueError("generation must be a positive exact integer")
|
|
217
|
+
_token(self.utility_id, "utility_id")
|
|
218
|
+
if type(self.utility_version) is not int or self.utility_version <= 0:
|
|
219
|
+
raise ValueError("utility_version must be positive")
|
|
220
|
+
require_sha256(self.utility_definition_sha256, "utility_definition_sha256")
|
|
221
|
+
require_sha256(self.pre_archive_sha256, "pre_archive_sha256")
|
|
222
|
+
require_sha256(self.post_archive_sha256, "post_archive_sha256")
|
|
223
|
+
_canonical_float(self.pre_utility, "pre_utility")
|
|
224
|
+
_canonical_float(self.post_utility, "post_utility")
|
|
225
|
+
if self.post_utility < self.pre_utility:
|
|
226
|
+
raise ValueError("archive utility cannot decrease")
|
|
227
|
+
_token(self.contribution_policy_id, "contribution_policy_id")
|
|
228
|
+
if (
|
|
229
|
+
type(self.contribution_policy_version) is not int
|
|
230
|
+
or self.contribution_policy_version <= 0
|
|
231
|
+
):
|
|
232
|
+
raise ValueError("contribution_policy_version must be positive")
|
|
233
|
+
require_sha256(
|
|
234
|
+
self.contribution_policy_definition_sha256,
|
|
235
|
+
"contribution_policy_definition_sha256",
|
|
236
|
+
)
|
|
237
|
+
if type(self.candidate_credits) is not tuple or not self.candidate_credits:
|
|
238
|
+
raise ValueError("candidate_credits must be a non-empty exact tuple")
|
|
239
|
+
if any(
|
|
240
|
+
type(value) is not CandidateConservedCredit
|
|
241
|
+
for value in self.candidate_credits
|
|
242
|
+
):
|
|
243
|
+
raise TypeError("candidate_credits must contain exact values")
|
|
244
|
+
for value in self.candidate_credits:
|
|
245
|
+
CandidateConservedCredit.__post_init__(value)
|
|
246
|
+
ids = tuple(value.candidate_id.value for value in self.candidate_credits)
|
|
247
|
+
if ids != tuple(sorted(set(ids))):
|
|
248
|
+
raise ValueError("candidate credits must use canonical unique ID order")
|
|
249
|
+
gain = self.post_utility - self.pre_utility
|
|
250
|
+
total = math.fsum(value.contribution for value in self.candidate_credits)
|
|
251
|
+
tolerance = 8.0 * math.ulp(max(1.0, self.post_utility, self.pre_utility))
|
|
252
|
+
if abs(total - gain) > tolerance:
|
|
253
|
+
raise ValueError("candidate contributions do not conserve stage gain")
|
|
254
|
+
|
|
255
|
+
@property
|
|
256
|
+
def stage_gain(self) -> float:
|
|
257
|
+
return self.post_utility - self.pre_utility
|
|
258
|
+
|
|
259
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
260
|
+
self.__post_init__()
|
|
261
|
+
return {
|
|
262
|
+
"schema_version": 1,
|
|
263
|
+
"generation": self.generation,
|
|
264
|
+
"utility": {
|
|
265
|
+
"utility_id": self.utility_id,
|
|
266
|
+
"utility_version": self.utility_version,
|
|
267
|
+
"definition_sha256": self.utility_definition_sha256,
|
|
268
|
+
"pre_archive_sha256": self.pre_archive_sha256,
|
|
269
|
+
"post_archive_sha256": self.post_archive_sha256,
|
|
270
|
+
"pre_utility_hex": self.pre_utility.hex(),
|
|
271
|
+
"post_utility_hex": self.post_utility.hex(),
|
|
272
|
+
"stage_gain_hex": self.stage_gain.hex(),
|
|
273
|
+
},
|
|
274
|
+
"contribution_policy": {
|
|
275
|
+
"policy_id": self.contribution_policy_id,
|
|
276
|
+
"policy_version": self.contribution_policy_version,
|
|
277
|
+
"definition_sha256": self.contribution_policy_definition_sha256,
|
|
278
|
+
},
|
|
279
|
+
"candidate_credits": [
|
|
280
|
+
value.to_record() for value in self.candidate_credits
|
|
281
|
+
],
|
|
282
|
+
"credit_conservation_verified": True,
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
@property
|
|
286
|
+
def receipt_sha256(self) -> str:
|
|
287
|
+
return _hash(_STAGE_CREDIT_DOMAIN, self._unsigned_record())
|
|
288
|
+
|
|
289
|
+
def to_record(self) -> dict[str, object]:
|
|
290
|
+
return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
@dataclass(frozen=True, slots=True)
|
|
294
|
+
class EarnedLineagePolicy:
|
|
295
|
+
"""Frozen rule mapping positive real challenger credit to tickets."""
|
|
296
|
+
|
|
297
|
+
positive_credit_floor: float = 0.0
|
|
298
|
+
max_tickets_per_stage: int = 2
|
|
299
|
+
max_open_tickets_per_candidate: int = 1
|
|
300
|
+
ticket_ttl_generations: int = 2
|
|
301
|
+
descendant_discount: Fraction = Fraction(1, 2)
|
|
302
|
+
|
|
303
|
+
def __post_init__(self) -> None:
|
|
304
|
+
_canonical_float(self.positive_credit_floor, "positive_credit_floor")
|
|
305
|
+
for name in (
|
|
306
|
+
"max_tickets_per_stage",
|
|
307
|
+
"max_open_tickets_per_candidate",
|
|
308
|
+
"ticket_ttl_generations",
|
|
309
|
+
):
|
|
310
|
+
value = getattr(self, name)
|
|
311
|
+
if type(value) is not int or value <= 0:
|
|
312
|
+
raise ValueError(f"{name} must be a positive exact integer")
|
|
313
|
+
if type(self.descendant_discount) is not Fraction:
|
|
314
|
+
raise TypeError("descendant_discount must be an exact Fraction")
|
|
315
|
+
if not Fraction(0, 1) < self.descendant_discount <= Fraction(1, 1):
|
|
316
|
+
raise ValueError("descendant_discount must lie in (0, 1]")
|
|
317
|
+
|
|
318
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
319
|
+
self.__post_init__()
|
|
320
|
+
return {
|
|
321
|
+
"schema_version": 1,
|
|
322
|
+
"positive_credit_floor_hex": self.positive_credit_floor.hex(),
|
|
323
|
+
"max_tickets_per_stage": self.max_tickets_per_stage,
|
|
324
|
+
"max_open_tickets_per_candidate": (
|
|
325
|
+
self.max_open_tickets_per_candidate
|
|
326
|
+
),
|
|
327
|
+
"ticket_ttl_generations": self.ticket_ttl_generations,
|
|
328
|
+
"descendant_discount": {
|
|
329
|
+
"numerator": self.descendant_discount.numerator,
|
|
330
|
+
"denominator": self.descendant_discount.denominator,
|
|
331
|
+
},
|
|
332
|
+
"quality_signal": "conserved_real_archive_contribution_only",
|
|
333
|
+
"ticket_scope": "one_parent_exposure_not_evaluation_guarantee",
|
|
334
|
+
"workload_model_provider_fields_consulted": False,
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
@property
|
|
338
|
+
def definition_sha256(self) -> str:
|
|
339
|
+
return _hash(_POLICY_DOMAIN, self._unsigned_record())
|
|
340
|
+
|
|
341
|
+
def to_record(self) -> dict[str, object]:
|
|
342
|
+
return {**self._unsigned_record(), "definition_sha256": self.definition_sha256}
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
@dataclass(frozen=True, slots=True)
|
|
346
|
+
class ReproductionTicket:
|
|
347
|
+
candidate_id: CandidateId
|
|
348
|
+
issued_after_generation: int
|
|
349
|
+
eligible_generation: int
|
|
350
|
+
expires_after_generation: int
|
|
351
|
+
conserved_credit: float
|
|
352
|
+
stage_credit_receipt_sha256: str
|
|
353
|
+
policy_definition_sha256: str
|
|
354
|
+
|
|
355
|
+
def __post_init__(self) -> None:
|
|
356
|
+
if type(self.candidate_id) is not CandidateId:
|
|
357
|
+
raise TypeError("candidate_id must be an exact CandidateId")
|
|
358
|
+
CandidateId.__post_init__(self.candidate_id)
|
|
359
|
+
for name in (
|
|
360
|
+
"issued_after_generation",
|
|
361
|
+
"eligible_generation",
|
|
362
|
+
"expires_after_generation",
|
|
363
|
+
):
|
|
364
|
+
value = getattr(self, name)
|
|
365
|
+
if type(value) is not int or value <= 0:
|
|
366
|
+
raise ValueError(f"{name} must be a positive exact integer")
|
|
367
|
+
if self.eligible_generation != self.issued_after_generation + 1:
|
|
368
|
+
raise ValueError("a ticket must first become eligible next generation")
|
|
369
|
+
if self.expires_after_generation < self.eligible_generation:
|
|
370
|
+
raise ValueError("ticket expiry precedes eligibility")
|
|
371
|
+
_canonical_float(self.conserved_credit, "conserved_credit")
|
|
372
|
+
if self.conserved_credit <= 0.0:
|
|
373
|
+
raise ValueError("a reproduction ticket requires positive credit")
|
|
374
|
+
require_sha256(
|
|
375
|
+
self.stage_credit_receipt_sha256,
|
|
376
|
+
"stage_credit_receipt_sha256",
|
|
377
|
+
)
|
|
378
|
+
require_sha256(self.policy_definition_sha256, "policy_definition_sha256")
|
|
379
|
+
|
|
380
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
381
|
+
self.__post_init__()
|
|
382
|
+
return {
|
|
383
|
+
"schema_version": 1,
|
|
384
|
+
"candidate_id": self.candidate_id.value,
|
|
385
|
+
"issued_after_generation": self.issued_after_generation,
|
|
386
|
+
"eligible_generation": self.eligible_generation,
|
|
387
|
+
"expires_after_generation": self.expires_after_generation,
|
|
388
|
+
"conserved_credit_hex": self.conserved_credit.hex(),
|
|
389
|
+
"stage_credit_receipt_sha256": self.stage_credit_receipt_sha256,
|
|
390
|
+
"policy_definition_sha256": self.policy_definition_sha256,
|
|
391
|
+
"entitlement": "one_parent_exposure_not_evaluation_guarantee",
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
@property
|
|
395
|
+
def ticket_sha256(self) -> str:
|
|
396
|
+
return _hash(_TICKET_DOMAIN, self._unsigned_record())
|
|
397
|
+
|
|
398
|
+
def to_record(self) -> dict[str, object]:
|
|
399
|
+
return {**self._unsigned_record(), "ticket_sha256": self.ticket_sha256}
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
@dataclass(frozen=True, slots=True)
|
|
403
|
+
class ReproductionTicketIssuance:
|
|
404
|
+
generation: int
|
|
405
|
+
stage_credit_receipt_sha256: str
|
|
406
|
+
policy_definition_sha256: str
|
|
407
|
+
tickets: tuple[ReproductionTicket, ...]
|
|
408
|
+
|
|
409
|
+
def __post_init__(self) -> None:
|
|
410
|
+
if type(self.generation) is not int or self.generation <= 0:
|
|
411
|
+
raise ValueError("generation must be positive")
|
|
412
|
+
require_sha256(
|
|
413
|
+
self.stage_credit_receipt_sha256,
|
|
414
|
+
"stage_credit_receipt_sha256",
|
|
415
|
+
)
|
|
416
|
+
require_sha256(self.policy_definition_sha256, "policy_definition_sha256")
|
|
417
|
+
if type(self.tickets) is not tuple or any(
|
|
418
|
+
type(value) is not ReproductionTicket for value in self.tickets
|
|
419
|
+
):
|
|
420
|
+
raise TypeError("tickets must contain exact ReproductionTicket values")
|
|
421
|
+
for value in self.tickets:
|
|
422
|
+
ReproductionTicket.__post_init__(value)
|
|
423
|
+
if (
|
|
424
|
+
value.issued_after_generation != self.generation
|
|
425
|
+
or value.stage_credit_receipt_sha256
|
|
426
|
+
!= self.stage_credit_receipt_sha256
|
|
427
|
+
or value.policy_definition_sha256 != self.policy_definition_sha256
|
|
428
|
+
):
|
|
429
|
+
raise ValueError("issued ticket differs from issuance identity")
|
|
430
|
+
ids = tuple(value.ticket_sha256 for value in self.tickets)
|
|
431
|
+
if len(set(ids)) != len(ids):
|
|
432
|
+
raise ValueError("issuance cannot repeat a ticket")
|
|
433
|
+
|
|
434
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
435
|
+
self.__post_init__()
|
|
436
|
+
return {
|
|
437
|
+
"schema_version": 1,
|
|
438
|
+
"generation": self.generation,
|
|
439
|
+
"stage_credit_receipt_sha256": self.stage_credit_receipt_sha256,
|
|
440
|
+
"policy_definition_sha256": self.policy_definition_sha256,
|
|
441
|
+
"tickets": [value.to_record() for value in self.tickets],
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
@property
|
|
445
|
+
def issuance_sha256(self) -> str:
|
|
446
|
+
return _hash(_ISSUANCE_DOMAIN, self._unsigned_record())
|
|
447
|
+
|
|
448
|
+
def to_record(self) -> dict[str, object]:
|
|
449
|
+
return {**self._unsigned_record(), "issuance_sha256": self.issuance_sha256}
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
@dataclass(frozen=True, slots=True)
|
|
453
|
+
class PreparedReproductionReservation:
|
|
454
|
+
generation: int
|
|
455
|
+
ledger_version: int
|
|
456
|
+
available_candidate_ids_sha256: str
|
|
457
|
+
tickets: tuple[ReproductionTicket, ...]
|
|
458
|
+
policy_definition_sha256: str
|
|
459
|
+
|
|
460
|
+
def __post_init__(self) -> None:
|
|
461
|
+
if type(self.generation) is not int or self.generation <= 0:
|
|
462
|
+
raise ValueError("generation must be positive")
|
|
463
|
+
if type(self.ledger_version) is not int or self.ledger_version < 0:
|
|
464
|
+
raise ValueError("ledger_version must be non-negative")
|
|
465
|
+
require_sha256(
|
|
466
|
+
self.available_candidate_ids_sha256,
|
|
467
|
+
"available_candidate_ids_sha256",
|
|
468
|
+
)
|
|
469
|
+
if type(self.tickets) is not tuple or any(
|
|
470
|
+
type(value) is not ReproductionTicket for value in self.tickets
|
|
471
|
+
):
|
|
472
|
+
raise TypeError("tickets must contain exact values")
|
|
473
|
+
for value in self.tickets:
|
|
474
|
+
ReproductionTicket.__post_init__(value)
|
|
475
|
+
if not value.eligible_generation <= self.generation <= (
|
|
476
|
+
value.expires_after_generation
|
|
477
|
+
):
|
|
478
|
+
raise ValueError("reservation contains an ineligible ticket")
|
|
479
|
+
if len({value.candidate_id for value in self.tickets}) != len(self.tickets):
|
|
480
|
+
raise ValueError("reservation repeats a candidate")
|
|
481
|
+
require_sha256(self.policy_definition_sha256, "policy_definition_sha256")
|
|
482
|
+
|
|
483
|
+
@property
|
|
484
|
+
def candidate_ids(self) -> tuple[CandidateId, ...]:
|
|
485
|
+
return tuple(value.candidate_id for value in self.tickets)
|
|
486
|
+
|
|
487
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
488
|
+
self.__post_init__()
|
|
489
|
+
return {
|
|
490
|
+
"schema_version": 1,
|
|
491
|
+
"generation": self.generation,
|
|
492
|
+
"ledger_version": self.ledger_version,
|
|
493
|
+
"available_candidate_ids_sha256": self.available_candidate_ids_sha256,
|
|
494
|
+
"ticket_sha256s": [value.ticket_sha256 for value in self.tickets],
|
|
495
|
+
"candidate_ids": [value.value for value in self.candidate_ids],
|
|
496
|
+
"policy_definition_sha256": self.policy_definition_sha256,
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
@property
|
|
500
|
+
def reservation_sha256(self) -> str:
|
|
501
|
+
return _hash(_RESERVATION_DOMAIN, self._unsigned_record())
|
|
502
|
+
|
|
503
|
+
def to_record(self) -> dict[str, object]:
|
|
504
|
+
return {
|
|
505
|
+
**self._unsigned_record(),
|
|
506
|
+
"reservation_sha256": self.reservation_sha256,
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
@dataclass(frozen=True, slots=True)
|
|
511
|
+
class ReproductionReservationCommit:
|
|
512
|
+
reservation_sha256: str
|
|
513
|
+
generation: int
|
|
514
|
+
consumed_ticket_sha256s: tuple[str, ...]
|
|
515
|
+
parent_exposure_receipt_sha256: str
|
|
516
|
+
|
|
517
|
+
def __post_init__(self) -> None:
|
|
518
|
+
require_sha256(self.reservation_sha256, "reservation_sha256")
|
|
519
|
+
if type(self.generation) is not int or self.generation <= 0:
|
|
520
|
+
raise ValueError("generation must be positive")
|
|
521
|
+
if (
|
|
522
|
+
type(self.consumed_ticket_sha256s) is not tuple
|
|
523
|
+
or not self.consumed_ticket_sha256s
|
|
524
|
+
):
|
|
525
|
+
raise ValueError("consumed_ticket_sha256s must be non-empty")
|
|
526
|
+
for value in self.consumed_ticket_sha256s:
|
|
527
|
+
require_sha256(value, "consumed_ticket_sha256")
|
|
528
|
+
if len(set(self.consumed_ticket_sha256s)) != len(
|
|
529
|
+
self.consumed_ticket_sha256s
|
|
530
|
+
):
|
|
531
|
+
raise ValueError("commit repeats a consumed ticket")
|
|
532
|
+
require_sha256(
|
|
533
|
+
self.parent_exposure_receipt_sha256,
|
|
534
|
+
"parent_exposure_receipt_sha256",
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
538
|
+
self.__post_init__()
|
|
539
|
+
return {
|
|
540
|
+
"schema_version": 1,
|
|
541
|
+
"reservation_sha256": self.reservation_sha256,
|
|
542
|
+
"generation": self.generation,
|
|
543
|
+
"consumed_ticket_sha256s": list(self.consumed_ticket_sha256s),
|
|
544
|
+
"parent_exposure_receipt_sha256": (
|
|
545
|
+
self.parent_exposure_receipt_sha256
|
|
546
|
+
),
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
@property
|
|
550
|
+
def commit_sha256(self) -> str:
|
|
551
|
+
return _hash(_COMMIT_DOMAIN, self._unsigned_record())
|
|
552
|
+
|
|
553
|
+
def to_record(self) -> dict[str, object]:
|
|
554
|
+
return {**self._unsigned_record(), "commit_sha256": self.commit_sha256}
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
@dataclass(frozen=True, slots=True)
|
|
558
|
+
class EarnedParentAllocation:
|
|
559
|
+
"""A fixed-width parent set containing every reserved earned parent."""
|
|
560
|
+
|
|
561
|
+
generation: int
|
|
562
|
+
base_parent_ids: tuple[CandidateId, ...]
|
|
563
|
+
earned_parent_ids: tuple[CandidateId, ...]
|
|
564
|
+
selected_parent_ids: tuple[CandidateId, ...]
|
|
565
|
+
reservation_sha256: str
|
|
566
|
+
|
|
567
|
+
def __post_init__(self) -> None:
|
|
568
|
+
if type(self.generation) is not int or self.generation <= 0:
|
|
569
|
+
raise ValueError("generation must be positive")
|
|
570
|
+
for name in (
|
|
571
|
+
"base_parent_ids",
|
|
572
|
+
"earned_parent_ids",
|
|
573
|
+
"selected_parent_ids",
|
|
574
|
+
):
|
|
575
|
+
_candidate_ids(
|
|
576
|
+
getattr(self, name),
|
|
577
|
+
name,
|
|
578
|
+
allow_empty=name == "earned_parent_ids",
|
|
579
|
+
)
|
|
580
|
+
if len(self.selected_parent_ids) != len(self.base_parent_ids):
|
|
581
|
+
raise ValueError("earned parent allocation must preserve width")
|
|
582
|
+
if not set(self.earned_parent_ids).issubset(self.selected_parent_ids):
|
|
583
|
+
raise ValueError("selected parents omit an earned parent")
|
|
584
|
+
require_sha256(self.reservation_sha256, "reservation_sha256")
|
|
585
|
+
|
|
586
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
587
|
+
self.__post_init__()
|
|
588
|
+
return {
|
|
589
|
+
"schema_version": 1,
|
|
590
|
+
"generation": self.generation,
|
|
591
|
+
"base_parent_ids": [value.value for value in self.base_parent_ids],
|
|
592
|
+
"earned_parent_ids": [value.value for value in self.earned_parent_ids],
|
|
593
|
+
"selected_parent_ids": [
|
|
594
|
+
value.value for value in self.selected_parent_ids
|
|
595
|
+
],
|
|
596
|
+
"reservation_sha256": self.reservation_sha256,
|
|
597
|
+
"workload_model_provider_fields_consulted": False,
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
@property
|
|
601
|
+
def allocation_sha256(self) -> str:
|
|
602
|
+
return _hash(_PARENT_ALLOCATION_DOMAIN, self._unsigned_record())
|
|
603
|
+
|
|
604
|
+
def to_record(self) -> dict[str, object]:
|
|
605
|
+
return {**self._unsigned_record(), "allocation_sha256": self.allocation_sha256}
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def allocate_earned_parents(
|
|
609
|
+
*,
|
|
610
|
+
generation: int,
|
|
611
|
+
base_parent_ids: tuple[CandidateId, ...],
|
|
612
|
+
reservation: PreparedReproductionReservation,
|
|
613
|
+
) -> EarnedParentAllocation:
|
|
614
|
+
"""Inject all earned parents while preserving the base selector's width."""
|
|
615
|
+
|
|
616
|
+
reservation.__post_init__()
|
|
617
|
+
if reservation.generation != generation:
|
|
618
|
+
raise ValueError("reservation belongs to another generation")
|
|
619
|
+
_candidate_ids(base_parent_ids, "base_parent_ids", allow_empty=False)
|
|
620
|
+
earned = reservation.candidate_ids
|
|
621
|
+
if len(earned) > len(base_parent_ids):
|
|
622
|
+
raise ValueError("earned tickets exceed parent-selection width")
|
|
623
|
+
retained = [value for value in base_parent_ids if value not in set(earned)]
|
|
624
|
+
retained = retained[: len(base_parent_ids) - len(earned)]
|
|
625
|
+
selected = tuple(retained) + earned
|
|
626
|
+
if len(selected) != len(base_parent_ids):
|
|
627
|
+
raise ValueError("base and earned parents cannot fill the required width")
|
|
628
|
+
return EarnedParentAllocation(
|
|
629
|
+
generation=generation,
|
|
630
|
+
base_parent_ids=base_parent_ids,
|
|
631
|
+
earned_parent_ids=earned,
|
|
632
|
+
selected_parent_ids=selected,
|
|
633
|
+
reservation_sha256=reservation.reservation_sha256,
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
@dataclass(frozen=True, slots=True)
|
|
638
|
+
class ConservedBackboneCeilingReceipt:
|
|
639
|
+
"""One dual-ledger checkpoint proving a protected additive ceiling."""
|
|
640
|
+
|
|
641
|
+
generation: int
|
|
642
|
+
backbone_state_before_sha256: str
|
|
643
|
+
backbone_ask_receipt_sha256: str
|
|
644
|
+
backbone_outcome_receipt_sha256: str
|
|
645
|
+
backbone_state_after_sha256: str
|
|
646
|
+
backbone_candidate_ids: tuple[CandidateId, ...]
|
|
647
|
+
challenger_candidate_ids: tuple[CandidateId, ...]
|
|
648
|
+
utility_definition_sha256: str
|
|
649
|
+
backbone_utility: float
|
|
650
|
+
union_utility: float
|
|
651
|
+
|
|
652
|
+
def __post_init__(self) -> None:
|
|
653
|
+
if type(self.generation) is not int or self.generation <= 0:
|
|
654
|
+
raise ValueError("generation must be positive")
|
|
655
|
+
for name in (
|
|
656
|
+
"backbone_state_before_sha256",
|
|
657
|
+
"backbone_ask_receipt_sha256",
|
|
658
|
+
"backbone_outcome_receipt_sha256",
|
|
659
|
+
"backbone_state_after_sha256",
|
|
660
|
+
"utility_definition_sha256",
|
|
661
|
+
):
|
|
662
|
+
require_sha256(getattr(self, name), name)
|
|
663
|
+
_candidate_ids(
|
|
664
|
+
self.backbone_candidate_ids,
|
|
665
|
+
"backbone_candidate_ids",
|
|
666
|
+
allow_empty=False,
|
|
667
|
+
)
|
|
668
|
+
_candidate_ids(
|
|
669
|
+
self.challenger_candidate_ids,
|
|
670
|
+
"challenger_candidate_ids",
|
|
671
|
+
allow_empty=True,
|
|
672
|
+
)
|
|
673
|
+
if set(self.backbone_candidate_ids).intersection(
|
|
674
|
+
self.challenger_candidate_ids
|
|
675
|
+
):
|
|
676
|
+
raise ValueError("backbone and challenger candidate sets must be disjoint")
|
|
677
|
+
_canonical_float(self.backbone_utility, "backbone_utility")
|
|
678
|
+
_canonical_float(self.union_utility, "union_utility")
|
|
679
|
+
if self.union_utility < self.backbone_utility:
|
|
680
|
+
raise ValueError("additive union utility cannot trail its backbone")
|
|
681
|
+
|
|
682
|
+
@property
|
|
683
|
+
def complement(self) -> float:
|
|
684
|
+
return self.union_utility - self.backbone_utility
|
|
685
|
+
|
|
686
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
687
|
+
self.__post_init__()
|
|
688
|
+
return {
|
|
689
|
+
"schema_version": 1,
|
|
690
|
+
"generation": self.generation,
|
|
691
|
+
"backbone_state_before_sha256": self.backbone_state_before_sha256,
|
|
692
|
+
"backbone_ask_receipt_sha256": self.backbone_ask_receipt_sha256,
|
|
693
|
+
"backbone_outcome_receipt_sha256": (
|
|
694
|
+
self.backbone_outcome_receipt_sha256
|
|
695
|
+
),
|
|
696
|
+
"backbone_state_after_sha256": self.backbone_state_after_sha256,
|
|
697
|
+
"backbone_candidate_ids": [
|
|
698
|
+
value.value for value in self.backbone_candidate_ids
|
|
699
|
+
],
|
|
700
|
+
"challenger_candidate_ids": [
|
|
701
|
+
value.value for value in self.challenger_candidate_ids
|
|
702
|
+
],
|
|
703
|
+
"utility_definition_sha256": self.utility_definition_sha256,
|
|
704
|
+
"backbone_utility_hex": self.backbone_utility.hex(),
|
|
705
|
+
"union_utility_hex": self.union_utility.hex(),
|
|
706
|
+
"complement_hex": self.complement.hex(),
|
|
707
|
+
"backbone_state_excludes_challenger_outcomes": True,
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
@property
|
|
711
|
+
def receipt_sha256(self) -> str:
|
|
712
|
+
return _hash(_CEILING_DOMAIN, self._unsigned_record())
|
|
713
|
+
|
|
714
|
+
def to_record(self) -> dict[str, object]:
|
|
715
|
+
return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
@dataclass(slots=True)
|
|
719
|
+
class EarnedLineageLedger:
|
|
720
|
+
"""Transactional ticket ledger driven only by authenticated prior credit."""
|
|
721
|
+
|
|
722
|
+
policy: EarnedLineagePolicy = field(default_factory=EarnedLineagePolicy)
|
|
723
|
+
_provenance: dict[CandidateId, CandidateProposalProvenance] = field(
|
|
724
|
+
init=False,
|
|
725
|
+
default_factory=dict,
|
|
726
|
+
)
|
|
727
|
+
_credits: list[ConservedStageCreditReceipt] = field(
|
|
728
|
+
init=False,
|
|
729
|
+
default_factory=list,
|
|
730
|
+
)
|
|
731
|
+
_tickets: dict[str, ReproductionTicket] = field(
|
|
732
|
+
init=False,
|
|
733
|
+
default_factory=dict,
|
|
734
|
+
)
|
|
735
|
+
_consumed: set[str] = field(init=False, default_factory=set)
|
|
736
|
+
_reservations: dict[str, PreparedReproductionReservation] = field(
|
|
737
|
+
init=False,
|
|
738
|
+
default_factory=dict,
|
|
739
|
+
)
|
|
740
|
+
_reserved_ticket_ids: set[str] = field(init=False, default_factory=set)
|
|
741
|
+
_commits: list[ReproductionReservationCommit] = field(
|
|
742
|
+
init=False,
|
|
743
|
+
default_factory=list,
|
|
744
|
+
)
|
|
745
|
+
_version: int = field(init=False, default=0)
|
|
746
|
+
|
|
747
|
+
def __post_init__(self) -> None:
|
|
748
|
+
if type(self.policy) is not EarnedLineagePolicy:
|
|
749
|
+
raise TypeError("policy must be an exact EarnedLineagePolicy")
|
|
750
|
+
EarnedLineagePolicy.__post_init__(self.policy)
|
|
751
|
+
|
|
752
|
+
@property
|
|
753
|
+
def version(self) -> int:
|
|
754
|
+
return self._version
|
|
755
|
+
|
|
756
|
+
def register(self, values: Iterable[CandidateProposalProvenance]) -> None:
|
|
757
|
+
batch = tuple(values)
|
|
758
|
+
if not batch:
|
|
759
|
+
raise ValueError("provenance registration batch cannot be empty")
|
|
760
|
+
for value in batch:
|
|
761
|
+
if type(value) is not CandidateProposalProvenance:
|
|
762
|
+
raise TypeError("provenance batch contains an invalid value")
|
|
763
|
+
CandidateProposalProvenance.__post_init__(value)
|
|
764
|
+
if len({value.candidate_id for value in batch}) != len(batch):
|
|
765
|
+
raise ValueError("provenance registration batch repeats a candidate")
|
|
766
|
+
known = set(self._provenance)
|
|
767
|
+
batch_ids = {value.candidate_id for value in batch}
|
|
768
|
+
batch_by_id = {value.candidate_id: value for value in batch}
|
|
769
|
+
for value in batch:
|
|
770
|
+
missing = set(value.parent_candidate_ids) - known - batch_ids
|
|
771
|
+
if missing:
|
|
772
|
+
raise ValueError("proposal provenance names unknown parents")
|
|
773
|
+
for parent_id in value.parent_candidate_ids:
|
|
774
|
+
# Do not use ``dict.get(parent_id, batch_by_id[parent_id])``:
|
|
775
|
+
# Python evaluates the default expression eagerly, so that
|
|
776
|
+
# form raises for a parent that is already registered but is
|
|
777
|
+
# intentionally absent from the current batch.
|
|
778
|
+
if parent_id in self._provenance:
|
|
779
|
+
parent = self._provenance[parent_id]
|
|
780
|
+
else:
|
|
781
|
+
parent = batch_by_id[parent_id]
|
|
782
|
+
if parent.generation >= value.generation:
|
|
783
|
+
raise ValueError(
|
|
784
|
+
"proposal parents must come from an earlier generation"
|
|
785
|
+
)
|
|
786
|
+
previous = self._provenance.get(value.candidate_id)
|
|
787
|
+
if previous is not None and previous != value:
|
|
788
|
+
raise ValueError("candidate provenance is immutable")
|
|
789
|
+
changed = False
|
|
790
|
+
for value in batch:
|
|
791
|
+
if value.candidate_id not in self._provenance:
|
|
792
|
+
self._provenance[value.candidate_id] = value
|
|
793
|
+
changed = True
|
|
794
|
+
if changed:
|
|
795
|
+
self._version += 1
|
|
796
|
+
|
|
797
|
+
def observe(
|
|
798
|
+
self,
|
|
799
|
+
receipt: ConservedStageCreditReceipt,
|
|
800
|
+
) -> ReproductionTicketIssuance:
|
|
801
|
+
if type(receipt) is not ConservedStageCreditReceipt:
|
|
802
|
+
raise TypeError("receipt must be an exact ConservedStageCreditReceipt")
|
|
803
|
+
ConservedStageCreditReceipt.__post_init__(receipt)
|
|
804
|
+
if self._credits and receipt.generation <= self._credits[-1].generation:
|
|
805
|
+
raise ValueError("stage credit generations must increase strictly")
|
|
806
|
+
unknown = {
|
|
807
|
+
value.candidate_id
|
|
808
|
+
for value in receipt.candidate_credits
|
|
809
|
+
if value.candidate_id not in self._provenance
|
|
810
|
+
}
|
|
811
|
+
if unknown:
|
|
812
|
+
raise ValueError("stage credit names candidates without provenance")
|
|
813
|
+
eligible = [
|
|
814
|
+
value
|
|
815
|
+
for value in receipt.candidate_credits
|
|
816
|
+
if value.contribution > self.policy.positive_credit_floor
|
|
817
|
+
and self._provenance[value.candidate_id].source_role
|
|
818
|
+
is ProposalLineageRole.CHALLENGER
|
|
819
|
+
]
|
|
820
|
+
eligible.sort(
|
|
821
|
+
key=lambda value: (-value.contribution, value.candidate_id.value)
|
|
822
|
+
)
|
|
823
|
+
tickets: list[ReproductionTicket] = []
|
|
824
|
+
for credit in eligible:
|
|
825
|
+
open_count = sum(
|
|
826
|
+
value.candidate_id == credit.candidate_id
|
|
827
|
+
and ticket_id not in self._consumed
|
|
828
|
+
and value.expires_after_generation >= receipt.generation + 1
|
|
829
|
+
for ticket_id, value in self._tickets.items()
|
|
830
|
+
)
|
|
831
|
+
if open_count >= self.policy.max_open_tickets_per_candidate:
|
|
832
|
+
continue
|
|
833
|
+
ticket = ReproductionTicket(
|
|
834
|
+
candidate_id=credit.candidate_id,
|
|
835
|
+
issued_after_generation=receipt.generation,
|
|
836
|
+
eligible_generation=receipt.generation + 1,
|
|
837
|
+
expires_after_generation=(
|
|
838
|
+
receipt.generation + self.policy.ticket_ttl_generations
|
|
839
|
+
),
|
|
840
|
+
conserved_credit=credit.contribution,
|
|
841
|
+
stage_credit_receipt_sha256=receipt.receipt_sha256,
|
|
842
|
+
policy_definition_sha256=self.policy.definition_sha256,
|
|
843
|
+
)
|
|
844
|
+
if ticket.ticket_sha256 in self._tickets:
|
|
845
|
+
raise RuntimeError("ticket identity collision")
|
|
846
|
+
tickets.append(ticket)
|
|
847
|
+
if len(tickets) == self.policy.max_tickets_per_stage:
|
|
848
|
+
break
|
|
849
|
+
issuance = ReproductionTicketIssuance(
|
|
850
|
+
generation=receipt.generation,
|
|
851
|
+
stage_credit_receipt_sha256=receipt.receipt_sha256,
|
|
852
|
+
policy_definition_sha256=self.policy.definition_sha256,
|
|
853
|
+
tickets=tuple(tickets),
|
|
854
|
+
)
|
|
855
|
+
self._credits.append(receipt)
|
|
856
|
+
self._tickets.update({value.ticket_sha256: value for value in tickets})
|
|
857
|
+
self._version += 1
|
|
858
|
+
return issuance
|
|
859
|
+
|
|
860
|
+
def prepare(
|
|
861
|
+
self,
|
|
862
|
+
*,
|
|
863
|
+
generation: int,
|
|
864
|
+
available_candidate_ids: tuple[CandidateId, ...],
|
|
865
|
+
maximum_tickets: int,
|
|
866
|
+
) -> PreparedReproductionReservation:
|
|
867
|
+
if type(generation) is not int or generation <= 0:
|
|
868
|
+
raise ValueError("generation must be positive")
|
|
869
|
+
_candidate_ids(
|
|
870
|
+
available_candidate_ids,
|
|
871
|
+
"available_candidate_ids",
|
|
872
|
+
allow_empty=False,
|
|
873
|
+
)
|
|
874
|
+
if type(maximum_tickets) is not int or maximum_tickets <= 0:
|
|
875
|
+
raise ValueError("maximum_tickets must be positive")
|
|
876
|
+
available = set(available_candidate_ids)
|
|
877
|
+
tickets = [
|
|
878
|
+
value
|
|
879
|
+
for ticket_id, value in self._tickets.items()
|
|
880
|
+
if ticket_id not in self._consumed
|
|
881
|
+
and ticket_id not in self._reserved_ticket_ids
|
|
882
|
+
and value.candidate_id in available
|
|
883
|
+
and value.eligible_generation <= generation
|
|
884
|
+
<= value.expires_after_generation
|
|
885
|
+
]
|
|
886
|
+
tickets.sort(
|
|
887
|
+
key=lambda value: (
|
|
888
|
+
-value.conserved_credit,
|
|
889
|
+
value.issued_after_generation,
|
|
890
|
+
value.candidate_id.value,
|
|
891
|
+
value.ticket_sha256,
|
|
892
|
+
)
|
|
893
|
+
)
|
|
894
|
+
selected = tuple(tickets[:maximum_tickets])
|
|
895
|
+
available_sha256 = hashlib.sha256(
|
|
896
|
+
_canonical_bytes(sorted(value.value for value in available_candidate_ids))
|
|
897
|
+
).hexdigest()
|
|
898
|
+
reservation = PreparedReproductionReservation(
|
|
899
|
+
generation=generation,
|
|
900
|
+
ledger_version=self._version,
|
|
901
|
+
available_candidate_ids_sha256=available_sha256,
|
|
902
|
+
tickets=selected,
|
|
903
|
+
policy_definition_sha256=self.policy.definition_sha256,
|
|
904
|
+
)
|
|
905
|
+
if reservation.tickets:
|
|
906
|
+
if reservation.reservation_sha256 in self._reservations:
|
|
907
|
+
raise RuntimeError("reservation identity collision")
|
|
908
|
+
self._reservations[reservation.reservation_sha256] = reservation
|
|
909
|
+
self._reserved_ticket_ids.update(
|
|
910
|
+
value.ticket_sha256 for value in reservation.tickets
|
|
911
|
+
)
|
|
912
|
+
return reservation
|
|
913
|
+
|
|
914
|
+
def abort(self, reservation: PreparedReproductionReservation) -> None:
|
|
915
|
+
reservation.__post_init__()
|
|
916
|
+
if not reservation.tickets:
|
|
917
|
+
return
|
|
918
|
+
known = self._reservations.pop(reservation.reservation_sha256, None)
|
|
919
|
+
if known != reservation:
|
|
920
|
+
raise ValueError("reservation is absent, stale, or already closed")
|
|
921
|
+
self._reserved_ticket_ids.difference_update(
|
|
922
|
+
value.ticket_sha256 for value in reservation.tickets
|
|
923
|
+
)
|
|
924
|
+
|
|
925
|
+
def commit(
|
|
926
|
+
self,
|
|
927
|
+
reservation: PreparedReproductionReservation,
|
|
928
|
+
*,
|
|
929
|
+
parent_exposure_receipt_sha256: str,
|
|
930
|
+
) -> ReproductionReservationCommit:
|
|
931
|
+
reservation.__post_init__()
|
|
932
|
+
require_sha256(
|
|
933
|
+
parent_exposure_receipt_sha256,
|
|
934
|
+
"parent_exposure_receipt_sha256",
|
|
935
|
+
)
|
|
936
|
+
if not reservation.tickets:
|
|
937
|
+
raise ValueError("an empty reservation has nothing to commit")
|
|
938
|
+
known = self._reservations.pop(reservation.reservation_sha256, None)
|
|
939
|
+
if known != reservation:
|
|
940
|
+
raise ValueError("reservation is absent, stale, or already closed")
|
|
941
|
+
ticket_ids = tuple(value.ticket_sha256 for value in reservation.tickets)
|
|
942
|
+
if any(value in self._consumed for value in ticket_ids):
|
|
943
|
+
raise RuntimeError("a reserved ticket was consumed concurrently")
|
|
944
|
+
commit = ReproductionReservationCommit(
|
|
945
|
+
reservation_sha256=reservation.reservation_sha256,
|
|
946
|
+
generation=reservation.generation,
|
|
947
|
+
consumed_ticket_sha256s=ticket_ids,
|
|
948
|
+
parent_exposure_receipt_sha256=parent_exposure_receipt_sha256,
|
|
949
|
+
)
|
|
950
|
+
self._reserved_ticket_ids.difference_update(ticket_ids)
|
|
951
|
+
self._consumed.update(ticket_ids)
|
|
952
|
+
self._commits.append(commit)
|
|
953
|
+
self._version += 1
|
|
954
|
+
return commit
|
|
955
|
+
|
|
956
|
+
def to_record(self, *, current_generation: int) -> dict[str, object]:
|
|
957
|
+
if type(current_generation) is not int or current_generation < 0:
|
|
958
|
+
raise ValueError("current_generation must be non-negative")
|
|
959
|
+
expired = {
|
|
960
|
+
ticket_id
|
|
961
|
+
for ticket_id, value in self._tickets.items()
|
|
962
|
+
if value.expires_after_generation < current_generation
|
|
963
|
+
and ticket_id not in self._consumed
|
|
964
|
+
}
|
|
965
|
+
return {
|
|
966
|
+
"schema_version": 1,
|
|
967
|
+
"policy": self.policy.to_record(),
|
|
968
|
+
"ledger_version": self._version,
|
|
969
|
+
"provenance": [
|
|
970
|
+
value.to_record()
|
|
971
|
+
for value in sorted(
|
|
972
|
+
self._provenance.values(),
|
|
973
|
+
key=lambda item: item.candidate_id.value,
|
|
974
|
+
)
|
|
975
|
+
],
|
|
976
|
+
"stage_credits": [value.to_record() for value in self._credits],
|
|
977
|
+
"tickets": [
|
|
978
|
+
value.to_record()
|
|
979
|
+
for value in sorted(
|
|
980
|
+
self._tickets.values(),
|
|
981
|
+
key=lambda item: item.ticket_sha256,
|
|
982
|
+
)
|
|
983
|
+
],
|
|
984
|
+
"consumed_ticket_sha256s": sorted(self._consumed),
|
|
985
|
+
"expired_unconsumed_ticket_sha256s": sorted(expired),
|
|
986
|
+
"open_reservations": [
|
|
987
|
+
value.to_record()
|
|
988
|
+
for value in sorted(
|
|
989
|
+
self._reservations.values(),
|
|
990
|
+
key=lambda item: item.reservation_sha256,
|
|
991
|
+
)
|
|
992
|
+
],
|
|
993
|
+
"commits": [value.to_record() for value in self._commits],
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
|
|
997
|
+
__all__ = [
|
|
998
|
+
"CandidateConservedCredit",
|
|
999
|
+
"CandidateProposalProvenance",
|
|
1000
|
+
"ConservedBackboneCeilingReceipt",
|
|
1001
|
+
"ConservedStageCreditReceipt",
|
|
1002
|
+
"EarnedLineageLedger",
|
|
1003
|
+
"EarnedLineagePolicy",
|
|
1004
|
+
"EarnedParentAllocation",
|
|
1005
|
+
"PreparedReproductionReservation",
|
|
1006
|
+
"ProposalLineageRole",
|
|
1007
|
+
"ReproductionReservationCommit",
|
|
1008
|
+
"ReproductionTicket",
|
|
1009
|
+
"ReproductionTicketIssuance",
|
|
1010
|
+
"allocate_earned_parents",
|
|
1011
|
+
]
|