diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 9943e2d4..9ffbb1d7 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -182,6 +182,7 @@ jobs: tests.test_adaptive_bundle_registry \ tests.test_adaptive_algorithm_scout \ tests.test_adaptive_candidate_screening \ + tests.test_adaptive_support_profile_governance \ tests.test_adaptive_managed_output \ tests.test_adaptive_qualification \ tests.test_adaptive_recommendation \ @@ -253,6 +254,7 @@ jobs: tests.test_adaptive_bundle_registry \ tests.test_adaptive_algorithm_scout \ tests.test_adaptive_candidate_screening \ + tests.test_adaptive_support_profile_governance \ tests.test_adaptive_managed_output \ tests.test_adaptive_qualification \ tests.test_adaptive_recommendation \ @@ -338,6 +340,7 @@ jobs: tests/test_adaptive_vision_roadmap_generator.py \ tests/test_adaptive_algorithm_scout.py \ tests/test_adaptive_candidate_screening.py \ + tests/test_adaptive_support_profile_governance.py \ tests/test_adaptive_evidence_activation.py \ tests/test_adaptive_qualification.py \ tests/test_adaptive_recommendation.py \ @@ -414,6 +417,7 @@ jobs: run: | yolozu doctor --output - yolozu activate-qualification-evidence --help + yolozu review-image-pipeline-support-profiles --help yolozu qualify-image-pipeline --help yolozu demo instance-seg --num-images 2 --image-size 48 --max-instances 2 --run-dir "${RUNNER_TEMP}/yolozu_demo" @@ -439,6 +443,7 @@ jobs: python3 tools/ci/install_with_hashes.py --requirements requirements-locks/requirements-runtime.lock --install-local-wheel yolozu doctor --output - yolozu activate-qualification-evidence --help + yolozu review-image-pipeline-support-profiles --help yolozu export --help yolozu qualify-image-pipeline --help python -c "from yolozu import resources; paths = resources.list_resource_paths(); assert 'schemas/predictions.schema.json' in paths; assert 'schemas/adaptive_vision_roadmap.schema.json' in paths; assert 'manifest/adaptive_vision_roadmap.json' in paths; assert 'protocols/yolo26_eval.json' in paths; print('resources OK:', len(paths))" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8bcd6e25..13347be6 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -45,6 +45,7 @@ jobs: tests.test_adaptive_bundle_registry \ tests.test_adaptive_algorithm_scout \ tests.test_adaptive_candidate_screening \ + tests.test_adaptive_support_profile_governance \ tests.test_adaptive_managed_output \ tests.test_adaptive_recommendation \ tests.test_adaptive_processing \ @@ -78,11 +79,15 @@ jobs: "yolozu/data/schemas/evidence_activation_record.schema.json", "yolozu/data/schemas/candidate_screening_record.schema.json", "yolozu/data/schemas/screening_eligibility_observation.schema.json", + "yolozu/data/schemas/support_profile_spec.schema.json", + "yolozu/data/schemas/support_profile_record.schema.json", "yolozu/data/schemas/support_profile_eligibility_observation.schema.json", + "yolozu/data/schemas/support_profile_set_proposal.schema.json", "yolozu/data/schemas/selection_decision.schema.json", "yolozu/data/adaptive_routing/bundle_specs.json", "yolozu/data/adaptive_routing/bundle_lifecycle.jsonl", "yolozu/data/adaptive_routing/candidate_screening.jsonl", + "yolozu/data/adaptive_routing/support_profiles.jsonl", "yolozu/data/adaptive_routing/evidence_activation.jsonl", } with zipfile.ZipFile(wheels[-1], "r") as archive: @@ -206,6 +211,7 @@ jobs: tests.test_adaptive_bundle_registry \ tests.test_adaptive_algorithm_scout \ tests.test_adaptive_candidate_screening \ + tests.test_adaptive_support_profile_governance \ tests.test_adaptive_managed_output \ tests.test_adaptive_recommendation \ tests.test_adaptive_processing \ @@ -237,11 +243,15 @@ jobs: "yolozu/data/schemas/evidence_activation_record.schema.json", "yolozu/data/schemas/candidate_screening_record.schema.json", "yolozu/data/schemas/screening_eligibility_observation.schema.json", + "yolozu/data/schemas/support_profile_spec.schema.json", + "yolozu/data/schemas/support_profile_record.schema.json", "yolozu/data/schemas/support_profile_eligibility_observation.schema.json", + "yolozu/data/schemas/support_profile_set_proposal.schema.json", "yolozu/data/schemas/selection_decision.schema.json", "yolozu/data/adaptive_routing/bundle_specs.json", "yolozu/data/adaptive_routing/bundle_lifecycle.jsonl", "yolozu/data/adaptive_routing/candidate_screening.jsonl", + "yolozu/data/adaptive_routing/support_profiles.jsonl", "yolozu/data/adaptive_routing/evidence_activation.jsonl", } wheels = sorted(glob.glob("dist/*.whl")) diff --git a/CHANGELOG.md b/CHANGELOG.md index eba4d701..2c527596 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Register the existing model zoo as non-promoted adaptive Candidate baselines. - Add an Experimental bounded official-source algorithm scout and nonselectable candidate inbox. - Add fail-closed non-executing candidate screening and recommendation preflight. +- Add reviewed dormant support-profile set governance and execution-time reprojection. ### Fixed - Keep repository-wrapper prediction and TTA/TTT log paths anchored to the checkout when invoked from another working directory. diff --git a/README.md b/README.md index c0fc946b..3389fad5 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,14 @@ cancellation, and atomic unactivated `qualification_report.json` publication. Experimental `yolozu activate-qualification-evidence` now dry-runs every review, trust, freshness, registry, lifecycle, and stale-head gate before it can append an activation, supersession, or terminal revocation. Mutation requires `--approve`. +Experimental `yolozu review-image-pipeline-support-profiles` separately reviews one +complete ordered exact-measured target set. It reads and, only with `--approve`, +atomically appends to the sole packaged `support_profiles.jsonl` SSOT. A reviewed +set remains dormant: it does not change a lifecycle pointer, activate evidence, +bind a runner, download a model, or claim current support. The stream is currently +empty. Recommendation and execution use the same loader-derived support-profile +provider, and execution reprojects the lifecycle-pinned historical set before any +runner session is opened. Locally emitted reports can reach only `site_managed` / `site_qualified`; arbitrary workspace JSON remains nonselectable. Repository-managed trust additionally requires the retained, tracked review workflow and a public review reference. diff --git a/Readme_jp.md b/Readme_jp.md index 79216243..feb811d7 100644 --- a/Readme_jp.md +++ b/Readme_jp.md @@ -145,7 +145,14 @@ preflight、固定したrepeat/soak protocol、child processのbounded cancellat unactivatedな`qualification_report.json`のatomic publicationを実装しています。 Experimental `yolozu activate-qualification-evidence` は、review、trust、freshness、 registry/lifecycle、stale-head の全gateをdry-runで確認し、`--approve`を明示した場合 -だけactivation、supersession、terminal revocationをatomicに追記します。localで生成 +だけactivation、supersession、terminal revocationをatomicに追記します。 +Experimental `yolozu review-image-pipeline-support-profiles` は、exact-measuredな +target profileの完全なordered setを別工程でreviewします。sole packaged SSOTである +`support_profiles.jsonl`だけを読み、`--approve`時だけatomicに追記します。review済み +setはdormantのままで、lifecycle pointer、evidence activation、runner binding、model +download、現在利用可能というsupport claimを変更しません。streamは現在空です。 +recommendationとexecutionは同じloader-derived providerを使い、executionはrunner +sessionを開く直前にlifecycleが固定したhistorical setを再projectします。localで生成 したreportは`site_managed` / `site_qualified`までで、任意のworkspace JSONは選択対象に なりません。repository-managed trustには、追跡されたreview workflowとpublic review referenceが別途必要です。 diff --git a/docs/README.md b/docs/README.md index 510c7db8..5366bd3a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -48,7 +48,10 @@ The environment-qualified local image-processing program targets an Experimental - Current Experimental implementation boundary: `yolozu qualify-image-pipeline` emits only an unactivated report; `yolozu activate-qualification-evidence` defaults to a no-write gate report and mutates only with explicit review and - `--approve`. A bounded non-executing candidate-screening provider derives pass, + `--approve`. `yolozu review-image-pipeline-support-profiles` likewise defaults + to no-write and can append only a complete reviewed dormant set to the canonical + support-profile SSOT. That review does not advertise support or make a model + executable. A bounded non-executing candidate-screening provider derives pass, hold, or reject before selection. Its packaged stream is empty; custom input stays operator-asserted and cannot satisfy the managed-pass gate. A file-free pure selector evaluates already validated records, and the MCP-only diff --git a/docs/adaptive_image_routing.md b/docs/adaptive_image_routing.md index 98560e79..a4ad6e3f 100644 --- a/docs/adaptive_image_routing.md +++ b/docs/adaptive_image_routing.md @@ -495,6 +495,33 @@ bundle was Candidate. The decision pins the current lifecycle projection digest only to detect change between recommendation and execution. +### Dormant support-profile review + +`yolozu review-image-pipeline-support-profiles` is the implemented Experimental +maintainer operation for one complete ordered set of 1..32 `SupportProfileSpec` +records. Its proposal must use exact `canonical_json_v1` bytes, repeat the exact +family/channel and complete ordered profile IDs, and contain no private/site values. +The default is a bounded no-write dry-run. An approved append requires the observed +global support-profile head, the exact current set-record/set digests or explicit +initial none, a non-personal repository review role, a public review reference, and +a reason. + +Approval appends only new immutable definitions followed by one set assignment to +`yolozu/data/adaptive_routing/support_profiles.jsonl`, then reads back the complete +projection. Existing bytes are unchanged and no derived projection file is written. +This produces a reviewed dormant target scope only. It does not change the current +lifecycle assignment, activate evidence, bind or import a runner, download assets, +promote maturity, or advertise support. The canonical stream remains empty until a +real reviewed proposal is approved; test fixtures are not support evidence. + +The loader-derived support-profile provider follows the exact historical set record +and index prefix pinned by each current lifecycle assignment. A newer dormant review +does not rewrite that advertised snapshot. Recommendation passes only these typed +observations to the pure selector. `process_images` reopens the support/lifecycle +SSOTs and requires the exact pinned observation again immediately before it resolves +a runner session. Missing, untrusted, conflicting, superseded, or tampered state +fails closed. + The environment fingerprint identifies a measured configuration, not one unique physical host. It excludes identifying host data. Evidence from representative images describes only that measured workload. It does not prove the same latency or diff --git a/docs/generated/cli_reference.md b/docs/generated/cli_reference.md index 8acad998..1aab9081 100644 --- a/docs/generated/cli_reference.md +++ b/docs/generated/cli_reference.md @@ -6,10 +6,10 @@ Keep narrative docs short and link here for the full command surface. ## Top-level `yolozu --help` ```text -usage: yolozu [-h] [--version] {guide,doctor,dr,list,fetch,export,export-dataset,predict-images,eval-coco,calibrate,eval-long-tail,long-tail-recipe,benchmark,parity,predictions,validate,eval-instance-seg,onnxrt,resources,migrate,import,train,train-orchestrate,test,demo,qualify-image-pipeline,activate-qualification-evidence,scout-algorithms,registry,completion,comp} ... +usage: yolozu [-h] [--version] {guide,doctor,dr,list,fetch,export,export-dataset,predict-images,eval-coco,calibrate,eval-long-tail,long-tail-recipe,benchmark,parity,predictions,validate,eval-instance-seg,onnxrt,resources,migrate,import,train,train-orchestrate,test,demo,qualify-image-pipeline,activate-qualification-evidence,review-image-pipeline-support-profiles,scout-algorithms,registry,completion,comp} ... positional arguments: - {guide,doctor,dr,list,fetch,export,export-dataset,predict-images,eval-coco,calibrate,eval-long-tail,long-tail-recipe,benchmark,parity,predictions,validate,eval-instance-seg,onnxrt,resources,migrate,import,train,train-orchestrate,test,demo,qualify-image-pipeline,activate-qualification-evidence,scout-algorithms,registry,completion,comp} + {guide,doctor,dr,list,fetch,export,export-dataset,predict-images,eval-coco,calibrate,eval-long-tail,long-tail-recipe,benchmark,parity,predictions,validate,eval-instance-seg,onnxrt,resources,migrate,import,train,train-orchestrate,test,demo,qualify-image-pipeline,activate-qualification-evidence,review-image-pipeline-support-profiles,scout-algorithms,registry,completion,comp} guide Show beginner-friendly routes and copy-paste commands. doctor (dr) Check the environment. Use --explain for beginner-friendly next actions. list List registries and built-in catalogs. @@ -38,6 +38,8 @@ positional arguments: Measure one exact local image bundle (Experimental). activate-qualification-evidence Review one exact qualification report; dry-run unless --approve is set. + review-image-pipeline-support-profiles + Review one complete dormant support-profile set; dry-run by default. scout-algorithms Plan or collect a bounded monitored-source candidate inbox (Experimental). registry AI-first tool registry: list/show/validate/run tools from the canonical manifest. completion (comp) Print shell completion script (bash/zsh). @@ -272,6 +274,7 @@ Contact: develop@toppymicros.com | render_synthgen_overlay | experimental | tools/render_synthgen_overlay.py | Render semantic + instance + keypoint overlays from SynthGen shard samples. | | render_ttt_manual_figures | research | tools/render_ttt_manual_figures.py | Render the six-file docs/manual TTT figure bundle atomically from validated synthetic-fixture or hash-bound measured sources. | | report_dependency_licenses | stable | tools/report_dependency_licenses.py | Generate a best-effort dependency license report from installed Python packages (not legal advice). | +| review_image_pipeline_support_profiles | experimental | tools/review_image_pipeline_support_profiles.py | Dry-run or atomically append one complete reviewed dormant exact-measured support-profile set; review alone never changes lifecycle support or availability. | | rtdetr_pose_backend_suite | experimental | tools/rtdetr_pose_backend_suite.py | Fail-closed RT-DETR backend parity + benchmark suite with shared checkpoint compatibility evidence. | | rtdetr_pose_train_continual | research | rtdetr_pose/tools/train_continual.py | Continual fine-tuning runner for rtdetr_pose with an explicit initial-checkpoint/FWT baseline, optional no-object-aware foreground response selection, replay/LoRA/EWC/SI, and per-task checkpoint, teacher, data-order, time, memory, and command provenance. | | run_actions_api | stable | tools/run_actions_api.py | Run the GPT Actions API, including fail-closed typed TTT/CTTA export jobs with full checkpoint preflight. | diff --git a/docs/generated/web_docs/commands.html b/docs/generated/web_docs/commands.html index 81758996..4753d28a 100644 --- a/docs/generated/web_docs/commands.html +++ b/docs/generated/web_docs/commands.html @@ -41,7 +41,7 @@
Generated from tools/manifest.json

Command reference

-

136 manifest entries with declared maturity, inputs, +

137 manifest entries with declared maturity, inputs, examples, implementation paths, and documentation links. The manifest remains the source of truth.

@@ -1563,6 +1563,21 @@

Examples

Implementation · license_policy.md

+
+ review_image_pipeline_support_profiles — Dry-run or atomically append one complete reviewed dormant exact-measured support-profile set; review alone never changes lifecycle support or availability. +
+ experimental + tools/review_image_pipeline_support_profiles.py +
+

Inputs

+ +

Examples

+
python3 tools/review_image_pipeline_support_profiles.py --proposal reports/support_profile_set_proposal.json --family-id yolox --channel Experimental --expected-head-digest <sha256> --expect-no-current-profile-set --reviewer-role-id repo_maintainer --public-review-id gh-<number> --reason 'Review complete dormant target scope'
+python3 tools/review_image_pipeline_support_profiles.py --help
+

Implementation · README.md · Readme_jp.md · adaptive_image_routing.md · support_profile_set_proposal.schema.json · support_profile_record.schema.json

+
+
rtdetr_pose_backend_suite — Fail-closed RT-DETR backend parity + benchmark suite with shared checkpoint compatibility evidence. diff --git a/docs/generated/web_docs/provenance.json b/docs/generated/web_docs/provenance.json index 9c8681cf..90257322 100644 --- a/docs/generated/web_docs/provenance.json +++ b/docs/generated/web_docs/provenance.json @@ -3,8 +3,8 @@ "examples": 4, "failure_guides": 8, "glossary_terms": 10, - "schemas": 49, - "tools": 136 + "schemas": 50, + "tools": 137 }, "generated_files": [ "assets/docs.js", @@ -26,20 +26,20 @@ "schema_version": 1, "source_hashes": { "CONTRIBUTING.md": "d80fb82f1e97f616c5e10b15adaf189acf8ede8df631d01e77af5c6526b312f1", - "README.md": "820a23148980368fd489aaf51b8e8a9bc621e6d57c87038a508d0449035891bf", + "README.md": "2a94d83b0c7f17d9cffbd3b9d672d97dfcef258484aff18a2656f44a730df4dd", "RELEASE.md": "b021e735a55712edc24a7f76e13c3c811a36f04e0bfd4dc7ceb0800e99e80e29", - "Readme_jp.md": "6186f87feb0e60e7f7603210961b9e4beca5bc9a328d634be10e5b84c822fe69", + "Readme_jp.md": "27dfdcec3e0cd3021c2289e6a3a5083e6cdca8c9602b92005c574f5174fefe48", "Readme_zh.md": "c4630845f9915401468d79cc5a9b5f5c28600a6147e0a83d2351afcae162a5dd", "data/smoke/README.md": "8763fbc7545484a77b99a077cded552b03680109cec70fcd4c56bc2ed2cd4284", "data/smoke/predictions/predictions_dummy.json": "e028f2d9fb9f567527cd2b38f94ad7ed366665cec840af61bec62c736013e6fe", "data/smoke/synthgen_minishard/README.md": "af36faf0dae4d7e7950931dbd079090e844a871cd9ac9bfa2b8a2b2fa8ac3af0", "deploy/docker/README.md": "7a280c69fa7e5e54e035e94f155d0f46ff667740dc114192d9ca78c5a73677c4", "deploy/runpod/README.md": "de43d7c3c97d17c6bac13e43fd48726ed23a27892df740ff6b2d440f55e50e2c", - "docs/README.md": "41fd9cee4211d9e0070549c98244475ef5e31855d2c67058db8fa9f5cf2fe9f4", + "docs/README.md": "8af620c5e6e35c8ae2c63f9286300784ea311c5249b13b8cdcfd4f5a6c7afe9c", "docs/adapter_contract.md": "24c180923cbba2f259c916c0b468559585f4ad6b667fa709ac5b42d632c825d8", "docs/adapter_strategy.md": "827913eb4930193db89bc7a57b6be2eeda99843e4d4f8ee857831909d8a5c83a", "docs/adapter_templates.md": "34c35ac8a115412886449937f9bd593223b67467428f19cdbb791facea3ca570", - "docs/adaptive_image_routing.md": "9b5604dde6c3b4041ff5968c6cfe53613bf148a75625e74eb1b5197b99c21879", + "docs/adaptive_image_routing.md": "369108fa6d24b383df77b711a32b8f73f99a67e6b4da114268d8902e9dde3163", "docs/ai_first.md": "4c32e2616fb491a5f4e5629864929932e450bca48ef63e80df3dd407f88de9ac", "docs/algorithm_intake/README.md": "0338251aa1219fc1e47bfae893ba3c5c466d4781a5a8989539bb8bfce91b1ed0", "docs/assets/instance_seg_coco_instances_demo.png": "c126d1fb8939c06778d071a2e9cc8abd12b07c17c304b7fd7f6327e67f385bf5", @@ -96,7 +96,7 @@ "docs/opencv_dnn_inference.md": "2c5db1b0a3b4db583eeaddc29bbb9bfd1952aa95169c4932a44fe1f4ca31df5c", "docs/predictions_interface_contract_policy.md": "86ef7f11cd33cd54c3585a2c3806eff3ff46001d2abea8ac1bb176c355bcb84e", "docs/predictions_schema.md": "10dbd712f48f504e1a2ecfcfec23e874264203ba8e711d1a892bdc514cf8c738", - "docs/production_readiness.md": "b86ac275a395ca898e79abe8dfae896fe11feebe69da5494755019ac8938f53e", + "docs/production_readiness.md": "0c0cbffbef61c737cec1c84bc7901bf41e5b17b5d3938d0b3cd92bf4b7da383d", "docs/proof_onepager.md": "aa97d01280188314badab3215abf707823bf58dfafdfeafdb20e44fb68958204", "docs/python_api.md": "e7255fac4be5d7d44ab6f2ca39984022408b3dd839b44c7091940648bea7dd7a", "docs/quantization.md": "d7d540f0cebfed4fad47f50a0940600563392fb6ae0668e1aabc11ff9bbcf509", @@ -111,7 +111,7 @@ "docs/runpod_gpu_validation_split.md": "4449e134d04ab7faa3dbf0d54f6729766aa75ec5962301c64ac0bd559bf2e26f", "docs/sar_design_spec.md": "a15382fc2cd6964b4021eb3641799ed83dc7ff591a09adff62a5280e2ffe2121", "docs/sar_evaluation.md": "8e8c340002c2fff76ca1487b9f4ed9a1bb72be2c0f95e2f510550db18d33724b", - "docs/schema_governance.md": "cfc6330cbe1283a5800aa3f8fb146ce8322b6ee592ee80234b6a17e8875f669c", + "docs/schema_governance.md": "20a5732d66f575eac02148c96ee907d724f176ac8ce75b7829af3f5b611f159e", "docs/schemas/adaptive_vision_roadmap.schema.json": "dfa549c0955d45fb06306caf54eb4a4ada97593343f72cfa6cffd9c6103f716b", "docs/schemas/ai_generate_config.schema.json": "067bef1c1ee16894ccbf89b2c45b1e071fe5f5d80cfa7dcd631664dad21a1e27", "docs/schemas/ai_review_config.schema.json": "fa8ab6f989ab0e7e5207eae20d449bf588a1ef5a05cc170c0d28669566fad7f5", @@ -155,6 +155,7 @@ "docs/schemas/selection_decision.schema.json": "4915470824b89d1a98b7b317688e2bb4435d3a7068374320008cc3f300e83054", "docs/schemas/support_profile_eligibility_observation.schema.json": "9c327c281766bd7c6a3da41e47d3ea3dfd689f269422a7908c40e5a0b1c2de0a", "docs/schemas/support_profile_record.schema.json": "9d6c4bddfe25d949e8fb7d39051c9a6fe6b2f096a061de06905fd1aec1d3fd5d", + "docs/schemas/support_profile_set_proposal.schema.json": "107c0a69eb0f45ee0410848346d827005d5001e58b93f9e94a7527927027dadb", "docs/schemas/support_profile_spec.schema.json": "230dad6c2fd426bebcea0e39ea9ec1706681d0e95c95bb9d70b0258696aa8aab", "docs/schemas/tools_manifest.schema.json": "7888f08cdb2667ad243fdd7d1e1f67f11216a30993c6738ddd916a5d47c345c5", "docs/schemas/training_handoff.schema.json": "632bc20ac75aab534be1a3ef3a872de990a2c98f1599567bdda9dc5bb481b560", @@ -163,7 +164,7 @@ "docs/schemas/training_run_summary.schema.json": "dbd984c1cb32da9c1f42c2c8475a34a0751352db821d68bc7152d2efeac9be6f", "docs/score_calibration.md": "f2e3c4653c9d024f26443aa091d97cecbf7419c89c2633503ad15998d0868016", "docs/security_scorecard_governance.md": "02c955fe1e1706f8b6888fca3d925b810fed79c1a6d772d9873d7e7d66338e9e", - "docs/ssot_capability_coverage_audit.md": "852eb113e0bf48b6e42bc0987225032a1425db5215ace3ecb25b0a92731ed333", + "docs/ssot_capability_coverage_audit.md": "7f43d23fe4bcb6893d87417c2c56628af807aa85341903958b02baa33e291934", "docs/synthgen_contract.md": "f4afdf2243841c9d1acbf74ed360a6b6c630d6681749b138028e9be896b3a189", "docs/synthgen_intake.md": "2ec1a5d0e73bfa67dcf9b9d2bcbf732b59464acf6de1b01a814efa4af95ea042", "docs/synthgen_repo_integration.md": "d76af65c445a852bb6e1dabea3ec4fd5fbfadd2fcfba16057030c8e777281739", @@ -184,7 +185,7 @@ "docs/yolo_detr_support.md": "52159c7102c61a61108c6ef91e31cd4def3b8aff60175896387b1d7893d551cc", "examples/infer_rust/README.md": "f2c88091777499a3c23643a3e3b9ce45178b382fcd4300b49a19b155f3a1e744", "manual/chapters/02_installation.tex": "08e564e5c4f73565abd41b1658d2c51ae7f5e820c90cc50e594163828f102c91", - "manual/chapters/04_cli_reference.tex": "398bd8522d8c526a2c5989c638688d1f714754c74a1f760e626dd3b62eb9bf49", + "manual/chapters/04_cli_reference.tex": "1b2fd90f2c3c0f9714f364310d5e455f11e5cf140d455b7e6b8c8a3c1db3df6e", "manual/chapters/05_workflows_eval_export.tex": "391acfc847f3c1606d38d019d1835f7db0e6ff3e9253e5159bacb6fbe0dd6d91", "manual/chapters/07_training_run_contract.tex": "7a681bd60fc67683e0590f7d28c5e2b8b6cd689e8a3266aa3531b37908f8932f", "manual/chapters/09_parity_bench_protocols.tex": "5cdd7965ddee2b9e8f308d2d63050c3ca01284c462bfb2bec2df1bc8b6652127", @@ -226,7 +227,7 @@ "scripts/download_coco_instances_tiny.py": "722fadc65813317703dfd8ddfe39650a46c364942cd1325d9e740da688428006", "scripts/fresh_install_journey.sh": "fb155222d2f107159642836a4b5d0b8917c6836e97c87d5c89caca1652e7d2e8", "scripts/pre_pr_quality.sh": "4b96242d3015465758882235b29f39d072e8e8ba8009ea1d52764ef800e3ce74", - "scripts/pre_push.sh": "cabf7def440e1666e73b1ba5640c57bf6a7f57dafc8a010cbe93462063b1363d", + "scripts/pre_push.sh": "72f499dd6598bc9f84cf2232747557dd87a020788d37ba14acd649c4560305be", "scripts/prepare_ttt_domain_shift_target.py": "d238643654edfd74d343b77eca7bf0ebbc8eb52d3cdfd20f329625ba779c08f6", "scripts/run_external_runtime_gpu_qualification.sh": "2744077ee3f012cef031eb96576311dc1d9f3835a109896c904adc79daaf682d", "scripts/smoke.sh": "64f7c9533a39ff35671093263c3f598af5b6fee45fc99bf720c6eadd9b06face", @@ -300,7 +301,7 @@ "tools/hpo_sweep.py": "969e2747753e7ffec3ee7c52692ed81a7d27f66378401ebbd8eb64840ba206e0", "tools/import_yolo_data_yaml.py": "a7e90969b9dd87fd603819ca8104de8d8b65e03eb5b53effdb50fb4da37b80f1", "tools/make_subset_dataset.py": "1d964b7f4e04944ad6b8191237861cadeee07c3a744c389e9d7e034cdff540a1", - "tools/manifest.json": "6150edebb3bb4b521adf2b4bdf7843eb59ba8ac84a8056478198def60bad195a", + "tools/manifest.json": "ed8578257b7724b6f57f43499eb58b8d58efe3b3e144e78bb9b6fc7b8f1e7d4a", "tools/measure_trt_latency.py": "7495d6e8f443b3ac03eb5e8bb4c411dd939f95d74bbe919f7a1ac6f7c06b97f0", "tools/normalize_predictions.py": "cae6eb84307d44defbddc66c5856e9c0f7981db065693a6fa396fcf75125df89", "tools/orchestrate_train.py": "5c9d7e0be705a6b0cbee0349872869140fad251bd8a4899a9c077ff30e386f1b", @@ -324,6 +325,7 @@ "tools/render_synthgen_overlay.py": "d70636ec41fe880177c39c24d353358aab598d16f5116e56b72578e439d2b9d0", "tools/render_ttt_manual_figures.py": "0a174e87851cf5231141fb6740727fc5bcd648558fdb69cc78f203ae3f60b0cb", "tools/report_dependency_licenses.py": "c5569c7ef49298afff02f68eca4c0e6a810dae54e85bb8365b4d1060529e03b8", + "tools/review_image_pipeline_support_profiles.py": "aa4c464d69a4c94bfce9d14963f72f949df6c6eddbb111702d17f0e96ed528f6", "tools/rtdetr_pose_backend_suite.py": "f6b8096ad083680cb9fcce9ec6d473e729a2b0c17ace2215f428c405913e42e4", "tools/run_actions_api.py": "28a2f020533470ba47497ab6e417c895ebc2d38daa3c68ef8cc484314a31f598", "tools/run_external_finetune_smoke.py": "482f906d950f493e96f56dcabcb0563e6da96fab36284b949fc566a7267cfd88", diff --git a/docs/generated/web_docs/schemas.html b/docs/generated/web_docs/schemas.html index 3bd73616..fc055945 100644 --- a/docs/generated/web_docs/schemas.html +++ b/docs/generated/web_docs/schemas.html @@ -41,7 +41,7 @@
Generated from docs/schemas/*.json

Schema browser

-

49 checked-in JSON Schemas covering predictions, +

50 checked-in JSON Schemas covering predictions, evaluation reports, training handoff, registry records, and Research artifacts. Open the source schema for complete field constraints.

@@ -616,6 +616,19 @@

YOLOZU SupportProfileRecord v1

Open the complete JSON Schema

+
+

YOLOZU SupportProfileSetProposal v1

+

Canonical public review input containing one complete ordered dormant support-profile set.

+
+
Schema ID
https://www.toppymicros.com/yolozu/schemas/support_profile_set_proposal.schema.json
+
Root properties
channel, complete_profile_ids, family_id, profiles, schema_version
+
Root required
channel, complete_profile_ids, family_id, profiles, schema_version
+
+

Open the complete JSON Schema

+
+
diff --git a/docs/generated/web_docs/search-index.json b/docs/generated/web_docs/search-index.json index 09ede31e..888b33e0 100644 --- a/docs/generated/web_docs/search-index.json +++ b/docs/generated/web_docs/search-index.json @@ -762,6 +762,13 @@ "summary": "Generate a best-effort dependency license report from installed Python packages (not legal advice).", "title": "report_dependency_licenses" }, + { + "href": "commands.html#tool-review-image-pipeline-support-profiles", + "kind": "command", + "search_text": "review_image_pipeline_support_profiles Dry-run or atomically append one complete reviewed dormant exact-measured support-profile set; review alone never changes lifecycle support or availability. experimental tools/review_image_pipeline_support_profiles.py --proposal --family-id --channel --expected-head-digest --expected-current-profile-set-record-digest --expected-current-profile-set-digest --expect-no-current-profile-set --reviewer-role-id --public-review-id --reason --workspace --approve", + "summary": "Dry-run or atomically append one complete reviewed dormant exact-measured support-profile set; review alone never changes lifecycle support or availability.", + "title": "review_image_pipeline_support_profiles" + }, { "href": "commands.html#tool-rtdetr-pose-backend-suite", "kind": "command", @@ -1301,6 +1308,13 @@ "summary": "One record in the append-only reviewed support-profile definition and dormant-set chain.", "title": "YOLOZU SupportProfileRecord v1" }, + { + "href": "schemas.html#schema-support-profile-set-proposal-schema", + "kind": "schema", + "search_text": "YOLOZU SupportProfileSetProposal v1 Canonical public review input containing one complete ordered dormant support-profile set. channel complete_profile_ids family_id profiles schema_version docs/schemas/support_profile_set_proposal.schema.json", + "summary": "Canonical public review input containing one complete ordered dormant support-profile set.", + "title": "YOLOZU SupportProfileSetProposal v1" + }, { "href": "schemas.html#schema-support-profile-spec-schema", "kind": "schema", diff --git a/docs/production_readiness.md b/docs/production_readiness.md index cb2a027e..359c5795 100644 --- a/docs/production_readiness.md +++ b/docs/production_readiness.md @@ -21,7 +21,7 @@ If your team already has inference outputs and wants fair evaluation without rew | Area | Maturity | Production posture | Primary references | |---|---|---|---| | Predictions validation/evaluation | Stable | Default production lane | [`predictions_schema.md`](predictions_schema.md), [`external_inference.md`](external_inference.md), [`../README.md`](../README.md) | -| Environment-qualified adaptive local vision | Experimental MCP recommendation and pinned processing; candidate screening, qualification, reviewed activation, and pure-selection foundations implemented | `doctor` emits a privacy-safe EnvironmentProfile. Non-executing candidate screening derives pass, hold, or reject from separate mechanical and human-review facts; the packaged stream is empty, and custom input cannot satisfy managed trust. `qualify-image-pipeline` pins bounded inputs/assets, runs the frozen repeat/soak protocol behind a child-process watchdog, and publishes an unactivated managed report. `activate-qualification-evidence` dry-runs every trust, freshness, lifecycle, and stale-head gate and mutates only with explicit review plus `--approve`. `recommend_image_pipeline` returns a selected or abstained SelectionDecision without execution. `process_images` requires that complete decision, repeats pinned current-state checks, defaults to no-write dry-run, and permits explicit bounded managed publication only through registered code-owned network-free routes. Local reports are limited to site-qualified scope; arbitrary JSON remains nonselectable. Three model-zoo records are packaged as non-promoted Candidate baselines with unbound execution. The screening/evidence streams and runner maps remain empty, so the default recommendation abstains, no real bundle can run, and no support or performance evidence is claimed. No model adapter is available. | [`doctor_diagnostics.md`](doctor_diagnostics.md), [`adaptive_image_routing.md`](adaptive_image_routing.md), [`../reports/adaptive_candidate_screening_foundation_2026-08-26.md`](../reports/adaptive_candidate_screening_foundation_2026-08-26.md), [`../reports/adaptive_baseline_bundle_registry_2026-08-26.md`](../reports/adaptive_baseline_bundle_registry_2026-08-26.md), [`../reports/adaptive_qualification_foundation_2026-08-25.md`](../reports/adaptive_qualification_foundation_2026-08-25.md), [`../reports/adaptive_routing_installed_verification_2026-08-26.md`](../reports/adaptive_routing_installed_verification_2026-08-26.md), [`../reports/adaptive_vision_roadmap.md`](../reports/adaptive_vision_roadmap.md), [`roadmap.md`](roadmap.md) | +| Environment-qualified adaptive local vision | Experimental MCP recommendation and pinned processing; candidate screening, qualification, reviewed activation, dormant support-profile review, and pure-selection foundations implemented | `doctor` emits a privacy-safe EnvironmentProfile. Non-executing candidate screening derives pass, hold, or reject from separate mechanical and human-review facts; the packaged stream is empty, and custom input cannot satisfy managed trust. `qualify-image-pipeline` publishes an unactivated measured report, while `activate-qualification-evidence` and `review-image-pipeline-support-profiles` are dry-run by default and mutate only after exact review gates plus `--approve`. A support-profile review creates dormant scope only and does not advertise availability. Recommendation and execution use the same loader-derived provider; execution reprojects the lifecycle-pinned historical set before runner resolution. Local reports remain site-qualified only. Three model-zoo records are non-promoted Candidate baselines with unbound execution. The screening, support-profile, and evidence streams and runner maps are empty, so the default recommendation abstains, no real bundle can run, and no support or performance evidence is claimed. No model adapter is available. | [`doctor_diagnostics.md`](doctor_diagnostics.md), [`adaptive_image_routing.md`](adaptive_image_routing.md), [`../reports/adaptive_support_profile_governance_2026-08-26.md`](../reports/adaptive_support_profile_governance_2026-08-26.md), [`../reports/adaptive_candidate_screening_foundation_2026-08-26.md`](../reports/adaptive_candidate_screening_foundation_2026-08-26.md), [`../reports/adaptive_baseline_bundle_registry_2026-08-26.md`](../reports/adaptive_baseline_bundle_registry_2026-08-26.md), [`../reports/adaptive_qualification_foundation_2026-08-25.md`](../reports/adaptive_qualification_foundation_2026-08-25.md), [`../reports/adaptive_routing_installed_verification_2026-08-26.md`](../reports/adaptive_routing_installed_verification_2026-08-26.md), [`../reports/adaptive_vision_roadmap.md`](../reports/adaptive_vision_roadmap.md), [`roadmap.md`](roadmap.md) | | Dataset I/O and mask-only label derivation | Deferred as standalone capabilities | Implemented and tested inside dataset workflows, but implementation presence and a Stable parent CLI are not standalone production-readiness evidence | [`yolozu_spec.md`](yolozu_spec.md), [`dataset_contract.md`](dataset_contract.md), [`ssot_capability_coverage_audit.md`](ssot_capability_coverage_audit.md) | | Inference constraints and template gating | Deferred as standalone capabilities | Adapter-internal utilities with no independent public production lane; qualify them with the consuming model and protocol | [`yolozu_spec.md`](yolozu_spec.md), [`gate_weight_tuning.md`](gate_weight_tuning.md), [`ssot_capability_coverage_audit.md`](ssot_capability_coverage_audit.md) | | Backend parity / benchmark orchestration | Experimental | Useful after environment-specific qualification; classification, OBB, segmentation, keypoints, depth, and pose6d have artifact-backed real eval/parity lanes, without claiming backend inference | [`backend_parity_matrix.md`](backend_parity_matrix.md), [`benchmark_mode.md`](benchmark_mode.md), `manual/chapters/09_parity_bench_protocols.tex` | diff --git a/docs/schema_governance.md b/docs/schema_governance.md index c85ed305..54bdddc6 100644 --- a/docs/schema_governance.md +++ b/docs/schema_governance.md @@ -107,6 +107,13 @@ Repository-managed report directories retain tracked `public_inputs.json`, `protocol.json`, `qualification_report.json`, `reproduce.txt`, and a `checksums.json` that covers every other file exactly. +`review-image-pipeline-support-profiles` consumes the canonical +`docs/schemas/support_profile_set_proposal.schema.json` shape and requires exact +`canonical_json_v1` bytes plus LF. It may append only to +`yolozu/data/adaptive_routing/support_profiles.jsonl`; installed packages read those +same bytes. No mutable projection copy exists. A successful review records dormant +scope and does not change lifecycle-advertised support. + `load_algorithm_bundle_registry` accepts only the exact packaged registry/lifecycle pair above or an explicit workspace-confined directory containing those two exact basenames. The packaged pair is `yolozu_managed`; a custom pair is always @@ -226,7 +233,7 @@ schema surface for that artifact family. | Predictions | `docs/schemas/predictions.schema.json` | [`predictions_schema.md`](predictions_schema.md) | Packaged copies live in `schemas/` and `yolozu/data/schemas/`. | | Adaptive vision roadmap projection | `docs/schemas/adaptive_vision_roadmap.schema.json` | [`roadmap.md`](roadmap.md), [`../reports/adaptive_vision_roadmap.md`](../reports/adaptive_vision_roadmap.md) | The byte-identical packaged schema and JSON projection describe future scope, not implementation or qualification evidence. | | Adaptive image request, workload, and environment | `docs/schemas/image_job_spec.schema.json`, `docs/schemas/qualification_workload_profile.schema.json`, `docs/schemas/environment_profile.schema.json` | [`adaptive_image_routing.md`](adaptive_image_routing.md), [`doctor_diagnostics.md`](doctor_diagnostics.md) | Byte-identical packaged schemas accompany standard-library validators. `doctor` produces EnvironmentProfile; recommendation and processing consume the typed request/workload records, but none advertises a selectable model. | -| Adaptive bundle, lifecycle, and support-profile records | `docs/schemas/algorithm_bundle_spec.schema.json`, `docs/schemas/algorithm_bundle_registry.schema.json`, `docs/schemas/bundle_lifecycle_record.schema.json`, `docs/schemas/support_profile_spec.schema.json`, `docs/schemas/support_profile_record.schema.json` | [`adaptive_image_routing.md`](adaptive_image_routing.md) | Immutable bundle facts are separate from append-only lifecycle and reviewed support scope. Empty packaged SSOT files keep the public default nonselectable. | +| Adaptive bundle, lifecycle, and support-profile records | `docs/schemas/algorithm_bundle_spec.schema.json`, `docs/schemas/algorithm_bundle_registry.schema.json`, `docs/schemas/bundle_lifecycle_record.schema.json`, `docs/schemas/support_profile_spec.schema.json`, `docs/schemas/support_profile_set_proposal.schema.json`, `docs/schemas/support_profile_record.schema.json` | [`adaptive_image_routing.md`](adaptive_image_routing.md) | Immutable bundle facts are separate from append-only lifecycle and reviewed dormant support scope. The review proposal must cover one complete ordered set. Empty packaged SSOT files keep the public default nonselectable. | | Adaptive candidate screening | `docs/schemas/candidate_screening_record.schema.json` | [`adaptive_image_routing.md`](adaptive_image_routing.md), [`algorithm_intake/README.md`](algorithm_intake/README.md) | Non-executing mechanical facts and human review produce pass, hold, or reject. The sole packaged stream is empty; workspace input remains operator-asserted, and screening output is not a bundle registry. | | Adaptive artifact and qualification evidence | `docs/schemas/local_artifact_inventory.schema.json`, `docs/schemas/qualification_report.schema.json`, `docs/schemas/evidence_activation_record.schema.json` | [`adaptive_image_routing.md`](adaptive_image_routing.md) | Inventory, measurement, and reviewed activation remain separate. The Experimental qualifier emits an unactivated report; the activation command defaults to dry-run and requires explicit review plus approval for an atomic append. The packaged Candidate baselines have unbound execution, and the runner map and public evidence storage remain empty, keeping the default nonselectable and non-executable. | | Adaptive selection observations and decisions | `docs/schemas/screening_eligibility_observation.schema.json`, `docs/schemas/support_profile_eligibility_observation.schema.json`, `docs/schemas/selection_decision.schema.json` | [`adaptive_image_routing.md`](adaptive_image_routing.md) | File-free typed observations and complete selected/abstained records expose every candidate reason. The pure selector consumes only validated in-memory values. An unpointed excluded catalog entry uses an empty pointed-channel set and a null support observation instead of invented evidence. MCP recommendation returns this interface contract; pinned processing accepts only a complete selected record and repeats current-state validation. | diff --git a/docs/schemas/support_profile_set_proposal.schema.json b/docs/schemas/support_profile_set_proposal.schema.json new file mode 100644 index 00000000..cb9b6281 --- /dev/null +++ b/docs/schemas/support_profile_set_proposal.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://www.toppymicros.com/yolozu/schemas/support_profile_set_proposal.schema.json", + "title": "YOLOZU SupportProfileSetProposal v1", + "description": "Canonical public review input containing one complete ordered dormant support-profile set.", + "type": "object", + "additionalProperties": false, + "required": ["schema_version", "family_id", "channel", "complete_profile_ids", "profiles"], + "properties": { + "schema_version": { "const": 1 }, + "family_id": { "$ref": "#/$defs/component" }, + "channel": { "enum": ["Experimental", "Stable"] }, + "complete_profile_ids": { + "type": "array", + "minItems": 1, + "maxItems": 32, + "uniqueItems": true, + "items": { "$ref": "#/$defs/component" } + }, + "profiles": { + "type": "array", + "minItems": 1, + "maxItems": 32, + "items": { "$ref": "support_profile_spec.schema.json" } + } + }, + "$defs": { + "component": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$" } + } +} diff --git a/docs/ssot_capability_coverage_audit.md b/docs/ssot_capability_coverage_audit.md index 07701355..883506c7 100644 --- a/docs/ssot_capability_coverage_audit.md +++ b/docs/ssot_capability_coverage_audit.md @@ -20,6 +20,7 @@ Adaptive pinned processing update: 2026-08-26 Adaptive installed-artifact verification update: 2026-08-26 Adaptive baseline bundle registration update: 2026-08-26 Adaptive monitored-source scout update: 2026-08-26 +Adaptive dormant support-profile governance update: 2026-08-26 The corresponding 30-run diagnostic artifacts and checkpoints are fixed in the [2026-07-27 prerelease](https://github.com/ToppyMicroServices/YOLOZU/releases/tag/ttt-evidence-2026-07-27) @@ -40,7 +41,7 @@ instead of promoting it by inference. | Capability | Maturity | Implementation | CLI | Manifest / packaged copy | Docs | Tests / evidence | Result / follow-up | |---|---|---|---|---|---|---|---| | Predictions validation/evaluation | Stable | `yolozu/api.py`, `yolozu/predictions/`, `yolozu/eval/` | `yolozu validate`, `eval-coco`, `eval-instance-seg`, `parity` | `validate_predictions`, `eval_coco`, `eval_instance_segmentation`, and `yolozu`; source and packaged manifests/schemas match | `python_api.md`, `predictions_schema.md`, `external_inference.md` | `tests/test_public_api.py`, `tests/test_eval_cli_guardrails.py`, `tests/test_predictions.py`, `data/smoke/predictions/predictions_dummy.json` | Aligned. `eval-coco` is strict by default, explicit repair records warnings, bounded subsets count/exclude known unselected predictions, and the typed in-process API ships `py.typed`. | -| Environment-qualified adaptive local vision | Experimental MCP recommendation and pinned processing; candidate-screening, qualification, reviewed activation, pure-selection, and monitored-source foundations implemented | Strict typed validators, live environment profiling, trusted registry loading, non-executing candidate screening, pinned input/artifact readers, bounded qualification/publication, reviewed activation, pure selection, read-only recommendation, dry-run-by-default pinned processing, and a bounded official-source scout exist. Three model-zoo entries remain non-promoted Candidate metadata with unbound execution. Scout output is a separate inbox and cannot load as a bundle registry. Screening output alone is also not a registry. No registered adaptive model runner, model adapter, or real execution claim is made. | `yolozu scout-algorithms` is network-free/write-free by default and collects only with `--collect`; screening keeps mandatory unknowns on hold; qualification emits an unactivated report; activation requires review plus `--approve`; MCP recommendation abstains by default; MCP processing defaults to no-write dry-run | Candidate-only registry/lifecycle SSOT, empty screening/support/evidence streams, screening/scout schemas, roadmap, synchronized manifests, and generated MCP reference are packaged | `algorithm_intake/README.md`, `adaptive_image_routing.md`, `schema_governance.md`, `production_readiness.md`, `reports/adaptive_algorithm_scout_foundation_2026-08-26.md`, `reports/adaptive_candidate_screening_foundation_2026-08-26.md`, `reports/adaptive_baseline_bundle_registry_2026-08-26.md`, `reports/adaptive_routing_installed_verification_2026-08-26.md`, `reports/adaptive_vision_roadmap.md` | `tests/test_adaptive_candidate_screening.py` covers outcome derivation, path trust, bounded streams, supersession, observation mapping, and immediate rejection; routing/installed tests retain the existing abstention and package boundaries | Beads `YOLOZU-ll2.81` is the live planning source. Screening and routing fixtures are interface tests, not performance evidence. The three records remain Candidate, screening/evidence streams and runner maps remain empty, and default recommendation abstains with `maturity_disallowed`. No real bundle qualification, support, execution, or human adoption is claimed. | +| Environment-qualified adaptive local vision | Experimental MCP recommendation and pinned processing; candidate-screening, qualification, reviewed activation, dormant support-profile review, pure-selection, and monitored-source foundations implemented | Strict typed validators, live environment profiling, trusted registry loading, non-executing candidate screening, pinned input/artifact readers, bounded qualification/publication, reviewed evidence activation, dormant support-profile review, pure selection, read-only recommendation, dry-run-by-default pinned processing, and a bounded official-source scout exist. Three model-zoo entries remain non-promoted Candidate metadata with unbound execution. Scout and screening output cannot load as bundle registry state. A reviewed support-profile set remains dormant until a separate lifecycle assignment. No registered adaptive model runner, model adapter, or real execution claim is made. | `yolozu scout-algorithms` collects only with `--collect`; screening keeps mandatory unknowns on hold; qualification emits an unactivated report; evidence activation and dormant support-profile review require exact review gates plus `--approve`; MCP recommendation abstains by default; MCP processing defaults to no-write dry-run and reprojects the lifecycle-pinned historical support set before runner resolution | Candidate-only registry/lifecycle SSOT, empty screening/support-profile/evidence streams, support-profile proposal/record schemas, screening/scout schemas, roadmap, synchronized manifests, and generated MCP reference are packaged | `algorithm_intake/README.md`, `adaptive_image_routing.md`, `schema_governance.md`, `production_readiness.md`, `reports/adaptive_support_profile_governance_2026-08-26.md`, `reports/adaptive_algorithm_scout_foundation_2026-08-26.md`, `reports/adaptive_candidate_screening_foundation_2026-08-26.md`, `reports/adaptive_baseline_bundle_registry_2026-08-26.md`, `reports/adaptive_routing_installed_verification_2026-08-26.md`, `reports/adaptive_vision_roadmap.md` | `tests/test_adaptive_support_profile_governance.py` covers complete-set review, stale/private/invalid input, atomic failure/readback, historical snapshots, provider trust/statuses, recommendation wiring, and execution-time tamper; routing/installed tests retain the abstention and package boundaries | Beads `YOLOZU-ll2.81` is the live planning source. Fixtures are interface tests, not performance evidence. The three records remain Candidate; screening, support-profile, and evidence streams and runner maps remain empty; default recommendation abstains with `maturity_disallowed`. No real bundle qualification, support, execution, or human adoption is claimed. | | Dataset I/O and mask-only label derivation | Explicitly deferred as standalone capabilities | `yolozu/datasets/dataset.py`, `dataset_contract.py`, `dataset_validator.py`, `tools/make_subset_dataset.py`, `rtdetr_pose/rtdetr_pose/train_dataset.py` | `yolozu validate dataset`, `doctor train-dataset`, `export-dataset`, `import`, sidecar-safe subset helper | Covered by stable `yolozu` and dataset preparation entries; no separate mask-derivation command | `yolozu_spec.md`, `dataset_contract.md`, `dataset_processing_matrix.md`, `training_inference_export.md`, `reports/dataset_preflight_2026-07-27.md`, `reports/dataset_roundtrip_2026-07-27.md` | Dataset/doctor/export/subset tests; `data/smoke/`, `data/coco128/`, `data/conversion_tiny_coco/`, `data/real_multitask_fewshot/` | Empty splits fail closed; doctor and validator share strict checks; installed-wheel COCO round trips preserve counts/classes and bbox geometry within the recorded no-clipping tolerance. Subsets retain mask/depth/keypoint/object-pose sidecars and variable-resolution aux arrays collate after task-appropriate resizing. Real keypoint/semantic upstream qualification and fixture license review remain explicitly unqualified; standalone maturity remains deferred. | | RT-DETR pose reference trainer | Stable reference lane | `rtdetr_pose/rtdetr_pose/`, `rtdetr_pose/tools/train_minimal.py` | `yolozu train` | Stable `yolozu` entry; packaged copy matches | `training_backend_interface.md`, `training_capability_matrix.md`, `run_contract.md` | `tests/test_rtdetr_pose_adapter.py`, `rtdetr_pose/tests/test_train_minimal_integration.py` | Aligned. Runtime/GPU qualification remains environment-specific. | | Reference backbone and neck boundary | Stable within the reference trainer | `rtdetr_pose/rtdetr_pose/backbone_interface.py`, `models/backbones/` | `yolozu train --config ...` | Covered by stable `yolozu`; no independent public command | `yolozu_spec.md`, `training_backend_interface.md` | `tests/test_backbone_shapes.py`, `tests/test_rtdetr_backbone_neck_parity.py` | Aligned at the adapter boundary; no repository-wide model-family claim. | @@ -54,17 +55,17 @@ instead of promoting it by inference. | TTA and TTT | TTA is Experimental; TTT is Research | `yolozu/tta/`, `yolozu/response_selection.py`, `tools/export_predictions.py`, `tools/run_ttt_evidence_suite.py` | Opt-in `--tta` has default postprocess and `rtdetr_pose` model-branch modes; `--method detector_response` is the concise selected-foreground compare; opt-in `--ttt` updates parameters or explicitly abstains below the minimum selection count | `export_predictions` is stable at entrypoint level; optional TTA/TTT features retain narrower maturity | `tta_support_matrix.md`, `training_inference_export.md`, `ttt_protocol.md`, `research_lanes.md` | Independently reproduced zero-delta matrix plus a one-checkpoint 10-image detection-native diagnostic with non-zero clean/shifted deltas; abstention tests assert zero backward, optimizer steps, and parameter drift | Detection-native class/box consistency produced a bounded positive observation with no guard stops. Abstention is structurally verified, but neither efficacy nor an optimal threshold is established; maturity remains Research. | | Hessian refinement | Research | `yolozu/calibration/hessian_solver.py`, `tools/refine_predictions_hessian.py` | `refine_predictions_hessian`, `qualify_artifact_research` | Research manifest entries and schemas; packaged manifest matches | `hessian_solver.md`, `research_lanes.md`, `reports/artifact_research_evidence_2026-07-28.md` | `tests/test_hessian_solver.py`, `tests/test_refine_predictions_hessian_cli.py`, `tests/test_qualify_artifact_research_cli.py`; three deterministic COCO128 repetitions | Wrapped output now satisfies the predictions interface contract and reports measured latency/hashes. All 1,280 detections per repetition were `no_signal`, metrics were unchanged, and promotion remains `hold`. | | Searchable web onboarding | Stable generated documentation over per-capability maturity labels | `tools/generate_web_docs.py`, `docs/web_docs_content.json` | Self-contained strict CLI journey plus stable typed Python example | Stable `generate_web_docs` entry; source and packaged manifests match | `web_docs_plan.md`, generated `web_docs/start.html`, `python_api.md` | `tests/test_web_docs_generation.py`, `tests/test_web_docs_candidate_wheel.py`; adversarial path/URL/output checks plus candidate wheel outside checkout through the installed console script | Aligned. Sources are repository-confined, replacement requires owned provenance, all referenced SSOT files are hashed, and CI fails unless the canonical path completes real COCOeval. The dependency-free dry run remains an explicitly non-metric fallback outside that gate. | -| Installed CLI and mixed-lane entrypoints | Mixed; maturity is per entrypoint, with narrower sub-lane rules | `yolozu/cli.py`, `cli_entry.py`, `cli_commands.py` | 31 canonical commands/aliases in current top-level help | 136 entries: 60 stable, 55 experimental, 21 research; source and packaged copies match | `generated/cli_reference.md`, `tools_index.md`, `manifest_declarative_spec.md` | Per-entrypoint help/manifest audit and manual audit are required quality gates | Stable parent maturity is explicitly non-transitive; generated reference and manifest descriptions repeat that boundary. | +| Installed CLI and mixed-lane entrypoints | Mixed; maturity is per entrypoint, with narrower sub-lane rules | `yolozu/cli.py`, `cli_entry.py`, `cli_commands.py` | 32 canonical commands/aliases in current top-level help | 137 entries: 60 stable, 56 experimental, 21 research; source and packaged copies match | `generated/cli_reference.md`, `tools_index.md`, `manifest_declarative_spec.md` | Per-entrypoint help/manifest audit and manual audit are required quality gates | Stable parent maturity is explicitly non-transitive; generated reference and manifest descriptions repeat that boundary. | ## Confirmed checks - `tools/manifest.json` and `yolozu/data/manifest/tools_manifest.json` are byte-identical. -- Strict manifest validation passes for all 136 entries. +- Strict manifest validation passes for all 137 entries. - Per-entrypoint help audit scans the current declared Python tool set with zero execution errors and zero missing flags. -- Manual CLI drift audit passes for the current 31-command/alias top-level surface. +- Manual CLI drift audit passes for the current 32-command/alias top-level surface. - Public docs example audit passes 114 shell examples. - The generated benchmark support matrix is current for 7 formats, 7 tasks, and 49 rows. -- The generated web-docs bundle is current for 136 tools and 48 JSON Schemas. +- The generated web-docs bundle is current for 137 tools and 50 JSON Schemas. - Public PyPI `yolozu==4.5.1` completed the fresh-install stable lane in all 10 Linux/macOS jobs for Python 3.10 through 3.14 in [workflow run 29421807474](https://github.com/ToppyMicroServices/YOLOZU/actions/runs/29421807474). diff --git a/manual/chapters/04_cli_reference.tex b/manual/chapters/04_cli_reference.tex index c62c6a58..378a242f 100644 --- a/manual/chapters/04_cli_reference.tex +++ b/manual/chapters/04_cli_reference.tex @@ -60,6 +60,7 @@ \section{Common Commands} \cmd{yolozu benchmark} & Compare benchmark/parity lanes under pinned settings. & \cmd{yolozu benchmark --help} & benchmark report JSON & No \\ \cmd{yolozu qualify-image-pipeline} & Measure an exact registered Experimental image bundle. & \cmd{yolozu qualify-image-pipeline --help} & unactivated managed qualification report & No \\ \cmd{yolozu activate-qualification-evidence} & Review one exact qualification report; dry-run by default. & \cmd{yolozu activate-qualification-evidence --help} & gate report or approved append-only activation event & No \\ +\cmd{yolozu review-image-pipeline-support-profiles} & Review one complete dormant exact-measured target set; dry-run by default. & \cmd{yolozu review-image-pipeline-support-profiles --help} & gate report or approved canonical support-profile append & No \\ \cmd{yolozu scout-algorithms} & Plan or collect an Experimental official-source candidate inbox. & \cmd{yolozu scout-algorithms --help} & no-write plan or dated nonselectable report & Only with \cmd{--collect} \\ \cmd{yolozu parity} & Compare two predictions artifacts for drift. & \cmd{yolozu parity --help} & parity JSON & No \\ \cmd{yolozu train} & Run config-driven training or external training wrappers. & \cmd{yolozu train --help} & run bundle under \path{runs/} & No \\ diff --git a/reports/adaptive_support_profile_governance_2026-08-26.md b/reports/adaptive_support_profile_governance_2026-08-26.md new file mode 100644 index 00000000..ae098a17 --- /dev/null +++ b/reports/adaptive_support_profile_governance_2026-08-26.md @@ -0,0 +1,40 @@ +# Adaptive support-profile governance foundation — 2026-08-26 + +## Outcome + +YOLOZU now has an Experimental reviewed operation for one complete ordered dormant +support-profile set. The operation is dry-run by default and can append only to the +canonical `yolozu/data/adaptive_routing/support_profiles.jsonl` SSOT after exact +head, current-set, proposal, and public-review checks. + +This does not make a model available. The canonical stream remains empty because no +real measured proposal was reviewed in this change. No lifecycle pointer, evidence +activation, runner binding, model asset, maturity, or public support statement was +added. + +## Implemented boundary + +- `SupportProfileSetProposal` binds the exact family/channel and complete ordered + 1..32 profile IDs to the accompanying immutable `SupportProfileSpec` records. +- The review service rejects noncanonical input, stale heads/current sets, changed + immutable profile IDs, incomplete/duplicate coverage, invalid measured gates, and + detected private identifiers or local paths. +- Approval appends new definitions followed by exactly one set assignment through + the shared bounded atomic control-stream helper, then validates the readback. +- Recommendation and execution share one loader-derived eligibility provider. + Execution reopens the support/lifecycle SSOTs and checks the lifecycle-pinned + historical observation before resolving a runner session. +- Newer dormant reviews do not rewrite an already advertised lifecycle snapshot. + +## Current availability + +The three packaged baselines remain Candidate-only with unbound execution. The +screening, support-profile, and public evidence streams remain empty. The default +recommendation therefore abstains, and no adaptive model can execute. + +## Verification + +Focused support-profile governance, bundle/lifecycle, selector, recommendation, +processing, and evidence-activation tests passed locally. Repository-wide required +manifest, documentation, packaging, and pre-push gates are recorded in the pull +request checks for this change. diff --git a/scripts/pre_push.sh b/scripts/pre_push.sh index 1bfdf8ab..dbf8ea42 100755 --- a/scripts/pre_push.sh +++ b/scripts/pre_push.sh @@ -86,6 +86,7 @@ else tests.test_adaptive_bundle_registry \ tests.test_adaptive_algorithm_scout \ tests.test_adaptive_candidate_screening \ + tests.test_adaptive_support_profile_governance \ tests.test_adaptive_managed_output \ tests.test_adaptive_recommendation \ tests.test_adaptive_processing \ diff --git a/tests/test_adaptive_support_profile_governance.py b/tests/test_adaptive_support_profile_governance.py new file mode 100644 index 00000000..1b3fd7b4 --- /dev/null +++ b/tests/test_adaptive_support_profile_governance.py @@ -0,0 +1,651 @@ +from __future__ import annotations + +import copy +import json +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path +from unittest.mock import patch + +from tests.test_adaptive_bundle_contracts import ( + _bundle_payload, + _lifecycle_event, + _registry_payload, + _support_record, +) +from tests.test_adaptive_selector import _environment, _job, _workload +from yolozu.adaptive.bundle_registry import LoadedAlgorithmBundleRegistry +from yolozu.adaptive.bundles import ( + EMPTY_PROFILE_SET_DIGEST, + ZERO_DIGEST, + project_bundle_lifecycle, + project_support_profiles, + validate_algorithm_bundle_registry, + validate_algorithm_bundle_spec, +) +from yolozu.adaptive.canonical import canonical_json_v1, canonical_sha256_v1 +from yolozu.adaptive.qualification import QUALIFICATION_PROTOCOL_FINGERPRINT +from yolozu.adaptive.processing import ( + ProcessingError, + _revalidate_support_profile_before_execution, +) +from yolozu.adaptive.recommendation import _support_observations +from yolozu.adaptive.support_profiles import ( + build_support_profile_eligibility_observation, + load_support_profile_jsonl_bytes, + review_image_pipeline_support_profiles, +) + + +ROOT = Path(__file__).resolve().parents[1] + + +def _profile( + profile_id: str = "cpu-batch", + *, + environment_fingerprint: str = "1" * 64, + workload_fingerprint: str = "2" * 64, + limitation: str = "Exact measured public fixture configuration only.", +) -> dict: + value = { + "schema_version": 1, + "profile_id": profile_id, + "profile_digest": ZERO_DIGEST, + "task": "object_detection", + "environment_fingerprint": environment_fingerprint, + "qualification_workload_fingerprint": workload_fingerprint, + "protocol_fingerprint": QUALIFICATION_PROTOCOL_FINGERPRINT, + "advertised_constraints": { + "execution_mode": "batch", + "max_cold_start_ms": "500", + "max_p95_latency_ms": "50", + "min_repeat_throughput_fps": "1", + }, + "public_limitations": [limitation], + } + value["profile_digest"] = canonical_sha256_v1( + value, + own_digest_field="profile_digest", + ) + return value + + +def _proposal(family_id: str, channel: str, *profiles: dict) -> bytes: + value = { + "schema_version": 1, + "family_id": family_id, + "channel": channel, + "complete_profile_ids": [item["profile_id"] for item in profiles], + "profiles": list(profiles), + } + return canonical_json_v1(value) + b"\n" + + +def _workspace() -> tuple[tempfile.TemporaryDirectory[str], Path, Path]: + temporary = tempfile.TemporaryDirectory() + root = Path(temporary.name) + data = root / "yolozu" / "data" / "adaptive_routing" + data.mkdir(parents=True) + stream = data / "support_profiles.jsonl" + stream.write_bytes(b"") + return temporary, root, stream + + +def _review(root: Path, *, approve: bool = False, **updates: object): + arguments: dict[str, object] = { + "proposal_path": "proposal.json", + "family_id": "example-detector", + "channel": "Experimental", + "workspace_root": root, + "expected_head_digest": ZERO_DIGEST, + "expected_current_profile_set_record_digest": None, + "expected_current_profile_set_digest": None, + "expect_no_current_profile_set": True, + "reviewer_role_id": "repo_maintainer", + "public_review_id": "gh-279", + "reason": "Review one complete dormant public target scope.", + "approve": approve, + "occurred_at": "2026-08-26T00:00:00Z", + } + arguments.update(updates) + return review_image_pipeline_support_profiles(**arguments) + + +class SupportProfileReviewTests(unittest.TestCase): + def test_dry_run_is_zero_write_and_approval_appends_exact_ssot(self) -> None: + temporary, root, stream = _workspace() + self.addCleanup(temporary.cleanup) + profile = _profile() + (root / "proposal.json").write_bytes( + _proposal("example-detector", "Experimental", profile) + ) + before = stream.read_bytes() + + dry_run = _review(root) + self.assertEqual(dry_run.status, "dry_run_ready") + self.assertEqual(stream.read_bytes(), before) + self.assertEqual(len(dry_run.planned_records), 2) + self.assertFalse(dry_run.to_dict()["support_state_changed"]) + + applied = _review(root, approve=True) + self.assertEqual(applied.status, "applied") + self.assertEqual(len(applied.applied_record_digests), 2) + projection = load_support_profile_jsonl_bytes( + stream.read_bytes(), + source_trust_domain="yolozu_managed", + ) + assigned = projection.assignments[("example-detector", "Experimental")] + self.assertEqual(assigned["profiles"][0]["profile_id"], "cpu-batch") + self.assertEqual(projection.head_digest, applied.observed_head_digest) + self.assertEqual(list(root.rglob("*.jsonl")), [stream]) + + def test_replacement_reuses_immutable_definitions_and_keeps_old_bytes(self) -> None: + temporary, root, stream = _workspace() + self.addCleanup(temporary.cleanup) + first = _profile() + (root / "proposal.json").write_bytes( + _proposal("example-detector", "Experimental", first) + ) + initial = _review(root, approve=True) + old_bytes = stream.read_bytes() + second = _profile("cpu-batch-low-latency") + (root / "proposal.json").write_bytes( + _proposal("example-detector", "Experimental", first, second) + ) + replacement = _review( + root, + approve=True, + expected_head_digest=initial.observed_head_digest, + expected_current_profile_set_record_digest=( + initial.observed_current_profile_set_record_digest + ), + expected_current_profile_set_digest=( + initial.observed_current_profile_set_digest + ), + expect_no_current_profile_set=False, + occurred_at="2026-08-26T00:01:00Z", + ) + self.assertEqual(replacement.status, "applied") + self.assertTrue(stream.read_bytes().startswith(old_bytes)) + self.assertEqual( + [item["kind"] for item in replacement.planned_records], + ["profile_definition", "profile_set_assignment"], + ) + + changed = copy.deepcopy(first) + changed["advertised_constraints"]["max_p95_latency_ms"] = "49" + changed["profile_digest"] = canonical_sha256_v1( + changed, + own_digest_field="profile_digest", + ) + (root / "proposal.json").write_bytes( + _proposal("example-detector", "Experimental", changed) + ) + unchanged = stream.read_bytes() + rejected = _review( + root, + approve=True, + expected_head_digest=replacement.observed_head_digest, + expected_current_profile_set_record_digest=( + replacement.observed_current_profile_set_record_digest + ), + expected_current_profile_set_digest=( + replacement.observed_current_profile_set_digest + ), + expect_no_current_profile_set=False, + occurred_at="2026-08-26T00:02:00Z", + ) + self.assertIn("profile_id_reused", [item.code for item in rejected.gates]) + self.assertEqual(stream.read_bytes(), unchanged) + + def test_stale_incomplete_private_and_noncanonical_inputs_fail_closed(self) -> None: + cases = [] + good = _profile() + incomplete = json.loads(_proposal("example-detector", "Experimental", good)) + incomplete["complete_profile_ids"].append("missing-profile") + cases.append((canonical_json_v1(incomplete) + b"\n", {}, "proposal_invalid")) + + duplicate = json.loads(_proposal("example-detector", "Experimental", good)) + duplicate["complete_profile_ids"] = ["cpu-batch", "cpu-batch"] + duplicate["profiles"] = [good, good] + cases.append((canonical_json_v1(duplicate) + b"\n", {}, "proposal_invalid")) + + private = _profile(limitation="Contact owner@example.com from /Users/owner/data.") + cases.append( + ( + _proposal("example-detector", "Experimental", private), + {}, + "proposal_invalid", + ) + ) + cases.append( + ( + json.dumps( + json.loads(_proposal("example-detector", "Experimental", good)), + indent=2, + ).encode() + + b"\n", + {}, + "proposal_invalid", + ) + ) + cases.append( + ( + _proposal("example-detector", "Experimental", good), + {"expected_head_digest": "9" * 64}, + "stale_head", + ) + ) + + for raw, updates, expected_gate in cases: + with self.subTest(expected_gate=expected_gate): + temporary, root, stream = _workspace() + try: + (root / "proposal.json").write_bytes(raw) + outcome = _review(root, approve=True, **updates) + self.assertEqual(outcome.status, "apply_failed") + self.assertIn(expected_gate, [item.code for item in outcome.gates]) + self.assertEqual(stream.read_bytes(), b"") + finally: + temporary.cleanup() + + def test_write_interruption_before_replace_preserves_stream(self) -> None: + temporary, root, stream = _workspace() + self.addCleanup(temporary.cleanup) + (root / "proposal.json").write_bytes( + _proposal("example-detector", "Experimental", _profile()) + ) + + def fail(step: str) -> None: + if step == "before_replace": + raise OSError("injected write interruption") + + outcome = _review(root, approve=True, fault_hook=fail) + self.assertEqual(outcome.status, "apply_failed") + self.assertEqual(stream.read_bytes(), b"") + self.assertFalse(any("stage" in item.name for item in stream.parent.iterdir())) + + def test_readback_mismatch_never_claims_success(self) -> None: + temporary, root, _stream = _workspace() + self.addCleanup(temporary.cleanup) + (root / "proposal.json").write_bytes( + _proposal("example-detector", "Experimental", _profile()) + ) + from yolozu.adaptive import support_profiles as module + + original = module._read_regular + calls = 0 + + def mismatch(*args: object, **kwargs: object) -> bytes: + nonlocal calls + calls += 1 + if calls == 4: + return b"" + return original(*args, **kwargs) + + with patch.object(module, "_read_regular", side_effect=mismatch): + outcome = _review(root, approve=True) + self.assertEqual(outcome.status, "apply_failed") + self.assertEqual(outcome.applied_record_digests, ()) + self.assertIn("atomic_write_failed", [item.code for item in outcome.gates]) + + def test_cli_help_and_dry_run_json(self) -> None: + help_result = subprocess.run( + [ + sys.executable, + "tools/review_image_pipeline_support_profiles.py", + "--help", + ], + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + self.assertEqual(help_result.returncode, 0, help_result.stderr) + self.assertIn("--expect-no-current-profile-set", help_result.stdout) + + +def _provider_fixture(*, include_newer_dormant_review: bool = False): + job = _job() + environment = _environment() + workload = _workload(job) + profile = _profile( + environment_fingerprint=environment.environment_fingerprint, + workload_fingerprint=workload.workload_fingerprint, + ) + definition = _support_record( + sequence=1, + previous=ZERO_DIGEST, + record_id="define-cpu-batch", + kind="profile_definition", + variant={"profile": profile}, + ) + refs = [ + { + "profile_id": profile["profile_id"], + "profile_digest": profile["profile_digest"], + } + ] + assignment = _support_record( + sequence=2, + previous=definition["record_digest"], + record_id="assign-cpu-batch", + kind="profile_set_assignment", + variant={ + "family_id": "example-detector", + "channel": "Experimental", + "profiles": refs, + "profile_set_digest": canonical_sha256_v1(refs), + }, + ) + support_records = [definition, assignment] + if include_newer_dormant_review: + later = _support_record( + sequence=3, + previous=assignment["record_digest"], + record_id="assign-later-dormant", + kind="profile_set_assignment", + variant={ + "family_id": "example-detector", + "channel": "Experimental", + "profiles": refs, + "profile_set_digest": canonical_sha256_v1(refs), + }, + ) + support_records.append(later) + support = project_support_profiles( + support_records, + source_trust_domain="yolozu_managed", + ) + bundle_payload = _bundle_payload() + bundle = validate_algorithm_bundle_spec(bundle_payload) + registry = validate_algorithm_bundle_registry(_registry_payload(bundle_payload)) + reviews = [{"artifact_id": "model", "review_state": "approved"}] + global_event = _lifecycle_event( + sequence=1, + previous=ZERO_DIGEST, + scope="bundle_global", + event_type="register_global", + variant={ + "family_id": "example-detector", + "bundle_spec_digest": bundle.spec_digest, + "artifact_set_digest": bundle.artifact_set_digest, + "bundle_state": "enabled", + "artifact_license_reviews": reviews, + }, + ) + candidate = _lifecycle_event( + sequence=2, + previous=global_event["event_digest"], + scope="channel_assignment", + event_type="candidate_registration", + variant={ + "family_id": "example-detector", + "channel": "Candidate", + "target_bundle_spec_digest": bundle.spec_digest, + "target_artifact_set_digest": bundle.artifact_set_digest, + "target_artifact_license_reviews": reviews, + "support_profile_index_head": support.head_digest, + "profile_set_record_id": None, + "profile_set_record_digest": None, + "profile_set_digest": EMPTY_PROFILE_SET_DIGEST, + "profiles": [], + "evidence_bindings": [], + }, + ) + public = _lifecycle_event( + sequence=3, + previous=candidate["event_digest"], + scope="channel_assignment", + event_type="public_assignment", + variant={ + "family_id": "example-detector", + "channel": "Experimental", + "target_bundle_spec_digest": bundle.spec_digest, + "target_artifact_set_digest": bundle.artifact_set_digest, + "target_artifact_license_reviews": reviews, + "support_profile_index_head": support.head_digest, + "profile_set_record_id": assignment["record_id"], + "profile_set_record_digest": assignment["record_digest"], + "profile_set_digest": canonical_sha256_v1(refs), + "profiles": refs, + "evidence_bindings": [ + { + "profile_id": profile["profile_id"], + "profile_digest": profile["profile_digest"], + "activation_id": "activation-fixture", + "activation_digest": "8" * 64, + "trust_domain_claim": "yolozu_managed", + } + ], + }, + ) + lifecycle = project_bundle_lifecycle( + registry, + [global_event, candidate, public], + source_trust_domain="yolozu_managed", + support_profiles=support, + ) + loaded = LoadedAlgorithmBundleRegistry( + registry=registry, + bundles=(bundle,), + lifecycle=lifecycle, + registry_trust_domain="yolozu_managed", + lifecycle_trust_domain="yolozu_managed", + source_kind="packaged_ssot", + ) + return loaded, support, bundle, job, environment, workload, assignment + + +class SupportProfileProviderTests(unittest.TestCase): + def test_exact_match_site_behavior_and_historical_snapshot(self) -> None: + for newer in (False, True): + with self.subTest(newer_dormant_review=newer): + registry, support, bundle, job, environment, workload, assignment = ( + _provider_fixture(include_newer_dormant_review=newer) + ) + observed = build_support_profile_eligibility_observation( + registry=registry, + profiles=support, + bundle=bundle, + channel="Experimental", + job=job, + environment=environment, + workload=workload, + evidence_trust_domain="yolozu_managed", + support_scope="public_qualified", + ) + self.assertIsNotNone(observed) + self.assertEqual(observed.to_dict()["status"], "matching_one") + self.assertEqual( + observed.to_dict()["profile_set_record_digest"], + assignment["record_digest"], + ) + site = build_support_profile_eligibility_observation( + registry=registry, + profiles=support, + bundle=bundle, + channel="Experimental", + job=job, + environment=environment, + workload=workload, + evidence_trust_domain="site_managed", + support_scope="site_qualified", + ) + self.assertEqual(site.to_dict()["status"], "not_required_site") + + with patch( + "yolozu.adaptive.recommendation._evidence_trust_for_bundle", + return_value=("yolozu_managed", "public_qualified"), + ): + integrated = _support_observations( + registry=registry, + profiles=support, + job=job, + environment=environment, + workload=workload, + evidence={}, + ) + self.assertEqual( + integrated[(bundle.spec_digest, "Experimental")].to_dict(), + observed.to_dict(), + ) + + def test_no_match_untrusted_absent_and_conflict_fail_closed(self) -> None: + registry, support, bundle, job, environment, workload, _assignment = ( + _provider_fixture() + ) + different_job = _job(max_p95_latency_ms="49") + no_match = build_support_profile_eligibility_observation( + registry=registry, + profiles=support, + bundle=bundle, + channel="Experimental", + job=different_job, + environment=environment, + workload=_workload(different_job), + evidence_trust_domain="yolozu_managed", + support_scope="public_qualified", + ) + self.assertEqual(no_match.to_dict()["status"], "no_match") + + raw = b"".join( + canonical_json_v1(item.to_dict()) + b"\n" + for item in support.record_by_digest.values() + ) + untrusted_support = load_support_profile_jsonl_bytes( + raw, + source_trust_domain="operator_asserted", + ) + untrusted = build_support_profile_eligibility_observation( + registry=registry, + profiles=untrusted_support, + bundle=bundle, + channel="Experimental", + job=job, + environment=environment, + workload=workload, + evidence_trust_domain="yolozu_managed", + support_scope="public_qualified", + ) + self.assertEqual(untrusted.to_dict()["status"], "untrusted") + + pointer = registry.lifecycle.channel_pointers[("example-detector", "Experimental")] + assert pointer is not None + original = pointer["profile_set_record_digest"] + pointer["profile_set_record_digest"] = "9" * 64 + absent = build_support_profile_eligibility_observation( + registry=registry, + profiles=support, + bundle=bundle, + channel="Experimental", + job=job, + environment=environment, + workload=workload, + evidence_trust_domain="yolozu_managed", + support_scope="public_qualified", + ) + self.assertEqual(absent.to_dict()["status"], "absent") + pointer["profile_set_record_digest"] = original + pointer["profile_set_digest"] = "7" * 64 + conflict = build_support_profile_eligibility_observation( + registry=registry, + profiles=support, + bundle=bundle, + channel="Experimental", + job=job, + environment=environment, + workload=workload, + evidence_trust_domain="yolozu_managed", + support_scope="public_qualified", + ) + self.assertEqual(conflict.to_dict()["status"], "conflict") + + def test_execution_preflight_rejects_support_pointer_tamper(self) -> None: + registry, support, bundle, job, environment, workload, _assignment = ( + _provider_fixture() + ) + observed = build_support_profile_eligibility_observation( + registry=registry, + profiles=support, + bundle=bundle, + channel="Experimental", + job=job, + environment=environment, + workload=workload, + evidence_trust_domain="yolozu_managed", + support_scope="public_qualified", + ) + assert observed is not None + selected = {"spec_digest": bundle.spec_digest} + pinned = { + "registry_digest": registry.registry.registry_digest, + "lifecycle_projection_digest": registry.lifecycle.head_digest, + } + evaluation = { + "effective_channel": "Experimental", + "evidence": {"trust_domain": "yolozu_managed"}, + "support_scope": "public_qualified", + "support_profile_observation": observed.to_dict(), + } + route = object() + with ( + patch( + "yolozu.adaptive.processing._load_support_profiles", + return_value=support, + ), + patch( + "yolozu.adaptive.processing.load_algorithm_bundle_registry", + return_value=registry, + ), + patch( + "yolozu.adaptive.processing._resolve_execution_route", + return_value=route, + ), + ): + current_bundle, current_route = ( + _revalidate_support_profile_before_execution( + selected=selected, + pinned_record=pinned, + selected_evaluation=evaluation, + bundle=bundle, + job=job, + environment=environment, + workload=workload, + ) + ) + self.assertEqual(current_bundle.spec_digest, bundle.spec_digest) + self.assertIs(current_route, route) + + pointer = registry.lifecycle.channel_pointers[ + ("example-detector", "Experimental") + ] + assert pointer is not None + pointer["profile_set_digest"] = "7" * 64 + with self.assertRaises(ProcessingError) as rejected: + _revalidate_support_profile_before_execution( + selected=selected, + pinned_record=pinned, + selected_evaluation=evaluation, + bundle=bundle, + job=job, + environment=environment, + workload=workload, + ) + self.assertEqual(rejected.exception.code, "selection_stale") + + def test_projection_rejects_chain_gap_and_changed_definition(self) -> None: + _registry, support, _bundle, _job_value, _environment_value, _workload_value, _ = ( + _provider_fixture() + ) + records = [item.to_dict() for item in support.record_by_digest.values()] + gap = copy.deepcopy(records) + gap[1]["sequence"] = 3 + gap[1]["record_digest"] = canonical_sha256_v1( + gap[1], + own_digest_field="record_digest", + ) + with self.assertRaises(ValueError): + project_support_profiles(gap, source_trust_domain="yolozu_managed") diff --git a/tests/test_candidate_artifact_ai_surface.py b/tests/test_candidate_artifact_ai_surface.py index e41f5e5c..e7985aec 100644 --- a/tests/test_candidate_artifact_ai_surface.py +++ b/tests/test_candidate_artifact_ai_surface.py @@ -104,6 +104,8 @@ def test_git_archive_to_clean_venv_outside_checkout(self) -> None: "yolozu/adaptive/processing.py", "yolozu/adaptive/algorithm_scout.py", "yolozu/adaptive/screening.py", + "yolozu/adaptive/support_profiles.py", + "yolozu/adaptive/control_stream.py", "yolozu/adaptive/safe_https.py", "yolozu/data/adaptive_routing/bundle_specs.json", "yolozu/data/adaptive_routing/bundle_lifecycle.jsonl", @@ -117,6 +119,7 @@ def test_git_archive_to_clean_venv_outside_checkout(self) -> None: "yolozu/data/schemas/algorithm_scout_sources.schema.json", "yolozu/data/schemas/algorithm_scout_report.schema.json", "yolozu/data/schemas/candidate_screening_record.schema.json", + "yolozu/data/schemas/support_profile_set_proposal.schema.json", "yolozu/data/integrations/mcp_actions_tool_reference.json", ): self.assertIn(required, sdist_names) diff --git a/tests/test_installed_ai_surface.py b/tests/test_installed_ai_surface.py index a34d7453..341494a2 100644 --- a/tests/test_installed_ai_surface.py +++ b/tests/test_installed_ai_surface.py @@ -156,6 +156,7 @@ def test_fresh_wheel_works_from_external_workspace(self) -> None: "screening_eligibility_observation.schema.json", "support_profile_record.schema.json", "support_profile_spec.schema.json", + "support_profile_set_proposal.schema.json", "support_profile_eligibility_observation.schema.json", "selection_decision.schema.json", ): diff --git a/tests/test_schema_governance.py b/tests/test_schema_governance.py index 725b6046..4427f4a6 100644 --- a/tests/test_schema_governance.py +++ b/tests/test_schema_governance.py @@ -32,6 +32,7 @@ def test_adaptive_routing_schema_copies_and_browser_are_current(self): "candidate_screening_record_json": "candidate_screening_record.schema.json", "support_profile_spec_json": "support_profile_spec.schema.json", "support_profile_record_json": "support_profile_record.schema.json", + "support_profile_set_proposal_json": "support_profile_set_proposal.schema.json", "local_artifact_inventory_json": "local_artifact_inventory.schema.json", "qualification_report_json": "qualification_report.schema.json", "evidence_activation_record_json": "evidence_activation_record.schema.json", @@ -112,6 +113,7 @@ def test_adaptive_evidence_contracts_are_required_by_ci_and_release(self): for suite in ( "tests.test_adaptive_evidence_contracts", "tests.test_adaptive_candidate_screening", + "tests.test_adaptive_support_profile_governance", "tests.test_adaptive_selection_contracts", "tests.test_schema_governance", ): @@ -126,9 +128,11 @@ def test_adaptive_evidence_contracts_are_required_by_ci_and_release(self): "yolozu/data/schemas/candidate_screening_record.schema.json", "yolozu/data/schemas/screening_eligibility_observation.schema.json", "yolozu/data/schemas/support_profile_eligibility_observation.schema.json", + "yolozu/data/schemas/support_profile_set_proposal.schema.json", "yolozu/data/schemas/selection_decision.schema.json", "yolozu/data/adaptive_routing/evidence_activation.jsonl", "yolozu/data/adaptive_routing/candidate_screening.jsonl", + "yolozu/data/adaptive_routing/support_profiles.jsonl", ): self.assertGreaterEqual(publish.count(resource), 2) diff --git a/tools/manifest.json b/tools/manifest.json index 3b669a51..8941e0db 100644 --- a/tools/manifest.json +++ b/tools/manifest.json @@ -254,6 +254,10 @@ "schema": "docs/schemas/support_profile_record.schema.json", "summary": "Append-only reviewed exact-environment support-profile definition and dormant-set record." }, + "support_profile_set_proposal_json": { + "schema": "docs/schemas/support_profile_set_proposal.schema.json", + "summary": "Canonical public review input that exactly covers one complete ordered dormant support-profile set." + }, "support_profile_spec_json": { "schema": "docs/schemas/support_profile_spec.schema.json", "summary": "Immutable exact measured environment, workload, protocol, and advertised-gate scope; it does not extrapolate hardware families." @@ -14422,6 +14426,153 @@ "license" ] }, + { + "contracts": { + "consumes": [ + "support_profile_set_proposal_json", + "support_profile_spec_json", + "support_profile_record_json" + ], + "produces": [ + "support_profile_record_json" + ] + }, + "docs": [ + "README.md", + "Readme_jp.md", + "docs/adaptive_image_routing.md", + "docs/schemas/support_profile_set_proposal.schema.json", + "docs/schemas/support_profile_record.schema.json" + ], + "effects": { + "fixed_writes": [ + { + "description": "With --approve, atomically appends only to the canonical dormant support-profile SSOT.", + "kind": "file", + "path": "yolozu/data/adaptive_routing/support_profiles.jsonl", + "scope": "path" + } + ], + "writes": [] + }, + "entrypoint": "tools/review_image_pipeline_support_profiles.py", + "examples": [ + { + "command": "python3 tools/review_image_pipeline_support_profiles.py --proposal reports/support_profile_set_proposal.json --family-id yolox --channel Experimental --expected-head-digest --expect-no-current-profile-set --reviewer-role-id repo_maintainer --public-review-id gh- --reason 'Review complete dormant target scope'", + "description": "Dry-run every complete-set, immutable-definition, review, and stale-head gate; no record is written." + }, + { + "command": "python3 tools/review_image_pipeline_support_profiles.py --help", + "description": "Inspect the Experimental dormant support-profile review interface contract." + } + ], + "id": "review_image_pipeline_support_profiles", + "inputs": [ + { + "flag": "--proposal", + "kind": "file", + "name": "proposal", + "required": false + }, + { + "flag": "--family-id", + "kind": "string", + "name": "family_id", + "required": false + }, + { + "flag": "--channel", + "kind": "string", + "name": "channel", + "required": false + }, + { + "flag": "--expected-head-digest", + "kind": "string", + "name": "expected_head_digest", + "required": false + }, + { + "flag": "--expected-current-profile-set-record-digest", + "kind": "string", + "name": "expected_current_profile_set_record_digest", + "required": false + }, + { + "flag": "--expected-current-profile-set-digest", + "kind": "string", + "name": "expected_current_profile_set_digest", + "required": false + }, + { + "flag": "--expect-no-current-profile-set", + "kind": "string", + "name": "expect_no_current_profile_set", + "required": false + }, + { + "flag": "--reviewer-role-id", + "kind": "string", + "name": "reviewer_role_id", + "required": false + }, + { + "flag": "--public-review-id", + "kind": "string", + "name": "public_review_id", + "required": false + }, + { + "flag": "--reason", + "kind": "string", + "name": "reason", + "required": false + }, + { + "default": ".", + "flag": "--workspace", + "kind": "dir", + "name": "workspace", + "required": false + }, + { + "flag": "--approve", + "kind": "string", + "name": "approve", + "required": false + } + ], + "maturity": "experimental", + "outputs": [ + { + "default": "stdout", + "description": "Bounded machine-readable dry-run or apply outcome that states dormant-only scope.", + "kind": "stdout", + "name": "support_profile_review_outcome" + }, + { + "default": "yolozu/data/adaptive_routing/support_profiles.jsonl", + "description": "Canonical append-only reviewed dormant support-profile stream after explicit approval.", + "kind": "file", + "name": "support_profile_stream" + } + ], + "platform": { + "cpu_ok": true, + "gpu_required": false, + "linux_ok": true, + "macos_ok": true + }, + "runner": "python3", + "summary": "Dry-run or atomically append one complete reviewed dormant exact-measured support-profile set; review alone never changes lifecycle support or availability.", + "tags": [ + "adaptive-inference", + "experimental", + "review", + "support-profile", + "ssot" + ] + }, { "docs": [ "docs/checkpoint_compatibility.md", diff --git a/tools/review_image_pipeline_support_profiles.py b/tools/review_image_pipeline_support_profiles.py new file mode 100644 index 00000000..e4c3c8f1 --- /dev/null +++ b/tools/review_image_pipeline_support_profiles.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +"""Repository wrapper for reviewed dormant support-profile sets.""" + +from __future__ import annotations + +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) +from yolozu.cli_entry import main + + +if __name__ == "__main__": + raise SystemExit( + main(["review-image-pipeline-support-profiles", *sys.argv[1:]]) + ) diff --git a/tools/yolozu.py b/tools/yolozu.py index 03010b97..f872b09c 100644 --- a/tools/yolozu.py +++ b/tools/yolozu.py @@ -53,6 +53,7 @@ "parity", "predictions", "qualify-image-pipeline", + "review-image-pipeline-support-profiles", "scout-algorithms", "resources", "test", @@ -1416,6 +1417,10 @@ def _parse_args(argv: list[str]) -> argparse.Namespace: "qualify-image-pipeline", "Delegate to yolozu package CLI qualification command.", ), + ( + "review-image-pipeline-support-profiles", + "Delegate to the reviewed dormant support-profile set command.", + ), ( "scout-algorithms", "Delegate to the Experimental monitored-source candidate inbox command.", diff --git a/yolozu/adaptive/__init__.py b/yolozu/adaptive/__init__.py index 55979452..40f09396 100644 --- a/yolozu/adaptive/__init__.py +++ b/yolozu/adaptive/__init__.py @@ -40,6 +40,7 @@ validate_bundle_lifecycle_record, validate_support_profile_record, validate_support_profile_spec, + validate_support_profile_snapshot, ) from .bundle_registry import ( AlgorithmRunner, @@ -141,6 +142,13 @@ evidence_eligibility_from_projection, select_qualified_pipeline, ) +from .support_profiles import ( + MAX_SUPPORT_PROFILE_STREAM_BYTES, + SupportProfileReviewOutcome, + build_support_profile_eligibility_observation, + load_support_profile_jsonl_bytes, + review_image_pipeline_support_profiles, +) __all__ = [ "AlgorithmBundleRegistry", @@ -173,6 +181,7 @@ "ManagedOutputTransaction", "MAX_SCREENING_RECORDS", "MAX_SCREENING_STREAM_BYTES", + "MAX_SUPPORT_PROFILE_STREAM_BYTES", "PinnedArtifactSet", "PinnedVerifiedArtifactSet", "PinnedInput", @@ -188,6 +197,7 @@ "ScreeningEligibilityObservation", "SelectionDecision", "SupportProfileProjection", + "SupportProfileReviewOutcome", "SupportProfileEligibilityObservation", "SupportProfileRecord", "SupportProfileSpec", @@ -195,6 +205,7 @@ "VerifiedArtifactSet", "build_fixed_class_mapping", "build_screening_eligibility_observation", + "build_support_profile_eligibility_observation", "build_decoded_input_inventory", "build_environment_profile", "build_qualification_workload_profile", @@ -217,6 +228,7 @@ "load_evidence_activation_jsonl", "load_evidence_activation_jsonl_bytes", "load_candidate_screening_jsonl_bytes", + "load_support_profile_jsonl_bytes", "map_fixed_class_outputs", "map_text_prompt_outputs", "nanoseconds_to_milliseconds", @@ -225,6 +237,7 @@ "project_candidate_screening_records", "project_evidence_activations", "project_support_profiles", + "review_image_pipeline_support_profiles", "process_images", "qualification_input_schedule", "qualify_image_pipeline", @@ -248,4 +261,5 @@ "validate_support_profile_eligibility_observation", "validate_support_profile_record", "validate_support_profile_spec", + "validate_support_profile_snapshot", ] diff --git a/yolozu/adaptive/activation.py b/yolozu/adaptive/activation.py index 1e8403a2..709ec919 100644 --- a/yolozu/adaptive/activation.py +++ b/yolozu/adaptive/activation.py @@ -22,6 +22,7 @@ from .bundles import ZERO_DIGEST from .canonical import canonical_json_v1, canonical_sha256_v1 from .control_records import load_bounded_json_bytes +from .control_stream import atomic_replace_control_stream from .evidence import ( MAX_EVIDENCE_ACTIVATION_BYTES, QualificationReport, @@ -503,92 +504,6 @@ def _event( return value -def _atomic_replace_stream( - *, - path: Path, - observed_bytes: bytes, - replacement_bytes: bytes, - fault_hook: FaultHook | None, -) -> None: - if len(replacement_bytes) > MAX_EVIDENCE_ACTIVATION_BYTES: - raise ValueError("evidence activation stream exceeds 64 MiB") - nofollow = getattr(os, "O_NOFOLLOW", 0) - directory_flag = getattr(os, "O_DIRECTORY", 0) - if os.name != "posix" or not nofollow or not directory_flag: - raise ValueError("atomic activation requires POSIX no-follow primitives") - parent_fd = os.open(path.parent, os.O_RDONLY | directory_flag | nofollow) - temporary = f".{path.name}.stage.{secrets.token_hex(16)}" - descriptor: int | None = None - published = False - try: - try: - before = os.stat(path.name, dir_fd=parent_fd, follow_symlinks=False) - except FileNotFoundError: - before = None - if before is not None and ( - not stat.S_ISREG(before.st_mode) or before.st_nlink != 1 - ): - raise ValueError("activation stream must be one singly linked regular file") - current = b"" if before is None else _read_regular( - path, maximum_bytes=MAX_EVIDENCE_ACTIVATION_BYTES, label="activation stream" - ) - if current != observed_bytes: - raise ValueError("activation stream changed after dry-run validation") - if fault_hook is not None: - fault_hook("before_stage_open") - descriptor = os.open( - temporary, - os.O_WRONLY | os.O_CREAT | os.O_EXCL | nofollow, - 0o600, - dir_fd=parent_fd, - ) - written = 0 - while written < len(replacement_bytes): - count = os.write(descriptor, replacement_bytes[written:]) - if count <= 0: - raise OSError("short activation-stream write") - written += count - os.fsync(descriptor) - os.close(descriptor) - descriptor = None - if fault_hook is not None: - fault_hook("before_replace") - try: - latest = os.stat(path.name, dir_fd=parent_fd, follow_symlinks=False) - except FileNotFoundError: - latest = None - if (before is None) != (latest is None): - raise ValueError("activation stream changed before commit") - if before is not None and latest is not None and ( - before.st_dev, - before.st_ino, - before.st_size, - before.st_mtime_ns, - ) != ( - latest.st_dev, - latest.st_ino, - latest.st_size, - latest.st_mtime_ns, - ): - raise ValueError("activation stream identity changed before commit") - os.replace(temporary, path.name, src_dir_fd=parent_fd, dst_dir_fd=parent_fd) - published = True - os.fsync(parent_fd) - if fault_hook is not None: - fault_hook("after_replace") - finally: - if descriptor is not None: - os.close(descriptor) - if not published: - try: - info = os.stat(temporary, dir_fd=parent_fd, follow_symlinks=False) - except FileNotFoundError: - info = None - if info is not None and stat.S_ISREG(info.st_mode) and info.st_nlink == 1: - os.unlink(temporary, dir_fd=parent_fd) - os.close(parent_fd) - - def activate_qualification_evidence( *, operation: str, @@ -993,10 +908,12 @@ def activate_qualification_evidence( appended = b"".join(canonical_json_v1(item) + b"\n" for item in planned) try: - _atomic_replace_stream( + atomic_replace_control_stream( path=stream, observed_bytes=raw_stream, replacement_bytes=raw_stream + appended, + maximum_bytes=MAX_EVIDENCE_ACTIVATION_BYTES, + label="activation stream", fault_hook=fault_hook, ) readback = _read_regular( diff --git a/yolozu/adaptive/bundles.py b/yolozu/adaptive/bundles.py index b2ab0195..7c74d396 100644 --- a/yolozu/adaptive/bundles.py +++ b/yolozu/adaptive/bundles.py @@ -3,6 +3,7 @@ from __future__ import annotations import copy +import ipaddress import re import unicodedata from dataclasses import dataclass @@ -30,6 +31,7 @@ "validate_bundle_lifecycle_record", "validate_support_profile_record", "validate_support_profile_spec", + "validate_support_profile_snapshot", ] @@ -46,6 +48,18 @@ _ID_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:+-]{0,127}\Z") _ROLE_ID_RE = re.compile(r"(?:repo_maintainer|release_reviewer|site_operator|automation)\Z") _UTC_RE = re.compile(r"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z\Z") +_UUID_RE = re.compile( + r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-" + r"[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" +) +_EMAIL_RE = re.compile( + r"(? str: return value +def _public_text(value: Any, *, field: str, maximum_bytes: int) -> str: + text = _safe_text(value, field=field, maximum_bytes=maximum_bytes) + if any(unicodedata.category(character).startswith("C") for character in text): + raise ValueError(f"{field}: private control/format characters are invalid") + if _UUID_RE.search(text) or _EMAIL_RE.search(text) or _ABSOLUTE_PATH_RE.search(text): + raise ValueError(f"{field}: private identifiers and paths are invalid") + for match in _IP_TOKEN_RE.finditer(text): + token = match.group(0).strip(".:") + try: + ipaddress.ip_address(token) + except ValueError: + continue + raise ValueError(f"{field}: IP addresses are invalid") + return text + + def _utc(value: Any, *, field: str) -> str: if not isinstance(value, str) or _UTC_RE.fullmatch(value) is None: raise ValueError(f"{field}: expected exact RFC3339 UTC second") @@ -1072,7 +1102,7 @@ def validate_support_profile_spec(value: Mapping[str, Any]) -> SupportProfileSpe if record["schema_version"] != 1 or isinstance(record["schema_version"], bool): raise ValueError("SupportProfileSpec.schema_version: expected 1") limitations = [ - _safe_text(item, field="public_limitations[]", maximum_bytes=512) + _public_text(item, field="public_limitations[]", maximum_bytes=512) for item in _list( record["public_limitations"], field="public_limitations", @@ -1798,9 +1828,10 @@ def validate_bundle_lifecycle_record( return BundleLifecycleRecord(normalized, trust) -def _assert_support_snapshot( +def validate_support_profile_snapshot( event: dict[str, Any], support_profiles: SupportProfileProjection ) -> None: + """Require one lifecycle pointer to bind an exact historical set snapshot.""" head = event["support_profile_index_head"] if head != ZERO_DIGEST and head not in support_profiles.record_by_digest: raise ValueError("lifecycle assignment references unknown support-profile head") @@ -1919,7 +1950,7 @@ def project_bundle_lifecycle( raise ValueError( "public assignment requires a validated support-profile projection" ) - _assert_support_snapshot(event, support_profiles) + validate_support_profile_snapshot(event, support_profiles) elif support_profiles is not None: head = event["support_profile_index_head"] if head != ZERO_DIGEST and head not in support_profiles.record_by_digest: diff --git a/yolozu/adaptive/control_stream.py b/yolozu/adaptive/control_stream.py new file mode 100644 index 00000000..d5e6d88f --- /dev/null +++ b/yolozu/adaptive/control_stream.py @@ -0,0 +1,151 @@ +"""Atomic replacement helper for bounded append-only control streams.""" + +from __future__ import annotations + +import os +import secrets +import stat +from pathlib import Path +from typing import Callable + +__all__ = ["atomic_replace_control_stream"] + + +FaultHook = Callable[[str], None] + + +def _read_regular(path: Path, *, maximum_bytes: int, label: str) -> bytes: + before = os.stat(path, follow_symlinks=False) + if not stat.S_ISREG(before.st_mode) or before.st_nlink != 1: + raise ValueError(f"{label} must be one singly linked regular file") + if before.st_size > maximum_bytes: + raise ValueError(f"{label} exceeds its byte limit") + flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0) + descriptor = os.open(path, flags) + try: + opened = os.fstat(descriptor) + identity = (before.st_dev, before.st_ino, before.st_size, before.st_mtime_ns) + if identity != ( + opened.st_dev, + opened.st_ino, + opened.st_size, + opened.st_mtime_ns, + ): + raise ValueError(f"{label} changed while opening") + chunks: list[bytes] = [] + total = 0 + while True: + chunk = os.read(descriptor, min(1024 * 1024, maximum_bytes + 1 - total)) + if not chunk: + break + total += len(chunk) + if total > maximum_bytes: + raise ValueError(f"{label} exceeds its byte limit") + chunks.append(chunk) + after = os.fstat(descriptor) + if total != after.st_size or identity != ( + after.st_dev, + after.st_ino, + after.st_size, + after.st_mtime_ns, + ): + raise ValueError(f"{label} changed while reading") + finally: + os.close(descriptor) + return b"".join(chunks) + + +def atomic_replace_control_stream( + *, + path: Path, + observed_bytes: bytes, + replacement_bytes: bytes, + maximum_bytes: int, + label: str, + fault_hook: FaultHook | None = None, +) -> None: + """Publish exact replacement bytes after revalidating the observed stream. + + The caller owns semantic append-only validation. This helper owns the + no-follow, compare-before-commit, same-directory replacement boundary. + """ + + if len(replacement_bytes) > maximum_bytes: + raise ValueError(f"{label} exceeds its byte limit") + nofollow = getattr(os, "O_NOFOLLOW", 0) + directory_flag = getattr(os, "O_DIRECTORY", 0) + if os.name != "posix" or not nofollow or not directory_flag: + raise ValueError("atomic control-stream replacement requires POSIX no-follow primitives") + parent_fd = os.open(path.parent, os.O_RDONLY | directory_flag | nofollow) + temporary = f".{path.name}.stage.{secrets.token_hex(16)}" + descriptor: int | None = None + published = False + try: + try: + before = os.stat(path.name, dir_fd=parent_fd, follow_symlinks=False) + except FileNotFoundError: + before = None + if before is not None and ( + not stat.S_ISREG(before.st_mode) or before.st_nlink != 1 + ): + raise ValueError(f"{label} must be one singly linked regular file") + current = ( + b"" + if before is None + else _read_regular(path, maximum_bytes=maximum_bytes, label=label) + ) + if current != observed_bytes: + raise ValueError(f"{label} changed after dry-run validation") + if fault_hook is not None: + fault_hook("before_stage_open") + descriptor = os.open( + temporary, + os.O_WRONLY | os.O_CREAT | os.O_EXCL | nofollow, + 0o600, + dir_fd=parent_fd, + ) + written = 0 + while written < len(replacement_bytes): + count = os.write(descriptor, replacement_bytes[written:]) + if count <= 0: + raise OSError("short control-stream write") + written += count + os.fsync(descriptor) + os.close(descriptor) + descriptor = None + if fault_hook is not None: + fault_hook("before_replace") + try: + latest = os.stat(path.name, dir_fd=parent_fd, follow_symlinks=False) + except FileNotFoundError: + latest = None + if (before is None) != (latest is None): + raise ValueError(f"{label} changed before commit") + if before is not None and latest is not None and ( + before.st_dev, + before.st_ino, + before.st_size, + before.st_mtime_ns, + ) != ( + latest.st_dev, + latest.st_ino, + latest.st_size, + latest.st_mtime_ns, + ): + raise ValueError(f"{label} identity changed before commit") + os.replace(temporary, path.name, src_dir_fd=parent_fd, dst_dir_fd=parent_fd) + published = True + os.fsync(parent_fd) + if fault_hook is not None: + fault_hook("after_replace") + finally: + if descriptor is not None: + os.close(descriptor) + if not published: + try: + info = os.stat(temporary, dir_fd=parent_fd, follow_symlinks=False) + except FileNotFoundError: + info = None + if info is not None and stat.S_ISREG(info.st_mode) and info.st_nlink == 1: + os.unlink(temporary, dir_fd=parent_fd) + os.close(parent_fd) diff --git a/yolozu/adaptive/processing.py b/yolozu/adaptive/processing.py index 1c0d2797..0bacc12e 100644 --- a/yolozu/adaptive/processing.py +++ b/yolozu/adaptive/processing.py @@ -21,7 +21,9 @@ HANDOFF_MAX_MASK_ARTIFACTS, HANDOFF_MAX_OUTPUT_BYTES, HANDOFF_MAX_OUTPUT_FILES, + EnvironmentProfile, ImageJobSpec, + QualificationWorkloadProfile, build_qualification_workload_profile, validate_image_job_spec, ) @@ -58,6 +60,7 @@ recommend_image_pipeline, ) from .selection import SelectionDecision, validate_selection_decision +from .support_profiles import build_support_profile_eligibility_observation __all__ = [ "IsolatedRunnerCapability", @@ -215,6 +218,63 @@ def _resolve_execution_route( return _ExecutionRoute("third_party_isolated", isolated_service=service) +def _revalidate_support_profile_before_execution( + *, + selected: Mapping[str, Any], + pinned_record: Mapping[str, Any], + selected_evaluation: Mapping[str, Any], + bundle: AlgorithmBundleSpec, + job: ImageJobSpec, + environment: EnvironmentProfile, + workload: QualificationWorkloadProfile, +) -> tuple[AlgorithmBundleSpec, _ExecutionRoute]: + """Reproject the lifecycle-pinned historical support set before runner use.""" + + latest_profiles = _load_support_profiles() + latest_registry = load_algorithm_bundle_registry( + support_profiles=latest_profiles, + ) + if ( + latest_registry.registry.registry_digest != pinned_record["registry_digest"] + or latest_registry.lifecycle.head_digest + != pinned_record["lifecycle_projection_digest"] + ): + raise _fail( + "selection_stale", + "the bundle or lifecycle projection changed before execution", + ) + latest_bundle = latest_registry.by_spec_digest().get(selected["spec_digest"]) + if latest_bundle is None or latest_bundle.to_dict() != bundle.to_dict(): + raise _fail( + "selection_stale", + "the selected bundle changed before execution", + ) + evidence_identity = selected_evaluation["evidence"] + expected_support = selected_evaluation["support_profile_observation"] + if evidence_identity is None or expected_support is None: + raise _fail( + "selection_stale", + "the selected support/evidence identity is missing", + ) + observed_support = build_support_profile_eligibility_observation( + registry=latest_registry, + profiles=latest_profiles, + bundle=latest_bundle, + channel=selected_evaluation["effective_channel"], + job=job, + environment=environment, + workload=workload, + evidence_trust_domain=evidence_identity["trust_domain"], + support_scope=selected_evaluation["support_scope"], + ) + if observed_support is None or observed_support.to_dict() != expected_support: + raise _fail( + "selection_stale", + "the lifecycle-pinned support profile changed before execution", + ) + return latest_bundle, _resolve_execution_route(latest_bundle) + + def _decimal(value: Any, *, field: str) -> Decimal: if not isinstance(value, str): raise _fail("invalid_runner_output", f"{field} is not canonical decimal text") @@ -588,7 +648,16 @@ def process_images( bundle = registry.by_spec_digest().get(selected["spec_digest"]) if bundle is None: raise _fail("selection_stale", "the selected bundle is no longer registered") - route = _resolve_execution_route(bundle) + selected_evaluation = next( + ( + item + for item in pinned_record["candidate_evaluations"] + if item["rank_state"] == "selected" + ), + None, + ) + if selected_evaluation is None: + raise _fail("selection_stale", "the selected candidate evaluation is missing") try: environment = build_environment_profile(collected_at=now) @@ -651,6 +720,19 @@ def process_images( ): raise _fail("selection_stale", "the selected artifact state changed") + # A later dormant review is valid, but the lifecycle-pinned + # historical observation must still match immediately before + # a runner route is resolved. + bundle, route = _revalidate_support_profile_before_execution( + selected=selected, + pinned_record=pinned_record, + selected_evaluation=selected_evaluation, + bundle=bundle, + job=job, + environment=environment, + workload=workload, + ) + def session_factory() -> _RunnerSession: if route.kind == "code_owned_audited": assert route.host_factory is not None diff --git a/yolozu/adaptive/recommendation.py b/yolozu/adaptive/recommendation.py index 82c93401..b0070a5d 100644 --- a/yolozu/adaptive/recommendation.py +++ b/yolozu/adaptive/recommendation.py @@ -16,7 +16,6 @@ from .bundles import ( AlgorithmBundleSpec, SupportProfileProjection, - project_support_profiles, ) from .canonical import canonical_sha256_v1 from .contracts import ( @@ -29,7 +28,6 @@ from .control_records import ( MAX_CONTROL_STREAM_BYTES, load_bounded_json_bytes, - load_bounded_jsonl_bytes, ) from .environment import build_environment_profile from .evidence import ( @@ -50,10 +48,7 @@ QUALIFICATION_PROTOCOL_FINGERPRINT, qualification_report_has_code_owned_issuer, ) -from .selection import ( - SupportProfileEligibilityObservation, - validate_support_profile_eligibility_observation, -) +from .selection import SupportProfileEligibilityObservation from .screening import ( CandidateScreeningProjection, MAX_SCREENING_STREAM_BYTES, @@ -63,10 +58,14 @@ from .selector import ( EvidenceEligibilityObservation, IsolationCapabilityObservation, - compute_advertised_gates_digest, evidence_eligibility_from_projection, select_qualified_pipeline, ) +from .support_profiles import ( + MAX_SUPPORT_PROFILE_STREAM_BYTES, + build_support_profile_eligibility_observation, + load_support_profile_jsonl_bytes, +) __all__ = [ "RecommendationError", @@ -74,7 +73,6 @@ ] -_MAX_SUPPORT_PROFILE_BYTES = 64 * 1024 * 1024 _MAX_QUALIFICATION_REPORT_BYTES = 64 * 1024 * 1024 _MAX_CHECKSUM_MANIFEST_BYTES = 4 * 1024 * 1024 _ZERO_DIGEST = "0" * 64 @@ -265,17 +263,12 @@ def _packaged_bytes(parts: tuple[str, ...], *, maximum_bytes: int, label: str) - def _load_support_profiles() -> SupportProfileProjection: payload = _packaged_bytes( ("adaptive_routing", "support_profiles.jsonl"), - maximum_bytes=_MAX_SUPPORT_PROFILE_BYTES, + maximum_bytes=MAX_SUPPORT_PROFILE_STREAM_BYTES, label="support-profile stream", ) try: - records = load_bounded_jsonl_bytes( + return load_support_profile_jsonl_bytes( payload, - label="support-profile stream", - max_records=128, - ) - return project_support_profiles( - records, source_trust_domain="yolozu_managed", ) except (TypeError, ValueError) as exc: @@ -583,9 +576,7 @@ def _support_observations( evidence: Mapping[str, EvidenceEligibilityObservation], ) -> dict[tuple[str, str], SupportProfileEligibilityObservation]: observations: dict[tuple[str, str], SupportProfileEligibilityObservation] = {} - advertised_digest = compute_advertised_gates_digest(job) for bundle in registry.bundles: - bundle_record = bundle.to_dict() evidence_trust, support_scope = _evidence_trust_for_bundle( bundle=bundle, environment=environment, @@ -593,94 +584,19 @@ def _support_observations( evidence=evidence, ) for channel in ("Experimental", "Stable"): - pointer = registry.lifecycle.channel_pointers.get( - (bundle_record["family_id"], channel) - ) - if pointer is None or pointer["bundle_spec_digest"] != bundle.spec_digest: - continue - if any( - pointer.get(field) is None - for field in ( - "profile_set_record_id", - "profile_set_record_digest", - ) - ): - continue - matching: list[dict[str, Any]] = [] - for reference in pointer["profiles"]: - profile = profiles.definitions.get(reference["profile_id"]) - if profile is None or profile.profile_digest != reference["profile_digest"]: - continue - value = profile.to_dict() - if ( - value["task"] == job.to_dict()["task"] - and value["environment_fingerprint"] - == environment.environment_fingerprint - and value["qualification_workload_fingerprint"] - == workload.workload_fingerprint - and value["protocol_fingerprint"] - == QUALIFICATION_PROTOCOL_FINGERPRINT - and canonical_sha256_v1(value["advertised_constraints"]) - == advertised_digest - ): - matching.append(value) - if evidence_trust == "site_managed" and support_scope == "site_qualified": - status = "not_required_site" - elif len(matching) == 1: - status = "matching_one" - elif len(matching) > 1: - status = "conflict" - else: - status = "no_match" - matched = matching[0] if status == "matching_one" else None - value = { - "schema_version": 1, - "provider_id": "support_profile_projection", - "provider_version": "1", - "family_id": bundle_record["family_id"], - "bundle_spec_digest": bundle.spec_digest, - "channel": channel, - "lifecycle_assignment_id": ( - "assignment-" + pointer["lifecycle_event_digest"][:16] - ), - "lifecycle_assignment_digest": pointer["lifecycle_event_digest"], - "support_profile_index_head_digest": pointer[ - "support_profile_index_head" - ], - "profile_set_record_id": pointer["profile_set_record_id"], - "profile_set_record_digest": pointer["profile_set_record_digest"], - "profile_set_digest": pointer["profile_set_digest"], - "status": status, - "profile_id": None if matched is None else matched["profile_id"], - "profile_digest": None if matched is None else matched["profile_digest"], - "environment_fingerprint": ( - None if matched is None else environment.environment_fingerprint - ), - "qualification_workload_fingerprint": ( - None if matched is None else workload.workload_fingerprint - ), - "protocol_fingerprint": ( - None if matched is None else QUALIFICATION_PROTOCOL_FINGERPRINT - ), - "advertised_gates_digest": ( - None if matched is None else advertised_digest - ), - "trust_domain": ( - "yolozu_managed" if matched is not None else "unknown" - ), - "observation_digest": _ZERO_DIGEST, - } - value["observation_digest"] = canonical_sha256_v1( - value, - own_digest_field="observation_digest", - ) - observations[(bundle.spec_digest, channel)] = ( - validate_support_profile_eligibility_observation( - value, - evidence_trust_domain=evidence_trust, - support_scope=support_scope, - ) + observed = build_support_profile_eligibility_observation( + registry=registry, + profiles=profiles, + bundle=bundle, + channel=channel, + job=job, + environment=environment, + workload=workload, + evidence_trust_domain=evidence_trust, + support_scope=support_scope, ) + if observed is not None: + observations[(bundle.spec_digest, channel)] = observed return observations diff --git a/yolozu/adaptive/support_profiles.py b/yolozu/adaptive/support_profiles.py new file mode 100644 index 00000000..4181fa03 --- /dev/null +++ b/yolozu/adaptive/support_profiles.py @@ -0,0 +1,828 @@ +"""Reviewed dormant support-profile sets and their sole eligibility provider.""" + +from __future__ import annotations + +import os +import re +import stat +from dataclasses import dataclass +from datetime import datetime, timezone +from pathlib import Path +from typing import Any, Callable, Literal, Mapping, Sequence + +from .bundle_registry import LoadedAlgorithmBundleRegistry +from .bundles import ( + ZERO_DIGEST, + AlgorithmBundleSpec, + SupportProfileProjection, + SupportProfileSpec, + project_support_profiles, + validate_support_profile_record, + validate_support_profile_spec, +) +from .canonical import canonical_json_v1, canonical_sha256_v1 +from .contracts import EnvironmentProfile, ImageJobSpec, QualificationWorkloadProfile +from .control_records import load_bounded_json_bytes, load_bounded_jsonl_bytes +from .control_stream import atomic_replace_control_stream +from .qualification import QUALIFICATION_PROTOCOL_FINGERPRINT +from .selection import ( + SupportProfileEligibilityObservation, + validate_support_profile_eligibility_observation, +) +from .selector import compute_advertised_gates_digest + +__all__ = [ + "MAX_SUPPORT_PROFILE_STREAM_BYTES", + "SupportProfileReviewOutcome", + "build_support_profile_eligibility_observation", + "load_support_profile_jsonl_bytes", + "review_image_pipeline_support_profiles", +] + + +MAX_SUPPORT_PROFILE_STREAM_BYTES = 64 * 1024 * 1024 +MAX_SUPPORT_PROFILE_RECORDS = 128 +CANONICAL_SUPPORT_PROFILE_STREAM = Path( + "yolozu/data/adaptive_routing/support_profiles.jsonl" +) + +_SHA256_RE = re.compile(r"[0-9a-f]{64}\Z") +_COMPONENT_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,127}\Z") +_REVIEW_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:+-]{0,127}\Z") +_REVIEWER_ROLES = frozenset({"repo_maintainer", "release_reviewer"}) +_PUBLIC_CHANNELS = frozenset({"Experimental", "Stable"}) + +FaultHook = Callable[[str], None] + + +@dataclass(frozen=True) +class _Gate: + code: str + detail: str + + def to_dict(self) -> dict[str, str]: + return {"code": self.code, "detail": self.detail} + + +@dataclass(frozen=True) +class SupportProfileReviewOutcome: + """Bounded dry-run/apply result for one dormant complete set review.""" + + status: Literal["dry_run_ready", "dry_run_blocked", "applied", "apply_failed"] + approved: bool + family_id: str | None + channel: str | None + observed_head_digest: str + observed_current_profile_set_record_digest: str | None + observed_current_profile_set_digest: str | None + proposed_profile_set_digest: str | None + gates: tuple[_Gate, ...] + planned_records: tuple[dict[str, Any], ...] + applied_record_digests: tuple[str, ...] + + def to_dict(self) -> dict[str, Any]: + return { + "schema_version": 1, + "kind": "support_profile_review_outcome", + "status": self.status, + "approved": self.approved, + "family_id": self.family_id, + "channel": self.channel, + "observed_head_digest": self.observed_head_digest, + "observed_current_profile_set_record_digest": ( + self.observed_current_profile_set_record_digest + ), + "observed_current_profile_set_digest": ( + self.observed_current_profile_set_digest + ), + "proposed_profile_set_digest": self.proposed_profile_set_digest, + "gates": [item.to_dict() for item in self.gates], + "planned_records": [dict(item) for item in self.planned_records], + "applied_record_digests": list(self.applied_record_digests), + "support_state_changed": False, + "advertised_support_changed": False, + "scope": "reviewed_dormant_target_only", + } + + +def _gate(gates: list[_Gate], code: str, detail: str) -> None: + if not any(item.code == code for item in gates): + gates.append(_Gate(code, detail)) + + +def _workspace_root(path: str | Path) -> Path: + lexical = Path(os.path.abspath(Path(path))) + if lexical.is_symlink(): + raise ValueError("workspace root cannot be a symlink") + resolved = lexical.resolve(strict=True) + if not resolved.is_dir(): + raise ValueError("workspace root must be a directory") + return resolved + + +def _confined_regular_file( + path: str | Path, + *, + workspace: Path, + label: str, +) -> Path: + candidate = Path(path) + if not candidate.is_absolute(): + candidate = workspace / candidate + lexical = Path(os.path.abspath(candidate)) + try: + relative = lexical.relative_to(workspace) + except ValueError as exc: + raise ValueError(f"{label} must stay inside the workspace") from exc + current = workspace + for component in relative.parts: + current = current / component + if current.is_symlink(): + raise ValueError(f"{label} contains a symlink component") + resolved = lexical.resolve(strict=True) + try: + resolved.relative_to(workspace) + except ValueError as exc: + raise ValueError(f"{label} resolves outside the workspace") from exc + info = os.stat(resolved, follow_symlinks=False) + if not stat.S_ISREG(info.st_mode) or info.st_nlink != 1: + raise ValueError(f"{label} must be one singly linked regular file") + return resolved + + +def _read_regular(path: Path, *, maximum_bytes: int, label: str) -> bytes: + before = os.stat(path, follow_symlinks=False) + if not stat.S_ISREG(before.st_mode) or before.st_nlink != 1: + raise ValueError(f"{label} must be one singly linked regular file") + if before.st_size > maximum_bytes: + raise ValueError(f"{label} exceeds its byte limit") + flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0) + descriptor = os.open(path, flags) + try: + opened = os.fstat(descriptor) + identity = ( + before.st_dev, + before.st_ino, + before.st_size, + before.st_mtime_ns, + ) + if identity != ( + opened.st_dev, + opened.st_ino, + opened.st_size, + opened.st_mtime_ns, + ): + raise ValueError(f"{label} changed while opening") + chunks: list[bytes] = [] + total = 0 + while True: + chunk = os.read(descriptor, min(1024 * 1024, maximum_bytes + 1 - total)) + if not chunk: + break + total += len(chunk) + if total > maximum_bytes: + raise ValueError(f"{label} exceeds its byte limit") + chunks.append(chunk) + after = os.fstat(descriptor) + if total != after.st_size or identity != ( + after.st_dev, + after.st_ino, + after.st_size, + after.st_mtime_ns, + ): + raise ValueError(f"{label} changed while reading") + finally: + os.close(descriptor) + return b"".join(chunks) + + +def load_support_profile_jsonl_bytes( + data: bytes, + *, + source_trust_domain: str, +) -> SupportProfileProjection: + """Load one bounded stream and derive trust only from the caller's path boundary.""" + + if len(data) > MAX_SUPPORT_PROFILE_STREAM_BYTES: + raise ValueError("support-profile stream exceeds 64 MiB") + records = load_bounded_jsonl_bytes( + data, + label="support-profile stream", + max_records=MAX_SUPPORT_PROFILE_RECORDS, + ) + return project_support_profiles( + records, + source_trust_domain=source_trust_domain, + ) + + +def _proposal( + raw: bytes, + *, + expected_family_id: str | None, + expected_channel: str | None, +) -> tuple[str, str, tuple[SupportProfileSpec, ...]]: + payload = load_bounded_json_bytes(raw, label="support-profile set proposal") + if not isinstance(payload, Mapping) or set(payload) != { + "schema_version", + "family_id", + "channel", + "complete_profile_ids", + "profiles", + }: + raise ValueError("proposal fields do not match v1") + if payload["schema_version"] != 1 or isinstance(payload["schema_version"], bool): + raise ValueError("proposal schema_version must be 1") + family_id = payload["family_id"] + channel = payload["channel"] + if not isinstance(family_id, str) or _COMPONENT_RE.fullmatch(family_id) is None: + raise ValueError("proposal family_id is invalid") + if channel not in _PUBLIC_CHANNELS: + raise ValueError("proposal channel must be Experimental or Stable") + if expected_family_id is not None and family_id != expected_family_id: + raise ValueError("proposal family_id does not match the requested family") + if expected_channel is not None and channel != expected_channel: + raise ValueError("proposal channel does not match the requested channel") + raw_ids = payload["complete_profile_ids"] + raw_profiles = payload["profiles"] + if not isinstance(raw_ids, list) or not isinstance(raw_profiles, list): + raise ValueError("proposal profile IDs and profiles must be arrays") + if not 1 <= len(raw_ids) <= 32 or len(raw_profiles) != len(raw_ids): + raise ValueError("proposal requires one complete ordered set of 1..32 profiles") + if any(not isinstance(item, str) or _COMPONENT_RE.fullmatch(item) is None for item in raw_ids): + raise ValueError("proposal contains an invalid complete profile ID") + if len(set(raw_ids)) != len(raw_ids): + raise ValueError("proposal complete profile IDs contain a duplicate") + profiles = tuple(validate_support_profile_spec(item) for item in raw_profiles) + observed_ids = [item.to_dict()["profile_id"] for item in profiles] + if observed_ids != raw_ids: + raise ValueError("proposal profiles must exactly cover complete_profile_ids in order") + if raw != canonical_json_v1(dict(payload)) + b"\n": + raise ValueError("proposal must use exact canonical_json_v1 bytes plus LF") + return family_id, channel, profiles + + +def _record( + *, + sequence: int, + previous: str, + kind: str, + reviewer_role_id: str, + public_review_id: str, + reason: str, + occurred_at: str, + profile: Mapping[str, Any] | None = None, + family_id: str | None = None, + channel: str | None = None, + references: Sequence[Mapping[str, str]] = (), +) -> dict[str, Any]: + identity = ( + str(profile["profile_digest"])[:16] + if profile is not None + else canonical_sha256_v1(list(references))[:16] + ) + value: dict[str, Any] = { + "schema_version": 1, + "stream_id": "support-profiles-v1", + "sequence": sequence, + "previous_record_digest": previous, + "record_id": f"support-{sequence}-{identity}", + "kind": kind, + "reviewer_role_id": reviewer_role_id, + "review_reference": { + "kind": "public_repository_id", + "value": public_review_id, + }, + "issuer_claim": "repository_source", + "reason": reason, + "occurred_at": occurred_at, + "record_digest": ZERO_DIGEST, + } + if profile is not None: + value["profile"] = dict(profile) + else: + refs = [dict(item) for item in references] + value.update( + { + "family_id": family_id, + "channel": channel, + "profiles": refs, + "profile_set_digest": canonical_sha256_v1(refs), + } + ) + value["record_digest"] = canonical_sha256_v1( + value, + own_digest_field="record_digest", + ) + return value + + +def _utc_now(value: str | datetime | None) -> str: + if value is None: + current = datetime.now(timezone.utc).replace(microsecond=0) + elif isinstance(value, datetime): + if value.tzinfo is None or value.utcoffset() is None: + raise ValueError("occurred_at must be timezone-aware UTC") + current = value.astimezone(timezone.utc).replace(microsecond=0) + elif isinstance(value, str): + try: + current = datetime.strptime(value, "%Y-%m-%dT%H:%M:%SZ").replace( + tzinfo=timezone.utc + ) + except ValueError as exc: + raise ValueError("occurred_at must use exact RFC3339 UTC seconds") from exc + if current.strftime("%Y-%m-%dT%H:%M:%SZ") != value: + raise ValueError("occurred_at is non-canonical") + else: + raise ValueError("occurred_at must use exact RFC3339 UTC seconds") + return current.strftime("%Y-%m-%dT%H:%M:%SZ") + + +def _outcome( + *, + status: Literal["dry_run_ready", "dry_run_blocked", "applied", "apply_failed"], + approved: bool, + family_id: str | None, + channel: str | None, + projection: SupportProfileProjection, + current: Mapping[str, Any] | None, + proposed_set_digest: str | None, + gates: list[_Gate], + planned: list[dict[str, Any]], + applied: Sequence[str] = (), +) -> SupportProfileReviewOutcome: + return SupportProfileReviewOutcome( + status=status, + approved=approved, + family_id=family_id, + channel=channel, + observed_head_digest=projection.head_digest, + observed_current_profile_set_record_digest=( + None if current is None else str(current["record_digest"]) + ), + observed_current_profile_set_digest=( + None if current is None else str(current["profile_set_digest"]) + ), + proposed_profile_set_digest=proposed_set_digest, + gates=tuple(gates), + planned_records=tuple(planned), + applied_record_digests=tuple(applied), + ) + + +def review_image_pipeline_support_profiles( + *, + proposal_path: str | Path | None, + family_id: str | None, + channel: str | None, + workspace_root: str | Path, + expected_head_digest: str | None, + expected_current_profile_set_record_digest: str | None, + expected_current_profile_set_digest: str | None, + expect_no_current_profile_set: bool = False, + reviewer_role_id: str | None, + public_review_id: str | None, + reason: str | None, + approve: bool = False, + occurred_at: str | datetime | None = None, + fault_hook: FaultHook | None = None, +) -> SupportProfileReviewOutcome: + """Dry-run or append one complete reviewed dormant support-profile set.""" + + gates: list[_Gate] = [] + planned: list[dict[str, Any]] = [] + projection = project_support_profiles((), source_trust_domain="yolozu_managed") + raw_stream = b"" + stream: Path | None = None + proposal_profiles: tuple[SupportProfileSpec, ...] = () + proposed_set_digest: str | None = None + current: Mapping[str, Any] | None = None + + try: + workspace = _workspace_root(workspace_root) + except (OSError, ValueError) as exc: + _gate(gates, "workspace_invalid", str(exc)) + workspace = Path(os.path.abspath(Path(workspace_root))) + + canonical = workspace / CANONICAL_SUPPORT_PROFILE_STREAM + try: + stream = _confined_regular_file( + canonical, + workspace=workspace, + label="canonical support-profile stream", + ) + if stream != canonical.resolve(strict=True): + raise ValueError("support-profile stream is not the canonical SSOT") + raw_stream = _read_regular( + stream, + maximum_bytes=MAX_SUPPORT_PROFILE_STREAM_BYTES, + label="support-profile stream", + ) + projection = load_support_profile_jsonl_bytes( + raw_stream, + source_trust_domain="yolozu_managed", + ) + except (OSError, ValueError) as exc: + _gate(gates, "support_profile_stream_invalid", str(exc)) + + if family_id is None or _COMPONENT_RE.fullmatch(family_id) is None: + _gate(gates, "family_id_invalid", "an exact bounded family_id is required") + if channel not in _PUBLIC_CHANNELS: + _gate(gates, "channel_invalid", "channel must be Experimental or Stable") + + if proposal_path is None: + _gate(gates, "proposal_missing", "a workspace-confined canonical proposal is required") + else: + try: + proposal_file = _confined_regular_file( + proposal_path, + workspace=workspace, + label="support-profile proposal", + ) + proposal_raw = _read_regular( + proposal_file, + maximum_bytes=4 * 1024 * 1024, + label="support-profile proposal", + ) + proposal_family, proposal_channel, proposal_profiles = _proposal( + proposal_raw, + expected_family_id=family_id, + expected_channel=channel, + ) + family_id = proposal_family + channel = proposal_channel + except (OSError, TypeError, ValueError) as exc: + _gate(gates, "proposal_invalid", str(exc)) + + if expected_head_digest is None or _SHA256_RE.fullmatch(expected_head_digest) is None: + _gate(gates, "expected_head_invalid", "an exact current global head digest is required") + elif expected_head_digest != projection.head_digest: + _gate(gates, "stale_head", "expected global head does not match the observed head") + + if family_id is not None and channel is not None: + current = projection.assignments.get((family_id, channel)) + if expect_no_current_profile_set: + if ( + expected_current_profile_set_record_digest is not None + or expected_current_profile_set_digest is not None + ): + _gate(gates, "current_expectation_conflict", "initial none forbids current set digests") + if current is not None: + _gate(gates, "stale_current_set", "a current dormant set already exists") + else: + if ( + expected_current_profile_set_record_digest is None + or _SHA256_RE.fullmatch(expected_current_profile_set_record_digest) is None + or expected_current_profile_set_digest is None + or _SHA256_RE.fullmatch(expected_current_profile_set_digest) is None + ): + _gate( + gates, + "current_expectation_missing", + "replacement requires exact current set-record and set digests", + ) + elif current is None or ( + current["record_digest"] != expected_current_profile_set_record_digest + or current["profile_set_digest"] != expected_current_profile_set_digest + ): + _gate(gates, "stale_current_set", "expected current dormant set does not match") + + if reviewer_role_id not in _REVIEWER_ROLES: + _gate(gates, "reviewer_role_invalid", "a non-personal repository review role is required") + if not isinstance(public_review_id, str) or _REVIEW_RE.fullmatch(public_review_id) is None: + _gate(gates, "public_review_invalid", "a bounded public repository review ID is required") + if ( + not isinstance(reason, str) + or not reason + or len(reason.encode("utf-8")) > 512 + or any(ord(character) < 32 or ord(character) == 127 for character in reason) + ): + _gate(gates, "reason_invalid", "a bounded review reason is required") + try: + occurred = _utc_now(occurred_at) + except ValueError as exc: + _gate(gates, "occurred_at_invalid", str(exc)) + occurred = datetime.now(timezone.utc).replace(microsecond=0).strftime( + "%Y-%m-%dT%H:%M:%SZ" + ) + + if proposal_profiles: + references = [ + { + "profile_id": item.to_dict()["profile_id"], + "profile_digest": item.profile_digest, + } + for item in proposal_profiles + ] + proposed_set_digest = canonical_sha256_v1(references) + if current is not None and current["profile_set_digest"] == proposed_set_digest: + _gate(gates, "profile_set_unchanged", "the proposed complete set is already current") + sequence = len(projection.record_by_digest) + 1 + previous = projection.head_digest + for profile in proposal_profiles: + payload = profile.to_dict() + existing = projection.definitions.get(payload["profile_id"]) + if existing is not None: + if existing.to_dict() != payload: + _gate( + gates, + "profile_id_reused", + "an immutable profile ID cannot be reused with changed bytes", + ) + continue + item = _record( + sequence=sequence, + previous=previous, + kind="profile_definition", + reviewer_role_id=str(reviewer_role_id), + public_review_id=str(public_review_id), + reason=str(reason), + occurred_at=occurred, + profile=payload, + ) + planned.append(item) + sequence += 1 + previous = item["record_digest"] + assignment = _record( + sequence=sequence, + previous=previous, + kind="profile_set_assignment", + reviewer_role_id=str(reviewer_role_id), + public_review_id=str(public_review_id), + reason=str(reason), + occurred_at=occurred, + family_id=family_id, + channel=channel, + references=references, + ) + planned.append(assignment) + if len(projection.record_by_digest) + len(planned) > MAX_SUPPORT_PROFILE_RECORDS: + _gate(gates, "record_limit_exceeded", "planned records exceed the global 128-record cap") + if not gates: + try: + for item in planned: + validate_support_profile_record( + item, + source_trust_domain="yolozu_managed", + ) + projected = project_support_profiles( + [ + *(record.to_dict() for record in projection.record_by_digest.values()), + *planned, + ], + source_trust_domain="yolozu_managed", + ) + expected = projected.assignments[(str(family_id), str(channel))] + if ( + expected["profiles"] != references + or expected["profile_set_digest"] != proposed_set_digest + or expected["record_digest"] != assignment["record_digest"] + ): + raise ValueError("planned complete set projection mismatch") + except (KeyError, TypeError, ValueError) as exc: + planned.clear() + _gate(gates, "planned_review_invalid", str(exc)) + + if not approve: + return _outcome( + status="dry_run_ready" if not gates else "dry_run_blocked", + approved=False, + family_id=family_id, + channel=channel, + projection=projection, + current=current, + proposed_set_digest=proposed_set_digest, + gates=gates, + planned=planned, + ) + if gates or stream is None or not planned: + return _outcome( + status="apply_failed", + approved=True, + family_id=family_id, + channel=channel, + projection=projection, + current=current, + proposed_set_digest=proposed_set_digest, + gates=gates, + planned=planned, + ) + + replacement = raw_stream + b"".join(canonical_json_v1(item) + b"\n" for item in planned) + try: + latest = _read_regular( + stream, + maximum_bytes=MAX_SUPPORT_PROFILE_STREAM_BYTES, + label="support-profile stream", + ) + latest_projection = load_support_profile_jsonl_bytes( + latest, + source_trust_domain="yolozu_managed", + ) + if latest != raw_stream or latest_projection.head_digest != projection.head_digest: + raise ValueError("support-profile stream changed before mutation") + atomic_replace_control_stream( + path=stream, + observed_bytes=raw_stream, + replacement_bytes=replacement, + maximum_bytes=MAX_SUPPORT_PROFILE_STREAM_BYTES, + label="support-profile stream", + fault_hook=fault_hook, + ) + readback = _read_regular( + stream, + maximum_bytes=MAX_SUPPORT_PROFILE_STREAM_BYTES, + label="support-profile stream readback", + ) + readback_projection = load_support_profile_jsonl_bytes( + readback, + source_trust_domain="yolozu_managed", + ) + observed = readback_projection.assignments[(str(family_id), str(channel))] + if ( + not readback.startswith(raw_stream) + or readback_projection.head_digest != planned[-1]["record_digest"] + or observed["record_digest"] != planned[-1]["record_digest"] + or observed["profiles"] != planned[-1]["profiles"] + or observed["profile_set_digest"] != proposed_set_digest + ): + raise ValueError("support-profile stream readback mismatch") + except (OSError, KeyError, TypeError, ValueError) as exc: + _gate(gates, "atomic_write_failed", str(exc)) + return _outcome( + status="apply_failed", + approved=True, + family_id=family_id, + channel=channel, + projection=projection, + current=current, + proposed_set_digest=proposed_set_digest, + gates=gates, + planned=planned, + ) + return _outcome( + status="applied", + approved=True, + family_id=family_id, + channel=channel, + projection=readback_projection, + current=readback_projection.assignments[(str(family_id), str(channel))], + proposed_set_digest=proposed_set_digest, + gates=[], + planned=planned, + applied=[item["record_digest"] for item in planned], + ) + + +def _snapshot_status( + *, + pointer: Mapping[str, Any], + profiles: SupportProfileProjection, +) -> tuple[str, tuple[SupportProfileSpec, ...]]: + required = ( + "support_profile_index_head", + "profile_set_record_id", + "profile_set_record_digest", + "profile_set_digest", + "profiles", + ) + if any(pointer.get(field) is None for field in required): + return "absent", () + head = str(pointer["support_profile_index_head"]) + set_record_digest = str(pointer["profile_set_record_digest"]) + set_record = profiles.record_by_digest.get(set_record_digest) + head_record = profiles.record_by_digest.get(head) + if set_record is None or head_record is None: + return "absent", () + set_payload = set_record.to_dict() + if ( + set_payload.get("kind") != "profile_set_assignment" + or set_payload.get("record_id") != pointer["profile_set_record_id"] + or set_payload.get("record_digest") != set_record_digest + or set_payload.get("profiles") != pointer["profiles"] + or set_payload.get("profile_set_digest") != pointer["profile_set_digest"] + or head_record.to_dict()["sequence"] < set_payload["sequence"] + ): + return "conflict", () + current = head + prefix: list[Any] = [] + while current != ZERO_DIGEST: + record = profiles.record_by_digest.get(current) + if record is None: + return "absent", () + prefix.append(record) + current = str(record.to_dict()["previous_record_digest"]) + if set_record not in prefix: + return "conflict", () + if any(record.source_trust_domain != "yolozu_managed" for record in prefix): + return "untrusted", () + resolved: list[SupportProfileSpec] = [] + for reference in pointer["profiles"]: + profile = profiles.definitions.get(reference["profile_id"]) + if profile is None: + return "absent", () + if profile.profile_digest != reference["profile_digest"]: + return "conflict", () + resolved.append(profile) + return "valid", tuple(resolved) + + +def build_support_profile_eligibility_observation( + *, + registry: LoadedAlgorithmBundleRegistry, + profiles: SupportProfileProjection, + bundle: AlgorithmBundleSpec, + channel: str, + job: ImageJobSpec, + environment: EnvironmentProfile, + workload: QualificationWorkloadProfile, + evidence_trust_domain: str, + support_scope: str, +) -> SupportProfileEligibilityObservation | None: + """Build the only loader-derived support observation used for routing.""" + + bundle_record = bundle.to_dict() + pointer = registry.lifecycle.channel_pointers.get( + (bundle_record["family_id"], channel) + ) + if pointer is None or pointer["bundle_spec_digest"] != bundle.spec_digest: + return None + snapshot_status, resolved = _snapshot_status(pointer=pointer, profiles=profiles) + status = snapshot_status + matched: dict[str, Any] | None = None + advertised_digest = compute_advertised_gates_digest(job) + if status == "valid": + if ( + registry.registry_trust_domain != "yolozu_managed" + or registry.lifecycle_trust_domain != "yolozu_managed" + ): + status = "untrusted" + elif evidence_trust_domain == "site_managed" and support_scope == "site_qualified": + status = "not_required_site" + else: + matching = [] + for profile in resolved: + value = profile.to_dict() + if ( + value["task"] == job.to_dict()["task"] + and value["environment_fingerprint"] + == environment.environment_fingerprint + and value["qualification_workload_fingerprint"] + == workload.workload_fingerprint + and value["protocol_fingerprint"] + == QUALIFICATION_PROTOCOL_FINGERPRINT + and canonical_sha256_v1(value["advertised_constraints"]) + == advertised_digest + ): + matching.append(value) + if len(matching) == 1: + status = "matching_one" + matched = matching[0] + elif len(matching) > 1: + status = "conflict" + else: + status = "no_match" + trust = "yolozu_managed" if status == "matching_one" else ( + "operator_asserted" if status == "untrusted" else "unknown" + ) + value: dict[str, Any] = { + "schema_version": 1, + "provider_id": "support_profile_projection", + "provider_version": "1", + "family_id": bundle_record["family_id"], + "bundle_spec_digest": bundle.spec_digest, + "channel": channel, + "lifecycle_assignment_id": ( + "assignment-" + pointer["lifecycle_event_digest"][:16] + ), + "lifecycle_assignment_digest": pointer["lifecycle_event_digest"], + "support_profile_index_head_digest": pointer["support_profile_index_head"], + "profile_set_record_id": pointer["profile_set_record_id"], + "profile_set_record_digest": pointer["profile_set_record_digest"], + "profile_set_digest": pointer["profile_set_digest"], + "status": status, + "profile_id": None if matched is None else matched["profile_id"], + "profile_digest": None if matched is None else matched["profile_digest"], + "environment_fingerprint": ( + None if matched is None else environment.environment_fingerprint + ), + "qualification_workload_fingerprint": ( + None if matched is None else workload.workload_fingerprint + ), + "protocol_fingerprint": ( + None if matched is None else QUALIFICATION_PROTOCOL_FINGERPRINT + ), + "advertised_gates_digest": None if matched is None else advertised_digest, + "trust_domain": trust, + "observation_digest": ZERO_DIGEST, + } + value["observation_digest"] = canonical_sha256_v1( + value, + own_digest_field="observation_digest", + ) + return validate_support_profile_eligibility_observation( + value, + source_trust_domain=trust, + evidence_trust_domain=evidence_trust_domain, + support_scope=support_scope, + ) diff --git a/yolozu/cli_commands.py b/yolozu/cli_commands.py index c3481cf9..c4e4edff 100644 --- a/yolozu/cli_commands.py +++ b/yolozu/cli_commands.py @@ -1187,6 +1187,42 @@ def _cmd_activate_qualification_evidence(args: argparse.Namespace) -> int: return 0 if outcome.status in {"dry_run_ready", "applied"} else 2 +def _cmd_review_image_pipeline_support_profiles(args: argparse.Namespace) -> int: + from yolozu.adaptive.support_profiles import ( + review_image_pipeline_support_profiles, + ) + + try: + outcome = review_image_pipeline_support_profiles( + proposal_path=getattr(args, "proposal", None), + family_id=getattr(args, "family_id", None), + channel=getattr(args, "channel", None), + workspace_root=str(args.workspace), + expected_head_digest=getattr(args, "expected_head_digest", None), + expected_current_profile_set_record_digest=getattr( + args, + "expected_current_profile_set_record_digest", + None, + ), + expected_current_profile_set_digest=getattr( + args, + "expected_current_profile_set_digest", + None, + ), + expect_no_current_profile_set=bool( + args.expect_no_current_profile_set + ), + reviewer_role_id=getattr(args, "reviewer_role_id", None), + public_review_id=getattr(args, "public_review_id", None), + reason=getattr(args, "reason", None), + approve=bool(args.approve), + ) + except (OSError, TypeError, ValueError) as exc: + raise SystemExit(str(exc)) from exc + print(json.dumps(outcome.to_dict(), ensure_ascii=False, indent=2, sort_keys=True)) + return 0 if outcome.status in {"dry_run_ready", "applied"} else 2 + + def _cmd_scout_algorithms(args: argparse.Namespace) -> int: from yolozu.adaptive.algorithm_scout import ( AlgorithmScoutError, diff --git a/yolozu/cli_entry.py b/yolozu/cli_entry.py index 2027a92e..25944ae0 100644 --- a/yolozu/cli_entry.py +++ b/yolozu/cli_entry.py @@ -29,6 +29,7 @@ _cmd_predictions, _cmd_qualify_image_pipeline, _cmd_activate_qualification_evidence, + _cmd_review_image_pipeline_support_profiles, _cmd_scout_algorithms, _cmd_validate, _cmd_eval_instance_seg, @@ -1370,6 +1371,61 @@ def main(argv: list[str] | None = None) -> int: help="Apply the validated atomic append; omission is always dry-run.", ) + support_review = sub.add_parser( + "review-image-pipeline-support-profiles", + help="Review one complete dormant support-profile set; dry-run by default.", + ) + support_review.add_argument( + "--proposal", + help="Workspace-confined canonical complete-set proposal JSON.", + ) + support_review.add_argument("--family-id", help="Exact bundle family ID.") + support_review.add_argument( + "--channel", + choices=("Experimental", "Stable"), + help="Exact future public lifecycle channel.", + ) + support_review.add_argument( + "--expected-head-digest", + help="Observed global support-profile head; use 64 zeroes initially.", + ) + support_review.add_argument( + "--expected-current-profile-set-record-digest", + help="Exact current dormant set-assignment record digest.", + ) + support_review.add_argument( + "--expected-current-profile-set-digest", + help="Exact current dormant ordered profile-set digest.", + ) + support_review.add_argument( + "--expect-no-current-profile-set", + action="store_true", + help="Explicitly require that this family/channel has no prior dormant set.", + ) + support_review.add_argument( + "--reviewer-role-id", + choices=("repo_maintainer", "release_reviewer"), + help="Non-personal repository review role.", + ) + support_review.add_argument( + "--public-review-id", + help="Bounded public repository review reference.", + ) + support_review.add_argument( + "--reason", + help="Public review reason in 1..512 UTF-8 bytes.", + ) + support_review.add_argument( + "--workspace", + default=".", + help="Repository workspace containing the canonical SSOT.", + ) + support_review.add_argument( + "--approve", + action="store_true", + help="Apply the atomic append; omission is always dry-run.", + ) + scout = sub.add_parser( "scout-algorithms", help="Plan or collect a bounded monitored-source candidate inbox (Experimental).", @@ -1493,6 +1549,8 @@ def main(argv: list[str] | None = None) -> int: return _cmd_qualify_image_pipeline(args) if args.command == "activate-qualification-evidence": return _cmd_activate_qualification_evidence(args) + if args.command == "review-image-pipeline-support-profiles": + return _cmd_review_image_pipeline_support_profiles(args) if args.command == "scout-algorithms": return _cmd_scout_algorithms(args) if args.command == "list": diff --git a/yolozu/data/manifest/tools_manifest.json b/yolozu/data/manifest/tools_manifest.json index 3b669a51..8941e0db 100644 --- a/yolozu/data/manifest/tools_manifest.json +++ b/yolozu/data/manifest/tools_manifest.json @@ -254,6 +254,10 @@ "schema": "docs/schemas/support_profile_record.schema.json", "summary": "Append-only reviewed exact-environment support-profile definition and dormant-set record." }, + "support_profile_set_proposal_json": { + "schema": "docs/schemas/support_profile_set_proposal.schema.json", + "summary": "Canonical public review input that exactly covers one complete ordered dormant support-profile set." + }, "support_profile_spec_json": { "schema": "docs/schemas/support_profile_spec.schema.json", "summary": "Immutable exact measured environment, workload, protocol, and advertised-gate scope; it does not extrapolate hardware families." @@ -14422,6 +14426,153 @@ "license" ] }, + { + "contracts": { + "consumes": [ + "support_profile_set_proposal_json", + "support_profile_spec_json", + "support_profile_record_json" + ], + "produces": [ + "support_profile_record_json" + ] + }, + "docs": [ + "README.md", + "Readme_jp.md", + "docs/adaptive_image_routing.md", + "docs/schemas/support_profile_set_proposal.schema.json", + "docs/schemas/support_profile_record.schema.json" + ], + "effects": { + "fixed_writes": [ + { + "description": "With --approve, atomically appends only to the canonical dormant support-profile SSOT.", + "kind": "file", + "path": "yolozu/data/adaptive_routing/support_profiles.jsonl", + "scope": "path" + } + ], + "writes": [] + }, + "entrypoint": "tools/review_image_pipeline_support_profiles.py", + "examples": [ + { + "command": "python3 tools/review_image_pipeline_support_profiles.py --proposal reports/support_profile_set_proposal.json --family-id yolox --channel Experimental --expected-head-digest --expect-no-current-profile-set --reviewer-role-id repo_maintainer --public-review-id gh- --reason 'Review complete dormant target scope'", + "description": "Dry-run every complete-set, immutable-definition, review, and stale-head gate; no record is written." + }, + { + "command": "python3 tools/review_image_pipeline_support_profiles.py --help", + "description": "Inspect the Experimental dormant support-profile review interface contract." + } + ], + "id": "review_image_pipeline_support_profiles", + "inputs": [ + { + "flag": "--proposal", + "kind": "file", + "name": "proposal", + "required": false + }, + { + "flag": "--family-id", + "kind": "string", + "name": "family_id", + "required": false + }, + { + "flag": "--channel", + "kind": "string", + "name": "channel", + "required": false + }, + { + "flag": "--expected-head-digest", + "kind": "string", + "name": "expected_head_digest", + "required": false + }, + { + "flag": "--expected-current-profile-set-record-digest", + "kind": "string", + "name": "expected_current_profile_set_record_digest", + "required": false + }, + { + "flag": "--expected-current-profile-set-digest", + "kind": "string", + "name": "expected_current_profile_set_digest", + "required": false + }, + { + "flag": "--expect-no-current-profile-set", + "kind": "string", + "name": "expect_no_current_profile_set", + "required": false + }, + { + "flag": "--reviewer-role-id", + "kind": "string", + "name": "reviewer_role_id", + "required": false + }, + { + "flag": "--public-review-id", + "kind": "string", + "name": "public_review_id", + "required": false + }, + { + "flag": "--reason", + "kind": "string", + "name": "reason", + "required": false + }, + { + "default": ".", + "flag": "--workspace", + "kind": "dir", + "name": "workspace", + "required": false + }, + { + "flag": "--approve", + "kind": "string", + "name": "approve", + "required": false + } + ], + "maturity": "experimental", + "outputs": [ + { + "default": "stdout", + "description": "Bounded machine-readable dry-run or apply outcome that states dormant-only scope.", + "kind": "stdout", + "name": "support_profile_review_outcome" + }, + { + "default": "yolozu/data/adaptive_routing/support_profiles.jsonl", + "description": "Canonical append-only reviewed dormant support-profile stream after explicit approval.", + "kind": "file", + "name": "support_profile_stream" + } + ], + "platform": { + "cpu_ok": true, + "gpu_required": false, + "linux_ok": true, + "macos_ok": true + }, + "runner": "python3", + "summary": "Dry-run or atomically append one complete reviewed dormant exact-measured support-profile set; review alone never changes lifecycle support or availability.", + "tags": [ + "adaptive-inference", + "experimental", + "review", + "support-profile", + "ssot" + ] + }, { "docs": [ "docs/checkpoint_compatibility.md", diff --git a/yolozu/data/schemas/support_profile_set_proposal.schema.json b/yolozu/data/schemas/support_profile_set_proposal.schema.json new file mode 100644 index 00000000..cb9b6281 --- /dev/null +++ b/yolozu/data/schemas/support_profile_set_proposal.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://www.toppymicros.com/yolozu/schemas/support_profile_set_proposal.schema.json", + "title": "YOLOZU SupportProfileSetProposal v1", + "description": "Canonical public review input containing one complete ordered dormant support-profile set.", + "type": "object", + "additionalProperties": false, + "required": ["schema_version", "family_id", "channel", "complete_profile_ids", "profiles"], + "properties": { + "schema_version": { "const": 1 }, + "family_id": { "$ref": "#/$defs/component" }, + "channel": { "enum": ["Experimental", "Stable"] }, + "complete_profile_ids": { + "type": "array", + "minItems": 1, + "maxItems": 32, + "uniqueItems": true, + "items": { "$ref": "#/$defs/component" } + }, + "profiles": { + "type": "array", + "minItems": 1, + "maxItems": 32, + "items": { "$ref": "support_profile_spec.schema.json" } + } + }, + "$defs": { + "component": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$" } + } +}