diff --git a/.aiwg/AIWG.md b/.aiwg/AIWG.md index 39e1a80..5e37c22 100644 --- a/.aiwg/AIWG.md +++ b/.aiwg/AIWG.md @@ -28,7 +28,7 @@ When a user asks whether AIWG is active or engaged in this project, run or read ### Tracker Authority Protocol -- Source of truth: [.aiwg/aiwg.config](./.aiwg/aiwg.config) +- Source of truth: [.aiwg/aiwg.config](./aiwg.config) - Internal/canonical tracker: `origin` (github; https://github.com/elder-plinius/OBLITERATUS.git) - Customer issue tracker: not configured - Primary repo remote: `origin`; CI remote: `origin` diff --git a/.aiwg/aiwg.config b/.aiwg/aiwg.config index a138a11..8948c07 100644 --- a/.aiwg/aiwg.config +++ b/.aiwg/aiwg.config @@ -49,16 +49,16 @@ "bt6-maintainer": { "version": "0.3.0", "source": "project-local", - "installedAt": "2026-08-21T22:00:58.629Z", + "installedAt": "2026-08-22T15:28:11.760Z", "deployedTo": { "codex": { "agents": 5, "commands": 0, - "skills": 6, + "skills": 7, "rules": 1 } }, - "manifestHash": "sha256:d14e8bdea0e1f845155d35a74251651ace95ee65a052eeb303e6a8148d8d9948", + "manifestHash": "sha256:870a24c7c362c7e71884381a4bc171deef54139ae50e9c76cac3aa967a9fc368", "localPath": ".aiwg/plugins/bt6-maintainer/", "localType": "plugin", "manifestVersion": "1", @@ -68,13 +68,14 @@ "agents/bt6-pr-auditor.md": "4d0b3f19e70a0406f0ace424f281a2444c98677d05fe7875ee0b389b75985e3e", "agents/bt6-provider-assessor.md": "39cc61c35cea9be9dd7f9330ec82d7cef99d9a1f8a384f9313e8df9aafc98b30", "agents/bt6-release-integrator.md": "e66bd1bf158d53265600c3a96240865dd716ff947a1be0a3a1bd384ca9a0bce7", - "rules/bt6-maintainer-guardrails.md": "68baf7dd88b856615377edbba51530c6d001421d4fc6fbcfbe3c8caf0d5c8014", + "rules/bt6-maintainer-guardrails.md": "081d398443b7f1c6e37e75b7bd0b9a68a24195eee7b96cc60d34d3febd62c694", "skills/bt6-issue-steward/SKILL.md": "187e6cb41e71cc3811dfcf3aa8dcb5e49429aaa5087dbb0c3fd8816751b4f02e", "skills/bt6-merge-train/SKILL.md": "87e2cfd9a84c6f2d1ddb3d054ff794d1afe242f67dd2803caaeea9e39a3242ba", "skills/bt6-pr-audit/SKILL.md": "38be15ba6c6c12f958c7ced0bf6152654dc6df53ddc17156fda84083ae990ea9", "skills/bt6-provider-review/SKILL.md": "a95ac6d2be20847626bb94e5038358148d4a19c25e00ade6049526f8a16d0c15", "skills/bt6-queue-audit/SKILL.md": "aaa77f2e14b46a2f8329025923ed54745152cea6896c947580f7a1babff81312", - "skills/bt6-release-validation/SKILL.md": "e38c817001aecf9e0ab695875a41ad1a7ef55f6ffd7641e2646a3189f202b964" + "skills/bt6-release-readiness/SKILL.md": "26c65c1f64ce29f947be8a90987489a499814c360f4789ed80ef35f711c3d4e0", + "skills/bt6-release-validation/SKILL.md": "9c0dfe72690bdcae273624be730751951b42bab36b538989a8708c0fb48f76d1" }, "deployedArtifactHashes": { "codex": { @@ -83,13 +84,14 @@ "agents/bt6-pr-auditor.md": "541754bb5acf433e4bd2b85a93cfbb40f3f28d3c84ed577f92242f9041b9252b", "agents/bt6-provider-assessor.md": "716c78830e8d5c987683b2ff240eb0f04eefddd628d763a3c5adaf371d2ec8dd", "agents/bt6-release-integrator.md": "5cadf095ef64c24dbcab36c991f7c9a26d793eddc20be28fe6b73435253d9df1", - "rules/bt6-maintainer-guardrails.md": "68baf7dd88b856615377edbba51530c6d001421d4fc6fbcfbe3c8caf0d5c8014", + "rules/bt6-maintainer-guardrails.md": "081d398443b7f1c6e37e75b7bd0b9a68a24195eee7b96cc60d34d3febd62c694", "skills/bt6-issue-steward/SKILL.md": "1889afb3c068765806d895949d38f1b0888f72e59a5dedee1bc9b728910561f2", "skills/bt6-merge-train/SKILL.md": "2e01a84f578b53d4cc1c7dbf0ae2395004fab45b8175b104ccc31d74f3d9aa3b", "skills/bt6-pr-audit/SKILL.md": "9a1e55bfd6972f3168f884d54cd6ede3673b541940b54352120ed4c0a4c49f2c", "skills/bt6-provider-review/SKILL.md": "2de8b37f546dda852bee88d023caf82877baab988aebc219fd83a784c2bf58a3", "skills/bt6-queue-audit/SKILL.md": "e0bdb36c8f8f85a2e9dff0180de2f502972bb8af9b7b13c4661be065354ef893", - "skills/bt6-release-validation/SKILL.md": "c25e1600aea5102374616f89efb7f28b0cefd0d52f346414012560776cbb5407" + "skills/bt6-release-readiness/SKILL.md": "db317a00f8f4198e93e0666a00af29ad645d4c3d50de2e42141d84d6e7c16eb1", + "skills/bt6-release-validation/SKILL.md": "e94720ea230a8206d7acf5b3c514c6bdf1b7f81865580b7b2c6df11472bdb384" } } } diff --git a/.aiwg/bt6-maintainer.yaml b/.aiwg/bt6-maintainer.yaml index 5105c19..8fc3c73 100644 --- a/.aiwg/bt6-maintainer.yaml +++ b/.aiwg/bt6-maintainer.yaml @@ -25,18 +25,28 @@ validation: - "python scripts/check_coverage_thresholds.py test-results/coverage-pr-core.json --min-line 0 --min-branch 0 --min-changed 50 --base-ref origin/main" - "python scripts/check_conditional_policy.py && python scripts/check_test_risk_map.py" full: - - "python -m pytest" - - "python -m build --sdist --wheel" + - "mkdir -p test-results && python -m pytest -m 'not slow and not gpu and not mps and not mlx and not network and not download and not remote and not operator_ui' --cov-branch --cov-fail-under=0 --cov-report=json:test-results/coverage-release.json" + - "python scripts/check_coverage_thresholds.py test-results/coverage-release.json --min-line 75 --min-branch 60 --min-file obliteratus/device.py=70 --min-file obliteratus/models/loader.py=70 --min-file obliteratus/architecture_profiles.py=70 --min-file obliteratus/cli.py=70 --min-file obliteratus/mlx_backend.py=70 --min-file obliteratus/evaluation/metrics.py=70 --min-file obliteratus/evaluation/advanced_metrics.py=70 --min-file obliteratus/reporting/report.py=70 --min-file obliteratus/community.py=70 --min-file obliteratus/telemetry.py=70" + - "python scripts/check_quality_policy.py --policy ci/test-quality-policy.json --coverage test-results/coverage-release.json && python scripts/check_conditional_policy.py && python scripts/check_test_risk_map.py" - "python -c 'import obliteratus; print(obliteratus.__version__)'" - "python -m obliteratus --help" qualityPolicy: pullRequestChangedLineCoverageFloor: 50 requireBehaviorTests: true - fullSuiteTrigger: "tagged-release" + fullSuiteTrigger: "release-readiness-and-tagged-validation" documentation: - "python -m ruff check --select F app.py obliteratus tests scripts/check_coverage_thresholds.py scripts/check_supply_chain_policy.py scripts/gemma4_12b_recursive_loop.py" researchIntegrity: - "python -m pytest tests/test_advanced_metrics.py tests/test_breakthrough_modules.py tests/test_community.py" +releaseEvidence: + artifactType: "source-zip" + hashAlgorithm: "sha256" + provenanceFormat: "slsa-v1" + attestationFormat: "in-toto" + signingMode: "sigstore-keyless" + sbomFormat: "cyclonedx" + snapshotOnce: true + verifyBeforePromotion: true riskSurfaces: - id: "model-loading" paths: ["obliteratus/models/**", "obliteratus/device.py", "scripts/**"] @@ -57,7 +67,7 @@ riskSurfaces: - id: "ci-supply-chain" paths: [".github/workflows/**", "ci/**", "docs/SUPPLY_CHAIN_POLICY.md", "pyproject.toml", "scripts/check_supply_chain_policy.py", "uv.lock"] concerns: ["workflow-permissions", "dependency-pinning", "untrusted-pull-request-code"] - requiredChecks: ["uv lock --check", "python -m pytest tests/test_ci_policy.py tests/test_supply_chain_policy.py", "python -m build --sdist --wheel"] + requiredChecks: ["uv lock --check", "python -m pytest tests/test_ci_policy.py tests/test_supply_chain_policy.py"] research: corpusPaths: ["obliteratus/prompts.py", "community_results/**"] evidencePaths: ["paper/**", "docs/**", "community_results/**"] diff --git a/.aiwg/generated/project-quickref/aiwg-project-obliteratus-quickref/SKILL.md b/.aiwg/generated/project-quickref/aiwg-project-obliteratus-quickref/SKILL.md index f735e9a..5fc8858 100644 --- a/.aiwg/generated/project-quickref/aiwg-project-obliteratus-quickref/SKILL.md +++ b/.aiwg/generated/project-quickref/aiwg-project-obliteratus-quickref/SKILL.md @@ -15,7 +15,7 @@ Use project-local capabilities before generic AIWG workflows when they apply. ## bt6-maintainer -Cross-repository maintenance, external-provider review, and tagged-release validation for BT6 research and support tooling. +Cross-repository maintenance, release readiness, and exact-tag validation for BT6 research and support tooling. - Discover: `aiwg discover "bt6-maintainer"` - Discover: `aiwg discover "bt6"` diff --git a/.aiwg/generated/project-quickref/definition.json b/.aiwg/generated/project-quickref/definition.json index 5039c7e..30d817a 100644 --- a/.aiwg/generated/project-quickref/definition.json +++ b/.aiwg/generated/project-quickref/definition.json @@ -9,7 +9,7 @@ "entries": [ { "title": "bt6-maintainer", - "summary": "Cross-repository maintenance, external-provider review, and tagged-release validation for BT6 research and support tooling.", + "summary": "Cross-repository maintenance, release readiness, and exact-tag validation for BT6 research and support tooling.", "discover": [ "bt6-maintainer", "bt6", diff --git a/.aiwg/plugins/bt6-maintainer/README.md b/.aiwg/plugins/bt6-maintainer/README.md index bf8a1fa..975f5d8 100644 --- a/.aiwg/plugins/bt6-maintainer/README.md +++ b/.aiwg/plugins/bt6-maintainer/README.md @@ -2,8 +2,8 @@ Cross-repository maintenance for BT6 research and support tooling. The plugin provides queue audit, pull-request audit, external-provider assessment, issue -stewardship, conservative merge-train workflows, and exact-tag release -validation that adapt to each repository's configured tracker, delivery policy, +stewardship, conservative merge-train workflows, pre-tag release hardening, and +exact-tag release validation that adapt to each repository's configured tracker, delivery policy, validation commands, and research/data risk surfaces. ## What this is @@ -70,9 +70,11 @@ the profile's `validation.quick` core suite, require relevant tests for behavior changes, and enforce a 50% changed-line coverage floor where coverage is measurable. Genuine but incomplete tests may be completed through `maintainer-assist`; behavior changes with zero relevant tests remain blocked. -The exhaustive `validation.full` suite runs through `bt6-release-validation` -against an exact tag and blocks artifact promotion until the release gate is -green. +Before tagging, `bt6-release-readiness` inventories merged risk, runs the +exhaustive locally applicable gates, and repairs test, coverage, correctness, +documentation, and policy gaps. After tagging, `bt6-release-validation` binds +the same higher bar plus hosted and platform evidence to the exact immutable +tag. Readiness never substitutes for tagged certification. Inspect health: ```bash diff --git a/.aiwg/plugins/bt6-maintainer/manifest.json b/.aiwg/plugins/bt6-maintainer/manifest.json index b49b8bd..4fa65a9 100644 --- a/.aiwg/plugins/bt6-maintainer/manifest.json +++ b/.aiwg/plugins/bt6-maintainer/manifest.json @@ -3,7 +3,7 @@ "type": "plugin", "name": "bt6-maintainer", "version": "0.3.0", - "description": "Cross-repository maintenance, external-provider review, and tagged-release validation for BT6 research and support tooling.", + "description": "Cross-repository maintenance, release readiness, and exact-tag validation for BT6 research and support tooling.", "manifestVersion": "1", "platforms": { "claude": "full", @@ -19,6 +19,8 @@ "external-providers", "trust-boundaries", "merge-train", + "release-readiness", + "release-validation", "plugin", "repository-operations" ], diff --git a/.aiwg/plugins/bt6-maintainer/payload/README.md b/.aiwg/plugins/bt6-maintainer/payload/README.md index 5148bf6..463548c 100644 --- a/.aiwg/plugins/bt6-maintainer/payload/README.md +++ b/.aiwg/plugins/bt6-maintainer/payload/README.md @@ -13,10 +13,12 @@ support codebases with consistent evidence and safety gates. triage. - `bt6-merge-train` — explicitly authorized, one-at-a-time merges with queue refresh and linked-issue reconciliation. +- `bt6-release-readiness` — pre-tag audit and repair from the bounded PR bar to + the exhaustive release bar. +- `bt6-release-validation` — exact-tag certification before artifact promotion. -Five matching agents orchestrate those skills. Five declarative capability -flows describe queue audit, PR audit, provider review, issue stewardship, and -merge train contracts. +The release integrator orchestrates both release workflows. Declarative +capability flows describe every workflow contract. ## Repository profile diff --git a/.aiwg/plugins/bt6-maintainer/payload/capabilities/bt6-release-readiness-flow.yaml b/.aiwg/plugins/bt6-maintainer/payload/capabilities/bt6-release-readiness-flow.yaml new file mode 100644 index 0000000..257916e --- /dev/null +++ b/.aiwg/plugins/bt6-maintainer/payload/capabilities/bt6-release-readiness-flow.yaml @@ -0,0 +1,52 @@ +apiVersion: ops.aiwg.io/v1 +kind: OpsCapability +metadata: + name: bt6-release-readiness-flow + labels: + category: release-management + scope: cross-repository + annotations: + blast-radius: "repository-local repairs only; tagging and publication are out of scope" +spec: + description: Audit and harden an untagged candidate from the bounded PR bar to the exhaustive BT6 release bar. + version: "0.3.0" + inputs: + - name: candidate + type: string + required: true + description: Exact candidate commit to harden. + - name: comparison_base + type: string + required: true + description: Previous release or configured exact base used to inventory merged change. + outputs: + - name: gap_inventory + type: list + description: Failed gates, coverage regressions, untested behavior, and unavailable evidence. + - name: repairs + type: list + description: Authorized code, test, documentation, and policy corrections with verification. + - name: decision + type: string + description: ready-to-tag, not-ready, or hold. + target_requirements: + os: [linux, macos] + capabilities: [git] + agent: bt6-release-integrator + idempotent: true + steps: + - name: resolve-candidate + description: Bind repository authority, candidate, comparison base, profile, and authorization. + - name: inventory-merged-risk + description: Map merged production changes to relevant tests, owners, public contracts, and risk surfaces. + - name: run-pre-tag-gates + description: Run the full locally applicable release suite, coverage, quality-depth, packaging, and integrity checks. + - name: repair-gaps + description: Add outcome-oriented tests and correct underlying implementation or policy defects without weakening thresholds. + - name: rerun-complete-gate + description: Re-run focused checks and then the complete applicable pre-tag suite. + - name: decide + description: Declare ready-to-tag only with green pre-tag evidence; exact-tag validation remains mandatory. + verification: + command: "git rev-parse --verify ^{commit} >/dev/null" + expect: "report binds the gap inventory, repairs, and final evidence to the exact candidate and comparison base" diff --git a/.aiwg/plugins/bt6-maintainer/payload/config/repository-profile.schema.json b/.aiwg/plugins/bt6-maintainer/payload/config/repository-profile.schema.json index 2c7d362..0017673 100644 --- a/.aiwg/plugins/bt6-maintainer/payload/config/repository-profile.schema.json +++ b/.aiwg/plugins/bt6-maintainer/payload/config/repository-profile.schema.json @@ -91,13 +91,37 @@ "properties": { "pullRequestChangedLineCoverageFloor": { "const": 50 }, "requireBehaviorTests": { "const": true }, - "fullSuiteTrigger": { "const": "tagged-release" } + "fullSuiteTrigger": { "const": "release-readiness-and-tagged-validation" } } }, "documentation": { "$ref": "#/$defs/commands" }, "researchIntegrity": { "$ref": "#/$defs/commands" } } }, + "releaseEvidence": { + "type": "object", + "additionalProperties": false, + "required": [ + "artifactType", + "hashAlgorithm", + "provenanceFormat", + "attestationFormat", + "signingMode", + "sbomFormat", + "snapshotOnce", + "verifyBeforePromotion" + ], + "properties": { + "artifactType": { "const": "source-zip" }, + "hashAlgorithm": { "const": "sha256" }, + "provenanceFormat": { "const": "slsa-v1" }, + "attestationFormat": { "const": "in-toto" }, + "signingMode": { "enum": ["sigstore-keyless", "project-defined"] }, + "sbomFormat": { "enum": ["cyclonedx", "spdx"] }, + "snapshotOnce": { "const": true }, + "verifyBeforePromotion": { "const": true } + } + }, "riskSurfaces": { "type": "array", "items": { diff --git a/.aiwg/plugins/bt6-maintainer/payload/manifest.json b/.aiwg/plugins/bt6-maintainer/payload/manifest.json index 9abb86e..e6bcd44 100644 --- a/.aiwg/plugins/bt6-maintainer/payload/manifest.json +++ b/.aiwg/plugins/bt6-maintainer/payload/manifest.json @@ -3,7 +3,7 @@ "type": "addon", "name": "bt6-maintainer", "version": "0.3.0", - "description": "Cross-repository queue, review, issue, provider-trust, merge, and tagged-release validation for BT6 research and support tooling.", + "description": "Cross-repository queue, review, issue, provider-trust, merge, release-readiness, and exact-tag validation for BT6 research and support tooling.", "manifestVersion": "1", "platforms": { "claude": "full", @@ -20,6 +20,8 @@ "external-providers", "trust-boundaries", "merge-train", + "release-readiness", + "release-validation", "provenance" ], "deployment": { diff --git a/.aiwg/plugins/bt6-maintainer/payload/rules/bt6-maintainer-guardrails.md b/.aiwg/plugins/bt6-maintainer/payload/rules/bt6-maintainer-guardrails.md index e75b02d..67f72b0 100644 --- a/.aiwg/plugins/bt6-maintainer/payload/rules/bt6-maintainer-guardrails.md +++ b/.aiwg/plugins/bt6-maintainer/payload/rules/bt6-maintainer-guardrails.md @@ -35,8 +35,8 @@ report. 11. Apply the shared two-tier quality model consistently. Pull requests run the profile's fast `validation.quick` core-system commands and must reach at least 50% changed-line coverage for measurable production-code changes. - Tagged-release validation runs `validation.full`; do not make that - exhaustive suite an ordinary contributor PR requirement. + Pre-tag readiness and tagged-release validation run `validation.full`; do + not make that exhaustive suite an ordinary contributor PR requirement. 12. Every behavior change needs a relevant outcome-oriented test. A material behavior change with zero relevant tests is never merge-ready, regardless of aggregate coverage. Documentation-only, metadata-only, and other @@ -51,3 +51,7 @@ report. profile's `validation.full` commands and applicable documentation, research-integrity, risk-surface, packaging, and platform checks pass for that exact tagged commit. +15. After merges and before tagging, run `bt6-release-readiness` from the + previous release or configured comparison base. Audit merged behavior, + repair release-depth gaps without weakening thresholds, and then run + `bt6-release-validation` after the immutable tag is created. diff --git a/.aiwg/plugins/bt6-maintainer/payload/skills/bt6-release-readiness/SKILL.md b/.aiwg/plugins/bt6-maintainer/payload/skills/bt6-release-readiness/SKILL.md new file mode 100644 index 0000000..5161bd1 --- /dev/null +++ b/.aiwg/plugins/bt6-maintainer/payload/skills/bt6-release-readiness/SKILL.md @@ -0,0 +1,74 @@ +--- +namespace: bt6-maintainer +name: bt6-release-readiness +platforms: [all] +description: Harden an untagged BT6 release candidate to the exhaustive release bar after pull requests were accepted through the bounded contributor gate. +triggers: + - prepare a BT6 release + - harden a BT6 release candidate + - audit merged PRs for release + - improve coverage before release +requires: + - candidate-reference: an exact candidate commit and comparison base + - repository-context: canonical repository, CI remote, and validation profile resolvable from project state +ensures: + - release-gaps-audited: merged behavior and risk surfaces are checked against the full release policy + - maintainer-gaps-repaired: authorized test and correctness gaps are fixed before tagging + - candidate-not-certified: readiness evidence never substitutes for exact-tag validation +--- + +# BT6 Release Readiness + +Prepare a release candidate after normal pull requests have passed the bounded +`validation.quick` contributor gate. Apply `bt6-maintainer-guardrails`. This +workflow may improve code, tests, documentation, and repository-owned release +policy when the operator authorizes implementation. It does not create or +promote a tag. + +## Required context + +1. Resolve the canonical repository, base branch, CI remote, profile, candidate + commit, comparison base, and current authorization. +2. Require a clean or explicitly understood working tree. Preserve unrelated + work and use an isolated checkout when validation could overwrite it. +3. Inventory merged changes since the previous release or configured comparison + base. Map production changes to behavior tests, ownership, and risk surfaces. +4. Treat PR acceptance as evidence of reviewability only. Do not infer release + readiness from quick checks, aggregate coverage, or merge status. + +## Hardening loop + +1. Run `validation.full` plus applicable documentation, research-integrity, + risk-surface, packaging, compatibility, supply-chain, repeat, mutation, and + conditional-platform checks that can run before tagging. +2. Measure repository line and branch coverage, configured mature/critical + scope, changed or touched-module regression, and new-module floors. Record + skipped, unavailable, or stale evidence explicitly. +3. Prioritize failures and coverage gaps by release risk, recent change, public + contract, trust boundary, and defect history—not by raw uncovered-line count. +4. For each blocking gap, add outcome-oriented tests and correct the underlying + implementation or policy defect. Never weaken a release threshold to make a + candidate pass. +5. Re-run focused checks after each repair, then re-run the complete applicable + pre-tag gate. Stop when all locally executable hard gates pass or a required + external/platform gate remains unresolved. +6. When `releaseEvidence` is configured, require one deterministic source ZIP + of the tested commit, a canonical SHA-256 manifest, a bound source SBOM, + SLSA/in-toto provenance, and the configured signature mechanism. Do not + compile or publish installable packages unless a repository explicitly + overrides the source-snapshot policy. Downstream gates must consume the same + ZIP bytes rather than recreating them. + +## Decision + +- `ready-to-tag` only when every applicable pre-tag hard gate passes and all + unavailable evidence is intentionally deferred to an exact-tag hosted gate. +- `not-ready` when any required check fails or a correctness, security, + integrity, compatibility, or coverage gap remains. +- `hold` when the candidate, comparison base, authority, profile, or required + environment cannot be resolved safely. + +Use `templates/bt6-release-readiness-report.md`. A `ready-to-tag` result becomes +stale on any candidate commit, dependency lock, policy, test, platform, or +release-configuration change. After tagging, always run `bt6-release-validation` +against the exact immutable tag; readiness is never release certification. diff --git a/.aiwg/plugins/bt6-maintainer/payload/skills/bt6-release-validation/SKILL.md b/.aiwg/plugins/bt6-maintainer/payload/skills/bt6-release-validation/SKILL.md index 1a20fd5..40ed919 100644 --- a/.aiwg/plugins/bt6-maintainer/payload/skills/bt6-release-validation/SKILL.md +++ b/.aiwg/plugins/bt6-maintainer/payload/skills/bt6-release-validation/SKILL.md @@ -45,14 +45,19 @@ and does not publish a release without separate explicit authorization. 1. Run every repository-profile `validation.full` command at the exact tagged commit. 2. Run applicable `documentation`, `researchIntegrity`, and risk-surface checks. -3. Run repository-defined packaging, artifact-integrity, compatibility, and - supported-platform checks. Verify generated artifacts come from the tagged - source rather than an unrelated checkout. +3. Run repository-defined source-snapshot integrity, compatibility, and + supported-platform checks. Verify the release ZIP comes from the tagged + source rather than an unrelated checkout; do not require a compiled package. 4. Compare local evidence with CI for the same tag and commit. Record missing or stale evidence as incomplete, not passing. 5. Treat warnings, flakes, skips, coverage changes, and conditional-gate gaps according to repository release policy; do not inherit the relaxed PR turnaround budget as a release exemption. +6. When `releaseEvidence` is configured, verify every artifact against the + canonical checksum manifest, authenticate its SLSA/in-toto attestation, + validate the source-SBOM binding, and prove promotion uses the tested + snapshot-once ZIP bytes. A checksum without authenticated provenance is + incomplete evidence. ## Decision diff --git a/.aiwg/plugins/bt6-maintainer/payload/templates/bt6-release-readiness-report.md b/.aiwg/plugins/bt6-maintainer/payload/templates/bt6-release-readiness-report.md new file mode 100644 index 0000000..71672df --- /dev/null +++ b/.aiwg/plugins/bt6-maintainer/payload/templates/bt6-release-readiness-report.md @@ -0,0 +1,44 @@ +--- +title: BT6 Release Readiness Report +description: Pre-tag audit and hardening report for moving a candidate from the bounded PR bar to the exhaustive release bar. +--- + +# BT6 release readiness: `` + +## Context + +- Repository: `` +- Candidate commit: `` +- Comparison base: `` +- Profile: `` +- Authorization: `` + +## Merged-risk inventory + +| Surface | Changed behavior | Relevant tests/evidence | Risk | Status | +| --- | --- | --- | --- | --- | +| `` | `` | `` | `` | `` | + +## Release-depth gates + +| Gate | Threshold or expectation | Result | Evidence | +| --- | --- | --- | --- | +| Full validation | `` | `` | `` | +| Coverage and regression | `` | `` | `` | +| Quality depth | `` | `` | `` | +| Source snapshot and supply chain | `` | `` | `` | +| Conditional/platform checks | `` | `` | `` | + +## Repairs + +| Finding | Correction | Verification | Residual risk | +| --- | --- | --- | --- | +| `` | `` | `` | `` | + +## Decision + +`` + +This report is pre-tag evidence only. Any candidate, dependency, test, policy, +platform, or release-configuration change invalidates it. Exact-tag +`bt6-release-validation` remains required before artifact promotion. diff --git a/.aiwg/plugins/bt6-maintainer/payload/templates/bt6-release-validation-report.md b/.aiwg/plugins/bt6-maintainer/payload/templates/bt6-release-validation-report.md index bc6c4ad..a467ac3 100644 --- a/.aiwg/plugins/bt6-maintainer/payload/templates/bt6-release-validation-report.md +++ b/.aiwg/plugins/bt6-maintainer/payload/templates/bt6-release-validation-report.md @@ -33,7 +33,7 @@ Profile: `` | Documentation | `` | `
` | | Research and provenance | `` | `
` | | Risk-surface checks | `` | `
` | -| Packaging and artifact integrity | `` | `
` | +| Source ZIP and artifact integrity | `` | `
` | | Supported platforms | `` | `
` | ## Decision diff --git a/.aiwg/plugins/bt6-maintainer/payload/templates/bt6-repository-profile.yaml b/.aiwg/plugins/bt6-maintainer/payload/templates/bt6-repository-profile.yaml index c350abf..2ea6f38 100644 --- a/.aiwg/plugins/bt6-maintainer/payload/templates/bt6-repository-profile.yaml +++ b/.aiwg/plugins/bt6-maintainer/payload/templates/bt6-repository-profile.yaml @@ -29,11 +29,20 @@ validation: qualityPolicy: pullRequestChangedLineCoverageFloor: 50 requireBehaviorTests: true - fullSuiteTrigger: "tagged-release" + fullSuiteTrigger: "release-readiness-and-tagged-validation" documentation: - "" researchIntegrity: - "" +releaseEvidence: + artifactType: "source-zip" + hashAlgorithm: "sha256" + provenanceFormat: "slsa-v1" + attestationFormat: "in-toto" + signingMode: "sigstore-keyless" + sbomFormat: "cyclonedx" + snapshotOnce: true + verifyBeforePromotion: true riskSurfaces: - id: "ingestion" paths: [""] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3354cbd..228a166 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,11 @@ jobs: if: github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest timeout-minutes: 15 + permissions: + artifact-metadata: write + attestations: write + contents: read + id-token: write env: BUILD_TOOLS: /tmp/obliteratus-ci-tools @@ -44,123 +49,76 @@ jobs: pyproject.toml uv.lock - - name: Install build tooling + - name: Install release evidence tooling run: | python -m pip install "uv==${UV_VERSION}" UV_PROJECT_ENVIRONMENT="$BUILD_TOOLS" uv sync --locked --only-group ci - - name: Build source and wheel distributions + - name: Create deterministic tested-source snapshot + run: | + mkdir -p release package-evidence + snapshot="release/OBLITERATUS-${GITHUB_SHA}.zip" + git archive \ + --format=zip \ + --prefix="OBLITERATUS-${GITHUB_SHA}/" \ + --output="$snapshot" \ + "$GITHUB_SHA" + test "$(find release -maxdepth 1 -type f -name '*.zip' | wc -l)" -eq 1 + unzip -t "$snapshot" | tee package-evidence/source-zip-check.txt + + - name: Generate and bind source CycloneDX SBOM + run: | + uv --preview-features sbom-export export \ + --locked \ + --format cyclonedx1.5 \ + --all-extras \ + --no-dev \ + --no-editable \ + --output-file package-evidence/obliteratus.cdx.unbound.json + mapfile -t snapshots < <(find release -maxdepth 1 -type f -name '*.zip' -print) + if [ "${#snapshots[@]}" -ne 1 ]; then + echo "expected exactly one source snapshot, found ${#snapshots[@]}" + exit 1 + fi + python scripts/check_supply_chain_policy.py sbom \ + --input package-evidence/obliteratus.cdx.unbound.json \ + --artifact "${snapshots[0]}" \ + --output package-evidence/obliteratus.cdx.json + rm package-evidence/obliteratus.cdx.unbound.json + + - name: Generate canonical release checksums run: >- - "$BUILD_TOOLS/bin/python" -m build --sdist --wheel + sha256sum release/*.zip package-evidence/obliteratus.cdx.json | + tee package-evidence/SHA256SUMS - - name: Verify wheel contents and entry point + - name: Attest release-set SLSA provenance + id: provenance + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 + with: + subject-checksums: package-evidence/SHA256SUMS + + - name: Attest source snapshot SBOM + id: sbom-attestation + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 + with: + subject-path: release/*.zip + sbom-path: package-evidence/obliteratus.cdx.json + + - name: Retain Sigstore attestation bundles run: | - python - <<'PY' - from pathlib import Path - from zipfile import ZipFile + cp "${{ steps.provenance.outputs.bundle-path }}" \ + package-evidence/slsa-provenance.sigstore.json + cp "${{ steps.sbom-attestation.outputs.bundle-path }}" \ + package-evidence/sbom-attestation.sigstore.json + sha256sum -c package-evidence/SHA256SUMS - wheels = list(Path("dist").glob("*.whl")) - if len(wheels) != 1: - raise SystemExit(f"expected one wheel, found: {wheels}") - - with ZipFile(wheels[0]) as archive: - names = set(archive.namelist()) - required = { - "app.py", - "obliteratus/__init__.py", - "obliteratus/local_ui.py", - } - missing = sorted(required - names) - if missing: - raise SystemExit(f"wheel is missing required modules: {missing}") - - entry_points = [ - name for name in names if name.endswith(".dist-info/entry_points.txt") - ] - if len(entry_points) != 1: - raise SystemExit(f"expected one entry_points.txt, found: {entry_points}") - contents = archive.read(entry_points[0]).decode("utf-8") - if "obliteratus = obliteratus.cli:main" not in contents: - raise SystemExit("wheel is missing the obliteratus console entry point") - - print(f"verified wheel contents: {wheels[0]}") - PY - - - name: Validate distribution metadata - run: | - mkdir -p package-evidence - "$BUILD_TOOLS/bin/python" -m twine check dist/* | tee package-evidence/twine-check.txt - sha256sum dist/* | tee package-evidence/SHA256SUMS - - - name: Verify installed wheel contract - run: | - mapfile -t wheels < <(find "$GITHUB_WORKSPACE/dist" -maxdepth 1 -type f -name '*.whl' -print) - if [ "${#wheels[@]}" -ne 1 ]; then - echo "expected exactly one wheel, found ${#wheels[@]}" - exit 1 - fi - - wheel_env="$RUNNER_TEMP/obliteratus-wheel-env" - wheel_cwd="$RUNNER_TEMP/obliteratus-wheel-cwd" - python -m venv "$wheel_env" - mkdir -p "$wheel_cwd" - "$wheel_env/bin/python" -m pip install --no-cache-dir "rich==15.0.0" - "$wheel_env/bin/python" -m pip install --no-cache-dir --no-deps "${wheels[0]}" - - cd "$wheel_cwd" - "$wheel_env/bin/python" -I - <<'PY' | tee "$GITHUB_WORKSPACE/package-evidence/wheel-import.txt" - import importlib.metadata - from pathlib import Path - - import obliteratus - - origin = Path(obliteratus.__file__).resolve() - assert "site-packages" in origin.parts, origin - assert obliteratus.__version__ == importlib.metadata.version("obliteratus") - print(f"installed wheel import: {origin}") - print(f"version: {obliteratus.__version__}") - PY - "$wheel_env/bin/python" -I -m obliteratus --help > "$GITHUB_WORKSPACE/package-evidence/wheel-module-help.txt" - "$wheel_env/bin/obliteratus" --help > "$GITHUB_WORKSPACE/package-evidence/wheel-console-help.txt" - - - name: Verify installed sdist contract - run: | - mapfile -t sdists < <(find "$GITHUB_WORKSPACE/dist" -maxdepth 1 -type f -name '*.tar.gz' -print) - if [ "${#sdists[@]}" -ne 1 ]; then - echo "expected exactly one sdist, found ${#sdists[@]}" - exit 1 - fi - - sdist_env="$RUNNER_TEMP/obliteratus-sdist-env" - sdist_cwd="$RUNNER_TEMP/obliteratus-sdist-cwd" - python -m venv "$sdist_env" - mkdir -p "$sdist_cwd" - "$sdist_env/bin/python" -m pip install --no-cache-dir "rich==15.0.0" - "$sdist_env/bin/python" -m pip install --no-cache-dir --no-deps "${sdists[0]}" - - cd "$sdist_cwd" - "$sdist_env/bin/python" -I - <<'PY' | tee "$GITHUB_WORKSPACE/package-evidence/sdist-import.txt" - import importlib.metadata - from pathlib import Path - - import obliteratus - - origin = Path(obliteratus.__file__).resolve() - assert "site-packages" in origin.parts, origin - assert obliteratus.__version__ == importlib.metadata.version("obliteratus") - print(f"installed sdist import: {origin}") - print(f"version: {obliteratus.__version__}") - PY - "$sdist_env/bin/python" -I -m obliteratus --help > "$GITHUB_WORKSPACE/package-evidence/sdist-module-help.txt" - "$sdist_env/bin/obliteratus" --help > "$GITHUB_WORKSPACE/package-evidence/sdist-console-help.txt" - - - name: Upload distributions and package evidence + - name: Upload tested source snapshot and release evidence if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: distributions-py3.12 + name: tested-source-snapshot-py3.12 path: | - dist/ + release/ package-evidence/ if-no-files-found: error retention-days: 14 @@ -706,6 +664,7 @@ jobs: supply-chain: name: Supply chain if: github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v') + needs: package runs-on: ubuntu-latest timeout-minutes: 30 env: @@ -719,6 +678,12 @@ jobs: - name: Check out repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Download attested tested-source snapshot + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: tested-source-snapshot-py3.12 + path: /tmp/supply-chain-evidence/release + - name: Set up Python uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: @@ -784,27 +749,19 @@ jobs: echo "$status" > "$EVIDENCE/uv-audit-py${version}.status" done - - name: Build wheel and bind CycloneDX SBOM + - name: Verify attested source snapshot and bound CycloneDX SBOM run: | - mkdir -p "$EVIDENCE/dist" - "$SUPPLY_TOOLS/bin/python" -m build --wheel --outdir "$EVIDENCE/dist" - uv --preview-features sbom-export export \ - --locked \ - --format cyclonedx1.5 \ - --all-extras \ - --no-dev \ - --no-editable \ - --output-file "$EVIDENCE/obliteratus.cdx.unbound.json" - mapfile -t wheels < <(find "$EVIDENCE/dist" -maxdepth 1 -type f -name '*.whl' -print) - if [ "${#wheels[@]}" -ne 1 ]; then - echo "expected exactly one wheel, found ${#wheels[@]}" + cd "$EVIDENCE/release" + sha256sum -c package-evidence/SHA256SUMS + mapfile -t snapshots < <(find release -maxdepth 1 -type f -name '*.zip' -print) + if [ "${#snapshots[@]}" -ne 1 ]; then + echo "expected exactly one source snapshot, found ${#snapshots[@]}" exit 1 fi - python scripts/check_supply_chain_policy.py sbom \ - --input "$EVIDENCE/obliteratus.cdx.unbound.json" \ - --wheel "${wheels[0]}" \ - --output "$EVIDENCE/obliteratus.cdx.json" - sha256sum "${wheels[0]}" > "$EVIDENCE/distribution.SHA256SUM" + python "$GITHUB_WORKSPACE/scripts/check_supply_chain_policy.py" sbom \ + --input package-evidence/obliteratus.cdx.json \ + --artifact "${snapshots[0]}" \ + --output "$EVIDENCE/verified-obliteratus.cdx.json" - name: Collect packaged-dependency license inventory run: >- diff --git a/WORKSPACE.md b/WORKSPACE.md index dc5271d..964ffa2 100644 --- a/WORKSPACE.md +++ b/WORKSPACE.md @@ -59,15 +59,27 @@ regression and 80%/75% new-module floors; 94%/84% mature CPU-scope coverage; selective mutation at 85%; repeat and duration budgets; Windows checkpoint contracts; packaging; and supply-chain certification. +Before a tag is created, run the BT6 release-readiness workflow against the +previous release or configured comparison base. It inventories merged behavior, +runs all locally applicable release-depth gates, and repairs coverage, +correctness, documentation, packaging, and policy gaps. After tagging, run +exact-tag release validation; pre-tag readiness is never promotion evidence. + Canonical integrations must retain a verifiable commit signature. The repository profile permits merge integration, not GitHub rebase integration, because rebase can recreate an audited signed head as an unsigned canonical commit. Verify the actual `main` commit after every merge; never rewrite `main` to repair history. -Release CI additionally validates wheel and sdist metadata, installs each distribution -in an independent environment outside the checkout, exercises both CLI entry -paths, and retains the distributions plus evidence. Immutable CI action/tool -pins are recorded in [ci/digests.txt](ci/digests.txt). +Release CI creates one deterministic ZIP of the exact tested repository commit. +The ZIP is a source snapshot and release-quality marker, not an installable or +compiled distribution. Immutable CI action/tool pins are recorded in +[ci/digests.txt](ci/digests.txt). + +The package job is the sole source-snapshot producer. It binds the ZIP and +CycloneDX source SBOM digests into `SHA256SUMS`, creates keyless +Sigstore-backed SLSA and SBOM attestations, and retains their bundles. +Downstream supply-chain and publication jobs must verify and reuse that exact +ZIP rather than recreating an equivalent-looking archive. The source-to-test ownership graph is versioned in [ci/test-risk-map.json](ci/test-risk-map.json). Coverage, JUnit, repeat, and @@ -79,8 +91,8 @@ flaking twice in 30 days requires an active owner/issue-linked quarantine. Python CI resolution is locked by `uv.lock`, including the official CPU-only PyTorch source for Linux and Windows. The required Supply chain job scans all supported Python versions for known vulnerabilities, scans the checkout for -secrets with fully redacted evidence, enforces the packaged-dependency license -allow list, and binds a CycloneDX SBOM to the built wheel. Exception and update +secrets with fully redacted evidence, enforces the dependency license allow +list, and binds a CycloneDX source SBOM to the repository ZIP. Exception and update rules are documented in [docs/SUPPLY_CHAIN_POLICY.md](docs/SUPPLY_CHAIN_POLICY.md). GPU, MPS, MLX, model-download, external-evaluation, network, operator-UI, and diff --git a/ci/digests.txt b/ci/digests.txt index 1d34d26..c093c40 100644 --- a/ci/digests.txt +++ b/ci/digests.txt @@ -2,6 +2,8 @@ action actions/checkout 3d3c42e5aac5ba805825da76410c181273ba90b1 v7.0.1 2026-08-14 baseline pin (#59) action actions/setup-python 5fda3b95a4ea91299a34e894583c3862153e4b97 v7.0.0 2026-08-14 baseline pin (#59) action actions/upload-artifact 043fb46d1a93c77aae656e7c1c64a875d1fc6a0a v7.0.1 2026-08-14 test/package evidence (#64, #65) +action actions/download-artifact 3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c v8.0.1 2026-03-11 immutable release-set reuse (#142) +action actions/attest 1e69f48acb82d1966a394da916b4c1698aa569d6 v4.2.2 2026-08-04 SLSA provenance and SBOM attestations (#142) tool rhysd/actionlint sha256:8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8 v1.7.12 2026-08-14 workflow syntax/policy gate (#65) tool astral-sh/uv pypi:0.12.4 v0.12.4 2026-08-14 locked dependency and SBOM engine (#66) tool gitleaks/gitleaks sha256:551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb v8.30.1 2026-08-14 redacted checkout secret scan (#66) diff --git a/docs/SUPPLY_CHAIN_POLICY.md b/docs/SUPPLY_CHAIN_POLICY.md index 932c030..37c7bdd 100644 --- a/docs/SUPPLY_CHAIN_POLICY.md +++ b/docs/SUPPLY_CHAIN_POLICY.md @@ -9,15 +9,45 @@ CI uses exact Python tool versions and checksum-pinned standalone binaries. `ci/digests.txt` records executable and GitHub Action pins; build, test, and license-tool transitive dependencies are covered by `uv.lock`. +## Digest-bound release evidence + +The release artifact is a deterministic ZIP snapshot of the tested repository +commit. No wheel, source distribution, executable, or other compiled package is +published. The release job creates the snapshot once with `git archive`, then +emits a bound CycloneDX source SBOM and a canonical `SHA256SUMS` manifest. + +GitHub's keyless Sigstore-backed attestation service signs two in-toto +statements for that release set: + +- SLSA build provenance for every subject named by `SHA256SUMS`; +- an SBOM attestation binding the CycloneDX document to the source ZIP digest. + +The supply-chain job downloads the package job's retained artifact, verifies +`SHA256SUMS`, and revalidates the SBOM binding. It must not create a substitute +archive. Publication must use this same tested and attested ZIP. A commit, +dependency lock, snapshot instruction, artifact, checksum manifest, SBOM, or +release-policy change invalidates the evidence. + +Consumers can perform the portable integrity check with: + +```bash +sha256sum --check SHA256SUMS +``` + +For authenticity and provenance, verify the source ZIP against the canonical +repository with GitHub's attestation verifier. A checksum without its signed +provenance proves byte integrity only; it does not prove who built the artifact +or which source and build instructions produced it. + ## Required evidence The Supply chain job retains these artifacts for 14 days: - one redacted Gitleaks JSON report for the checkout; - OSV audit JSON and scanner status for Python 3.10, 3.11, and 3.12 on Linux; -- a JSON license inventory for all packaged extras; -- a CycloneDX 1.5 SBOM bound to the built wheel by SHA-256; -- the policy decisions and built wheel used by those checks. +- a JSON license inventory for all supported dependency extras; +- a CycloneDX 1.5 source SBOM bound to the repository ZIP by SHA-256; +- the policy decisions and exact tested source ZIP used by those checks. Every secret finding and every OSV vulnerability is blocking by default. OSV does not provide a normalized severity for every advisory, so the policy treats diff --git a/scripts/check_supply_chain_policy.py b/scripts/check_supply_chain_policy.py index 6ccdbad..d7f9b8e 100644 --- a/scripts/check_supply_chain_policy.py +++ b/scripts/check_supply_chain_policy.py @@ -291,8 +291,8 @@ def evaluate_secrets( } -def bind_sbom(sbom: object, wheel: Path) -> dict[str, Any]: - """Bind a CycloneDX SBOM to the exact built wheel by SHA-256.""" +def bind_sbom(sbom: object, artifact: Path) -> dict[str, Any]: + """Bind a CycloneDX SBOM to the exact promoted source artifact by SHA-256.""" if not isinstance(sbom, dict) or sbom.get("bomFormat") != "CycloneDX": raise ValueError("SBOM must be a CycloneDX JSON object") if sbom.get("specVersion") != "1.5": @@ -303,12 +303,12 @@ def bind_sbom(sbom: object, wheel: Path) -> dict[str, Any]: component = metadata.get("component") if not isinstance(component, dict) or component.get("name") != "obliteratus": raise ValueError("SBOM metadata must describe obliteratus") - digest = hashlib.sha256(wheel.read_bytes()).hexdigest() + digest = hashlib.sha256(artifact.read_bytes()).hexdigest() component["hashes"] = [{"alg": "SHA-256", "content": digest}] properties = component.setdefault("properties", []) if not isinstance(properties, list): raise ValueError("SBOM component properties must be a list") - properties.append({"name": "obliteratus:distribution-file", "value": wheel.name}) + properties.append({"name": "obliteratus:release-artifact", "value": artifact.name}) return sbom @@ -343,7 +343,7 @@ def _parser() -> argparse.ArgumentParser: licenses.add_argument("--decision", type=Path, required=True) sbom = commands.add_parser("sbom") sbom.add_argument("--input", type=Path, required=True) - sbom.add_argument("--wheel", type=Path, required=True) + sbom.add_argument("--artifact", type=Path, required=True) sbom.add_argument("--output", type=Path, required=True) return parser @@ -354,9 +354,9 @@ def main() -> int: failures = validate_policy(_read_json(args.policy)) return _write_decision(Path("/dev/null"), {"passed": not failures, "failures": failures}) if args.command == "sbom": - bound = bind_sbom(_read_json(args.input), args.wheel) + bound = bind_sbom(_read_json(args.input), args.artifact) args.output.write_text(json.dumps(bound, indent=2) + "\n", encoding="utf-8") - print(f"bound SBOM to {args.wheel.name}") + print(f"bound SBOM to {args.artifact.name}") return 0 policy = _read_json(args.policy) diff --git a/tests/test_aiwg_workspace_contracts.py b/tests/test_aiwg_workspace_contracts.py index 1f44fc3..b3a7b19 100644 --- a/tests/test_aiwg_workspace_contracts.py +++ b/tests/test_aiwg_workspace_contracts.py @@ -56,7 +56,7 @@ def test_bt6_maintainer_installation_matches_project_plugin(): assert installation["deployedTo"]["codex"] == { "agents": 5, "commands": 0, - "skills": 6, + "skills": 7, "rules": 1, } assert "skills/bt6-release-validation/SKILL.md" in installation["artifactHashes"] @@ -113,3 +113,33 @@ def test_delivery_profile_preserves_signed_canonical_history(): assert profile["delivery"]["defaultMergeMethod"] == "merge" assert profile["delivery"]["allowedMergeMethods"] == ["merge"] + + +def test_release_profile_enforces_cpu_coverage_without_collecting_hardware_gates(): + profile = yaml.safe_load( + (ROOT / ".aiwg" / "bt6-maintainer.yaml").read_text(encoding="utf-8"), + ) + commands = "\n".join(profile["validation"]["full"]) + + assert "--cov-branch" in commands + assert "--min-line 75" in commands + assert "--min-branch 60" in commands + assert "scripts/check_quality_policy.py" in commands + assert "scripts/check_conditional_policy.py" in commands + assert "scripts/check_test_risk_map.py" in commands + for marker in ("gpu", "mps", "mlx", "network", "download", "remote", "operator_ui"): + assert f"not {marker}" in commands + + assert profile["validation"]["qualityPolicy"]["fullSuiteTrigger"] == ( + "release-readiness-and-tagged-validation" + ) + assert profile["releaseEvidence"] == { + "hashAlgorithm": "sha256", + "provenanceFormat": "slsa-v1", + "attestationFormat": "in-toto", + "signingMode": "sigstore-keyless", + "sbomFormat": "cyclonedx", + "artifactType": "source-zip", + "snapshotOnce": True, + "verifyBeforePromotion": True, + } diff --git a/tests/test_ci_policy.py b/tests/test_ci_policy.py index aa3adfc..1034eb4 100644 --- a/tests/test_ci_policy.py +++ b/tests/test_ci_policy.py @@ -41,6 +41,36 @@ def test_every_external_action_is_sha_pinned_and_manifested(): assert entries[("action", name)] == (pin, version) +def test_tested_source_snapshot_is_created_once_attested_and_reused(): + workflow = WORKFLOW.read_text(encoding="utf-8") + package = workflow.split(" package:\n", maxsplit=1)[1].split( + " lint:\n", maxsplit=1, + )[0] + supply_chain = workflow.split(" supply-chain:\n", maxsplit=1)[1] + + assert "artifact-metadata: write" in package + assert "attestations: write" in package + assert "id-token: write" in package + assert "package-evidence/SHA256SUMS" in package + assert "package-evidence/obliteratus.cdx.json" in package + assert 'git archive \\' in package + assert '--format=zip' in package + assert 'subject-path: release/*.zip' in package + assert "python -m build" not in package + assert "*.whl" not in package + assert "*.tar.gz" not in package + assert package.count("uses: actions/attest@") == 2 + assert "subject-checksums: package-evidence/SHA256SUMS" in package + assert "sbom-path: package-evidence/obliteratus.cdx.json" in package + + assert "needs: package" in supply_chain + assert "uses: actions/download-artifact@" in supply_chain + assert "sha256sum -c package-evidence/SHA256SUMS" in supply_chain + assert "tested-source-snapshot-py3.12" in supply_chain + assert "find release -maxdepth 1 -type f -name '*.zip'" in supply_chain + assert "-m build" not in supply_chain + + def test_actionlint_version_and_checksum_match_manifest(): workflow = WORKFLOW.read_text(encoding="utf-8") pin, version = _manifest_entries()[("tool", "rhysd/actionlint")] diff --git a/tests/test_jetson_support_tooling.py b/tests/test_jetson_support_tooling.py index 2a0ddb8..84e6f7e 100644 --- a/tests/test_jetson_support_tooling.py +++ b/tests/test_jetson_support_tooling.py @@ -5,13 +5,17 @@ from __future__ import annotations import importlib.metadata import json import sys -import tomllib from pathlib import Path from types import SimpleNamespace import pytest import yaml +try: + import tomllib +except ModuleNotFoundError: # pragma: no cover - exercised by the Python 3.10 CI lane + import tomli as tomllib + from scripts import jetson_support from scripts import run_conditional_gate from scripts import setup_jetson diff --git a/tests/test_supply_chain_policy.py b/tests/test_supply_chain_policy.py index 046c7e7..5917503 100644 --- a/tests/test_supply_chain_policy.py +++ b/tests/test_supply_chain_policy.py @@ -177,24 +177,24 @@ def test_secret_gate_requires_redaction_even_for_suppressed_finding(): assert any("not fully redacted" in failure for failure in decision["failures"]) -def test_bind_sbom_adds_exact_wheel_hash(tmp_path): - wheel = tmp_path / "obliteratus-0.1.2-py3-none-any.whl" - wheel.write_bytes(b"wheel") +def test_bind_sbom_adds_exact_release_artifact_hash(tmp_path): + artifact = tmp_path / "OBLITERATUS-deadbeef.zip" + artifact.write_bytes(b"source snapshot") sbom = { "bomFormat": "CycloneDX", "specVersion": "1.5", "metadata": {"component": {"name": "obliteratus"}}, } - bound = MODULE.bind_sbom(sbom, wheel) + bound = MODULE.bind_sbom(sbom, artifact) component = bound["metadata"]["component"] assert component["hashes"] == [ { "alg": "SHA-256", - "content": "ba59926159d2aa256eb8739b8da7e2b574b960e1202c6d624cbe981cef996c91", + "content": "41637c7875947b50775c0ab2bb30a28b292be748376a5713eb8f527d9953433c", }, ] assert component["properties"] == [ - {"name": "obliteratus:distribution-file", "value": wheel.name}, + {"name": "obliteratus:release-artifact", "value": artifact.name}, ]