From 6415cf0cd59655929765e1042d4aa0b8e34dc770 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:36:04 +0100 Subject: [PATCH 1/7] fix(ci): clear five startup_failures caused by reusable permission escalation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit standards' main has had five workflows dead — Rust CI, Elixir CI, Deno CI, CodeQL and Scorecard — all reporting `startup_failure` with ZERO jobs: no logs, no red step, just an empty run. Single root cause: a reusable workflow may NARROW its caller's permissions but never WIDEN them. Requesting a permission the caller has not granted aborts the whole run before any job starts. Each pair was mismatched: caller granted reusable requested rust-ci contents:read + actions:read elixir-ci contents:read + actions:read deno-ci contents:read + actions:read codeql contents:read + actions:read, security-events:write scorecard actions,contents + security-events:write, id-token:write Two different fixes, chosen per case rather than uniformly: * rust/elixir/deno — the top-level `actions: read` was used by NO job (all 8 jobs declare their own `contents: read`, and nothing in these files downloads cross-workflow artifacts or calls the Actions API). Removed it from the reusable. This is least-privilege AND fixes every consumer at once without touching ~150 wrapper workflows, since a reusable that requests less is compatible with strictly more callers. * codeql/scorecard — security-events:write (SARIF upload) and id-token:write (Scorecard OIDC) are genuinely required, so the CALLER must grant them. Widened standards' own two wrappers only. Verified: every caller/reusable pair now satisfies the subset rule, and all five files re-parse with their job counts intact. Co-Authored-By: Claude Opus 5 --- .github/workflows/codeql.yml | 6 ++++++ .github/workflows/deno-ci-reusable.yml | 7 ++++++- .github/workflows/elixir-ci-reusable.yml | 7 ++++++- .github/workflows/rust-ci-reusable.yml | 7 ++++++- .github/workflows/scorecard.yml | 5 +++++ 5 files changed, 29 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 8ad06a41..1ad033ba 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -28,8 +28,14 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: false +# Must cover everything codeql-reusable's `analyze` job requests: a reusable +# cannot widen its caller's grant, and asking for more aborts the run as +# `startup_failure` with zero jobs. security-events: write is what uploads +# the SARIF results that branch protection requires. permissions: + actions: read contents: read + security-events: write jobs: analyze-js: diff --git a/.github/workflows/deno-ci-reusable.yml b/.github/workflows/deno-ci-reusable.yml index 62a54fc9..7232051d 100644 --- a/.github/workflows/deno-ci-reusable.yml +++ b/.github/workflows/deno-ci-reusable.yml @@ -41,8 +41,13 @@ on: required: false default: v2.x +# Only `contents: read` is requested. A reusable workflow may narrow the +# caller's permissions but never widen them: requesting a permission the +# caller has not granted aborts the run as `startup_failure` with ZERO +# jobs — no logs, no red step, just an empty run. The previous +# `actions: read` here was used by no job in this file, so every caller +# granting only `contents: read` (the estate default) failed to start. permissions: - actions: read contents: read jobs: diff --git a/.github/workflows/elixir-ci-reusable.yml b/.github/workflows/elixir-ci-reusable.yml index b359b1c5..23d6a76b 100644 --- a/.github/workflows/elixir-ci-reusable.yml +++ b/.github/workflows/elixir-ci-reusable.yml @@ -103,8 +103,13 @@ on: 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 +# caller has not granted aborts the run as `startup_failure` with ZERO +# jobs — no logs, no red step, just an empty run. The previous +# `actions: read` here was used by no job in this file, so every caller +# granting only `contents: read` (the estate default) failed to start. permissions: - actions: read contents: read jobs: diff --git a/.github/workflows/rust-ci-reusable.yml b/.github/workflows/rust-ci-reusable.yml index 89f428cb..f4c3ba51 100644 --- a/.github/workflows/rust-ci-reusable.yml +++ b/.github/workflows/rust-ci-reusable.yml @@ -101,8 +101,13 @@ on: 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 +# caller has not granted aborts the run as `startup_failure` with ZERO +# jobs — no logs, no red step, just an empty run. The previous +# `actions: read` here was used by no job in this file, so every caller +# granting only `contents: read` (the estate default) failed to start. permissions: - actions: read contents: read jobs: diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 9b259806..fad266ef 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -14,9 +14,14 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +# Must cover everything scorecard-reusable's job requests (see codeql.yml). +# id-token: write is the OIDC token Scorecard uses to publish results; +# security-events: write uploads the SARIF. permissions: actions: read contents: read + security-events: write + id-token: write jobs: scorecard: From fc6c3a8ffe2432e88037a71ba5949bdbd52be857 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:41:01 +0100 Subject: [PATCH 2/7] fix(deno): add deno.json so lint stops reporting on compiler output With no deno.json, the deno-ci reusable falls back to scanning raw *.js and swept in four bsc-generated axel-protocol/src/*.res.js artifacts, failing the lint on prefer-const violations in code no human wrote. The exclude list deliberately MIRRORS this repo's own no-js-scan.yml, which already classifies *.res.js, *.res.mjs, lib/{js,es6,bs}, out, dist, .deno and generated/ as generated/compiled. Two gates in one repo disagreeing about what counts as source was the actual defect. Does NOT make Deno CI green: 133 genuine lint problems remain in 17 hand-authored files (82 prefer-const, 28 no-import-prefix, 15 no-unused-vars, 6 no-case-declarations, 2 require-await), mostly in 0-ai-gatekeeper-protocol/mcp-repo-guardian and k9-coordination-protocol. That debt was previously invisible behind startup_failure. `deno lint --fix` does not fix these rules in Deno 2.9.2, so it needs hand edits and is left for a separate, reviewable change. Co-Authored-By: Claude Opus 5 --- deno.json | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 deno.json diff --git a/deno.json b/deno.json new file mode 100644 index 00000000..0b556655 --- /dev/null +++ b/deno.json @@ -0,0 +1,39 @@ +{ + "_comment": [ + "Canonical Deno config for hyperpolymath/standards.", + "", + "Its job is to stop `deno lint`/`deno fmt` reporting on files that are", + "COMPILER OUTPUT rather than hand-authored source. Without this file the", + "deno-ci reusable falls back to scanning raw *.js, which swept in the four", + "bsc-generated axel-protocol/src/*.res.js artifacts and failed the lint on", + "prefer-const violations in generated code — code no human wrote and nobody", + "should edit.", + "", + "The exclude list deliberately MIRRORS the one already used by this repo's", + "own .github/workflows/no-js-scan.yml, which classifies *.res.js, *.res.mjs,", + "lib/{js,es6,bs}, out, dist, .deno and generated/ as 'generated/compiled'.", + "Two gates in the same repo disagreeing about what counts as source is the", + "actual defect; this makes them agree rather than inventing new policy.", + "", + "Per estate language policy the .affine sources are primary — every one of", + "those .res.js files already has an .affine sibling." + ], + "exclude": [ + ".git", + "node_modules", + "**/node_modules", + "**/deps", + "**/vendor", + "**/lib/js", + "**/lib/es6", + "**/lib/bs", + "**/out", + "**/dist", + "**/.deno", + "**/generated", + "**/*.res.js", + "**/*.res.mjs", + "**/*.min.js", + "**/*.d.ts" + ] +} From 19361a06ace37c491b6f5d8e24be13c19679757b Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:43:15 +0100 Subject: [PATCH 3/7] fix(governance): repair Allowlist Preflight (exit 127 on every run) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit check-actions-policy.sh ends by exec'ing its SIBLING script: exec bash "${0%/*}/check-allowed-actions.sh" "$CANON" "$WF_DIR" but the workflow copied only check-actions-policy.sh into $RUNNER_TEMP and then `rm -rf`'d the standards checkout. `${0%/*}` therefore resolved to $RUNNER_TEMP, where the sibling did not exist — so the step died with exit 127 (command not found) rather than ever running the check. Also staged the canonical allowlist and taught the wrapper to honour $ALLOWLIST_JSON. The old hard-coded path was relative to the CALLER's working directory, so a consumer repo — which has no rhodium-standard-repositories/ of its own — would take the `exit 2` "allowlist not found" branch even once the 127 was fixed. Verified by reproducing the CI staging exactly (both scripts copied out, checkout deleted, allowlist passed by env): "checked 34 `uses:` refs across .github/workflows — 0 not covered by the allowlist", exit 0. Co-Authored-By: Claude Opus 5 --- .github/workflows/governance-reusable.yml | 13 +++++++++++-- scripts/check-actions-policy.sh | 5 ++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/governance-reusable.yml b/.github/workflows/governance-reusable.yml index 113ee245..a682b88f 100644 --- a/.github/workflows/governance-reusable.yml +++ b/.github/workflows/governance-reusable.yml @@ -84,9 +84,18 @@ jobs: - name: Run allowlist preflight check run: | - cp .standards-checkout/scripts/check-actions-policy.sh "$RUNNER_TEMP/" + # check-actions-policy.sh `exec`s its SIBLING check-allowed-actions.sh + # via "${0%/*}/...". Copying only the first script and then deleting + # the checkout left that sibling missing, so the step died with exit + # 127 (command not found) on every run. Stage both, plus the canonical + # allowlist itself — consumer repos have no copy of it in their tree. + cp .standards-checkout/scripts/check-actions-policy.sh \ + .standards-checkout/scripts/check-allowed-actions.sh "$RUNNER_TEMP/" + cp .standards-checkout/rhodium-standard-repositories/actions-allowlist/allowed-actions.json \ + "$RUNNER_TEMP/allowed-actions.json" rm -rf .standards-checkout - bash "$RUNNER_TEMP/check-actions-policy.sh" .github/workflows + ALLOWLIST_JSON="$RUNNER_TEMP/allowed-actions.json" \ + bash "$RUNNER_TEMP/check-actions-policy.sh" .github/workflows validate-hypatia-baseline: name: Validate Hypatia Baseline diff --git a/scripts/check-actions-policy.sh b/scripts/check-actions-policy.sh index b3083f7c..83f327bb 100755 --- a/scripts/check-actions-policy.sh +++ b/scripts/check-actions-policy.sh @@ -23,7 +23,10 @@ WF_DIR="${1:-.github/workflows}" # The canonical allowlist lives in this repo at # rhodium-standard-repositories/actions-allowlist/allowed-actions.json -CANON="rhodium-standard-repositories/actions-allowlist/allowed-actions.json" +# Resolve the canonical allowlist. $ALLOWLIST_JSON lets the caller point at a +# copy staged outside the scanned tree — required for consumer repos, which do +# not have rhodium-standard-repositories/ in their own checkout. +CANON="${ALLOWLIST_JSON:-rhodium-standard-repositories/actions-allowlist/allowed-actions.json}" if [ ! -f "$CANON" ]; then echo "::error::Canonical allowlist not found: $CANON" From 56a246bfdd9ef1dea48ca165b751029d45951014 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:44:25 +0100 Subject: [PATCH 4/7] fix(policy): remove the last Python file from standards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Language/package anti-pattern gate has been failing on standards' own main with: ❌ Python files detected — Python is fully banned scripts/bulk-fix-workflows.py The repository that DEFINES the estate language policy was the one breaking it. Python is banned with no exceptions (SaltStack carve-out removed 2026-01-03), so an exemption pragma would be the wrong instrument here. scripts/bulk-fix-workflows.py is a 355-line one-off migration tool that replaced duplicate workflow files with calls to the root reusables. That migration has landed (d8f8975), nothing in the repo references the script — no workflow, justfile, doc or other script — and it is fully recoverable from git history if ever needed. Porting dead code to Deno would be effort spent on something with no callers, so it is removed rather than rewritten. This was the only *.py file in the repository. Co-Authored-By: Claude Opus 5 --- scripts/bulk-fix-workflows.py | 355 ---------------------------------- 1 file changed, 355 deletions(-) delete mode 100644 scripts/bulk-fix-workflows.py diff --git a/scripts/bulk-fix-workflows.py b/scripts/bulk-fix-workflows.py deleted file mode 100644 index 6edfecf4..00000000 --- a/scripts/bulk-fix-workflows.py +++ /dev/null @@ -1,355 +0,0 @@ -#!/usr/bin/env python3 -""" -Bulk script to replace duplicate workflow files with calls to root reusable workflows. - -Usage: python3 scripts/bulk-fix-workflows.py --dry-run - python3 scripts/bulk-fix-workflows.py --execute -""" - -import os -import re -import sys -from pathlib import Path - -REPO_ROOT = Path(__file__).parent.parent.absolute() - -# Workflows to fix and their reusable equivalents -WORKFLOWS = { - "codeql.yml": { - "reusable": "codeql-reusable.yml", - "triggers": ["push", "pull_request", "schedule"], - "content": """# SPDX-License-Identifier: MPL-2.0 -name: CodeQL Security Analysis - -on: - push: - branches: [main, master] - pull_request: - branches: [main, master] - schedule: - - cron: '0 6 * * 1' - -concurrency: - group: ${{{ github.workflow }}}-${{{ github.ref }}} - cancel-in-progress: false - -permissions: - contents: read - -jobs: - analyze-js: - uses: {relpath}/.github/workflows/codeql-reusable.yml - with: - language: javascript-typescript -""", - }, - "governance.yml": { - "reusable": "governance-reusable.yml", - "triggers": ["push", "pull_request", "workflow_dispatch"], - "content": """# SPDX-License-Identifier: MPL-2.0 -# governance.yml — single wrapper calling the shared estate governance bundle -# in hyperpolymath/standards instead of carrying per-repo copies. -# -# Replaces the per-repo governance scaffolding removed in the same commit: -# quality.yml, guix-nix-policy.yml, npm-bun-blocker.yml, ts-blocker.yml, -# security-policy.yml, rsr-antipattern.yml, wellknown-enforcement.yml, -# workflow-linter.yml -# -# Load-bearing build/security workflows stay standalone in the repo -# (rust-ci, codeql, dependabot, release, scan/mirror/pages plumbing). - -name: Governance - -on: - push: - branches: [main, master] - pull_request: - workflow_dispatch: - -permissions: - contents: read - -jobs: - governance: - uses: {relpath}/.github/workflows/governance-reusable.yml -""", - }, - "hypatia-scan.yml": { - "reusable": "hypatia-scan-reusable.yml", - "triggers": ["push", "pull_request", "schedule", "workflow_dispatch"], - "content": """# SPDX-License-Identifier: MPL-2.0 -name: Hypatia Security Scan - -on: - push: - branches: [main, master, develop] - pull_request: - branches: [main, master] - schedule: - - cron: '0 0 * * 0' - workflow_dispatch: - -permissions: - actions: read - contents: read - security-events: write - -jobs: - scan: - uses: {relpath}/.github/workflows/hypatia-scan-reusable.yml - secrets: inherit -""", - }, - "instant-sync.yml": { - "reusable": None, # No reusable, but we have a standard version - "triggers": ["push", "release"], - "content": """# SPDX-License-Identifier: MPL-2.0 -# Instant Forge Sync - Triggers propagation to all forges on push/release -name: Instant Sync - -on: - push: - branches: [main, master] - release: - types: [published] - -concurrency: - group: ${{{ github.workflow }}}-${{{ github.ref }}} - cancel-in-progress: true - -permissions: - actions: read - contents: read - -jobs: - dispatch: - timeout-minutes: 10 - runs-on: ubuntu-latest - env: - FARM_DISPATCH_TOKEN: ${{{ secrets.FARM_DISPATCH_TOKEN }}} - steps: - - name: Trigger Propagation - if: ${{{ env.FARM_DISPATCH_TOKEN != '' }}} - uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v3 - with: - token: ${{{ secrets.FARM_DISPATCH_TOKEN }}} - repository: hyperpolymath/.git-private-farm - event-type: propagate - client-payload: |- - { - "repo": "${{{ github.event.repository.name }}}", - "ref": "${{{ github.ref }}}", - "sha": "${{{ github.sha }}}", - "forges": "" - } - - - name: Skipped (FARM_DISPATCH_TOKEN not configured) - if: ${{{ env.FARM_DISPATCH_TOKEN == '' }}} - env: - REPO_NAME: ${{{ github.event.repository.name }}} - run: | - echo "::notice::FARM_DISPATCH_TOKEN secret not configured on ${{{ REPO_NAME }}}; skipping cross-repo dispatch." - - - name: Confirm - env: - REPO_NAME: ${{{ github.event.repository.name }}} - run: echo "::notice::Propagation triggered for ${{{ REPO_NAME }}}" -""", - }, - "mirror.yml": { - "reusable": "mirror-reusable.yml", - "triggers": ["push", "workflow_dispatch"], - "content": """# SPDX-License-Identifier: MPL-2.0 -name: Mirror to Git Forges - -on: - push: - branches: [main] - workflow_dispatch: - -permissions: - contents: read - -jobs: - mirror: - uses: {relpath}/.github/workflows/mirror-reusable.yml - secrets: inherit -""", - }, - "scorecard.yml": { - "reusable": "scorecard-reusable.yml", - "triggers": ["push", "schedule", "workflow_dispatch"], - "content": """# SPDX-License-Identifier: MPL-2.0 -name: OSSF Scorecard - -on: - push: - branches: [main, master] - schedule: - - cron: '0 4 * * *' - workflow_dispatch: - -permissions: - contents: read - -jobs: - scorecard: - uses: {relpath}/.github/workflows/scorecard-reusable.yml -""", - }, - "scorecard-enforcer.yml": { - "reusable": "scorecard-reusable.yml", - "triggers": ["push", "schedule", "workflow_dispatch"], - "content": """# SPDX-License-Identifier: MPL-2.0 -# Prevention workflow - runs OpenSSF Scorecard and fails on low scores -name: OpenSSF Scorecard Enforcer - -on: - push: - branches: [main] - schedule: - - cron: '0 6 * * 1' - workflow_dispatch: - -permissions: - contents: read - -jobs: - scorecard: - uses: {relpath}/.github/workflows/scorecard-reusable.yml -""", - }, - "secret-scanner.yml": { - "reusable": "secret-scanner-reusable.yml", - "triggers": ["push", "pull_request"], - "content": """# SPDX-License-Identifier: MPL-2.0 -name: Secret Scanner - -on: - pull_request: - push: - branches: [main] - -concurrency: - group: ${{{ github.workflow }}}-${{{ github.ref }}} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - scan: - permissions: - contents: read - uses: {relpath}/.github/workflows/secret-scanner-reusable.yml - secrets: inherit -""", - }, -} - - -def get_relative_path(target_dir): - """Calculate the relative path from target_dir to repo root .github/workflows/""" - target_path = Path(target_dir).resolve() - root_path = REPO_ROOT.resolve() - - # Count the number of levels between target_dir and repo root - try: - rel = target_path.relative_to(root_path) - depth = len(rel.parts) - except ValueError: - depth = 0 - - # Go up depth levels, then down to .github/workflows/ - return "../" * depth + ".github/workflows/" - - -def find_workflow_dirs(): - """Find all directories containing .github/workflows/""" - workflow_dirs = [] - for root, dirs, files in os.walk(REPO_ROOT): - if ".github/workflows" in dirs: - workflow_dir = Path(root) / ".github/workflows" - # Skip the root .github/workflows - if workflow_dir != REPO_ROOT / ".github/workflows": - workflow_dirs.append(workflow_dir) - return workflow_dirs - - -def needs_fix(workflow_dir, workflow_name): - """Check if a workflow file needs to be fixed (i.e., doesn't already use reusable)""" - workflow_file = workflow_dir / workflow_name - if not workflow_file.exists(): - return False - - content = workflow_file.read_text() - # Check if it already uses a reusable workflow - if "uses:" in content and "reusable" in content: - return False - - return True - - -def fix_workflow(workflow_dir, workflow_name, dry_run=True): - """Replace a workflow file with a call to the reusable version""" - workflow_file = workflow_dir / workflow_name - - if not workflow_file.exists(): - return None - - # Calculate relative path - rel_path = get_relative_path(workflow_dir.parent) - - # Get the template - if workflow_name not in WORKFLOWS: - return None - - template = WORKFLOWS[workflow_name]["content"] - content = template.format(relpath=rel_path) - - if dry_run: - print(f" WOULD FIX: {workflow_file}") - print(f" Using: {rel_path}.github/workflows/{WORKFLOWS[workflow_name]['reusable']}") - return (workflow_file, content) - else: - workflow_file.write_text(content) - print(f" FIXED: {workflow_file}") - return (workflow_file, content) - - -def main(): - dry_run = "--dry-run" in sys.argv - execute = "--execute" in sys.argv - - if not execute and not dry_run: - print("Usage: python3 scripts/bulk-fix-workflows.py --dry-run") - print(" python3 scripts/bulk-fix-workflows.py --execute") - return - - workflow_dirs = find_workflow_dirs() - print(f"Found {len(workflow_dirs)} workflow directories") - print() - - total_found = 0 - total_fixed = 0 - - for workflow_dir in sorted(workflow_dirs): - # Skip if already processed - rel_dir = workflow_dir.relative_to(REPO_ROOT) - print(f"Processing: {rel_dir}") - - for workflow_name in WORKFLOWS: - if needs_fix(workflow_dir, workflow_name): - total_found += 1 - if dry_run: - fix_workflow(workflow_dir, workflow_name, dry_run=True) - else: - fix_workflow(workflow_dir, workflow_name, dry_run=False) - total_fixed += 1 - - print() - print(f"Summary: {total_found} workflow files need fixing, {total_fixed} fixed") - - -if __name__ == "__main__": - main() From 4b752c7fc085d247e9691b9d1194316a78daa884 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:46:00 +0100 Subject: [PATCH 5/7] fix(ci): add the missing timeout-minutes on signed-push-smoke MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hypatia's baseline gate reported exactly two NEW (unfiltered) findings on standards' main. One was scripts/bulk-fix-workflows.py, removed in the previous commit. This is the other: missing_timeout_minutes signed-push-smoke.yml (medium) An un-bounded job can occupy a runner for the full 6-hour default on hang, against a shared account-wide Actions pool. Deliberately applied ONLY to `smoke`, which is a steps-job. `timeout-minutes` is an ILLEGAL key on a job that `uses:` a reusable workflow, where it makes GitHub refuse to parse the entire file — killing every job in it, with no check run to show why. Swept all of .github/workflows/ distinguishing the two job shapes: this was the last steps-job in the repo without a bound, and there are now zero. Co-Authored-By: Claude Opus 5 --- .github/workflows/signed-push-smoke.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/signed-push-smoke.yml b/.github/workflows/signed-push-smoke.yml index dc962303..c867dc4d 100644 --- a/.github/workflows/signed-push-smoke.yml +++ b/.github/workflows/signed-push-smoke.yml @@ -18,6 +18,10 @@ permissions: jobs: smoke: runs-on: ubuntu-latest + # Safe here because `smoke` is a steps-job. Never add timeout-minutes to a + # job that `uses:` a reusable workflow — it is an illegal key there and + # GitHub refuses to parse the ENTIRE file, killing every job in it. + timeout-minutes: 10 steps: - name: Checkout uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 From 4779125471ceb1f0555ec434fd95c1e3faf2a32a Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:47:18 +0100 Subject: [PATCH 6/7] fix(registry): regenerate REGISTRY.a2ml to clear Registry Verify drift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Registry Verify has been red on main. Its first step is the drift check, and under `set -euo pipefail` it aborts the job before the Mustfile and scorecard steps ever run: DRIFT: .machine_readable/REGISTRY.a2ml is stale — run 'just registry' Regenerated with scripts/build-registry.sh, exactly as the failure message and the job summary instruct. The diff is only what the generator derives: · three refreshed source_hash values (a2ml, adoption-readiness-grades, rhodium-standard-repositories) — their spec homes changed without the derived registry being rebuilt; · publication-pre-flight's canonical_doc re-pointed from the retired ESTATE-AUDIT-BASELINE-2026-03-30.adoc to HOL-SUITABILITY-CHECKLIST.adoc. No hand edits. `build-registry.sh --check` now passes, and check-mustfile-structure.sh passes. The scorecard --verify step could not be exercised locally (xmllint absent — the script correctly declines to judge pass-rows rather than call them false on missing tooling); CI installs ripgrep + libxml2-utils for exactly that reason, so it is verified there. Co-Authored-By: Claude Opus 5 --- .machine_readable/REGISTRY.a2ml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.machine_readable/REGISTRY.a2ml b/.machine_readable/REGISTRY.a2ml index 52ae7325..a3dd0c60 100644 --- a/.machine_readable/REGISTRY.a2ml +++ b/.machine_readable/REGISTRY.a2ml @@ -45,7 +45,7 @@ name = "A2ML — Attested Markup Language" stream = "foundation" home = "a2ml/" canonical_doc = "a2ml/README.adoc" -source_hash = "sha256:3409e79367d002ba52f4ed1742b2846c1beffdf8acf78c53997a69a32bd46617" +source_hash = "sha256:4ce7ddc2e22f4fe4138dfef944b4eab1ab4bc58e7bd473f9094c518ee0eb0b04" route = "the typed/verified machine-readable document format" [[spec]] @@ -189,7 +189,7 @@ name = "ARG — Adoption Readiness Grades" stream = "readiness" home = "adoption-readiness-grades/" canonical_doc = "adoption-readiness-grades/README.adoc" -source_hash = "sha256:77e3c0d74e9fd037b57dc883804501be1117ac12534d3d817654f2c96919a0e8" +source_hash = "sha256:8943491cf3991b8c6fd5a53bd83657c63978592510cbe79996b4034ab34ae40d" route = "per-language adoption-maturity profile templates" [[spec]] @@ -225,7 +225,7 @@ name = "RSR — Rhodium Standard Repositories" stream = "governance" home = "rhodium-standard-repositories/" canonical_doc = "rhodium-standard-repositories/README.adoc" -source_hash = "sha256:03252ce83c0361887c6a96530c8bba15c4c7f816c07d6d2ffbffd53250e3bf61" +source_hash = "sha256:4cad25af39c3a27a79bf5ad64ea70ec0f6ff888855fdefc09d617f2df4d2d018" route = "the repository-compliance standard every repo is graded against" [[spec]] @@ -269,8 +269,8 @@ id = "publication-pre-flight" name = "Publication Pre-Flight" stream = "governance" home = "publication-pre-flight/" -canonical_doc = "publication-pre-flight/ESTATE-AUDIT-BASELINE-2026-03-30.adoc" -source_hash = "sha256:8e1f3bb0515e80636046332b99639346655d87412e1f2f3613903854213025a1" +canonical_doc = "publication-pre-flight/HOL-SUITABILITY-CHECKLIST.adoc" +source_hash = "sha256:86e93a00784d646d99dcaf412efc3d647a02ff7ac2e38cc1f94c1d6bc775c188" route = "submission gate (HOL + Zenodo checklists)" [[spec]] From b3efb6a52ed73b60090fb8fb5a09c6abb9d1a63e Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Tue, 28 Jul 2026 23:57:10 +0100 Subject: [PATCH 7/7] fix(scorecards): make five broken PASS claims honest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clearing the REGISTRY drift let Registry Verify reach its --verify step for the first time, which RUNS every claimed pass-row's check. Five claims did not hold: 82 grounded pass · 5 broken pass · 0 unrunnable Investigated each; they split cleanly into two causes. STALE CHECKS (2) — the compliance is real, the check text was not: · a2ml/S4 greps for an inline 'gitleaks/gitleaks-action', but secret-scanner.yml now `uses:` secret-scanner-reusable. · axel-protocol/S4 greps for 'ossf/scorecard-action', but scorecard.yml now `uses:` scorecard-reusable. Both repos ARE scanning; the checks tested a pre-migration implementation detail. Widened each to accept the inline action OR the reusable, so the check survives the migration instead of silently going false. GENUINELY UNMET (3) — the artifact does not exist, so the claim was false: · avow-protocol/M5 codeql.yml absent (avow-protocol has no .github/workflows/ at all) · avow-protocol/S1 AVOW-THREAT-MODEL.adoc absent (only BINDING.adoc) · rhodium-.../M2 wave0-false-green-test.sh fails 1/13 — it references $ROOT/audit-contractiles.sh, which exists nowhere in the repo Corrected status pass -> fail. This does NOT fabricate compliance and does not paper over the gap: `fail` is already a normal state on these scorecards, and an honest red is worth more than a green the repo's own verifier calls "not real". Restoring the three artifacts is real work and is left to a deliberate follow-up. No other pass rows were touched (4 files, 5 lines). Dashboard regenerated. Co-Authored-By: Claude Opus 5 --- .machine_readable/scorecards/a2ml.scorecard.a2ml | 2 +- .machine_readable/scorecards/avow-protocol.scorecard.a2ml | 4 ++-- .machine_readable/scorecards/axel-protocol.scorecard.a2ml | 2 +- .../rhodium-standard-repositories.scorecard.a2ml | 2 +- COMPLIANCE-DASHBOARD.md | 8 ++++---- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.machine_readable/scorecards/a2ml.scorecard.a2ml b/.machine_readable/scorecards/a2ml.scorecard.a2ml index 80504db7..c42eca1b 100644 --- a/.machine_readable/scorecards/a2ml.scorecard.a2ml +++ b/.machine_readable/scorecards/a2ml.scorecard.a2ml @@ -84,7 +84,7 @@ text = "Secrets SHOULD be scanned on every push/PR to prevent credential leaks." system = ".github/workflows/secret-scanner.yml (Gitleaks + rust/shell secret jobs)" status = "pass" evidence = "secret-scanner.yml runs gitleaks/gitleaks-action plus rust-secrets and shell-secrets jobs on pull_request and push to main. TruffleHog was retired as redundant to the pinned gitleaks gate (#507/#525)." -check = "test -f a2ml/.github/workflows/secret-scanner.yml && grep -q 'gitleaks/gitleaks-action' a2ml/.github/workflows/secret-scanner.yml" +check = "test -f a2ml/.github/workflows/secret-scanner.yml && grep -qE 'gitleaks/gitleaks-action|secret-scanner-reusable' a2ml/.github/workflows/secret-scanner.yml" effects = "Prevents accidental credential leakage into repo history, which would otherwise require costly rotation/remediation across the hyperpolymath estate." [[should]] diff --git a/.machine_readable/scorecards/avow-protocol.scorecard.a2ml b/.machine_readable/scorecards/avow-protocol.scorecard.a2ml index 744e4a60..5d957dfc 100644 --- a/.machine_readable/scorecards/avow-protocol.scorecard.a2ml +++ b/.machine_readable/scorecards/avow-protocol.scorecard.a2ml @@ -41,7 +41,7 @@ effects = "Three different license identifiers appear: LICENSE declares 'SPDX-Li id = "M5" text = "The codebase MUST be scanned for security vulnerabilities via static analysis on every push and pull request." system = ".github/workflows/codeql.yml (CodeQL Advanced) — runs on push to main, pull_request to main, and weekly schedule, analyzing javascript-typescript and actions languages" -status = "pass" +status = "fail" evidence = ".github/workflows/codeql.yml defines the 'analyze' job with a language matrix (actions, javascript-typescript) using github/codeql-action/init and analyze@v3, triggered on push/pull_request/schedule; this is a real, currently configured CI workflow." check = "cd avow-protocol && test -f .github/workflows/codeql.yml && grep -q 'github/codeql-action/analyze' .github/workflows/codeql.yml" effects = "Downstream repos/consumers get continuous automated code-scanning coverage for JS/TS and GitHub Actions definitions in this repo, reducing risk of shipping known vulnerability patterns; if this workflow were removed, that scanning safety net would be lost estate-wide for this spec." @@ -50,7 +50,7 @@ effects = "Downstream repos/consumers get continuous automated code-scanning cov id = "S1" text = "The spec SHOULD maintain a dedicated threat-model document covering adversaries, trust boundaries, and mitigations." system = "none (manually authored document, no automated freshness/consistency check)" -status = "pass" +status = "fail" evidence = "AVOW-THREAT-MODEL.adoc (24,831 bytes) exists with a substantive threat-actor analysis, threat-landscape matrix (Mass Bot Creation, Verification Bypass, Identity Theft, etc.) and mitigation mapping. Note it is still internally titled 'STAMP Protocol: Comprehensive Threat Model', indicating it has not been fully updated for the AVOW rename." check = "cd avow-protocol && test -f AVOW-THREAT-MODEL.adoc && [ $(wc -c < AVOW-THREAT-MODEL.adoc) -gt 10000 ]" effects = "Reviewers and integrators evaluating AVOW's security posture have a concrete artifact to consult, though the stale STAMP naming inside it means downstream readers may not immediately recognize it as the current AVOW threat model." diff --git a/.machine_readable/scorecards/axel-protocol.scorecard.a2ml b/.machine_readable/scorecards/axel-protocol.scorecard.a2ml index 7d49945b..d07d1982 100644 --- a/.machine_readable/scorecards/axel-protocol.scorecard.a2ml +++ b/.machine_readable/scorecards/axel-protocol.scorecard.a2ml @@ -75,7 +75,7 @@ text = "The project SHOULD track and report an OSSF Scorecard supply-chain secur system = ".github/workflows/scorecard.yml — OSSF Scorecard action, uploads SARIF to code scanning, runs daily and on push" status = "pass" evidence = ".github/workflows/scorecard.yml runs ossf/scorecard-action@4eaacf05... on a daily cron and push to main/master, uploading results.sarif via github/codeql-action/upload-sarif." -check = "test -f axel-protocol/.github/workflows/scorecard.yml && grep -q 'ossf/scorecard-action' axel-protocol/.github/workflows/scorecard.yml" +check = "test -f axel-protocol/.github/workflows/scorecard.yml && grep -qE 'ossf/scorecard-action|scorecard-reusable' axel-protocol/.github/workflows/scorecard.yml" effects = "Consumers evaluating supply-chain trust of this repo (branch protection, pinned actions, etc.) have an ongoing automated signal; without it they'd have to audit manually." [[should]] diff --git a/.machine_readable/scorecards/rhodium-standard-repositories.scorecard.a2ml b/.machine_readable/scorecards/rhodium-standard-repositories.scorecard.a2ml index 60be281d..17c9ebdf 100644 --- a/.machine_readable/scorecards/rhodium-standard-repositories.scorecard.a2ml +++ b/.machine_readable/scorecards/rhodium-standard-repositories.scorecard.a2ml @@ -22,7 +22,7 @@ effects = "Every estate repo is graded against this; a missing spec voids all gr id = "M2" text = "RSR MUST ship a runnable audit tool that scores a repo and exits non-zero on error." system = "rhodium-standard-repositories/rsr-audit.sh (arg parsing fixed standards#387); Justfile validate via scripts/rsr-selfaudit.sh" -status = "pass" +status = "fail" evidence = "rsr-audit.sh runs; invalid --format exits 4; scripts/tests/wave0-false-green-test.sh asserts the arg contract." check = "bash scripts/tests/wave0-false-green-test.sh" effects = "Without a working auditor, RSR grades are hand-waved; downstream badge claims become unverifiable." diff --git a/COMPLIANCE-DASHBOARD.md b/COMPLIANCE-DASHBOARD.md index d740e92c..5017ba4c 100644 --- a/COMPLIANCE-DASHBOARD.md +++ b/COMPLIANCE-DASHBOARD.md @@ -33,7 +33,7 @@ | `anchor-a2ml` | ❌ gap | 0/5 | 0/5 | 0/3 | 15% | – | 2026-07-03 | | `0-ai-gatekeeper-protocol` | ❌ gap | 3/5 | 0/4 | 0/2 | 54% | 3/3 | 2026-07-03 | | `k9-coordination-protocol` | ❌ gap | 3/5 | 2/4 | 0/3 | 100% | 5/5 | 2026-07-03 | -| `avow-protocol` | ❌ gap | 1/5 | 2/4 | 0/3 | 58% | 3/3 | 2026-07-03 | +| `avow-protocol` | ❌ gap | 0/5 | 1/4 | 0/3 | 58% | 1/1 | 2026-07-03 | | `axel-protocol` | ❌ gap | 0/5 | 4/5 | 0/3 | 92% | 4/4 | 2026-07-03 | | `overlay-protocol` | ❌ gap | 1/5 | 0/4 | 0/3 | 50% | 1/1 | 2026-07-03 | | `consent-aware-http` | ❌ gap | 1/5 | 1/5 | 0/3 | 69% | 2/2 | 2026-07-03 | @@ -41,7 +41,7 @@ | `foundations-readiness-grades` | ❌ gap | 2/5 | 1/4 | 0/2 | 72% | 3/3 | 2026-07-03 | | `component-readiness-grades` | ❌ gap | 2/5 | 2/4 | 0/3 | 66% | 4/4 | 2026-07-03 | | `toolchain-readiness-grades` | ❌ gap | 1/5 | 2/4 | 0/3 | 83% | 3/3 | 2026-07-03 | -| `rhodium-standard-repositories` | ❌ gap | 2/3 | 1/2 | 0/1 | 50% | 3/3 | 2026-07-03 | +| `rhodium-standard-repositories` | ❌ gap | 1/3 | 1/2 | 0/1 | 50% | 2/2 | 2026-07-03 | | `session-management-standards` | ❌ gap | 1/5 | 1/4 | 0/3 | 41% | 2/2 | 2026-07-03 | | `did-you-actually-do-that` | ✅ met | 5/5 | 2/3 | 0/2 | 90% | 7/7 | 2026-07-03 | | `ensaid-config` | ❌ gap | 0/5 | 0/3 | 0/3 | 90% | – | 2026-07-03 | @@ -55,9 +55,9 @@ - **Specs registered (local):** 30 - **Specs with a scorecard:** 30 / 30 -- **MUST requirements:** 47 passing / 147 total (73 failing) +- **MUST requirements:** 45 passing / 147 total (75 failing) - **Estate systems coverage:** 67% of 343 graded requirements have a mechanical check -- **Grounded passes:** 87 / 87 (100%) pass rows carry an executable `check` run by `--verify` +- **Grounded passes:** 84 / 84 (100%) pass rows carry an executable `check` run by `--verify` ## How this dashboard stays honest