diff --git a/.github/workflows/hypatia-scan-reusable.yml b/.github/workflows/hypatia-scan-reusable.yml index 95d8c5e..d89e0d5 100644 --- a/.github/workflows/hypatia-scan-reusable.yml +++ b/.github/workflows/hypatia-scan-reusable.yml @@ -2,7 +2,7 @@ # SPDX-License-Identifier: MPL-2.0 # This workflow is managed by gh actions-lock. # -# Fork-local copy of hyperpolymath/standards/.github/workflows/ +# Fork-local copy of metadatastician/standards/.github/workflows/ # hypatia-scan-reusable.yml at 01c9b0c9f2646ebea0b30a9588462642ac94aee9. # Keep that source pin when updating this file and reapply the three fork deltas: # 1. Do not upload SARIF for pull_request events. This lane is advisory on @@ -11,7 +11,7 @@ # Raw findings and the verdict grid still cover the PR, while push and # manual runs continue to reconcile code scanning on the default branch. # 2. Pin the standards checkout explicitly. job.workflow_sha now names a -# MetaManifold commit, not a commit in hyperpolymath/standards. +# MetaManifold commit, not a commit in metadatastician/standards. # 3. Filter only upstream-inherited TypeScript paths from the effective # findings, validate the raw/effective JSON with pinned ijson-jcs, and keep # raw scanner outputs plus fork-only paths/rules. @@ -619,7 +619,7 @@ jobs: continue-on-error: true uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - repository: hyperpolymath/standards + repository: metadatastician/standards persist-credentials: false # Includes the filter's completion output and literal-glob matching. # Keep the helper immutable and compatible with the strict gate. @@ -703,7 +703,7 @@ jobs: continue-on-error: true uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - repository: hyperpolymath/standards + repository: metadatastician/standards # This fork-local reusable's job.workflow_sha belongs to # MetaManifold, not standards, so the source commit must be literal. ref: 01c9b0c9f2646ebea0b30a9588462642ac94aee9 @@ -743,7 +743,7 @@ jobs: # No renderer is missing evidence, rendered as such, never a pass. jq -n '{schemaVersion: 1, label: "Hypatia grid", message: "indeterminate", color: "orange"}' > verdict-grid/badge.json printf '%s\n%s\n' '' \ - '๐ŸŸ  indeterminate ยท the verdict grid renderer could not be checked out from hyperpolymath/standards; see the job log.' \ + '๐ŸŸ  indeterminate ยท the verdict grid renderer could not be checked out from metadatastician/standards; see the job log.' \ > verdict-grid/report.md cat verdict-grid/report.md >> "$GITHUB_STEP_SUMMARY" echo "state=orange" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index b307aa3..e874741 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -1,7 +1,7 @@ # SPDX-License-Identifier: MPL-2.0 # SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) # -# Fork governance lane. DEED and K9 conformance come from hyperpolymath/standards; the +# Fork governance lane. DEED and K9 conformance come from metadatastician/standards; the # Hypatia verdict grid uses a fork-local copy of the standards reusable so its # advisory PR scan cannot become a blocking code-scanning-results check. Both # sources are pinned to commits. The grid covers Hypatia, panic-attack, pons @@ -52,7 +52,7 @@ jobs: deed: name: DEED syntax - uses: hyperpolymath/standards/.github/workflows/deed-conformance.yml@75bd5994470661a099df562d6d5ad2fe744dbd66 + uses: metadatastician/standards/.github/workflows/deed-conformance.yml@75bd5994470661a099df562d6d5ad2fe744dbd66 permissions: contents: read with: diff --git a/.github/workflows/k9-conformance-reusable.yml b/.github/workflows/k9-conformance-reusable.yml index 12e3806..b58e6a8 100644 --- a/.github/workflows/k9-conformance-reusable.yml +++ b/.github/workflows/k9-conformance-reusable.yml @@ -42,7 +42,7 @@ name: k9-conformance # Caller: # jobs: # k9: -# uses: hyperpolymath/standards/.github/workflows/k9-conformance.yml@ +# uses: metadatastician/standards/.github/workflows/k9-conformance.yml@ # permissions: # contents: read # pull-requests: read @@ -89,7 +89,7 @@ jobs: # In this repo the PR's own validator is in the first checkout; a caller # gets validator, contract and renderer from the canon checkout at # job.workflow_sha. - CANON: ${{ github.repository == 'hyperpolymath/standards' && '.' || '.k9-canon' }} + CANON: ${{ github.repository == 'metadatastician/standards' && '.' || '.k9-canon' }} NICKEL_VERSION: '1.18.0' NICKEL_SHA256: '9cba4dd65ae9915ec61f73033aafcff307a377665a83fd8f530df086763318cb' IJSON_JCS_REV: '0fdeea716fd558e9807362acf6f8202d56d8d88f' @@ -104,10 +104,10 @@ jobs: persist-credentials: false - name: checkout the K9 validator, contract and renderer at this workflow's commit - if: github.repository != 'hyperpolymath/standards' + if: github.repository != 'metadatastician/standards' uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - repository: hyperpolymath/standards + repository: metadatastician/standards ref: c6d78606f3928c22c3b0bdc6f5f5b47b96e05a5d path: .k9-canon persist-credentials: false @@ -121,7 +121,7 @@ jobs: WORKFLOW_SHA: c6d78606f3928c22c3b0bdc6f5f5b47b96e05a5d HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} run: | - if [ "$GITHUB_REPOSITORY" = "hyperpolymath/standards" ]; then + if [ "$GITHUB_REPOSITORY" = "metadatastician/standards" ]; then echo "CANON_REF=$HEAD_SHA" >> "$GITHUB_ENV" else echo "CANON_REF=$WORKFLOW_SHA" >> "$GITHUB_ENV" @@ -154,7 +154,7 @@ jobs: || echo "::warning::ijson-jcs did not install; report.json falls back to jq -S -c" - name: Renderer regression tests - if: github.repository == 'hyperpolymath/standards' + if: github.repository == 'metadatastician/standards' run: bash scripts/tests/k9-grid-test.sh - name: Validator self-test diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index c8bd4a4..4200498 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -20,7 +20,7 @@ jobs: scorecard: # The default-branch SARIF upload needs security-events: write; never # expose that grant or the publication OIDC token to PR code. - uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@c6d78606f3928c22c3b0bdc6f5f5b47b96e05a5d + uses: metadatastician/standards/.github/workflows/scorecard-reusable.yml@c6d78606f3928c22c3b0bdc6f5f5b47b96e05a5d permissions: actions: read contents: read diff --git a/.github/workflows/secret-scanner-reusable.yml b/.github/workflows/secret-scanner-reusable.yml index e946286..14984b0 100644 --- a/.github/workflows/secret-scanner-reusable.yml +++ b/.github/workflows/secret-scanner-reusable.yml @@ -97,7 +97,7 @@ # scan: # permissions: # contents: read -# uses: hyperpolymath/standards/.github/workflows/secret-scanner-reusable.yml@ +# uses: metadatastician/standards/.github/workflows/secret-scanner-reusable.yml@ # # Deliberately NO `secrets:` line โ€” the callee declares none and # # needs none (see the Design note above). @@ -214,7 +214,7 @@ jobs: if: hashFiles('.gitleaks.toml') == '' uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - repository: hyperpolymath/standards + repository: metadatastician/standards ref: c6d78606f3928c22c3b0bdc6f5f5b47b96e05a5d path: .standards-gitleaks sparse-checkout: | @@ -291,7 +291,7 @@ jobs: if: steps.estate_baseline.outputs.referenced == 'true' uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - repository: hyperpolymath/standards + repository: metadatastician/standards ref: c6d78606f3928c22c3b0bdc6f5f5b47b96e05a5d path: .estate-baseline-checkout sparse-checkout: config/gitleaks diff --git a/docs/maintenance/repository-hygiene.md b/docs/maintenance/repository-hygiene.md index e63ef22..bfae4b3 100644 --- a/docs/maintenance/repository-hygiene.md +++ b/docs/maintenance/repository-hygiene.md @@ -217,6 +217,78 @@ acts on them; the first one was reached, wrongly, during the 2026-10-25 pass. A new directory under `docs/` needs a whitelist line or it will not be committed; `!docs/maintenance/` was added for this document. +## External pins can move under you + +**Incident, 2026-10-25.** `hyperpolymath/standards` โ€” the source of this +repository's DEED, K9, Hypatia, Scorecard and secret-scanner reusables โ€” was +transferred to `metadatastician/standards` at 10:13 UTC. From the next run +onwards, every workflow that called one of its reusable workflows **failed at +plan time with zero jobs**, and GitHub displayed the run under the workflow's +file path instead of its `name:`, because the workflow never loaded far enough +to have one: + +| Time | Workflow | Result | +| --- | --- | --- | +| 09:26 | `Governance (fork)` (`hypatia-scan.yml`) | success | +| 09:26 | `OpenSSF Scorecard` (`scorecard.yml`) | success | +| 10:13 | โ€” standards repository transferred โ€” | | +| 10:52 | `hypatia-scan.yml` on a pull request | failure, 0 jobs, 0 s | +| 11:27 | `hypatia-scan.yml`, `scorecard.yml` on the push | failure, 0 jobs, 0 s | +| 11:27 | `Secret Scanner` (local reusable only) | success | + +The distinction is the whole lesson: **`actions/checkout` with +`repository: hyperpolymath/standards` kept working**, because git and the REST +API follow a transfer's redirect, while a `uses: //.github/ +workflows/@` reference **does not**. So the scanners that *check the +repository out* were fine and the ones that *call a reusable workflow* were +dark. Both read as "governance is green" in a summary that does not look at +`conclusion` per workflow. + +How to detect it in one command, before trusting a red or a green: + +``` +gh api repos// --jq .full_name # a different name means transferred +``` + +### The actual cause is an Actions policy, not just the rename + +Repointing the references is **not sufficient**, and the run says so once you can +read the annotation. A startup-failure run has no jobs and no logs, but its +workflow page carries one error: + +> The reusable workflow `metadatastician/standards/.github/workflows/deed-conformance.yml@75bd5994โ€ฆ` +> is not allowed in `hyperpolymath/MetaManifold-WebUI` because all reusable +> workflows must be from a repository owned by `hyperpolymath` or match the +> pattern: `arena-ai-coding-agent[bot]`. + +The repository's Actions policy permits reusable workflows only from +repositories owned by `hyperpolymath` โ€” so `hyperpolymath/standards` was allowed +and `metadatastician/standards` is not, whatever name the workflow file uses. +The `uses:` references therefore cannot be repaired by editing this repository +alone. Three ways out, in increasing order of effort: + +1. **Permit the new owner.** Repository โ†’ Settings โ†’ Actions โ†’ General โ†’ + *Access* for reusable workflows, and allow `metadatastician` (or "any + repository"). This is the smallest change and keeps the pins intact. +2. **Move `standards` back** under `hyperpolymath`. Then the existing workflow + files work unchanged, and no pull request is needed. +3. **Run DEED conformance from a checkout** instead of a remote `uses:`, the way + the K9, Hypatia and secret-scanner lanes already do. Correct, but it puts a + copy of someone else's workflow steps in this repository, and that copy then + drifts. + +What was done: every reference was repointed at `metadatastician/standards` +with the **same pinned SHAs** (verified to resolve in the new location, with +matching commit subjects), `scripts/check-governance-contract.sh` now fails if +any workflow names the pre-transfer owner or if the Scorecard reusable stops +being the pinned file, and the mutation test carries a case for each. The pins +themselves did not move, so no new code is trusted; only the name used to reach +the same commits. + +What a future reader should not do: "fix" a failed governance run by dropping +the `uses:` and inlining a scanner, or by pointing the pin at `@main`. Both turn +a loud failure into a quiet one. + ## Decisions for the owner These are not cleanup, they are choices with trade-offs; the reasoning is diff --git a/scripts/check-governance-contract.sh b/scripts/check-governance-contract.sh index 93609ee..507b5ed 100755 --- a/scripts/check-governance-contract.sh +++ b/scripts/check-governance-contract.sh @@ -118,8 +118,13 @@ reusable="$(cat "$REUSABLE")" contains "$caller" 'uses: ./.github/workflows/hypatia-scan-reusable.yml' \ 'the governance lane calls the fork-local reusable' -not_contains "$caller" 'uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@' \ +# The standards repository is metadatastician/standards; it was +# hyperpolymath/standards until the transfer on 2026-10-25. Both names are +# checked, because a bypass through either one skips the fork guard. +not_contains "$caller" 'uses: metadatastician/standards/.github/workflows/hypatia-scan-reusable.yml@' \ 'the governance lane cannot bypass the fork guard by calling standards directly' +not_contains "$caller" 'uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@' \ + 'the governance lane cannot bypass the fork guard through the pre-transfer name' contains "$caller" 'needs: [workflow-contract]' \ 'this contract runs before the Hypatia scan' if grep -Eq '^ pull_request:$' <<<"$caller"; then @@ -261,7 +266,7 @@ not_contains "$reusable" 'steps.scan.outputs.critical > 0' \ 'the raw critical count cannot drive an exception-inconsistent advisory' if renderer="$(step_block "$REUSABLE" 'Check out the verdict grid renderer at this workflow'"'"'s commit')"; then - contains "$renderer" 'repository: hyperpolymath/standards' \ + contains "$renderer" 'repository: metadatastician/standards' \ 'the renderer comes from the standards repository' contains "$renderer" "ref: $STANDARDS_SHA" \ 'the standards renderer uses the immutable source pin' @@ -269,6 +274,13 @@ else fail 'the standards renderer checkout exists' fi +stale_owner="$(grep -rn 'hyperpolymath/standards' .github/workflows/ 2>/dev/null || true)" +if [ -z "$stale_owner" ]; then + pass 'no workflow still names the pre-transfer standards owner' +else + fail "workflows still reference hyperpolymath/standards, which no longer resolves for a reusable uses: - $stale_owner" +fi + upload_count="$(grep -c 'uses: github/codeql-action/upload-sarif@' "$REUSABLE" || true)" if [ "$upload_count" -eq 1 ]; then pass 'there is exactly one SARIF upload site to guard' @@ -313,7 +325,7 @@ not_contains "$secret_workflow" 'secrets:' 'the secret scanner receives no calle not_contains "$secret_workflow" 'continue-on-error:' 'secret scanner failures cannot be advisory' not_contains "$secret_workflow" 'pull_request_target:' 'secret scanning does not use a privileged PR trigger' scorecard_workflow="$(cat .github/workflows/scorecard.yml)" -contains "$scorecard_workflow" 'uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@c6d78606f3928c22c3b0bdc6f5f5b47b96e05a5d' \ +contains "$scorecard_workflow" 'uses: metadatastician/standards/.github/workflows/scorecard-reusable.yml@c6d78606f3928c22c3b0bdc6f5f5b47b96e05a5d' \ 'Scorecard uses the audited immutable reusable' if grep -Eq '^ pull_request(_target)?:' <<<"$scorecard_workflow"; then fail 'Scorecard publication must not run against PR code' diff --git a/scripts/tests/test-governance-contract.sh b/scripts/tests/test-governance-contract.sh index 4d86936..8945b29 100644 --- a/scripts/tests/test-governance-contract.sh +++ b/scripts/tests/test-governance-contract.sh @@ -14,7 +14,7 @@ reset_fixture() { } reset_fixture bash "$fixture/scripts/check-governance-contract.sh" > "$fixture/log" 2>&1 -for mutation in allowlist-addition baseline-zero-gate baseline-operator-gate baseline-grouping-gate sarif upstream-exception effective-grid jcs-manifest jcs-canonicalizer raw-ijson k9-pin k9-require k9-indeterminate k9-verdict scanner-advisory scanner-secrets scanner-trigger scorecard-trigger; do +for mutation in allowlist-addition baseline-zero-gate baseline-operator-gate baseline-grouping-gate sarif upstream-exception effective-grid jcs-manifest jcs-canonicalizer raw-ijson k9-pin k9-require k9-indeterminate k9-verdict scanner-advisory scanner-secrets scanner-trigger scorecard-trigger stale-standards-owner scorecard-owner; do reset_fixture case "$mutation" in allowlist-addition) echo 'frontend/src/fork-only.ts' >> "$fixture/config/hypatia/upstream-typescript-allowlist.txt" ;; @@ -35,6 +35,11 @@ for mutation in allowlist-addition baseline-zero-gate baseline-operator-gate bas scanner-secrets) echo ' secrets: inherit' >> "$fixture/.github/workflows/secret-scanner.yml" ;; scanner-trigger) sed -i 's/^ pull_request:/ pull_request_target:/' "$fixture/.github/workflows/secret-scanner.yml" ;; scorecard-trigger) sed -i '/^on:/a\ pull_request:' "$fixture/.github/workflows/scorecard.yml" ;; + # The standards repository was transferred on 2026-10-25 and a reusable + # `uses:` no longer follows the old owner, so either of these mutations + # leaves a workflow that fails at plan time with no jobs at all. + stale-standards-owner) sed -i 's@metadatastician/standards@hyperpolymath/standards@g' "$fixture/.github/workflows/scorecard.yml" ;; + scorecard-owner) sed -i 's@metadatastician/standards/.github/workflows/scorecard-reusable.yml@metadatastician/standards/.github/workflows/other.yml@' "$fixture/.github/workflows/scorecard.yml" ;; *) echo "ERROR: unknown mutation $mutation" >&2; exit 1 ;; esac if bash "$fixture/scripts/check-governance-contract.sh" > "$fixture/log" 2>&1; then