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,1841 @@
|
|
|
1
|
+
"""Authenticated global falsification for reflected optimization hypotheses.
|
|
2
|
+
|
|
3
|
+
The gate in this module is deliberately provider- and workload-neutral. A
|
|
4
|
+
workload-owned matcher interprets typed trigger predicates and intervention
|
|
5
|
+
signatures. The engine then joins that classification to every authenticated
|
|
6
|
+
observation visible at a sealed cutoff, applies exact metric predictions, and
|
|
7
|
+
issues an immutable receipt.
|
|
8
|
+
|
|
9
|
+
Semantic claim auditing and memory-treatment causality are intentionally
|
|
10
|
+
different estimands. Candidate-level contrasts may support or falsify a
|
|
11
|
+
hypothesis, but every observation remains attached to its enclosing wave or
|
|
12
|
+
portfolio ITT unit. Audit receipts contain no reward and cannot assign causal
|
|
13
|
+
credit to an individual insight.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import hashlib
|
|
19
|
+
import math
|
|
20
|
+
import re
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
from enum import Enum
|
|
23
|
+
from typing import Protocol, Sequence, runtime_checkable
|
|
24
|
+
|
|
25
|
+
from agent_evolve.domain.insight import InsightRef
|
|
26
|
+
from agent_evolve.domain.ids import CandidateId, OperatorInvocationId
|
|
27
|
+
from agent_evolve.domain.patch import require_sha256
|
|
28
|
+
from agent_evolve.domain.typed_json import (
|
|
29
|
+
FrozenJsonObject,
|
|
30
|
+
canonical_typed_json_bytes,
|
|
31
|
+
freeze_json,
|
|
32
|
+
thaw_json,
|
|
33
|
+
)
|
|
34
|
+
from agent_evolve.ports.agentic_generator import MetricEffectDirection
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
_TOKEN = re.compile(r"^[a-z][a-z0-9_.-]{0,127}$")
|
|
38
|
+
_METRIC = re.compile(r"^[a-z][a-z0-9_.:-]{0,191}$")
|
|
39
|
+
_PATH = re.compile(r"^\$\.[^.\[\]\s]+(?:\.[^.\[\]\s]+|\[(?:0|[1-9][0-9]*)\])*$")
|
|
40
|
+
_TEXT_LIMIT = 4_096
|
|
41
|
+
_MAX_EVENT_INDEX = (1 << 63) - 1
|
|
42
|
+
|
|
43
|
+
_PREDICATE_DOMAIN = b"agent-evolve:typed-hypothesis-predicate:v1\x00"
|
|
44
|
+
_INTERVENTION_DOMAIN = b"agent-evolve:typed-intervention-signature:v1\x00"
|
|
45
|
+
_SCOPE_DOMAIN = b"agent-evolve:global-hypothesis-audit-scope:v1\x00"
|
|
46
|
+
_REQUEST_DOMAIN = b"agent-evolve:global-hypothesis-audit-request:v1\x00"
|
|
47
|
+
_BOUNDARY_DOMAIN = b"agent-evolve:hypothesis-evidence-itt-boundary:v1\x00"
|
|
48
|
+
_OBSERVATION_DOMAIN = b"agent-evolve:global-hypothesis-observation:v3\x00"
|
|
49
|
+
_REGISTRY_DOMAIN = b"agent-evolve:global-hypothesis-registry:v1\x00"
|
|
50
|
+
_MATCH_DOMAIN = b"agent-evolve:hypothesis-evidence-match:v1\x00"
|
|
51
|
+
_CLUSTER_DOMAIN = b"agent-evolve:hypothesis-effective-cluster:v1\x00"
|
|
52
|
+
_RECEIPT_DOMAIN = b"agent-evolve:global-hypothesis-audit-receipt:v1\x00"
|
|
53
|
+
_REVISION_DOMAIN = b"agent-evolve:append-only-hypothesis-revision:v1\x00"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _hash_record(domain: bytes, record: object) -> str:
|
|
57
|
+
return hashlib.sha256(
|
|
58
|
+
domain + canonical_typed_json_bytes(freeze_json(record))
|
|
59
|
+
).hexdigest()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
GLOBAL_FALSIFICATION_POLICY_ID = "global_hypothesis_falsification"
|
|
63
|
+
GLOBAL_FALSIFICATION_POLICY_VERSION = 2
|
|
64
|
+
GLOBAL_FALSIFICATION_POLICY_DEFINITION_SHA256 = _hash_record(
|
|
65
|
+
b"agent-evolve:global-hypothesis-falsification-policy:v2\x00",
|
|
66
|
+
{
|
|
67
|
+
"policy_id": GLOBAL_FALSIFICATION_POLICY_ID,
|
|
68
|
+
"policy_version": GLOBAL_FALSIFICATION_POLICY_VERSION,
|
|
69
|
+
"join": "exhaustive_authenticated_registry_at_inclusive_event_cutoff",
|
|
70
|
+
"repeat_rule": "earliest_event_then_source_id_per_configuration_evaluator",
|
|
71
|
+
"support_rule": "all_required_known_directions_and_magnitudes_match",
|
|
72
|
+
"counterexample_rule": "any_required_direction_or_magnitude_mismatch",
|
|
73
|
+
"necessity_rule": "matching_off_trigger_response_contradicts_necessity",
|
|
74
|
+
"cluster_rule": (
|
|
75
|
+
"instance_evaluator_campaign_lineage_factorial_block_itt_unit"
|
|
76
|
+
),
|
|
77
|
+
"support_temporality_rule": (
|
|
78
|
+
"declared_support_thresholds_must_be_met_by_post_origin_evidence"
|
|
79
|
+
),
|
|
80
|
+
"mechanism_temporality_rule": (
|
|
81
|
+
"mechanistic_support_requires_post_origin_identifying_evidence"
|
|
82
|
+
),
|
|
83
|
+
"causal_credit_rule": "semantic_audit_only_no_retroactive_card_credit",
|
|
84
|
+
},
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _require_token(value: str, *, name: str) -> None:
|
|
89
|
+
if type(value) is not str or _TOKEN.fullmatch(value) is None:
|
|
90
|
+
raise ValueError(f"{name} must use the closed lowercase token grammar")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _require_metric(value: str, *, name: str = "metric_id") -> None:
|
|
94
|
+
if type(value) is not str or _METRIC.fullmatch(value) is None:
|
|
95
|
+
raise ValueError(f"{name} must use the closed metric identifier grammar")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _require_event_index(value: int, *, name: str) -> None:
|
|
99
|
+
if type(value) is not int or not 0 <= value <= _MAX_EVENT_INDEX:
|
|
100
|
+
raise ValueError(f"{name} must be an exact non-negative int63")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _require_canonical_text(value: str, *, name: str) -> None:
|
|
104
|
+
if (
|
|
105
|
+
type(value) is not str
|
|
106
|
+
or not value
|
|
107
|
+
or value != value.strip()
|
|
108
|
+
or len(value) > _TEXT_LIMIT
|
|
109
|
+
):
|
|
110
|
+
raise ValueError(f"{name} must be bounded canonical non-empty text")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _require_sha_tuple(values: tuple[str, ...], *, name: str, empty: bool) -> None:
|
|
114
|
+
if type(values) is not tuple or any(type(value) is not str for value in values):
|
|
115
|
+
raise TypeError(f"{name} must be an exact tuple of SHA-256 strings")
|
|
116
|
+
for value in values:
|
|
117
|
+
require_sha256(value, name)
|
|
118
|
+
if not empty and not values:
|
|
119
|
+
raise ValueError(f"{name} cannot be empty")
|
|
120
|
+
if values != tuple(sorted(set(values))):
|
|
121
|
+
raise ValueError(f"{name} must be unique and canonically sorted")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _require_frozen_object(value: FrozenJsonObject, *, name: str) -> None:
|
|
125
|
+
if type(value) is not FrozenJsonObject:
|
|
126
|
+
raise TypeError(f"{name} must be an exact FrozenJsonObject")
|
|
127
|
+
if freeze_json(value) is not value:
|
|
128
|
+
raise TypeError(f"{name} must already be frozen typed JSON")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@dataclass(frozen=True, slots=True)
|
|
132
|
+
class TypedEvidencePredicate:
|
|
133
|
+
"""Workload-owned, replayable predicate with engine-authenticated identity."""
|
|
134
|
+
|
|
135
|
+
schema_id: str
|
|
136
|
+
schema_version: int
|
|
137
|
+
schema_definition_sha256: str
|
|
138
|
+
payload: FrozenJsonObject
|
|
139
|
+
predicate_sha256: str = field(init=False)
|
|
140
|
+
|
|
141
|
+
def __post_init__(self) -> None:
|
|
142
|
+
_require_token(self.schema_id, name="schema_id")
|
|
143
|
+
if type(self.schema_version) is not int or self.schema_version <= 0:
|
|
144
|
+
raise ValueError("schema_version must be a positive exact integer")
|
|
145
|
+
require_sha256(self.schema_definition_sha256, "schema_definition_sha256")
|
|
146
|
+
_require_frozen_object(self.payload, name="payload")
|
|
147
|
+
object.__setattr__(
|
|
148
|
+
self,
|
|
149
|
+
"predicate_sha256",
|
|
150
|
+
_hash_record(_PREDICATE_DOMAIN, self._unsigned_record()),
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
154
|
+
return {
|
|
155
|
+
"schema_version": 1,
|
|
156
|
+
"predicate_schema_id": self.schema_id,
|
|
157
|
+
"predicate_schema_version": self.schema_version,
|
|
158
|
+
"predicate_schema_definition_sha256": self.schema_definition_sha256,
|
|
159
|
+
"payload": thaw_json(self.payload),
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
def to_record(self) -> dict[str, object]:
|
|
163
|
+
return {**self._unsigned_record(), "predicate_sha256": self.predicate_sha256}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@dataclass(frozen=True, slots=True)
|
|
167
|
+
class TypedInterventionSignature:
|
|
168
|
+
"""Exact semantic edit requested by a sealed hypothesis."""
|
|
169
|
+
|
|
170
|
+
affected_paths: tuple[str, ...]
|
|
171
|
+
old_value_predicate: TypedEvidencePredicate
|
|
172
|
+
new_action: TypedEvidencePredicate
|
|
173
|
+
admissible_operator_families: tuple[str, ...]
|
|
174
|
+
signature_sha256: str = field(init=False)
|
|
175
|
+
|
|
176
|
+
def __post_init__(self) -> None:
|
|
177
|
+
if type(self.affected_paths) is not tuple or not self.affected_paths:
|
|
178
|
+
raise ValueError("affected_paths must be a non-empty exact tuple")
|
|
179
|
+
if any(
|
|
180
|
+
type(path) is not str or _PATH.fullmatch(path) is None
|
|
181
|
+
for path in self.affected_paths
|
|
182
|
+
):
|
|
183
|
+
raise ValueError("affected_paths must contain canonical JSON paths")
|
|
184
|
+
if self.affected_paths != tuple(sorted(set(self.affected_paths))):
|
|
185
|
+
raise ValueError("affected_paths must be unique and canonically sorted")
|
|
186
|
+
if type(self.old_value_predicate) is not TypedEvidencePredicate:
|
|
187
|
+
raise TypeError("old_value_predicate must be exact TypedEvidencePredicate")
|
|
188
|
+
if type(self.new_action) is not TypedEvidencePredicate:
|
|
189
|
+
raise TypeError("new_action must be exact TypedEvidencePredicate")
|
|
190
|
+
if (
|
|
191
|
+
type(self.admissible_operator_families) is not tuple
|
|
192
|
+
or not self.admissible_operator_families
|
|
193
|
+
):
|
|
194
|
+
raise ValueError("admissible_operator_families must be non-empty")
|
|
195
|
+
for family in self.admissible_operator_families:
|
|
196
|
+
_require_token(family, name="admissible_operator_families")
|
|
197
|
+
if self.admissible_operator_families != tuple(
|
|
198
|
+
sorted(set(self.admissible_operator_families))
|
|
199
|
+
):
|
|
200
|
+
raise ValueError(
|
|
201
|
+
"admissible_operator_families must be unique and canonically sorted"
|
|
202
|
+
)
|
|
203
|
+
object.__setattr__(
|
|
204
|
+
self,
|
|
205
|
+
"signature_sha256",
|
|
206
|
+
_hash_record(_INTERVENTION_DOMAIN, self._unsigned_record()),
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
210
|
+
return {
|
|
211
|
+
"schema_version": 1,
|
|
212
|
+
"affected_paths": list(self.affected_paths),
|
|
213
|
+
"old_value_predicate_sha256": self.old_value_predicate.predicate_sha256,
|
|
214
|
+
"new_action_sha256": self.new_action.predicate_sha256,
|
|
215
|
+
"admissible_operator_families": list(self.admissible_operator_families),
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
def to_record(self) -> dict[str, object]:
|
|
219
|
+
return {**self._unsigned_record(), "signature_sha256": self.signature_sha256}
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@dataclass(frozen=True, slots=True)
|
|
223
|
+
class HypothesisMetricPrediction:
|
|
224
|
+
"""One required direction and optional child-minus-parent magnitude interval."""
|
|
225
|
+
|
|
226
|
+
metric_id: str
|
|
227
|
+
direction: MetricEffectDirection
|
|
228
|
+
minimum_delta: float | None = None
|
|
229
|
+
maximum_delta: float | None = None
|
|
230
|
+
|
|
231
|
+
def __post_init__(self) -> None:
|
|
232
|
+
_require_metric(self.metric_id)
|
|
233
|
+
if (
|
|
234
|
+
type(self.direction) is not MetricEffectDirection
|
|
235
|
+
or self.direction is MetricEffectDirection.UNKNOWN
|
|
236
|
+
):
|
|
237
|
+
raise ValueError("direction must be a known MetricEffectDirection")
|
|
238
|
+
if (self.minimum_delta is None) != (self.maximum_delta is None):
|
|
239
|
+
raise ValueError("magnitude claims require both interval endpoints")
|
|
240
|
+
if self.minimum_delta is not None:
|
|
241
|
+
if (
|
|
242
|
+
type(self.minimum_delta) is not float
|
|
243
|
+
or type(self.maximum_delta) is not float
|
|
244
|
+
or not math.isfinite(self.minimum_delta)
|
|
245
|
+
or not math.isfinite(self.maximum_delta)
|
|
246
|
+
):
|
|
247
|
+
raise TypeError("magnitude endpoints must be finite canonical floats")
|
|
248
|
+
if self.minimum_delta > self.maximum_delta:
|
|
249
|
+
raise ValueError("minimum_delta cannot exceed maximum_delta")
|
|
250
|
+
|
|
251
|
+
@property
|
|
252
|
+
def has_magnitude_claim(self) -> bool:
|
|
253
|
+
return self.minimum_delta is not None
|
|
254
|
+
|
|
255
|
+
def to_record(self) -> dict[str, object]:
|
|
256
|
+
return {
|
|
257
|
+
"metric_id": self.metric_id,
|
|
258
|
+
"direction": self.direction.value,
|
|
259
|
+
"minimum_delta_hex": (
|
|
260
|
+
None if self.minimum_delta is None else self.minimum_delta.hex()
|
|
261
|
+
),
|
|
262
|
+
"maximum_delta_hex": (
|
|
263
|
+
None if self.maximum_delta is None else self.maximum_delta.hex()
|
|
264
|
+
),
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
@dataclass(frozen=True, slots=True)
|
|
269
|
+
class HypothesisClaimStrength:
|
|
270
|
+
"""Typed logical flags; prose is never parsed by the gate."""
|
|
271
|
+
|
|
272
|
+
sufficiency: bool = True
|
|
273
|
+
necessity: bool = False
|
|
274
|
+
invariance: bool = False
|
|
275
|
+
mechanistic_or_causal: bool = False
|
|
276
|
+
|
|
277
|
+
def __post_init__(self) -> None:
|
|
278
|
+
if any(
|
|
279
|
+
type(getattr(self, name)) is not bool
|
|
280
|
+
for name in (
|
|
281
|
+
"sufficiency",
|
|
282
|
+
"necessity",
|
|
283
|
+
"invariance",
|
|
284
|
+
"mechanistic_or_causal",
|
|
285
|
+
)
|
|
286
|
+
):
|
|
287
|
+
raise TypeError("logical strength flags must be exact booleans")
|
|
288
|
+
if not any(
|
|
289
|
+
(
|
|
290
|
+
self.sufficiency,
|
|
291
|
+
self.necessity,
|
|
292
|
+
self.invariance,
|
|
293
|
+
self.mechanistic_or_causal,
|
|
294
|
+
)
|
|
295
|
+
):
|
|
296
|
+
raise ValueError("a hypothesis must declare at least one logical claim")
|
|
297
|
+
|
|
298
|
+
def to_record(self) -> dict[str, bool]:
|
|
299
|
+
return {
|
|
300
|
+
"sufficiency": self.sufficiency,
|
|
301
|
+
"necessity": self.necessity,
|
|
302
|
+
"invariance": self.invariance,
|
|
303
|
+
"mechanistic_or_causal": self.mechanistic_or_causal,
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
@dataclass(frozen=True, slots=True)
|
|
308
|
+
class HypothesisAuditScope:
|
|
309
|
+
"""Closed workload/evaluator/campaign scope for one global join."""
|
|
310
|
+
|
|
311
|
+
workload_instance_sha256s: tuple[str, ...]
|
|
312
|
+
evaluator_contract_sha256: str
|
|
313
|
+
metric_adjudicator_definition_sha256: str
|
|
314
|
+
campaign_sha256s: tuple[str, ...] = ()
|
|
315
|
+
cross_instance_claim: bool = False
|
|
316
|
+
scope_sha256: str = field(init=False)
|
|
317
|
+
|
|
318
|
+
def __post_init__(self) -> None:
|
|
319
|
+
_require_sha_tuple(
|
|
320
|
+
self.workload_instance_sha256s,
|
|
321
|
+
name="workload_instance_sha256s",
|
|
322
|
+
empty=False,
|
|
323
|
+
)
|
|
324
|
+
require_sha256(self.evaluator_contract_sha256, "evaluator_contract_sha256")
|
|
325
|
+
require_sha256(
|
|
326
|
+
self.metric_adjudicator_definition_sha256,
|
|
327
|
+
"metric_adjudicator_definition_sha256",
|
|
328
|
+
)
|
|
329
|
+
_require_sha_tuple(self.campaign_sha256s, name="campaign_sha256s", empty=True)
|
|
330
|
+
if type(self.cross_instance_claim) is not bool:
|
|
331
|
+
raise TypeError("cross_instance_claim must be an exact boolean")
|
|
332
|
+
if self.cross_instance_claim and len(self.workload_instance_sha256s) < 2:
|
|
333
|
+
raise ValueError("cross-instance claims require at least two instances")
|
|
334
|
+
object.__setattr__(
|
|
335
|
+
self, "scope_sha256", _hash_record(_SCOPE_DOMAIN, self._unsigned_record())
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
339
|
+
return {
|
|
340
|
+
"schema_version": 1,
|
|
341
|
+
"workload_instance_sha256s": list(self.workload_instance_sha256s),
|
|
342
|
+
"evaluator_contract_sha256": self.evaluator_contract_sha256,
|
|
343
|
+
"metric_adjudicator_definition_sha256": (
|
|
344
|
+
self.metric_adjudicator_definition_sha256
|
|
345
|
+
),
|
|
346
|
+
"campaign_sha256s": list(self.campaign_sha256s),
|
|
347
|
+
"cross_instance_claim": self.cross_instance_claim,
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
def to_record(self) -> dict[str, object]:
|
|
351
|
+
return {**self._unsigned_record(), "scope_sha256": self.scope_sha256}
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
@dataclass(frozen=True, slots=True)
|
|
355
|
+
class GlobalHypothesisAuditRequest:
|
|
356
|
+
"""Sealed hypothesis, evidence universe, and predeclared support threshold."""
|
|
357
|
+
|
|
358
|
+
reference: InsightRef
|
|
359
|
+
draft_content_sha256: str
|
|
360
|
+
trigger: TypedEvidencePredicate
|
|
361
|
+
intervention: TypedInterventionSignature
|
|
362
|
+
predictions: tuple[HypothesisMetricPrediction, ...]
|
|
363
|
+
claim_strength: HypothesisClaimStrength
|
|
364
|
+
scope: HypothesisAuditScope
|
|
365
|
+
matcher_definition_sha256: str
|
|
366
|
+
origin_cutoff_event_index: int
|
|
367
|
+
audit_cutoff_event_index: int
|
|
368
|
+
registry_snapshot_sha256: str
|
|
369
|
+
minimum_support_clusters: int = 2
|
|
370
|
+
minimum_support_instances: int = 1
|
|
371
|
+
audit_policy_definition_sha256: str = GLOBAL_FALSIFICATION_POLICY_DEFINITION_SHA256
|
|
372
|
+
request_sha256: str = field(init=False)
|
|
373
|
+
|
|
374
|
+
def __post_init__(self) -> None:
|
|
375
|
+
if type(self.reference) is not InsightRef:
|
|
376
|
+
raise TypeError("reference must be an exact InsightRef")
|
|
377
|
+
InsightRef.__post_init__(self.reference)
|
|
378
|
+
require_sha256(self.draft_content_sha256, "draft_content_sha256")
|
|
379
|
+
if type(self.trigger) is not TypedEvidencePredicate:
|
|
380
|
+
raise TypeError("trigger must be exact TypedEvidencePredicate")
|
|
381
|
+
if type(self.intervention) is not TypedInterventionSignature:
|
|
382
|
+
raise TypeError("intervention must be exact TypedInterventionSignature")
|
|
383
|
+
if (
|
|
384
|
+
type(self.predictions) is not tuple
|
|
385
|
+
or not self.predictions
|
|
386
|
+
or any(
|
|
387
|
+
type(value) is not HypothesisMetricPrediction
|
|
388
|
+
for value in self.predictions
|
|
389
|
+
)
|
|
390
|
+
):
|
|
391
|
+
raise ValueError("predictions must contain typed metric predictions")
|
|
392
|
+
metric_ids = tuple(value.metric_id for value in self.predictions)
|
|
393
|
+
if metric_ids != tuple(sorted(set(metric_ids))):
|
|
394
|
+
raise ValueError("predictions must use unique canonical metric order")
|
|
395
|
+
if type(self.claim_strength) is not HypothesisClaimStrength:
|
|
396
|
+
raise TypeError("claim_strength must be exact HypothesisClaimStrength")
|
|
397
|
+
if type(self.scope) is not HypothesisAuditScope:
|
|
398
|
+
raise TypeError("scope must be exact HypothesisAuditScope")
|
|
399
|
+
require_sha256(self.matcher_definition_sha256, "matcher_definition_sha256")
|
|
400
|
+
_require_event_index(
|
|
401
|
+
self.origin_cutoff_event_index, name="origin_cutoff_event_index"
|
|
402
|
+
)
|
|
403
|
+
_require_event_index(
|
|
404
|
+
self.audit_cutoff_event_index, name="audit_cutoff_event_index"
|
|
405
|
+
)
|
|
406
|
+
if self.origin_cutoff_event_index > self.audit_cutoff_event_index:
|
|
407
|
+
raise ValueError("origin cutoff cannot follow the audit cutoff")
|
|
408
|
+
require_sha256(self.registry_snapshot_sha256, "registry_snapshot_sha256")
|
|
409
|
+
for name in ("minimum_support_clusters", "minimum_support_instances"):
|
|
410
|
+
value = getattr(self, name)
|
|
411
|
+
if type(value) is not int or value <= 0:
|
|
412
|
+
raise ValueError(f"{name} must be a positive exact integer")
|
|
413
|
+
require_sha256(
|
|
414
|
+
self.audit_policy_definition_sha256,
|
|
415
|
+
"audit_policy_definition_sha256",
|
|
416
|
+
)
|
|
417
|
+
if self.scope.cross_instance_claim and self.minimum_support_instances < 2:
|
|
418
|
+
raise ValueError(
|
|
419
|
+
"cross-instance claims require support from at least two instances"
|
|
420
|
+
)
|
|
421
|
+
object.__setattr__(
|
|
422
|
+
self,
|
|
423
|
+
"request_sha256",
|
|
424
|
+
_hash_record(_REQUEST_DOMAIN, self._unsigned_record()),
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
428
|
+
return {
|
|
429
|
+
"schema_version": 1,
|
|
430
|
+
"reference": {
|
|
431
|
+
"insight_id": self.reference.insight_id.value,
|
|
432
|
+
"version": self.reference.version,
|
|
433
|
+
},
|
|
434
|
+
"draft_content_sha256": self.draft_content_sha256,
|
|
435
|
+
"trigger_predicate_sha256": self.trigger.predicate_sha256,
|
|
436
|
+
"intervention_signature_sha256": self.intervention.signature_sha256,
|
|
437
|
+
"predictions": [value.to_record() for value in self.predictions],
|
|
438
|
+
"claim_strength": self.claim_strength.to_record(),
|
|
439
|
+
"scope_sha256": self.scope.scope_sha256,
|
|
440
|
+
"matcher_definition_sha256": self.matcher_definition_sha256,
|
|
441
|
+
"origin_cutoff_event_index": self.origin_cutoff_event_index,
|
|
442
|
+
"audit_cutoff_event_index": self.audit_cutoff_event_index,
|
|
443
|
+
"registry_snapshot_sha256": self.registry_snapshot_sha256,
|
|
444
|
+
"minimum_support_clusters": self.minimum_support_clusters,
|
|
445
|
+
"minimum_support_instances": self.minimum_support_instances,
|
|
446
|
+
"audit_policy_definition_sha256": (self.audit_policy_definition_sha256),
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
def to_record(self) -> dict[str, object]:
|
|
450
|
+
return {**self._unsigned_record(), "request_sha256": self.request_sha256}
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
class EvidenceProvenance(str, Enum):
|
|
454
|
+
DIRECT_MUTATION = "direct_mutation"
|
|
455
|
+
RECOMBINATION_EXACT_ABLATION = "recombination_exact_ablation"
|
|
456
|
+
FULL_SLATE_DIAGNOSTIC = "full_slate_diagnostic"
|
|
457
|
+
RANDOMIZED_ADMINISTRATION = "randomized_administration"
|
|
458
|
+
OBSERVATIONAL_ASSOCIATION = "observational_association"
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
class InterventionIdentifiability(str, Enum):
|
|
462
|
+
EXACT_SINGLE = "exact_single_intervention"
|
|
463
|
+
JOINT_WITHOUT_ABLATION = "joint_intervention_without_exact_ablation"
|
|
464
|
+
AMBIGUOUS = "ambiguous_intervention_identity"
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
class CausalEstimandUnit(str, Enum):
|
|
468
|
+
WAVE = "wave"
|
|
469
|
+
PORTFOLIO = "portfolio"
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
@dataclass(frozen=True, slots=True)
|
|
473
|
+
class EvidenceCausalBoundary:
|
|
474
|
+
"""Enclosing prospective ITT unit; never an individual reflected card."""
|
|
475
|
+
|
|
476
|
+
wave_sha256: str
|
|
477
|
+
estimand_unit: CausalEstimandUnit
|
|
478
|
+
portfolio_sha256: str | None = None
|
|
479
|
+
prospective_assignment_sha256: str | None = None
|
|
480
|
+
boundary_sha256: str = field(init=False)
|
|
481
|
+
|
|
482
|
+
def __post_init__(self) -> None:
|
|
483
|
+
require_sha256(self.wave_sha256, "wave_sha256")
|
|
484
|
+
if type(self.estimand_unit) is not CausalEstimandUnit:
|
|
485
|
+
raise TypeError("estimand_unit must be exact CausalEstimandUnit")
|
|
486
|
+
if self.portfolio_sha256 is not None:
|
|
487
|
+
require_sha256(self.portfolio_sha256, "portfolio_sha256")
|
|
488
|
+
if (
|
|
489
|
+
self.estimand_unit is CausalEstimandUnit.PORTFOLIO
|
|
490
|
+
and self.portfolio_sha256 is None
|
|
491
|
+
):
|
|
492
|
+
raise ValueError("a portfolio estimand requires portfolio_sha256")
|
|
493
|
+
if self.prospective_assignment_sha256 is not None:
|
|
494
|
+
require_sha256(
|
|
495
|
+
self.prospective_assignment_sha256,
|
|
496
|
+
"prospective_assignment_sha256",
|
|
497
|
+
)
|
|
498
|
+
object.__setattr__(
|
|
499
|
+
self,
|
|
500
|
+
"boundary_sha256",
|
|
501
|
+
_hash_record(_BOUNDARY_DOMAIN, self._unsigned_record()),
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
@property
|
|
505
|
+
def estimand_unit_sha256(self) -> str:
|
|
506
|
+
if self.estimand_unit is CausalEstimandUnit.PORTFOLIO:
|
|
507
|
+
assert self.portfolio_sha256 is not None
|
|
508
|
+
return self.portfolio_sha256
|
|
509
|
+
return self.wave_sha256
|
|
510
|
+
|
|
511
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
512
|
+
return {
|
|
513
|
+
"schema_version": 1,
|
|
514
|
+
"wave_sha256": self.wave_sha256,
|
|
515
|
+
"estimand_unit": self.estimand_unit.value,
|
|
516
|
+
"portfolio_sha256": self.portfolio_sha256,
|
|
517
|
+
"prospective_assignment_sha256": self.prospective_assignment_sha256,
|
|
518
|
+
"credit_boundary": "semantic_audit_only_no_per_card_credit",
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
def to_record(self) -> dict[str, object]:
|
|
522
|
+
return {**self._unsigned_record(), "boundary_sha256": self.boundary_sha256}
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
@dataclass(frozen=True, slots=True)
|
|
526
|
+
class ObservedMetricEffect:
|
|
527
|
+
metric_id: str
|
|
528
|
+
direction: MetricEffectDirection
|
|
529
|
+
delta: float
|
|
530
|
+
adjudicator_definition_sha256: str
|
|
531
|
+
|
|
532
|
+
def __post_init__(self) -> None:
|
|
533
|
+
_require_metric(self.metric_id)
|
|
534
|
+
if (
|
|
535
|
+
type(self.direction) is not MetricEffectDirection
|
|
536
|
+
or self.direction is MetricEffectDirection.UNKNOWN
|
|
537
|
+
):
|
|
538
|
+
raise ValueError("direction must be a known MetricEffectDirection")
|
|
539
|
+
if type(self.delta) is not float or not math.isfinite(self.delta):
|
|
540
|
+
raise TypeError("delta must be a finite canonical float")
|
|
541
|
+
require_sha256(
|
|
542
|
+
self.adjudicator_definition_sha256,
|
|
543
|
+
"adjudicator_definition_sha256",
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
def to_record(self) -> dict[str, object]:
|
|
547
|
+
return {
|
|
548
|
+
"metric_id": self.metric_id,
|
|
549
|
+
"direction": self.direction.value,
|
|
550
|
+
"delta_hex": self.delta.hex(),
|
|
551
|
+
"adjudicator_definition_sha256": self.adjudicator_definition_sha256,
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
@dataclass(frozen=True, slots=True)
|
|
556
|
+
class AuthenticatedHypothesisObservation:
|
|
557
|
+
"""One immutable parent/child fact available to the global evidence join."""
|
|
558
|
+
|
|
559
|
+
source_evidence_id: str
|
|
560
|
+
event_index: int
|
|
561
|
+
workload_instance_sha256: str
|
|
562
|
+
evaluator_contract_sha256: str
|
|
563
|
+
campaign_sha256: str
|
|
564
|
+
parent_candidate_id: CandidateId
|
|
565
|
+
child_candidate_id: CandidateId
|
|
566
|
+
operator_invocation_id: OperatorInvocationId
|
|
567
|
+
finite_contract_identity_sha256: str
|
|
568
|
+
provenance: EvidenceProvenance
|
|
569
|
+
causal_boundary: EvidenceCausalBoundary
|
|
570
|
+
parent_configuration: FrozenJsonObject
|
|
571
|
+
child_configuration: FrozenJsonObject
|
|
572
|
+
parent_configuration_sha256: str
|
|
573
|
+
child_configuration_sha256: str
|
|
574
|
+
parent_outcome_sha256: str
|
|
575
|
+
child_outcome_sha256: str
|
|
576
|
+
operator_family: str
|
|
577
|
+
affected_paths: tuple[str, ...]
|
|
578
|
+
observed_action: FrozenJsonObject
|
|
579
|
+
action_semantics_compiler_id: str
|
|
580
|
+
action_semantics_compiler_version: int
|
|
581
|
+
action_semantics_definition_sha256: str
|
|
582
|
+
intervention_identifiability: InterventionIdentifiability
|
|
583
|
+
metrics: tuple[ObservedMetricEffect, ...]
|
|
584
|
+
lineage_cluster_sha256: str
|
|
585
|
+
factorial_block_sha256: str
|
|
586
|
+
mechanism_identifying_design: bool = False
|
|
587
|
+
observation_sha256: str = field(init=False)
|
|
588
|
+
|
|
589
|
+
def __post_init__(self) -> None:
|
|
590
|
+
require_sha256(self.source_evidence_id, "source_evidence_id")
|
|
591
|
+
_require_event_index(self.event_index, name="event_index")
|
|
592
|
+
for name in (
|
|
593
|
+
"workload_instance_sha256",
|
|
594
|
+
"evaluator_contract_sha256",
|
|
595
|
+
"campaign_sha256",
|
|
596
|
+
"finite_contract_identity_sha256",
|
|
597
|
+
"parent_configuration_sha256",
|
|
598
|
+
"child_configuration_sha256",
|
|
599
|
+
"parent_outcome_sha256",
|
|
600
|
+
"child_outcome_sha256",
|
|
601
|
+
"lineage_cluster_sha256",
|
|
602
|
+
"factorial_block_sha256",
|
|
603
|
+
):
|
|
604
|
+
require_sha256(getattr(self, name), name)
|
|
605
|
+
if type(self.parent_candidate_id) is not CandidateId:
|
|
606
|
+
raise TypeError("parent_candidate_id must be an exact CandidateId")
|
|
607
|
+
if type(self.child_candidate_id) is not CandidateId:
|
|
608
|
+
raise TypeError("child_candidate_id must be an exact CandidateId")
|
|
609
|
+
if type(self.operator_invocation_id) is not OperatorInvocationId:
|
|
610
|
+
raise TypeError(
|
|
611
|
+
"operator_invocation_id must be an exact OperatorInvocationId"
|
|
612
|
+
)
|
|
613
|
+
CandidateId.__post_init__(self.parent_candidate_id)
|
|
614
|
+
CandidateId.__post_init__(self.child_candidate_id)
|
|
615
|
+
OperatorInvocationId.__post_init__(self.operator_invocation_id)
|
|
616
|
+
if self.parent_candidate_id == self.child_candidate_id:
|
|
617
|
+
raise ValueError(
|
|
618
|
+
"authenticated child occurrence cannot reuse its parent ID"
|
|
619
|
+
)
|
|
620
|
+
if type(self.provenance) is not EvidenceProvenance:
|
|
621
|
+
raise TypeError("provenance must be exact EvidenceProvenance")
|
|
622
|
+
if type(self.causal_boundary) is not EvidenceCausalBoundary:
|
|
623
|
+
raise TypeError("causal_boundary must be exact EvidenceCausalBoundary")
|
|
624
|
+
_require_frozen_object(self.parent_configuration, name="parent_configuration")
|
|
625
|
+
_require_frozen_object(self.child_configuration, name="child_configuration")
|
|
626
|
+
if (
|
|
627
|
+
_hash_record(
|
|
628
|
+
b"agent-evolve:configuration:v1\x00",
|
|
629
|
+
thaw_json(self.parent_configuration),
|
|
630
|
+
)
|
|
631
|
+
!= self.parent_configuration_sha256
|
|
632
|
+
):
|
|
633
|
+
raise ValueError("parent_configuration_sha256 does not match configuration")
|
|
634
|
+
if (
|
|
635
|
+
_hash_record(
|
|
636
|
+
b"agent-evolve:configuration:v1\x00",
|
|
637
|
+
thaw_json(self.child_configuration),
|
|
638
|
+
)
|
|
639
|
+
!= self.child_configuration_sha256
|
|
640
|
+
):
|
|
641
|
+
raise ValueError("child_configuration_sha256 does not match configuration")
|
|
642
|
+
_require_token(self.operator_family, name="operator_family")
|
|
643
|
+
if type(self.affected_paths) is not tuple or not self.affected_paths:
|
|
644
|
+
raise ValueError("affected_paths must be a non-empty exact tuple")
|
|
645
|
+
if any(
|
|
646
|
+
type(path) is not str or _PATH.fullmatch(path) is None
|
|
647
|
+
for path in self.affected_paths
|
|
648
|
+
):
|
|
649
|
+
raise ValueError("affected_paths must contain canonical JSON paths")
|
|
650
|
+
if self.affected_paths != tuple(sorted(set(self.affected_paths))):
|
|
651
|
+
raise ValueError("affected_paths must be unique and canonically sorted")
|
|
652
|
+
_require_frozen_object(self.observed_action, name="observed_action")
|
|
653
|
+
_require_token(
|
|
654
|
+
self.action_semantics_compiler_id,
|
|
655
|
+
name="action_semantics_compiler_id",
|
|
656
|
+
)
|
|
657
|
+
if (
|
|
658
|
+
type(self.action_semantics_compiler_version) is not int
|
|
659
|
+
or self.action_semantics_compiler_version <= 0
|
|
660
|
+
):
|
|
661
|
+
raise ValueError("action_semantics_compiler_version must be positive")
|
|
662
|
+
require_sha256(
|
|
663
|
+
self.action_semantics_definition_sha256,
|
|
664
|
+
"action_semantics_definition_sha256",
|
|
665
|
+
)
|
|
666
|
+
if type(self.intervention_identifiability) is not InterventionIdentifiability:
|
|
667
|
+
raise TypeError(
|
|
668
|
+
"intervention_identifiability must be exact InterventionIdentifiability"
|
|
669
|
+
)
|
|
670
|
+
if (
|
|
671
|
+
type(self.metrics) is not tuple
|
|
672
|
+
or not self.metrics
|
|
673
|
+
or any(type(value) is not ObservedMetricEffect for value in self.metrics)
|
|
674
|
+
):
|
|
675
|
+
raise ValueError("metrics must contain exact ObservedMetricEffect values")
|
|
676
|
+
metric_ids = tuple(value.metric_id for value in self.metrics)
|
|
677
|
+
if metric_ids != tuple(sorted(set(metric_ids))):
|
|
678
|
+
raise ValueError("metrics must use unique canonical metric order")
|
|
679
|
+
if type(self.mechanism_identifying_design) is not bool:
|
|
680
|
+
raise TypeError("mechanism_identifying_design must be an exact boolean")
|
|
681
|
+
if self.mechanism_identifying_design and self.provenance is not (
|
|
682
|
+
EvidenceProvenance.RANDOMIZED_ADMINISTRATION
|
|
683
|
+
):
|
|
684
|
+
raise ValueError(
|
|
685
|
+
"mechanism-identifying evidence must be randomized administration"
|
|
686
|
+
)
|
|
687
|
+
if (
|
|
688
|
+
self.provenance is EvidenceProvenance.RANDOMIZED_ADMINISTRATION
|
|
689
|
+
and self.causal_boundary.prospective_assignment_sha256 is None
|
|
690
|
+
):
|
|
691
|
+
raise ValueError(
|
|
692
|
+
"randomized administration requires a prospective ITT assignment"
|
|
693
|
+
)
|
|
694
|
+
object.__setattr__(
|
|
695
|
+
self,
|
|
696
|
+
"observation_sha256",
|
|
697
|
+
_hash_record(_OBSERVATION_DOMAIN, self._unsigned_record()),
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
@classmethod
|
|
701
|
+
def configuration_sha256(cls, value: FrozenJsonObject) -> str:
|
|
702
|
+
_require_frozen_object(value, name="configuration")
|
|
703
|
+
return _hash_record(b"agent-evolve:configuration:v1\x00", thaw_json(value))
|
|
704
|
+
|
|
705
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
706
|
+
return {
|
|
707
|
+
"schema_version": 3,
|
|
708
|
+
"source_evidence_id": self.source_evidence_id,
|
|
709
|
+
"event_index": self.event_index,
|
|
710
|
+
"workload_instance_sha256": self.workload_instance_sha256,
|
|
711
|
+
"evaluator_contract_sha256": self.evaluator_contract_sha256,
|
|
712
|
+
"campaign_sha256": self.campaign_sha256,
|
|
713
|
+
"parent_candidate_id": self.parent_candidate_id.value,
|
|
714
|
+
"child_candidate_id": self.child_candidate_id.value,
|
|
715
|
+
"operator_invocation_id": self.operator_invocation_id.value,
|
|
716
|
+
"finite_contract_identity_sha256": (self.finite_contract_identity_sha256),
|
|
717
|
+
"provenance": self.provenance.value,
|
|
718
|
+
"causal_boundary": self.causal_boundary.to_record(),
|
|
719
|
+
"parent_configuration": thaw_json(self.parent_configuration),
|
|
720
|
+
"child_configuration": thaw_json(self.child_configuration),
|
|
721
|
+
"parent_configuration_sha256": self.parent_configuration_sha256,
|
|
722
|
+
"child_configuration_sha256": self.child_configuration_sha256,
|
|
723
|
+
"parent_outcome_sha256": self.parent_outcome_sha256,
|
|
724
|
+
"child_outcome_sha256": self.child_outcome_sha256,
|
|
725
|
+
"operator_family": self.operator_family,
|
|
726
|
+
"affected_paths": list(self.affected_paths),
|
|
727
|
+
"observed_action": thaw_json(self.observed_action),
|
|
728
|
+
"action_semantics_compiler": {
|
|
729
|
+
"compiler_id": self.action_semantics_compiler_id,
|
|
730
|
+
"compiler_version": self.action_semantics_compiler_version,
|
|
731
|
+
"definition_sha256": self.action_semantics_definition_sha256,
|
|
732
|
+
},
|
|
733
|
+
"intervention_identifiability": self.intervention_identifiability.value,
|
|
734
|
+
"metrics": [value.to_record() for value in self.metrics],
|
|
735
|
+
"lineage_cluster_sha256": self.lineage_cluster_sha256,
|
|
736
|
+
"factorial_block_sha256": self.factorial_block_sha256,
|
|
737
|
+
"mechanism_identifying_design": self.mechanism_identifying_design,
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
def to_record(self) -> dict[str, object]:
|
|
741
|
+
return {
|
|
742
|
+
**self._unsigned_record(),
|
|
743
|
+
"observation_sha256": self.observation_sha256,
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
@dataclass(frozen=True, slots=True)
|
|
748
|
+
class GlobalEvidenceRegistrySnapshot:
|
|
749
|
+
"""Complete immutable observation registry visible through one event index."""
|
|
750
|
+
|
|
751
|
+
captured_through_event_index: int
|
|
752
|
+
observations: tuple[AuthenticatedHypothesisObservation, ...]
|
|
753
|
+
snapshot_sha256: str = field(init=False)
|
|
754
|
+
|
|
755
|
+
def __post_init__(self) -> None:
|
|
756
|
+
_require_event_index(
|
|
757
|
+
self.captured_through_event_index,
|
|
758
|
+
name="captured_through_event_index",
|
|
759
|
+
)
|
|
760
|
+
if type(self.observations) is not tuple or any(
|
|
761
|
+
type(value) is not AuthenticatedHypothesisObservation
|
|
762
|
+
for value in self.observations
|
|
763
|
+
):
|
|
764
|
+
raise TypeError(
|
|
765
|
+
"observations must be an exact tuple of authenticated observations"
|
|
766
|
+
)
|
|
767
|
+
keys = tuple(value.source_evidence_id for value in self.observations)
|
|
768
|
+
if keys != tuple(sorted(set(keys))):
|
|
769
|
+
raise ValueError(
|
|
770
|
+
"observations must have unique source IDs in canonical order"
|
|
771
|
+
)
|
|
772
|
+
if any(
|
|
773
|
+
value.event_index > self.captured_through_event_index
|
|
774
|
+
for value in self.observations
|
|
775
|
+
):
|
|
776
|
+
raise ValueError("registry contains evidence after its capture cutoff")
|
|
777
|
+
object.__setattr__(
|
|
778
|
+
self,
|
|
779
|
+
"snapshot_sha256",
|
|
780
|
+
_hash_record(_REGISTRY_DOMAIN, self._unsigned_record()),
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
@classmethod
|
|
784
|
+
def seal(
|
|
785
|
+
cls,
|
|
786
|
+
*,
|
|
787
|
+
captured_through_event_index: int,
|
|
788
|
+
observations: Sequence[AuthenticatedHypothesisObservation],
|
|
789
|
+
) -> "GlobalEvidenceRegistrySnapshot":
|
|
790
|
+
if isinstance(observations, (str, bytes)) or not isinstance(
|
|
791
|
+
observations, Sequence
|
|
792
|
+
):
|
|
793
|
+
raise TypeError("observations must be a sequence")
|
|
794
|
+
if any(
|
|
795
|
+
type(value) is not AuthenticatedHypothesisObservation
|
|
796
|
+
for value in observations
|
|
797
|
+
):
|
|
798
|
+
raise TypeError(
|
|
799
|
+
"observations must contain exact authenticated observations"
|
|
800
|
+
)
|
|
801
|
+
canonical = tuple(
|
|
802
|
+
sorted(observations, key=lambda value: value.source_evidence_id)
|
|
803
|
+
)
|
|
804
|
+
return cls(captured_through_event_index, canonical)
|
|
805
|
+
|
|
806
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
807
|
+
return {
|
|
808
|
+
"schema_version": 1,
|
|
809
|
+
"captured_through_event_index": self.captured_through_event_index,
|
|
810
|
+
"observation_sha256s": [
|
|
811
|
+
value.observation_sha256 for value in self.observations
|
|
812
|
+
],
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
def to_record(self) -> dict[str, object]:
|
|
816
|
+
return {**self._unsigned_record(), "snapshot_sha256": self.snapshot_sha256}
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
class TriggerMatch(str, Enum):
|
|
820
|
+
EXACT = "exact_trigger"
|
|
821
|
+
OFF_TRIGGER = "off_trigger"
|
|
822
|
+
AMBIGUOUS = "ambiguous_trigger"
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
class InterventionMatch(str, Enum):
|
|
826
|
+
EXACT = "exact_intervention"
|
|
827
|
+
NEAR = "near_intervention"
|
|
828
|
+
DIFFERENT = "different_intervention"
|
|
829
|
+
NON_IDENTIFIABLE = "non_identifiable_intervention"
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
@dataclass(frozen=True, slots=True)
|
|
833
|
+
class HypothesisEvidenceMatchReceipt:
|
|
834
|
+
"""Workload matcher result bound to one request and one observation."""
|
|
835
|
+
|
|
836
|
+
request_sha256: str
|
|
837
|
+
observation_sha256: str
|
|
838
|
+
trigger_match: TriggerMatch
|
|
839
|
+
intervention_match: InterventionMatch
|
|
840
|
+
matcher_policy_id: str
|
|
841
|
+
matcher_policy_version: int
|
|
842
|
+
matcher_definition_sha256: str
|
|
843
|
+
receipt_sha256: str = field(init=False)
|
|
844
|
+
|
|
845
|
+
def __post_init__(self) -> None:
|
|
846
|
+
require_sha256(self.request_sha256, "request_sha256")
|
|
847
|
+
require_sha256(self.observation_sha256, "observation_sha256")
|
|
848
|
+
if type(self.trigger_match) is not TriggerMatch:
|
|
849
|
+
raise TypeError("trigger_match must be exact TriggerMatch")
|
|
850
|
+
if type(self.intervention_match) is not InterventionMatch:
|
|
851
|
+
raise TypeError("intervention_match must be exact InterventionMatch")
|
|
852
|
+
_require_token(self.matcher_policy_id, name="matcher_policy_id")
|
|
853
|
+
if (
|
|
854
|
+
type(self.matcher_policy_version) is not int
|
|
855
|
+
or self.matcher_policy_version <= 0
|
|
856
|
+
):
|
|
857
|
+
raise ValueError("matcher_policy_version must be a positive integer")
|
|
858
|
+
require_sha256(self.matcher_definition_sha256, "matcher_definition_sha256")
|
|
859
|
+
object.__setattr__(
|
|
860
|
+
self,
|
|
861
|
+
"receipt_sha256",
|
|
862
|
+
_hash_record(_MATCH_DOMAIN, self._unsigned_record()),
|
|
863
|
+
)
|
|
864
|
+
|
|
865
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
866
|
+
return {
|
|
867
|
+
"schema_version": 1,
|
|
868
|
+
"request_sha256": self.request_sha256,
|
|
869
|
+
"observation_sha256": self.observation_sha256,
|
|
870
|
+
"trigger_match": self.trigger_match.value,
|
|
871
|
+
"intervention_match": self.intervention_match.value,
|
|
872
|
+
"matcher": {
|
|
873
|
+
"policy_id": self.matcher_policy_id,
|
|
874
|
+
"policy_version": self.matcher_policy_version,
|
|
875
|
+
"definition_sha256": self.matcher_definition_sha256,
|
|
876
|
+
},
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
def to_record(self) -> dict[str, object]:
|
|
880
|
+
return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
@runtime_checkable
|
|
884
|
+
class GlobalHypothesisEvidenceMatcher(Protocol):
|
|
885
|
+
"""Inverted workload seam for typed trigger/intervention matching."""
|
|
886
|
+
|
|
887
|
+
policy_id: str
|
|
888
|
+
policy_version: int
|
|
889
|
+
definition_sha256: str
|
|
890
|
+
|
|
891
|
+
def classify(
|
|
892
|
+
self,
|
|
893
|
+
request: GlobalHypothesisAuditRequest,
|
|
894
|
+
observation: AuthenticatedHypothesisObservation,
|
|
895
|
+
) -> HypothesisEvidenceMatchReceipt: ...
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
class EvidenceDisposition(str, Enum):
|
|
899
|
+
SUPPORT = "support"
|
|
900
|
+
COUNTEREXAMPLE = "counterexample"
|
|
901
|
+
OFF_TRIGGER_CONTROL = "off_trigger_control"
|
|
902
|
+
NON_IDENTIFIABLE = "non_identifiable"
|
|
903
|
+
NEAR_INTERVENTION = "near_intervention"
|
|
904
|
+
IRRELEVANT = "irrelevant"
|
|
905
|
+
DUPLICATE = "deduplicated_exact_repeat"
|
|
906
|
+
OUT_OF_SCOPE = "out_of_scope"
|
|
907
|
+
AFTER_CUTOFF = "after_cutoff"
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
@dataclass(frozen=True, slots=True)
|
|
911
|
+
class MetricClaimAssessment:
|
|
912
|
+
metric_id: str
|
|
913
|
+
expected_direction: MetricEffectDirection
|
|
914
|
+
actual_direction: MetricEffectDirection
|
|
915
|
+
actual_delta: float
|
|
916
|
+
direction_matches: bool
|
|
917
|
+
magnitude_matches: bool | None
|
|
918
|
+
|
|
919
|
+
@property
|
|
920
|
+
def matches(self) -> bool:
|
|
921
|
+
return self.direction_matches and self.magnitude_matches is not False
|
|
922
|
+
|
|
923
|
+
def to_record(self) -> dict[str, object]:
|
|
924
|
+
return {
|
|
925
|
+
"metric_id": self.metric_id,
|
|
926
|
+
"expected_direction": self.expected_direction.value,
|
|
927
|
+
"actual_direction": self.actual_direction.value,
|
|
928
|
+
"actual_delta_hex": self.actual_delta.hex(),
|
|
929
|
+
"direction_matches": self.direction_matches,
|
|
930
|
+
"magnitude_matches": self.magnitude_matches,
|
|
931
|
+
"matches": self.matches,
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
|
|
935
|
+
@dataclass(frozen=True, slots=True)
|
|
936
|
+
class GlobalEvidenceDecision:
|
|
937
|
+
source_evidence_id: str
|
|
938
|
+
observation_sha256: str
|
|
939
|
+
event_index: int
|
|
940
|
+
workload_instance_sha256: str
|
|
941
|
+
itt_estimand_unit_sha256: str
|
|
942
|
+
disposition: EvidenceDisposition
|
|
943
|
+
match_receipt_sha256: str | None
|
|
944
|
+
metric_assessments: tuple[MetricClaimAssessment, ...]
|
|
945
|
+
effective_cluster_sha256: str | None
|
|
946
|
+
post_origin_revision_evidence: bool
|
|
947
|
+
duplicate_of_source_evidence_id: str | None = None
|
|
948
|
+
predictions_match: bool | None = None
|
|
949
|
+
|
|
950
|
+
def __post_init__(self) -> None:
|
|
951
|
+
require_sha256(self.source_evidence_id, "source_evidence_id")
|
|
952
|
+
require_sha256(self.observation_sha256, "observation_sha256")
|
|
953
|
+
require_sha256(self.workload_instance_sha256, "workload_instance_sha256")
|
|
954
|
+
require_sha256(self.itt_estimand_unit_sha256, "itt_estimand_unit_sha256")
|
|
955
|
+
_require_event_index(self.event_index, name="event_index")
|
|
956
|
+
if type(self.disposition) is not EvidenceDisposition:
|
|
957
|
+
raise TypeError("disposition must be exact EvidenceDisposition")
|
|
958
|
+
for name in (
|
|
959
|
+
"match_receipt_sha256",
|
|
960
|
+
"effective_cluster_sha256",
|
|
961
|
+
"duplicate_of_source_evidence_id",
|
|
962
|
+
):
|
|
963
|
+
value = getattr(self, name)
|
|
964
|
+
if value is not None:
|
|
965
|
+
require_sha256(value, name)
|
|
966
|
+
if type(self.metric_assessments) is not tuple:
|
|
967
|
+
raise TypeError("metric_assessments must be an exact tuple")
|
|
968
|
+
if type(self.post_origin_revision_evidence) is not bool:
|
|
969
|
+
raise TypeError("post_origin_revision_evidence must be an exact boolean")
|
|
970
|
+
if (
|
|
971
|
+
self.predictions_match is not None
|
|
972
|
+
and type(self.predictions_match) is not bool
|
|
973
|
+
):
|
|
974
|
+
raise TypeError("predictions_match must be an exact boolean or None")
|
|
975
|
+
if self.disposition is EvidenceDisposition.DUPLICATE:
|
|
976
|
+
if self.duplicate_of_source_evidence_id is None:
|
|
977
|
+
raise ValueError("duplicate evidence must identify its representative")
|
|
978
|
+
elif self.duplicate_of_source_evidence_id is not None:
|
|
979
|
+
raise ValueError("only duplicate evidence may identify a representative")
|
|
980
|
+
|
|
981
|
+
def to_record(self) -> dict[str, object]:
|
|
982
|
+
return {
|
|
983
|
+
"source_evidence_id": self.source_evidence_id,
|
|
984
|
+
"observation_sha256": self.observation_sha256,
|
|
985
|
+
"event_index": self.event_index,
|
|
986
|
+
"workload_instance_sha256": self.workload_instance_sha256,
|
|
987
|
+
"itt_estimand_unit_sha256": self.itt_estimand_unit_sha256,
|
|
988
|
+
"disposition": self.disposition.value,
|
|
989
|
+
"match_receipt_sha256": self.match_receipt_sha256,
|
|
990
|
+
"metric_assessments": [
|
|
991
|
+
value.to_record() for value in self.metric_assessments
|
|
992
|
+
],
|
|
993
|
+
"effective_cluster_sha256": self.effective_cluster_sha256,
|
|
994
|
+
"post_origin_revision_evidence": self.post_origin_revision_evidence,
|
|
995
|
+
"duplicate_of_source_evidence_id": self.duplicate_of_source_evidence_id,
|
|
996
|
+
"predictions_match": self.predictions_match,
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
class GlobalHypothesisVerdict(str, Enum):
|
|
1001
|
+
SUPPORT = "globally_audited_support"
|
|
1002
|
+
COUNTEREXAMPLE = "contradicted"
|
|
1003
|
+
INSUFFICIENT = "insufficient_evidence"
|
|
1004
|
+
NON_IDENTIFIABLE = "non_identifiable"
|
|
1005
|
+
|
|
1006
|
+
|
|
1007
|
+
@dataclass(frozen=True, slots=True)
|
|
1008
|
+
class GlobalHypothesisAuditReceipt:
|
|
1009
|
+
"""Closed semantic decision with no causal-memory reward channel."""
|
|
1010
|
+
|
|
1011
|
+
request_sha256: str
|
|
1012
|
+
registry_snapshot_sha256: str
|
|
1013
|
+
audit_policy_id: str
|
|
1014
|
+
audit_policy_version: int
|
|
1015
|
+
audit_policy_definition_sha256: str
|
|
1016
|
+
verdict: GlobalHypothesisVerdict
|
|
1017
|
+
decisions: tuple[GlobalEvidenceDecision, ...]
|
|
1018
|
+
support_ids: tuple[str, ...]
|
|
1019
|
+
counterexample_ids: tuple[str, ...]
|
|
1020
|
+
off_trigger_control_ids: tuple[str, ...]
|
|
1021
|
+
non_identifiable_ids: tuple[str, ...]
|
|
1022
|
+
raw_support_count: int
|
|
1023
|
+
effective_support_cluster_count: int
|
|
1024
|
+
support_instance_count: int
|
|
1025
|
+
untested_workload_instance_sha256s: tuple[str, ...]
|
|
1026
|
+
coverage_gaps: tuple[str, ...]
|
|
1027
|
+
necessity_contradicted: bool
|
|
1028
|
+
mechanism_identified: bool
|
|
1029
|
+
lifecycle_decision: str
|
|
1030
|
+
audit_receipt_sha256: str = field(init=False)
|
|
1031
|
+
|
|
1032
|
+
def __post_init__(self) -> None:
|
|
1033
|
+
require_sha256(self.request_sha256, "request_sha256")
|
|
1034
|
+
require_sha256(self.registry_snapshot_sha256, "registry_snapshot_sha256")
|
|
1035
|
+
_require_token(self.audit_policy_id, name="audit_policy_id")
|
|
1036
|
+
if type(self.audit_policy_version) is not int or self.audit_policy_version <= 0:
|
|
1037
|
+
raise ValueError("audit_policy_version must be a positive exact integer")
|
|
1038
|
+
require_sha256(
|
|
1039
|
+
self.audit_policy_definition_sha256,
|
|
1040
|
+
"audit_policy_definition_sha256",
|
|
1041
|
+
)
|
|
1042
|
+
if type(self.verdict) is not GlobalHypothesisVerdict:
|
|
1043
|
+
raise TypeError("verdict must be exact GlobalHypothesisVerdict")
|
|
1044
|
+
if type(self.decisions) is not tuple or any(
|
|
1045
|
+
type(value) is not GlobalEvidenceDecision for value in self.decisions
|
|
1046
|
+
):
|
|
1047
|
+
raise TypeError(
|
|
1048
|
+
"decisions must contain exact GlobalEvidenceDecision values"
|
|
1049
|
+
)
|
|
1050
|
+
decision_ids = tuple(value.source_evidence_id for value in self.decisions)
|
|
1051
|
+
if decision_ids != tuple(sorted(set(decision_ids))):
|
|
1052
|
+
raise ValueError("decisions must have unique canonical source order")
|
|
1053
|
+
for name in (
|
|
1054
|
+
"support_ids",
|
|
1055
|
+
"counterexample_ids",
|
|
1056
|
+
"off_trigger_control_ids",
|
|
1057
|
+
"non_identifiable_ids",
|
|
1058
|
+
):
|
|
1059
|
+
_require_sha_tuple(getattr(self, name), name=name, empty=True)
|
|
1060
|
+
expected_ids = {
|
|
1061
|
+
disposition: tuple(
|
|
1062
|
+
value.source_evidence_id
|
|
1063
|
+
for value in self.decisions
|
|
1064
|
+
if value.disposition is disposition
|
|
1065
|
+
)
|
|
1066
|
+
for disposition in (
|
|
1067
|
+
EvidenceDisposition.SUPPORT,
|
|
1068
|
+
EvidenceDisposition.COUNTEREXAMPLE,
|
|
1069
|
+
EvidenceDisposition.OFF_TRIGGER_CONTROL,
|
|
1070
|
+
EvidenceDisposition.NON_IDENTIFIABLE,
|
|
1071
|
+
)
|
|
1072
|
+
}
|
|
1073
|
+
observed_ids = {
|
|
1074
|
+
EvidenceDisposition.SUPPORT: self.support_ids,
|
|
1075
|
+
EvidenceDisposition.COUNTEREXAMPLE: self.counterexample_ids,
|
|
1076
|
+
EvidenceDisposition.OFF_TRIGGER_CONTROL: self.off_trigger_control_ids,
|
|
1077
|
+
EvidenceDisposition.NON_IDENTIFIABLE: self.non_identifiable_ids,
|
|
1078
|
+
}
|
|
1079
|
+
if observed_ids != expected_ids:
|
|
1080
|
+
raise ValueError("receipt evidence ID summaries differ from its decisions")
|
|
1081
|
+
for name in (
|
|
1082
|
+
"raw_support_count",
|
|
1083
|
+
"effective_support_cluster_count",
|
|
1084
|
+
"support_instance_count",
|
|
1085
|
+
):
|
|
1086
|
+
value = getattr(self, name)
|
|
1087
|
+
if type(value) is not int or value < 0:
|
|
1088
|
+
raise ValueError(f"{name} must be a non-negative exact integer")
|
|
1089
|
+
_require_sha_tuple(
|
|
1090
|
+
self.untested_workload_instance_sha256s,
|
|
1091
|
+
name="untested_workload_instance_sha256s",
|
|
1092
|
+
empty=True,
|
|
1093
|
+
)
|
|
1094
|
+
support_decisions = tuple(
|
|
1095
|
+
value
|
|
1096
|
+
for value in self.decisions
|
|
1097
|
+
if value.disposition is EvidenceDisposition.SUPPORT
|
|
1098
|
+
)
|
|
1099
|
+
if self.raw_support_count != len(support_decisions):
|
|
1100
|
+
raise ValueError("raw_support_count differs from support decisions")
|
|
1101
|
+
if any(value.effective_cluster_sha256 is None for value in support_decisions):
|
|
1102
|
+
raise ValueError("support decisions require an effective cluster")
|
|
1103
|
+
if self.effective_support_cluster_count != len(
|
|
1104
|
+
{value.effective_cluster_sha256 for value in support_decisions}
|
|
1105
|
+
):
|
|
1106
|
+
raise ValueError(
|
|
1107
|
+
"effective_support_cluster_count differs from support decisions"
|
|
1108
|
+
)
|
|
1109
|
+
if self.support_instance_count != len(
|
|
1110
|
+
{value.workload_instance_sha256 for value in support_decisions}
|
|
1111
|
+
):
|
|
1112
|
+
raise ValueError("support_instance_count differs from support decisions")
|
|
1113
|
+
if type(self.coverage_gaps) is not tuple:
|
|
1114
|
+
raise TypeError("coverage_gaps must be an exact tuple")
|
|
1115
|
+
for value in self.coverage_gaps:
|
|
1116
|
+
_require_token(value, name="coverage_gaps")
|
|
1117
|
+
if self.coverage_gaps != tuple(sorted(set(self.coverage_gaps))):
|
|
1118
|
+
raise ValueError("coverage_gaps must be unique and canonically sorted")
|
|
1119
|
+
if type(self.necessity_contradicted) is not bool:
|
|
1120
|
+
raise TypeError("necessity_contradicted must be an exact boolean")
|
|
1121
|
+
if type(self.mechanism_identified) is not bool:
|
|
1122
|
+
raise TypeError("mechanism_identified must be an exact boolean")
|
|
1123
|
+
_require_canonical_text(self.lifecycle_decision, name="lifecycle_decision")
|
|
1124
|
+
object.__setattr__(
|
|
1125
|
+
self,
|
|
1126
|
+
"audit_receipt_sha256",
|
|
1127
|
+
_hash_record(_RECEIPT_DOMAIN, self._unsigned_record()),
|
|
1128
|
+
)
|
|
1129
|
+
|
|
1130
|
+
@property
|
|
1131
|
+
def causal_credit_updates(self) -> tuple[()]:
|
|
1132
|
+
"""Truth audits can never retroactively credit an individual card."""
|
|
1133
|
+
|
|
1134
|
+
return ()
|
|
1135
|
+
|
|
1136
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
1137
|
+
return {
|
|
1138
|
+
"schema_version": 1,
|
|
1139
|
+
"request_sha256": self.request_sha256,
|
|
1140
|
+
"registry_snapshot_sha256": self.registry_snapshot_sha256,
|
|
1141
|
+
"audit_policy": {
|
|
1142
|
+
"policy_id": self.audit_policy_id,
|
|
1143
|
+
"policy_version": self.audit_policy_version,
|
|
1144
|
+
"definition_sha256": self.audit_policy_definition_sha256,
|
|
1145
|
+
},
|
|
1146
|
+
"verdict": self.verdict.value,
|
|
1147
|
+
"decisions": [value.to_record() for value in self.decisions],
|
|
1148
|
+
"support_ids": list(self.support_ids),
|
|
1149
|
+
"counterexample_ids": list(self.counterexample_ids),
|
|
1150
|
+
"off_trigger_control_ids": list(self.off_trigger_control_ids),
|
|
1151
|
+
"non_identifiable_ids": list(self.non_identifiable_ids),
|
|
1152
|
+
"raw_support_count": self.raw_support_count,
|
|
1153
|
+
"effective_support_cluster_count": self.effective_support_cluster_count,
|
|
1154
|
+
"support_instance_count": self.support_instance_count,
|
|
1155
|
+
"untested_workload_instance_sha256s": list(
|
|
1156
|
+
self.untested_workload_instance_sha256s
|
|
1157
|
+
),
|
|
1158
|
+
"coverage_gaps": list(self.coverage_gaps),
|
|
1159
|
+
"necessity_contradicted": self.necessity_contradicted,
|
|
1160
|
+
"mechanism_identified": self.mechanism_identified,
|
|
1161
|
+
"lifecycle_decision": self.lifecycle_decision,
|
|
1162
|
+
"causal_credit_policy": ("semantic_truth_only_no_retroactive_card_credit"),
|
|
1163
|
+
"causal_credit_updates": [],
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
def to_record(self) -> dict[str, object]:
|
|
1167
|
+
return {
|
|
1168
|
+
**self._unsigned_record(),
|
|
1169
|
+
"audit_receipt_sha256": self.audit_receipt_sha256,
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
|
|
1173
|
+
class AmbiguousInterventionIdentityError(ValueError):
|
|
1174
|
+
"""A matcher attempted to identify an ambiguous or joint intervention."""
|
|
1175
|
+
|
|
1176
|
+
|
|
1177
|
+
@dataclass(frozen=True, slots=True)
|
|
1178
|
+
class GlobalHypothesisFalsificationGate:
|
|
1179
|
+
"""Exhaustively adjudicate one sealed hypothesis against a sealed registry."""
|
|
1180
|
+
|
|
1181
|
+
policy_id: str = GLOBAL_FALSIFICATION_POLICY_ID
|
|
1182
|
+
policy_version: int = GLOBAL_FALSIFICATION_POLICY_VERSION
|
|
1183
|
+
definition_sha256: str = GLOBAL_FALSIFICATION_POLICY_DEFINITION_SHA256
|
|
1184
|
+
|
|
1185
|
+
def __post_init__(self) -> None:
|
|
1186
|
+
_require_token(self.policy_id, name="policy_id")
|
|
1187
|
+
if (
|
|
1188
|
+
type(self.policy_version) is not int
|
|
1189
|
+
or self.policy_version != GLOBAL_FALSIFICATION_POLICY_VERSION
|
|
1190
|
+
):
|
|
1191
|
+
raise ValueError("unsupported global falsification policy version")
|
|
1192
|
+
if self.definition_sha256 != GLOBAL_FALSIFICATION_POLICY_DEFINITION_SHA256:
|
|
1193
|
+
raise ValueError("unsupported global falsification policy definition")
|
|
1194
|
+
|
|
1195
|
+
def audit(
|
|
1196
|
+
self,
|
|
1197
|
+
*,
|
|
1198
|
+
request: GlobalHypothesisAuditRequest,
|
|
1199
|
+
registry: GlobalEvidenceRegistrySnapshot,
|
|
1200
|
+
matcher: GlobalHypothesisEvidenceMatcher,
|
|
1201
|
+
) -> GlobalHypothesisAuditReceipt:
|
|
1202
|
+
if type(request) is not GlobalHypothesisAuditRequest:
|
|
1203
|
+
raise TypeError("request must be exact GlobalHypothesisAuditRequest")
|
|
1204
|
+
if type(registry) is not GlobalEvidenceRegistrySnapshot:
|
|
1205
|
+
raise TypeError("registry must be exact GlobalEvidenceRegistrySnapshot")
|
|
1206
|
+
if registry.snapshot_sha256 != request.registry_snapshot_sha256:
|
|
1207
|
+
raise ValueError("request is bound to a different registry snapshot")
|
|
1208
|
+
if registry.captured_through_event_index < request.audit_cutoff_event_index:
|
|
1209
|
+
raise ValueError("registry does not cover the requested audit cutoff")
|
|
1210
|
+
if request.audit_policy_definition_sha256 != self.definition_sha256:
|
|
1211
|
+
raise ValueError("audit policy differs from the sealed request")
|
|
1212
|
+
if not isinstance(matcher, GlobalHypothesisEvidenceMatcher):
|
|
1213
|
+
raise TypeError("matcher must implement GlobalHypothesisEvidenceMatcher")
|
|
1214
|
+
_require_token(matcher.policy_id, name="matcher.policy_id")
|
|
1215
|
+
if type(matcher.policy_version) is not int or matcher.policy_version <= 0:
|
|
1216
|
+
raise ValueError("matcher.policy_version must be a positive integer")
|
|
1217
|
+
require_sha256(matcher.definition_sha256, "matcher.definition_sha256")
|
|
1218
|
+
if matcher.definition_sha256 != request.matcher_definition_sha256:
|
|
1219
|
+
raise ValueError("matcher identity differs from the sealed request")
|
|
1220
|
+
|
|
1221
|
+
representative_by_repeat: dict[tuple[str, str, str, str, str], str] = {}
|
|
1222
|
+
for observation in sorted(
|
|
1223
|
+
registry.observations,
|
|
1224
|
+
key=lambda value: (value.event_index, value.source_evidence_id),
|
|
1225
|
+
):
|
|
1226
|
+
if (
|
|
1227
|
+
observation.event_index <= request.audit_cutoff_event_index
|
|
1228
|
+
and self._in_scope(request.scope, observation)
|
|
1229
|
+
):
|
|
1230
|
+
repeat_key = (
|
|
1231
|
+
observation.workload_instance_sha256,
|
|
1232
|
+
observation.campaign_sha256,
|
|
1233
|
+
observation.parent_configuration_sha256,
|
|
1234
|
+
observation.child_configuration_sha256,
|
|
1235
|
+
observation.evaluator_contract_sha256,
|
|
1236
|
+
)
|
|
1237
|
+
representative_by_repeat.setdefault(
|
|
1238
|
+
repeat_key, observation.source_evidence_id
|
|
1239
|
+
)
|
|
1240
|
+
decisions: list[GlobalEvidenceDecision] = []
|
|
1241
|
+
support_instances: set[str] = set()
|
|
1242
|
+
support_clusters: set[str] = set()
|
|
1243
|
+
post_origin_support_instances: set[str] = set()
|
|
1244
|
+
post_origin_support_clusters: set[str] = set()
|
|
1245
|
+
mechanism_identified = False
|
|
1246
|
+
post_origin_mechanism_identified = False
|
|
1247
|
+
|
|
1248
|
+
for observation in registry.observations:
|
|
1249
|
+
post_origin = observation.event_index > request.origin_cutoff_event_index
|
|
1250
|
+
if observation.event_index > request.audit_cutoff_event_index:
|
|
1251
|
+
decisions.append(
|
|
1252
|
+
self._simple_decision(
|
|
1253
|
+
observation,
|
|
1254
|
+
EvidenceDisposition.AFTER_CUTOFF,
|
|
1255
|
+
post_origin=post_origin,
|
|
1256
|
+
)
|
|
1257
|
+
)
|
|
1258
|
+
continue
|
|
1259
|
+
if not self._in_scope(request.scope, observation):
|
|
1260
|
+
decisions.append(
|
|
1261
|
+
self._simple_decision(
|
|
1262
|
+
observation,
|
|
1263
|
+
EvidenceDisposition.OUT_OF_SCOPE,
|
|
1264
|
+
post_origin=post_origin,
|
|
1265
|
+
)
|
|
1266
|
+
)
|
|
1267
|
+
continue
|
|
1268
|
+
repeat_key = (
|
|
1269
|
+
observation.workload_instance_sha256,
|
|
1270
|
+
observation.campaign_sha256,
|
|
1271
|
+
observation.parent_configuration_sha256,
|
|
1272
|
+
observation.child_configuration_sha256,
|
|
1273
|
+
observation.evaluator_contract_sha256,
|
|
1274
|
+
)
|
|
1275
|
+
representative = representative_by_repeat[repeat_key]
|
|
1276
|
+
if representative != observation.source_evidence_id:
|
|
1277
|
+
decisions.append(
|
|
1278
|
+
self._simple_decision(
|
|
1279
|
+
observation,
|
|
1280
|
+
EvidenceDisposition.DUPLICATE,
|
|
1281
|
+
post_origin=post_origin,
|
|
1282
|
+
duplicate_of=representative,
|
|
1283
|
+
)
|
|
1284
|
+
)
|
|
1285
|
+
continue
|
|
1286
|
+
match = matcher.classify(request, observation)
|
|
1287
|
+
self._validate_match(request, observation, matcher, match)
|
|
1288
|
+
assessments = self._metric_assessments(request, observation)
|
|
1289
|
+
predictions_match = (
|
|
1290
|
+
None
|
|
1291
|
+
if assessments is None
|
|
1292
|
+
else all(value.matches for value in assessments)
|
|
1293
|
+
)
|
|
1294
|
+
cluster = self._effective_cluster(observation)
|
|
1295
|
+
disposition = self._disposition(
|
|
1296
|
+
observation=observation,
|
|
1297
|
+
match=match,
|
|
1298
|
+
assessments=assessments,
|
|
1299
|
+
)
|
|
1300
|
+
if disposition is EvidenceDisposition.SUPPORT:
|
|
1301
|
+
support_instances.add(observation.workload_instance_sha256)
|
|
1302
|
+
support_clusters.add(cluster)
|
|
1303
|
+
mechanism_identified = mechanism_identified or (
|
|
1304
|
+
observation.mechanism_identifying_design
|
|
1305
|
+
)
|
|
1306
|
+
if post_origin:
|
|
1307
|
+
post_origin_support_instances.add(
|
|
1308
|
+
observation.workload_instance_sha256
|
|
1309
|
+
)
|
|
1310
|
+
post_origin_support_clusters.add(cluster)
|
|
1311
|
+
post_origin_mechanism_identified = (
|
|
1312
|
+
post_origin_mechanism_identified
|
|
1313
|
+
or observation.mechanism_identifying_design
|
|
1314
|
+
)
|
|
1315
|
+
decisions.append(
|
|
1316
|
+
GlobalEvidenceDecision(
|
|
1317
|
+
source_evidence_id=observation.source_evidence_id,
|
|
1318
|
+
observation_sha256=observation.observation_sha256,
|
|
1319
|
+
event_index=observation.event_index,
|
|
1320
|
+
workload_instance_sha256=(observation.workload_instance_sha256),
|
|
1321
|
+
itt_estimand_unit_sha256=(
|
|
1322
|
+
observation.causal_boundary.estimand_unit_sha256
|
|
1323
|
+
),
|
|
1324
|
+
disposition=disposition,
|
|
1325
|
+
match_receipt_sha256=match.receipt_sha256,
|
|
1326
|
+
metric_assessments=() if assessments is None else assessments,
|
|
1327
|
+
effective_cluster_sha256=(
|
|
1328
|
+
cluster
|
|
1329
|
+
if disposition
|
|
1330
|
+
in {
|
|
1331
|
+
EvidenceDisposition.SUPPORT,
|
|
1332
|
+
EvidenceDisposition.COUNTEREXAMPLE,
|
|
1333
|
+
EvidenceDisposition.OFF_TRIGGER_CONTROL,
|
|
1334
|
+
}
|
|
1335
|
+
else None
|
|
1336
|
+
),
|
|
1337
|
+
post_origin_revision_evidence=post_origin,
|
|
1338
|
+
predictions_match=predictions_match,
|
|
1339
|
+
)
|
|
1340
|
+
)
|
|
1341
|
+
|
|
1342
|
+
canonical = tuple(sorted(decisions, key=lambda value: value.source_evidence_id))
|
|
1343
|
+
support_ids = self._ids(canonical, EvidenceDisposition.SUPPORT)
|
|
1344
|
+
counterexample_ids = self._ids(canonical, EvidenceDisposition.COUNTEREXAMPLE)
|
|
1345
|
+
control_ids = self._ids(canonical, EvidenceDisposition.OFF_TRIGGER_CONTROL)
|
|
1346
|
+
non_identifiable_ids = self._ids(
|
|
1347
|
+
canonical, EvidenceDisposition.NON_IDENTIFIABLE
|
|
1348
|
+
)
|
|
1349
|
+
necessity_contradicted = request.claim_strength.necessity and any(
|
|
1350
|
+
decision.disposition is EvidenceDisposition.OFF_TRIGGER_CONTROL
|
|
1351
|
+
and decision.predictions_match is True
|
|
1352
|
+
for decision in canonical
|
|
1353
|
+
)
|
|
1354
|
+
exact_trigger_instance_ids = {
|
|
1355
|
+
decision.workload_instance_sha256
|
|
1356
|
+
for decision in canonical
|
|
1357
|
+
if decision.disposition
|
|
1358
|
+
in {
|
|
1359
|
+
EvidenceDisposition.SUPPORT,
|
|
1360
|
+
EvidenceDisposition.COUNTEREXAMPLE,
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
untested_instances = tuple(
|
|
1364
|
+
sorted(
|
|
1365
|
+
set(request.scope.workload_instance_sha256s)
|
|
1366
|
+
- exact_trigger_instance_ids
|
|
1367
|
+
)
|
|
1368
|
+
)
|
|
1369
|
+
coverage_gaps = []
|
|
1370
|
+
if not support_ids and not counterexample_ids:
|
|
1371
|
+
coverage_gaps.append("no_identifiable_exact_trigger_intervention_evidence")
|
|
1372
|
+
if len(support_clusters) < request.minimum_support_clusters:
|
|
1373
|
+
coverage_gaps.append("support_cluster_threshold_not_met")
|
|
1374
|
+
if len(support_instances) < request.minimum_support_instances:
|
|
1375
|
+
coverage_gaps.append("support_instance_threshold_not_met")
|
|
1376
|
+
post_origin_support_ids = tuple(
|
|
1377
|
+
value.source_evidence_id
|
|
1378
|
+
for value in canonical
|
|
1379
|
+
if value.disposition is EvidenceDisposition.SUPPORT
|
|
1380
|
+
and value.post_origin_revision_evidence
|
|
1381
|
+
and value.event_index > request.origin_cutoff_event_index
|
|
1382
|
+
)
|
|
1383
|
+
if not post_origin_support_ids:
|
|
1384
|
+
coverage_gaps.append("post_origin_support_absent")
|
|
1385
|
+
if len(post_origin_support_clusters) < request.minimum_support_clusters:
|
|
1386
|
+
coverage_gaps.append("post_origin_support_cluster_threshold_not_met")
|
|
1387
|
+
if len(post_origin_support_instances) < request.minimum_support_instances:
|
|
1388
|
+
coverage_gaps.append("post_origin_support_instance_threshold_not_met")
|
|
1389
|
+
if request.claim_strength.necessity and not control_ids:
|
|
1390
|
+
coverage_gaps.append("necessity_off_trigger_controls_absent")
|
|
1391
|
+
if request.claim_strength.mechanistic_or_causal and not mechanism_identified:
|
|
1392
|
+
coverage_gaps.append("mechanism_identifying_trial_absent")
|
|
1393
|
+
if (
|
|
1394
|
+
request.claim_strength.mechanistic_or_causal
|
|
1395
|
+
and not post_origin_mechanism_identified
|
|
1396
|
+
):
|
|
1397
|
+
coverage_gaps.append("post_origin_mechanism_identifying_trial_absent")
|
|
1398
|
+
if untested_instances:
|
|
1399
|
+
coverage_gaps.append(
|
|
1400
|
+
"workload_instances_without_identifiable_trigger_evidence"
|
|
1401
|
+
)
|
|
1402
|
+
if counterexample_ids or necessity_contradicted:
|
|
1403
|
+
verdict = GlobalHypothesisVerdict.COUNTEREXAMPLE
|
|
1404
|
+
lifecycle = "contradicted__retain_or_deprecate_predecessor_and_revise"
|
|
1405
|
+
elif (
|
|
1406
|
+
request.claim_strength.mechanistic_or_causal
|
|
1407
|
+
and not post_origin_mechanism_identified
|
|
1408
|
+
):
|
|
1409
|
+
verdict = GlobalHypothesisVerdict.NON_IDENTIFIABLE
|
|
1410
|
+
lifecycle = "quarantined__mechanism_requires_post_origin_identifying_trial"
|
|
1411
|
+
elif (
|
|
1412
|
+
len(post_origin_support_clusters) >= request.minimum_support_clusters
|
|
1413
|
+
and len(post_origin_support_instances) >= request.minimum_support_instances
|
|
1414
|
+
):
|
|
1415
|
+
verdict = GlobalHypothesisVerdict.SUPPORT
|
|
1416
|
+
lifecycle = (
|
|
1417
|
+
"globally_audited_local_support__eligible_for_preregistered_trial"
|
|
1418
|
+
)
|
|
1419
|
+
elif not support_ids and non_identifiable_ids:
|
|
1420
|
+
verdict = GlobalHypothesisVerdict.NON_IDENTIFIABLE
|
|
1421
|
+
lifecycle = "quarantined__intervention_or_trigger_not_identifiable"
|
|
1422
|
+
else:
|
|
1423
|
+
verdict = GlobalHypothesisVerdict.INSUFFICIENT
|
|
1424
|
+
lifecycle = "quarantined__undersupported_or_scope_restricted"
|
|
1425
|
+
return GlobalHypothesisAuditReceipt(
|
|
1426
|
+
request_sha256=request.request_sha256,
|
|
1427
|
+
registry_snapshot_sha256=registry.snapshot_sha256,
|
|
1428
|
+
audit_policy_id=self.policy_id,
|
|
1429
|
+
audit_policy_version=self.policy_version,
|
|
1430
|
+
audit_policy_definition_sha256=self.definition_sha256,
|
|
1431
|
+
verdict=verdict,
|
|
1432
|
+
decisions=canonical,
|
|
1433
|
+
support_ids=support_ids,
|
|
1434
|
+
counterexample_ids=counterexample_ids,
|
|
1435
|
+
off_trigger_control_ids=control_ids,
|
|
1436
|
+
non_identifiable_ids=non_identifiable_ids,
|
|
1437
|
+
raw_support_count=len(support_ids),
|
|
1438
|
+
effective_support_cluster_count=len(support_clusters),
|
|
1439
|
+
support_instance_count=len(support_instances),
|
|
1440
|
+
untested_workload_instance_sha256s=untested_instances,
|
|
1441
|
+
coverage_gaps=tuple(sorted(coverage_gaps)),
|
|
1442
|
+
necessity_contradicted=necessity_contradicted,
|
|
1443
|
+
mechanism_identified=mechanism_identified,
|
|
1444
|
+
lifecycle_decision=lifecycle,
|
|
1445
|
+
)
|
|
1446
|
+
|
|
1447
|
+
@staticmethod
|
|
1448
|
+
def _in_scope(
|
|
1449
|
+
scope: HypothesisAuditScope,
|
|
1450
|
+
observation: AuthenticatedHypothesisObservation,
|
|
1451
|
+
) -> bool:
|
|
1452
|
+
return (
|
|
1453
|
+
observation.workload_instance_sha256 in scope.workload_instance_sha256s
|
|
1454
|
+
and observation.evaluator_contract_sha256 == scope.evaluator_contract_sha256
|
|
1455
|
+
and all(
|
|
1456
|
+
metric.adjudicator_definition_sha256
|
|
1457
|
+
== scope.metric_adjudicator_definition_sha256
|
|
1458
|
+
for metric in observation.metrics
|
|
1459
|
+
)
|
|
1460
|
+
and (
|
|
1461
|
+
not scope.campaign_sha256s
|
|
1462
|
+
or observation.campaign_sha256 in scope.campaign_sha256s
|
|
1463
|
+
)
|
|
1464
|
+
)
|
|
1465
|
+
|
|
1466
|
+
@staticmethod
|
|
1467
|
+
def _validate_match(
|
|
1468
|
+
request: GlobalHypothesisAuditRequest,
|
|
1469
|
+
observation: AuthenticatedHypothesisObservation,
|
|
1470
|
+
matcher: GlobalHypothesisEvidenceMatcher,
|
|
1471
|
+
receipt: HypothesisEvidenceMatchReceipt,
|
|
1472
|
+
) -> None:
|
|
1473
|
+
if type(receipt) is not HypothesisEvidenceMatchReceipt:
|
|
1474
|
+
raise TypeError("matcher returned a foreign receipt type")
|
|
1475
|
+
observed = (
|
|
1476
|
+
receipt.request_sha256,
|
|
1477
|
+
receipt.observation_sha256,
|
|
1478
|
+
receipt.matcher_policy_id,
|
|
1479
|
+
receipt.matcher_policy_version,
|
|
1480
|
+
receipt.matcher_definition_sha256,
|
|
1481
|
+
)
|
|
1482
|
+
expected = (
|
|
1483
|
+
request.request_sha256,
|
|
1484
|
+
observation.observation_sha256,
|
|
1485
|
+
matcher.policy_id,
|
|
1486
|
+
matcher.policy_version,
|
|
1487
|
+
matcher.definition_sha256,
|
|
1488
|
+
)
|
|
1489
|
+
if observed != expected:
|
|
1490
|
+
raise ValueError("matcher receipt belongs to a foreign request or policy")
|
|
1491
|
+
if (
|
|
1492
|
+
observation.intervention_identifiability
|
|
1493
|
+
is not InterventionIdentifiability.EXACT_SINGLE
|
|
1494
|
+
and receipt.intervention_match is not InterventionMatch.NON_IDENTIFIABLE
|
|
1495
|
+
):
|
|
1496
|
+
raise AmbiguousInterventionIdentityError(
|
|
1497
|
+
"ambiguous or joint interventions must fail closed as non-identifiable"
|
|
1498
|
+
)
|
|
1499
|
+
if receipt.intervention_match is InterventionMatch.EXACT and (
|
|
1500
|
+
observation.affected_paths != request.intervention.affected_paths
|
|
1501
|
+
or observation.operator_family
|
|
1502
|
+
not in request.intervention.admissible_operator_families
|
|
1503
|
+
):
|
|
1504
|
+
raise ValueError(
|
|
1505
|
+
"exact intervention match contradicts the sealed path or operator family"
|
|
1506
|
+
)
|
|
1507
|
+
|
|
1508
|
+
@staticmethod
|
|
1509
|
+
def _metric_assessments(
|
|
1510
|
+
request: GlobalHypothesisAuditRequest,
|
|
1511
|
+
observation: AuthenticatedHypothesisObservation,
|
|
1512
|
+
) -> tuple[MetricClaimAssessment, ...] | None:
|
|
1513
|
+
by_metric = {value.metric_id: value for value in observation.metrics}
|
|
1514
|
+
if any(value.metric_id not in by_metric for value in request.predictions):
|
|
1515
|
+
return None
|
|
1516
|
+
result = []
|
|
1517
|
+
for prediction in request.predictions:
|
|
1518
|
+
actual = by_metric[prediction.metric_id]
|
|
1519
|
+
magnitude_matches = None
|
|
1520
|
+
if prediction.has_magnitude_claim:
|
|
1521
|
+
assert prediction.minimum_delta is not None
|
|
1522
|
+
assert prediction.maximum_delta is not None
|
|
1523
|
+
magnitude_matches = (
|
|
1524
|
+
prediction.minimum_delta <= actual.delta <= prediction.maximum_delta
|
|
1525
|
+
)
|
|
1526
|
+
result.append(
|
|
1527
|
+
MetricClaimAssessment(
|
|
1528
|
+
metric_id=prediction.metric_id,
|
|
1529
|
+
expected_direction=prediction.direction,
|
|
1530
|
+
actual_direction=actual.direction,
|
|
1531
|
+
actual_delta=actual.delta,
|
|
1532
|
+
direction_matches=prediction.direction is actual.direction,
|
|
1533
|
+
magnitude_matches=magnitude_matches,
|
|
1534
|
+
)
|
|
1535
|
+
)
|
|
1536
|
+
return tuple(result)
|
|
1537
|
+
|
|
1538
|
+
@staticmethod
|
|
1539
|
+
def _disposition(
|
|
1540
|
+
*,
|
|
1541
|
+
observation: AuthenticatedHypothesisObservation,
|
|
1542
|
+
match: HypothesisEvidenceMatchReceipt,
|
|
1543
|
+
assessments: tuple[MetricClaimAssessment, ...] | None,
|
|
1544
|
+
) -> EvidenceDisposition:
|
|
1545
|
+
if (
|
|
1546
|
+
observation.intervention_identifiability
|
|
1547
|
+
is not InterventionIdentifiability.EXACT_SINGLE
|
|
1548
|
+
or match.intervention_match is InterventionMatch.NON_IDENTIFIABLE
|
|
1549
|
+
or match.trigger_match is TriggerMatch.AMBIGUOUS
|
|
1550
|
+
):
|
|
1551
|
+
return EvidenceDisposition.NON_IDENTIFIABLE
|
|
1552
|
+
if match.intervention_match is InterventionMatch.NEAR:
|
|
1553
|
+
return EvidenceDisposition.NEAR_INTERVENTION
|
|
1554
|
+
if match.intervention_match is InterventionMatch.DIFFERENT:
|
|
1555
|
+
return EvidenceDisposition.IRRELEVANT
|
|
1556
|
+
if assessments is None:
|
|
1557
|
+
return EvidenceDisposition.NON_IDENTIFIABLE
|
|
1558
|
+
if match.trigger_match is TriggerMatch.OFF_TRIGGER:
|
|
1559
|
+
return EvidenceDisposition.OFF_TRIGGER_CONTROL
|
|
1560
|
+
if all(value.matches for value in assessments):
|
|
1561
|
+
return EvidenceDisposition.SUPPORT
|
|
1562
|
+
return EvidenceDisposition.COUNTEREXAMPLE
|
|
1563
|
+
|
|
1564
|
+
@staticmethod
|
|
1565
|
+
def _effective_cluster(observation: AuthenticatedHypothesisObservation) -> str:
|
|
1566
|
+
return _hash_record(
|
|
1567
|
+
_CLUSTER_DOMAIN,
|
|
1568
|
+
{
|
|
1569
|
+
"workload_instance_sha256": observation.workload_instance_sha256,
|
|
1570
|
+
"evaluator_contract_sha256": observation.evaluator_contract_sha256,
|
|
1571
|
+
"campaign_sha256": observation.campaign_sha256,
|
|
1572
|
+
"lineage_cluster_sha256": observation.lineage_cluster_sha256,
|
|
1573
|
+
"factorial_block_sha256": observation.factorial_block_sha256,
|
|
1574
|
+
"itt_estimand_unit_sha256": (
|
|
1575
|
+
observation.causal_boundary.estimand_unit_sha256
|
|
1576
|
+
),
|
|
1577
|
+
},
|
|
1578
|
+
)
|
|
1579
|
+
|
|
1580
|
+
@staticmethod
|
|
1581
|
+
def _ids(
|
|
1582
|
+
decisions: tuple[GlobalEvidenceDecision, ...],
|
|
1583
|
+
disposition: EvidenceDisposition,
|
|
1584
|
+
) -> tuple[str, ...]:
|
|
1585
|
+
return tuple(
|
|
1586
|
+
value.source_evidence_id
|
|
1587
|
+
for value in decisions
|
|
1588
|
+
if value.disposition is disposition
|
|
1589
|
+
)
|
|
1590
|
+
|
|
1591
|
+
@staticmethod
|
|
1592
|
+
def _simple_decision(
|
|
1593
|
+
observation: AuthenticatedHypothesisObservation,
|
|
1594
|
+
disposition: EvidenceDisposition,
|
|
1595
|
+
*,
|
|
1596
|
+
post_origin: bool,
|
|
1597
|
+
duplicate_of: str | None = None,
|
|
1598
|
+
) -> GlobalEvidenceDecision:
|
|
1599
|
+
return GlobalEvidenceDecision(
|
|
1600
|
+
source_evidence_id=observation.source_evidence_id,
|
|
1601
|
+
observation_sha256=observation.observation_sha256,
|
|
1602
|
+
event_index=observation.event_index,
|
|
1603
|
+
workload_instance_sha256=observation.workload_instance_sha256,
|
|
1604
|
+
itt_estimand_unit_sha256=(observation.causal_boundary.estimand_unit_sha256),
|
|
1605
|
+
disposition=disposition,
|
|
1606
|
+
match_receipt_sha256=None,
|
|
1607
|
+
metric_assessments=(),
|
|
1608
|
+
effective_cluster_sha256=None,
|
|
1609
|
+
post_origin_revision_evidence=post_origin,
|
|
1610
|
+
duplicate_of_source_evidence_id=duplicate_of,
|
|
1611
|
+
)
|
|
1612
|
+
|
|
1613
|
+
|
|
1614
|
+
class RevisionEvidenceTiming(str, Enum):
|
|
1615
|
+
AVAILABLE_AT_ORIGIN = "available_at_origin"
|
|
1616
|
+
POST_ORIGIN_REVISION_EVIDENCE = "post_origin_revision_evidence"
|
|
1617
|
+
|
|
1618
|
+
|
|
1619
|
+
@dataclass(frozen=True, slots=True)
|
|
1620
|
+
class RevisionEvidenceReference:
|
|
1621
|
+
source_evidence_id: str
|
|
1622
|
+
disposition: EvidenceDisposition
|
|
1623
|
+
timing: RevisionEvidenceTiming
|
|
1624
|
+
|
|
1625
|
+
def __post_init__(self) -> None:
|
|
1626
|
+
require_sha256(self.source_evidence_id, "source_evidence_id")
|
|
1627
|
+
if type(self.disposition) is not EvidenceDisposition:
|
|
1628
|
+
raise TypeError("disposition must be exact EvidenceDisposition")
|
|
1629
|
+
if type(self.timing) is not RevisionEvidenceTiming:
|
|
1630
|
+
raise TypeError("timing must be exact RevisionEvidenceTiming")
|
|
1631
|
+
|
|
1632
|
+
def to_record(self) -> dict[str, str]:
|
|
1633
|
+
return {
|
|
1634
|
+
"source_evidence_id": self.source_evidence_id,
|
|
1635
|
+
"disposition": self.disposition.value,
|
|
1636
|
+
"timing": self.timing.value,
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
|
|
1640
|
+
@dataclass(frozen=True, slots=True)
|
|
1641
|
+
class AppendOnlyHypothesisRevision:
|
|
1642
|
+
"""Successor metadata that never mutates or inherits credit from its parent."""
|
|
1643
|
+
|
|
1644
|
+
predecessor: InsightRef
|
|
1645
|
+
successor: InsightRef
|
|
1646
|
+
predecessor_draft_content_sha256: str
|
|
1647
|
+
successor_draft_content_sha256: str
|
|
1648
|
+
predecessor_audit_request_sha256: str
|
|
1649
|
+
audit_receipt_sha256: str
|
|
1650
|
+
successor_trigger_predicate_sha256: str
|
|
1651
|
+
successor_intervention_signature_sha256: str
|
|
1652
|
+
successor_scope_sha256: str
|
|
1653
|
+
claim_diff: str
|
|
1654
|
+
scope_diff: str
|
|
1655
|
+
evidence: tuple[RevisionEvidenceReference, ...]
|
|
1656
|
+
successor_lifecycle_state: str = field(init=False, default="quarantined")
|
|
1657
|
+
trial_eligibility_reset: bool = field(init=False, default=True)
|
|
1658
|
+
inherited_confirmation_count: int = field(init=False, default=0)
|
|
1659
|
+
inherited_causal_credit: bool = field(init=False, default=False)
|
|
1660
|
+
revision_sha256: str = field(init=False)
|
|
1661
|
+
|
|
1662
|
+
def __post_init__(self) -> None:
|
|
1663
|
+
if (
|
|
1664
|
+
type(self.predecessor) is not InsightRef
|
|
1665
|
+
or type(self.successor) is not InsightRef
|
|
1666
|
+
):
|
|
1667
|
+
raise TypeError("predecessor and successor must be exact InsightRef values")
|
|
1668
|
+
if (
|
|
1669
|
+
self.successor.insight_id != self.predecessor.insight_id
|
|
1670
|
+
or self.successor.version != self.predecessor.version + 1
|
|
1671
|
+
):
|
|
1672
|
+
raise ValueError("successor must be the exact next version of predecessor")
|
|
1673
|
+
for name in (
|
|
1674
|
+
"predecessor_draft_content_sha256",
|
|
1675
|
+
"successor_draft_content_sha256",
|
|
1676
|
+
"predecessor_audit_request_sha256",
|
|
1677
|
+
"audit_receipt_sha256",
|
|
1678
|
+
"successor_trigger_predicate_sha256",
|
|
1679
|
+
"successor_intervention_signature_sha256",
|
|
1680
|
+
"successor_scope_sha256",
|
|
1681
|
+
):
|
|
1682
|
+
require_sha256(getattr(self, name), name)
|
|
1683
|
+
_require_canonical_text(self.claim_diff, name="claim_diff")
|
|
1684
|
+
_require_canonical_text(self.scope_diff, name="scope_diff")
|
|
1685
|
+
if (
|
|
1686
|
+
self.predecessor_draft_content_sha256 == self.successor_draft_content_sha256
|
|
1687
|
+
and self.claim_diff == "unchanged"
|
|
1688
|
+
and self.scope_diff == "unchanged"
|
|
1689
|
+
):
|
|
1690
|
+
raise ValueError("a revision must change claim content or scope")
|
|
1691
|
+
if type(self.evidence) is not tuple or any(
|
|
1692
|
+
type(value) is not RevisionEvidenceReference for value in self.evidence
|
|
1693
|
+
):
|
|
1694
|
+
raise TypeError(
|
|
1695
|
+
"evidence must contain exact RevisionEvidenceReference values"
|
|
1696
|
+
)
|
|
1697
|
+
allowed_evidence_dispositions = {
|
|
1698
|
+
EvidenceDisposition.SUPPORT,
|
|
1699
|
+
EvidenceDisposition.COUNTEREXAMPLE,
|
|
1700
|
+
EvidenceDisposition.OFF_TRIGGER_CONTROL,
|
|
1701
|
+
EvidenceDisposition.NON_IDENTIFIABLE,
|
|
1702
|
+
}
|
|
1703
|
+
if any(
|
|
1704
|
+
value.disposition not in allowed_evidence_dispositions
|
|
1705
|
+
for value in self.evidence
|
|
1706
|
+
):
|
|
1707
|
+
raise ValueError(
|
|
1708
|
+
"revision evidence may contain only matched semantic audit rows"
|
|
1709
|
+
)
|
|
1710
|
+
evidence_ids = tuple(value.source_evidence_id for value in self.evidence)
|
|
1711
|
+
if evidence_ids != tuple(sorted(set(evidence_ids))):
|
|
1712
|
+
raise ValueError("revision evidence must use unique canonical source order")
|
|
1713
|
+
object.__setattr__(
|
|
1714
|
+
self,
|
|
1715
|
+
"revision_sha256",
|
|
1716
|
+
_hash_record(_REVISION_DOMAIN, self._unsigned_record()),
|
|
1717
|
+
)
|
|
1718
|
+
|
|
1719
|
+
@classmethod
|
|
1720
|
+
def from_audit(
|
|
1721
|
+
cls,
|
|
1722
|
+
*,
|
|
1723
|
+
request: GlobalHypothesisAuditRequest,
|
|
1724
|
+
receipt: GlobalHypothesisAuditReceipt,
|
|
1725
|
+
successor: InsightRef,
|
|
1726
|
+
successor_draft_content_sha256: str,
|
|
1727
|
+
successor_trigger_predicate_sha256: str,
|
|
1728
|
+
successor_intervention_signature_sha256: str,
|
|
1729
|
+
successor_scope_sha256: str,
|
|
1730
|
+
claim_diff: str,
|
|
1731
|
+
scope_diff: str,
|
|
1732
|
+
) -> "AppendOnlyHypothesisRevision":
|
|
1733
|
+
if type(request) is not GlobalHypothesisAuditRequest:
|
|
1734
|
+
raise TypeError("request must be exact GlobalHypothesisAuditRequest")
|
|
1735
|
+
if type(receipt) is not GlobalHypothesisAuditReceipt:
|
|
1736
|
+
raise TypeError("receipt must be exact GlobalHypothesisAuditReceipt")
|
|
1737
|
+
if receipt.request_sha256 != request.request_sha256:
|
|
1738
|
+
raise ValueError("audit receipt belongs to a different hypothesis request")
|
|
1739
|
+
evidence = tuple(
|
|
1740
|
+
RevisionEvidenceReference(
|
|
1741
|
+
source_evidence_id=decision.source_evidence_id,
|
|
1742
|
+
disposition=decision.disposition,
|
|
1743
|
+
timing=(
|
|
1744
|
+
RevisionEvidenceTiming.POST_ORIGIN_REVISION_EVIDENCE
|
|
1745
|
+
if decision.post_origin_revision_evidence
|
|
1746
|
+
else RevisionEvidenceTiming.AVAILABLE_AT_ORIGIN
|
|
1747
|
+
),
|
|
1748
|
+
)
|
|
1749
|
+
for decision in receipt.decisions
|
|
1750
|
+
if decision.disposition
|
|
1751
|
+
in {
|
|
1752
|
+
EvidenceDisposition.SUPPORT,
|
|
1753
|
+
EvidenceDisposition.COUNTEREXAMPLE,
|
|
1754
|
+
EvidenceDisposition.OFF_TRIGGER_CONTROL,
|
|
1755
|
+
EvidenceDisposition.NON_IDENTIFIABLE,
|
|
1756
|
+
}
|
|
1757
|
+
)
|
|
1758
|
+
return cls(
|
|
1759
|
+
predecessor=request.reference,
|
|
1760
|
+
successor=successor,
|
|
1761
|
+
predecessor_draft_content_sha256=request.draft_content_sha256,
|
|
1762
|
+
successor_draft_content_sha256=successor_draft_content_sha256,
|
|
1763
|
+
predecessor_audit_request_sha256=request.request_sha256,
|
|
1764
|
+
audit_receipt_sha256=receipt.audit_receipt_sha256,
|
|
1765
|
+
successor_trigger_predicate_sha256=(successor_trigger_predicate_sha256),
|
|
1766
|
+
successor_intervention_signature_sha256=(
|
|
1767
|
+
successor_intervention_signature_sha256
|
|
1768
|
+
),
|
|
1769
|
+
successor_scope_sha256=successor_scope_sha256,
|
|
1770
|
+
claim_diff=claim_diff,
|
|
1771
|
+
scope_diff=scope_diff,
|
|
1772
|
+
evidence=evidence,
|
|
1773
|
+
)
|
|
1774
|
+
|
|
1775
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
1776
|
+
return {
|
|
1777
|
+
"schema_version": 1,
|
|
1778
|
+
"predecessor": {
|
|
1779
|
+
"insight_id": self.predecessor.insight_id.value,
|
|
1780
|
+
"version": self.predecessor.version,
|
|
1781
|
+
},
|
|
1782
|
+
"successor": {
|
|
1783
|
+
"insight_id": self.successor.insight_id.value,
|
|
1784
|
+
"version": self.successor.version,
|
|
1785
|
+
},
|
|
1786
|
+
"predecessor_draft_content_sha256": (self.predecessor_draft_content_sha256),
|
|
1787
|
+
"successor_draft_content_sha256": self.successor_draft_content_sha256,
|
|
1788
|
+
"predecessor_audit_request_sha256": (self.predecessor_audit_request_sha256),
|
|
1789
|
+
"audit_receipt_sha256": self.audit_receipt_sha256,
|
|
1790
|
+
"successor_trigger_predicate_sha256": (
|
|
1791
|
+
self.successor_trigger_predicate_sha256
|
|
1792
|
+
),
|
|
1793
|
+
"successor_intervention_signature_sha256": (
|
|
1794
|
+
self.successor_intervention_signature_sha256
|
|
1795
|
+
),
|
|
1796
|
+
"successor_scope_sha256": self.successor_scope_sha256,
|
|
1797
|
+
"claim_diff": self.claim_diff,
|
|
1798
|
+
"scope_diff": self.scope_diff,
|
|
1799
|
+
"evidence": [value.to_record() for value in self.evidence],
|
|
1800
|
+
"successor_lifecycle_state": self.successor_lifecycle_state,
|
|
1801
|
+
"trial_eligibility_reset": self.trial_eligibility_reset,
|
|
1802
|
+
"inherited_confirmation_count": self.inherited_confirmation_count,
|
|
1803
|
+
"inherited_causal_credit": self.inherited_causal_credit,
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
def to_record(self) -> dict[str, object]:
|
|
1807
|
+
return {**self._unsigned_record(), "revision_sha256": self.revision_sha256}
|
|
1808
|
+
|
|
1809
|
+
|
|
1810
|
+
__all__ = [
|
|
1811
|
+
"AmbiguousInterventionIdentityError",
|
|
1812
|
+
"AppendOnlyHypothesisRevision",
|
|
1813
|
+
"AuthenticatedHypothesisObservation",
|
|
1814
|
+
"CausalEstimandUnit",
|
|
1815
|
+
"EvidenceCausalBoundary",
|
|
1816
|
+
"EvidenceDisposition",
|
|
1817
|
+
"EvidenceProvenance",
|
|
1818
|
+
"GlobalEvidenceDecision",
|
|
1819
|
+
"GlobalEvidenceRegistrySnapshot",
|
|
1820
|
+
"GLOBAL_FALSIFICATION_POLICY_DEFINITION_SHA256",
|
|
1821
|
+
"GLOBAL_FALSIFICATION_POLICY_ID",
|
|
1822
|
+
"GLOBAL_FALSIFICATION_POLICY_VERSION",
|
|
1823
|
+
"GlobalHypothesisAuditReceipt",
|
|
1824
|
+
"GlobalHypothesisAuditRequest",
|
|
1825
|
+
"GlobalHypothesisEvidenceMatcher",
|
|
1826
|
+
"GlobalHypothesisFalsificationGate",
|
|
1827
|
+
"GlobalHypothesisVerdict",
|
|
1828
|
+
"HypothesisAuditScope",
|
|
1829
|
+
"HypothesisClaimStrength",
|
|
1830
|
+
"HypothesisEvidenceMatchReceipt",
|
|
1831
|
+
"HypothesisMetricPrediction",
|
|
1832
|
+
"InterventionIdentifiability",
|
|
1833
|
+
"InterventionMatch",
|
|
1834
|
+
"MetricClaimAssessment",
|
|
1835
|
+
"ObservedMetricEffect",
|
|
1836
|
+
"RevisionEvidenceReference",
|
|
1837
|
+
"RevisionEvidenceTiming",
|
|
1838
|
+
"TriggerMatch",
|
|
1839
|
+
"TypedEvidencePredicate",
|
|
1840
|
+
"TypedInterventionSignature",
|
|
1841
|
+
]
|