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,1812 @@
|
|
|
1
|
+
"""Closed-loop lifecycle for reflected campaign insights.
|
|
2
|
+
|
|
3
|
+
This module joins three evidence channels without conflating them:
|
|
4
|
+
|
|
5
|
+
* a reflection creates an unverified card that remains quarantined;
|
|
6
|
+
* a campaign admission barrier may expose that exact version only to a
|
|
7
|
+
randomized diagnostic assignment;
|
|
8
|
+
* a later generation barrier supplies causal search-utility evidence, while a
|
|
9
|
+
separate global audit supplies semantic support or counterexamples.
|
|
10
|
+
|
|
11
|
+
Normal retrieval becomes possible only when both semantic support and minimum
|
|
12
|
+
causal usefulness are present. All decisions in one generation are prepared
|
|
13
|
+
before the memory bank publishes a canonical lifecycle batch.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import hashlib
|
|
19
|
+
import json
|
|
20
|
+
import math
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from enum import Enum
|
|
23
|
+
from fractions import Fraction
|
|
24
|
+
|
|
25
|
+
from agent_evolve.application.insight_memory import (
|
|
26
|
+
InsightLifecycleChangeRequest,
|
|
27
|
+
InsightLifecycleState,
|
|
28
|
+
InsightMemoryBank,
|
|
29
|
+
InsightMemoryEntry,
|
|
30
|
+
InsightOrigin,
|
|
31
|
+
QuarantineTestAdmissionReceipt,
|
|
32
|
+
)
|
|
33
|
+
from agent_evolve.application.portfolio_evolution import (
|
|
34
|
+
PortfolioMemoryCreditBatchReceipt,
|
|
35
|
+
)
|
|
36
|
+
from agent_evolve.domain.insight import InsightRef
|
|
37
|
+
from agent_evolve.domain.patch import require_sha256
|
|
38
|
+
from agent_evolve.policies.memory.global_falsification import (
|
|
39
|
+
GLOBAL_FALSIFICATION_POLICY_DEFINITION_SHA256,
|
|
40
|
+
GLOBAL_FALSIFICATION_POLICY_ID,
|
|
41
|
+
GLOBAL_FALSIFICATION_POLICY_VERSION,
|
|
42
|
+
EvidenceDisposition,
|
|
43
|
+
GlobalHypothesisAuditReceipt,
|
|
44
|
+
GlobalHypothesisAuditRequest,
|
|
45
|
+
GlobalHypothesisVerdict,
|
|
46
|
+
HypothesisAuditScope,
|
|
47
|
+
HypothesisClaimStrength,
|
|
48
|
+
HypothesisMetricPrediction,
|
|
49
|
+
TypedEvidencePredicate,
|
|
50
|
+
TypedInterventionSignature,
|
|
51
|
+
)
|
|
52
|
+
from agent_evolve.policies.memory.randomized_subset import (
|
|
53
|
+
InsightTrial,
|
|
54
|
+
MarginalEffectEstimate,
|
|
55
|
+
estimate_marginal_effect,
|
|
56
|
+
)
|
|
57
|
+
from agent_evolve.policies.memory.staged_causal import (
|
|
58
|
+
insight_selection_decision_sha256,
|
|
59
|
+
)
|
|
60
|
+
from agent_evolve.ports.agentic_generator import (
|
|
61
|
+
MetricEffectDirection,
|
|
62
|
+
ReflectionInsightKind,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
_REGISTRATION_DOMAIN = b"agent-evolve:campaign-insight-registration:v2\x00"
|
|
67
|
+
_ADMISSION_DOMAIN = b"agent-evolve:campaign-diagnostic-admission:v1\x00"
|
|
68
|
+
_CAUSAL_DOMAIN = b"agent-evolve:campaign-causal-usefulness:v2\x00"
|
|
69
|
+
_PROMOTION_EVIDENCE_DOMAIN = (
|
|
70
|
+
b"agent-evolve:campaign-randomized-promotion-evidence:v1\x00"
|
|
71
|
+
)
|
|
72
|
+
_DECISION_DOMAIN = b"agent-evolve:campaign-insight-lifecycle-decision:v1\x00"
|
|
73
|
+
_BARRIER_DOMAIN = b"agent-evolve:campaign-learning-barrier:v1\x00"
|
|
74
|
+
_POLICY_DOMAIN = b"agent-evolve:campaign-insight-promotion-policy:v2\x00"
|
|
75
|
+
_SEMANTIC_AUDIT_PLAN_DOMAIN = b"agent-evolve:campaign-semantic-audit-plan:v1\x00"
|
|
76
|
+
_PREPARED_BARRIER_DOMAIN = b"agent-evolve:campaign-prepared-learning-barrier:v1\x00"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _exact_one_sided_sign_p_value(*, successes: int, blocks: int) -> Fraction:
|
|
80
|
+
"""Exact upper binomial tail under a fair-sign null.
|
|
81
|
+
|
|
82
|
+
A block whose effect is equal to the practical margin is conservatively a
|
|
83
|
+
failure. The returned rational is retained in receipts so promotion does
|
|
84
|
+
not depend on platform-specific special functions or rounded p-values.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
if type(successes) is not int or type(blocks) is not int:
|
|
88
|
+
raise TypeError("sign-test counts must be exact integers")
|
|
89
|
+
if successes < 0 or blocks < 0 or successes > blocks:
|
|
90
|
+
raise ValueError("sign-test counts are invalid")
|
|
91
|
+
if blocks == 0:
|
|
92
|
+
return Fraction(1, 1)
|
|
93
|
+
return Fraction(
|
|
94
|
+
sum(math.comb(blocks, value) for value in range(successes, blocks + 1)),
|
|
95
|
+
2**blocks,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _canonical_json(value: object) -> bytes:
|
|
100
|
+
return json.dumps(
|
|
101
|
+
value,
|
|
102
|
+
allow_nan=False,
|
|
103
|
+
ensure_ascii=True,
|
|
104
|
+
separators=(",", ":"),
|
|
105
|
+
sort_keys=True,
|
|
106
|
+
).encode("ascii", errors="strict")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _hash(domain: bytes, value: object) -> str:
|
|
110
|
+
return hashlib.sha256(domain + _canonical_json(value)).hexdigest()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _reference_record(reference: InsightRef) -> dict[str, object]:
|
|
114
|
+
return {
|
|
115
|
+
"insight_id": reference.insight_id.value,
|
|
116
|
+
"version": reference.version,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _trial_record(trial: InsightTrial) -> dict[str, object]:
|
|
121
|
+
InsightTrial.__post_init__(trial)
|
|
122
|
+
return {
|
|
123
|
+
"credit_unit_id": trial.credit_unit_id.value,
|
|
124
|
+
"candidate_ids": [value.value for value in trial.candidate_ids],
|
|
125
|
+
"reward_definition_sha256": trial.reward_definition_hash,
|
|
126
|
+
"selection_decision_sha256": insight_selection_decision_sha256(trial.decision),
|
|
127
|
+
"reward_hex": trial.reward.hex(),
|
|
128
|
+
"treatment_binding_sha256": trial.treatment_binding_sha256,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@dataclass(frozen=True, slots=True)
|
|
133
|
+
class CampaignSemanticAuditPlan:
|
|
134
|
+
"""Pre-outcome identity for the exact semantic claim that may be audited.
|
|
135
|
+
|
|
136
|
+
Registry identity and the inclusive audit cutoff are intentionally absent:
|
|
137
|
+
those values exist only once the later evidence universe is sealed. Every
|
|
138
|
+
semantic degree of freedom, including the origin cutoff, is fixed before a
|
|
139
|
+
quarantined card may enter diagnostic testing.
|
|
140
|
+
|
|
141
|
+
The plan reuses the global falsification layer's typed predicate,
|
|
142
|
+
intervention, prediction, strength, and scope constructs. It deliberately
|
|
143
|
+
does not reuse the parent-bound executable-treatment compilation receipt:
|
|
144
|
+
that receipt answers whether one current finite palette can execute a card,
|
|
145
|
+
whereas this plan identifies the claim audited across a sealed registry.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
reference: InsightRef
|
|
149
|
+
draft_content_sha256: str
|
|
150
|
+
draft_hypothesis_sha256: str
|
|
151
|
+
trigger: TypedEvidencePredicate
|
|
152
|
+
intervention: TypedInterventionSignature
|
|
153
|
+
predictions: tuple[HypothesisMetricPrediction, ...]
|
|
154
|
+
claim_strength: HypothesisClaimStrength
|
|
155
|
+
scope: HypothesisAuditScope
|
|
156
|
+
matcher_definition_sha256: str
|
|
157
|
+
origin_cutoff_event_index: int
|
|
158
|
+
minimum_support_clusters: int
|
|
159
|
+
minimum_support_instances: int
|
|
160
|
+
audit_policy_definition_sha256: str = GLOBAL_FALSIFICATION_POLICY_DEFINITION_SHA256
|
|
161
|
+
plan_sha256: str = field(init=False)
|
|
162
|
+
|
|
163
|
+
def __post_init__(self) -> None:
|
|
164
|
+
if type(self.reference) is not InsightRef:
|
|
165
|
+
raise TypeError("reference must be an exact InsightRef")
|
|
166
|
+
InsightRef.__post_init__(self.reference)
|
|
167
|
+
require_sha256(self.draft_content_sha256, "draft_content_sha256")
|
|
168
|
+
require_sha256(self.draft_hypothesis_sha256, "draft_hypothesis_sha256")
|
|
169
|
+
if type(self.trigger) is not TypedEvidencePredicate:
|
|
170
|
+
raise TypeError("trigger must be an exact TypedEvidencePredicate")
|
|
171
|
+
TypedEvidencePredicate.__post_init__(self.trigger)
|
|
172
|
+
if type(self.intervention) is not TypedInterventionSignature:
|
|
173
|
+
raise TypeError("intervention must be an exact TypedInterventionSignature")
|
|
174
|
+
TypedInterventionSignature.__post_init__(self.intervention)
|
|
175
|
+
if (
|
|
176
|
+
type(self.predictions) is not tuple
|
|
177
|
+
or not self.predictions
|
|
178
|
+
or any(
|
|
179
|
+
type(value) is not HypothesisMetricPrediction
|
|
180
|
+
for value in self.predictions
|
|
181
|
+
)
|
|
182
|
+
):
|
|
183
|
+
raise ValueError("predictions must contain exact typed predictions")
|
|
184
|
+
for prediction in self.predictions:
|
|
185
|
+
HypothesisMetricPrediction.__post_init__(prediction)
|
|
186
|
+
if type(self.claim_strength) is not HypothesisClaimStrength:
|
|
187
|
+
raise TypeError("claim_strength must be exact")
|
|
188
|
+
HypothesisClaimStrength.__post_init__(self.claim_strength)
|
|
189
|
+
if type(self.scope) is not HypothesisAuditScope:
|
|
190
|
+
raise TypeError("scope must be exact")
|
|
191
|
+
HypothesisAuditScope.__post_init__(self.scope)
|
|
192
|
+
require_sha256(self.matcher_definition_sha256, "matcher_definition_sha256")
|
|
193
|
+
if (
|
|
194
|
+
type(self.origin_cutoff_event_index) is not int
|
|
195
|
+
or self.origin_cutoff_event_index < 0
|
|
196
|
+
):
|
|
197
|
+
raise ValueError("origin_cutoff_event_index must be non-negative")
|
|
198
|
+
for name in ("minimum_support_clusters", "minimum_support_instances"):
|
|
199
|
+
value = getattr(self, name)
|
|
200
|
+
if type(value) is not int or value <= 0:
|
|
201
|
+
raise ValueError(f"{name} must be a positive exact integer")
|
|
202
|
+
if (
|
|
203
|
+
self.audit_policy_definition_sha256
|
|
204
|
+
!= GLOBAL_FALSIFICATION_POLICY_DEFINITION_SHA256
|
|
205
|
+
):
|
|
206
|
+
raise ValueError("unsupported global falsification policy definition")
|
|
207
|
+
object.__setattr__(
|
|
208
|
+
self,
|
|
209
|
+
"plan_sha256",
|
|
210
|
+
_hash(_SEMANTIC_AUDIT_PLAN_DOMAIN, self._unsigned_record()),
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
@classmethod
|
|
214
|
+
def from_request(
|
|
215
|
+
cls,
|
|
216
|
+
request: GlobalHypothesisAuditRequest,
|
|
217
|
+
*,
|
|
218
|
+
draft_hypothesis_sha256: str,
|
|
219
|
+
) -> "CampaignSemanticAuditPlan":
|
|
220
|
+
if type(request) is not GlobalHypothesisAuditRequest:
|
|
221
|
+
raise TypeError("request must be an exact GlobalHypothesisAuditRequest")
|
|
222
|
+
GlobalHypothesisAuditRequest.__post_init__(request)
|
|
223
|
+
return cls(
|
|
224
|
+
reference=request.reference,
|
|
225
|
+
draft_content_sha256=request.draft_content_sha256,
|
|
226
|
+
draft_hypothesis_sha256=draft_hypothesis_sha256,
|
|
227
|
+
trigger=request.trigger,
|
|
228
|
+
intervention=request.intervention,
|
|
229
|
+
predictions=request.predictions,
|
|
230
|
+
claim_strength=request.claim_strength,
|
|
231
|
+
scope=request.scope,
|
|
232
|
+
matcher_definition_sha256=request.matcher_definition_sha256,
|
|
233
|
+
origin_cutoff_event_index=request.origin_cutoff_event_index,
|
|
234
|
+
minimum_support_clusters=request.minimum_support_clusters,
|
|
235
|
+
minimum_support_instances=request.minimum_support_instances,
|
|
236
|
+
audit_policy_definition_sha256=(request.audit_policy_definition_sha256),
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
240
|
+
return {
|
|
241
|
+
"schema_version": 1,
|
|
242
|
+
"reference": _reference_record(self.reference),
|
|
243
|
+
"draft_content_sha256": self.draft_content_sha256,
|
|
244
|
+
"draft_hypothesis_sha256": self.draft_hypothesis_sha256,
|
|
245
|
+
"trigger_predicate_sha256": self.trigger.predicate_sha256,
|
|
246
|
+
"intervention_signature_sha256": self.intervention.signature_sha256,
|
|
247
|
+
"predictions": [value.to_record() for value in self.predictions],
|
|
248
|
+
"claim_strength": self.claim_strength.to_record(),
|
|
249
|
+
"scope_sha256": self.scope.scope_sha256,
|
|
250
|
+
"matcher_definition_sha256": self.matcher_definition_sha256,
|
|
251
|
+
"origin_cutoff_event_index": self.origin_cutoff_event_index,
|
|
252
|
+
"minimum_support_clusters": self.minimum_support_clusters,
|
|
253
|
+
"minimum_support_instances": self.minimum_support_instances,
|
|
254
|
+
"audit_policy_definition_sha256": (self.audit_policy_definition_sha256),
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
def to_record(self) -> dict[str, object]:
|
|
258
|
+
self.__post_init__()
|
|
259
|
+
return {**self._unsigned_record(), "plan_sha256": self.plan_sha256}
|
|
260
|
+
|
|
261
|
+
def admits(self, request: GlobalHypothesisAuditRequest) -> bool:
|
|
262
|
+
"""Whether a later sealed request preserves every planned semantic field."""
|
|
263
|
+
|
|
264
|
+
if type(request) is not GlobalHypothesisAuditRequest:
|
|
265
|
+
return False
|
|
266
|
+
GlobalHypothesisAuditRequest.__post_init__(request)
|
|
267
|
+
observed = type(self).from_request(
|
|
268
|
+
request,
|
|
269
|
+
draft_hypothesis_sha256=self.draft_hypothesis_sha256,
|
|
270
|
+
)
|
|
271
|
+
return observed.plan_sha256 == self.plan_sha256
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
@dataclass(frozen=True, slots=True)
|
|
275
|
+
class CampaignRandomizedPromotionEvidence:
|
|
276
|
+
"""Exact robustness evidence for one randomized insight contrast.
|
|
277
|
+
|
|
278
|
+
This is intentionally separate from ``MarginalEffectEstimate``. The
|
|
279
|
+
latter remains useful for scoring, while this receipt records the stronger
|
|
280
|
+
evidence required to make a quarantined card normally retrievable.
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
reference: InsightRef
|
|
284
|
+
exact_context_sha256: str
|
|
285
|
+
source_trial_set_sha256: str
|
|
286
|
+
promotion_policy_definition_sha256: str
|
|
287
|
+
practical_effect_margin: float
|
|
288
|
+
independent_block_unit: str
|
|
289
|
+
block_effects: tuple[tuple[int, float], ...]
|
|
290
|
+
excluded_block_generations: tuple[int, ...]
|
|
291
|
+
missing_generation_trial_count: int
|
|
292
|
+
successful_block_count: int
|
|
293
|
+
exact_one_sided_p_value: Fraction
|
|
294
|
+
family_hypothesis_count: int
|
|
295
|
+
adjusted_alpha: float
|
|
296
|
+
failure_reasons: tuple[str, ...]
|
|
297
|
+
evidence_sha256: str = field(init=False)
|
|
298
|
+
|
|
299
|
+
def __post_init__(self) -> None:
|
|
300
|
+
if type(self.reference) is not InsightRef:
|
|
301
|
+
raise TypeError("reference must be an exact InsightRef")
|
|
302
|
+
InsightRef.__post_init__(self.reference)
|
|
303
|
+
require_sha256(self.exact_context_sha256, "exact_context_sha256")
|
|
304
|
+
require_sha256(self.source_trial_set_sha256, "source_trial_set_sha256")
|
|
305
|
+
require_sha256(
|
|
306
|
+
self.promotion_policy_definition_sha256,
|
|
307
|
+
"promotion_policy_definition_sha256",
|
|
308
|
+
)
|
|
309
|
+
for name in ("practical_effect_margin", "adjusted_alpha"):
|
|
310
|
+
value = getattr(self, name)
|
|
311
|
+
if type(value) is not float or not math.isfinite(value):
|
|
312
|
+
raise TypeError(f"{name} must be a finite canonical float")
|
|
313
|
+
if self.practical_effect_margin < 0:
|
|
314
|
+
raise ValueError("practical_effect_margin must be non-negative")
|
|
315
|
+
if not 0 < self.adjusted_alpha <= 0.5:
|
|
316
|
+
raise ValueError("adjusted_alpha must lie in (0, 0.5]")
|
|
317
|
+
if self.independent_block_unit != "campaign_generation":
|
|
318
|
+
raise ValueError("unsupported independent_block_unit")
|
|
319
|
+
if type(self.block_effects) is not tuple:
|
|
320
|
+
raise TypeError("block_effects must be an exact tuple")
|
|
321
|
+
generations: list[int] = []
|
|
322
|
+
effects: list[float] = []
|
|
323
|
+
for item in self.block_effects:
|
|
324
|
+
if type(item) is not tuple or len(item) != 2:
|
|
325
|
+
raise TypeError("block effects must be generation/effect pairs")
|
|
326
|
+
generation, effect = item
|
|
327
|
+
if type(generation) is not int or generation <= 0:
|
|
328
|
+
raise ValueError("block generation must be positive")
|
|
329
|
+
if type(effect) is not float or not math.isfinite(effect):
|
|
330
|
+
raise TypeError("block effect must be a finite canonical float")
|
|
331
|
+
generations.append(generation)
|
|
332
|
+
effects.append(effect)
|
|
333
|
+
if tuple(generations) != tuple(sorted(set(generations))):
|
|
334
|
+
raise ValueError("block generations must be unique and canonical")
|
|
335
|
+
if (
|
|
336
|
+
type(self.excluded_block_generations) is not tuple
|
|
337
|
+
or self.excluded_block_generations
|
|
338
|
+
!= tuple(sorted(set(self.excluded_block_generations)))
|
|
339
|
+
or set(self.excluded_block_generations).intersection(generations)
|
|
340
|
+
):
|
|
341
|
+
raise ValueError(
|
|
342
|
+
"excluded block generations must be disjoint and canonical"
|
|
343
|
+
)
|
|
344
|
+
for value in self.excluded_block_generations:
|
|
345
|
+
if type(value) is not int or value <= 0:
|
|
346
|
+
raise ValueError("excluded block generation must be positive")
|
|
347
|
+
if (
|
|
348
|
+
type(self.missing_generation_trial_count) is not int
|
|
349
|
+
or self.missing_generation_trial_count < 0
|
|
350
|
+
):
|
|
351
|
+
raise ValueError("missing_generation_trial_count must be non-negative")
|
|
352
|
+
expected_successes = sum(
|
|
353
|
+
value > self.practical_effect_margin for value in effects
|
|
354
|
+
)
|
|
355
|
+
if self.successful_block_count != expected_successes:
|
|
356
|
+
raise ValueError("successful_block_count differs from block effects")
|
|
357
|
+
if type(self.exact_one_sided_p_value) is not Fraction:
|
|
358
|
+
raise TypeError("exact_one_sided_p_value must be an exact Fraction")
|
|
359
|
+
expected_p_value = _exact_one_sided_sign_p_value(
|
|
360
|
+
successes=self.successful_block_count,
|
|
361
|
+
blocks=len(self.block_effects),
|
|
362
|
+
)
|
|
363
|
+
if self.exact_one_sided_p_value != expected_p_value:
|
|
364
|
+
raise ValueError("exact sign p-value differs from block effects")
|
|
365
|
+
if (
|
|
366
|
+
type(self.family_hypothesis_count) is not int
|
|
367
|
+
or self.family_hypothesis_count <= 0
|
|
368
|
+
):
|
|
369
|
+
raise ValueError("family_hypothesis_count must be positive")
|
|
370
|
+
if type(self.failure_reasons) is not tuple or any(
|
|
371
|
+
type(value) is not str or not value for value in self.failure_reasons
|
|
372
|
+
):
|
|
373
|
+
raise TypeError("failure_reasons must contain non-empty exact strings")
|
|
374
|
+
if self.failure_reasons != tuple(dict.fromkeys(self.failure_reasons)):
|
|
375
|
+
raise ValueError("failure_reasons must be unique and canonical")
|
|
376
|
+
object.__setattr__(
|
|
377
|
+
self,
|
|
378
|
+
"evidence_sha256",
|
|
379
|
+
_hash(_PROMOTION_EVIDENCE_DOMAIN, self._unsigned_record()),
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
@property
|
|
383
|
+
def passes_policy(self) -> bool:
|
|
384
|
+
return not self.failure_reasons
|
|
385
|
+
|
|
386
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
387
|
+
return {
|
|
388
|
+
"schema_version": 1,
|
|
389
|
+
"reference": _reference_record(self.reference),
|
|
390
|
+
"exact_context_sha256": self.exact_context_sha256,
|
|
391
|
+
"source_trial_set_sha256": self.source_trial_set_sha256,
|
|
392
|
+
"promotion_policy_definition_sha256": (
|
|
393
|
+
self.promotion_policy_definition_sha256
|
|
394
|
+
),
|
|
395
|
+
"practical_effect_margin_hex": self.practical_effect_margin.hex(),
|
|
396
|
+
"independent_block_unit": self.independent_block_unit,
|
|
397
|
+
"block_effects": [
|
|
398
|
+
{"generation": generation, "effect_hex": effect.hex()}
|
|
399
|
+
for generation, effect in self.block_effects
|
|
400
|
+
],
|
|
401
|
+
"excluded_block_generations": list(self.excluded_block_generations),
|
|
402
|
+
"missing_generation_trial_count": self.missing_generation_trial_count,
|
|
403
|
+
"successful_block_count": self.successful_block_count,
|
|
404
|
+
"exact_one_sided_p_value": {
|
|
405
|
+
"numerator": self.exact_one_sided_p_value.numerator,
|
|
406
|
+
"denominator": self.exact_one_sided_p_value.denominator,
|
|
407
|
+
},
|
|
408
|
+
"family_hypothesis_count": self.family_hypothesis_count,
|
|
409
|
+
"adjusted_alpha_hex": self.adjusted_alpha.hex(),
|
|
410
|
+
"failure_reasons": list(self.failure_reasons),
|
|
411
|
+
"passes_policy": self.passes_policy,
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
def to_record(self) -> dict[str, object]:
|
|
415
|
+
self.__post_init__()
|
|
416
|
+
return {**self._unsigned_record(), "evidence_sha256": self.evidence_sha256}
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
@dataclass(frozen=True, slots=True)
|
|
420
|
+
class CampaignInsightPromotionPolicy:
|
|
421
|
+
"""Predeclared conservative evidence for normal insight retrieval.
|
|
422
|
+
|
|
423
|
+
Promotion is deliberately harder than estimating a marginal effect. The
|
|
424
|
+
pooled stabilized-IPW contrast must clear a practical margin, both arms
|
|
425
|
+
must have adequate support, and that margin must recur across distinct
|
|
426
|
+
campaign-generation randomization blocks. The latter conjunct uses an
|
|
427
|
+
exact one-sided sign test and Bonferroni family-wise error control.
|
|
428
|
+
|
|
429
|
+
``generation`` is the narrowest authenticated block identifier carried by
|
|
430
|
+
every production ``InsightTrial`` today. Trials without that identifier
|
|
431
|
+
can still update diagnostic scores, but they cannot promote a card.
|
|
432
|
+
"""
|
|
433
|
+
|
|
434
|
+
minimum_treated_trials: int = 5
|
|
435
|
+
minimum_control_trials: int = 5
|
|
436
|
+
minimum_effective_support: float = 4.0
|
|
437
|
+
minimum_effect: float = 0.01
|
|
438
|
+
minimum_independent_blocks: int = 5
|
|
439
|
+
minimum_treated_trials_per_block: int = 1
|
|
440
|
+
minimum_control_trials_per_block: int = 1
|
|
441
|
+
familywise_error_rate: float = 0.05
|
|
442
|
+
independent_block_unit: str = "campaign_generation"
|
|
443
|
+
evidence_rule: str = "exact_one_sided_block_sign_test"
|
|
444
|
+
multiplicity_correction: str = "bonferroni"
|
|
445
|
+
policy_id: str = "semantic_support_and_robust_randomized_usefulness"
|
|
446
|
+
policy_version: int = 2
|
|
447
|
+
|
|
448
|
+
def __post_init__(self) -> None:
|
|
449
|
+
for name in (
|
|
450
|
+
"minimum_treated_trials",
|
|
451
|
+
"minimum_control_trials",
|
|
452
|
+
"minimum_independent_blocks",
|
|
453
|
+
"minimum_treated_trials_per_block",
|
|
454
|
+
"minimum_control_trials_per_block",
|
|
455
|
+
):
|
|
456
|
+
value = getattr(self, name)
|
|
457
|
+
if type(value) is not int or value <= 0:
|
|
458
|
+
raise ValueError(f"{name} must be a positive exact integer")
|
|
459
|
+
for name in (
|
|
460
|
+
"minimum_effective_support",
|
|
461
|
+
"minimum_effect",
|
|
462
|
+
"familywise_error_rate",
|
|
463
|
+
):
|
|
464
|
+
value = getattr(self, name)
|
|
465
|
+
if type(value) is not float or not math.isfinite(value):
|
|
466
|
+
raise TypeError(f"{name} must be a finite canonical float")
|
|
467
|
+
if self.minimum_effective_support <= 0:
|
|
468
|
+
raise ValueError("minimum_effective_support must be positive")
|
|
469
|
+
if self.minimum_effect < 0:
|
|
470
|
+
raise ValueError("minimum_effect must be non-negative")
|
|
471
|
+
if not 0 < self.familywise_error_rate <= 0.5:
|
|
472
|
+
raise ValueError("familywise_error_rate must lie in (0, 0.5]")
|
|
473
|
+
if self.independent_block_unit != "campaign_generation":
|
|
474
|
+
raise ValueError("unsupported independent_block_unit")
|
|
475
|
+
if self.evidence_rule != "exact_one_sided_block_sign_test":
|
|
476
|
+
raise ValueError("unsupported promotion evidence_rule")
|
|
477
|
+
if self.multiplicity_correction != "bonferroni":
|
|
478
|
+
raise ValueError("unsupported multiplicity_correction")
|
|
479
|
+
if self.policy_id != "semantic_support_and_robust_randomized_usefulness":
|
|
480
|
+
raise ValueError("unsupported promotion policy_id")
|
|
481
|
+
if type(self.policy_version) is not int or self.policy_version != 2:
|
|
482
|
+
raise ValueError("unsupported promotion policy_version")
|
|
483
|
+
|
|
484
|
+
def to_record(self) -> dict[str, object]:
|
|
485
|
+
self.__post_init__()
|
|
486
|
+
return {
|
|
487
|
+
"policy_id": self.policy_id,
|
|
488
|
+
"policy_version": self.policy_version,
|
|
489
|
+
"minimum_treated_trials": self.minimum_treated_trials,
|
|
490
|
+
"minimum_control_trials": self.minimum_control_trials,
|
|
491
|
+
"minimum_effective_support_hex": self.minimum_effective_support.hex(),
|
|
492
|
+
"minimum_effect_hex": self.minimum_effect.hex(),
|
|
493
|
+
"minimum_independent_blocks": self.minimum_independent_blocks,
|
|
494
|
+
"minimum_treated_trials_per_block": (self.minimum_treated_trials_per_block),
|
|
495
|
+
"minimum_control_trials_per_block": (self.minimum_control_trials_per_block),
|
|
496
|
+
"familywise_error_rate_hex": self.familywise_error_rate.hex(),
|
|
497
|
+
"independent_block_unit": self.independent_block_unit,
|
|
498
|
+
"evidence_rule": self.evidence_rule,
|
|
499
|
+
"multiplicity_correction": self.multiplicity_correction,
|
|
500
|
+
"effect_estimand": (
|
|
501
|
+
"stabilized_ipw_selected_minus_unselected_portfolio_reward"
|
|
502
|
+
),
|
|
503
|
+
"block_effect_estimand": (
|
|
504
|
+
"within_generation_stabilized_ipw_selected_minus_unselected_reward"
|
|
505
|
+
),
|
|
506
|
+
"tie_handling": "effect_equal_to_margin_is_not_success",
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
@property
|
|
510
|
+
def definition_sha256(self) -> str:
|
|
511
|
+
return _hash(_POLICY_DOMAIN, self.to_record())
|
|
512
|
+
|
|
513
|
+
def evaluate(
|
|
514
|
+
self,
|
|
515
|
+
*,
|
|
516
|
+
estimate: MarginalEffectEstimate,
|
|
517
|
+
source_trials: tuple[InsightTrial, ...],
|
|
518
|
+
source_trial_set_sha256: str,
|
|
519
|
+
family_hypothesis_count: int,
|
|
520
|
+
) -> CampaignRandomizedPromotionEvidence:
|
|
521
|
+
"""Build exact, replayable promotion evidence for one insight."""
|
|
522
|
+
|
|
523
|
+
self.__post_init__()
|
|
524
|
+
if type(estimate) is not MarginalEffectEstimate:
|
|
525
|
+
raise TypeError("estimate must be an exact MarginalEffectEstimate")
|
|
526
|
+
if estimate.context_hash is None:
|
|
527
|
+
raise ValueError("promotion requires an exact context stratum")
|
|
528
|
+
if type(source_trials) is not tuple or any(
|
|
529
|
+
type(value) is not InsightTrial for value in source_trials
|
|
530
|
+
):
|
|
531
|
+
raise TypeError("source_trials must contain exact InsightTrial values")
|
|
532
|
+
for value in source_trials:
|
|
533
|
+
InsightTrial.__post_init__(value)
|
|
534
|
+
require_sha256(source_trial_set_sha256, "source_trial_set_sha256")
|
|
535
|
+
if type(family_hypothesis_count) is not int or family_hypothesis_count <= 0:
|
|
536
|
+
raise ValueError("family_hypothesis_count must be positive")
|
|
537
|
+
|
|
538
|
+
replayed = estimate_marginal_effect(
|
|
539
|
+
source_trials,
|
|
540
|
+
estimate.insight,
|
|
541
|
+
context_hash=estimate.context_hash,
|
|
542
|
+
)
|
|
543
|
+
if replayed != estimate:
|
|
544
|
+
raise ValueError("promotion estimate differs from its source trials")
|
|
545
|
+
|
|
546
|
+
trials_by_generation: dict[int, list[InsightTrial]] = {}
|
|
547
|
+
missing_generation_trial_count = 0
|
|
548
|
+
for trial in source_trials:
|
|
549
|
+
if trial.generation is None:
|
|
550
|
+
missing_generation_trial_count += 1
|
|
551
|
+
else:
|
|
552
|
+
trials_by_generation.setdefault(trial.generation, []).append(trial)
|
|
553
|
+
|
|
554
|
+
block_effects: list[tuple[int, float]] = []
|
|
555
|
+
excluded_generations: list[int] = []
|
|
556
|
+
for generation in sorted(trials_by_generation):
|
|
557
|
+
block_estimate = estimate_marginal_effect(
|
|
558
|
+
tuple(trials_by_generation[generation]),
|
|
559
|
+
estimate.insight,
|
|
560
|
+
context_hash=estimate.context_hash,
|
|
561
|
+
)
|
|
562
|
+
if (
|
|
563
|
+
block_estimate.effect is None
|
|
564
|
+
or block_estimate.treated_trials < self.minimum_treated_trials_per_block
|
|
565
|
+
or block_estimate.control_trials < self.minimum_control_trials_per_block
|
|
566
|
+
):
|
|
567
|
+
excluded_generations.append(generation)
|
|
568
|
+
continue
|
|
569
|
+
block_effects.append((generation, block_estimate.effect))
|
|
570
|
+
|
|
571
|
+
successes = sum(effect > self.minimum_effect for _, effect in block_effects)
|
|
572
|
+
p_value = _exact_one_sided_sign_p_value(
|
|
573
|
+
successes=successes,
|
|
574
|
+
blocks=len(block_effects),
|
|
575
|
+
)
|
|
576
|
+
adjusted_alpha = self.familywise_error_rate / family_hypothesis_count
|
|
577
|
+
failure_reasons = self._failure_reasons(
|
|
578
|
+
estimate=estimate,
|
|
579
|
+
block_count=len(block_effects),
|
|
580
|
+
missing_generation_trial_count=missing_generation_trial_count,
|
|
581
|
+
exact_one_sided_p_value=p_value,
|
|
582
|
+
adjusted_alpha=adjusted_alpha,
|
|
583
|
+
)
|
|
584
|
+
return CampaignRandomizedPromotionEvidence(
|
|
585
|
+
reference=estimate.insight,
|
|
586
|
+
exact_context_sha256=estimate.context_hash,
|
|
587
|
+
source_trial_set_sha256=source_trial_set_sha256,
|
|
588
|
+
promotion_policy_definition_sha256=self.definition_sha256,
|
|
589
|
+
practical_effect_margin=self.minimum_effect,
|
|
590
|
+
independent_block_unit=self.independent_block_unit,
|
|
591
|
+
block_effects=tuple(block_effects),
|
|
592
|
+
excluded_block_generations=tuple(excluded_generations),
|
|
593
|
+
missing_generation_trial_count=missing_generation_trial_count,
|
|
594
|
+
successful_block_count=successes,
|
|
595
|
+
exact_one_sided_p_value=p_value,
|
|
596
|
+
family_hypothesis_count=family_hypothesis_count,
|
|
597
|
+
adjusted_alpha=adjusted_alpha,
|
|
598
|
+
failure_reasons=failure_reasons,
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
def _failure_reasons(
|
|
602
|
+
self,
|
|
603
|
+
*,
|
|
604
|
+
estimate: MarginalEffectEstimate,
|
|
605
|
+
block_count: int,
|
|
606
|
+
missing_generation_trial_count: int,
|
|
607
|
+
exact_one_sided_p_value: Fraction,
|
|
608
|
+
adjusted_alpha: float,
|
|
609
|
+
) -> tuple[str, ...]:
|
|
610
|
+
reasons: list[str] = []
|
|
611
|
+
if estimate.effect is None:
|
|
612
|
+
reasons.append("pooled_effect_not_identified")
|
|
613
|
+
elif estimate.effect <= self.minimum_effect:
|
|
614
|
+
reasons.append("pooled_effect_does_not_clear_practical_margin")
|
|
615
|
+
if estimate.treated_trials < self.minimum_treated_trials:
|
|
616
|
+
reasons.append("insufficient_treated_trials")
|
|
617
|
+
if estimate.control_trials < self.minimum_control_trials:
|
|
618
|
+
reasons.append("insufficient_control_trials")
|
|
619
|
+
if (
|
|
620
|
+
min(
|
|
621
|
+
estimate.treated_effective_sample_size,
|
|
622
|
+
estimate.control_effective_sample_size,
|
|
623
|
+
)
|
|
624
|
+
< self.minimum_effective_support
|
|
625
|
+
):
|
|
626
|
+
reasons.append("insufficient_effective_support")
|
|
627
|
+
if missing_generation_trial_count:
|
|
628
|
+
reasons.append("missing_authenticated_generation_block")
|
|
629
|
+
if block_count < self.minimum_independent_blocks:
|
|
630
|
+
reasons.append("insufficient_independent_blocks")
|
|
631
|
+
if float(exact_one_sided_p_value) > adjusted_alpha:
|
|
632
|
+
reasons.append("exact_block_sign_evidence_exceeds_adjusted_alpha")
|
|
633
|
+
return tuple(reasons)
|
|
634
|
+
|
|
635
|
+
def admits(self, evidence: CampaignRandomizedPromotionEvidence) -> bool:
|
|
636
|
+
self.__post_init__()
|
|
637
|
+
if type(evidence) is not CampaignRandomizedPromotionEvidence:
|
|
638
|
+
raise TypeError(
|
|
639
|
+
"evidence must be exact CampaignRandomizedPromotionEvidence"
|
|
640
|
+
)
|
|
641
|
+
CampaignRandomizedPromotionEvidence.__post_init__(evidence)
|
|
642
|
+
if evidence.promotion_policy_definition_sha256 != self.definition_sha256:
|
|
643
|
+
raise ValueError("promotion evidence was built under another policy")
|
|
644
|
+
return evidence.passes_policy
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
@dataclass(frozen=True, slots=True)
|
|
648
|
+
class CampaignInsightRegistrationReceipt:
|
|
649
|
+
origin_generation: int
|
|
650
|
+
entries: tuple[tuple[InsightRef, str], ...]
|
|
651
|
+
semantic_audit_plans: tuple[tuple[InsightRef, str, str], ...]
|
|
652
|
+
memory_trial_count_at_registration: int
|
|
653
|
+
receipt_sha256: str = field(init=False)
|
|
654
|
+
|
|
655
|
+
def __post_init__(self) -> None:
|
|
656
|
+
if type(self.origin_generation) is not int or self.origin_generation <= 0:
|
|
657
|
+
raise ValueError("origin_generation must be positive")
|
|
658
|
+
if type(self.entries) is not tuple or not self.entries:
|
|
659
|
+
raise ValueError("registration entries must be a non-empty exact tuple")
|
|
660
|
+
references: list[InsightRef] = []
|
|
661
|
+
for item in self.entries:
|
|
662
|
+
if type(item) is not tuple or len(item) != 2:
|
|
663
|
+
raise TypeError("registration entries must be reference/hash pairs")
|
|
664
|
+
reference, content_sha256 = item
|
|
665
|
+
if type(reference) is not InsightRef:
|
|
666
|
+
raise TypeError("registration reference must be exact")
|
|
667
|
+
require_sha256(content_sha256, "draft_content_sha256")
|
|
668
|
+
references.append(reference)
|
|
669
|
+
if tuple(references) != tuple(sorted(set(references))):
|
|
670
|
+
raise ValueError("registration references must be unique and canonical")
|
|
671
|
+
if type(self.semantic_audit_plans) is not tuple:
|
|
672
|
+
raise TypeError("semantic_audit_plans must be an exact tuple")
|
|
673
|
+
planned_references: list[InsightRef] = []
|
|
674
|
+
for item in self.semantic_audit_plans:
|
|
675
|
+
if type(item) is not tuple or len(item) != 3:
|
|
676
|
+
raise TypeError(
|
|
677
|
+
"semantic audit plans must be reference/hypothesis/plan triples"
|
|
678
|
+
)
|
|
679
|
+
reference, hypothesis_sha256, plan_sha256 = item
|
|
680
|
+
if type(reference) is not InsightRef:
|
|
681
|
+
raise TypeError("semantic audit plan reference must be exact")
|
|
682
|
+
require_sha256(hypothesis_sha256, "draft_hypothesis_sha256")
|
|
683
|
+
require_sha256(plan_sha256, "semantic_audit_plan_sha256")
|
|
684
|
+
planned_references.append(reference)
|
|
685
|
+
if tuple(planned_references) != tuple(references):
|
|
686
|
+
raise ValueError(
|
|
687
|
+
"semantic audit plans must exactly cover registration references"
|
|
688
|
+
)
|
|
689
|
+
if (
|
|
690
|
+
type(self.memory_trial_count_at_registration) is not int
|
|
691
|
+
or self.memory_trial_count_at_registration < 0
|
|
692
|
+
):
|
|
693
|
+
raise ValueError("memory trial count must be non-negative")
|
|
694
|
+
object.__setattr__(
|
|
695
|
+
self,
|
|
696
|
+
"receipt_sha256",
|
|
697
|
+
_hash(_REGISTRATION_DOMAIN, self._unsigned_record()),
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
701
|
+
return {
|
|
702
|
+
"schema_version": 2,
|
|
703
|
+
"origin_generation": self.origin_generation,
|
|
704
|
+
"entries": [
|
|
705
|
+
{
|
|
706
|
+
"reference": _reference_record(reference),
|
|
707
|
+
"draft_content_sha256": content_sha256,
|
|
708
|
+
}
|
|
709
|
+
for reference, content_sha256 in self.entries
|
|
710
|
+
],
|
|
711
|
+
"semantic_audit_plans": [
|
|
712
|
+
{
|
|
713
|
+
"reference": _reference_record(reference),
|
|
714
|
+
"draft_hypothesis_sha256": hypothesis_sha256,
|
|
715
|
+
"semantic_audit_plan_sha256": plan_sha256,
|
|
716
|
+
}
|
|
717
|
+
for reference, hypothesis_sha256, plan_sha256 in (
|
|
718
|
+
self.semantic_audit_plans
|
|
719
|
+
)
|
|
720
|
+
],
|
|
721
|
+
"memory_trial_count_at_registration": (
|
|
722
|
+
self.memory_trial_count_at_registration
|
|
723
|
+
),
|
|
724
|
+
"lifecycle_state": "quarantined",
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
def to_record(self) -> dict[str, object]:
|
|
728
|
+
return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
@dataclass(frozen=True, slots=True)
|
|
732
|
+
class CampaignDiagnosticAdmissionReceipt:
|
|
733
|
+
admission_generation: int
|
|
734
|
+
references: tuple[InsightRef, ...]
|
|
735
|
+
registration_receipt_sha256s: tuple[str, ...]
|
|
736
|
+
campaign_admission_request_sha256: str
|
|
737
|
+
memory_admission_receipt_sha256: str
|
|
738
|
+
operator_kind: str
|
|
739
|
+
editable_paths: tuple[str, ...]
|
|
740
|
+
memory_trial_count_cutoff: int
|
|
741
|
+
receipt_sha256: str = field(init=False)
|
|
742
|
+
|
|
743
|
+
def __post_init__(self) -> None:
|
|
744
|
+
if type(self.admission_generation) is not int or self.admission_generation <= 0:
|
|
745
|
+
raise ValueError("admission_generation must be positive")
|
|
746
|
+
if (
|
|
747
|
+
type(self.references) is not tuple
|
|
748
|
+
or not self.references
|
|
749
|
+
or any(type(value) is not InsightRef for value in self.references)
|
|
750
|
+
):
|
|
751
|
+
raise ValueError("references must be a non-empty exact tuple")
|
|
752
|
+
if self.references != tuple(sorted(set(self.references))):
|
|
753
|
+
raise ValueError("references must be unique and canonical")
|
|
754
|
+
if (
|
|
755
|
+
self.registration_receipt_sha256s
|
|
756
|
+
!= tuple(sorted(set(self.registration_receipt_sha256s)))
|
|
757
|
+
or not self.registration_receipt_sha256s
|
|
758
|
+
):
|
|
759
|
+
raise ValueError("registration receipt hashes must be canonical")
|
|
760
|
+
for value in self.registration_receipt_sha256s:
|
|
761
|
+
require_sha256(value, "registration_receipt_sha256")
|
|
762
|
+
require_sha256(
|
|
763
|
+
self.campaign_admission_request_sha256,
|
|
764
|
+
"campaign_admission_request_sha256",
|
|
765
|
+
)
|
|
766
|
+
require_sha256(
|
|
767
|
+
self.memory_admission_receipt_sha256,
|
|
768
|
+
"memory_admission_receipt_sha256",
|
|
769
|
+
)
|
|
770
|
+
if type(self.operator_kind) is not str or not self.operator_kind:
|
|
771
|
+
raise ValueError("operator_kind must be non-empty")
|
|
772
|
+
if type(self.editable_paths) is not tuple:
|
|
773
|
+
raise TypeError("editable_paths must be an exact tuple")
|
|
774
|
+
if self.editable_paths != tuple(sorted(set(self.editable_paths))):
|
|
775
|
+
raise ValueError("editable_paths must be unique and canonical")
|
|
776
|
+
if (
|
|
777
|
+
type(self.memory_trial_count_cutoff) is not int
|
|
778
|
+
or self.memory_trial_count_cutoff < 0
|
|
779
|
+
):
|
|
780
|
+
raise ValueError("memory trial cutoff must be non-negative")
|
|
781
|
+
object.__setattr__(
|
|
782
|
+
self,
|
|
783
|
+
"receipt_sha256",
|
|
784
|
+
_hash(_ADMISSION_DOMAIN, self._unsigned_record()),
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
788
|
+
return {
|
|
789
|
+
"schema_version": 1,
|
|
790
|
+
"admission_generation": self.admission_generation,
|
|
791
|
+
"references": [_reference_record(value) for value in self.references],
|
|
792
|
+
"registration_receipt_sha256s": list(self.registration_receipt_sha256s),
|
|
793
|
+
"campaign_admission_request_sha256": (
|
|
794
|
+
self.campaign_admission_request_sha256
|
|
795
|
+
),
|
|
796
|
+
"memory_admission_receipt_sha256": (self.memory_admission_receipt_sha256),
|
|
797
|
+
"operator_kind": self.operator_kind,
|
|
798
|
+
"editable_paths": list(self.editable_paths),
|
|
799
|
+
"memory_trial_count_cutoff": self.memory_trial_count_cutoff,
|
|
800
|
+
"visibility": "diagnostic_only_not_normal_retrieval",
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
def to_record(self) -> dict[str, object]:
|
|
804
|
+
return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
@dataclass(frozen=True, slots=True)
|
|
808
|
+
class CampaignInsightAuditBinding:
|
|
809
|
+
request: GlobalHypothesisAuditRequest
|
|
810
|
+
receipt: GlobalHypothesisAuditReceipt
|
|
811
|
+
exact_context_sha256: str
|
|
812
|
+
|
|
813
|
+
def __post_init__(self) -> None:
|
|
814
|
+
if type(self.request) is not GlobalHypothesisAuditRequest:
|
|
815
|
+
raise TypeError("request must be an exact GlobalHypothesisAuditRequest")
|
|
816
|
+
if type(self.receipt) is not GlobalHypothesisAuditReceipt:
|
|
817
|
+
raise TypeError("receipt must be an exact GlobalHypothesisAuditReceipt")
|
|
818
|
+
GlobalHypothesisAuditRequest.__post_init__(self.request)
|
|
819
|
+
GlobalHypothesisAuditReceipt.__post_init__(self.receipt)
|
|
820
|
+
if self.receipt.request_sha256 != self.request.request_sha256:
|
|
821
|
+
raise ValueError("global audit receipt belongs to a different request")
|
|
822
|
+
if (
|
|
823
|
+
self.receipt.registry_snapshot_sha256
|
|
824
|
+
!= self.request.registry_snapshot_sha256
|
|
825
|
+
):
|
|
826
|
+
raise ValueError("global audit receipt names a different registry")
|
|
827
|
+
if (
|
|
828
|
+
self.request.audit_policy_definition_sha256
|
|
829
|
+
!= GLOBAL_FALSIFICATION_POLICY_DEFINITION_SHA256
|
|
830
|
+
):
|
|
831
|
+
raise ValueError("global audit request substituted the sealed policy")
|
|
832
|
+
expected_policy = (
|
|
833
|
+
GLOBAL_FALSIFICATION_POLICY_ID,
|
|
834
|
+
GLOBAL_FALSIFICATION_POLICY_VERSION,
|
|
835
|
+
GLOBAL_FALSIFICATION_POLICY_DEFINITION_SHA256,
|
|
836
|
+
)
|
|
837
|
+
observed_policy = (
|
|
838
|
+
self.receipt.audit_policy_id,
|
|
839
|
+
self.receipt.audit_policy_version,
|
|
840
|
+
self.receipt.audit_policy_definition_sha256,
|
|
841
|
+
)
|
|
842
|
+
if observed_policy != expected_policy:
|
|
843
|
+
raise ValueError("global audit receipt substituted the sealed policy")
|
|
844
|
+
require_sha256(self.exact_context_sha256, "exact_context_sha256")
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
@dataclass(frozen=True, slots=True)
|
|
848
|
+
class CampaignCausalUsefulnessReceipt:
|
|
849
|
+
reference: InsightRef
|
|
850
|
+
exact_context_sha256: str
|
|
851
|
+
admission_generation: int
|
|
852
|
+
barrier_generation: int
|
|
853
|
+
admission_trial_count_cutoff: int
|
|
854
|
+
barrier_trial_count_cutoff: int
|
|
855
|
+
source_trial_set_sha256: str
|
|
856
|
+
estimate: MarginalEffectEstimate
|
|
857
|
+
randomized_promotion_evidence: CampaignRandomizedPromotionEvidence
|
|
858
|
+
receipt_sha256: str = field(init=False)
|
|
859
|
+
|
|
860
|
+
def __post_init__(self) -> None:
|
|
861
|
+
if type(self.reference) is not InsightRef:
|
|
862
|
+
raise TypeError("reference must be exact")
|
|
863
|
+
require_sha256(self.exact_context_sha256, "exact_context_sha256")
|
|
864
|
+
if (
|
|
865
|
+
type(self.admission_generation) is not int
|
|
866
|
+
or type(self.barrier_generation) is not int
|
|
867
|
+
or self.admission_generation <= 0
|
|
868
|
+
or self.barrier_generation <= self.admission_generation
|
|
869
|
+
):
|
|
870
|
+
raise ValueError("causal evidence requires a later barrier generation")
|
|
871
|
+
if (
|
|
872
|
+
type(self.admission_trial_count_cutoff) is not int
|
|
873
|
+
or type(self.barrier_trial_count_cutoff) is not int
|
|
874
|
+
or self.admission_trial_count_cutoff < 0
|
|
875
|
+
or self.barrier_trial_count_cutoff < self.admission_trial_count_cutoff
|
|
876
|
+
):
|
|
877
|
+
raise ValueError("causal trial cutoffs are invalid")
|
|
878
|
+
require_sha256(self.source_trial_set_sha256, "source_trial_set_sha256")
|
|
879
|
+
if type(self.estimate) is not MarginalEffectEstimate:
|
|
880
|
+
raise TypeError("estimate must be exact")
|
|
881
|
+
if (
|
|
882
|
+
self.estimate.insight != self.reference
|
|
883
|
+
or self.estimate.context_hash != self.exact_context_sha256
|
|
884
|
+
):
|
|
885
|
+
raise ValueError("causal estimate differs from its insight/context")
|
|
886
|
+
if (
|
|
887
|
+
type(self.randomized_promotion_evidence)
|
|
888
|
+
is not CampaignRandomizedPromotionEvidence
|
|
889
|
+
):
|
|
890
|
+
raise TypeError("randomized_promotion_evidence must be exact")
|
|
891
|
+
CampaignRandomizedPromotionEvidence.__post_init__(
|
|
892
|
+
self.randomized_promotion_evidence
|
|
893
|
+
)
|
|
894
|
+
evidence = self.randomized_promotion_evidence
|
|
895
|
+
if (
|
|
896
|
+
evidence.reference != self.reference
|
|
897
|
+
or evidence.exact_context_sha256 != self.exact_context_sha256
|
|
898
|
+
or evidence.source_trial_set_sha256 != self.source_trial_set_sha256
|
|
899
|
+
):
|
|
900
|
+
raise ValueError("randomized promotion evidence differs from causal source")
|
|
901
|
+
object.__setattr__(
|
|
902
|
+
self,
|
|
903
|
+
"receipt_sha256",
|
|
904
|
+
_hash(_CAUSAL_DOMAIN, self._unsigned_record()),
|
|
905
|
+
)
|
|
906
|
+
|
|
907
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
908
|
+
estimate = self.estimate
|
|
909
|
+
return {
|
|
910
|
+
"schema_version": 2,
|
|
911
|
+
"reference": _reference_record(self.reference),
|
|
912
|
+
"exact_context_sha256": self.exact_context_sha256,
|
|
913
|
+
"admission_generation": self.admission_generation,
|
|
914
|
+
"barrier_generation": self.barrier_generation,
|
|
915
|
+
"admission_trial_count_cutoff": self.admission_trial_count_cutoff,
|
|
916
|
+
"barrier_trial_count_cutoff": self.barrier_trial_count_cutoff,
|
|
917
|
+
"source_trial_set_sha256": self.source_trial_set_sha256,
|
|
918
|
+
"effect_hex": None if estimate.effect is None else estimate.effect.hex(),
|
|
919
|
+
"treated_trials": estimate.treated_trials,
|
|
920
|
+
"control_trials": estimate.control_trials,
|
|
921
|
+
"treated_ess_hex": estimate.treated_effective_sample_size.hex(),
|
|
922
|
+
"control_ess_hex": estimate.control_effective_sample_size.hex(),
|
|
923
|
+
"eligible_trials": estimate.eligible_trials,
|
|
924
|
+
"overlap_trials": estimate.overlap_trials,
|
|
925
|
+
"estimand": "randomized_selected_minus_unselected_portfolio_reward",
|
|
926
|
+
"randomized_promotion_evidence": (
|
|
927
|
+
self.randomized_promotion_evidence.to_record()
|
|
928
|
+
),
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
def to_record(self) -> dict[str, object]:
|
|
932
|
+
return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
|
|
933
|
+
|
|
934
|
+
|
|
935
|
+
class CampaignInsightLifecycleDecision(str, Enum):
|
|
936
|
+
PROMOTE = "promote"
|
|
937
|
+
DEPRECATE = "deprecate"
|
|
938
|
+
RETAIN_QUARANTINE = "retain_quarantine"
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
@dataclass(frozen=True, slots=True)
|
|
942
|
+
class CampaignInsightDecisionReceipt:
|
|
943
|
+
reference: InsightRef
|
|
944
|
+
semantic_audit_request_sha256: str
|
|
945
|
+
semantic_audit_receipt_sha256: str
|
|
946
|
+
causal_usefulness: CampaignCausalUsefulnessReceipt
|
|
947
|
+
decision: CampaignInsightLifecycleDecision
|
|
948
|
+
reason: str
|
|
949
|
+
lifecycle_transition_sequence: int | None
|
|
950
|
+
receipt_sha256: str = field(init=False)
|
|
951
|
+
|
|
952
|
+
def __post_init__(self) -> None:
|
|
953
|
+
if type(self.reference) is not InsightRef:
|
|
954
|
+
raise TypeError("reference must be exact")
|
|
955
|
+
require_sha256(
|
|
956
|
+
self.semantic_audit_request_sha256,
|
|
957
|
+
"semantic_audit_request_sha256",
|
|
958
|
+
)
|
|
959
|
+
require_sha256(
|
|
960
|
+
self.semantic_audit_receipt_sha256,
|
|
961
|
+
"semantic_audit_receipt_sha256",
|
|
962
|
+
)
|
|
963
|
+
if type(self.causal_usefulness) is not CampaignCausalUsefulnessReceipt:
|
|
964
|
+
raise TypeError("causal_usefulness must be exact")
|
|
965
|
+
if self.causal_usefulness.reference != self.reference:
|
|
966
|
+
raise ValueError("causal usefulness belongs to another insight")
|
|
967
|
+
if type(self.decision) is not CampaignInsightLifecycleDecision:
|
|
968
|
+
raise TypeError("decision must be exact")
|
|
969
|
+
if type(self.reason) is not str or not self.reason.strip():
|
|
970
|
+
raise ValueError("reason must be non-empty")
|
|
971
|
+
transitioned = self.decision is not (
|
|
972
|
+
CampaignInsightLifecycleDecision.RETAIN_QUARANTINE
|
|
973
|
+
)
|
|
974
|
+
if transitioned != (self.lifecycle_transition_sequence is not None):
|
|
975
|
+
raise ValueError("lifecycle transition sequence differs from decision")
|
|
976
|
+
if self.lifecycle_transition_sequence is not None and (
|
|
977
|
+
type(self.lifecycle_transition_sequence) is not int
|
|
978
|
+
or self.lifecycle_transition_sequence <= 0
|
|
979
|
+
):
|
|
980
|
+
raise ValueError("lifecycle transition sequence must be positive")
|
|
981
|
+
object.__setattr__(
|
|
982
|
+
self,
|
|
983
|
+
"receipt_sha256",
|
|
984
|
+
_hash(_DECISION_DOMAIN, self._unsigned_record()),
|
|
985
|
+
)
|
|
986
|
+
|
|
987
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
988
|
+
return {
|
|
989
|
+
"schema_version": 1,
|
|
990
|
+
"reference": _reference_record(self.reference),
|
|
991
|
+
"semantic_audit_request_sha256": self.semantic_audit_request_sha256,
|
|
992
|
+
"semantic_audit_receipt_sha256": self.semantic_audit_receipt_sha256,
|
|
993
|
+
"causal_usefulness_receipt_sha256": (self.causal_usefulness.receipt_sha256),
|
|
994
|
+
"decision": self.decision.value,
|
|
995
|
+
"reason": self.reason,
|
|
996
|
+
"lifecycle_transition_sequence": self.lifecycle_transition_sequence,
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
def to_record(self) -> dict[str, object]:
|
|
1000
|
+
return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
|
|
1001
|
+
|
|
1002
|
+
|
|
1003
|
+
@dataclass(frozen=True, slots=True)
|
|
1004
|
+
class CampaignLearningBarrierReceipt:
|
|
1005
|
+
generation: int
|
|
1006
|
+
memory_credit_batch_receipt_sha256: str
|
|
1007
|
+
promotion_policy_definition_sha256: str
|
|
1008
|
+
decisions: tuple[CampaignInsightDecisionReceipt, ...]
|
|
1009
|
+
receipt_sha256: str = field(init=False)
|
|
1010
|
+
|
|
1011
|
+
def __post_init__(self) -> None:
|
|
1012
|
+
if type(self.generation) is not int or self.generation <= 0:
|
|
1013
|
+
raise ValueError("generation must be positive")
|
|
1014
|
+
require_sha256(
|
|
1015
|
+
self.memory_credit_batch_receipt_sha256,
|
|
1016
|
+
"memory_credit_batch_receipt_sha256",
|
|
1017
|
+
)
|
|
1018
|
+
require_sha256(
|
|
1019
|
+
self.promotion_policy_definition_sha256,
|
|
1020
|
+
"promotion_policy_definition_sha256",
|
|
1021
|
+
)
|
|
1022
|
+
if (
|
|
1023
|
+
type(self.decisions) is not tuple
|
|
1024
|
+
or not self.decisions
|
|
1025
|
+
or any(
|
|
1026
|
+
type(value) is not CampaignInsightDecisionReceipt
|
|
1027
|
+
for value in self.decisions
|
|
1028
|
+
)
|
|
1029
|
+
):
|
|
1030
|
+
raise ValueError("decisions must be a non-empty exact tuple")
|
|
1031
|
+
references = tuple(value.reference for value in self.decisions)
|
|
1032
|
+
if references != tuple(sorted(set(references))):
|
|
1033
|
+
raise ValueError("decisions must use unique canonical insight order")
|
|
1034
|
+
object.__setattr__(
|
|
1035
|
+
self,
|
|
1036
|
+
"receipt_sha256",
|
|
1037
|
+
_hash(_BARRIER_DOMAIN, self._unsigned_record()),
|
|
1038
|
+
)
|
|
1039
|
+
|
|
1040
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
1041
|
+
return {
|
|
1042
|
+
"schema_version": 1,
|
|
1043
|
+
"generation": self.generation,
|
|
1044
|
+
"memory_credit_batch_receipt_sha256": (
|
|
1045
|
+
self.memory_credit_batch_receipt_sha256
|
|
1046
|
+
),
|
|
1047
|
+
"promotion_policy_definition_sha256": (
|
|
1048
|
+
self.promotion_policy_definition_sha256
|
|
1049
|
+
),
|
|
1050
|
+
"decisions": [value.to_record() for value in self.decisions],
|
|
1051
|
+
"publication_scope": "post_generation_barrier_atomic_lifecycle_batch",
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
def to_record(self) -> dict[str, object]:
|
|
1055
|
+
return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
|
|
1056
|
+
|
|
1057
|
+
|
|
1058
|
+
@dataclass(frozen=True, slots=True)
|
|
1059
|
+
class CampaignPreparedLearningBarrier:
|
|
1060
|
+
"""Pure, sealed lifecycle decision awaiting memory-credit publication.
|
|
1061
|
+
|
|
1062
|
+
``batch_trials`` are the exact trials expected to become the memory suffix.
|
|
1063
|
+
Preparing this value does not mutate either the bank or coordinator. Commit
|
|
1064
|
+
succeeds only if that exact suffix was published and lifecycle state stayed
|
|
1065
|
+
unchanged, leaving a deterministic atomic lifecycle batch.
|
|
1066
|
+
"""
|
|
1067
|
+
|
|
1068
|
+
memory_credit_batch: PortfolioMemoryCreditBatchReceipt
|
|
1069
|
+
audits: tuple[CampaignInsightAuditBinding, ...]
|
|
1070
|
+
batch_trials: tuple[InsightTrial, ...]
|
|
1071
|
+
lifecycle_requests: tuple[InsightLifecycleChangeRequest, ...]
|
|
1072
|
+
barrier_receipt: CampaignLearningBarrierReceipt
|
|
1073
|
+
memory_trial_count_at_prepare: int
|
|
1074
|
+
transition_count_at_prepare: int
|
|
1075
|
+
preparation_sha256: str = field(init=False)
|
|
1076
|
+
|
|
1077
|
+
def __post_init__(self) -> None:
|
|
1078
|
+
if type(self.memory_credit_batch) is not PortfolioMemoryCreditBatchReceipt:
|
|
1079
|
+
raise TypeError("memory_credit_batch must be exact")
|
|
1080
|
+
PortfolioMemoryCreditBatchReceipt.__post_init__(self.memory_credit_batch)
|
|
1081
|
+
if (
|
|
1082
|
+
type(self.audits) is not tuple
|
|
1083
|
+
or not self.audits
|
|
1084
|
+
or any(
|
|
1085
|
+
type(value) is not CampaignInsightAuditBinding for value in self.audits
|
|
1086
|
+
)
|
|
1087
|
+
):
|
|
1088
|
+
raise ValueError("audits must contain exact audit bindings")
|
|
1089
|
+
for value in self.audits:
|
|
1090
|
+
CampaignInsightAuditBinding.__post_init__(value)
|
|
1091
|
+
audit_references = tuple(value.request.reference for value in self.audits)
|
|
1092
|
+
if audit_references != tuple(sorted(set(audit_references))):
|
|
1093
|
+
raise ValueError("audits must use unique canonical insight order")
|
|
1094
|
+
if (
|
|
1095
|
+
type(self.batch_trials) is not tuple
|
|
1096
|
+
or not self.batch_trials
|
|
1097
|
+
or any(type(value) is not InsightTrial for value in self.batch_trials)
|
|
1098
|
+
):
|
|
1099
|
+
raise ValueError("batch_trials must contain exact trials")
|
|
1100
|
+
for value in self.batch_trials:
|
|
1101
|
+
InsightTrial.__post_init__(value)
|
|
1102
|
+
credit_ids = tuple(value.credit_unit_id.value for value in self.batch_trials)
|
|
1103
|
+
if credit_ids != tuple(sorted(set(credit_ids))):
|
|
1104
|
+
raise ValueError("batch_trials must use canonical unique credit order")
|
|
1105
|
+
if len(self.batch_trials) != len(self.memory_credit_batch.credits):
|
|
1106
|
+
raise ValueError("batch_trials differ from the memory credit batch")
|
|
1107
|
+
if type(self.lifecycle_requests) is not tuple or any(
|
|
1108
|
+
type(value) is not InsightLifecycleChangeRequest
|
|
1109
|
+
for value in self.lifecycle_requests
|
|
1110
|
+
):
|
|
1111
|
+
raise TypeError("lifecycle_requests must contain exact requests")
|
|
1112
|
+
for value in self.lifecycle_requests:
|
|
1113
|
+
InsightLifecycleChangeRequest.__post_init__(value)
|
|
1114
|
+
lifecycle_references = tuple(
|
|
1115
|
+
value.reference for value in self.lifecycle_requests
|
|
1116
|
+
)
|
|
1117
|
+
if lifecycle_references != tuple(sorted(set(lifecycle_references))):
|
|
1118
|
+
raise ValueError("lifecycle requests must use canonical insight order")
|
|
1119
|
+
if type(self.barrier_receipt) is not CampaignLearningBarrierReceipt:
|
|
1120
|
+
raise TypeError("barrier_receipt must be exact")
|
|
1121
|
+
CampaignLearningBarrierReceipt.__post_init__(self.barrier_receipt)
|
|
1122
|
+
if (
|
|
1123
|
+
self.barrier_receipt.generation != self.memory_credit_batch.generation
|
|
1124
|
+
or self.barrier_receipt.memory_credit_batch_receipt_sha256
|
|
1125
|
+
!= self.memory_credit_batch.receipt_sha256
|
|
1126
|
+
):
|
|
1127
|
+
raise ValueError("prepared barrier differs from its memory credit batch")
|
|
1128
|
+
transitioned = tuple(
|
|
1129
|
+
value.reference
|
|
1130
|
+
for value in self.barrier_receipt.decisions
|
|
1131
|
+
if value.lifecycle_transition_sequence is not None
|
|
1132
|
+
)
|
|
1133
|
+
if transitioned != lifecycle_references:
|
|
1134
|
+
raise ValueError("prepared lifecycle requests differ from decisions")
|
|
1135
|
+
for name in (
|
|
1136
|
+
"memory_trial_count_at_prepare",
|
|
1137
|
+
"transition_count_at_prepare",
|
|
1138
|
+
):
|
|
1139
|
+
value = getattr(self, name)
|
|
1140
|
+
if type(value) is not int or value < 0:
|
|
1141
|
+
raise ValueError(f"{name} must be a non-negative exact integer")
|
|
1142
|
+
if self.memory_trial_count_at_prepare not in {
|
|
1143
|
+
self.memory_credit_batch.memory_trial_count_before,
|
|
1144
|
+
self.memory_credit_batch.memory_trial_count_after,
|
|
1145
|
+
}:
|
|
1146
|
+
raise ValueError("prepare trial count is outside the batch boundary")
|
|
1147
|
+
object.__setattr__(
|
|
1148
|
+
self,
|
|
1149
|
+
"preparation_sha256",
|
|
1150
|
+
_hash(_PREPARED_BARRIER_DOMAIN, self._unsigned_record()),
|
|
1151
|
+
)
|
|
1152
|
+
|
|
1153
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
1154
|
+
return {
|
|
1155
|
+
"schema_version": 1,
|
|
1156
|
+
"memory_credit_batch_receipt_sha256": (
|
|
1157
|
+
self.memory_credit_batch.receipt_sha256
|
|
1158
|
+
),
|
|
1159
|
+
"semantic_audit_request_sha256s": [
|
|
1160
|
+
value.request.request_sha256 for value in self.audits
|
|
1161
|
+
],
|
|
1162
|
+
"semantic_audit_receipt_sha256s": [
|
|
1163
|
+
value.receipt.audit_receipt_sha256 for value in self.audits
|
|
1164
|
+
],
|
|
1165
|
+
"batch_trials": [_trial_record(value) for value in self.batch_trials],
|
|
1166
|
+
"lifecycle_requests": [
|
|
1167
|
+
{
|
|
1168
|
+
"reference": _reference_record(value.reference),
|
|
1169
|
+
"new_state": value.new_state.value,
|
|
1170
|
+
"reason": value.reason,
|
|
1171
|
+
"supporting_evidence": list(value.supporting_evidence),
|
|
1172
|
+
}
|
|
1173
|
+
for value in self.lifecycle_requests
|
|
1174
|
+
],
|
|
1175
|
+
"barrier_receipt_sha256": self.barrier_receipt.receipt_sha256,
|
|
1176
|
+
"memory_trial_count_at_prepare": self.memory_trial_count_at_prepare,
|
|
1177
|
+
"transition_count_at_prepare": self.transition_count_at_prepare,
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
def to_record(self) -> dict[str, object]:
|
|
1181
|
+
self.__post_init__()
|
|
1182
|
+
return {
|
|
1183
|
+
**self._unsigned_record(),
|
|
1184
|
+
"preparation_sha256": self.preparation_sha256,
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
|
|
1188
|
+
@dataclass(slots=True)
|
|
1189
|
+
class ClosedLoopCampaignLearning:
|
|
1190
|
+
"""Stateful coordinator around one campaign-owned insight memory bank."""
|
|
1191
|
+
|
|
1192
|
+
memory: InsightMemoryBank
|
|
1193
|
+
promotion_policy: CampaignInsightPromotionPolicy = field(
|
|
1194
|
+
default_factory=CampaignInsightPromotionPolicy
|
|
1195
|
+
)
|
|
1196
|
+
_registrations: dict[InsightRef, CampaignInsightRegistrationReceipt] = field(
|
|
1197
|
+
init=False,
|
|
1198
|
+
default_factory=dict,
|
|
1199
|
+
)
|
|
1200
|
+
_admissions: dict[InsightRef, CampaignDiagnosticAdmissionReceipt] = field(
|
|
1201
|
+
init=False,
|
|
1202
|
+
default_factory=dict,
|
|
1203
|
+
)
|
|
1204
|
+
_audit_plans: dict[InsightRef, CampaignSemanticAuditPlan] = field(
|
|
1205
|
+
init=False,
|
|
1206
|
+
default_factory=dict,
|
|
1207
|
+
)
|
|
1208
|
+
_closed_generations: set[int] = field(init=False, default_factory=set)
|
|
1209
|
+
|
|
1210
|
+
def __post_init__(self) -> None:
|
|
1211
|
+
if type(self.memory) is not InsightMemoryBank:
|
|
1212
|
+
raise TypeError("memory must be an exact InsightMemoryBank")
|
|
1213
|
+
if type(self.promotion_policy) is not CampaignInsightPromotionPolicy:
|
|
1214
|
+
raise TypeError("promotion_policy must be exact")
|
|
1215
|
+
self.promotion_policy.__post_init__()
|
|
1216
|
+
|
|
1217
|
+
def register_quarantined_reflections(
|
|
1218
|
+
self,
|
|
1219
|
+
*,
|
|
1220
|
+
origin_generation: int,
|
|
1221
|
+
references: tuple[InsightRef, ...],
|
|
1222
|
+
semantic_audit_plans: tuple[CampaignSemanticAuditPlan, ...],
|
|
1223
|
+
) -> CampaignInsightRegistrationReceipt:
|
|
1224
|
+
self.__post_init__()
|
|
1225
|
+
if type(origin_generation) is not int or origin_generation <= 0:
|
|
1226
|
+
raise ValueError("origin_generation must be positive")
|
|
1227
|
+
if type(references) is not tuple or not references:
|
|
1228
|
+
raise ValueError("references must be a non-empty exact tuple")
|
|
1229
|
+
canonical = tuple(sorted(references))
|
|
1230
|
+
if len(set(canonical)) != len(canonical):
|
|
1231
|
+
raise ValueError("references cannot contain duplicates")
|
|
1232
|
+
if any(value in self._registrations for value in canonical):
|
|
1233
|
+
raise ValueError("an insight was already registered")
|
|
1234
|
+
if type(semantic_audit_plans) is not tuple or any(
|
|
1235
|
+
type(value) is not CampaignSemanticAuditPlan
|
|
1236
|
+
for value in semantic_audit_plans
|
|
1237
|
+
):
|
|
1238
|
+
raise TypeError(
|
|
1239
|
+
"semantic_audit_plans must contain exact CampaignSemanticAuditPlan values"
|
|
1240
|
+
)
|
|
1241
|
+
for value in semantic_audit_plans:
|
|
1242
|
+
CampaignSemanticAuditPlan.__post_init__(value)
|
|
1243
|
+
plans = tuple(sorted(semantic_audit_plans, key=lambda value: value.reference))
|
|
1244
|
+
if tuple(value.reference for value in plans) != canonical:
|
|
1245
|
+
raise ValueError(
|
|
1246
|
+
"semantic audit plans must exactly cover registration references"
|
|
1247
|
+
)
|
|
1248
|
+
entries = self.memory.entries_for(canonical)
|
|
1249
|
+
if any(
|
|
1250
|
+
entry.lifecycle_state is not InsightLifecycleState.QUARANTINED
|
|
1251
|
+
or entry.origin is not InsightOrigin.REFLECTION
|
|
1252
|
+
for entry in entries
|
|
1253
|
+
):
|
|
1254
|
+
raise ValueError("registration requires reflection-origin quarantine cards")
|
|
1255
|
+
for entry, plan in zip(entries, plans, strict=True):
|
|
1256
|
+
self._validate_semantic_audit_plan(entry, plan)
|
|
1257
|
+
receipt = CampaignInsightRegistrationReceipt(
|
|
1258
|
+
origin_generation=origin_generation,
|
|
1259
|
+
entries=tuple(
|
|
1260
|
+
(entry.reference, entry.draft.content_sha256) for entry in entries
|
|
1261
|
+
),
|
|
1262
|
+
semantic_audit_plans=tuple(
|
|
1263
|
+
(
|
|
1264
|
+
entry.reference,
|
|
1265
|
+
entry.draft.hypothesis_sha256,
|
|
1266
|
+
plan.plan_sha256,
|
|
1267
|
+
)
|
|
1268
|
+
for entry, plan in zip(entries, plans, strict=True)
|
|
1269
|
+
),
|
|
1270
|
+
memory_trial_count_at_registration=len(self.memory.trials),
|
|
1271
|
+
)
|
|
1272
|
+
for reference, plan in zip(canonical, plans, strict=True):
|
|
1273
|
+
self._registrations[reference] = receipt
|
|
1274
|
+
self._audit_plans[reference] = plan
|
|
1275
|
+
return receipt
|
|
1276
|
+
|
|
1277
|
+
@staticmethod
|
|
1278
|
+
def _validate_semantic_audit_plan(
|
|
1279
|
+
entry: InsightMemoryEntry,
|
|
1280
|
+
plan: CampaignSemanticAuditPlan,
|
|
1281
|
+
) -> None:
|
|
1282
|
+
"""Join a pre-outcome typed audit plan to one exact actionable card."""
|
|
1283
|
+
|
|
1284
|
+
draft = entry.draft
|
|
1285
|
+
if not draft.has_semantic_contract or not draft.has_intervention_contract:
|
|
1286
|
+
raise ValueError(
|
|
1287
|
+
"closed-loop registration requires an actionable semantic-v3 card"
|
|
1288
|
+
)
|
|
1289
|
+
if draft.insight_kind not in {
|
|
1290
|
+
ReflectionInsightKind.EMPIRICAL_PREDICTIVE_RULE,
|
|
1291
|
+
ReflectionInsightKind.MECHANISTIC_CONJECTURE,
|
|
1292
|
+
}:
|
|
1293
|
+
raise ValueError(
|
|
1294
|
+
"closed-loop registration rejects heuristic or invariant card kinds"
|
|
1295
|
+
)
|
|
1296
|
+
if not entry.applicable_operator_kinds:
|
|
1297
|
+
raise ValueError(
|
|
1298
|
+
"closed-loop registration requires explicit operator applicability"
|
|
1299
|
+
)
|
|
1300
|
+
if draft.affected_paths != tuple(sorted(set(draft.affected_paths))):
|
|
1301
|
+
raise ValueError("closed-loop affected paths must be canonical")
|
|
1302
|
+
if any(
|
|
1303
|
+
value.direction is MetricEffectDirection.UNKNOWN
|
|
1304
|
+
for value in draft.effect_predictions
|
|
1305
|
+
):
|
|
1306
|
+
raise ValueError(
|
|
1307
|
+
"closed-loop promotion requires known auditable metric predictions"
|
|
1308
|
+
)
|
|
1309
|
+
expected_predictions = tuple(
|
|
1310
|
+
(value.metric_id, value.direction, None, None)
|
|
1311
|
+
for value in draft.effect_predictions
|
|
1312
|
+
)
|
|
1313
|
+
observed_predictions = tuple(
|
|
1314
|
+
(
|
|
1315
|
+
value.metric_id,
|
|
1316
|
+
value.direction,
|
|
1317
|
+
value.minimum_delta,
|
|
1318
|
+
value.maximum_delta,
|
|
1319
|
+
)
|
|
1320
|
+
for value in plan.predictions
|
|
1321
|
+
)
|
|
1322
|
+
expected_strength = HypothesisClaimStrength(
|
|
1323
|
+
sufficiency=True,
|
|
1324
|
+
necessity=False,
|
|
1325
|
+
invariance=False,
|
|
1326
|
+
mechanistic_or_causal=(
|
|
1327
|
+
draft.insight_kind is ReflectionInsightKind.MECHANISTIC_CONJECTURE
|
|
1328
|
+
),
|
|
1329
|
+
)
|
|
1330
|
+
expected = (
|
|
1331
|
+
entry.reference,
|
|
1332
|
+
draft.content_sha256,
|
|
1333
|
+
draft.hypothesis_sha256,
|
|
1334
|
+
draft.affected_paths,
|
|
1335
|
+
entry.applicable_operator_kinds,
|
|
1336
|
+
expected_predictions,
|
|
1337
|
+
expected_strength,
|
|
1338
|
+
)
|
|
1339
|
+
observed = (
|
|
1340
|
+
plan.reference,
|
|
1341
|
+
plan.draft_content_sha256,
|
|
1342
|
+
plan.draft_hypothesis_sha256,
|
|
1343
|
+
plan.intervention.affected_paths,
|
|
1344
|
+
plan.intervention.admissible_operator_families,
|
|
1345
|
+
observed_predictions,
|
|
1346
|
+
plan.claim_strength,
|
|
1347
|
+
)
|
|
1348
|
+
if observed != expected:
|
|
1349
|
+
raise ValueError(
|
|
1350
|
+
"semantic audit plan differs from the registered card hypothesis"
|
|
1351
|
+
)
|
|
1352
|
+
|
|
1353
|
+
def audit_plans_for(
|
|
1354
|
+
self,
|
|
1355
|
+
references: tuple[InsightRef, ...],
|
|
1356
|
+
) -> tuple[CampaignSemanticAuditPlan, ...]:
|
|
1357
|
+
"""Return revalidated pre-outcome plans in exact canonical order.
|
|
1358
|
+
|
|
1359
|
+
This narrow projection lets an outcome-audit adapter compile a later
|
|
1360
|
+
sealed request without reaching into coordinator state. It never
|
|
1361
|
+
sorts on the caller's behalf: order substitution is rejected so the
|
|
1362
|
+
returned tuple can be joined positionally to a canonical audit family.
|
|
1363
|
+
"""
|
|
1364
|
+
|
|
1365
|
+
self.__post_init__()
|
|
1366
|
+
if type(references) is not tuple or not references:
|
|
1367
|
+
raise ValueError("references must be a non-empty exact tuple")
|
|
1368
|
+
if any(type(value) is not InsightRef for value in references):
|
|
1369
|
+
raise TypeError("references must contain exact InsightRef values")
|
|
1370
|
+
for value in references:
|
|
1371
|
+
InsightRef.__post_init__(value)
|
|
1372
|
+
if references != tuple(sorted(set(references))):
|
|
1373
|
+
raise ValueError("references must be unique and canonically ordered")
|
|
1374
|
+
try:
|
|
1375
|
+
plans = tuple(self._audit_plans[value] for value in references)
|
|
1376
|
+
except KeyError as error:
|
|
1377
|
+
raise ValueError(
|
|
1378
|
+
"reference has no registered semantic audit plan"
|
|
1379
|
+
) from error
|
|
1380
|
+
entries = self.memory.entries_for(references)
|
|
1381
|
+
for entry, plan in zip(entries, plans, strict=True):
|
|
1382
|
+
CampaignSemanticAuditPlan.__post_init__(plan)
|
|
1383
|
+
self._validate_semantic_audit_plan(entry, plan)
|
|
1384
|
+
return plans
|
|
1385
|
+
|
|
1386
|
+
def admit_for_diagnostic_testing(
|
|
1387
|
+
self,
|
|
1388
|
+
*,
|
|
1389
|
+
admission_generation: int,
|
|
1390
|
+
references: tuple[InsightRef, ...],
|
|
1391
|
+
campaign_admission_request_sha256: str,
|
|
1392
|
+
operator_kind: str,
|
|
1393
|
+
editable_paths: tuple[str, ...] = (),
|
|
1394
|
+
) -> CampaignDiagnosticAdmissionReceipt:
|
|
1395
|
+
self.__post_init__()
|
|
1396
|
+
if type(references) is not tuple or not references:
|
|
1397
|
+
raise ValueError("references must be a non-empty exact tuple")
|
|
1398
|
+
canonical = tuple(sorted(references))
|
|
1399
|
+
if len(set(canonical)) != len(canonical):
|
|
1400
|
+
raise ValueError("references cannot contain duplicates")
|
|
1401
|
+
if any(value not in self._registrations for value in canonical):
|
|
1402
|
+
raise ValueError("diagnostic admission names an unregistered insight")
|
|
1403
|
+
if any(value in self._admissions for value in canonical):
|
|
1404
|
+
raise ValueError("an insight was already admitted for diagnostics")
|
|
1405
|
+
if any(
|
|
1406
|
+
admission_generation < self._registrations[value].origin_generation
|
|
1407
|
+
for value in canonical
|
|
1408
|
+
):
|
|
1409
|
+
raise ValueError("diagnostic admission precedes reflection origin")
|
|
1410
|
+
memory_admission: QuarantineTestAdmissionReceipt = (
|
|
1411
|
+
self.memory.admit_quarantine_test_assignment(
|
|
1412
|
+
canonical,
|
|
1413
|
+
operator_kind=operator_kind,
|
|
1414
|
+
source_admission_request_sha256=(campaign_admission_request_sha256),
|
|
1415
|
+
editable_paths=editable_paths or None,
|
|
1416
|
+
)
|
|
1417
|
+
)
|
|
1418
|
+
receipt = CampaignDiagnosticAdmissionReceipt(
|
|
1419
|
+
admission_generation=admission_generation,
|
|
1420
|
+
references=memory_admission.references,
|
|
1421
|
+
registration_receipt_sha256s=tuple(
|
|
1422
|
+
sorted(
|
|
1423
|
+
{
|
|
1424
|
+
self._registrations[value].receipt_sha256
|
|
1425
|
+
for value in memory_admission.references
|
|
1426
|
+
}
|
|
1427
|
+
)
|
|
1428
|
+
),
|
|
1429
|
+
campaign_admission_request_sha256=(campaign_admission_request_sha256),
|
|
1430
|
+
memory_admission_receipt_sha256=(memory_admission.receipt_sha256),
|
|
1431
|
+
operator_kind=operator_kind,
|
|
1432
|
+
editable_paths=tuple(sorted(set(editable_paths))),
|
|
1433
|
+
memory_trial_count_cutoff=(memory_admission.memory_trial_count_cutoff),
|
|
1434
|
+
)
|
|
1435
|
+
for reference in memory_admission.references:
|
|
1436
|
+
self._admissions[reference] = receipt
|
|
1437
|
+
return receipt
|
|
1438
|
+
|
|
1439
|
+
def prepare_generation_close(
|
|
1440
|
+
self,
|
|
1441
|
+
*,
|
|
1442
|
+
memory_credit_batch: PortfolioMemoryCreditBatchReceipt,
|
|
1443
|
+
audits: tuple[CampaignInsightAuditBinding, ...],
|
|
1444
|
+
prospective_trials: tuple[InsightTrial, ...] = (),
|
|
1445
|
+
) -> CampaignPreparedLearningBarrier:
|
|
1446
|
+
"""Prepare all semantic/causal decisions without mutating campaign state."""
|
|
1447
|
+
|
|
1448
|
+
self.__post_init__()
|
|
1449
|
+
if type(memory_credit_batch) is not PortfolioMemoryCreditBatchReceipt:
|
|
1450
|
+
raise TypeError("memory_credit_batch must be exact")
|
|
1451
|
+
PortfolioMemoryCreditBatchReceipt.__post_init__(memory_credit_batch)
|
|
1452
|
+
generation = memory_credit_batch.generation
|
|
1453
|
+
if generation in self._closed_generations:
|
|
1454
|
+
raise ValueError("campaign learning generation was already closed")
|
|
1455
|
+
if type(prospective_trials) is not tuple or any(
|
|
1456
|
+
type(value) is not InsightTrial for value in prospective_trials
|
|
1457
|
+
):
|
|
1458
|
+
raise TypeError("prospective_trials must contain exact InsightTrial values")
|
|
1459
|
+
for value in prospective_trials:
|
|
1460
|
+
InsightTrial.__post_init__(value)
|
|
1461
|
+
memory_trial_count = len(self.memory.trials)
|
|
1462
|
+
if memory_trial_count == memory_credit_batch.memory_trial_count_after:
|
|
1463
|
+
committed = self.memory.trials[
|
|
1464
|
+
memory_credit_batch.memory_trial_count_before : memory_credit_batch.memory_trial_count_after
|
|
1465
|
+
]
|
|
1466
|
+
if prospective_trials and prospective_trials != committed:
|
|
1467
|
+
raise ValueError("prospective trials differ from committed memory")
|
|
1468
|
+
batch_trials = committed
|
|
1469
|
+
trial_universe = self.memory.trials
|
|
1470
|
+
elif memory_trial_count == memory_credit_batch.memory_trial_count_before:
|
|
1471
|
+
if not prospective_trials:
|
|
1472
|
+
raise ValueError(
|
|
1473
|
+
"precommit campaign learning requires prospective memory trials"
|
|
1474
|
+
)
|
|
1475
|
+
batch_trials = prospective_trials
|
|
1476
|
+
trial_universe = (*self.memory.trials, *prospective_trials)
|
|
1477
|
+
else:
|
|
1478
|
+
raise ValueError("memory bank is outside the sealed batch boundary")
|
|
1479
|
+
self._validate_memory_credit_batch_trials(
|
|
1480
|
+
memory_credit_batch,
|
|
1481
|
+
batch_trials,
|
|
1482
|
+
)
|
|
1483
|
+
if len(trial_universe) != memory_credit_batch.memory_trial_count_after:
|
|
1484
|
+
raise ValueError("prospective trials do not close the memory batch")
|
|
1485
|
+
if type(audits) is not tuple or not audits:
|
|
1486
|
+
raise ValueError("audits must be a non-empty exact tuple")
|
|
1487
|
+
if any(type(value) is not CampaignInsightAuditBinding for value in audits):
|
|
1488
|
+
raise TypeError("audits must contain exact bindings")
|
|
1489
|
+
for value in audits:
|
|
1490
|
+
CampaignInsightAuditBinding.__post_init__(value)
|
|
1491
|
+
canonical = tuple(sorted(audits, key=lambda value: value.request.reference))
|
|
1492
|
+
references = tuple(value.request.reference for value in canonical)
|
|
1493
|
+
if len(set(references)) != len(references):
|
|
1494
|
+
raise ValueError("audits cannot repeat an insight reference")
|
|
1495
|
+
|
|
1496
|
+
prepared: list[
|
|
1497
|
+
tuple[
|
|
1498
|
+
CampaignInsightAuditBinding,
|
|
1499
|
+
CampaignCausalUsefulnessReceipt,
|
|
1500
|
+
CampaignInsightLifecycleDecision,
|
|
1501
|
+
str,
|
|
1502
|
+
]
|
|
1503
|
+
] = []
|
|
1504
|
+
lifecycle_requests: list[InsightLifecycleChangeRequest] = []
|
|
1505
|
+
for binding in canonical:
|
|
1506
|
+
reference = binding.request.reference
|
|
1507
|
+
admission = self._admissions.get(reference)
|
|
1508
|
+
if admission is None:
|
|
1509
|
+
raise ValueError("audit names an insight not admitted for diagnostics")
|
|
1510
|
+
if generation <= admission.admission_generation:
|
|
1511
|
+
raise ValueError("same-generation diagnostic outcomes are ineligible")
|
|
1512
|
+
entry = self.memory.entries_for((reference,))[0]
|
|
1513
|
+
if entry.lifecycle_state is not InsightLifecycleState.QUARANTINED:
|
|
1514
|
+
raise ValueError("audit insight is no longer quarantined")
|
|
1515
|
+
if binding.request.draft_content_sha256 != entry.draft.content_sha256:
|
|
1516
|
+
raise ValueError("global audit is bound to different card content")
|
|
1517
|
+
plan = self._audit_plans.get(reference)
|
|
1518
|
+
if plan is None:
|
|
1519
|
+
raise ValueError("audit insight has no pre-outcome semantic audit plan")
|
|
1520
|
+
self._validate_semantic_audit_plan(entry, plan)
|
|
1521
|
+
if not plan.admits(binding.request):
|
|
1522
|
+
raise ValueError(
|
|
1523
|
+
"global audit request substituted the registered semantic plan"
|
|
1524
|
+
)
|
|
1525
|
+
if (
|
|
1526
|
+
binding.request.origin_cutoff_event_index
|
|
1527
|
+
>= binding.request.audit_cutoff_event_index
|
|
1528
|
+
):
|
|
1529
|
+
raise ValueError("semantic replay lacks a post-origin audit interval")
|
|
1530
|
+
self._validate_semantic_verdict(binding)
|
|
1531
|
+
|
|
1532
|
+
source_trials = tuple(
|
|
1533
|
+
trial
|
|
1534
|
+
for trial in trial_universe[
|
|
1535
|
+
admission.memory_trial_count_cutoff : memory_credit_batch.memory_trial_count_after
|
|
1536
|
+
]
|
|
1537
|
+
if trial.decision.context_hash == binding.exact_context_sha256
|
|
1538
|
+
and reference in trial.decision.eligible
|
|
1539
|
+
)
|
|
1540
|
+
estimate = estimate_marginal_effect(
|
|
1541
|
+
source_trials,
|
|
1542
|
+
reference,
|
|
1543
|
+
context_hash=binding.exact_context_sha256,
|
|
1544
|
+
)
|
|
1545
|
+
source_trial_set_sha256 = _hash(
|
|
1546
|
+
b"agent-evolve:campaign-causal-trial-set:v1\x00",
|
|
1547
|
+
[_trial_record(value) for value in source_trials],
|
|
1548
|
+
)
|
|
1549
|
+
randomized_promotion_evidence = self.promotion_policy.evaluate(
|
|
1550
|
+
estimate=estimate,
|
|
1551
|
+
source_trials=source_trials,
|
|
1552
|
+
source_trial_set_sha256=source_trial_set_sha256,
|
|
1553
|
+
family_hypothesis_count=len(canonical),
|
|
1554
|
+
)
|
|
1555
|
+
causal = CampaignCausalUsefulnessReceipt(
|
|
1556
|
+
reference=reference,
|
|
1557
|
+
exact_context_sha256=binding.exact_context_sha256,
|
|
1558
|
+
admission_generation=admission.admission_generation,
|
|
1559
|
+
barrier_generation=generation,
|
|
1560
|
+
admission_trial_count_cutoff=(admission.memory_trial_count_cutoff),
|
|
1561
|
+
barrier_trial_count_cutoff=(
|
|
1562
|
+
memory_credit_batch.memory_trial_count_after
|
|
1563
|
+
),
|
|
1564
|
+
source_trial_set_sha256=source_trial_set_sha256,
|
|
1565
|
+
estimate=estimate,
|
|
1566
|
+
randomized_promotion_evidence=randomized_promotion_evidence,
|
|
1567
|
+
)
|
|
1568
|
+
verdict = binding.receipt.verdict
|
|
1569
|
+
if verdict is GlobalHypothesisVerdict.COUNTEREXAMPLE:
|
|
1570
|
+
decision = CampaignInsightLifecycleDecision.DEPRECATE
|
|
1571
|
+
reason = "global semantic audit found a counterexample"
|
|
1572
|
+
lifecycle_requests.append(
|
|
1573
|
+
InsightLifecycleChangeRequest(
|
|
1574
|
+
reference=reference,
|
|
1575
|
+
new_state=InsightLifecycleState.DEPRECATED,
|
|
1576
|
+
reason=reason,
|
|
1577
|
+
supporting_evidence=(binding.receipt.audit_receipt_sha256,),
|
|
1578
|
+
)
|
|
1579
|
+
)
|
|
1580
|
+
elif (
|
|
1581
|
+
verdict is GlobalHypothesisVerdict.SUPPORT
|
|
1582
|
+
and self.promotion_policy.admits(randomized_promotion_evidence)
|
|
1583
|
+
):
|
|
1584
|
+
decision = CampaignInsightLifecycleDecision.PROMOTE
|
|
1585
|
+
reason = (
|
|
1586
|
+
"semantic replay, practical effect, and exact block robustness "
|
|
1587
|
+
"all passed"
|
|
1588
|
+
)
|
|
1589
|
+
lifecycle_requests.append(
|
|
1590
|
+
InsightLifecycleChangeRequest(
|
|
1591
|
+
reference=reference,
|
|
1592
|
+
new_state=InsightLifecycleState.PROMOTED,
|
|
1593
|
+
reason=reason,
|
|
1594
|
+
supporting_evidence=tuple(
|
|
1595
|
+
sorted(
|
|
1596
|
+
(
|
|
1597
|
+
binding.receipt.audit_receipt_sha256,
|
|
1598
|
+
causal.receipt_sha256,
|
|
1599
|
+
)
|
|
1600
|
+
)
|
|
1601
|
+
),
|
|
1602
|
+
)
|
|
1603
|
+
)
|
|
1604
|
+
else:
|
|
1605
|
+
decision = CampaignInsightLifecycleDecision.RETAIN_QUARANTINE
|
|
1606
|
+
if verdict is GlobalHypothesisVerdict.SUPPORT:
|
|
1607
|
+
detail = ",".join(randomized_promotion_evidence.failure_reasons)
|
|
1608
|
+
reason = f"randomized promotion evidence insufficient: {detail}"
|
|
1609
|
+
else:
|
|
1610
|
+
reason = "semantic promotion evidence remains insufficient"
|
|
1611
|
+
prepared.append((binding, causal, decision, reason))
|
|
1612
|
+
transition_count_before = len(self.memory.transitions)
|
|
1613
|
+
canonical_lifecycle_requests = tuple(
|
|
1614
|
+
sorted(lifecycle_requests, key=lambda value: value.reference)
|
|
1615
|
+
)
|
|
1616
|
+
transition_by_reference = {
|
|
1617
|
+
value.reference: transition_count_before + index
|
|
1618
|
+
for index, value in enumerate(
|
|
1619
|
+
canonical_lifecycle_requests,
|
|
1620
|
+
start=1,
|
|
1621
|
+
)
|
|
1622
|
+
}
|
|
1623
|
+
decisions = tuple(
|
|
1624
|
+
CampaignInsightDecisionReceipt(
|
|
1625
|
+
reference=binding.request.reference,
|
|
1626
|
+
semantic_audit_request_sha256=binding.request.request_sha256,
|
|
1627
|
+
semantic_audit_receipt_sha256=(binding.receipt.audit_receipt_sha256),
|
|
1628
|
+
causal_usefulness=causal,
|
|
1629
|
+
decision=decision,
|
|
1630
|
+
reason=reason,
|
|
1631
|
+
lifecycle_transition_sequence=transition_by_reference.get(
|
|
1632
|
+
binding.request.reference
|
|
1633
|
+
),
|
|
1634
|
+
)
|
|
1635
|
+
for binding, causal, decision, reason in prepared
|
|
1636
|
+
)
|
|
1637
|
+
receipt = CampaignLearningBarrierReceipt(
|
|
1638
|
+
generation=generation,
|
|
1639
|
+
memory_credit_batch_receipt_sha256=(memory_credit_batch.receipt_sha256),
|
|
1640
|
+
promotion_policy_definition_sha256=(
|
|
1641
|
+
self.promotion_policy.definition_sha256
|
|
1642
|
+
),
|
|
1643
|
+
decisions=decisions,
|
|
1644
|
+
)
|
|
1645
|
+
return CampaignPreparedLearningBarrier(
|
|
1646
|
+
memory_credit_batch=memory_credit_batch,
|
|
1647
|
+
audits=canonical,
|
|
1648
|
+
batch_trials=batch_trials,
|
|
1649
|
+
lifecycle_requests=canonical_lifecycle_requests,
|
|
1650
|
+
barrier_receipt=receipt,
|
|
1651
|
+
memory_trial_count_at_prepare=memory_trial_count,
|
|
1652
|
+
transition_count_at_prepare=transition_count_before,
|
|
1653
|
+
)
|
|
1654
|
+
|
|
1655
|
+
def commit_generation_close(
|
|
1656
|
+
self,
|
|
1657
|
+
preparation: CampaignPreparedLearningBarrier,
|
|
1658
|
+
) -> CampaignLearningBarrierReceipt:
|
|
1659
|
+
"""Commit one pure preparation after its exact memory batch is published."""
|
|
1660
|
+
|
|
1661
|
+
self.__post_init__()
|
|
1662
|
+
if type(preparation) is not CampaignPreparedLearningBarrier:
|
|
1663
|
+
raise TypeError("preparation must be exact")
|
|
1664
|
+
CampaignPreparedLearningBarrier.__post_init__(preparation)
|
|
1665
|
+
generation = preparation.memory_credit_batch.generation
|
|
1666
|
+
if generation in self._closed_generations:
|
|
1667
|
+
raise ValueError("campaign learning generation was already closed")
|
|
1668
|
+
if (
|
|
1669
|
+
len(self.memory.trials)
|
|
1670
|
+
!= preparation.memory_credit_batch.memory_trial_count_after
|
|
1671
|
+
):
|
|
1672
|
+
raise ValueError("memory batch was not published before lifecycle commit")
|
|
1673
|
+
self._validate_memory_credit_batch(preparation.memory_credit_batch)
|
|
1674
|
+
committed_trials = self.memory.trials[
|
|
1675
|
+
preparation.memory_credit_batch.memory_trial_count_before : preparation.memory_credit_batch.memory_trial_count_after
|
|
1676
|
+
]
|
|
1677
|
+
if committed_trials != preparation.batch_trials:
|
|
1678
|
+
raise ValueError("published memory trials differ from learning preparation")
|
|
1679
|
+
if len(self.memory.transitions) != preparation.transition_count_at_prepare:
|
|
1680
|
+
raise ValueError("lifecycle state changed after learning preparation")
|
|
1681
|
+
if any(
|
|
1682
|
+
self.memory.entries_for((value.reference,))[0].lifecycle_state
|
|
1683
|
+
is not InsightLifecycleState.QUARANTINED
|
|
1684
|
+
for value in preparation.lifecycle_requests
|
|
1685
|
+
):
|
|
1686
|
+
raise ValueError("prepared lifecycle card changed before commit")
|
|
1687
|
+
if preparation.lifecycle_requests:
|
|
1688
|
+
self.memory.apply_lifecycle_batch(preparation.lifecycle_requests)
|
|
1689
|
+
self._closed_generations.add(generation)
|
|
1690
|
+
return preparation.barrier_receipt
|
|
1691
|
+
|
|
1692
|
+
def close_generation(
|
|
1693
|
+
self,
|
|
1694
|
+
*,
|
|
1695
|
+
memory_credit_batch: PortfolioMemoryCreditBatchReceipt,
|
|
1696
|
+
audits: tuple[CampaignInsightAuditBinding, ...],
|
|
1697
|
+
) -> CampaignLearningBarrierReceipt:
|
|
1698
|
+
"""Compatibility wrapper for an already-committed memory batch."""
|
|
1699
|
+
|
|
1700
|
+
preparation = self.prepare_generation_close(
|
|
1701
|
+
memory_credit_batch=memory_credit_batch,
|
|
1702
|
+
audits=audits,
|
|
1703
|
+
)
|
|
1704
|
+
return self.commit_generation_close(preparation)
|
|
1705
|
+
|
|
1706
|
+
@staticmethod
|
|
1707
|
+
def _validate_semantic_verdict(binding: CampaignInsightAuditBinding) -> None:
|
|
1708
|
+
request = binding.request
|
|
1709
|
+
receipt = binding.receipt
|
|
1710
|
+
if receipt.verdict is GlobalHypothesisVerdict.SUPPORT:
|
|
1711
|
+
post_origin_support = tuple(
|
|
1712
|
+
value
|
|
1713
|
+
for value in receipt.decisions
|
|
1714
|
+
if value.disposition is EvidenceDisposition.SUPPORT
|
|
1715
|
+
and value.post_origin_revision_evidence
|
|
1716
|
+
and value.event_index > request.origin_cutoff_event_index
|
|
1717
|
+
)
|
|
1718
|
+
post_origin_support_ids = tuple(
|
|
1719
|
+
value.source_evidence_id for value in post_origin_support
|
|
1720
|
+
)
|
|
1721
|
+
post_origin_clusters = {
|
|
1722
|
+
value.effective_cluster_sha256 for value in post_origin_support
|
|
1723
|
+
}
|
|
1724
|
+
post_origin_instances = {
|
|
1725
|
+
value.workload_instance_sha256 for value in post_origin_support
|
|
1726
|
+
}
|
|
1727
|
+
if (
|
|
1728
|
+
not receipt.support_ids
|
|
1729
|
+
or receipt.effective_support_cluster_count
|
|
1730
|
+
< request.minimum_support_clusters
|
|
1731
|
+
or receipt.support_instance_count < request.minimum_support_instances
|
|
1732
|
+
or receipt.counterexample_ids
|
|
1733
|
+
or receipt.necessity_contradicted
|
|
1734
|
+
):
|
|
1735
|
+
raise ValueError(
|
|
1736
|
+
"support verdict lacks its declared evidence threshold"
|
|
1737
|
+
)
|
|
1738
|
+
if (
|
|
1739
|
+
not post_origin_support_ids
|
|
1740
|
+
or len(post_origin_clusters) < request.minimum_support_clusters
|
|
1741
|
+
or len(post_origin_instances) < request.minimum_support_instances
|
|
1742
|
+
):
|
|
1743
|
+
raise ValueError(
|
|
1744
|
+
"support verdict lacks post-origin evidence thresholds"
|
|
1745
|
+
)
|
|
1746
|
+
elif receipt.verdict is GlobalHypothesisVerdict.COUNTEREXAMPLE:
|
|
1747
|
+
if not receipt.counterexample_ids and not receipt.necessity_contradicted:
|
|
1748
|
+
raise ValueError("counterexample verdict contains no counterexample")
|
|
1749
|
+
|
|
1750
|
+
def _validate_memory_credit_batch(
|
|
1751
|
+
self,
|
|
1752
|
+
batch: PortfolioMemoryCreditBatchReceipt,
|
|
1753
|
+
) -> None:
|
|
1754
|
+
trials = self.memory.trials[
|
|
1755
|
+
batch.memory_trial_count_before : batch.memory_trial_count_after
|
|
1756
|
+
]
|
|
1757
|
+
self._validate_memory_credit_batch_trials(batch, trials)
|
|
1758
|
+
|
|
1759
|
+
@staticmethod
|
|
1760
|
+
def _validate_memory_credit_batch_trials(
|
|
1761
|
+
batch: PortfolioMemoryCreditBatchReceipt,
|
|
1762
|
+
trials: tuple[InsightTrial, ...],
|
|
1763
|
+
) -> None:
|
|
1764
|
+
if type(batch) is not PortfolioMemoryCreditBatchReceipt:
|
|
1765
|
+
raise TypeError("batch must be exact")
|
|
1766
|
+
PortfolioMemoryCreditBatchReceipt.__post_init__(batch)
|
|
1767
|
+
if type(trials) is not tuple or any(
|
|
1768
|
+
type(value) is not InsightTrial for value in trials
|
|
1769
|
+
):
|
|
1770
|
+
raise TypeError("trials must contain exact InsightTrial values")
|
|
1771
|
+
for value in trials:
|
|
1772
|
+
InsightTrial.__post_init__(value)
|
|
1773
|
+
if len(trials) != len(batch.credits):
|
|
1774
|
+
raise ValueError("memory batch differs from its exact trial suffix")
|
|
1775
|
+
trial_by_credit = {value.credit_unit_id: value for value in trials}
|
|
1776
|
+
if len(trial_by_credit) != len(trials):
|
|
1777
|
+
raise ValueError("trial suffix repeats a credit unit")
|
|
1778
|
+
for credit in batch.credits:
|
|
1779
|
+
trial = trial_by_credit.get(credit.credit_unit_id)
|
|
1780
|
+
if trial is None:
|
|
1781
|
+
raise ValueError("memory batch credit is absent from the trial suffix")
|
|
1782
|
+
if (
|
|
1783
|
+
trial.generation != batch.generation
|
|
1784
|
+
or trial.candidate_ids != credit.candidate_ids
|
|
1785
|
+
or trial.reward_definition_hash != credit.aggregation_definition_sha256
|
|
1786
|
+
or insight_selection_decision_sha256(trial.decision)
|
|
1787
|
+
!= credit.selection_decision_sha256
|
|
1788
|
+
or trial.decision.context_hash
|
|
1789
|
+
!= credit.selection_decision_context_sha256
|
|
1790
|
+
or trial.reward != credit.reward
|
|
1791
|
+
or trial.treatment_binding_sha256 != credit.treatment_binding_sha256
|
|
1792
|
+
or trial.treatment_binding_sha256 is None
|
|
1793
|
+
):
|
|
1794
|
+
raise ValueError(
|
|
1795
|
+
"memory batch credit differs from its exact committed trial"
|
|
1796
|
+
)
|
|
1797
|
+
|
|
1798
|
+
|
|
1799
|
+
__all__ = [
|
|
1800
|
+
"CampaignCausalUsefulnessReceipt",
|
|
1801
|
+
"CampaignDiagnosticAdmissionReceipt",
|
|
1802
|
+
"CampaignInsightAuditBinding",
|
|
1803
|
+
"CampaignInsightDecisionReceipt",
|
|
1804
|
+
"CampaignInsightLifecycleDecision",
|
|
1805
|
+
"CampaignInsightPromotionPolicy",
|
|
1806
|
+
"CampaignInsightRegistrationReceipt",
|
|
1807
|
+
"CampaignLearningBarrierReceipt",
|
|
1808
|
+
"CampaignPreparedLearningBarrier",
|
|
1809
|
+
"CampaignRandomizedPromotionEvidence",
|
|
1810
|
+
"CampaignSemanticAuditPlan",
|
|
1811
|
+
"ClosedLoopCampaignLearning",
|
|
1812
|
+
]
|