diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock index b851c524..ea76a3be 100644 --- a/.github/workflows/actions.lock +++ b/.github/workflows/actions.lock @@ -89,6 +89,7 @@ workflows: '.github/workflows/rust-ci-reusable.yml': - 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' - 'dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772' + - 'goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406' - 'swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6' '.github/workflows/scorecard-enforcer.yml': - 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' @@ -96,9 +97,9 @@ workflows: - 'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' - 'ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc' '.github/workflows/scorecard-reusable.yml': - - 'github/codeql-action@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28' - 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' - 'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' + - 'github/codeql-action@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28' - 'ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc' '.github/workflows/secret-scanner-reusable.yml': - 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' @@ -177,7 +178,7 @@ dependencies: owner_id: 42048915 repo_id: 356423100 'dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772': - ref: 'v1' + ref: '6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772' commit: 'sha1-6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772' owner_id: 1940490 repo_id: 260749683 @@ -196,6 +197,11 @@ dependencies: commit: 'sha1-db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28' owner_id: 9919 repo_id: 259445878 + 'goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406': + ref: 'v2.2.1' + commit: 'sha1-abea47f85e598557f500fa1fd2ab7464fcb39406' + owner_id: 1006268 + repo_id: 212984112 'haskell-actions/setup@6037f33647c3f17758a2356c80fc4a53d7e0685d': ref: 'v2.12.0' commit: 'sha1-6037f33647c3f17758a2356c80fc4a53d7e0685d' @@ -217,7 +223,7 @@ dependencies: owner_id: 18365890 repo_id: 220359305 'swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6': - ref: 'v2.9.2' + ref: '6323deb102c322ba6fcbdcafc7e3dddab59af2b6' commit: 'sha1-6323deb102c322ba6fcbdcafc7e3dddab59af2b6' owner_id: 580492 repo_id: 298565987 diff --git a/.github/workflows/allowlist-preflight-reusable.yml b/.github/workflows/allowlist-preflight-reusable.yml index e33c099e..b31e5235 100644 --- a/.github/workflows/allowlist-preflight-reusable.yml +++ b/.github/workflows/allowlist-preflight-reusable.yml @@ -15,7 +15,7 @@ permissions: jobs: preflight: - name: Actions policy (tree + live) + name: Actions policy (tree) runs-on: ubuntu-latest timeout-minutes: 5 steps: @@ -29,7 +29,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: hyperpolymath/standards - ref: main + ref: ${{ job.workflow_sha }} path: .standards-checkout sparse-checkout: | rhodium-standard-repositories/actions-allowlist @@ -56,9 +56,56 @@ jobs: bash "$RUNNER_TEMP/check-allowed-actions.sh" \ "$RUNNER_TEMP/allowed-actions.json" .github/workflows + live-policy: + name: Live Actions policy (credentialed advisory) + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout policy source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: hyperpolymath/standards + ref: ${{ job.workflow_sha }} + path: .standards-checkout + sparse-checkout: | + rhodium-standard-repositories/actions-allowlist + scripts/check-actions-policy.sh + sparse-checkout-cone-mode: false + + - name: Report unavailable live policy credential + env: + GH_TOKEN: ${{ secrets.HYPATIA_SCAN_PAT }} + if: ${{ env.GH_TOKEN == '' }} + run: | + echo "::notice::Live Actions policy was not evaluated: HYPATIA_SCAN_PAT was not supplied by the caller. The separate tree allowlist gate still ran." + # shellcheck disable=SC2016 + printf '%s\n' \ + '### Live Actions policy not evaluated' \ + '' \ + 'The caller did not supply `HYPATIA_SCAN_PAT`. This advisory job' \ + 'does not claim that the repository Administration setting passed.' \ + 'The independent tree allowlist preflight remains enforced.' \ + >> "$GITHUB_STEP_SUMMARY" + - name: Check live repository policy env: - GH_TOKEN: ${{ secrets.HYPATIA_SCAN_PAT || github.token }} + GH_TOKEN: ${{ secrets.HYPATIA_SCAN_PAT }} + if: ${{ env.GH_TOKEN != '' }} run: | - bash "$RUNNER_TEMP/check-actions-policy.sh" \ - "$GITHUB_REPOSITORY" "$RUNNER_TEMP/allowed-actions.json" + set +e + bash .standards-checkout/scripts/check-actions-policy.sh \ + "$GITHUB_REPOSITORY" \ + .standards-checkout/rhodium-standard-repositories/actions-allowlist/allowed-actions.json + rc=$? + set -e + if [ "$rc" -eq 3 ]; then + echo "::notice::Live Actions policy was not evaluated: the supplied credential could not read the Administration endpoint." + printf '%s\n' \ + '### Live Actions policy not evaluated' \ + '' \ + 'A credential was supplied, but the Administration API was unavailable.' \ + 'No live-policy pass is claimed; rotate or re-authorise the credential.' \ + >> "$GITHUB_STEP_SUMMARY" + exit 0 + fi + exit "$rc" diff --git a/.github/workflows/governance-reusable.yml b/.github/workflows/governance-reusable.yml index 5a8a1929..decd2d64 100644 --- a/.github/workflows/governance-reusable.yml +++ b/.github/workflows/governance-reusable.yml @@ -40,14 +40,18 @@ jobs: echo "has_baseline=false" >> $GITHUB_OUTPUT fi - - name: Clone standards repository - run: | - # Treeless partial clone: full commit graph (needed by the staleness - # gate's ancestry / commits-behind / age math) without paying for - # every tree+blob in history. Falls back to a deep clone if the - # server rejects the partial-clone filter. - git clone --filter=tree:0 https://github.com/hyperpolymath/standards.git "$HOME/standards" \ - || git clone --depth 200 https://github.com/hyperpolymath/standards.git "$HOME/standards" + - name: Checkout pinned standards history + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: hyperpolymath/standards + # The gate needs the full graph for ancestry and age checks, but its + # script must come from the exact reusable commit rather than moving + # main. This also lets a Standards PR test its own validator change. + ref: ${{ job.workflow_sha }} + path: .standards-history + fetch-depth: 0 + filter: tree:0 + persist-credentials: false - name: Run staleness check env: @@ -60,8 +64,9 @@ jobs: # past this commit: the script comes from standards HEAD, but the job # definition comes from whatever SHA the consumer pins. GITHUB_TOKEN: ${{ github.token }} + STALENESS_STANDARDS_DIR: ${{ github.workspace }}/.standards-history run: | - bash "$HOME/standards/scripts/check-workflow-staleness.sh" . + bash .standards-history/scripts/check-workflow-staleness.sh . allowlist-preflight: name: Allowlist Preflight @@ -80,7 +85,9 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: hyperpolymath/standards - ref: main + # Exact commit of this reusable workflow. This preserves consumer + # pin semantics and lets a standards PR test its own scripts. + ref: ${{ job.workflow_sha }} path: .standards-checkout sparse-checkout: | rhodium-standard-repositories/actions-allowlist @@ -103,17 +110,66 @@ jobs: fi bash "$RUNNER_TEMP/check-allowed-actions.sh" \ "$RUNNER_TEMP/allowed-actions.json" .github/workflows + rm -rf .standards-checkout + + actions-policy-live: + name: Live Actions policy (credentialed advisory) + runs-on: ${{ inputs.runs-on }} + timeout-minutes: 5 + permissions: + contents: read + steps: + - name: Checkout standards for the live policy gate + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: hyperpolymath/standards + ref: ${{ job.workflow_sha }} + path: .standards-checkout + sparse-checkout: | + rhodium-standard-repositories/actions-allowlist + scripts/check-actions-policy.sh + sparse-checkout-cone-mode: false + + - name: Report unavailable live policy credential + env: + GH_TOKEN: ${{ secrets.HYPATIA_SCAN_PAT }} + if: ${{ env.GH_TOKEN == '' }} + run: | + echo "::notice::Live Actions policy was not evaluated: HYPATIA_SCAN_PAT was not supplied by the caller. The separate tree allowlist gate still ran." + # shellcheck disable=SC2016 + printf '%s\n' \ + '### Live Actions policy not evaluated' \ + '' \ + 'The caller did not supply `HYPATIA_SCAN_PAT`. This advisory job' \ + 'does not claim that the repository Administration setting passed.' \ + 'The independent tree allowlist preflight remains enforced.' \ + >> "$GITHUB_STEP_SUMMARY" - name: Check live Actions policy env: - # GitHub's job token cannot read this Administration endpoint. Keep - # this dependency explicit: missing/expired credentials must make the - # live control red rather than silently reducing it to a tree check. + # The ordinary job token cannot read the repository Administration + # endpoint. When a caller deliberately supplies this credential, an + # API/authentication failure or real policy violation remains red. GH_TOKEN: ${{ secrets.HYPATIA_SCAN_PAT }} + if: ${{ env.GH_TOKEN != '' }} run: | - rm -rf .standards-checkout - bash "$RUNNER_TEMP/check-actions-policy.sh" \ - "$GITHUB_REPOSITORY" "$RUNNER_TEMP/allowed-actions.json" + set +e + bash .standards-checkout/scripts/check-actions-policy.sh \ + "$GITHUB_REPOSITORY" \ + .standards-checkout/rhodium-standard-repositories/actions-allowlist/allowed-actions.json + rc=$? + set -e + if [ "$rc" -eq 3 ]; then + echo "::notice::Live Actions policy was not evaluated: the supplied credential could not read the Administration endpoint." + printf '%s\n' \ + '### Live Actions policy not evaluated' \ + '' \ + 'A credential was supplied, but the Administration API was unavailable.' \ + 'No live-policy pass is claimed; rotate or re-authorise the credential.' \ + >> "$GITHUB_STEP_SUMMARY" + exit 0 + fi + exit "$rc" validate-hypatia-baseline: name: Validate Hypatia Baseline @@ -188,14 +244,16 @@ jobs: # A reusable workflow only auto-checks-out its own YAML, not sibling # scripts. Sparse-check-out standards' scripts/ to get apply-baseline.sh, - # mirroring the language-policy job below. Pinned to main because - # github.workflow_sha resolves to the *caller* repo's SHA (which would 404). + # mirroring the language-policy job below. `job.workflow_sha` is the exact + # commit of this called workflow, so the filter and schema semantics cannot + # drift from the reusable workflow that invoked them. This also lets a + # standards PR test its own script change instead of silently using main. - name: Check out standards for the baseline filter if: needs.workflow-staleness.outputs.has_baseline == 'true' uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: hyperpolymath/standards - ref: main + ref: ${{ job.workflow_sha }} path: .standards-checkout sparse-checkout: | scripts diff --git a/.github/workflows/label-triage.yml b/.github/workflows/label-triage.yml index 9886e920..e61baef9 100644 --- a/.github/workflows/label-triage.yml +++ b/.github/workflows/label-triage.yml @@ -46,6 +46,7 @@ permissions: jobs: triage: runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Classify and label env: diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index c80b676c..c536afb9 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -32,6 +32,7 @@ permissions: jobs: sync: runs-on: ubuntu-latest + timeout-minutes: 10 steps: - name: Apply canonical labels env: diff --git a/.github/workflows/rust-ci-reusable.yml b/.github/workflows/rust-ci-reusable.yml index 085f7771..84683bab 100644 --- a/.github/workflows/rust-ci-reusable.yml +++ b/.github/workflows/rust-ci-reusable.yml @@ -30,6 +30,7 @@ # with: # enable_audit: true # enable_coverage: true +# zig_version: "0.15.2" # for Rust crates with a Zig-native build # # Sub-crate / monorepo workspace (Cargo.toml lives in a subdirectory): # @@ -101,6 +102,14 @@ on: type: string required: false default: "." + zig_version: + description: | + Exact Zig version required by a Rust crate's native build. Leave + empty for pure-Rust workspaces. When set, check, test, and coverage + jobs install the same compiler before invoking Cargo. + type: string + required: false + default: "" # Only `contents: read` is requested. A reusable workflow may narrow the # caller's permissions but never widen them: requesting a permission the @@ -171,6 +180,12 @@ jobs: toolchain: stable components: clippy, rustfmt + - name: Install Zig for native build + if: ${{ inputs.zig_version != '' }} + uses: goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406 # v2.2.1 + with: + version: ${{ inputs.zig_version }} + - name: Cache cargo registry and build uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 with: @@ -213,6 +228,12 @@ jobs: with: toolchain: stable + - name: Install Zig for native build + if: ${{ inputs.zig_version != '' }} + uses: goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406 # v2.2.1 + with: + version: ${{ inputs.zig_version }} + - name: Cache cargo registry and build uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 with: @@ -293,6 +314,12 @@ jobs: toolchain: stable components: llvm-tools-preview + - name: Install Zig for native build + if: ${{ inputs.zig_version != '' }} + uses: goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406 # v2.2.1 + with: + version: ${{ inputs.zig_version }} + - name: Install cargo-llvm-cov run: cargo install cargo-llvm-cov --locked diff --git a/.hypatia-baseline.json b/.hypatia-baseline.json index 893b1dfb..4c09eb2b 100644 --- a/.hypatia-baseline.json +++ b/.hypatia-baseline.json @@ -769,5 +769,230 @@ "file_pattern": "docs/proofs/spec-templates/**/*.adoc", "note": "FALSE POSITIVE CLASS (2026-08-24): these are cross-repository proof-spec templates. Their src/... examples describe the target repository named by each template, not directories asserted to exist inside standards. The Markdown-to-AsciiDoc migration made this established corpus newly visible to SD022.", "tracking_issue": "hyperpolymath/standards#399" + }, + { + "severity": "medium", + "rule_module": "structural_drift", + "type": "SD022", + "file": ".machine_readable/scorecards/hypatia-rules.scorecard.a2ml", + "note": "TEMPORARY REVIEW BASELINE (2026-08-29): classify whether the src/A2ML path is target-relative example content or stale repository-local documentation.", + "expires_at": "2026-11-29", + "tracking_issue": "hyperpolymath/standards#687" + }, + { + "severity": "medium", + "rule_module": "structural_drift", + "type": "SD022", + "file": ".machine_readable/template-capability-gates.toml", + "note": "TEMPORARY REVIEW BASELINE (2026-08-29): classify the template's target-relative src/interface path before changing either content or rule semantics.", + "expires_at": "2026-11-29", + "tracking_issue": "hyperpolymath/standards#687" + }, + { + "severity": "medium", + "rule_module": "structural_drift", + "type": "SD022", + "file": "TEMPLATE-APPLICABILITY-POLICY.adoc", + "note": "TEMPORARY REVIEW BASELINE (2026-08-29): classify the policy's target-relative src/interface example before correction or rule refinement.", + "expires_at": "2026-11-29", + "tracking_issue": "hyperpolymath/standards#687" + }, + { + "severity": "medium", + "rule_module": "structural_drift", + "type": "SD022", + "file": "docs/audits/dogfooding-matrix-2026-04-04.adoc", + "note": "TEMPORARY REVIEW BASELINE (2026-08-29): historical audit path may be intentionally historical or stale; review without rewriting audit evidence.", + "expires_at": "2026-11-29", + "tracking_issue": "hyperpolymath/standards#687" + }, + { + "severity": "medium", + "rule_module": "structural_drift", + "type": "SD022", + "file": "docs/proofs/REQUIREMENTS-MASTER.adoc", + "note": "TEMPORARY REVIEW BASELINE (2026-08-29): classify the src/vql and src/registry references as target-relative requirements or local drift.", + "expires_at": "2026-11-29", + "tracking_issue": "hyperpolymath/standards#687" + }, + { + "severity": "medium", + "rule_module": "structural_drift", + "type": "SD022", + "file": "groove-protocol/spec/GROOVE-RANKED-OWNERSHIP-CLEAVE-SPEC.adoc", + "note": "TEMPORARY REVIEW BASELINE (2026-08-29): the Groove spec references a consumer source layout; verify target context before altering the interface design.", + "expires_at": "2026-11-29", + "tracking_issue": "hyperpolymath/standards#687" + }, + { + "severity": "medium", + "rule_module": "structural_drift", + "type": "SD022", + "file": "rhodium-standard-repositories/docs/haskell-registry-design.adoc", + "note": "TEMPORARY REVIEW BASELINE (2026-08-29): classify the src/RSR reference against the documented target repository rather than assuming local drift.", + "expires_at": "2026-11-29", + "tracking_issue": "hyperpolymath/standards#687" + }, + { + "severity": "medium", + "rule_module": "structural_drift", + "type": "SD022", + "file": "rhodium-standard-repositories/satellites/palimpsest-license/PROJECT_MANAGEMENT/RECONCILIATION_AUDIT.adoc", + "note": "TEMPORARY REVIEW BASELINE (2026-08-29): preserve the satellite audit while classifying its src/widget-ts and src/scripts references.", + "expires_at": "2026-11-29", + "tracking_issue": "hyperpolymath/standards#687" + }, + { + "severity": "medium", + "rule_module": "structural_drift", + "type": "SD022", + "file": "rhodium-standard-repositories/spec/LANGUAGE-POLICY.adoc", + "note": "TEMPORARY REVIEW BASELINE (2026-08-29): classify the src/api policy example as target-relative content or stale local documentation.", + "expires_at": "2026-11-29", + "tracking_issue": "hyperpolymath/standards#687" + }, + { + "severity": "medium", + "rule_module": "implementation_inside_canon", + "type": "HYP-S009", + "file": "axel-protocol/CNAME", + "note": "TEMPORARY ARCHITECTURE BASELINE (2026-08-29): decide extraction or an explicit co-location carve-out for AXEL deployment metadata.", + "expires_at": "2026-11-29", + "tracking_issue": "hyperpolymath/standards#687" + }, + { + "severity": "medium", + "rule_module": "implementation_inside_canon", + "type": "HYP-S009", + "file": "axel-protocol/Containerfile", + "note": "TEMPORARY ARCHITECTURE BASELINE (2026-08-29): decide extraction or an explicit co-location carve-out for the AXEL implementation.", + "expires_at": "2026-11-29", + "tracking_issue": "hyperpolymath/standards#687" + }, + { + "severity": "medium", + "rule_module": "implementation_inside_canon", + "type": "HYP-S009", + "file": "axel-protocol/ads.txt", + "note": "TEMPORARY ARCHITECTURE BASELINE (2026-08-29): decide extraction or an explicit co-location carve-out for AXEL deployment metadata.", + "expires_at": "2026-11-29", + "tracking_issue": "hyperpolymath/standards#687" + }, + { + "severity": "medium", + "rule_module": "implementation_inside_canon", + "type": "HYP-S009", + "file": "axel-protocol/docs/CNAME", + "note": "TEMPORARY ARCHITECTURE BASELINE (2026-08-29): decide extraction or an explicit co-location carve-out for AXEL documentation deployment metadata.", + "expires_at": "2026-11-29", + "tracking_issue": "hyperpolymath/standards#687" + }, + { + "severity": "medium", + "rule_module": "implementation_inside_canon", + "type": "HYP-S009", + "file": "axel-protocol/docs/ads.txt", + "note": "TEMPORARY ARCHITECTURE BASELINE (2026-08-29): decide extraction or an explicit co-location carve-out for AXEL documentation deployment metadata.", + "expires_at": "2026-11-29", + "tracking_issue": "hyperpolymath/standards#687" + }, + { + "severity": "medium", + "rule_module": "implementation_inside_canon", + "type": "HYP-S009", + "file": "axel-protocol/public/CNAME", + "note": "TEMPORARY ARCHITECTURE BASELINE (2026-08-29): decide extraction or an explicit co-location carve-out for AXEL public deployment metadata.", + "expires_at": "2026-11-29", + "tracking_issue": "hyperpolymath/standards#687" + }, + { + "severity": "medium", + "rule_module": "implementation_inside_canon", + "type": "HYP-S009", + "file": "axel-protocol/public/ads.txt", + "note": "TEMPORARY ARCHITECTURE BASELINE (2026-08-29): decide extraction or an explicit co-location carve-out for AXEL public deployment metadata.", + "expires_at": "2026-11-29", + "tracking_issue": "hyperpolymath/standards#687" + }, + { + "severity": "medium", + "rule_module": "implementation_inside_canon", + "type": "HYP-S009", + "file": "k9-coordination-protocol/tools/k9-init/Cargo.toml", + "note": "TEMPORARY ARCHITECTURE BASELINE (2026-08-29): decide whether k9-init belongs beside the protocol or must be extracted.", + "expires_at": "2026-11-29", + "tracking_issue": "hyperpolymath/standards#687" + }, + { + "severity": "medium", + "rule_module": "implementation_inside_canon", + "type": "HYP-S009", + "file": "rhodium-standard-repositories/satellites/cccp/satellites/nextgen-languages/7-tentacles/package.json", + "note": "TEMPORARY ARCHITECTURE BASELINE (2026-08-29): classify nested satellite implementation ownership before extraction or carve-out.", + "expires_at": "2026-11-29", + "tracking_issue": "hyperpolymath/standards#687" + }, + { + "severity": "medium", + "rule_module": "implementation_inside_canon", + "type": "HYP-S009", + "file": "rhodium-standard-repositories/satellites/palimpsest-license/Containerfile", + "note": "TEMPORARY ARCHITECTURE BASELINE (2026-08-29): classify satellite implementation ownership before extraction or carve-out.", + "expires_at": "2026-11-29", + "tracking_issue": "hyperpolymath/standards#687" + }, + { + "severity": "medium", + "rule_module": "implementation_inside_canon", + "type": "HYP-S009", + "file": "rhodium-standard-repositories/satellites/rsr-certifier/Cargo.toml", + "note": "TEMPORARY ARCHITECTURE BASELINE (2026-08-29): review the RSR certifier workspace's placement under the canonical home.", + "expires_at": "2026-11-29", + "tracking_issue": "hyperpolymath/standards#687" + }, + { + "severity": "medium", + "rule_module": "implementation_inside_canon", + "type": "HYP-S009", + "file": "rhodium-standard-repositories/satellites/rsr-certifier/container/Containerfile", + "note": "TEMPORARY ARCHITECTURE BASELINE (2026-08-29): review the RSR certifier container's placement under the canonical home.", + "expires_at": "2026-11-29", + "tracking_issue": "hyperpolymath/standards#687" + }, + { + "severity": "medium", + "rule_module": "implementation_inside_canon", + "type": "HYP-S009", + "file": "rhodium-standard-repositories/satellites/rsr-certifier/engine/Cargo.toml", + "note": "TEMPORARY ARCHITECTURE BASELINE (2026-08-29): review the RSR certifier engine's placement under the canonical home.", + "expires_at": "2026-11-29", + "tracking_issue": "hyperpolymath/standards#687" + }, + { + "severity": "medium", + "rule_module": "implementation_inside_canon", + "type": "HYP-S009", + "file": "rhodium-standard-repositories/satellites/rsr-certifier/extensions/vscode/package.json", + "note": "TEMPORARY ARCHITECTURE BASELINE (2026-08-29): review the RSR certifier editor extension's placement under the canonical home.", + "expires_at": "2026-11-29", + "tracking_issue": "hyperpolymath/standards#687" + }, + { + "severity": "medium", + "rule_module": "implementation_inside_canon", + "type": "HYP-S009", + "file": "rhodium-standard-repositories/satellites/rsr-certifier/lsp/Cargo.toml", + "note": "TEMPORARY ARCHITECTURE BASELINE (2026-08-29): review the RSR certifier LSP's placement under the canonical home.", + "expires_at": "2026-11-29", + "tracking_issue": "hyperpolymath/standards#687" + }, + { + "severity": "medium", + "rule_module": "implementation_inside_canon", + "type": "HYP-S009", + "file": "rhodium-standard-repositories/satellites/state.scm/Containerfile", + "note": "TEMPORARY ARCHITECTURE BASELINE (2026-08-29): classify satellite implementation ownership before extraction or carve-out.", + "expires_at": "2026-11-29", + "tracking_issue": "hyperpolymath/standards#687" } ] diff --git a/.machine_readable/REGISTRY.a2ml b/.machine_readable/REGISTRY.a2ml index 2d7e2ce3..c1d8390d 100644 --- a/.machine_readable/REGISTRY.a2ml +++ b/.machine_readable/REGISTRY.a2ml @@ -153,7 +153,7 @@ name = "AXEL Protocol" stream = "protocol" home = "axel-protocol/" canonical_doc = "axel-protocol/README.adoc" -source_hash = "sha256:e5107e43de348a9aecb9991e33bf066fefcc2b4738925140ea91a2fde627c56d" +source_hash = "sha256:03ce83b73eb01290dda2cac2fd837e4cdc68412c842375e29331aa1e4be0a57b" route = "age-gating + explicit-content enforcement" [[spec]] @@ -162,7 +162,7 @@ name = "Overlay Protocol" stream = "protocol" home = "overlay-protocol/" canonical_doc = "overlay-protocol/.machine_readable/descriptiles/ECOSYSTEM.a2ml" -source_hash = "sha256:a4864b45d3cc12ca541b09ed7042285d6ff0d4231f89e3438fd1e0045e72becf" +source_hash = "sha256:7bde0638703825f37abc83ee98160e4cc2e72298b13ff50b8cf3632cb5444a15" route = "layered overlay composition spec" [[spec]] @@ -207,7 +207,7 @@ name = "RSR — Rhodium Standard Repositories" stream = "governance" home = "rhodium-standard-repositories/" canonical_doc = "rhodium-standard-repositories/README.adoc" -source_hash = "sha256:b4a862d4c8014e17813ba5b7fc286ef4147530c13665824188dddfdcae5c9bc6" +source_hash = "sha256:f9d32937c6d0541c82ada3d7c42cb7347fd4bbb3daa167b51b0cc420824918cc" route = "the repository-compliance standard every repo is graded against" [[spec]] diff --git a/.machine_readable/hypatia-baseline.schema.json b/.machine_readable/hypatia-baseline.schema.json index e27f6a2c..67d04625 100644 --- a/.machine_readable/hypatia-baseline.schema.json +++ b/.machine_readable/hypatia-baseline.schema.json @@ -30,7 +30,7 @@ "type": { "description": "Hypatia finding type within the rule module. Two shapes exist: lowercase snake_case (`banned_language_file`, `secret_detected`) and uppercase mnemonic codes emitted by structural_drift/git_state/code_scanning_alerts (`SD022`, `GS007`, `CSA001`). Entries must use the literal value the finding carries (standards#477).", "type": "string", - "pattern": "^([a-z][a-z0-9_]*|[A-Z]{2,3}[0-9]{3}|[A-Z][A-Za-z0-9]+)$" + "pattern": "^([a-z][a-z0-9_]*|[A-Z]{2,3}(-[A-Z])?[0-9]{3}|[A-Z][A-Za-z0-9]+)$" }, "file": { "description": "Repo-relative path to a single file the entry exempts. Mutually exclusive with `file_pattern`.", diff --git a/axel-protocol/src/Tea.affine b/axel-protocol/src/Tea.affine index c94f8fbd..26879d8b 100644 --- a/axel-protocol/src/Tea.affine +++ b/axel-protocol/src/Tea.affine @@ -24,18 +24,22 @@ module Html = { @val external document: 'a = "document" @send external getElementById: ('a, string) => Js.Nullable.t = "getElementById" - @set external setInnerHTML: (Dom.element, string) => unit = "innerHTML" + @send external replaceChildren: (Dom.element, node) => unit = "replaceChildren" fn noNode: node = Obj.magic("") - fn text = (str: string): node => Obj.magic(str) + fn text = (str: string): node => Obj.magic(%raw(`document.createTextNode(str)`)) - fn tag = (tagName: string, _attrs: array, children: array): node => { - // Use raw JavaScript to concatenate children - fn childrenHtml: string = %raw(` - children.map(c => c).join('') - `) - Obj.magic(`<${tagName}>${childrenHtml}`) + fn tag = (tagName: string, attrs: array, children: array): node => { + // Construct nodes directly. Text is never interpreted as markup. + Obj.magic(%raw(` + (() => { + const element = document.createElement(tagName); + attrs.forEach(attribute => attribute(element)); + children.forEach(child => element.appendChild(child)); + return element; + })() + `)) } fn div = (attrs, children) => tag("div", attrs, children) @@ -48,8 +52,10 @@ module Html = { fn button = (attrs, children) => tag("button", attrs, children) fn section = (attrs, children) => tag("section", attrs, children) - fn class' = (name: string): attribute => Obj.magic(`class="${name}"`) - fn id = (name: string): attribute => Obj.magic(`id="${name}"`) + fn class' = (name: string): attribute => + Obj.magic(%raw(`element => element.setAttribute("class", name)`)) + fn id = (name: string): attribute => + Obj.magic(%raw(`element => element.setAttribute("id", name)`)) fn onClick = (_handler: 'msg): attribute => Obj.magic("") } @@ -67,9 +73,8 @@ module App = { // Mount to DOM switch Html.document->Html.getElementById("tea-app")->Js.Nullable.toOption { - | Some(el) => el->Html.setInnerHTML(Obj.magic(html)) + | Some(el) => el->Html.replaceChildren(html) | None => Js.log("TEA mount point #tea-app not found") } } } - diff --git a/axel-protocol/src/Tea.res.js b/axel-protocol/src/Tea.res.js index d0988833..482b4ea6 100644 --- a/axel-protocol/src/Tea.res.js +++ b/axel-protocol/src/Tea.res.js @@ -15,12 +15,14 @@ let Sub = { }; function text(str) { - return str; + return document.createTextNode(str); } -function tag(tagName, _attrs, children) { - let childrenHtml = (children.map(c => c).join('')); - return `<` + tagName + `>` + childrenHtml + ``; +function tag(tagName, attrs, children) { + let element = document.createElement(tagName); + attrs.forEach(attribute => attribute(element)); + children.forEach(child => element.appendChild(child)); + return element; } function div(attrs, children) { @@ -60,11 +62,11 @@ function section(attrs, children) { } function class$p(name) { - return `class="` + name + `"`; + return element => element.setAttribute("class", name); } function id(name) { - return `id="` + name + `"`; + return element => element.setAttribute("id", name); } function onClick(_handler) { @@ -96,7 +98,7 @@ function standardProgram(program) { if (el == null) { console.log("TEA mount point #tea-app not found"); } else { - el.innerHTML = html; + el.replaceChildren(html); } } diff --git a/overlay-protocol/ffi/zig/src/main.zig b/overlay-protocol/ffi/zig/src/main.zig index f1b4c9d6..cb121c07 100644 --- a/overlay-protocol/ffi/zig/src/main.zig +++ b/overlay-protocol/ffi/zig/src/main.zig @@ -91,12 +91,15 @@ threadlocal var last_error_buf: [1024]u8 = undefined; threadlocal var last_error_len: usize = 0; fn setError(msg: []const u8) void { - const len = @min(msg.len, last_error_buf.len); + // Reserve one byte for the sentinel required by the exported C pointer. + const len = @min(msg.len, last_error_buf.len - 1); @memcpy(last_error_buf[0..len], msg[0..len]); + last_error_buf[len] = 0; last_error_len = len; } fn clearError() void { + last_error_buf[0] = 0; last_error_len = 0; } @@ -415,11 +418,8 @@ pub export fn overlay_peer_types_distinct(a: PeerType, b: PeerType) u32 { /// Get the last error message. Returns null if no error. pub export fn overlay_last_error() ?[*:0]const u8 { if (last_error_len == 0) return null; - // Null-terminate for C compatibility - if (last_error_len < last_error_buf.len) { - last_error_buf[last_error_len] = 0; - } - return @ptrCast(&last_error_buf); + // setError owns and preserves the in-bounds sentinel invariant. + return last_error_buf[0..last_error_len :0].ptr; } // ============================================================================ @@ -548,6 +548,27 @@ test "peer types distinct" { try std.testing.expectEqual(@as(u32, 0), overlay_peer_types_distinct(.o_extension, .o_extension)); } +test "last error reserves and writes an in-bounds C sentinel" { + const oversized = [_]u8{'x'} ** (last_error_buf.len + 32); + setError(&oversized); + + try std.testing.expectEqual(last_error_buf.len - 1, last_error_len); + try std.testing.expectEqual(@as(u8, 0), last_error_buf[last_error_len]); + + const err = overlay_last_error() orelse return error.ExpectedLastError; + const message = std.mem.span(err); + try std.testing.expectEqual(last_error_buf.len - 1, message.len); + try std.testing.expect(std.mem.allEqual(u8, message, 'x')); +} + +test "clearing the last error clears its sentinel and exported value" { + setError("failure"); + clearError(); + + try std.testing.expectEqual(@as(u8, 0), last_error_buf[0]); + try std.testing.expectEqual(@as(?[*:0]const u8, null), overlay_last_error()); +} + test "version" { const ver = std.mem.span(overlay_version()); try std.testing.expectEqualStrings(VERSION, ver); diff --git a/rhodium-standard-repositories/satellites/rsr-certifier/Cargo.lock b/rhodium-standard-repositories/satellites/rsr-certifier/Cargo.lock index 7689322b..7042c9c7 100644 --- a/rhodium-standard-repositories/satellites/rsr-certifier/Cargo.lock +++ b/rhodium-standard-repositories/satellites/rsr-certifier/Cargo.lock @@ -62,7 +62,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -73,7 +73,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -466,7 +466,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -960,9 +960,9 @@ dependencies = [ [[package]] name = "gix-packetline" -version = "0.21.3" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "362246df440ee691699f0664cbf7006a6ece477db6734222be95e4198e5656e6" +checksum = "b217dd0ee0c4021ecf169a4a519b1b4f80d15e3f3765f3dc466223dc0ac891d7" dependencies = [ "bstr", "faster-hex", @@ -1119,9 +1119,9 @@ dependencies = [ [[package]] name = "gix-trace" -version = "0.1.19" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f23569e55f2ffaf958617353b9734a7d52a7c19c439eeaa5e3efc217fd2270e" +checksum = "be3eb81d9dc914335923e50d52829c551feefd6a72d176c4130c546b67a60814" [[package]] name = "gix-transport" @@ -1195,9 +1195,9 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "h2" -version = "0.4.13" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "a9f37a958b41b3b19ee2707c06439c0e9e547e847223eb791ecb0cb821c65e27" dependencies = [ "atomic-waker", "bytes", @@ -1576,7 +1576,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1777,7 +1777,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -2193,7 +2193,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2411,7 +2411,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -2473,7 +2473,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3051,7 +3051,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] diff --git a/scripts/apply-baseline.sh b/scripts/apply-baseline.sh index b1308ceb..0916c7bf 100755 --- a/scripts/apply-baseline.sh +++ b/scripts/apply-baseline.sh @@ -95,7 +95,7 @@ SCHEMA_ERRORS="$(jq -r ' else empty end), # `type` accepts three shapes, and the third is not cosmetic: # snake_case — the Hypatia rule modules (unsafe_block, ...) - # SD007-style codes — structural_drift + # SD007/HYP-S009 codes — structural_drift and canonical-home rules # CamelCase — SCORECARD PROBE NAMES (DependencyPinning, # BranchProtection, ...). Hypatia emits these # verbatim from Scorecard, and without this @@ -106,7 +106,7 @@ SCHEMA_ERRORS="$(jq -r ' # than "this finding is unrepresentable". # Found 2026-08-06 in metadatastician/stapeln. (if ($e.type|type) == "string" - and (($e.type|test("^([a-z][a-z0-9_]*|[A-Z]{2,3}[0-9]{3}|[A-Z][A-Za-z0-9]+)$"))|not) + and (($e.type|test("^([a-z][a-z0-9_]*|[A-Z]{2,3}(-[A-Z])?[0-9]{3}|[A-Z][A-Za-z0-9]+)$"))|not) then "entry[\($i)]: type fails pattern: \($e.type)" else empty end), (if ($e|has("file")) and ((($e.file|type) != "string") or ($e.file == "")) then "entry[\($i)]: file must be a non-empty string" else empty end), diff --git a/scripts/check-actions-policy.sh b/scripts/check-actions-policy.sh index a03a4946..65a8d117 100755 --- a/scripts/check-actions-policy.sh +++ b/scripts/check-actions-policy.sh @@ -3,7 +3,8 @@ # Check the LIVE GitHub Actions policy for a repository (standards#486). # # Usage: check-actions-policy.sh [allowed-actions.json] -# Exit: 0 compliant | 1 policy violation/API failure | 2 setup error +# Exit: 0 compliant | 1 policy violation | 2 local setup error | +# 3 live policy unavailable (authentication/API failure) set -euo pipefail REPOSITORY="${1:?usage: check-actions-policy.sh [allowed-actions.json]}" @@ -16,7 +17,7 @@ command -v "$GH_BIN" >/dev/null 2>&1 || { echo "ERROR: GitHub CLI not found: $GH permissions="$($GH_BIN api "repos/$REPOSITORY/actions/permissions")" || { echo "ERROR: could not read live Actions permissions for $REPOSITORY" >&2 - exit 1 + exit 3 } enabled="$(jq -r '.enabled // false' <<<"$permissions")" @@ -33,7 +34,7 @@ case "$allowed" in selected) selected="$($GH_BIN api "repos/$REPOSITORY/actions/permissions/selected-actions")" || { echo "ERROR: could not read selected Actions policy for $REPOSITORY" >&2 - exit 1 + exit 3 } count="$(jq -r '(.patterns_allowed // []) | length' <<<"$selected")" [ "$count" -gt 0 ] || { diff --git a/scripts/check-workflow-staleness.sh b/scripts/check-workflow-staleness.sh index 51faa3ea..e46ebbb1 100755 --- a/scripts/check-workflow-staleness.sh +++ b/scripts/check-workflow-staleness.sh @@ -41,7 +41,8 @@ set -eo pipefail # calendar. The pre-cache-fix Hypatia scanner (#441) is the first entry. # * FORGED — the pin is not a commit of this repository reachable from the # default branch, confirmed server-side (supply-chain integrity). -# * the two structural rules (retired scorecard-enforcer; Scorecard SARIF). +# * the two structural rules (retired scorecard-enforcer; direct consumer +# Scorecard SARIF publication outside the canonical reusable). # Age outside the window is reported as a ::notice for the propagation path to # act on. # @@ -454,9 +455,13 @@ fi for wf in "$REPO_ROOT"/.github/workflows/*.yml "$REPO_ROOT"/.github/workflows/*.yaml; do [ -f "$wf" ] || continue - # Rule: no_scorecard_sarif_code_scanning (structural — independent of pins) - if grep -q "ossf/scorecard-action@" "$wf" && grep -q "github/codeql-action/upload-sarif@" "$wf"; then - echo "::error file=$wf::OSSF Scorecard must not upload SARIF to GitHub Code Scanning unless it runs for every PR head commit." + # Rule: no direct consumer-owned Scorecard SARIF publisher. The canonical + # reusable in standards owns publication so alert delivery and policy can be + # repaired once rather than drifting across the estate. + if [ "$IS_STANDARDS" = "false" ] && \ + grep -q "ossf/scorecard-action@" "$wf" && \ + grep -q "github/codeql-action/upload-sarif@" "$wf"; then + echo "::error file=$wf::Direct Scorecard SARIF publication is retired. Call standards/scorecard-reusable.yml so publication policy remains centrally controlled." FAILED=1 fi @@ -473,7 +478,7 @@ for wf in "$REPO_ROOT"/.github/workflows/*.yml "$REPO_ROOT"/.github/workflows/*. done if [ $FAILED -ne 0 ]; then - echo "::error::Staleness gate failed. Each error above names a specific defect: a pin predating a known false-green fix (refresh it — waiting will not help), a pin that is not a published standards commit, a retired scorecard-enforcer.yml, or Scorecard uploading SARIF to Code Scanning. Pins that are merely old are reported as notices and do not fail." + echo "::error::Staleness gate failed. Each error above names a specific defect: a pin predating a known false-green fix (refresh it — waiting will not help), a pin that is not a published standards commit, a retired scorecard-enforcer.yml, or a consumer-owned direct Scorecard SARIF publisher. Pins that are merely old are reported as notices and do not fail." exit 1 fi diff --git a/scripts/tests/actions-policy-486-test.sh b/scripts/tests/actions-policy-486-test.sh index c4bb25ce..addbfedd 100755 --- a/scripts/tests/actions-policy-486-test.sh +++ b/scripts/tests/actions-policy-486-test.sh @@ -36,6 +36,7 @@ if [[ "$args" == *"/selected-actions "* ]]; then fi case "$scenario" in + api-unavailable) exit 4 ;; sha-off|setter-reset) printf '%s\n' '{"enabled":true,"allowed_actions":"all","sha_pinning_required":false}' ;; selected-empty|selected-missing|selected-ok|setter-selected) printf '%s\n' '{"enabled":true,"allowed_actions":"selected","sha_pinning_required":true}' ;; @@ -61,6 +62,7 @@ expect() { } expect "all + SHA pinning passes" 0 all-ok "$CHECK" owner/repo "$CANON" +expect "API/authentication failure is unavailable, not a policy verdict" 3 api-unavailable "$CHECK" owner/repo "$CANON" expect "SHA pinning off blocks" 1 sha-off "$CHECK" owner/repo "$CANON" expect "disabled Actions blocks" 1 disabled "$CHECK" owner/repo "$CANON" expect "empty selected allowlist blocks" 1 selected-empty "$CHECK" owner/repo "$CANON" diff --git a/scripts/tests/apply-baseline-test.sh b/scripts/tests/apply-baseline-test.sh index 29f30e75..3bf2f87f 100755 --- a/scripts/tests/apply-baseline-test.sh +++ b/scripts/tests/apply-baseline-test.sh @@ -84,6 +84,28 @@ echo '[]' > "$WORK/empty.json" assert_status "empty baseline keeps finding" \ "$WORK/findings2.json" "$WORK/empty.json" "0,1" +# === Case 6: emitted hyphenated Hypatia rule IDs are representable === +cat > "$WORK/findings6.json" <<'EOF' +[{"severity":"medium","rule_module":"implementation_inside_canon","type":"HYP-S009","file":"spec/Cargo.toml"}] +EOF +cat > "$WORK/baseline6.json" <<'EOF' +[{"severity":"medium","rule_module":"implementation_inside_canon","type":"HYP-S009","file":"spec/Cargo.toml"}] +EOF +assert_status "hyphenated HYP-S009 rule type is valid and matches" \ + "$WORK/findings6.json" "$WORK/baseline6.json" "1,0" + +# A malformed doubled separator must remain invalid. +cat > "$WORK/baseline6-invalid.json" <<'EOF' +[{"severity":"medium","rule_module":"implementation_inside_canon","type":"HYP--S009","file":"spec/Cargo.toml"}] +EOF +if "$APPLY" "$WORK/findings6.json" "$WORK/baseline6-invalid.json" advisory >/dev/null 2>&1; then + echo "FAIL: malformed HYP--S009 rule type was accepted" + fail=$((fail + 1)) +else + echo "PASS: malformed HYP--S009 rule type is rejected" + pass=$((pass + 1)) +fi + echo echo "Total: $pass passed, $fail failed" [ "$fail" -eq 0 ] diff --git a/scripts/tests/check-workflow-staleness-test.sh b/scripts/tests/check-workflow-staleness-test.sh index 6c211fa5..46c75d14 100755 --- a/scripts/tests/check-workflow-staleness-test.sh +++ b/scripts/tests/check-workflow-staleness-test.sh @@ -62,8 +62,8 @@ run_case() { local desc="$1" expected="$2" repo="$3"; shift 3 TOTAL=$((TOTAL + 1)) set +e - env "$@" \ - GITHUB_REPOSITORY="hyperpolymath/test-repo" \ + env GITHUB_REPOSITORY="hyperpolymath/test-repo" \ + "$@" \ STALENESS_STANDARDS_DIR="$FIX" \ bash "$CHECK_SCRIPT" "$repo" >/dev/null 2>&1 local rc=$? @@ -85,8 +85,8 @@ run_case_out() { TOTAL=$((TOTAL + 1)) local out rc set +e - out=$(env "$@" \ - GITHUB_REPOSITORY="hyperpolymath/test-repo" \ + out=$(env GITHUB_REPOSITORY="hyperpolymath/test-repo" \ + "$@" \ STALENESS_STANDARDS_DIR="$FIX" \ bash "$CHECK_SCRIPT" "$repo" 2>&1) rc=$? @@ -183,7 +183,7 @@ R="$TEST_DIR/enforcer"; mk_repo "$R" touch "$R/.github/workflows/scorecard-enforcer.yml" run_case "retired scorecard-enforcer.yml fails" 1 "$R" -# ── 9. Direct Scorecard SARIF upload -> fail ──────────────────────────────── +# ── 9. Consumer-owned direct Scorecard SARIF upload -> fail ──────────────── R="$TEST_DIR/sarif"; mk_repo "$R" cat > "$R/.github/workflows/scorecard.yml" <<'EOF' name: Scorecard @@ -194,7 +194,12 @@ jobs: - uses: ossf/scorecard-action@abc - uses: github/codeql-action/upload-sarif@xyz EOF -run_case "direct Scorecard SARIF upload fails" 1 "$R" +run_case "consumer direct Scorecard SARIF upload fails" 1 "$R" + +# The standards repository contains the canonical reusable that consumers call. +# Its uploader is the controlled source, not a forbidden ad hoc consumer copy. +run_case "canonical standards Scorecard SARIF uploader passes" 0 "$R" \ + GITHUB_REPOSITORY=hyperpolymath/standards # ── 10. No workflows dir -> pass ──────────────────────────────────────────── R="$TEST_DIR/empty"; mkdir -p "$R"