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,854 @@
|
|
|
1
|
+
"""Immutable value objects for typed, reversible candidate patches."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Union
|
|
8
|
+
|
|
9
|
+
from agent_evolve.domain.ids import CandidateId
|
|
10
|
+
from agent_evolve.domain.typed_json import (
|
|
11
|
+
DEFAULT_TYPED_JSON_LIMITS,
|
|
12
|
+
FrozenJsonArray,
|
|
13
|
+
FrozenJsonObject,
|
|
14
|
+
FrozenJsonValue,
|
|
15
|
+
TypedJsonLimits,
|
|
16
|
+
canonical_typed_json_bytes,
|
|
17
|
+
freeze_json,
|
|
18
|
+
is_frozen_json_value,
|
|
19
|
+
is_json_scalar,
|
|
20
|
+
typed_json_equal,
|
|
21
|
+
typed_json_sha256,
|
|
22
|
+
validate_typed_json_limits,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_LOWER_HEX = frozenset("0123456789abcdef")
|
|
27
|
+
_PATCH_HASH_DOMAIN = b"agent-evolve:typed-patch:v1\x00"
|
|
28
|
+
_PATH_HASH_DOMAIN = b"agent-evolve:typed-path:v1\x00"
|
|
29
|
+
_OPERATION_EFFECT_HASH_DOMAIN = b"agent-evolve:patch-operation-effect:v1\x00"
|
|
30
|
+
MAX_PATH_SEGMENTS = 64
|
|
31
|
+
MAX_PATH_KEY_BYTES = 4096
|
|
32
|
+
MAX_COMPONENT_TAG_BYTES = 256
|
|
33
|
+
MAX_PATCH_OPERATIONS = 4096
|
|
34
|
+
MAX_PATCH_BYTES = 67_108_864
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def require_sha256(value: str, name: str) -> None:
|
|
38
|
+
if (
|
|
39
|
+
type(value) is not str
|
|
40
|
+
or len(value) != 64
|
|
41
|
+
or any(character not in _LOWER_HEX for character in value)
|
|
42
|
+
):
|
|
43
|
+
raise ValueError(f"{name} must be a lowercase SHA-256 digest")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _validate_candidate_id(value: CandidateId, name: str) -> None:
|
|
47
|
+
if type(value) is not CandidateId:
|
|
48
|
+
raise TypeError(f"{name} must be an exact CandidateId")
|
|
49
|
+
CandidateId.__post_init__(value)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _strict_utf8(value: str, *, name: str, max_bytes: int) -> bytes:
|
|
53
|
+
if type(value) is not str:
|
|
54
|
+
raise TypeError(f"{name} must be an exact string")
|
|
55
|
+
# UTF-8 never uses fewer bytes than Python code points. Rejecting this
|
|
56
|
+
# cheap lower bound first avoids encoding an attacker-sized exact string
|
|
57
|
+
# solely to discover that it exceeds a small field cap.
|
|
58
|
+
if len(value) > max_bytes:
|
|
59
|
+
raise ValueError(f"{name} exceeds its byte limit")
|
|
60
|
+
try:
|
|
61
|
+
encoded = value.encode("utf-8", errors="strict")
|
|
62
|
+
except UnicodeEncodeError as exc:
|
|
63
|
+
raise ValueError(f"{name} is not valid UTF-8 text") from exc
|
|
64
|
+
if len(encoded) > max_bytes:
|
|
65
|
+
raise ValueError(f"{name} exceeds its byte limit")
|
|
66
|
+
return encoded
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _frame(value: bytes) -> bytes:
|
|
70
|
+
if type(value) is not bytes:
|
|
71
|
+
raise TypeError("framed values must be exact bytes")
|
|
72
|
+
return len(value).to_bytes(8, "big", signed=False) + value
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
76
|
+
class ObjectKey:
|
|
77
|
+
"""An object-key path segment; never interchangeable with an array index."""
|
|
78
|
+
|
|
79
|
+
value: str
|
|
80
|
+
|
|
81
|
+
def __post_init__(self) -> None:
|
|
82
|
+
_strict_utf8(self.value, name="object-key path segment", max_bytes=MAX_PATH_KEY_BYTES)
|
|
83
|
+
|
|
84
|
+
def __eq__(self, other: object) -> bool:
|
|
85
|
+
if type(self) is not ObjectKey or type(other) is not ObjectKey:
|
|
86
|
+
return False
|
|
87
|
+
ObjectKey.__post_init__(self)
|
|
88
|
+
ObjectKey.__post_init__(other)
|
|
89
|
+
return self.value == other.value
|
|
90
|
+
|
|
91
|
+
def __hash__(self) -> int:
|
|
92
|
+
if type(self) is not ObjectKey:
|
|
93
|
+
raise TypeError("object-key path segments must be exact ObjectKey values")
|
|
94
|
+
ObjectKey.__post_init__(self)
|
|
95
|
+
return hash((ObjectKey, self.value))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
99
|
+
class ArrayIndex:
|
|
100
|
+
"""An array-index path segment; never interchangeable with a string key."""
|
|
101
|
+
|
|
102
|
+
value: int
|
|
103
|
+
|
|
104
|
+
def __post_init__(self) -> None:
|
|
105
|
+
if type(self.value) is not int:
|
|
106
|
+
raise TypeError("array-index path segments must be exact integers")
|
|
107
|
+
if self.value < 0 or self.value > (1 << 63) - 1:
|
|
108
|
+
raise ValueError("array-index path segment is outside uint63 range")
|
|
109
|
+
|
|
110
|
+
def __eq__(self, other: object) -> bool:
|
|
111
|
+
if type(self) is not ArrayIndex or type(other) is not ArrayIndex:
|
|
112
|
+
return False
|
|
113
|
+
ArrayIndex.__post_init__(self)
|
|
114
|
+
ArrayIndex.__post_init__(other)
|
|
115
|
+
return self.value == other.value
|
|
116
|
+
|
|
117
|
+
def __hash__(self) -> int:
|
|
118
|
+
if type(self) is not ArrayIndex:
|
|
119
|
+
raise TypeError("array-index path segments must be exact ArrayIndex values")
|
|
120
|
+
ArrayIndex.__post_init__(self)
|
|
121
|
+
return hash((ArrayIndex, self.value))
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
PathSegment = Union[ObjectKey, ArrayIndex]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def canonical_path_bytes(path: "JsonPath") -> bytes:
|
|
128
|
+
validate_json_path(path)
|
|
129
|
+
chunks = [len(path.segments).to_bytes(8, "big", signed=False)]
|
|
130
|
+
for segment in path.segments:
|
|
131
|
+
if type(segment) is ObjectKey:
|
|
132
|
+
key = _strict_utf8(
|
|
133
|
+
segment.value,
|
|
134
|
+
name="object-key path segment",
|
|
135
|
+
max_bytes=MAX_PATH_KEY_BYTES,
|
|
136
|
+
)
|
|
137
|
+
chunks.extend((b"k", _frame(key)))
|
|
138
|
+
elif type(segment) is ArrayIndex:
|
|
139
|
+
chunks.extend((b"i", segment.value.to_bytes(8, "big", signed=False)))
|
|
140
|
+
else: # pragma: no cover - JsonPath validation closes the union.
|
|
141
|
+
raise AssertionError("unsupported path segment")
|
|
142
|
+
return b"".join(chunks)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
146
|
+
class JsonPath:
|
|
147
|
+
"""A typed path through objects and arrays; the empty path denotes root."""
|
|
148
|
+
|
|
149
|
+
segments: tuple[PathSegment, ...] = ()
|
|
150
|
+
|
|
151
|
+
def __post_init__(self) -> None:
|
|
152
|
+
if type(self.segments) is not tuple:
|
|
153
|
+
raise TypeError("JsonPath.segments must be an exact tuple")
|
|
154
|
+
if len(self.segments) > MAX_PATH_SEGMENTS:
|
|
155
|
+
raise ValueError("path exceeds MAX_PATH_SEGMENTS")
|
|
156
|
+
for segment in self.segments:
|
|
157
|
+
if type(segment) is ObjectKey:
|
|
158
|
+
ObjectKey.__post_init__(segment)
|
|
159
|
+
elif type(segment) is ArrayIndex:
|
|
160
|
+
ArrayIndex.__post_init__(segment)
|
|
161
|
+
else:
|
|
162
|
+
raise TypeError(
|
|
163
|
+
"paths contain only exact ObjectKey or ArrayIndex segments"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def __eq__(self, other: object) -> bool:
|
|
167
|
+
if type(self) is not JsonPath or type(other) is not JsonPath:
|
|
168
|
+
return False
|
|
169
|
+
return canonical_path_bytes(self) == canonical_path_bytes(other)
|
|
170
|
+
|
|
171
|
+
def __hash__(self) -> int:
|
|
172
|
+
return hash((JsonPath, canonical_path_bytes(self)))
|
|
173
|
+
|
|
174
|
+
def child_key(self, key: str) -> "JsonPath":
|
|
175
|
+
validate_json_path(self)
|
|
176
|
+
return JsonPath(self.segments + (ObjectKey(key),))
|
|
177
|
+
|
|
178
|
+
def child_index(self, index: int) -> "JsonPath":
|
|
179
|
+
validate_json_path(self)
|
|
180
|
+
return JsonPath(self.segments + (ArrayIndex(index),))
|
|
181
|
+
|
|
182
|
+
def is_prefix_of(self, other: "JsonPath") -> bool:
|
|
183
|
+
validate_json_path(self)
|
|
184
|
+
validate_json_path(other)
|
|
185
|
+
if len(self.segments) > len(other.segments):
|
|
186
|
+
return False
|
|
187
|
+
return self.segments == other.segments[: len(self.segments)]
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def schema_identity(self) -> str:
|
|
191
|
+
digest = hashlib.sha256()
|
|
192
|
+
digest.update(_PATH_HASH_DOMAIN)
|
|
193
|
+
digest.update(canonical_path_bytes(self))
|
|
194
|
+
return digest.hexdigest()
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def validate_json_path(path: JsonPath) -> None:
|
|
198
|
+
"""Recursively validate an exact immutable typed path."""
|
|
199
|
+
|
|
200
|
+
if type(path) is not JsonPath:
|
|
201
|
+
raise TypeError("path must be an exact JsonPath")
|
|
202
|
+
JsonPath.__post_init__(path)
|
|
203
|
+
for segment in path.segments:
|
|
204
|
+
if type(segment) is ObjectKey:
|
|
205
|
+
ObjectKey.__post_init__(segment)
|
|
206
|
+
elif type(segment) is ArrayIndex:
|
|
207
|
+
ArrayIndex.__post_init__(segment)
|
|
208
|
+
else: # pragma: no cover - JsonPath.__post_init__ rejects this first.
|
|
209
|
+
raise TypeError("path contains an unsupported segment")
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
ROOT_PATH = JsonPath()
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
216
|
+
class PatchLimits:
|
|
217
|
+
"""Replay-visible bounds for one typed patch."""
|
|
218
|
+
|
|
219
|
+
json_limits: TypedJsonLimits = DEFAULT_TYPED_JSON_LIMITS
|
|
220
|
+
max_operations: int = 1024
|
|
221
|
+
max_path_segments: int = 32
|
|
222
|
+
max_patch_bytes: int = 33_554_432
|
|
223
|
+
|
|
224
|
+
def __post_init__(self) -> None:
|
|
225
|
+
validate_typed_json_limits(self.json_limits)
|
|
226
|
+
if type(self.max_operations) is not int:
|
|
227
|
+
raise TypeError("max_operations must be an exact integer")
|
|
228
|
+
if self.max_operations <= 0 or self.max_operations > MAX_PATCH_OPERATIONS:
|
|
229
|
+
raise ValueError(f"max_operations must lie in [1, {MAX_PATCH_OPERATIONS}]")
|
|
230
|
+
if type(self.max_path_segments) is not int:
|
|
231
|
+
raise TypeError("max_path_segments must be an exact integer")
|
|
232
|
+
if self.max_path_segments <= 0 or self.max_path_segments > MAX_PATH_SEGMENTS:
|
|
233
|
+
raise ValueError(
|
|
234
|
+
f"max_path_segments must lie in [1, {MAX_PATH_SEGMENTS}]"
|
|
235
|
+
)
|
|
236
|
+
if type(self.max_patch_bytes) is not int:
|
|
237
|
+
raise TypeError("max_patch_bytes must be an exact integer")
|
|
238
|
+
if self.max_patch_bytes <= 0 or self.max_patch_bytes > MAX_PATCH_BYTES:
|
|
239
|
+
raise ValueError(f"max_patch_bytes must lie in [1, {MAX_PATCH_BYTES}]")
|
|
240
|
+
|
|
241
|
+
def _validated_values(self) -> tuple[TypedJsonLimits, int, int, int]:
|
|
242
|
+
if type(self) is not PatchLimits:
|
|
243
|
+
raise TypeError("limits must be an exact PatchLimits value")
|
|
244
|
+
PatchLimits.__post_init__(self)
|
|
245
|
+
return (
|
|
246
|
+
self.json_limits,
|
|
247
|
+
self.max_operations,
|
|
248
|
+
self.max_path_segments,
|
|
249
|
+
self.max_patch_bytes,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
def __eq__(self, other: object) -> bool:
|
|
253
|
+
if type(self) is not PatchLimits or type(other) is not PatchLimits:
|
|
254
|
+
return False
|
|
255
|
+
return self._validated_values() == other._validated_values()
|
|
256
|
+
|
|
257
|
+
def __hash__(self) -> int:
|
|
258
|
+
return hash((PatchLimits, self._validated_values()))
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
DEFAULT_PATCH_LIMITS = PatchLimits()
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def validate_patch_limits(limits: PatchLimits) -> None:
|
|
265
|
+
"""Revalidate an exact patch-limit graph, including nested JSON limits."""
|
|
266
|
+
|
|
267
|
+
if type(limits) is not PatchLimits:
|
|
268
|
+
raise TypeError("limits must be an exact PatchLimits value")
|
|
269
|
+
PatchLimits.__post_init__(limits)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def validate_semantic_component(value: str | None) -> None:
|
|
273
|
+
"""Validate one exact bounded semantic-component tag.
|
|
274
|
+
|
|
275
|
+
This is public because both patch operations and replay-derived relation
|
|
276
|
+
values carry the same identity-bearing tag. Validation must happen before
|
|
277
|
+
equality, hashing, or encoding so string-like objects cannot execute code or
|
|
278
|
+
supply a different digest preimage.
|
|
279
|
+
"""
|
|
280
|
+
|
|
281
|
+
if value is None:
|
|
282
|
+
return
|
|
283
|
+
encoded = _strict_utf8(
|
|
284
|
+
value,
|
|
285
|
+
name="semantic_component",
|
|
286
|
+
max_bytes=MAX_COMPONENT_TAG_BYTES,
|
|
287
|
+
)
|
|
288
|
+
if not encoded:
|
|
289
|
+
raise ValueError("semantic_component cannot be empty")
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _validate_operation_common(
|
|
293
|
+
path: JsonPath,
|
|
294
|
+
source_candidate_id: CandidateId,
|
|
295
|
+
semantic_component: str | None,
|
|
296
|
+
) -> None:
|
|
297
|
+
validate_json_path(path)
|
|
298
|
+
_validate_candidate_id(source_candidate_id, "source_candidate_id")
|
|
299
|
+
validate_semantic_component(semantic_component)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _canonical_permutation(
|
|
303
|
+
before: FrozenJsonArray,
|
|
304
|
+
after: FrozenJsonArray,
|
|
305
|
+
*,
|
|
306
|
+
limits: TypedJsonLimits = DEFAULT_TYPED_JSON_LIMITS,
|
|
307
|
+
) -> tuple[int, ...] | None:
|
|
308
|
+
"""Return the stable lowest-source-index matching permutation, if any."""
|
|
309
|
+
|
|
310
|
+
if type(before) is not FrozenJsonArray or type(after) is not FrozenJsonArray:
|
|
311
|
+
raise TypeError("permutation operands must be exact FrozenJsonArray values")
|
|
312
|
+
freeze_json(before, limits=limits)
|
|
313
|
+
freeze_json(after, limits=limits)
|
|
314
|
+
if len(before.items) != len(after.items):
|
|
315
|
+
return None
|
|
316
|
+
positions: dict[bytes, list[int]] = {}
|
|
317
|
+
for index, item in enumerate(before.items):
|
|
318
|
+
key = canonical_typed_json_bytes(item, limits=limits)
|
|
319
|
+
positions.setdefault(key, []).append(index)
|
|
320
|
+
consumed: dict[bytes, int] = {}
|
|
321
|
+
result: list[int] = []
|
|
322
|
+
for item in after.items:
|
|
323
|
+
key = canonical_typed_json_bytes(item, limits=limits)
|
|
324
|
+
cursor = consumed.get(key, 0)
|
|
325
|
+
candidates = positions.get(key)
|
|
326
|
+
if candidates is None or cursor >= len(candidates):
|
|
327
|
+
return None
|
|
328
|
+
result.append(candidates[cursor])
|
|
329
|
+
consumed[key] = cursor + 1
|
|
330
|
+
if any(consumed.get(key, 0) != len(indices) for key, indices in positions.items()):
|
|
331
|
+
return None
|
|
332
|
+
return tuple(result)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class _CanonicalPatchOperation:
|
|
336
|
+
"""Type-sensitive comparison for immutable operation occurrences."""
|
|
337
|
+
|
|
338
|
+
__hash__ = None
|
|
339
|
+
|
|
340
|
+
def __eq__(self, other: object) -> bool:
|
|
341
|
+
if type(self) not in _OPERATION_KINDS or type(other) is not type(self):
|
|
342
|
+
return False
|
|
343
|
+
return operation_occurrence_bytes(self) == operation_occurrence_bytes(other)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
347
|
+
class ReplaceScalar(_CanonicalPatchOperation):
|
|
348
|
+
path: JsonPath
|
|
349
|
+
old_value: FrozenJsonValue
|
|
350
|
+
new_value: FrozenJsonValue
|
|
351
|
+
source_candidate_id: CandidateId
|
|
352
|
+
semantic_component: str | None = None
|
|
353
|
+
|
|
354
|
+
def __post_init__(self) -> None:
|
|
355
|
+
_validate_operation_common(
|
|
356
|
+
self.path, self.source_candidate_id, self.semantic_component
|
|
357
|
+
)
|
|
358
|
+
if not is_json_scalar(self.old_value) or not is_json_scalar(self.new_value):
|
|
359
|
+
raise TypeError("replace_scalar requires exact typed-JSON scalar values")
|
|
360
|
+
if typed_json_equal(self.old_value, self.new_value):
|
|
361
|
+
raise ValueError("replace_scalar cannot encode a no-op")
|
|
362
|
+
|
|
363
|
+
@property
|
|
364
|
+
def old_value_hash(self) -> str:
|
|
365
|
+
return typed_json_sha256(self.old_value)
|
|
366
|
+
|
|
367
|
+
@property
|
|
368
|
+
def new_value_hash(self) -> str:
|
|
369
|
+
return typed_json_sha256(self.new_value)
|
|
370
|
+
|
|
371
|
+
@property
|
|
372
|
+
def schema_field_identity(self) -> str:
|
|
373
|
+
return self.path.schema_identity
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
377
|
+
class ReplaceSubtree(_CanonicalPatchOperation):
|
|
378
|
+
path: JsonPath
|
|
379
|
+
old_value: FrozenJsonValue
|
|
380
|
+
new_value: FrozenJsonValue
|
|
381
|
+
source_candidate_id: CandidateId
|
|
382
|
+
semantic_component: str | None = None
|
|
383
|
+
|
|
384
|
+
def __post_init__(self) -> None:
|
|
385
|
+
_validate_operation_common(
|
|
386
|
+
self.path, self.source_candidate_id, self.semantic_component
|
|
387
|
+
)
|
|
388
|
+
if not is_frozen_json_value(self.old_value) or not is_frozen_json_value(
|
|
389
|
+
self.new_value
|
|
390
|
+
):
|
|
391
|
+
raise TypeError("replace_subtree requires frozen typed-JSON values")
|
|
392
|
+
if is_json_scalar(self.old_value) and is_json_scalar(self.new_value):
|
|
393
|
+
raise TypeError("scalar-to-scalar edits must use replace_scalar")
|
|
394
|
+
if typed_json_equal(self.old_value, self.new_value):
|
|
395
|
+
raise ValueError("replace_subtree cannot encode a no-op")
|
|
396
|
+
|
|
397
|
+
@property
|
|
398
|
+
def old_value_hash(self) -> str:
|
|
399
|
+
return typed_json_sha256(self.old_value)
|
|
400
|
+
|
|
401
|
+
@property
|
|
402
|
+
def new_value_hash(self) -> str:
|
|
403
|
+
return typed_json_sha256(self.new_value)
|
|
404
|
+
|
|
405
|
+
@property
|
|
406
|
+
def schema_field_identity(self) -> str:
|
|
407
|
+
return self.path.schema_identity
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
411
|
+
class InsertSequenceItem(_CanonicalPatchOperation):
|
|
412
|
+
path: JsonPath
|
|
413
|
+
index: int
|
|
414
|
+
item: FrozenJsonValue
|
|
415
|
+
before_sequence: FrozenJsonArray
|
|
416
|
+
after_sequence: FrozenJsonArray
|
|
417
|
+
source_candidate_id: CandidateId
|
|
418
|
+
semantic_component: str | None = None
|
|
419
|
+
|
|
420
|
+
def __post_init__(self) -> None:
|
|
421
|
+
_validate_operation_common(
|
|
422
|
+
self.path, self.source_candidate_id, self.semantic_component
|
|
423
|
+
)
|
|
424
|
+
if type(self.index) is not int:
|
|
425
|
+
raise TypeError("insert index must be an exact integer")
|
|
426
|
+
if type(self.before_sequence) is not FrozenJsonArray or type(
|
|
427
|
+
self.after_sequence
|
|
428
|
+
) is not FrozenJsonArray:
|
|
429
|
+
raise TypeError("insert_sequence_item requires frozen arrays")
|
|
430
|
+
if not is_frozen_json_value(self.item):
|
|
431
|
+
raise TypeError("inserted item must be a frozen typed-JSON value")
|
|
432
|
+
freeze_json(self.before_sequence)
|
|
433
|
+
freeze_json(self.after_sequence)
|
|
434
|
+
freeze_json(self.item)
|
|
435
|
+
if self.index < 0 or self.index > len(self.before_sequence.items):
|
|
436
|
+
raise ValueError("insert index is outside the source array")
|
|
437
|
+
expected = FrozenJsonArray(
|
|
438
|
+
self.before_sequence.items[: self.index]
|
|
439
|
+
+ (self.item,)
|
|
440
|
+
+ self.before_sequence.items[self.index :]
|
|
441
|
+
)
|
|
442
|
+
if not typed_json_equal(expected, self.after_sequence):
|
|
443
|
+
raise ValueError("after_sequence is not the declared insertion result")
|
|
444
|
+
|
|
445
|
+
@property
|
|
446
|
+
def old_value_hash(self) -> str:
|
|
447
|
+
return typed_json_sha256(self.before_sequence)
|
|
448
|
+
|
|
449
|
+
@property
|
|
450
|
+
def new_value_hash(self) -> str:
|
|
451
|
+
return typed_json_sha256(self.after_sequence)
|
|
452
|
+
|
|
453
|
+
@property
|
|
454
|
+
def schema_field_identity(self) -> str:
|
|
455
|
+
return self.path.schema_identity
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
459
|
+
class DeleteSequenceItem(_CanonicalPatchOperation):
|
|
460
|
+
path: JsonPath
|
|
461
|
+
index: int
|
|
462
|
+
item: FrozenJsonValue
|
|
463
|
+
before_sequence: FrozenJsonArray
|
|
464
|
+
after_sequence: FrozenJsonArray
|
|
465
|
+
source_candidate_id: CandidateId
|
|
466
|
+
semantic_component: str | None = None
|
|
467
|
+
|
|
468
|
+
def __post_init__(self) -> None:
|
|
469
|
+
_validate_operation_common(
|
|
470
|
+
self.path, self.source_candidate_id, self.semantic_component
|
|
471
|
+
)
|
|
472
|
+
if type(self.index) is not int:
|
|
473
|
+
raise TypeError("delete index must be an exact integer")
|
|
474
|
+
if type(self.before_sequence) is not FrozenJsonArray or type(
|
|
475
|
+
self.after_sequence
|
|
476
|
+
) is not FrozenJsonArray:
|
|
477
|
+
raise TypeError("delete_sequence_item requires frozen arrays")
|
|
478
|
+
if not is_frozen_json_value(self.item):
|
|
479
|
+
raise TypeError("deleted item must be a frozen typed-JSON value")
|
|
480
|
+
freeze_json(self.before_sequence)
|
|
481
|
+
freeze_json(self.after_sequence)
|
|
482
|
+
freeze_json(self.item)
|
|
483
|
+
if self.index < 0 or self.index >= len(self.before_sequence.items):
|
|
484
|
+
raise ValueError("delete index is outside the source array")
|
|
485
|
+
if not typed_json_equal(self.before_sequence.items[self.index], self.item):
|
|
486
|
+
raise ValueError("deleted item does not match the source array")
|
|
487
|
+
expected = FrozenJsonArray(
|
|
488
|
+
self.before_sequence.items[: self.index]
|
|
489
|
+
+ self.before_sequence.items[self.index + 1 :]
|
|
490
|
+
)
|
|
491
|
+
if not typed_json_equal(expected, self.after_sequence):
|
|
492
|
+
raise ValueError("after_sequence is not the declared deletion result")
|
|
493
|
+
|
|
494
|
+
@property
|
|
495
|
+
def old_value_hash(self) -> str:
|
|
496
|
+
return typed_json_sha256(self.before_sequence)
|
|
497
|
+
|
|
498
|
+
@property
|
|
499
|
+
def new_value_hash(self) -> str:
|
|
500
|
+
return typed_json_sha256(self.after_sequence)
|
|
501
|
+
|
|
502
|
+
@property
|
|
503
|
+
def schema_field_identity(self) -> str:
|
|
504
|
+
return self.path.schema_identity
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
508
|
+
class PermuteSequence(_CanonicalPatchOperation):
|
|
509
|
+
path: JsonPath
|
|
510
|
+
permutation: tuple[int, ...]
|
|
511
|
+
before_sequence: FrozenJsonArray
|
|
512
|
+
after_sequence: FrozenJsonArray
|
|
513
|
+
source_candidate_id: CandidateId
|
|
514
|
+
semantic_component: str | None = None
|
|
515
|
+
|
|
516
|
+
def __post_init__(self) -> None:
|
|
517
|
+
_validate_operation_common(
|
|
518
|
+
self.path, self.source_candidate_id, self.semantic_component
|
|
519
|
+
)
|
|
520
|
+
if type(self.before_sequence) is not FrozenJsonArray or type(
|
|
521
|
+
self.after_sequence
|
|
522
|
+
) is not FrozenJsonArray:
|
|
523
|
+
raise TypeError("permute_sequence requires frozen arrays")
|
|
524
|
+
freeze_json(self.before_sequence)
|
|
525
|
+
freeze_json(self.after_sequence)
|
|
526
|
+
size = len(self.before_sequence.items)
|
|
527
|
+
if type(self.permutation) is not tuple:
|
|
528
|
+
raise TypeError("permutation must be an exact tuple of exact integers")
|
|
529
|
+
if len(self.permutation) != size:
|
|
530
|
+
raise ValueError("permutation must contain every source index exactly once")
|
|
531
|
+
if any(type(index) is not int for index in self.permutation):
|
|
532
|
+
raise TypeError("permutation must be an exact tuple of exact integers")
|
|
533
|
+
if set(self.permutation) != set(range(size)):
|
|
534
|
+
raise ValueError("permutation must contain every source index exactly once")
|
|
535
|
+
canonical = _canonical_permutation(
|
|
536
|
+
self.before_sequence,
|
|
537
|
+
self.after_sequence,
|
|
538
|
+
)
|
|
539
|
+
if canonical is None:
|
|
540
|
+
raise ValueError("before_sequence and after_sequence are not permutations")
|
|
541
|
+
if self.permutation != canonical:
|
|
542
|
+
raise ValueError(
|
|
543
|
+
"permutation is not the deterministic lowest-source-index mapping"
|
|
544
|
+
)
|
|
545
|
+
if self.permutation == tuple(range(size)):
|
|
546
|
+
raise ValueError("permute_sequence cannot encode an identity permutation")
|
|
547
|
+
|
|
548
|
+
@property
|
|
549
|
+
def old_value_hash(self) -> str:
|
|
550
|
+
return typed_json_sha256(self.before_sequence)
|
|
551
|
+
|
|
552
|
+
@property
|
|
553
|
+
def new_value_hash(self) -> str:
|
|
554
|
+
return typed_json_sha256(self.after_sequence)
|
|
555
|
+
|
|
556
|
+
@property
|
|
557
|
+
def schema_field_identity(self) -> str:
|
|
558
|
+
return self.path.schema_identity
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
PatchOperation = Union[
|
|
562
|
+
ReplaceScalar,
|
|
563
|
+
ReplaceSubtree,
|
|
564
|
+
InsertSequenceItem,
|
|
565
|
+
DeleteSequenceItem,
|
|
566
|
+
PermuteSequence,
|
|
567
|
+
]
|
|
568
|
+
|
|
569
|
+
_OPERATION_KINDS = {
|
|
570
|
+
ReplaceScalar: b"replace_scalar",
|
|
571
|
+
ReplaceSubtree: b"replace_subtree",
|
|
572
|
+
InsertSequenceItem: b"insert_sequence_item",
|
|
573
|
+
DeleteSequenceItem: b"delete_sequence_item",
|
|
574
|
+
PermuteSequence: b"permute_sequence",
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
def validate_patch_operation(operation: PatchOperation) -> None:
|
|
579
|
+
"""Re-run the complete invariant set for one exact operation value."""
|
|
580
|
+
|
|
581
|
+
operation_type = type(operation)
|
|
582
|
+
if operation_type not in _OPERATION_KINDS:
|
|
583
|
+
raise TypeError("unsupported patch operation type")
|
|
584
|
+
operation_type.__post_init__(operation)
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def operation_kind(operation: PatchOperation) -> str:
|
|
588
|
+
validate_patch_operation(operation)
|
|
589
|
+
kind = _OPERATION_KINDS.get(type(operation))
|
|
590
|
+
if kind is None: # pragma: no cover - validator closes the mapping.
|
|
591
|
+
raise AssertionError("validated operation lacked a kind")
|
|
592
|
+
return kind.decode("ascii")
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
def _operation_sort_key_unchecked(operation: PatchOperation) -> tuple[bytes, bytes]:
|
|
596
|
+
return canonical_path_bytes(operation.path), _OPERATION_KINDS[type(operation)]
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
def operation_sort_key(operation: PatchOperation) -> tuple[bytes, bytes]:
|
|
600
|
+
validate_patch_operation(operation)
|
|
601
|
+
return _operation_sort_key_unchecked(operation)
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def _operation_values(operation: PatchOperation) -> tuple[FrozenJsonValue, FrozenJsonValue]:
|
|
605
|
+
if type(operation) in (ReplaceScalar, ReplaceSubtree):
|
|
606
|
+
return operation.old_value, operation.new_value
|
|
607
|
+
if type(operation) in (InsertSequenceItem, DeleteSequenceItem, PermuteSequence):
|
|
608
|
+
return operation.before_sequence, operation.after_sequence
|
|
609
|
+
raise TypeError("unsupported patch operation type")
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
def operation_effect_bytes(
|
|
613
|
+
operation: PatchOperation,
|
|
614
|
+
*,
|
|
615
|
+
include_component: bool = True,
|
|
616
|
+
limits: TypedJsonLimits = DEFAULT_TYPED_JSON_LIMITS,
|
|
617
|
+
) -> bytes:
|
|
618
|
+
"""Canonical operation effect, excluding branch-specific occurrence IDs."""
|
|
619
|
+
|
|
620
|
+
if type(include_component) is not bool:
|
|
621
|
+
raise TypeError("include_component must be an exact Boolean")
|
|
622
|
+
validate_patch_operation(operation)
|
|
623
|
+
validate_typed_json_limits(limits)
|
|
624
|
+
old_value, new_value = _operation_values(operation)
|
|
625
|
+
component = (
|
|
626
|
+
b""
|
|
627
|
+
if not include_component or operation.semantic_component is None
|
|
628
|
+
else _strict_utf8(
|
|
629
|
+
operation.semantic_component,
|
|
630
|
+
name="semantic_component",
|
|
631
|
+
max_bytes=MAX_COMPONENT_TAG_BYTES,
|
|
632
|
+
)
|
|
633
|
+
)
|
|
634
|
+
parts = [
|
|
635
|
+
_frame(_OPERATION_KINDS[type(operation)]),
|
|
636
|
+
_frame(canonical_path_bytes(operation.path)),
|
|
637
|
+
_frame(component),
|
|
638
|
+
_frame(canonical_typed_json_bytes(old_value, limits=limits)),
|
|
639
|
+
_frame(canonical_typed_json_bytes(new_value, limits=limits)),
|
|
640
|
+
]
|
|
641
|
+
if type(operation) in (InsertSequenceItem, DeleteSequenceItem):
|
|
642
|
+
parts.append(operation.index.to_bytes(8, "big", signed=False))
|
|
643
|
+
parts.append(
|
|
644
|
+
_frame(canonical_typed_json_bytes(operation.item, limits=limits))
|
|
645
|
+
)
|
|
646
|
+
elif type(operation) is PermuteSequence:
|
|
647
|
+
parts.append(len(operation.permutation).to_bytes(8, "big", signed=False))
|
|
648
|
+
parts.extend(
|
|
649
|
+
index.to_bytes(8, "big", signed=False)
|
|
650
|
+
for index in operation.permutation
|
|
651
|
+
)
|
|
652
|
+
return b"".join(parts)
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
def operation_occurrence_bytes(
|
|
656
|
+
operation: PatchOperation,
|
|
657
|
+
*,
|
|
658
|
+
limits: TypedJsonLimits = DEFAULT_TYPED_JSON_LIMITS,
|
|
659
|
+
) -> bytes:
|
|
660
|
+
"""Canonical operation occurrence, including its exact source identity."""
|
|
661
|
+
|
|
662
|
+
validate_patch_operation(operation)
|
|
663
|
+
source = operation.source_candidate_id.value.encode("ascii", errors="strict")
|
|
664
|
+
return _frame(source) + _frame(operation_effect_bytes(operation, limits=limits))
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
def operation_effect_sha256(
|
|
668
|
+
operation: PatchOperation,
|
|
669
|
+
*,
|
|
670
|
+
limits: TypedJsonLimits = DEFAULT_TYPED_JSON_LIMITS,
|
|
671
|
+
) -> str:
|
|
672
|
+
"""Hash one exact operation effect independently of its branch occurrence."""
|
|
673
|
+
|
|
674
|
+
digest = hashlib.sha256()
|
|
675
|
+
digest.update(_OPERATION_EFFECT_HASH_DOMAIN)
|
|
676
|
+
digest.update(operation_effect_bytes(operation, limits=limits))
|
|
677
|
+
return digest.hexdigest()
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
def _validate_non_overlapping_operation_paths(
|
|
681
|
+
operations: tuple[PatchOperation, ...],
|
|
682
|
+
*,
|
|
683
|
+
work_counter: list[int] | None = None,
|
|
684
|
+
) -> None:
|
|
685
|
+
"""Reject equal/prefix paths in linear total path length using a trie."""
|
|
686
|
+
|
|
687
|
+
terminal = object()
|
|
688
|
+
root: dict[object, object] = {}
|
|
689
|
+
for operation in operations:
|
|
690
|
+
node = root
|
|
691
|
+
for segment in operation.path.segments:
|
|
692
|
+
if work_counter is not None:
|
|
693
|
+
work_counter[0] += 1
|
|
694
|
+
if terminal in node:
|
|
695
|
+
raise ValueError("patch operation paths cannot overlap")
|
|
696
|
+
child = node.setdefault(segment, {})
|
|
697
|
+
if type(child) is not dict: # pragma: no cover - local trie invariant.
|
|
698
|
+
raise AssertionError("invalid patch-path trie")
|
|
699
|
+
node = child
|
|
700
|
+
if terminal in node or node:
|
|
701
|
+
raise ValueError("patch operation paths cannot overlap")
|
|
702
|
+
node[terminal] = True
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
def _validate_operation_limits(operation: PatchOperation, limits: PatchLimits) -> None:
|
|
706
|
+
validate_patch_operation(operation)
|
|
707
|
+
validate_patch_limits(limits)
|
|
708
|
+
if len(operation.path.segments) > limits.max_path_segments:
|
|
709
|
+
raise ValueError("operation path exceeds patch max_path_segments")
|
|
710
|
+
old_value, new_value = _operation_values(operation)
|
|
711
|
+
canonical_typed_json_bytes(old_value, limits=limits.json_limits)
|
|
712
|
+
canonical_typed_json_bytes(new_value, limits=limits.json_limits)
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
716
|
+
class TypedPatch:
|
|
717
|
+
"""One canonical, bounded transformation between exact candidate contents."""
|
|
718
|
+
|
|
719
|
+
base_candidate_id: CandidateId
|
|
720
|
+
target_candidate_id: CandidateId
|
|
721
|
+
base_hash: str
|
|
722
|
+
target_hash: str
|
|
723
|
+
operations: tuple[PatchOperation, ...]
|
|
724
|
+
limits: PatchLimits = DEFAULT_PATCH_LIMITS
|
|
725
|
+
schema_version: str = "typed_json_patch_v1"
|
|
726
|
+
|
|
727
|
+
def __post_init__(self) -> None:
|
|
728
|
+
_validate_candidate_id(self.base_candidate_id, "base_candidate_id")
|
|
729
|
+
_validate_candidate_id(self.target_candidate_id, "target_candidate_id")
|
|
730
|
+
if self.base_candidate_id == self.target_candidate_id:
|
|
731
|
+
raise ValueError("patch endpoints must be distinct candidate occurrences")
|
|
732
|
+
require_sha256(self.base_hash, "base_hash")
|
|
733
|
+
require_sha256(self.target_hash, "target_hash")
|
|
734
|
+
if type(self.operations) is not tuple:
|
|
735
|
+
raise TypeError("patch operations must be an exact tuple")
|
|
736
|
+
validate_patch_limits(self.limits)
|
|
737
|
+
if type(self.schema_version) is not str or self.schema_version != "typed_json_patch_v1":
|
|
738
|
+
raise ValueError("unsupported typed-patch schema_version")
|
|
739
|
+
if len(self.operations) > self.limits.max_operations:
|
|
740
|
+
raise ValueError("patch exceeds max_operations")
|
|
741
|
+
for operation in self.operations:
|
|
742
|
+
validate_patch_operation(operation)
|
|
743
|
+
canonical = tuple(sorted(self.operations, key=_operation_sort_key_unchecked))
|
|
744
|
+
if self.operations != canonical:
|
|
745
|
+
raise ValueError("patch operations must use canonical path/kind order")
|
|
746
|
+
for operation in self.operations:
|
|
747
|
+
if operation.source_candidate_id != self.base_candidate_id:
|
|
748
|
+
raise ValueError("every operation must bind the patch base occurrence")
|
|
749
|
+
_validate_operation_limits(operation, self.limits)
|
|
750
|
+
# Bound the exact framed preimage consumed by ``patch_hash`` (apart
|
|
751
|
+
# from the fixed domain tag, which is process-constant).
|
|
752
|
+
total_patch_bytes = (
|
|
753
|
+
8
|
|
754
|
+
+ len(self.schema_version.encode("ascii", errors="strict"))
|
|
755
|
+
+ 8
|
|
756
|
+
+ len(self.base_candidate_id.value.encode("ascii", errors="strict"))
|
|
757
|
+
+ 8
|
|
758
|
+
+ len(self.target_candidate_id.value.encode("ascii", errors="strict"))
|
|
759
|
+
+ 64 # two raw SHA-256 endpoint digests
|
|
760
|
+
+ 9 * 8 # replay limits
|
|
761
|
+
+ 8 # operation count
|
|
762
|
+
)
|
|
763
|
+
if total_patch_bytes > self.limits.max_patch_bytes:
|
|
764
|
+
raise ValueError("patch exceeds max_patch_bytes")
|
|
765
|
+
for operation in self.operations:
|
|
766
|
+
total_patch_bytes += 8 + len(
|
|
767
|
+
operation_effect_bytes(operation, limits=self.limits.json_limits)
|
|
768
|
+
)
|
|
769
|
+
if total_patch_bytes > self.limits.max_patch_bytes:
|
|
770
|
+
raise ValueError("patch exceeds max_patch_bytes")
|
|
771
|
+
_validate_non_overlapping_operation_paths(self.operations)
|
|
772
|
+
if not self.operations and self.base_hash != self.target_hash:
|
|
773
|
+
raise ValueError("an empty patch requires identical base and target hashes")
|
|
774
|
+
if self.operations and self.base_hash == self.target_hash:
|
|
775
|
+
raise ValueError("a non-empty patch cannot claim identical endpoint hashes")
|
|
776
|
+
|
|
777
|
+
def __eq__(self, other: object) -> bool:
|
|
778
|
+
if type(self) is not TypedPatch or type(other) is not TypedPatch:
|
|
779
|
+
return False
|
|
780
|
+
return self.patch_hash == other.patch_hash
|
|
781
|
+
|
|
782
|
+
__hash__ = None
|
|
783
|
+
|
|
784
|
+
@property
|
|
785
|
+
def patch_hash(self) -> str:
|
|
786
|
+
validate_typed_patch(self)
|
|
787
|
+
digest = hashlib.sha256()
|
|
788
|
+
digest.update(_PATCH_HASH_DOMAIN)
|
|
789
|
+
digest.update(_frame(self.schema_version.encode("ascii", errors="strict")))
|
|
790
|
+
digest.update(_frame(self.base_candidate_id.value.encode("ascii", errors="strict")))
|
|
791
|
+
digest.update(_frame(self.target_candidate_id.value.encode("ascii", errors="strict")))
|
|
792
|
+
digest.update(bytes.fromhex(self.base_hash))
|
|
793
|
+
digest.update(bytes.fromhex(self.target_hash))
|
|
794
|
+
limit_values = (
|
|
795
|
+
self.limits.max_operations,
|
|
796
|
+
self.limits.max_path_segments,
|
|
797
|
+
self.limits.max_patch_bytes,
|
|
798
|
+
self.limits.json_limits.max_depth,
|
|
799
|
+
self.limits.json_limits.max_nodes,
|
|
800
|
+
self.limits.json_limits.max_container_items,
|
|
801
|
+
self.limits.json_limits.max_string_bytes,
|
|
802
|
+
self.limits.json_limits.max_integer_bits,
|
|
803
|
+
self.limits.json_limits.max_canonical_bytes,
|
|
804
|
+
)
|
|
805
|
+
for value in limit_values:
|
|
806
|
+
digest.update(value.to_bytes(8, "big", signed=False))
|
|
807
|
+
digest.update(len(self.operations).to_bytes(8, "big", signed=False))
|
|
808
|
+
for operation in self.operations:
|
|
809
|
+
digest.update(
|
|
810
|
+
_frame(
|
|
811
|
+
operation_effect_bytes(
|
|
812
|
+
operation,
|
|
813
|
+
limits=self.limits.json_limits,
|
|
814
|
+
)
|
|
815
|
+
)
|
|
816
|
+
)
|
|
817
|
+
return digest.hexdigest()
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
def validate_typed_patch(patch: TypedPatch) -> None:
|
|
821
|
+
"""Revalidate a complete exact patch graph at a consuming boundary."""
|
|
822
|
+
|
|
823
|
+
if type(patch) is not TypedPatch:
|
|
824
|
+
raise TypeError("patch must be an exact TypedPatch")
|
|
825
|
+
TypedPatch.__post_init__(patch)
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
__all__ = [
|
|
829
|
+
"ArrayIndex",
|
|
830
|
+
"DEFAULT_PATCH_LIMITS",
|
|
831
|
+
"DeleteSequenceItem",
|
|
832
|
+
"InsertSequenceItem",
|
|
833
|
+
"JsonPath",
|
|
834
|
+
"ObjectKey",
|
|
835
|
+
"PatchLimits",
|
|
836
|
+
"PatchOperation",
|
|
837
|
+
"PermuteSequence",
|
|
838
|
+
"ROOT_PATH",
|
|
839
|
+
"ReplaceScalar",
|
|
840
|
+
"ReplaceSubtree",
|
|
841
|
+
"TypedPatch",
|
|
842
|
+
"canonical_path_bytes",
|
|
843
|
+
"operation_effect_bytes",
|
|
844
|
+
"operation_effect_sha256",
|
|
845
|
+
"operation_occurrence_bytes",
|
|
846
|
+
"operation_kind",
|
|
847
|
+
"operation_sort_key",
|
|
848
|
+
"require_sha256",
|
|
849
|
+
"validate_json_path",
|
|
850
|
+
"validate_patch_limits",
|
|
851
|
+
"validate_patch_operation",
|
|
852
|
+
"validate_semantic_component",
|
|
853
|
+
"validate_typed_patch",
|
|
854
|
+
]
|