agentinputlab 0.1.0a1__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.
- agentinputlab/__init__.py +28 -0
- agentinputlab/algorithms/__init__.py +6 -0
- agentinputlab/algorithms/acquire/__init__.py +145 -0
- agentinputlab/algorithms/acquire/adaptive_rag/__init__.py +43 -0
- agentinputlab/algorithms/acquire/adaptive_rag/replay.py +367 -0
- agentinputlab/algorithms/acquire/adaptive_rag/runtime.py +1202 -0
- agentinputlab/algorithms/acquire/adaptive_rag_common_support.py +542 -0
- agentinputlab/algorithms/acquire/hyde.py +296 -0
- agentinputlab/algorithms/acquire/hyde_controlled.py +200 -0
- agentinputlab/algorithms/acquire/hyde_external.py +148 -0
- agentinputlab/algorithms/acquire/ircot.py +28 -0
- agentinputlab/algorithms/acquire/ircot_common_support.py +478 -0
- agentinputlab/algorithms/acquire/ircot_native.py +355 -0
- agentinputlab/algorithms/acquire/ircot_replay.py +327 -0
- agentinputlab/algorithms/acquire/qrag/__init__.py +79 -0
- agentinputlab/algorithms/acquire/qrag/bridge.py +828 -0
- agentinputlab/algorithms/acquire/qrag/common_support.py +891 -0
- agentinputlab/algorithms/acquire/qrag/native_axis.py +71 -0
- agentinputlab/algorithms/acquire/qrag/reader_adapter.py +139 -0
- agentinputlab/algorithms/acquire/r2ag/__init__.py +57 -0
- agentinputlab/algorithms/acquire/r2ag/bridge.py +1312 -0
- agentinputlab/algorithms/acquire/r2ag/common_support.py +1029 -0
- agentinputlab/algorithms/acquire/r2ag/socket_runtime.py +255 -0
- agentinputlab/algorithms/clarify/__init__.py +61 -0
- agentinputlab/algorithms/clarify/_structured.py +64 -0
- agentinputlab/algorithms/clarify/active_task_disambiguation.py +371 -0
- agentinputlab/algorithms/clarify/ask_or_assume/__init__.py +17 -0
- agentinputlab/algorithms/clarify/ask_or_assume/monitor.py +410 -0
- agentinputlab/algorithms/clarify/future_turns.py +379 -0
- agentinputlab/algorithms/clarify/udcs.py +255 -0
- agentinputlab/algorithms/clarify/udcs_deterministic.py +196 -0
- agentinputlab/algorithms/curate/__init__.py +65 -0
- agentinputlab/algorithms/curate/acon/__init__.py +27 -0
- agentinputlab/algorithms/curate/acon/common.py +284 -0
- agentinputlab/algorithms/curate/acon/common_support.py +136 -0
- agentinputlab/algorithms/curate/acon/history_bridge.py +309 -0
- agentinputlab/algorithms/curate/acon/observation_bridge.py +290 -0
- agentinputlab/algorithms/curate/llmlingua/__init__.py +30 -0
- agentinputlab/algorithms/curate/llmlingua/adapters.py +537 -0
- agentinputlab/algorithms/curate/llmlingua/budget.py +48 -0
- agentinputlab/algorithms/curate/llmlingua/controlled.py +175 -0
- agentinputlab/algorithms/curate/llmlingua/multilingual.py +105 -0
- agentinputlab/algorithms/curate/llmlingua/span_map.py +167 -0
- agentinputlab/algorithms/curate/recomp/__init__.py +47 -0
- agentinputlab/algorithms/curate/recomp/external.py +1129 -0
- agentinputlab/algorithms/curate/recomp/selective.py +304 -0
- agentinputlab/algorithms/decompose/__init__.py +107 -0
- agentinputlab/algorithms/decompose/decomp/__init__.py +53 -0
- agentinputlab/algorithms/decompose/decomp/common_support.py +478 -0
- agentinputlab/algorithms/decompose/decomp/planner_component.py +199 -0
- agentinputlab/algorithms/decompose/decomp/worker_bridge.py +416 -0
- agentinputlab/algorithms/decompose/llm_compiler/__init__.py +35 -0
- agentinputlab/algorithms/decompose/llm_compiler/bridge.py +520 -0
- agentinputlab/algorithms/decompose/llm_compiler/common_support.py +504 -0
- agentinputlab/algorithms/decompose/plan_and_budget/__init__.py +43 -0
- agentinputlab/algorithms/decompose/plan_and_budget/cached_soft_budget.py +362 -0
- agentinputlab/algorithms/decompose/plan_and_budget/decomposer_component.py +107 -0
- agentinputlab/algorithms/decompose/plan_and_budget/full.py +205 -0
- agentinputlab/algorithms/decompose/plan_and_budget/hard_cap.py +246 -0
- agentinputlab/algorithms/decompose/plan_and_budget/regenerated_soft_budget.py +448 -0
- agentinputlab/algorithms/decompose/plan_and_solve/__init__.py +33 -0
- agentinputlab/algorithms/decompose/plan_and_solve/plan_component.py +192 -0
- agentinputlab/algorithms/decompose/plan_and_solve/ps_prompt.py +127 -0
- agentinputlab/algorithms/decompose/self_ask/__init__.py +27 -0
- agentinputlab/algorithms/decompose/self_ask/loop.py +362 -0
- agentinputlab/algorithms/decompose/self_ask/planner_component.py +117 -0
- agentinputlab/algorithms/reformulate/__init__.py +85 -0
- agentinputlab/algorithms/reformulate/infocqr/__init__.py +40 -0
- agentinputlab/algorithms/reformulate/infocqr/cache_replay.py +362 -0
- agentinputlab/algorithms/reformulate/infocqr/external.py +675 -0
- agentinputlab/algorithms/reformulate/infocqr/student.py +189 -0
- agentinputlab/algorithms/reformulate/query2doc/__init__.py +42 -0
- agentinputlab/algorithms/reformulate/query2doc/controlled_live.py +190 -0
- agentinputlab/algorithms/reformulate/query2doc/models.py +95 -0
- agentinputlab/algorithms/reformulate/query2doc/official_cached.py +335 -0
- agentinputlab/algorithms/reformulate/query2doc/querygym_bridge.py +223 -0
- agentinputlab/algorithms/reformulate/query2doc/querygym_runtime.py +326 -0
- agentinputlab/algorithms/reformulate/reformer/__init__.py +23 -0
- agentinputlab/algorithms/reformulate/reformer/external.py +417 -0
- agentinputlab/algorithms/reformulate/rrr.py +696 -0
- agentinputlab/annotation/__init__.py +5 -0
- agentinputlab/annotation/independent.py +428 -0
- agentinputlab/annotation/server.py +233 -0
- agentinputlab/annotation/static/app.js +121 -0
- agentinputlab/annotation/static/index.html +60 -0
- agentinputlab/annotation/static/review.css +2 -0
- agentinputlab/annotation/static/review.html +10 -0
- agentinputlab/annotation/static/review.js +11 -0
- agentinputlab/annotation/static/styles.css +55 -0
- agentinputlab/annotation/store.py +444 -0
- agentinputlab/api.py +368 -0
- agentinputlab/artifacts/__init__.py +15 -0
- agentinputlab/artifacts/json_file.py +104 -0
- agentinputlab/artifacts/manifest.py +98 -0
- agentinputlab/artifacts/store.py +157 -0
- agentinputlab/backends/__init__.py +98 -0
- agentinputlab/backends/embedding/__init__.py +25 -0
- agentinputlab/backends/embedding/base.py +145 -0
- agentinputlab/backends/embedding/callable.py +75 -0
- agentinputlab/backends/embedding/replay.py +129 -0
- agentinputlab/backends/llm/__init__.py +31 -0
- agentinputlab/backends/llm/base.py +149 -0
- agentinputlab/backends/llm/callable.py +108 -0
- agentinputlab/backends/llm/decomp_completion.py +88 -0
- agentinputlab/backends/llm/final_content_stop.py +72 -0
- agentinputlab/backends/llm/gpt_oss_final_channel.py +84 -0
- agentinputlab/backends/llm/openai_compatible.py +332 -0
- agentinputlab/backends/llm/transformers.py +171 -0
- agentinputlab/backends/retrieval/__init__.py +35 -0
- agentinputlab/backends/retrieval/base.py +50 -0
- agentinputlab/backends/retrieval/dense.py +777 -0
- agentinputlab/backends/retrieval/memory.py +119 -0
- agentinputlab/backends/retrieval/sqlite_fts.py +155 -0
- agentinputlab/backends/user/__init__.py +15 -0
- agentinputlab/backends/user/base.py +66 -0
- agentinputlab/backends/user/callable.py +54 -0
- agentinputlab/backends/user/replay.py +89 -0
- agentinputlab/backends/vector.py +38 -0
- agentinputlab/benchmarks/__init__.py +246 -0
- agentinputlab/benchmarks/acquisition.py +106 -0
- agentinputlab/benchmarks/ale.py +540 -0
- agentinputlab/benchmarks/appworld_ul.py +669 -0
- agentinputlab/benchmarks/assets.py +226 -0
- agentinputlab/benchmarks/base.py +68 -0
- agentinputlab/benchmarks/beir_archive.py +235 -0
- agentinputlab/benchmarks/beir_sample.py +269 -0
- agentinputlab/benchmarks/bfcl_native.py +245 -0
- agentinputlab/benchmarks/bfcl_v3.py +607 -0
- agentinputlab/benchmarks/cache.py +218 -0
- agentinputlab/benchmarks/clariq.py +203 -0
- agentinputlab/benchmarks/data/registry.json +321 -0
- agentinputlab/benchmarks/data/smoke_assets.json +511 -0
- agentinputlab/benchmarks/data/statebench_v0_8_1_task_ids.json +475 -0
- agentinputlab/benchmarks/data/toolsandbox_165848b_scenario_names.json +1 -0
- agentinputlab/benchmarks/execution.py +1111 -0
- agentinputlab/benchmarks/interactive.py +257 -0
- agentinputlab/benchmarks/ir_query.py +204 -0
- agentinputlab/benchmarks/longbench.py +203 -0
- agentinputlab/benchmarks/paired_replay.py +479 -0
- agentinputlab/benchmarks/preprocess.py +2313 -0
- agentinputlab/benchmarks/production.py +236 -0
- agentinputlab/benchmarks/qa.py +963 -0
- agentinputlab/benchmarks/registry.py +110 -0
- agentinputlab/benchmarks/retrieval.py +459 -0
- agentinputlab/benchmarks/runtime.py +509 -0
- agentinputlab/benchmarks/statebench.py +938 -0
- agentinputlab/benchmarks/tau3.py +725 -0
- agentinputlab/benchmarks/toolsandbox.py +626 -0
- agentinputlab/benchmarks/trec_dl_sample.py +263 -0
- agentinputlab/campaign.py +654 -0
- agentinputlab/campaign_runner.py +953 -0
- agentinputlab/cli/__init__.py +6 -0
- agentinputlab/cli/main.py +933 -0
- agentinputlab/connectors/__init__.py +46 -0
- agentinputlab/connectors/knowledge/__init__.py +4 -0
- agentinputlab/connectors/tools/__init__.py +30 -0
- agentinputlab/connectors/tools/base.py +57 -0
- agentinputlab/connectors/tools/mcp.py +222 -0
- agentinputlab/connectors/tools/memory.py +41 -0
- agentinputlab/connectors/tools/openapi.py +695 -0
- agentinputlab/connectors/tools/schema.py +102 -0
- agentinputlab/contracts/__init__.py +91 -0
- agentinputlab/contracts/availability.py +24 -0
- agentinputlab/contracts/dependency.py +58 -0
- agentinputlab/contracts/enums.py +82 -0
- agentinputlab/contracts/errors.py +63 -0
- agentinputlab/contracts/evidence.py +43 -0
- agentinputlab/contracts/intent.py +110 -0
- agentinputlab/contracts/lifecycle.py +164 -0
- agentinputlab/contracts/patch.py +167 -0
- agentinputlab/contracts/serialization.py +93 -0
- agentinputlab/contracts/state.py +127 -0
- agentinputlab/contracts/trace.py +59 -0
- agentinputlab/contracts/worker.py +42 -0
- agentinputlab/core/__init__.py +7 -0
- agentinputlab/data/__init__.py +32 -0
- agentinputlab/data/checksums.py +9 -0
- agentinputlab/data/downloader.py +235 -0
- agentinputlab/data/http.py +171 -0
- agentinputlab/data/index.py +21 -0
- agentinputlab/data/overlap.py +28 -0
- agentinputlab/data/preprocess.py +32 -0
- agentinputlab/data/storage.py +57 -0
- agentinputlab/data/trec_sample.py +427 -0
- agentinputlab/evaluation/__init__.py +5 -0
- agentinputlab/evaluation/outcomes.py +32 -0
- agentinputlab/experiments/__init__.py +278 -0
- agentinputlab/experiments/analysis.py +981 -0
- agentinputlab/experiments/artifacts.py +687 -0
- agentinputlab/experiments/automatic_measurement.py +678 -0
- agentinputlab/experiments/auxiliary_matrix.py +203 -0
- agentinputlab/experiments/bindings.py +430 -0
- agentinputlab/experiments/blueprints.py +147 -0
- agentinputlab/experiments/builders/__init__.py +45 -0
- agentinputlab/experiments/builders/executable_external_payload.py +423 -0
- agentinputlab/experiments/builders/external_process_arena.py +1018 -0
- agentinputlab/experiments/builders/registry_intervention.py +463 -0
- agentinputlab/experiments/cli.py +158 -0
- agentinputlab/experiments/controls.py +340 -0
- agentinputlab/experiments/driver_guard.py +205 -0
- agentinputlab/experiments/drivers/__init__.py +89 -0
- agentinputlab/experiments/drivers/external_arena.py +2908 -0
- agentinputlab/experiments/drivers/method_process.py +662 -0
- agentinputlab/experiments/drivers/parity_process.py +1484 -0
- agentinputlab/experiments/drivers/static_production.py +1574 -0
- agentinputlab/experiments/executor.py +448 -0
- agentinputlab/experiments/loader.py +605 -0
- agentinputlab/experiments/measurement.py +675 -0
- agentinputlab/experiments/paper_analysis.py +1818 -0
- agentinputlab/experiments/parity_identity.py +605 -0
- agentinputlab/experiments/plan.py +277 -0
- agentinputlab/experiments/production_builders.py +3739 -0
- agentinputlab/experiments/readiness.py +432 -0
- agentinputlab/experiments/schema.py +514 -0
- agentinputlab/experiments/secrets.py +177 -0
- agentinputlab/experiments/stateful_analysis.py +1738 -0
- agentinputlab/experiments/transfer_analysis.py +1316 -0
- agentinputlab/experiments/unseen_analysis.py +1025 -0
- agentinputlab/integrations/__init__.py +1 -0
- agentinputlab/integrations/action_space/__init__.py +1 -0
- agentinputlab/integrations/external_workers/__init__.py +76 -0
- agentinputlab/integrations/external_workers/ace.py +431 -0
- agentinputlab/integrations/external_workers/base.py +181 -0
- agentinputlab/integrations/external_workers/decomp_live.py +249 -0
- agentinputlab/integrations/external_workers/decomp_planner.py +30 -0
- agentinputlab/integrations/external_workers/infocqr_student.py +280 -0
- agentinputlab/integrations/external_workers/ircot.py +576 -0
- agentinputlab/integrations/external_workers/llmcompiler_live.py +360 -0
- agentinputlab/integrations/external_workers/longllmlingua.py +162 -0
- agentinputlab/integrations/external_workers/method.py +314 -0
- agentinputlab/integrations/external_workers/process.py +678 -0
- agentinputlab/integrations/external_workers/supervisor.py +1071 -0
- agentinputlab/integrations/full_architecture/__init__.py +1 -0
- agentinputlab/integrations/full_architecture/hyde.py +212 -0
- agentinputlab/integrations/full_architecture/plan_and_budget.py +188 -0
- agentinputlab/integrations/live_history.py +222 -0
- agentinputlab/integrations/prompt_optimization/__init__.py +1 -0
- agentinputlab/integrations/training/__init__.py +1 -0
- agentinputlab/metrics/__init__.py +50 -0
- agentinputlab/metrics/catalog.py +313 -0
- agentinputlab/metrics/harm.py +95 -0
- agentinputlab/metrics/paired.py +40 -0
- agentinputlab/metrics/qa.py +76 -0
- agentinputlab/metrics/text.py +33 -0
- agentinputlab/metrics/third_party.py +161 -0
- agentinputlab/models/__init__.py +4 -0
- agentinputlab/observability/__init__.py +56 -0
- agentinputlab/observability/budget.py +316 -0
- agentinputlab/observability/instrumentation.py +754 -0
- agentinputlab/observability/models.py +403 -0
- agentinputlab/observability/monitor.py +380 -0
- agentinputlab/optimizers/__init__.py +1 -0
- agentinputlab/optimizers/clarify/__init__.py +12 -0
- agentinputlab/optimizers/context/__init__.py +22 -0
- agentinputlab/optimizers/decompose/__init__.py +64 -0
- agentinputlab/optimizers/evidence/__init__.py +22 -0
- agentinputlab/optimizers/rewrite/__init__.py +30 -0
- agentinputlab/parity/__init__.py +5 -0
- agentinputlab/parity/base.py +206 -0
- agentinputlab/production_campaign.py +763 -0
- agentinputlab/py.typed +1 -0
- agentinputlab/recipes/__init__.py +333 -0
- agentinputlab/recipes/ace.py +418 -0
- agentinputlab/recipes/ace_bfcl.py +273 -0
- agentinputlab/recipes/ace_common_support.py +169 -0
- agentinputlab/recipes/ace_native_plan.py +322 -0
- agentinputlab/recipes/ace_online.py +711 -0
- agentinputlab/recipes/ace_retrieval.py +330 -0
- agentinputlab/recipes/ace_statebench.py +382 -0
- agentinputlab/recipes/ace_tau.py +463 -0
- agentinputlab/recipes/ace_toolsandbox.py +432 -0
- agentinputlab/recipes/acon_guideline.py +22 -0
- agentinputlab/recipes/adaptive_rag.py +822 -0
- agentinputlab/recipes/llmlingua2_inference.py +146 -0
- agentinputlab/recipes/llmlingua2_training.py +796 -0
- agentinputlab/recipes/mce.py +1486 -0
- agentinputlab/recipes/mce_final.py +335 -0
- agentinputlab/recipes/mce_text_fit.py +499 -0
- agentinputlab/recipes/mce_text_only.py +298 -0
- agentinputlab/recipes/qrag.py +729 -0
- agentinputlab/recipes/qrag_inference.py +364 -0
- agentinputlab/recipes/r2ag_features.py +236 -0
- agentinputlab/recipes/r2ag_inference.py +503 -0
- agentinputlab/recipes/r2ag_training.py +948 -0
- agentinputlab/recipes/recomp_abstractive.py +592 -0
- agentinputlab/recipes/recomp_extractive.py +525 -0
- agentinputlab/recipes/reformer.py +17 -0
- agentinputlab/recipes/rqrag.py +1516 -0
- agentinputlab/recipes/rqrag_common_support.py +193 -0
- agentinputlab/recipes/rqrag_completion.py +147 -0
- agentinputlab/recipes/skillopt.py +462 -0
- agentinputlab/recipes/skillopt_common_support.py +154 -0
- agentinputlab/recipes/skillopt_environment.py +165 -0
- agentinputlab/recipes/skillopt_environment_cli.py +5 -0
- agentinputlab/recipes/skillopt_retrieval.py +188 -0
- agentinputlab/recipes/smartsearch.py +897 -0
- agentinputlab/recipes/smartsearch_curriculum.py +283 -0
- agentinputlab/recipes/smartsearch_grpo_contracts.py +58 -0
- agentinputlab/recipes/smartsearch_grpo_data.py +314 -0
- agentinputlab/recipes/smartsearch_grpo_meter.py +324 -0
- agentinputlab/recipes/smartsearch_grpo_prefix.py +144 -0
- agentinputlab/recipes/smartsearch_grpo_rollout.py +119 -0
- agentinputlab/recipes/smartsearch_grpo_tools.py +364 -0
- agentinputlab/recipes/smartsearch_grpo_training.py +305 -0
- agentinputlab/recipes/smartsearch_grpo_transport.py +248 -0
- agentinputlab/recipes/smartsearch_grpo_worker.py +364 -0
- agentinputlab/recipes/smartsearch_inference.py +524 -0
- agentinputlab/recipes/smartsearch_native_data.py +172 -0
- agentinputlab/recipes/smartsearch_policy.py +616 -0
- agentinputlab/recipes/smartsearch_policy_worker.py +9 -0
- agentinputlab/recipes/smartsearch_sampling.py +232 -0
- agentinputlab/recipes/smartsearch_teacher.py +203 -0
- agentinputlab/recipes/smartsearch_training.py +265 -0
- agentinputlab/registry/__init__.py +46 -0
- agentinputlab/registry/beta.py +727 -0
- agentinputlab/registry/data/algorithms.json +1959 -0
- agentinputlab/registry/data/auxiliary_method_profiles.json +34 -0
- agentinputlab/registry/loader.py +100 -0
- agentinputlab/registry/result_manifest.py +184 -0
- agentinputlab/registry/runtime.py +195 -0
- agentinputlab/registry/schema.py +449 -0
- agentinputlab/runner/__init__.py +30 -0
- agentinputlab/runner/balance.py +248 -0
- agentinputlab/runner/cache.py +135 -0
- agentinputlab/runner/composite.py +130 -0
- agentinputlab/runner/engine.py +285 -0
- agentinputlab/runner/runtime_guard.py +268 -0
- agentinputlab/runner/worker.py +80 -0
- agentinputlab/runtime_bootstrap.py +288 -0
- agentinputlab/selectors/__init__.py +196 -0
- agentinputlab/selectors/advanced.py +1514 -0
- agentinputlab/selectors/artifacts.py +1087 -0
- agentinputlab/selectors/base.py +112 -0
- agentinputlab/selectors/campaigns.py +1893 -0
- agentinputlab/selectors/catalog.py +86 -0
- agentinputlab/selectors/discovery.py +1860 -0
- agentinputlab/selectors/evaluation.py +1348 -0
- agentinputlab/selectors/features.py +383 -0
- agentinputlab/selectors/learned.py +502 -0
- agentinputlab/selectors/no_op.py +28 -0
- agentinputlab/selectors/outcome_table.py +475 -0
- agentinputlab/selectors/producer.py +853 -0
- agentinputlab/selectors/rules.py +109 -0
- agentinputlab/selectors/runtime.py +420 -0
- agentinputlab/studies/__init__.py +16 -0
- agentinputlab/studies/design.py +165 -0
- agentinputlab/studies/paired.py +322 -0
- agentinputlab/studio/__init__.py +6 -0
- agentinputlab/studio/campaigns.py +819 -0
- agentinputlab/studio/engine.py +1408 -0
- agentinputlab/studio/model_backends.py +425 -0
- agentinputlab/studio/prepared.py +410 -0
- agentinputlab/studio/server.py +613 -0
- agentinputlab/studio/static/app.js +397 -0
- agentinputlab/studio/static/campaigns.html +175 -0
- agentinputlab/studio/static/campaigns.js +426 -0
- agentinputlab/studio/static/index.html +192 -0
- agentinputlab/studio/static/result.html +103 -0
- agentinputlab/studio/static/result.js +273 -0
- agentinputlab/studio/static/styles.css +467 -0
- agentinputlab-0.1.0a1.data/data/share/agentinputlab/CITATION.cff +22 -0
- agentinputlab-0.1.0a1.dist-info/METADATA +392 -0
- agentinputlab-0.1.0a1.dist-info/RECORD +469 -0
- agentinputlab-0.1.0a1.dist-info/WHEEL +5 -0
- agentinputlab-0.1.0a1.dist-info/entry_points.txt +6 -0
- agentinputlab-0.1.0a1.dist-info/licenses/LICENSE +21 -0
- agentinputlab-0.1.0a1.dist-info/licenses/NOTICE +31 -0
- agentinputlab-0.1.0a1.dist-info/licenses/THIRD_PARTY_NOTICES.md +94 -0
- agentinputlab-0.1.0a1.dist-info/top_level.txt +2 -0
- workers/README.md +94 -0
- workers/ace/README.md +261 -0
- workers/ace/__init__.py +1 -0
- workers/ace/compatibility_manifest.json +39 -0
- workers/ace/driver.py +1682 -0
- workers/acon/README.md +215 -0
- workers/acon/__init__.py +5 -0
- workers/acon/compatibility_manifest.json +93 -0
- workers/acon/driver.py +1859 -0
- workers/acon/fitted.py +395 -0
- workers/acon/guideline.py +470 -0
- workers/acon/guideline_source_manifest.json +20 -0
- workers/adaptive_rag/README.md +96 -0
- workers/adaptive_rag/__init__.py +15 -0
- workers/adaptive_rag/compatibility_manifest.json +61 -0
- workers/adaptive_rag/driver.py +1470 -0
- workers/adaptive_rag/fixture_runtime.py +163 -0
- workers/adaptive_rag/runtime.py +252 -0
- workers/ask_or_assume/README.md +124 -0
- workers/ask_or_assume/__init__.py +1 -0
- workers/ask_or_assume/compatibility_manifest.json +36 -0
- workers/ask_or_assume/driver.py +1227 -0
- workers/decomp/README.md +133 -0
- workers/decomp/UPSTREAM_LICENSE.Apache-2.0.txt +201 -0
- workers/decomp/__init__.py +5 -0
- workers/decomp/compatibility_manifest.json +205 -0
- workers/decomp/driver.py +1015 -0
- workers/decomp/live.py +137 -0
- workers/decomp/runtime.py +231 -0
- workers/infocqr/README.md +106 -0
- workers/infocqr/__init__.py +1 -0
- workers/infocqr/compatibility_manifest.json +84 -0
- workers/infocqr/driver.py +1766 -0
- workers/ircot/README.md +167 -0
- workers/ircot/__init__.py +7 -0
- workers/ircot/compatibility_manifest.json +121 -0
- workers/ircot/controlled.py +457 -0
- workers/ircot/driver.py +1373 -0
- workers/ircot/fixture_bindings.json +39 -0
- workers/ircot/fixture_config.json +48 -0
- workers/ircot/fixture_prompt.txt +2 -0
- workers/ircot/native_qa.py +398 -0
- workers/ircot/qa_compatibility_manifest.json +57 -0
- workers/ircot/runtime.py +97 -0
- workers/llmcompiler/README.md +160 -0
- workers/llmcompiler/__init__.py +1 -0
- workers/llmcompiler/compatibility_manifest.json +45 -0
- workers/llmcompiler/driver.py +1514 -0
- workers/llmcompiler/export_prompts.py +74 -0
- workers/llmcompiler/live.py +174 -0
- workers/llmcompiler/patch_manifest.json +147 -0
- workers/mce/README.md +198 -0
- workers/mce/__init__.py +29 -0
- workers/mce/compatibility_manifest.json +115 -0
- workers/mce/driver.py +2585 -0
- workers/mce/final_eval.py +173 -0
- workers/official_arenas/README.md +25 -0
- workers/official_arenas/__init__.py +1 -0
- workers/official_arenas/capture_records.py +110 -0
- workers/official_arenas/driver.py +619 -0
- workers/official_arenas/protocol.py +334 -0
- workers/official_arenas/providers/__init__.py +1 -0
- workers/official_arenas/providers/_live_history.py +228 -0
- workers/official_arenas/providers/_message_compat.py +77 -0
- workers/official_arenas/providers/appworld_ul.py +196 -0
- workers/official_arenas/providers/fixture.py +112 -0
- workers/official_arenas/providers/statebench.py +960 -0
- workers/official_arenas/providers/tau3.py +1672 -0
- workers/official_arenas/providers/tau_retail.py +586 -0
- workers/official_arenas/providers/toolsandbox.py +1321 -0
- workers/official_arenas/providers/tooltalk.py +699 -0
- workers/official_arenas/retail_capture.py +188 -0
- workers/official_arenas/retail_driver.py +275 -0
- workers/qrag/README.md +172 -0
- workers/qrag/__init__.py +1 -0
- workers/qrag/compatibility_manifest.json +64 -0
- workers/qrag/driver.py +673 -0
- workers/r2ag/README.md +154 -0
- workers/r2ag/__init__.py +5 -0
- workers/r2ag/compatibility_manifest.json +55 -0
- workers/r2ag/driver.py +1287 -0
- workers/r2ag/feature_schema.json +8 -0
- workers/r2ag/runtime.py +589 -0
- workers/recomp/README.md +120 -0
- workers/recomp/__init__.py +1 -0
- workers/recomp/compatibility_manifest.json +43 -0
- workers/recomp/driver.py +1653 -0
- workers/reformer/README.md +125 -0
- workers/reformer/__init__.py +1 -0
- workers/reformer/compatibility_manifest.json +68 -0
- workers/reformer/driver.py +1459 -0
- workers/reformer/induction.py +457 -0
- workers/rrr/README.md +126 -0
- workers/rrr/__init__.py +1 -0
- workers/rrr/compatibility_manifest.json +57 -0
- workers/rrr/driver.py +1133 -0
- workers/smartsearch/README.md +331 -0
- workers/smartsearch/__init__.py +1 -0
- workers/smartsearch/compatibility_manifest.json +41 -0
- workers/smartsearch/driver.py +1643 -0
|
@@ -0,0 +1,1738 @@
|
|
|
1
|
+
"""Fail-closed analysis for gated stateful-arena replications.
|
|
2
|
+
|
|
3
|
+
Stateful arenas do not share an official raw outcome scale. This module
|
|
4
|
+
therefore validates the complete atomic-replay evidence chain and estimates
|
|
5
|
+
effects inside each arena relative to the same-origin ``NO_OP`` result. It
|
|
6
|
+
never pools raw metrics across arenas. The only cross-arena summaries are
|
|
7
|
+
scale-free direction and win-minus-harm diagnostics over arena-local paired
|
|
8
|
+
effects.
|
|
9
|
+
|
|
10
|
+
The public record validator is deliberately reusable by transfer analyses.
|
|
11
|
+
It validates record-local atomic, scorer, source, dependency, and balance
|
|
12
|
+
evidence, but leaves the decision to require an approved external gate to the
|
|
13
|
+
calling protocol. The stateful-replication entry point always requires that
|
|
14
|
+
gate and additionally cross-checks it against the immutable driver
|
|
15
|
+
fingerprint stored in the verified run directory.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import math
|
|
22
|
+
import random
|
|
23
|
+
from collections import defaultdict
|
|
24
|
+
from collections.abc import Mapping, Sequence
|
|
25
|
+
from dataclasses import dataclass
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from statistics import fmean, median
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
from agentinputlab.benchmarks import (
|
|
31
|
+
ATOMIC_EXTERNAL_PAIR_CONTRACT,
|
|
32
|
+
AtomicReplayArmReceipt,
|
|
33
|
+
AtomicReplayReceipt,
|
|
34
|
+
)
|
|
35
|
+
from agentinputlab.contracts.enums import SemanticFamily
|
|
36
|
+
from agentinputlab.contracts.errors import ArtifactError, ValidationError
|
|
37
|
+
from agentinputlab.contracts.serialization import content_hash
|
|
38
|
+
|
|
39
|
+
from .analysis import _load_manifest, load_verified_cell_results
|
|
40
|
+
from .artifacts import verify_experiment_run_directory
|
|
41
|
+
from .schema import ExperimentManifest, ExperimentStage
|
|
42
|
+
|
|
43
|
+
STATEFUL_ARENA_REPLICATION_PROTOCOL = "stateful-arena-replication-v1"
|
|
44
|
+
STATEFUL_ARENA_REPLICATION_ARTIFACT = "agentinputlab.stateful_arena_replication_analysis"
|
|
45
|
+
|
|
46
|
+
_EXTERNAL_ARENA_DRIVER = "agentinputlab.experiments.drivers.external_arena:factory"
|
|
47
|
+
_EXTERNAL_ARENA_IMPLEMENTATION = "agentinputlab.external_arena.atomic_multi_arm"
|
|
48
|
+
_VALID_SUCCESS_FORMULA = "agentinputlab.valid_success.v1"
|
|
49
|
+
_VALID_SUCCESS_METRIC = "valid_success"
|
|
50
|
+
_SUPPORTED_STATEFUL_SUITES = frozenset(
|
|
51
|
+
{
|
|
52
|
+
"tau3.banking_knowledge",
|
|
53
|
+
"state_bench.common_support",
|
|
54
|
+
"toolsandbox.common_support",
|
|
55
|
+
"appworld_ul.common_support",
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
_DEPENDENCY_SLOTS = frozenset(
|
|
59
|
+
{
|
|
60
|
+
"agent_model",
|
|
61
|
+
"official_runtime",
|
|
62
|
+
"corpus",
|
|
63
|
+
"preprocessing",
|
|
64
|
+
"index",
|
|
65
|
+
"embedder",
|
|
66
|
+
"retriever",
|
|
67
|
+
"reranker",
|
|
68
|
+
"reader",
|
|
69
|
+
"scorer",
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
_NON_MODEL_DEPENDENCY_SLOTS = _DEPENDENCY_SLOTS - {"agent_model"}
|
|
73
|
+
_ALLOWED_STAGES = frozenset(
|
|
74
|
+
{
|
|
75
|
+
ExperimentStage.CONFIRMATORY,
|
|
76
|
+
ExperimentStage.SELECTOR_CONFIRMATION,
|
|
77
|
+
ExperimentStage.REPLICATION,
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass(frozen=True, slots=True)
|
|
83
|
+
class _VerifiedStatefulRun:
|
|
84
|
+
root: Path
|
|
85
|
+
manifest: ExperimentManifest
|
|
86
|
+
records: tuple[Mapping[str, Any], ...]
|
|
87
|
+
verification: Mapping[str, Any]
|
|
88
|
+
driver_fingerprint: Mapping[str, Any]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def validate_external_arena_record_receipts(
|
|
92
|
+
manifest: ExperimentManifest,
|
|
93
|
+
record: Mapping[str, Any],
|
|
94
|
+
*,
|
|
95
|
+
metric: str,
|
|
96
|
+
) -> Mapping[str, Any]:
|
|
97
|
+
"""Validate one external-arena result and return a normalized paired row.
|
|
98
|
+
|
|
99
|
+
The returned mapping contains no unpaired raw outcome summary. It carries
|
|
100
|
+
the requested value for each arm only so a caller can form arena-local,
|
|
101
|
+
same-checkpoint effects. An ``external_gate_receipt`` is validated when
|
|
102
|
+
present and returned as ``gate_receipt``; protocols such as unseen-suite
|
|
103
|
+
transfer may apply a different gate rule, whereas
|
|
104
|
+
:func:`analyze_stateful_arena_replication` requires it unconditionally.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
if not isinstance(manifest, ExperimentManifest):
|
|
108
|
+
raise TypeError("external arena record validation requires ExperimentManifest")
|
|
109
|
+
metric_name = _metric_name(metric)
|
|
110
|
+
if not isinstance(record, Mapping):
|
|
111
|
+
raise ArtifactError("external arena result record must be an object")
|
|
112
|
+
|
|
113
|
+
cell = _object(record.get("cell"), label="external arena result cell")
|
|
114
|
+
result = _object(record.get("result"), label="external arena result")
|
|
115
|
+
if result.get("status") != "succeeded":
|
|
116
|
+
raise ArtifactError("external arena receipt analysis requires a succeeded cell")
|
|
117
|
+
payload = _object(
|
|
118
|
+
result.get("payload"),
|
|
119
|
+
label="external arena result payload",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
suite_id = _text(cell.get("benchmark_id"), label="external arena cell benchmark_id")
|
|
123
|
+
split = _text(cell.get("split"), label="external arena cell split")
|
|
124
|
+
unit_id = _text(cell.get("unit_id"), label="external arena cell unit_id")
|
|
125
|
+
model_slot = _text(cell.get("model_slot"), label="external arena cell model_slot")
|
|
126
|
+
seed = _seed(cell.get("seed"), label="external arena cell seed")
|
|
127
|
+
target = _manifest_target(manifest, suite_id=suite_id, split=split)
|
|
128
|
+
if target.external is not True or target.source_revision is None:
|
|
129
|
+
raise ArtifactError("stateful arena analysis requires an external source-locked target")
|
|
130
|
+
if manifest.metadata.get("requires_atomic_external_replay") is not True:
|
|
131
|
+
raise ArtifactError("external arena manifest lacks requires_atomic_external_replay=true")
|
|
132
|
+
if model_slot not in manifest.model_slots or seed not in manifest.seeds:
|
|
133
|
+
raise ArtifactError("external arena cell model slot or seed is outside the manifest")
|
|
134
|
+
|
|
135
|
+
planned_arm_ids = _text_sequence(
|
|
136
|
+
cell.get("arm_ids"),
|
|
137
|
+
label="external arena cell arm_ids",
|
|
138
|
+
)
|
|
139
|
+
expected_arm_ids = target.arm_ids or tuple(arm.arm_id for arm in manifest.arms)
|
|
140
|
+
if planned_arm_ids != expected_arm_ids:
|
|
141
|
+
raise ArtifactError("external arena cell arms differ from the manifest target")
|
|
142
|
+
_require_payload_identity(
|
|
143
|
+
payload,
|
|
144
|
+
suite_id=suite_id,
|
|
145
|
+
split=split,
|
|
146
|
+
unit_id=unit_id,
|
|
147
|
+
model_slot=model_slot,
|
|
148
|
+
seed=seed,
|
|
149
|
+
planned_arm_ids=planned_arm_ids,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
baseline_arm_id = _text(
|
|
153
|
+
payload.get("baseline_arm_id"),
|
|
154
|
+
label="external arena baseline_arm_id",
|
|
155
|
+
)
|
|
156
|
+
arm_specs = {arm.arm_id: arm for arm in manifest.arms}
|
|
157
|
+
if baseline_arm_id not in planned_arm_ids:
|
|
158
|
+
raise ArtifactError("external arena baseline is outside the planned arm inventory")
|
|
159
|
+
baseline_spec = arm_specs.get(baseline_arm_id)
|
|
160
|
+
if baseline_spec is None or baseline_spec.family is not SemanticFamily.NO_OP:
|
|
161
|
+
raise ArtifactError("external arena baseline must be the manifest NO_OP arm")
|
|
162
|
+
|
|
163
|
+
source_revision = target.source_revision
|
|
164
|
+
checkpoint = _checkpoint_identity(
|
|
165
|
+
payload.get("checkpoint_identity"),
|
|
166
|
+
source_revision=source_revision,
|
|
167
|
+
)
|
|
168
|
+
if payload.get("target_source_revision") != source_revision:
|
|
169
|
+
raise ArtifactError("external arena target source receipt differs from the manifest")
|
|
170
|
+
if payload.get("atomic_replay_contract") != ATOMIC_EXTERNAL_PAIR_CONTRACT:
|
|
171
|
+
raise ArtifactError("external arena result lacks the atomic replay contract")
|
|
172
|
+
|
|
173
|
+
dependencies = _dependency_hashes(
|
|
174
|
+
payload.get("observed_dependency_hashes"),
|
|
175
|
+
label="external arena observed dependency receipt",
|
|
176
|
+
exact_slots=True,
|
|
177
|
+
)
|
|
178
|
+
_validate_not_applicable_slots(payload, dependencies=dependencies)
|
|
179
|
+
|
|
180
|
+
baseline_consistency = _sha256(
|
|
181
|
+
payload.get("baseline_consistency_sha256"),
|
|
182
|
+
label="external arena baseline consistency receipt",
|
|
183
|
+
)
|
|
184
|
+
raw_outcomes = _sequence(
|
|
185
|
+
payload.get("arms"),
|
|
186
|
+
label="external arena arm outcomes",
|
|
187
|
+
)
|
|
188
|
+
if len(raw_outcomes) != len(planned_arm_ids):
|
|
189
|
+
raise ArtifactError("external arena arm outcome inventory is partial")
|
|
190
|
+
outcomes: dict[str, Mapping[str, Any]] = {}
|
|
191
|
+
metric_schema: tuple[str, ...] | None = None
|
|
192
|
+
scorer_identity: tuple[str, str] | None = None
|
|
193
|
+
official_success_metric: str | None = None
|
|
194
|
+
outcome_contract_sha256: str | None = None
|
|
195
|
+
normalized_arms: dict[str, Mapping[str, Any]] = {}
|
|
196
|
+
for index, raw_outcome in enumerate(raw_outcomes):
|
|
197
|
+
outcome = _object(raw_outcome, label=f"external arena arm outcome {index}")
|
|
198
|
+
arm_id = _text(
|
|
199
|
+
outcome.get("arm_id"),
|
|
200
|
+
label=f"external arena arm outcome {index} arm_id",
|
|
201
|
+
)
|
|
202
|
+
if arm_id in outcomes:
|
|
203
|
+
raise ArtifactError("external arena result contains duplicate arm outcomes")
|
|
204
|
+
if arm_id != planned_arm_ids[index]:
|
|
205
|
+
raise ArtifactError("external arena arm outcomes differ from planned order")
|
|
206
|
+
spec = arm_specs.get(arm_id)
|
|
207
|
+
if spec is None:
|
|
208
|
+
raise ArtifactError("external arena result references an unknown manifest arm")
|
|
209
|
+
if outcome.get("status") != "succeeded":
|
|
210
|
+
raise ArtifactError("stateful arena analysis refuses failed or imputed arm outcomes")
|
|
211
|
+
if (
|
|
212
|
+
outcome.get("release_id") != spec.release_id
|
|
213
|
+
or outcome.get("family") != spec.family.value
|
|
214
|
+
or outcome.get("profile_kind") != spec.profile_kind.value
|
|
215
|
+
):
|
|
216
|
+
raise ArtifactError(f"external arena arm {arm_id!r} identity differs from the manifest")
|
|
217
|
+
metrics = _metrics(
|
|
218
|
+
outcome.get("metrics"),
|
|
219
|
+
label=f"external arena arm {arm_id!r} metrics",
|
|
220
|
+
)
|
|
221
|
+
current_schema = tuple(sorted(metrics))
|
|
222
|
+
if metric_schema is None:
|
|
223
|
+
metric_schema = current_schema
|
|
224
|
+
elif current_schema != metric_schema:
|
|
225
|
+
raise ArtifactError("external arena arms expose heterogeneous metric schemas")
|
|
226
|
+
if metric_name not in metrics:
|
|
227
|
+
raise ArtifactError(
|
|
228
|
+
f"external arena arm {arm_id!r} lacks requested metric {metric_name!r}"
|
|
229
|
+
)
|
|
230
|
+
_sha256(
|
|
231
|
+
outcome.get("payload_sha256"),
|
|
232
|
+
label=f"external arena arm {arm_id!r} payload receipt",
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
composition = _validate_valid_success_composition(
|
|
236
|
+
outcome,
|
|
237
|
+
metrics=metrics,
|
|
238
|
+
suite_id=suite_id,
|
|
239
|
+
arm_id=arm_id,
|
|
240
|
+
)
|
|
241
|
+
current_scorer = (
|
|
242
|
+
str(composition["scorer_id"]),
|
|
243
|
+
str(composition["scorer_revision"]),
|
|
244
|
+
)
|
|
245
|
+
current_official_metric = str(composition["official_success_metric"])
|
|
246
|
+
current_outcome_contract = str(composition["outcome_contract_sha256"])
|
|
247
|
+
if scorer_identity is None:
|
|
248
|
+
scorer_identity = current_scorer
|
|
249
|
+
official_success_metric = current_official_metric
|
|
250
|
+
outcome_contract_sha256 = current_outcome_contract
|
|
251
|
+
elif (
|
|
252
|
+
current_scorer != scorer_identity
|
|
253
|
+
or current_official_metric != official_success_metric
|
|
254
|
+
or current_outcome_contract != outcome_contract_sha256
|
|
255
|
+
):
|
|
256
|
+
raise ArtifactError(
|
|
257
|
+
"external arena official scorer, metric, or outcome contract "
|
|
258
|
+
"identity differs across arms"
|
|
259
|
+
)
|
|
260
|
+
outcomes[arm_id] = outcome
|
|
261
|
+
normalized_arms[arm_id] = {
|
|
262
|
+
"arm_id": arm_id,
|
|
263
|
+
"release_id": spec.release_id,
|
|
264
|
+
"family": spec.family.value,
|
|
265
|
+
"profile_kind": spec.profile_kind.value,
|
|
266
|
+
"arm_kind": spec.arm_kind.value,
|
|
267
|
+
"value": metrics[metric_name],
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
assert metric_schema is not None
|
|
271
|
+
assert scorer_identity is not None
|
|
272
|
+
assert official_success_metric is not None
|
|
273
|
+
assert outcome_contract_sha256 is not None
|
|
274
|
+
atomic_pairs = _validate_atomic_pairs(
|
|
275
|
+
payload,
|
|
276
|
+
outcomes=outcomes,
|
|
277
|
+
planned_arm_ids=planned_arm_ids,
|
|
278
|
+
baseline_arm_id=baseline_arm_id,
|
|
279
|
+
suite_id=suite_id,
|
|
280
|
+
unit_id=unit_id,
|
|
281
|
+
seed=seed,
|
|
282
|
+
source_revision=source_revision,
|
|
283
|
+
checkpoint=checkpoint,
|
|
284
|
+
dependencies=dependencies,
|
|
285
|
+
scorer_identity=scorer_identity,
|
|
286
|
+
baseline_consistency=baseline_consistency,
|
|
287
|
+
)
|
|
288
|
+
gate_receipt = _optional_gate_receipt(
|
|
289
|
+
payload.get("external_gate_receipt"),
|
|
290
|
+
manifest=manifest,
|
|
291
|
+
suite_id=suite_id,
|
|
292
|
+
split=split,
|
|
293
|
+
source_revision=source_revision,
|
|
294
|
+
scorer_identity=scorer_identity,
|
|
295
|
+
atomic_replay_contract=ATOMIC_EXTERNAL_PAIR_CONTRACT,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
source_receipt = {
|
|
299
|
+
"suite_id": suite_id,
|
|
300
|
+
"split": split,
|
|
301
|
+
"source_revision": source_revision,
|
|
302
|
+
"checkpoint_identity_sha256": checkpoint["identity_sha256"],
|
|
303
|
+
"origin_state_sha256": checkpoint["origin_state_sha256"],
|
|
304
|
+
"replay_identity_sha256": checkpoint["replay_identity_sha256"],
|
|
305
|
+
"atomic_pair_receipt_sha256s": tuple(pair["receipt_sha256"] for pair in atomic_pairs),
|
|
306
|
+
}
|
|
307
|
+
dependency_receipt = {
|
|
308
|
+
"model_slot": model_slot,
|
|
309
|
+
"locked_dependency_hashes": dependencies,
|
|
310
|
+
"atomic_pair_count": len(atomic_pairs),
|
|
311
|
+
}
|
|
312
|
+
balance_receipt = {
|
|
313
|
+
"baseline_arm_id": baseline_arm_id,
|
|
314
|
+
"planned_arm_ids": planned_arm_ids,
|
|
315
|
+
"complete_atomic_pair_inventory": True,
|
|
316
|
+
"baseline_consistency_sha256": baseline_consistency,
|
|
317
|
+
"metric_schema": metric_schema,
|
|
318
|
+
"dependency_receipt_sha256": content_hash(dependency_receipt),
|
|
319
|
+
}
|
|
320
|
+
observation_id = content_hash(
|
|
321
|
+
{
|
|
322
|
+
"suite_id": suite_id,
|
|
323
|
+
"split": split,
|
|
324
|
+
"unit_id": unit_id,
|
|
325
|
+
"model_slot": model_slot,
|
|
326
|
+
"seed": seed,
|
|
327
|
+
}
|
|
328
|
+
)
|
|
329
|
+
return {
|
|
330
|
+
"suite_id": suite_id,
|
|
331
|
+
"split": split,
|
|
332
|
+
"unit_id": unit_id,
|
|
333
|
+
"group_id": checkpoint["group_sha256"],
|
|
334
|
+
"observation_id": observation_id,
|
|
335
|
+
"model_slot": model_slot,
|
|
336
|
+
"seed": seed,
|
|
337
|
+
"metric": metric_name,
|
|
338
|
+
"metric_schema": metric_schema,
|
|
339
|
+
"official_success_metric": official_success_metric,
|
|
340
|
+
"outcome_contract_sha256": outcome_contract_sha256,
|
|
341
|
+
"source_revision": source_revision,
|
|
342
|
+
"scorer_identity": {
|
|
343
|
+
"scorer_id": scorer_identity[0],
|
|
344
|
+
"scorer_revision": scorer_identity[1],
|
|
345
|
+
},
|
|
346
|
+
"bridge_identity": {
|
|
347
|
+
"bridge_id": atomic_pairs[0]["bridge_id"],
|
|
348
|
+
"bridge_revision": atomic_pairs[0]["bridge_revision"],
|
|
349
|
+
},
|
|
350
|
+
"dependency_identity": dependencies,
|
|
351
|
+
"baseline_arm_id": baseline_arm_id,
|
|
352
|
+
"baseline_identity": {
|
|
353
|
+
key: normalized_arms[baseline_arm_id][key]
|
|
354
|
+
for key in ("release_id", "family", "profile_kind", "arm_kind")
|
|
355
|
+
},
|
|
356
|
+
"arms": normalized_arms,
|
|
357
|
+
"gate_receipt": gate_receipt,
|
|
358
|
+
"source_receipt": {
|
|
359
|
+
**source_receipt,
|
|
360
|
+
"receipt_sha256": content_hash(source_receipt),
|
|
361
|
+
},
|
|
362
|
+
"dependency_receipt": {
|
|
363
|
+
**dependency_receipt,
|
|
364
|
+
"receipt_sha256": content_hash(dependency_receipt),
|
|
365
|
+
},
|
|
366
|
+
"balance_receipt": {
|
|
367
|
+
**balance_receipt,
|
|
368
|
+
"receipt_sha256": content_hash(balance_receipt),
|
|
369
|
+
},
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def analyze_stateful_arena_replication(
|
|
374
|
+
run_dirs: Sequence[str | Path],
|
|
375
|
+
*,
|
|
376
|
+
metric: str = _VALID_SUCCESS_METRIC,
|
|
377
|
+
bootstrap_samples: int = 2_000,
|
|
378
|
+
random_seed: int = 2027,
|
|
379
|
+
pool_raw_metrics: bool = False,
|
|
380
|
+
) -> Mapping[str, Any]:
|
|
381
|
+
"""Analyze one or more verified, gate-approved external arena runs.
|
|
382
|
+
|
|
383
|
+
Every arena retains its own estimand. ``pool_raw_metrics`` exists only as
|
|
384
|
+
an explicit fail-closed API boundary: any value other than ``False`` is
|
|
385
|
+
rejected because official reward, task completion, similarity, and task
|
|
386
|
+
success are not a common raw scale.
|
|
387
|
+
"""
|
|
388
|
+
|
|
389
|
+
metric_name = _metric_name(metric)
|
|
390
|
+
if pool_raw_metrics is not False:
|
|
391
|
+
raise ValidationError("stateful arena analysis forbids pooling heterogeneous raw metrics")
|
|
392
|
+
_resample_arguments(
|
|
393
|
+
bootstrap_samples=bootstrap_samples,
|
|
394
|
+
random_seed=random_seed,
|
|
395
|
+
)
|
|
396
|
+
roots = _run_roots(run_dirs)
|
|
397
|
+
runs = tuple(_load_verified_stateful_run(root) for root in roots)
|
|
398
|
+
|
|
399
|
+
rows_by_suite: dict[str, list[Mapping[str, Any]]] = defaultdict(list)
|
|
400
|
+
run_receipts_by_suite: dict[str, list[Mapping[str, Any]]] = defaultdict(list)
|
|
401
|
+
observed_ids: set[str] = set()
|
|
402
|
+
for run in runs:
|
|
403
|
+
_validate_stateful_manifest(run.manifest)
|
|
404
|
+
normalized = tuple(
|
|
405
|
+
validate_external_arena_record_receipts(
|
|
406
|
+
run.manifest,
|
|
407
|
+
record,
|
|
408
|
+
metric=metric_name,
|
|
409
|
+
)
|
|
410
|
+
for record in run.records
|
|
411
|
+
)
|
|
412
|
+
if not normalized:
|
|
413
|
+
raise ArtifactError("verified stateful arena run contains no result records")
|
|
414
|
+
_validate_run_fingerprint(run, normalized)
|
|
415
|
+
for row in normalized:
|
|
416
|
+
if row["gate_receipt"] is None:
|
|
417
|
+
raise ArtifactError(
|
|
418
|
+
"stateful arena replication requires an approved external gate receipt"
|
|
419
|
+
)
|
|
420
|
+
observation_id = str(row["observation_id"])
|
|
421
|
+
if observation_id in observed_ids:
|
|
422
|
+
raise ArtifactError(
|
|
423
|
+
"stateful arena runs contain a duplicate checkpoint/model/seed observation"
|
|
424
|
+
)
|
|
425
|
+
observed_ids.add(observation_id)
|
|
426
|
+
suite_id = str(row["suite_id"])
|
|
427
|
+
rows_by_suite[suite_id].append(row)
|
|
428
|
+
for suite_id in sorted({str(row["suite_id"]) for row in normalized}):
|
|
429
|
+
run_receipts_by_suite[suite_id].append(
|
|
430
|
+
{
|
|
431
|
+
"run_dir": str(run.root),
|
|
432
|
+
"study_id": run.manifest.study_id,
|
|
433
|
+
"manifest_hash": run.verification["manifest_hash"],
|
|
434
|
+
"plan_hash": run.verification["plan_hash"],
|
|
435
|
+
"driver_hash": run.verification["driver_hash"],
|
|
436
|
+
"comparison_profile_hash": run.verification["comparison_profile_hash"],
|
|
437
|
+
}
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
rng = random.Random(random_seed)
|
|
441
|
+
suites: dict[str, Mapping[str, Any]] = {}
|
|
442
|
+
for suite_id, rows in sorted(rows_by_suite.items()):
|
|
443
|
+
suites[suite_id] = _summarize_suite(
|
|
444
|
+
suite_id,
|
|
445
|
+
rows,
|
|
446
|
+
run_receipts=run_receipts_by_suite[suite_id],
|
|
447
|
+
bootstrap_samples=bootstrap_samples,
|
|
448
|
+
rng=rng,
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
return {
|
|
452
|
+
"schema_version": 1,
|
|
453
|
+
"artifact": STATEFUL_ARENA_REPLICATION_ARTIFACT,
|
|
454
|
+
"analysis": "stateful_arena_replication",
|
|
455
|
+
"analysis_protocol": STATEFUL_ARENA_REPLICATION_PROTOCOL,
|
|
456
|
+
"metric": metric_name,
|
|
457
|
+
"run_count": len(runs),
|
|
458
|
+
"suite_count": len(suites),
|
|
459
|
+
"estimand": (
|
|
460
|
+
"arena-local protected-checkpoint-group mean paired difference "
|
|
461
|
+
"relative to same-origin NO_OP"
|
|
462
|
+
),
|
|
463
|
+
"analysis_unit": (
|
|
464
|
+
"protected checkpoint group; unit variants, model slots, and seeds "
|
|
465
|
+
"are repeated observations collapsed within group"
|
|
466
|
+
),
|
|
467
|
+
"receipt_policy": {
|
|
468
|
+
"complete_verified_runs_required": True,
|
|
469
|
+
"approved_gate_required": True,
|
|
470
|
+
"atomic_replay_required": True,
|
|
471
|
+
"official_scorer_receipt_required": True,
|
|
472
|
+
"source_receipt_required": True,
|
|
473
|
+
"dependency_balance_required": True,
|
|
474
|
+
},
|
|
475
|
+
"suites": suites,
|
|
476
|
+
"cross_suite_robustness": _cross_suite_robustness(suites),
|
|
477
|
+
"raw_metric_pooling_performed": False,
|
|
478
|
+
"bootstrap_samples": bootstrap_samples,
|
|
479
|
+
"random_seed": random_seed,
|
|
480
|
+
"experiments_executed_by_analysis": False,
|
|
481
|
+
"paper_claim_authorized": False,
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def _load_verified_stateful_run(root: Path) -> _VerifiedStatefulRun:
|
|
486
|
+
verification = verify_experiment_run_directory(root)
|
|
487
|
+
if verification["is_shard"]:
|
|
488
|
+
raise ArtifactError(
|
|
489
|
+
"stateful arena analysis refuses a shard without an exact verified merge"
|
|
490
|
+
)
|
|
491
|
+
if not verification["complete"] or not verification["all_succeeded"]:
|
|
492
|
+
raise ArtifactError(
|
|
493
|
+
"stateful arena analysis requires one complete all-succeeded verified run"
|
|
494
|
+
)
|
|
495
|
+
manifest = _load_manifest(root)
|
|
496
|
+
records = load_verified_cell_results(root)
|
|
497
|
+
identity = _read_json_object(
|
|
498
|
+
root / "run_identity.json",
|
|
499
|
+
label="stateful arena run identity",
|
|
500
|
+
)
|
|
501
|
+
fingerprint = identity.get("driver_fingerprint")
|
|
502
|
+
if not isinstance(fingerprint, Mapping):
|
|
503
|
+
raise ArtifactError("stateful arena run lacks a driver fingerprint")
|
|
504
|
+
return _VerifiedStatefulRun(
|
|
505
|
+
root=root,
|
|
506
|
+
manifest=manifest,
|
|
507
|
+
records=records,
|
|
508
|
+
verification=verification,
|
|
509
|
+
driver_fingerprint=fingerprint,
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def _validate_stateful_manifest(manifest: ExperimentManifest) -> None:
|
|
514
|
+
if manifest.stage not in _ALLOWED_STAGES:
|
|
515
|
+
raise ArtifactError(
|
|
516
|
+
"stateful arena replication requires a confirmatory, selector-confirmation, "
|
|
517
|
+
"or replication manifest"
|
|
518
|
+
)
|
|
519
|
+
if manifest.driver_entrypoint != _EXTERNAL_ARENA_DRIVER:
|
|
520
|
+
raise ArtifactError("stateful arena replication requires the external atomic-arena driver")
|
|
521
|
+
if manifest.metadata.get("requires_atomic_external_replay") is not True:
|
|
522
|
+
raise ArtifactError(
|
|
523
|
+
"stateful arena replication manifest lacks the atomic replay requirement"
|
|
524
|
+
)
|
|
525
|
+
unsupported = sorted(
|
|
526
|
+
{
|
|
527
|
+
target.cell_id
|
|
528
|
+
for target in manifest.benchmarks
|
|
529
|
+
if target.cell_id not in _SUPPORTED_STATEFUL_SUITES
|
|
530
|
+
}
|
|
531
|
+
)
|
|
532
|
+
if unsupported:
|
|
533
|
+
raise ArtifactError(
|
|
534
|
+
"stateful arena replication contains unsupported suites: " + ", ".join(unsupported)
|
|
535
|
+
)
|
|
536
|
+
if any(not target.external or target.source_revision is None for target in manifest.benchmarks):
|
|
537
|
+
raise ArtifactError("stateful arena replication targets must be external and source locked")
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def _validate_run_fingerprint(
|
|
541
|
+
run: _VerifiedStatefulRun,
|
|
542
|
+
rows: Sequence[Mapping[str, Any]],
|
|
543
|
+
) -> None:
|
|
544
|
+
fingerprint = run.driver_fingerprint
|
|
545
|
+
if (
|
|
546
|
+
fingerprint.get("implementation_id") != _EXTERNAL_ARENA_IMPLEMENTATION
|
|
547
|
+
or fingerprint.get("schema_version") != 3
|
|
548
|
+
):
|
|
549
|
+
raise ArtifactError(
|
|
550
|
+
"stateful arena run was not produced by the locked atomic multi-arm driver"
|
|
551
|
+
)
|
|
552
|
+
targets = _object(
|
|
553
|
+
fingerprint.get("benchmark_targets"),
|
|
554
|
+
label="stateful arena driver benchmark targets",
|
|
555
|
+
)
|
|
556
|
+
arms = _object(
|
|
557
|
+
fingerprint.get("arms"),
|
|
558
|
+
label="stateful arena driver arm bindings",
|
|
559
|
+
)
|
|
560
|
+
gates = _object(
|
|
561
|
+
fingerprint.get("resolved_gate_bindings"),
|
|
562
|
+
label="stateful arena driver gate bindings",
|
|
563
|
+
)
|
|
564
|
+
baseline_arm_id = _text(
|
|
565
|
+
fingerprint.get("baseline_arm_id"),
|
|
566
|
+
label="stateful arena driver baseline_arm_id",
|
|
567
|
+
)
|
|
568
|
+
manifest_arms = {arm.arm_id: arm for arm in run.manifest.arms}
|
|
569
|
+
for arm_id, spec in manifest_arms.items():
|
|
570
|
+
binding = _object(
|
|
571
|
+
arms.get(arm_id),
|
|
572
|
+
label=f"stateful arena driver arm binding {arm_id!r}",
|
|
573
|
+
)
|
|
574
|
+
if (
|
|
575
|
+
binding.get("release_id") != spec.release_id
|
|
576
|
+
or binding.get("family") != spec.family.value
|
|
577
|
+
or binding.get("profile_kind") != spec.profile_kind.value
|
|
578
|
+
or binding.get("arm_kind") != spec.arm_kind.value
|
|
579
|
+
):
|
|
580
|
+
raise ArtifactError(
|
|
581
|
+
f"stateful arena driver binding for arm {arm_id!r} has mixed identity"
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
for row in rows:
|
|
585
|
+
if row["baseline_arm_id"] != baseline_arm_id:
|
|
586
|
+
raise ArtifactError(
|
|
587
|
+
"stateful arena result baseline differs from the driver fingerprint"
|
|
588
|
+
)
|
|
589
|
+
key = f"{row['suite_id']}:{row['split']}"
|
|
590
|
+
target = _object(
|
|
591
|
+
targets.get(key),
|
|
592
|
+
label=f"stateful arena driver target {key!r}",
|
|
593
|
+
)
|
|
594
|
+
bridge_lock = _object(
|
|
595
|
+
target.get("bridge_lock"),
|
|
596
|
+
label=f"stateful arena driver bridge lock {key!r}",
|
|
597
|
+
)
|
|
598
|
+
outcome_contract = _object(
|
|
599
|
+
target.get("outcome_contract"),
|
|
600
|
+
label=f"stateful arena driver outcome contract {key!r}",
|
|
601
|
+
)
|
|
602
|
+
expected_scorer = {
|
|
603
|
+
"scorer_id": bridge_lock.get("scorer_id"),
|
|
604
|
+
"scorer_revision": bridge_lock.get("scorer_revision"),
|
|
605
|
+
}
|
|
606
|
+
expected_bridge = {
|
|
607
|
+
"bridge_id": bridge_lock.get("bridge_id"),
|
|
608
|
+
"bridge_revision": bridge_lock.get("bridge_revision"),
|
|
609
|
+
}
|
|
610
|
+
dependency_by_model = _object(
|
|
611
|
+
bridge_lock.get("dependency_hashes_by_model_slot"),
|
|
612
|
+
label=f"stateful arena driver dependency locks {key!r}",
|
|
613
|
+
)
|
|
614
|
+
expected_dependencies = _dependency_hashes(
|
|
615
|
+
dependency_by_model.get(row["model_slot"]),
|
|
616
|
+
label=(f"stateful arena driver dependencies for model {row['model_slot']!r}"),
|
|
617
|
+
exact_slots=True,
|
|
618
|
+
)
|
|
619
|
+
if (
|
|
620
|
+
target.get("benchmark_id") != row["suite_id"]
|
|
621
|
+
or target.get("split") != row["split"]
|
|
622
|
+
or target.get("source_revision") != row["source_revision"]
|
|
623
|
+
or expected_scorer != row["scorer_identity"]
|
|
624
|
+
or expected_bridge != row["bridge_identity"]
|
|
625
|
+
or expected_dependencies != row["dependency_identity"]
|
|
626
|
+
or outcome_contract.get("formula_id") != _VALID_SUCCESS_FORMULA
|
|
627
|
+
or outcome_contract.get("official_success_metric") != row["official_success_metric"]
|
|
628
|
+
or content_hash(outcome_contract) != row["outcome_contract_sha256"]
|
|
629
|
+
):
|
|
630
|
+
raise ArtifactError(
|
|
631
|
+
"stateful arena result identity differs from its driver fingerprint"
|
|
632
|
+
)
|
|
633
|
+
gate = row["gate_receipt"]
|
|
634
|
+
resolved_gate = gates.get(key)
|
|
635
|
+
if (
|
|
636
|
+
gate is None
|
|
637
|
+
or not isinstance(resolved_gate, Mapping)
|
|
638
|
+
or dict(gate) != dict(resolved_gate)
|
|
639
|
+
):
|
|
640
|
+
raise ArtifactError(
|
|
641
|
+
"stateful arena gate receipt differs from the resolved driver binding"
|
|
642
|
+
)
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
def _summarize_suite(
|
|
646
|
+
suite_id: str,
|
|
647
|
+
rows: Sequence[Mapping[str, Any]],
|
|
648
|
+
*,
|
|
649
|
+
run_receipts: Sequence[Mapping[str, Any]],
|
|
650
|
+
bootstrap_samples: int,
|
|
651
|
+
rng: random.Random,
|
|
652
|
+
) -> Mapping[str, Any]:
|
|
653
|
+
reference = rows[0]
|
|
654
|
+
reference_fields = (
|
|
655
|
+
"split",
|
|
656
|
+
"source_revision",
|
|
657
|
+
"metric",
|
|
658
|
+
"metric_schema",
|
|
659
|
+
"official_success_metric",
|
|
660
|
+
"outcome_contract_sha256",
|
|
661
|
+
"scorer_identity",
|
|
662
|
+
"bridge_identity",
|
|
663
|
+
"baseline_identity",
|
|
664
|
+
)
|
|
665
|
+
for row in rows[1:]:
|
|
666
|
+
for field in reference_fields:
|
|
667
|
+
if row[field] != reference[field]:
|
|
668
|
+
raise ArtifactError(f"stateful suite {suite_id!r} mixes {field} identities")
|
|
669
|
+
|
|
670
|
+
dependencies_by_model: dict[str, Mapping[str, str]] = {}
|
|
671
|
+
arm_identity_by_release: dict[str, Mapping[str, Any]] = {}
|
|
672
|
+
arm_ids_by_release: dict[str, set[str]] = defaultdict(set)
|
|
673
|
+
effects_by_release_group: dict[str, dict[str, list[float]]] = defaultdict(
|
|
674
|
+
lambda: defaultdict(list)
|
|
675
|
+
)
|
|
676
|
+
effects_by_release_model_group: dict[str, dict[str, dict[str, list[float]]]] = defaultdict(
|
|
677
|
+
lambda: defaultdict(lambda: defaultdict(list))
|
|
678
|
+
)
|
|
679
|
+
replicate_design_by_release_group: dict[str, dict[str, set[tuple[str, int]]]] = defaultdict(
|
|
680
|
+
lambda: defaultdict(set)
|
|
681
|
+
)
|
|
682
|
+
source_receipt_hashes: set[str] = set()
|
|
683
|
+
dependency_receipt_hashes: set[str] = set()
|
|
684
|
+
balance_receipt_hashes: set[str] = set()
|
|
685
|
+
gate_receipt_hashes: set[str] = set()
|
|
686
|
+
|
|
687
|
+
for row in rows:
|
|
688
|
+
model_slot = str(row["model_slot"])
|
|
689
|
+
dependencies = row["dependency_identity"]
|
|
690
|
+
prior_dependencies = dependencies_by_model.get(model_slot)
|
|
691
|
+
if prior_dependencies is None:
|
|
692
|
+
dependencies_by_model[model_slot] = dependencies
|
|
693
|
+
elif prior_dependencies != dependencies:
|
|
694
|
+
raise ArtifactError(
|
|
695
|
+
f"stateful suite {suite_id!r} dependency identity drifts within "
|
|
696
|
+
f"model slot {model_slot!r}"
|
|
697
|
+
)
|
|
698
|
+
baseline = float(row["arms"][row["baseline_arm_id"]]["value"])
|
|
699
|
+
for arm_id, arm in row["arms"].items():
|
|
700
|
+
if arm_id == row["baseline_arm_id"]:
|
|
701
|
+
continue
|
|
702
|
+
release_id = str(arm["release_id"])
|
|
703
|
+
identity = {
|
|
704
|
+
key: arm[key] for key in ("release_id", "family", "profile_kind", "arm_kind")
|
|
705
|
+
}
|
|
706
|
+
prior_identity = arm_identity_by_release.get(release_id)
|
|
707
|
+
if prior_identity is None:
|
|
708
|
+
arm_identity_by_release[release_id] = identity
|
|
709
|
+
elif prior_identity != identity:
|
|
710
|
+
raise ArtifactError(
|
|
711
|
+
f"stateful suite {suite_id!r} mixes treatment identity "
|
|
712
|
+
f"for release {release_id!r}"
|
|
713
|
+
)
|
|
714
|
+
arm_ids_by_release[release_id].add(str(arm_id))
|
|
715
|
+
effect = float(arm["value"]) - baseline
|
|
716
|
+
group_id = str(row["group_id"])
|
|
717
|
+
effects_by_release_group[release_id][group_id].append(effect)
|
|
718
|
+
effects_by_release_model_group[release_id][model_slot][group_id].append(effect)
|
|
719
|
+
replicate_design_by_release_group[release_id][group_id].add(
|
|
720
|
+
(model_slot, int(row["seed"]))
|
|
721
|
+
)
|
|
722
|
+
source_receipt_hashes.add(str(row["source_receipt"]["receipt_sha256"]))
|
|
723
|
+
dependency_receipt_hashes.add(str(row["dependency_receipt"]["receipt_sha256"]))
|
|
724
|
+
balance_receipt_hashes.add(str(row["balance_receipt"]["receipt_sha256"]))
|
|
725
|
+
gate_receipt_hashes.add(str(row["gate_receipt"]["receipt_sha256"]))
|
|
726
|
+
|
|
727
|
+
_validate_cross_model_dependency_balance(
|
|
728
|
+
suite_id,
|
|
729
|
+
dependencies_by_model=dependencies_by_model,
|
|
730
|
+
)
|
|
731
|
+
treatments: dict[str, Mapping[str, Any]] = {}
|
|
732
|
+
for release_id in sorted(effects_by_release_group):
|
|
733
|
+
by_group = effects_by_release_group[release_id]
|
|
734
|
+
replicate_designs = {
|
|
735
|
+
tuple(sorted(design))
|
|
736
|
+
for design in replicate_design_by_release_group[release_id].values()
|
|
737
|
+
}
|
|
738
|
+
if len(replicate_designs) != 1:
|
|
739
|
+
raise ArtifactError(
|
|
740
|
+
f"stateful suite {suite_id!r} treatment {release_id!r} has "
|
|
741
|
+
"unbalanced model/seed coverage across checkpoint groups"
|
|
742
|
+
)
|
|
743
|
+
cluster_effects = tuple(fmean(values) for _group_id, values in sorted(by_group.items()))
|
|
744
|
+
if len(cluster_effects) < 2:
|
|
745
|
+
raise ArtifactError(
|
|
746
|
+
f"stateful suite {suite_id!r} treatment {release_id!r} "
|
|
747
|
+
"requires at least two independent checkpoint groups"
|
|
748
|
+
)
|
|
749
|
+
win_rate = sum(value > 0 for value in cluster_effects) / len(cluster_effects)
|
|
750
|
+
tie_rate = sum(value == 0 for value in cluster_effects) / len(cluster_effects)
|
|
751
|
+
harm_rate = sum(value < 0 for value in cluster_effects) / len(cluster_effects)
|
|
752
|
+
model_strata: dict[str, Mapping[str, Any]] = {}
|
|
753
|
+
for model_slot, group_values in sorted(effects_by_release_model_group[release_id].items()):
|
|
754
|
+
collapsed = tuple(fmean(values) for _group_id, values in sorted(group_values.items()))
|
|
755
|
+
model_strata[model_slot] = {
|
|
756
|
+
"checkpoint_group_count": len(collapsed),
|
|
757
|
+
"mean_effect_vs_no_op": fmean(collapsed),
|
|
758
|
+
"win_rate_vs_no_op": (sum(value > 0 for value in collapsed) / len(collapsed)),
|
|
759
|
+
"harm_rate_vs_no_op": (sum(value < 0 for value in collapsed) / len(collapsed)),
|
|
760
|
+
}
|
|
761
|
+
treatments[release_id] = {
|
|
762
|
+
**arm_identity_by_release[release_id],
|
|
763
|
+
"arm_ids": sorted(arm_ids_by_release[release_id]),
|
|
764
|
+
"checkpoint_group_count": len(cluster_effects),
|
|
765
|
+
"paired_record_count": sum(len(values) for values in by_group.values()),
|
|
766
|
+
"model_seed_replicate_design": [
|
|
767
|
+
{"model_slot": model_slot, "seed": seed}
|
|
768
|
+
for model_slot, seed in next(iter(replicate_designs))
|
|
769
|
+
],
|
|
770
|
+
"mean_effect_vs_no_op": fmean(cluster_effects),
|
|
771
|
+
"median_effect_vs_no_op": median(cluster_effects),
|
|
772
|
+
"cluster_bootstrap_95ci": list(
|
|
773
|
+
_bootstrap_mean_interval(
|
|
774
|
+
cluster_effects,
|
|
775
|
+
samples=bootstrap_samples,
|
|
776
|
+
rng=rng,
|
|
777
|
+
)
|
|
778
|
+
),
|
|
779
|
+
"win_rate_vs_no_op": win_rate,
|
|
780
|
+
"tie_rate_vs_no_op": tie_rate,
|
|
781
|
+
"harm_rate_vs_no_op": harm_rate,
|
|
782
|
+
"normalized_win_minus_harm": win_rate - harm_rate,
|
|
783
|
+
"model_strata": model_strata,
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
group_ids = {str(row["group_id"]) for row in rows}
|
|
787
|
+
return {
|
|
788
|
+
"suite_id": suite_id,
|
|
789
|
+
"split": reference["split"],
|
|
790
|
+
"source_revision": reference["source_revision"],
|
|
791
|
+
"metric": reference["metric"],
|
|
792
|
+
"metric_schema": list(reference["metric_schema"]),
|
|
793
|
+
"official_success_metric": reference["official_success_metric"],
|
|
794
|
+
"outcome_contract_sha256": reference["outcome_contract_sha256"],
|
|
795
|
+
"scorer_identity": reference["scorer_identity"],
|
|
796
|
+
"bridge_identity": reference["bridge_identity"],
|
|
797
|
+
"baseline_identity": reference["baseline_identity"],
|
|
798
|
+
"run_count": len(run_receipts),
|
|
799
|
+
"record_count": len(rows),
|
|
800
|
+
"checkpoint_group_count": len(group_ids),
|
|
801
|
+
"run_receipts": list(run_receipts),
|
|
802
|
+
"gate_receipts": {
|
|
803
|
+
"count": len(gate_receipt_hashes),
|
|
804
|
+
"receipt_sha256s": sorted(gate_receipt_hashes),
|
|
805
|
+
"approved": True,
|
|
806
|
+
},
|
|
807
|
+
"source_receipts": {
|
|
808
|
+
"count": len(source_receipt_hashes),
|
|
809
|
+
"receipt_sha256s": sorted(source_receipt_hashes),
|
|
810
|
+
"verified": True,
|
|
811
|
+
},
|
|
812
|
+
"dependency_receipts": {
|
|
813
|
+
"count": len(dependency_receipt_hashes),
|
|
814
|
+
"receipt_sha256s": sorted(dependency_receipt_hashes),
|
|
815
|
+
"by_model_slot": {
|
|
816
|
+
model_slot: dict(dependencies)
|
|
817
|
+
for model_slot, dependencies in sorted(dependencies_by_model.items())
|
|
818
|
+
},
|
|
819
|
+
"balanced": True,
|
|
820
|
+
},
|
|
821
|
+
"balance_receipts": {
|
|
822
|
+
"count": len(balance_receipt_hashes),
|
|
823
|
+
"receipt_sha256s": sorted(balance_receipt_hashes),
|
|
824
|
+
"atomic_pair_inventory_complete": True,
|
|
825
|
+
"same_origin_no_op_verified": True,
|
|
826
|
+
"model_seed_replicate_balance_verified": True,
|
|
827
|
+
},
|
|
828
|
+
"treatments": treatments,
|
|
829
|
+
"raw_metric_pooled_across_suites": False,
|
|
830
|
+
"estimand_scope": "this arena and split only",
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
def _cross_suite_robustness(
|
|
835
|
+
suites: Mapping[str, Mapping[str, Any]],
|
|
836
|
+
) -> Mapping[str, Any]:
|
|
837
|
+
scorer_identities = {
|
|
838
|
+
(
|
|
839
|
+
str(suite["scorer_identity"]["scorer_id"]),
|
|
840
|
+
str(suite["scorer_identity"]["scorer_revision"]),
|
|
841
|
+
)
|
|
842
|
+
for suite in suites.values()
|
|
843
|
+
}
|
|
844
|
+
schemas = {tuple(str(item) for item in suite["metric_schema"]) for suite in suites.values()}
|
|
845
|
+
identities: dict[str, Mapping[str, Any]] = {}
|
|
846
|
+
normalized: dict[str, dict[str, float]] = defaultdict(dict)
|
|
847
|
+
directions: dict[str, dict[str, str]] = defaultdict(dict)
|
|
848
|
+
for suite_id, suite in suites.items():
|
|
849
|
+
for release_id, treatment in suite["treatments"].items():
|
|
850
|
+
identity = {
|
|
851
|
+
key: treatment[key] for key in ("release_id", "family", "profile_kind", "arm_kind")
|
|
852
|
+
}
|
|
853
|
+
prior = identities.get(release_id)
|
|
854
|
+
if prior is None:
|
|
855
|
+
identities[release_id] = identity
|
|
856
|
+
elif prior != identity:
|
|
857
|
+
raise ArtifactError(
|
|
858
|
+
f"cross-suite release {release_id!r} has mixed treatment identity"
|
|
859
|
+
)
|
|
860
|
+
normalized[release_id][suite_id] = float(treatment["normalized_win_minus_harm"])
|
|
861
|
+
effect = float(treatment["mean_effect_vs_no_op"])
|
|
862
|
+
directions[release_id][suite_id] = (
|
|
863
|
+
"positive" if effect > 0 else "negative" if effect < 0 else "tie"
|
|
864
|
+
)
|
|
865
|
+
|
|
866
|
+
by_treatment: dict[str, Mapping[str, Any]] = {}
|
|
867
|
+
for release_id in sorted(normalized):
|
|
868
|
+
values = tuple(normalized[release_id].values())
|
|
869
|
+
direction_values = tuple(directions[release_id].values())
|
|
870
|
+
by_treatment[release_id] = {
|
|
871
|
+
**identities[release_id],
|
|
872
|
+
"suite_count": len(values),
|
|
873
|
+
"direction_counts": {
|
|
874
|
+
direction: sum(value == direction for value in direction_values)
|
|
875
|
+
for direction in ("positive", "tie", "negative")
|
|
876
|
+
},
|
|
877
|
+
"direction_by_suite": dict(sorted(directions[release_id].items())),
|
|
878
|
+
"normalized_win_minus_harm_by_suite": dict(sorted(normalized[release_id].items())),
|
|
879
|
+
"normalized_heterogeneity_range": (
|
|
880
|
+
max(values) - min(values) if len(values) > 1 else None
|
|
881
|
+
),
|
|
882
|
+
"direction_consistent_across_suites": (
|
|
883
|
+
len({value for value in direction_values if value != "tie"}) <= 1
|
|
884
|
+
),
|
|
885
|
+
}
|
|
886
|
+
return {
|
|
887
|
+
"summary_scope": (
|
|
888
|
+
"arena-level direction and win-minus-harm diagnostics only; "
|
|
889
|
+
"no official raw metric is pooled"
|
|
890
|
+
),
|
|
891
|
+
"suite_estimands_retained": True,
|
|
892
|
+
"raw_metric_pooling_performed": False,
|
|
893
|
+
"metric_schemas_identical": len(schemas) == 1,
|
|
894
|
+
"official_scorer_identities_identical": len(scorer_identities) == 1,
|
|
895
|
+
"incompatible_scorers_or_schemas_are_stratified": True,
|
|
896
|
+
"by_treatment": by_treatment,
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
def _validate_atomic_pairs(
|
|
901
|
+
payload: Mapping[str, Any],
|
|
902
|
+
*,
|
|
903
|
+
outcomes: Mapping[str, Mapping[str, Any]],
|
|
904
|
+
planned_arm_ids: tuple[str, ...],
|
|
905
|
+
baseline_arm_id: str,
|
|
906
|
+
suite_id: str,
|
|
907
|
+
unit_id: str,
|
|
908
|
+
seed: int,
|
|
909
|
+
source_revision: str,
|
|
910
|
+
checkpoint: Mapping[str, str],
|
|
911
|
+
dependencies: Mapping[str, str],
|
|
912
|
+
scorer_identity: tuple[str, str],
|
|
913
|
+
baseline_consistency: str,
|
|
914
|
+
) -> tuple[Mapping[str, Any], ...]:
|
|
915
|
+
raw_pairs = _sequence(
|
|
916
|
+
payload.get("atomic_pairs"),
|
|
917
|
+
label="external arena atomic pair receipts",
|
|
918
|
+
)
|
|
919
|
+
expected_treatments = tuple(arm_id for arm_id in planned_arm_ids if arm_id != baseline_arm_id)
|
|
920
|
+
if len(raw_pairs) != len(expected_treatments):
|
|
921
|
+
raise ArtifactError("external arena atomic pair receipt inventory is partial")
|
|
922
|
+
|
|
923
|
+
parsed: list[Mapping[str, Any]] = []
|
|
924
|
+
runtime_instances: set[str] = set()
|
|
925
|
+
receipt_hashes: list[str] = []
|
|
926
|
+
baseline_outcome = outcomes[baseline_arm_id]
|
|
927
|
+
baseline_metrics = _metrics(
|
|
928
|
+
baseline_outcome.get("metrics"),
|
|
929
|
+
label="external arena NO_OP metrics",
|
|
930
|
+
)
|
|
931
|
+
raw_metric_schema = tuple(
|
|
932
|
+
key for key in sorted(baseline_metrics) if key != _VALID_SUCCESS_METRIC
|
|
933
|
+
)
|
|
934
|
+
for index, raw_pair in enumerate(raw_pairs):
|
|
935
|
+
pair = _strict_object(
|
|
936
|
+
raw_pair,
|
|
937
|
+
label=f"external arena atomic pair {index}",
|
|
938
|
+
keys={
|
|
939
|
+
"treatment_arm_id",
|
|
940
|
+
"request_sha256",
|
|
941
|
+
"receipt",
|
|
942
|
+
"receipt_sha256",
|
|
943
|
+
"baseline_consistency_sha256",
|
|
944
|
+
"metric_deltas",
|
|
945
|
+
"resource_receipt_sha256s",
|
|
946
|
+
},
|
|
947
|
+
)
|
|
948
|
+
treatment_id = _text(
|
|
949
|
+
pair["treatment_arm_id"],
|
|
950
|
+
label=f"external arena atomic pair {index} treatment_arm_id",
|
|
951
|
+
)
|
|
952
|
+
if treatment_id != expected_treatments[index]:
|
|
953
|
+
raise ArtifactError(
|
|
954
|
+
"external arena atomic pairs differ from the planned treatment order"
|
|
955
|
+
)
|
|
956
|
+
_sha256(
|
|
957
|
+
pair["request_sha256"],
|
|
958
|
+
label=f"external arena atomic pair {index} request receipt",
|
|
959
|
+
)
|
|
960
|
+
receipt = _atomic_replay_receipt(
|
|
961
|
+
pair["receipt"],
|
|
962
|
+
label=f"external arena atomic pair {index} receipt",
|
|
963
|
+
)
|
|
964
|
+
receipt_sha256 = _sha256(
|
|
965
|
+
pair["receipt_sha256"],
|
|
966
|
+
label=f"external arena atomic pair {index} receipt_sha256",
|
|
967
|
+
)
|
|
968
|
+
if content_hash(pair["receipt"]) != receipt_sha256:
|
|
969
|
+
raise ArtifactError(f"external arena atomic pair {index} receipt checksum mismatch")
|
|
970
|
+
if pair["baseline_consistency_sha256"] != baseline_consistency:
|
|
971
|
+
raise ArtifactError("external arena atomic pair baseline consistency receipt changed")
|
|
972
|
+
if (
|
|
973
|
+
receipt.benchmark_id != suite_id
|
|
974
|
+
or receipt.checkpoint_id != unit_id
|
|
975
|
+
or receipt.source_revision != source_revision
|
|
976
|
+
or receipt.origin_state_hash != checkpoint["origin_state_sha256"]
|
|
977
|
+
or receipt.replay_identity != checkpoint["replay_identity_sha256"]
|
|
978
|
+
or receipt.seed != seed
|
|
979
|
+
or dict(receipt.locked_dependency_hashes) != dict(dependencies)
|
|
980
|
+
or (receipt.scorer_id, receipt.scorer_revision) != scorer_identity
|
|
981
|
+
):
|
|
982
|
+
raise ArtifactError(
|
|
983
|
+
"external arena atomic receipt source, replay, dependency, or scorer "
|
|
984
|
+
"identity differs from the result"
|
|
985
|
+
)
|
|
986
|
+
by_role = {arm.role: arm for arm in receipt.arms}
|
|
987
|
+
if (
|
|
988
|
+
by_role["baseline"].arm_id != baseline_arm_id
|
|
989
|
+
or by_role["treatment"].arm_id != treatment_id
|
|
990
|
+
):
|
|
991
|
+
raise ArtifactError("external arena atomic receipt arms differ from the plan")
|
|
992
|
+
for arm_receipt in receipt.arms:
|
|
993
|
+
if arm_receipt.runtime_instance_id in runtime_instances:
|
|
994
|
+
raise ArtifactError("external arena atomic pairs reuse a runtime instance identity")
|
|
995
|
+
runtime_instances.add(arm_receipt.runtime_instance_id)
|
|
996
|
+
|
|
997
|
+
deltas = _metrics(
|
|
998
|
+
pair["metric_deltas"],
|
|
999
|
+
label=f"external arena atomic pair {index} metric deltas",
|
|
1000
|
+
)
|
|
1001
|
+
if tuple(sorted(deltas)) != raw_metric_schema:
|
|
1002
|
+
raise ArtifactError(
|
|
1003
|
+
"external arena atomic delta schema differs from official arm metrics"
|
|
1004
|
+
)
|
|
1005
|
+
treatment_metrics = _metrics(
|
|
1006
|
+
outcomes[treatment_id].get("metrics"),
|
|
1007
|
+
label=f"external arena treatment {treatment_id!r} metrics",
|
|
1008
|
+
)
|
|
1009
|
+
for name, delta in deltas.items():
|
|
1010
|
+
if not math.isclose(
|
|
1011
|
+
delta,
|
|
1012
|
+
treatment_metrics[name] - baseline_metrics[name],
|
|
1013
|
+
rel_tol=0.0,
|
|
1014
|
+
abs_tol=1e-12,
|
|
1015
|
+
):
|
|
1016
|
+
raise ArtifactError(f"external arena atomic metric delta {name!r} is inconsistent")
|
|
1017
|
+
resource_hashes = _text_sequence(
|
|
1018
|
+
pair["resource_receipt_sha256s"],
|
|
1019
|
+
label=f"external arena atomic pair {index} resource receipt hashes",
|
|
1020
|
+
allow_empty=True,
|
|
1021
|
+
)
|
|
1022
|
+
for digest in resource_hashes:
|
|
1023
|
+
_sha256(
|
|
1024
|
+
digest,
|
|
1025
|
+
label=f"external arena atomic pair {index} resource receipt",
|
|
1026
|
+
)
|
|
1027
|
+
expected_consistency = _baseline_consistency_hash(
|
|
1028
|
+
baseline_outcome,
|
|
1029
|
+
receipt=receipt,
|
|
1030
|
+
)
|
|
1031
|
+
if expected_consistency != baseline_consistency:
|
|
1032
|
+
raise ArtifactError(
|
|
1033
|
+
"external arena NO_OP metrics, trace, usage, or logical receipt "
|
|
1034
|
+
"differs from its baseline consistency hash"
|
|
1035
|
+
)
|
|
1036
|
+
receipt_hashes.append(receipt_sha256)
|
|
1037
|
+
parsed.append(
|
|
1038
|
+
{
|
|
1039
|
+
"treatment_arm_id": treatment_id,
|
|
1040
|
+
"receipt_sha256": receipt_sha256,
|
|
1041
|
+
"bridge_id": receipt.bridge_id,
|
|
1042
|
+
"bridge_revision": receipt.bridge_revision,
|
|
1043
|
+
}
|
|
1044
|
+
)
|
|
1045
|
+
|
|
1046
|
+
baseline_hashes = _text_sequence(
|
|
1047
|
+
outcomes[baseline_arm_id].get("atomic_pair_receipt_sha256s"),
|
|
1048
|
+
label="external arena NO_OP atomic receipt inventory",
|
|
1049
|
+
)
|
|
1050
|
+
if baseline_hashes != tuple(receipt_hashes):
|
|
1051
|
+
raise ArtifactError("external arena NO_OP receipt inventory differs from atomic pairs")
|
|
1052
|
+
for treatment_id, receipt_sha256 in zip(
|
|
1053
|
+
expected_treatments,
|
|
1054
|
+
receipt_hashes,
|
|
1055
|
+
strict=True,
|
|
1056
|
+
):
|
|
1057
|
+
if outcomes[treatment_id].get("atomic_pair_receipt_sha256") != receipt_sha256:
|
|
1058
|
+
raise ArtifactError(
|
|
1059
|
+
f"external arena treatment {treatment_id!r} receipt binding differs "
|
|
1060
|
+
"from its atomic pair"
|
|
1061
|
+
)
|
|
1062
|
+
bridge_identities = {(str(item["bridge_id"]), str(item["bridge_revision"])) for item in parsed}
|
|
1063
|
+
if len(bridge_identities) != 1:
|
|
1064
|
+
raise ArtifactError("external arena bridge identity changes across atomic pairs")
|
|
1065
|
+
return tuple(parsed)
|
|
1066
|
+
|
|
1067
|
+
|
|
1068
|
+
def _validate_valid_success_composition(
|
|
1069
|
+
outcome: Mapping[str, Any],
|
|
1070
|
+
*,
|
|
1071
|
+
metrics: Mapping[str, float],
|
|
1072
|
+
suite_id: str,
|
|
1073
|
+
arm_id: str,
|
|
1074
|
+
) -> Mapping[str, Any]:
|
|
1075
|
+
composition = _strict_object(
|
|
1076
|
+
outcome.get("valid_success_composition"),
|
|
1077
|
+
label=f"external arena arm {arm_id!r} valid-success composition",
|
|
1078
|
+
keys={
|
|
1079
|
+
"schema_version",
|
|
1080
|
+
"formula_id",
|
|
1081
|
+
"benchmark_id",
|
|
1082
|
+
"arm_id",
|
|
1083
|
+
"official_success_metric",
|
|
1084
|
+
"components",
|
|
1085
|
+
"valid_success",
|
|
1086
|
+
"outcome_contract_sha256",
|
|
1087
|
+
"scorer_id",
|
|
1088
|
+
"scorer_revision",
|
|
1089
|
+
"trace_hash",
|
|
1090
|
+
"composition_receipt_sha256",
|
|
1091
|
+
},
|
|
1092
|
+
)
|
|
1093
|
+
if (
|
|
1094
|
+
composition["schema_version"] != 1
|
|
1095
|
+
or composition["formula_id"] != _VALID_SUCCESS_FORMULA
|
|
1096
|
+
or composition["benchmark_id"] != suite_id
|
|
1097
|
+
or composition["arm_id"] != arm_id
|
|
1098
|
+
):
|
|
1099
|
+
raise ArtifactError(
|
|
1100
|
+
f"external arena arm {arm_id!r} valid-success receipt identity is invalid"
|
|
1101
|
+
)
|
|
1102
|
+
official_metric = _text(
|
|
1103
|
+
composition["official_success_metric"],
|
|
1104
|
+
label=f"external arena arm {arm_id!r} official metric",
|
|
1105
|
+
)
|
|
1106
|
+
components = _strict_object(
|
|
1107
|
+
composition["components"],
|
|
1108
|
+
label=f"external arena arm {arm_id!r} valid-success components",
|
|
1109
|
+
keys={
|
|
1110
|
+
"official_success",
|
|
1111
|
+
"contract_preserved",
|
|
1112
|
+
"privileged_leakage",
|
|
1113
|
+
},
|
|
1114
|
+
)
|
|
1115
|
+
official = _unit_interval(
|
|
1116
|
+
components["official_success"],
|
|
1117
|
+
label=f"external arena arm {arm_id!r} official success",
|
|
1118
|
+
)
|
|
1119
|
+
preserved = _unit_interval(
|
|
1120
|
+
components["contract_preserved"],
|
|
1121
|
+
label=f"external arena arm {arm_id!r} contract preservation",
|
|
1122
|
+
)
|
|
1123
|
+
leakage = _unit_interval(
|
|
1124
|
+
components["privileged_leakage"],
|
|
1125
|
+
label=f"external arena arm {arm_id!r} privileged leakage",
|
|
1126
|
+
)
|
|
1127
|
+
valid_success = _unit_interval(
|
|
1128
|
+
composition["valid_success"],
|
|
1129
|
+
label=f"external arena arm {arm_id!r} valid success",
|
|
1130
|
+
)
|
|
1131
|
+
if (
|
|
1132
|
+
official_metric not in metrics
|
|
1133
|
+
or not math.isclose(metrics[official_metric], official, rel_tol=0.0, abs_tol=1e-12)
|
|
1134
|
+
or not math.isclose(
|
|
1135
|
+
metrics.get("contract_preserved", math.nan),
|
|
1136
|
+
preserved,
|
|
1137
|
+
rel_tol=0.0,
|
|
1138
|
+
abs_tol=1e-12,
|
|
1139
|
+
)
|
|
1140
|
+
or not math.isclose(
|
|
1141
|
+
metrics.get("privileged_leakage", math.nan),
|
|
1142
|
+
leakage,
|
|
1143
|
+
rel_tol=0.0,
|
|
1144
|
+
abs_tol=1e-12,
|
|
1145
|
+
)
|
|
1146
|
+
or not math.isclose(
|
|
1147
|
+
metrics.get(_VALID_SUCCESS_METRIC, math.nan),
|
|
1148
|
+
valid_success,
|
|
1149
|
+
rel_tol=0.0,
|
|
1150
|
+
abs_tol=1e-12,
|
|
1151
|
+
)
|
|
1152
|
+
or not math.isclose(
|
|
1153
|
+
official * preserved * (1.0 - leakage),
|
|
1154
|
+
valid_success,
|
|
1155
|
+
rel_tol=0.0,
|
|
1156
|
+
abs_tol=1e-12,
|
|
1157
|
+
)
|
|
1158
|
+
):
|
|
1159
|
+
raise ArtifactError(
|
|
1160
|
+
f"external arena arm {arm_id!r} valid-success composition is inconsistent"
|
|
1161
|
+
)
|
|
1162
|
+
_sha256(
|
|
1163
|
+
composition["outcome_contract_sha256"],
|
|
1164
|
+
label=f"external arena arm {arm_id!r} outcome contract receipt",
|
|
1165
|
+
)
|
|
1166
|
+
_text(
|
|
1167
|
+
composition["scorer_id"],
|
|
1168
|
+
label=f"external arena arm {arm_id!r} scorer_id",
|
|
1169
|
+
)
|
|
1170
|
+
_sha256(
|
|
1171
|
+
composition["scorer_revision"],
|
|
1172
|
+
label=f"external arena arm {arm_id!r} scorer_revision",
|
|
1173
|
+
)
|
|
1174
|
+
trace_hash = _sha256(
|
|
1175
|
+
composition["trace_hash"],
|
|
1176
|
+
label=f"external arena arm {arm_id!r} trace hash",
|
|
1177
|
+
)
|
|
1178
|
+
if outcome.get("trace_hash") != trace_hash:
|
|
1179
|
+
raise ArtifactError(f"external arena arm {arm_id!r} trace differs from its scorer receipt")
|
|
1180
|
+
usage = _object(
|
|
1181
|
+
outcome.get("usage"),
|
|
1182
|
+
label=f"external arena arm {arm_id!r} usage",
|
|
1183
|
+
)
|
|
1184
|
+
for name, value in usage.items():
|
|
1185
|
+
if (
|
|
1186
|
+
not isinstance(name, str)
|
|
1187
|
+
or not name
|
|
1188
|
+
or isinstance(value, bool)
|
|
1189
|
+
or not isinstance(value, int | float)
|
|
1190
|
+
or not math.isfinite(float(value))
|
|
1191
|
+
or float(value) < 0
|
|
1192
|
+
):
|
|
1193
|
+
raise ArtifactError(f"external arena arm {arm_id!r} usage receipt is invalid")
|
|
1194
|
+
expected_hash = _sha256(
|
|
1195
|
+
composition["composition_receipt_sha256"],
|
|
1196
|
+
label=f"external arena arm {arm_id!r} composition receipt",
|
|
1197
|
+
)
|
|
1198
|
+
body = {key: value for key, value in composition.items() if key != "composition_receipt_sha256"}
|
|
1199
|
+
if content_hash(body) != expected_hash:
|
|
1200
|
+
raise ArtifactError(f"external arena arm {arm_id!r} composition receipt checksum mismatch")
|
|
1201
|
+
return composition
|
|
1202
|
+
|
|
1203
|
+
|
|
1204
|
+
def _optional_gate_receipt(
|
|
1205
|
+
value: Any,
|
|
1206
|
+
*,
|
|
1207
|
+
manifest: ExperimentManifest,
|
|
1208
|
+
suite_id: str,
|
|
1209
|
+
split: str,
|
|
1210
|
+
source_revision: str,
|
|
1211
|
+
scorer_identity: tuple[str, str],
|
|
1212
|
+
atomic_replay_contract: str,
|
|
1213
|
+
) -> Mapping[str, Any] | None:
|
|
1214
|
+
if value is None:
|
|
1215
|
+
return None
|
|
1216
|
+
receipt = _strict_object(
|
|
1217
|
+
value,
|
|
1218
|
+
label="external arena gate receipt",
|
|
1219
|
+
keys={
|
|
1220
|
+
"file_sha256",
|
|
1221
|
+
"receipt_sha256",
|
|
1222
|
+
"scope_sha256",
|
|
1223
|
+
"benchmark_id",
|
|
1224
|
+
"split",
|
|
1225
|
+
"source_revision",
|
|
1226
|
+
"manifest_hash",
|
|
1227
|
+
"driver_config_sha256",
|
|
1228
|
+
"bridge",
|
|
1229
|
+
"scorer",
|
|
1230
|
+
"approval",
|
|
1231
|
+
"verification",
|
|
1232
|
+
"unit_inventory_sha256",
|
|
1233
|
+
},
|
|
1234
|
+
)
|
|
1235
|
+
for name in (
|
|
1236
|
+
"file_sha256",
|
|
1237
|
+
"receipt_sha256",
|
|
1238
|
+
"scope_sha256",
|
|
1239
|
+
"manifest_hash",
|
|
1240
|
+
"driver_config_sha256",
|
|
1241
|
+
"unit_inventory_sha256",
|
|
1242
|
+
):
|
|
1243
|
+
_sha256(receipt[name], label=f"external arena gate {name}")
|
|
1244
|
+
if (
|
|
1245
|
+
receipt["benchmark_id"] != suite_id
|
|
1246
|
+
or receipt["split"] != split
|
|
1247
|
+
or receipt["source_revision"] != source_revision
|
|
1248
|
+
or receipt["manifest_hash"] != manifest.manifest_hash
|
|
1249
|
+
):
|
|
1250
|
+
raise ArtifactError("external arena gate receipt is bound to another source or manifest")
|
|
1251
|
+
bridge = _strict_object(
|
|
1252
|
+
receipt["bridge"],
|
|
1253
|
+
label="external arena gate bridge",
|
|
1254
|
+
keys={"bridge_id", "bridge_revision"},
|
|
1255
|
+
)
|
|
1256
|
+
_text(bridge["bridge_id"], label="external arena gate bridge_id")
|
|
1257
|
+
_sha256(bridge["bridge_revision"], label="external arena gate bridge_revision")
|
|
1258
|
+
scorer = _strict_object(
|
|
1259
|
+
receipt["scorer"],
|
|
1260
|
+
label="external arena gate scorer",
|
|
1261
|
+
keys={"scorer_id", "scorer_revision"},
|
|
1262
|
+
)
|
|
1263
|
+
if (
|
|
1264
|
+
scorer.get("scorer_id"),
|
|
1265
|
+
scorer.get("scorer_revision"),
|
|
1266
|
+
) != scorer_identity:
|
|
1267
|
+
raise ArtifactError(
|
|
1268
|
+
"external arena gate official scorer differs from arm composition receipts"
|
|
1269
|
+
)
|
|
1270
|
+
_text(scorer["scorer_id"], label="external arena gate scorer_id")
|
|
1271
|
+
_sha256(scorer["scorer_revision"], label="external arena gate scorer_revision")
|
|
1272
|
+
verification = _strict_object(
|
|
1273
|
+
receipt["verification"],
|
|
1274
|
+
label="external arena gate verification",
|
|
1275
|
+
keys={
|
|
1276
|
+
"atomic_replay_contract",
|
|
1277
|
+
"reset_mode",
|
|
1278
|
+
"reset_verified",
|
|
1279
|
+
"reset_receipt_sha256",
|
|
1280
|
+
"no_op_parity_verified",
|
|
1281
|
+
"no_op_parity_receipt_sha256",
|
|
1282
|
+
},
|
|
1283
|
+
)
|
|
1284
|
+
if (
|
|
1285
|
+
verification["atomic_replay_contract"] != atomic_replay_contract
|
|
1286
|
+
or verification["reset_verified"] is not True
|
|
1287
|
+
or verification["no_op_parity_verified"] is not True
|
|
1288
|
+
):
|
|
1289
|
+
raise ArtifactError("external arena gate does not approve atomic reset and NO_OP parity")
|
|
1290
|
+
_text(verification["reset_mode"], label="external arena gate reset_mode")
|
|
1291
|
+
_sha256(
|
|
1292
|
+
verification["reset_receipt_sha256"],
|
|
1293
|
+
label="external arena gate reset receipt",
|
|
1294
|
+
)
|
|
1295
|
+
_sha256(
|
|
1296
|
+
verification["no_op_parity_receipt_sha256"],
|
|
1297
|
+
label="external arena gate NO_OP parity receipt",
|
|
1298
|
+
)
|
|
1299
|
+
approval = _strict_object(
|
|
1300
|
+
receipt["approval"],
|
|
1301
|
+
label="external arena gate approval",
|
|
1302
|
+
keys={
|
|
1303
|
+
"approved",
|
|
1304
|
+
"approver_id",
|
|
1305
|
+
"approval_revision",
|
|
1306
|
+
"approved_at",
|
|
1307
|
+
"scope_sha256",
|
|
1308
|
+
},
|
|
1309
|
+
)
|
|
1310
|
+
if approval["approved"] is not True or approval["scope_sha256"] != receipt["scope_sha256"]:
|
|
1311
|
+
raise ArtifactError("external arena gate receipt is not approved for this scope")
|
|
1312
|
+
_text(approval["approver_id"], label="external arena gate approver_id")
|
|
1313
|
+
_sha256(
|
|
1314
|
+
approval["approval_revision"],
|
|
1315
|
+
label="external arena gate approval revision",
|
|
1316
|
+
)
|
|
1317
|
+
_text(approval["approved_at"], label="external arena gate approval timestamp")
|
|
1318
|
+
return receipt
|
|
1319
|
+
|
|
1320
|
+
|
|
1321
|
+
def _checkpoint_identity(value: Any, *, source_revision: str) -> Mapping[str, str]:
|
|
1322
|
+
checkpoint = _strict_object(
|
|
1323
|
+
value,
|
|
1324
|
+
label="external arena checkpoint identity",
|
|
1325
|
+
keys={
|
|
1326
|
+
"identity_sha256",
|
|
1327
|
+
"group_sha256",
|
|
1328
|
+
"visible_state_sha256",
|
|
1329
|
+
"origin_state_sha256",
|
|
1330
|
+
"replay_identity_sha256",
|
|
1331
|
+
"source_revision",
|
|
1332
|
+
},
|
|
1333
|
+
)
|
|
1334
|
+
if checkpoint["source_revision"] != source_revision:
|
|
1335
|
+
raise ArtifactError("external arena checkpoint source differs from the manifest")
|
|
1336
|
+
for name in (
|
|
1337
|
+
"identity_sha256",
|
|
1338
|
+
"visible_state_sha256",
|
|
1339
|
+
"origin_state_sha256",
|
|
1340
|
+
"replay_identity_sha256",
|
|
1341
|
+
):
|
|
1342
|
+
_sha256(checkpoint[name], label=f"external arena checkpoint {name}")
|
|
1343
|
+
group_id = checkpoint["group_sha256"]
|
|
1344
|
+
if (
|
|
1345
|
+
not isinstance(group_id, str)
|
|
1346
|
+
or not group_id.startswith("group_")
|
|
1347
|
+
or len(group_id) != 70
|
|
1348
|
+
or not _is_sha256(group_id[6:])
|
|
1349
|
+
):
|
|
1350
|
+
raise ArtifactError("external arena checkpoint group receipt must be group_<sha256>")
|
|
1351
|
+
return checkpoint
|
|
1352
|
+
|
|
1353
|
+
|
|
1354
|
+
def _atomic_replay_receipt(value: Any, *, label: str) -> AtomicReplayReceipt:
|
|
1355
|
+
raw = _strict_object(
|
|
1356
|
+
value,
|
|
1357
|
+
label=label,
|
|
1358
|
+
keys={
|
|
1359
|
+
"benchmark_id",
|
|
1360
|
+
"checkpoint_id",
|
|
1361
|
+
"source_revision",
|
|
1362
|
+
"bridge_id",
|
|
1363
|
+
"bridge_revision",
|
|
1364
|
+
"scorer_id",
|
|
1365
|
+
"scorer_revision",
|
|
1366
|
+
"origin_state_hash",
|
|
1367
|
+
"replay_identity",
|
|
1368
|
+
"seed",
|
|
1369
|
+
"locked_dependency_hashes",
|
|
1370
|
+
"arms",
|
|
1371
|
+
},
|
|
1372
|
+
)
|
|
1373
|
+
raw_arms = _sequence(raw["arms"], label=f"{label} arms")
|
|
1374
|
+
if len(raw_arms) != 2:
|
|
1375
|
+
raise ArtifactError(f"{label} requires exactly two arm receipts")
|
|
1376
|
+
arms: list[AtomicReplayArmReceipt] = []
|
|
1377
|
+
for index, value_arm in enumerate(raw_arms):
|
|
1378
|
+
arm = _strict_object(
|
|
1379
|
+
value_arm,
|
|
1380
|
+
label=f"{label} arm {index}",
|
|
1381
|
+
keys={
|
|
1382
|
+
"role",
|
|
1383
|
+
"arm_id",
|
|
1384
|
+
"origin_state_hash",
|
|
1385
|
+
"replay_identity",
|
|
1386
|
+
"seed",
|
|
1387
|
+
"locked_dependency_hashes",
|
|
1388
|
+
"runtime_instance_id",
|
|
1389
|
+
"completed",
|
|
1390
|
+
},
|
|
1391
|
+
)
|
|
1392
|
+
try:
|
|
1393
|
+
arms.append(
|
|
1394
|
+
AtomicReplayArmReceipt(
|
|
1395
|
+
role=arm["role"],
|
|
1396
|
+
arm_id=arm["arm_id"],
|
|
1397
|
+
origin_state_hash=arm["origin_state_hash"],
|
|
1398
|
+
replay_identity=arm["replay_identity"],
|
|
1399
|
+
seed=arm["seed"],
|
|
1400
|
+
locked_dependency_hashes=arm["locked_dependency_hashes"],
|
|
1401
|
+
runtime_instance_id=arm["runtime_instance_id"],
|
|
1402
|
+
completed=arm["completed"],
|
|
1403
|
+
)
|
|
1404
|
+
)
|
|
1405
|
+
except (TypeError, ValueError) as exc:
|
|
1406
|
+
raise ArtifactError(f"{label} arm {index} is invalid: {exc}") from exc
|
|
1407
|
+
try:
|
|
1408
|
+
return AtomicReplayReceipt(
|
|
1409
|
+
benchmark_id=raw["benchmark_id"],
|
|
1410
|
+
checkpoint_id=raw["checkpoint_id"],
|
|
1411
|
+
source_revision=raw["source_revision"],
|
|
1412
|
+
bridge_id=raw["bridge_id"],
|
|
1413
|
+
bridge_revision=raw["bridge_revision"],
|
|
1414
|
+
scorer_id=raw["scorer_id"],
|
|
1415
|
+
scorer_revision=raw["scorer_revision"],
|
|
1416
|
+
origin_state_hash=raw["origin_state_hash"],
|
|
1417
|
+
replay_identity=raw["replay_identity"],
|
|
1418
|
+
seed=raw["seed"],
|
|
1419
|
+
locked_dependency_hashes=raw["locked_dependency_hashes"],
|
|
1420
|
+
arms=tuple(arms),
|
|
1421
|
+
)
|
|
1422
|
+
except (TypeError, ValueError) as exc:
|
|
1423
|
+
raise ArtifactError(f"{label} is invalid: {exc}") from exc
|
|
1424
|
+
|
|
1425
|
+
|
|
1426
|
+
def _baseline_consistency_hash(
|
|
1427
|
+
baseline_outcome: Mapping[str, Any],
|
|
1428
|
+
*,
|
|
1429
|
+
receipt: AtomicReplayReceipt,
|
|
1430
|
+
) -> str:
|
|
1431
|
+
metrics = {
|
|
1432
|
+
key: value
|
|
1433
|
+
for key, value in _metrics(
|
|
1434
|
+
baseline_outcome.get("metrics"),
|
|
1435
|
+
label="external arena NO_OP metrics",
|
|
1436
|
+
).items()
|
|
1437
|
+
if key != _VALID_SUCCESS_METRIC
|
|
1438
|
+
}
|
|
1439
|
+
baseline_receipts = tuple(arm for arm in receipt.arms if arm.role == "baseline")
|
|
1440
|
+
if len(baseline_receipts) != 1:
|
|
1441
|
+
raise ArtifactError("external arena atomic pair lacks one NO_OP receipt")
|
|
1442
|
+
arm = baseline_receipts[0]
|
|
1443
|
+
# ``ResourceUsage.as_dict()`` adds the derived ``total_cost_usd`` property,
|
|
1444
|
+
# while the driver's logical baseline hash is computed from the dataclass
|
|
1445
|
+
# fields. Remove only that deterministic projection before recomputing the
|
|
1446
|
+
# receipt; every primitive usage field remains covered.
|
|
1447
|
+
usage = dict(
|
|
1448
|
+
_object(
|
|
1449
|
+
baseline_outcome.get("usage"),
|
|
1450
|
+
label="external arena NO_OP usage",
|
|
1451
|
+
)
|
|
1452
|
+
)
|
|
1453
|
+
usage.pop("total_cost_usd", None)
|
|
1454
|
+
return content_hash(
|
|
1455
|
+
{
|
|
1456
|
+
"metrics": metrics,
|
|
1457
|
+
"trace_hash": baseline_outcome.get("trace_hash"),
|
|
1458
|
+
"usage": usage,
|
|
1459
|
+
"receipt": {
|
|
1460
|
+
"benchmark_id": receipt.benchmark_id,
|
|
1461
|
+
"checkpoint_id": receipt.checkpoint_id,
|
|
1462
|
+
"source_revision": receipt.source_revision,
|
|
1463
|
+
"bridge_id": receipt.bridge_id,
|
|
1464
|
+
"bridge_revision": receipt.bridge_revision,
|
|
1465
|
+
"scorer_id": receipt.scorer_id,
|
|
1466
|
+
"scorer_revision": receipt.scorer_revision,
|
|
1467
|
+
"origin_state_hash": receipt.origin_state_hash,
|
|
1468
|
+
"replay_identity": receipt.replay_identity,
|
|
1469
|
+
"seed": receipt.seed,
|
|
1470
|
+
"locked_dependency_hashes": receipt.locked_dependency_hashes,
|
|
1471
|
+
"baseline_arm": {
|
|
1472
|
+
"role": arm.role,
|
|
1473
|
+
"arm_id": arm.arm_id,
|
|
1474
|
+
"origin_state_hash": arm.origin_state_hash,
|
|
1475
|
+
"replay_identity": arm.replay_identity,
|
|
1476
|
+
"seed": arm.seed,
|
|
1477
|
+
"locked_dependency_hashes": arm.locked_dependency_hashes,
|
|
1478
|
+
"completed": arm.completed,
|
|
1479
|
+
},
|
|
1480
|
+
},
|
|
1481
|
+
}
|
|
1482
|
+
)
|
|
1483
|
+
|
|
1484
|
+
|
|
1485
|
+
def _validate_cross_model_dependency_balance(
|
|
1486
|
+
suite_id: str,
|
|
1487
|
+
*,
|
|
1488
|
+
dependencies_by_model: Mapping[str, Mapping[str, str]],
|
|
1489
|
+
) -> None:
|
|
1490
|
+
if not dependencies_by_model:
|
|
1491
|
+
raise ArtifactError(f"stateful suite {suite_id!r} has no dependency receipts")
|
|
1492
|
+
for slot in sorted(_NON_MODEL_DEPENDENCY_SLOTS):
|
|
1493
|
+
values = {str(dependencies[slot]) for dependencies in dependencies_by_model.values()}
|
|
1494
|
+
if len(values) != 1:
|
|
1495
|
+
raise ArtifactError(f"stateful suite {suite_id!r} mixes non-model dependency {slot!r}")
|
|
1496
|
+
|
|
1497
|
+
|
|
1498
|
+
def _require_payload_identity(
|
|
1499
|
+
payload: Mapping[str, Any],
|
|
1500
|
+
*,
|
|
1501
|
+
suite_id: str,
|
|
1502
|
+
split: str,
|
|
1503
|
+
unit_id: str,
|
|
1504
|
+
model_slot: str,
|
|
1505
|
+
seed: int,
|
|
1506
|
+
planned_arm_ids: tuple[str, ...],
|
|
1507
|
+
) -> None:
|
|
1508
|
+
expected = {
|
|
1509
|
+
"benchmark_id": suite_id,
|
|
1510
|
+
"split": split,
|
|
1511
|
+
"unit_id": unit_id,
|
|
1512
|
+
"model_slot": model_slot,
|
|
1513
|
+
"seed": seed,
|
|
1514
|
+
}
|
|
1515
|
+
for name, value in expected.items():
|
|
1516
|
+
if payload.get(name) != value:
|
|
1517
|
+
raise ArtifactError(f"external arena payload {name} differs from its frozen cell")
|
|
1518
|
+
if (
|
|
1519
|
+
_text_sequence(
|
|
1520
|
+
payload.get("planned_arm_ids"),
|
|
1521
|
+
label="external arena payload planned_arm_ids",
|
|
1522
|
+
)
|
|
1523
|
+
!= planned_arm_ids
|
|
1524
|
+
):
|
|
1525
|
+
raise ArtifactError("external arena payload arm inventory differs from its cell")
|
|
1526
|
+
|
|
1527
|
+
|
|
1528
|
+
def _validate_not_applicable_slots(
|
|
1529
|
+
payload: Mapping[str, Any],
|
|
1530
|
+
*,
|
|
1531
|
+
dependencies: Mapping[str, str],
|
|
1532
|
+
) -> None:
|
|
1533
|
+
slots = _text_sequence(
|
|
1534
|
+
payload.get("not_applicable_dependency_slots"),
|
|
1535
|
+
label="external arena not-applicable dependency slots",
|
|
1536
|
+
allow_empty=True,
|
|
1537
|
+
)
|
|
1538
|
+
if tuple(sorted(slots)) != slots or len(slots) != len(set(slots)):
|
|
1539
|
+
raise ArtifactError(
|
|
1540
|
+
"external arena not-applicable dependency slots must be sorted and unique"
|
|
1541
|
+
)
|
|
1542
|
+
for slot in slots:
|
|
1543
|
+
if slot not in _DEPENDENCY_SLOTS:
|
|
1544
|
+
raise ArtifactError(
|
|
1545
|
+
"external arena not-applicable dependency receipt contains an unknown slot"
|
|
1546
|
+
)
|
|
1547
|
+
expected = content_hash(
|
|
1548
|
+
{
|
|
1549
|
+
"agentinputlab_dependency": "not_applicable",
|
|
1550
|
+
"schema_version": 1,
|
|
1551
|
+
"slot": slot,
|
|
1552
|
+
}
|
|
1553
|
+
)
|
|
1554
|
+
if dependencies[slot] != expected:
|
|
1555
|
+
raise ArtifactError(
|
|
1556
|
+
f"external arena not-applicable dependency {slot!r} has another hash"
|
|
1557
|
+
)
|
|
1558
|
+
|
|
1559
|
+
|
|
1560
|
+
def _manifest_target(
|
|
1561
|
+
manifest: ExperimentManifest,
|
|
1562
|
+
*,
|
|
1563
|
+
suite_id: str,
|
|
1564
|
+
split: str,
|
|
1565
|
+
) -> Any:
|
|
1566
|
+
targets = tuple(
|
|
1567
|
+
target
|
|
1568
|
+
for target in manifest.benchmarks
|
|
1569
|
+
if target.cell_id == suite_id and target.split == split
|
|
1570
|
+
)
|
|
1571
|
+
if len(targets) != 1:
|
|
1572
|
+
raise ArtifactError("external arena result has no unique source-locked manifest target")
|
|
1573
|
+
return targets[0]
|
|
1574
|
+
|
|
1575
|
+
|
|
1576
|
+
def _dependency_hashes(
|
|
1577
|
+
value: Any,
|
|
1578
|
+
*,
|
|
1579
|
+
label: str,
|
|
1580
|
+
exact_slots: bool,
|
|
1581
|
+
) -> Mapping[str, str]:
|
|
1582
|
+
dependencies = _object(value, label=label)
|
|
1583
|
+
if exact_slots and set(dependencies) != _DEPENDENCY_SLOTS:
|
|
1584
|
+
raise ArtifactError(f"{label} must contain the exact shared dependency slots")
|
|
1585
|
+
parsed: dict[str, str] = {}
|
|
1586
|
+
for name, digest in dependencies.items():
|
|
1587
|
+
if not isinstance(name, str) or not name:
|
|
1588
|
+
raise ArtifactError(f"{label} contains an invalid slot")
|
|
1589
|
+
parsed[name] = _sha256(digest, label=f"{label} slot {name!r}")
|
|
1590
|
+
if not parsed:
|
|
1591
|
+
raise ArtifactError(f"{label} must not be empty")
|
|
1592
|
+
return parsed
|
|
1593
|
+
|
|
1594
|
+
|
|
1595
|
+
def _metrics(value: Any, *, label: str) -> Mapping[str, float]:
|
|
1596
|
+
raw = _object(value, label=label)
|
|
1597
|
+
if not raw:
|
|
1598
|
+
raise ArtifactError(f"{label} must not be empty")
|
|
1599
|
+
metrics: dict[str, float] = {}
|
|
1600
|
+
for name, value_item in raw.items():
|
|
1601
|
+
if not isinstance(name, str) or not name:
|
|
1602
|
+
raise ArtifactError(f"{label} contains an invalid metric name")
|
|
1603
|
+
if (
|
|
1604
|
+
isinstance(value_item, bool)
|
|
1605
|
+
or not isinstance(value_item, int | float)
|
|
1606
|
+
or not math.isfinite(float(value_item))
|
|
1607
|
+
):
|
|
1608
|
+
raise ArtifactError(f"{label} metric {name!r} must be finite numeric")
|
|
1609
|
+
metrics[name] = float(value_item)
|
|
1610
|
+
return metrics
|
|
1611
|
+
|
|
1612
|
+
|
|
1613
|
+
def _bootstrap_mean_interval(
|
|
1614
|
+
values: Sequence[float],
|
|
1615
|
+
*,
|
|
1616
|
+
samples: int,
|
|
1617
|
+
rng: random.Random,
|
|
1618
|
+
) -> tuple[float, float]:
|
|
1619
|
+
estimates = [fmean(values[rng.randrange(len(values))] for _ in values) for _ in range(samples)]
|
|
1620
|
+
ordered = sorted(estimates)
|
|
1621
|
+
lower = ordered[max(0, math.floor(0.025 * (len(ordered) - 1)))]
|
|
1622
|
+
upper = ordered[min(len(ordered) - 1, math.ceil(0.975 * (len(ordered) - 1)))]
|
|
1623
|
+
return lower, upper
|
|
1624
|
+
|
|
1625
|
+
|
|
1626
|
+
def _run_roots(run_dirs: Sequence[str | Path]) -> tuple[Path, ...]:
|
|
1627
|
+
if isinstance(run_dirs, str | bytes) or not isinstance(run_dirs, Sequence):
|
|
1628
|
+
raise ValidationError("run_dirs must be a sequence of paths")
|
|
1629
|
+
roots = tuple(Path(path).expanduser().resolve() for path in run_dirs)
|
|
1630
|
+
if not roots:
|
|
1631
|
+
raise ValidationError("stateful arena analysis requires at least one run directory")
|
|
1632
|
+
if len(roots) != len(set(roots)):
|
|
1633
|
+
raise ValidationError("stateful arena run directories must be unique")
|
|
1634
|
+
return roots
|
|
1635
|
+
|
|
1636
|
+
|
|
1637
|
+
def _resample_arguments(*, bootstrap_samples: int, random_seed: int) -> None:
|
|
1638
|
+
if (
|
|
1639
|
+
isinstance(bootstrap_samples, bool)
|
|
1640
|
+
or not isinstance(bootstrap_samples, int)
|
|
1641
|
+
or bootstrap_samples < 100
|
|
1642
|
+
):
|
|
1643
|
+
raise ValidationError("bootstrap_samples must be an integer >= 100")
|
|
1644
|
+
if isinstance(random_seed, bool) or not isinstance(random_seed, int) or random_seed < 0:
|
|
1645
|
+
raise ValidationError("random_seed must be a non-negative integer")
|
|
1646
|
+
|
|
1647
|
+
|
|
1648
|
+
def _metric_name(value: Any) -> str:
|
|
1649
|
+
return _text(value, label="stateful arena analysis metric")
|
|
1650
|
+
|
|
1651
|
+
|
|
1652
|
+
def _read_json_object(path: Path, *, label: str) -> Mapping[str, Any]:
|
|
1653
|
+
try:
|
|
1654
|
+
value = json.loads(path.read_text(encoding="utf-8"))
|
|
1655
|
+
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
|
|
1656
|
+
raise ArtifactError(f"{label} is missing or invalid") from exc
|
|
1657
|
+
return _object(value, label=label)
|
|
1658
|
+
|
|
1659
|
+
|
|
1660
|
+
def _strict_object(value: Any, *, label: str, keys: set[str]) -> Mapping[str, Any]:
|
|
1661
|
+
result = _object(value, label=label)
|
|
1662
|
+
if set(result) != keys:
|
|
1663
|
+
missing = sorted(keys - set(result))
|
|
1664
|
+
unknown = sorted(set(result) - keys)
|
|
1665
|
+
raise ArtifactError(f"{label} fields are invalid; missing={missing}, unknown={unknown}")
|
|
1666
|
+
return result
|
|
1667
|
+
|
|
1668
|
+
|
|
1669
|
+
def _object(value: Any, *, label: str) -> Mapping[str, Any]:
|
|
1670
|
+
if not isinstance(value, Mapping):
|
|
1671
|
+
raise ArtifactError(f"{label} must be an object")
|
|
1672
|
+
if any(not isinstance(key, str) for key in value):
|
|
1673
|
+
raise ArtifactError(f"{label} keys must be strings")
|
|
1674
|
+
return value
|
|
1675
|
+
|
|
1676
|
+
|
|
1677
|
+
def _sequence(value: Any, *, label: str) -> tuple[Any, ...]:
|
|
1678
|
+
if isinstance(value, str | bytes) or not isinstance(value, Sequence):
|
|
1679
|
+
raise ArtifactError(f"{label} must be an array")
|
|
1680
|
+
return tuple(value)
|
|
1681
|
+
|
|
1682
|
+
|
|
1683
|
+
def _text_sequence(
|
|
1684
|
+
value: Any,
|
|
1685
|
+
*,
|
|
1686
|
+
label: str,
|
|
1687
|
+
allow_empty: bool = False,
|
|
1688
|
+
) -> tuple[str, ...]:
|
|
1689
|
+
items = _sequence(value, label=label)
|
|
1690
|
+
if not items and not allow_empty:
|
|
1691
|
+
raise ArtifactError(f"{label} must not be empty")
|
|
1692
|
+
return tuple(_text(item, label=f"{label}[{index}]") for index, item in enumerate(items))
|
|
1693
|
+
|
|
1694
|
+
|
|
1695
|
+
def _text(value: Any, *, label: str) -> str:
|
|
1696
|
+
if (
|
|
1697
|
+
not isinstance(value, str)
|
|
1698
|
+
or not value.strip()
|
|
1699
|
+
or value != value.strip()
|
|
1700
|
+
or any(not character.isprintable() for character in value)
|
|
1701
|
+
):
|
|
1702
|
+
raise ValidationError(f"{label} must be trimmed non-empty text")
|
|
1703
|
+
return value
|
|
1704
|
+
|
|
1705
|
+
|
|
1706
|
+
def _seed(value: Any, *, label: str) -> int:
|
|
1707
|
+
if isinstance(value, bool) or not isinstance(value, int) or value < 0:
|
|
1708
|
+
raise ArtifactError(f"{label} must be a non-negative integer")
|
|
1709
|
+
return value
|
|
1710
|
+
|
|
1711
|
+
|
|
1712
|
+
def _unit_interval(value: Any, *, label: str) -> float:
|
|
1713
|
+
if (
|
|
1714
|
+
isinstance(value, bool)
|
|
1715
|
+
or not isinstance(value, int | float)
|
|
1716
|
+
or not math.isfinite(float(value))
|
|
1717
|
+
or not 0.0 <= float(value) <= 1.0
|
|
1718
|
+
):
|
|
1719
|
+
raise ArtifactError(f"{label} must be finite and in [0, 1]")
|
|
1720
|
+
return float(value)
|
|
1721
|
+
|
|
1722
|
+
|
|
1723
|
+
def _sha256(value: Any, *, label: str) -> str:
|
|
1724
|
+
if not isinstance(value, str) or not _is_sha256(value):
|
|
1725
|
+
raise ArtifactError(f"{label} must be a lowercase SHA-256")
|
|
1726
|
+
return value
|
|
1727
|
+
|
|
1728
|
+
|
|
1729
|
+
def _is_sha256(value: str) -> bool:
|
|
1730
|
+
return len(value) == 64 and all(character in "0123456789abcdef" for character in value)
|
|
1731
|
+
|
|
1732
|
+
|
|
1733
|
+
__all__ = [
|
|
1734
|
+
"STATEFUL_ARENA_REPLICATION_ARTIFACT",
|
|
1735
|
+
"STATEFUL_ARENA_REPLICATION_PROTOCOL",
|
|
1736
|
+
"analyze_stateful_arena_replication",
|
|
1737
|
+
"validate_external_arena_record_receipts",
|
|
1738
|
+
]
|