Repository navigation
fix(governance): make the DEED and Scorecard lanes fork-local (restores both) #293
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| # SPDX-License-Identifier: MPL-2.0 | ||
| # SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk> | ||
| # | ||
| # Fork-local copy of metadatastician/standards/.github/workflows/ | ||
| # deed-conformance.yml at 75bd5994470661a099df562d6d5ad2fe744dbd66. | ||
| # | ||
| # Why a copy. The repository's Actions policy allows a reusable workflow only | ||
| # from a repository owned by `hyperpolymath`; a call to another owner fails at | ||
| # plan time with zero jobs, so the lane is silently absent from every check | ||
| # summary. `standards` was transferred to `metadatastician` on 2026-10-25 and the | ||
| # call stopped working. Deleting the lane would be worse than copying it: DEED | ||
| # conformance is one row of the verdict grid and one of the three independent | ||
| # governance lanes. | ||
| # | ||
| # This is the CALLER path of the upstream workflow, which is the only path this | ||
| # repository ever ran: check out the caller, check out the linter from standards | ||
| # at the pinned commit, run it over every committed *.deed file. The upstream | ||
| # file's self-test, fixture-corpus and uuid-v8 steps are gated on | ||
| # `github.repository == 'hyperpolymath/standards'` and were never executed here, | ||
| # so they are not copied. | ||
| # | ||
| # The pins are the point: the linter still comes from standards at the same | ||
| # commit, verified by `scripts/check-governance-contract.sh`, which fails if this | ||
| # header's source commit changes or if a caller reaches for the remote reusable | ||
| # again. | ||
| name: DEED conformance (fork-local) | ||
|
|
||
| on: | ||
| workflow_call: | ||
| inputs: | ||
| require-files: | ||
| description: "Fail when the caller has no committed .deed file (a zero-file run is otherwise a pass)." | ||
| type: boolean | ||
| required: false | ||
| default: false | ||
|
|
||
| permissions: {} | ||
|
|
||
| jobs: | ||
| lint: | ||
| name: DEED syntax / lint | ||
| runs-on: ubuntu-24.04 | ||
| timeout-minutes: 10 | ||
| permissions: | ||
| contents: read | ||
| steps: | ||
| - name: Check out the caller | ||
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| # A checkout of another owner's public repository IS allowed by the | ||
| # policy - only reusable workflows are restricted - so the linter itself | ||
| # is still the pinned upstream one, not a copy of it. | ||
| - name: Check out the DEED linter at the pinned commit | ||
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| repository: metadatastician/standards | ||
| ref: 75bd5994470661a099df562d6d5ad2fe744dbd66 | ||
| path: .deed-canon | ||
| persist-credentials: false | ||
| sparse-checkout: | | ||
| 1-formats/deed/tools | ||
| sparse-checkout-cone-mode: false | ||
|
|
||
| - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 | ||
|
|
||
| - name: Lint every committed deed | ||
| env: | ||
| REQUIRE_FILES: ${{ inputs.require-files || false }} | ||
| LINTER: .deed-canon/1-formats/deed/tools/deed_lint.js | ||
| run: | | ||
| set -euo pipefail | ||
| test -s "$LINTER" || { echo "::error::the pinned DEED linter is missing at $LINTER"; exit 1; } | ||
| find . -name '*.deed' -not -path './.deed-canon/*' -print | sort > "$RUNNER_TEMP/deeds" | ||
| if [ ! -s "$RUNNER_TEMP/deeds" ]; then | ||
| if [ "$REQUIRE_FILES" = "true" ]; then | ||
| echo "::error::require-files is set and no committed .deed file was found" | ||
| exit 1 | ||
| fi | ||
| echo "no committed .deed files - nothing to lint" | ||
| exit 0 | ||
| fi | ||
| echo "linting $(wc -l < "$RUNNER_TEMP/deeds") file(s)" | ||
| # A lint failure must be readable from the checks API, not only from a | ||
| # log a reviewer cannot open from every client. | ||
| if ! xargs bun "$LINTER" < "$RUNNER_TEMP/deeds"; then | ||
| echo "::error::DEED lint failed; see the step log for the file and the reason" | ||
| exit 1 | ||
| fi | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| # SPDX-License-Identifier: MPL-2.0 | ||
| # SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk> | ||
| # | ||
| # Fork-local copy of metadatastician/standards/.github/workflows/ | ||
| # scorecard-reusable.yml at c6d78606f3928c22c3b0bdc6f5f5b47b96e05a5d. | ||
| # | ||
| # Why a copy: the repository's Actions policy allows a reusable workflow only | ||
| # from a repository owned by `hyperpolymath`, so the call to `standards` (moved | ||
| # to `metadatastician` on 2026-10-25) fails at plan time with zero jobs and the | ||
| # lane disappears without a line in any check summary. | ||
| # | ||
| # The `scorecard` job below is copied verbatim from that commit, including the | ||
| # native action-pin reconciliation, because its comments record two incidents | ||
| # that a rewrite would reintroduce: a 2-month freeze of the Security tab when | ||
| # the SARIF upload was deleted as collateral of an unrelated refactor, and a | ||
| # silent re-freeze when a reconciler failure could skip the upload. Read them | ||
| # before editing it. | ||
| # | ||
| # The upstream file's `pull-request` job is NOT copied: `scorecard.yml` has no | ||
| # pull-request trigger and `scripts/check-governance-contract.sh` fails if one is | ||
| # added, so that job is unreachable here. Adding the trigger means porting it. | ||
| name: OSSF Scorecard (fork-local) | ||
|
|
||
| on: | ||
| workflow_call: | ||
|
|
||
| # Callers must grant actions: read, contents: read, security-events: write and | ||
| # id-token: write ON THE CALLING JOB. A job-level `permissions:` block REPLACES | ||
| # the workflow-level one and is never merged with it, so the declaration below | ||
| # cannot elevate a caller that omitted them. | ||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| scorecard: | ||
| if: github.event_name != 'pull_request' | ||
| name: Run Scorecard | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 15 | ||
| permissions: | ||
| actions: read | ||
| contents: read | ||
| security-events: write | ||
| id-token: write | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - name: Run Scorecard Analysis | ||
| uses: ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e # v2.5.0 | ||
| with: | ||
| results_file: results.sarif | ||
| results_format: sarif | ||
| # publish_results feeds the public OpenSSF API and the badge, and is | ||
| # INDEPENDENT of results_format -- so emitting SARIF does not cost us | ||
| # the badge. Both outputs are live with this shape. | ||
| publish_results: ${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }} | ||
|
|
||
| # RESTORED 2026-08-25. Commit 5a93d9d5 (PR #393, "Ci/gitleaks self hosted | ||
| # fix", 2026-06-21) deleted this step and switched results_format to json | ||
| # as collateral of an unrelated gitleaks refactor. Without a SARIF upload | ||
| # nothing feeds GitHub code scanning, so every Scorecard alert in the | ||
| # estate froze: measured in this repo on 2026-08-25, all 12 Scorecard | ||
| # alerts were stuck at 2026-06-03 while Hypatia's updated 2026-08-24. | ||
| # The badge kept working, which is why it went unnoticed. | ||
| - name: Checkout native-lock reconciliation helper | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep the raw SARIF upload reachable if the helper checkout fails. If this checkout fails after Scorecard creates
|
||
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| repository: metadatastician/standards | ||
| ref: 5a0891e9e58b785181a92e141505ca96ca987b42 # per-file reconciliation (#1168) | ||
| path: .standards-scorecard | ||
| persist-credentials: false | ||
| sparse-checkout: scripts/reconcile-scorecard-actions-lock.rb | ||
| sparse-checkout-cone-mode: false | ||
| - name: Reconcile native action pins | ||
| id: reconcile | ||
| # Fail open on the ARTEFACT, never on the outcome. Previously this step | ||
| # ran under `set -euo pipefail` with no `continue-on-error`, and the | ||
| # upload below consumed results.reconciled.sarif. Any reconciler failure | ||
| # therefore skipped the upload entirely, and code scanning silently kept | ||
| # serving the PREVIOUS scan's alerts behind a green badge. That is the | ||
| # same shape as the ~2-month freeze recorded above (PR #393). | ||
| # The upload is now unconditional; the run still fails, at the end of | ||
| # the job, once the results are safely published. | ||
| continue-on-error: true | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| set -euo pipefail | ||
| gh extension install github/gh-actions-lock --pin v0.2.0 | ||
| # Exit 3: the reconciled SARIF was written, but some workflows failed | ||
| # native lock verification and keep their findings. Exit 2: usage or | ||
| # SARIF-structure error, nothing written. The code is passed on so | ||
| # the final step can say which one happened. | ||
| rc=0 | ||
| ruby .standards-scorecard/scripts/reconcile-scorecard-actions-lock.rb \ | ||
| results.sarif results.reconciled.sarif actions-lock-audit.json || rc=$? | ||
| printf 'rc=%s\n' "$rc" >> "$GITHUB_OUTPUT" | ||
| exit "$rc" | ||
|
|
||
| - name: Select SARIF to upload | ||
| id: select-sarif | ||
| run: | | ||
| set -euo pipefail | ||
| if [ -s results.reconciled.sarif ]; then | ||
| printf 'file=%s\n' results.reconciled.sarif >> "$GITHUB_OUTPUT" | ||
| printf 'reconciled=true\n' >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "::warning title=Scorecard reconciliation produced no SARIF::Uploading raw results.sarif. Native action pins will appear UNRECONCILED in this upload." | ||
| printf 'file=%s\n' results.sarif >> "$GITHUB_OUTPUT" | ||
| printf 'reconciled=false\n' >> "$GITHUB_OUTPUT" | ||
| fi | ||
| - name: Upload SARIF to code scanning | ||
| uses: github/codeql-action/upload-sarif@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0 (4.38.1 blocked estate-wide; nexia-list#100) | ||
| with: | ||
| sarif_file: ${{ steps.select-sarif.outputs.file }} | ||
|
|
||
| - name: Upload results artifact | ||
| uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 | ||
| with: | ||
| name: scorecard-results | ||
| path: | | ||
| results.sarif | ||
| results.reconciled.sarif | ||
| actions-lock-audit.json | ||
| retention-days: 90 | ||
|
|
||
| # The upload above can no longer be skipped, so a reconciler outage cannot | ||
| # freeze this repo's Security tab. A failed reconciliation is still a | ||
| # failure, and is surfaced here rather than swallowed. | ||
| - name: Fail if reconciliation did not succeed | ||
| if: "!cancelled() && (steps.reconcile.outcome != 'success' || steps.select-sarif.outputs.reconciled != 'true')" | ||
| run: | | ||
| if [ "${{ steps.reconcile.outputs.rc }}" = "3" ] && [ "${{ steps.select-sarif.outputs.reconciled }}" = "true" ]; then | ||
| echo "::error title=Scorecard reconciliation incomplete::Reconciled SARIF was uploaded. Workflows that failed native action-lock verification keep their findings; the reconcile step log names each one. This run fails by design." | ||
| else | ||
| echo "::error title=Scorecard reconciliation failed::Reconcile step outcome was '${{ steps.reconcile.outcome }}' (exit '${{ steps.reconcile.outputs.rc }}'). Raw SARIF was uploaded so results are not lost, but this run fails by design." | ||
| fi | ||
| exit 1 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,7 +20,12 @@ jobs: | |
| scorecard: | ||
| # The default-branch SARIF upload needs security-events: write; never | ||
| # expose that grant or the publication OIDC token to PR code. | ||
| uses: metadatastician/standards/.github/workflows/scorecard-reusable.yml@c6d78606f3928c22c3b0bdc6f5f5b47b96e05a5d | ||
| # | ||
| # Fork-local copy of the standards reusable at c6d78606: a reusable workflow | ||
| # owned by another account is refused by this repository's Actions policy at | ||
| # plan time, which left this lane dark after standards moved to | ||
| # metadatastician on 2026-10-25. | ||
| uses: ./.github/workflows/scorecard-reusable.yml | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔎 Supported by static analysis
Keep the publishing job within Scorecard’s approved step list. On the default branch, 🧰 Tools
|
||
| permissions: | ||
| actions: read | ||
| contents: read | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Preserve DEED filenames when passing them to the linter.
If a committed
.deedfilename contains a space or newline,xargssplits the path into separate arguments. The pinned linter then tries to read those arguments as separate files, so a valid DEED file fails this job. Usefind -print0,sort -zandxargs -0together. The pinned linter accepts file paths as arguments. (raw.githubusercontent.com)Proposed change