diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock index bc650e1..0288335 100644 --- a/.github/workflows/actions.lock +++ b/.github/workflows/actions.lock @@ -71,8 +71,6 @@ workflows: - 'softprops/action-gh-release@v3.0.3' '.github/workflows/rsr-compliance-canary.yml': - 'actions/checkout@v7.0.1' - '.github/workflows/runtime-policy.yml': - - 'actions/checkout@v7.0.1' '.github/workflows/rust-ci.yml': - 'hyperpolymath/standards@75bd5994470661a099df562d6d5ad2fe744dbd66' '.github/workflows/scorecard.yml': diff --git a/.github/workflows/estate-rules.yml b/.github/workflows/estate-rules.yml index 8c8f44d..1726316 100644 --- a/.github/workflows/estate-rules.yml +++ b/.github/workflows/estate-rules.yml @@ -98,6 +98,10 @@ jobs: # and the sync gate names each one if its record goes missing. - name: actions.lock SHA-ref regression controls run: bash tests/workflows/actions_lock_sha_refs_test.sh + # The gate above must reject each way the retired runtime policy could + # come back, including a workflow and lock record restored together. + - name: Runtime policy retirement guards reject each regression + run: bash tests/workflows/runtime_policy_retirement_test.sh # The Scorecard calling job's own permissions map must grant actions: read; # the workflow-level grant does not reach a job that declares its own map. - name: Scorecard caller grants diff --git a/.github/workflows/runtime-policy.yml b/.github/workflows/runtime-policy.yml deleted file mode 100644 index 7319b77..0000000 --- a/.github/workflows/runtime-policy.yml +++ /dev/null @@ -1,72 +0,0 @@ -# This workflow is managed by gh actions-lock. -# SPDX-License-Identifier: MPL-2.0 -# Runtime and package-manager policy check. -# -# Authority: hyperpolymath/standards LANGUAGE-POLICY.adoc §1. -# Ordering: Bun (1st) > pnpm (2nd) > npm (last resort). Deno is RETIRED, not a tier. -# -# REPLACES npm-bun-blocker.yml, which failed any build carrying `bun.lockb` with -# the message "npm/bun artifacts detected. Use Deno instead." That gate blocked -# what is now the FIRST-choice runtime and mandated the second. It was present in -# 55 repositories. -# -# What this fails on, deliberately: -# MIXED TOOLCHAINS -- two different package managers' lockfiles in one repo. -# That is real, actionable drift: two dependency graphs that can disagree. -# What it does NOT fail on: -# Using bun, pnpm or npm. Deno is RETIRED (2026-08-26). npm is LAST but PERMITTED; the check reports -# the tier in use so drift is visible without blocking legitimate work. -name: Runtime Policy -on: - push: - branches: [main, master] - pull_request: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - runtime-policy: - name: Runtime Policy - runs-on: ubuntu-24.04 - timeout-minutes: 10 - permissions: - contents: read - steps: - - uses: actions/checkout@v7.0.1 - - - name: Report runtime tier and reject mixed toolchains - run: | - set -euo pipefail - - bun=0; deno=0; pnpm=0; npm=0 - [ -f bun.lockb ] || [ -f bun.lock ] && bun=1 || true - [ -f deno.lock ] || [ -f deno.json ] || [ -f deno.jsonc ] && deno=1 || true - [ -f pnpm-lock.yaml ] && pnpm=1 || true - [ -f package-lock.json ] && npm=1 || true - - total=$((bun + deno + pnpm + npm)) - - if [ "$total" -eq 0 ]; then - echo "::notice::No JS/TS package manager in use — nothing to check." - exit 0 - fi - - # Report the tier actually in use (LANGUAGE-POLICY.adoc §1). - [ "$bun" -eq 1 ] && echo "Bun — tier 1 (preferred)" - [ "$deno" -eq 1 ] && echo "::warning::Deno lockfile/config present. Deno is BEING REMOVED, not grandfathered (owner ruling 2026-08-26, canon standards#655). Migrate to Bun — package.json + bun.lock. This is a warning rather than an error only while the estate migration is in flight; it escalates to an error when that completes." - [ "$pnpm" -eq 1 ] && echo "pnpm — tier 3" - [ "$npm" -eq 1 ] && echo "::warning::npm lockfile present. npm is tier 4, the last resort — permitted, never preferred. See LANGUAGE-POLICY.adoc §1." - - if [ "$total" -gt 1 ]; then - echo "::error::Mixed toolchains: $total package managers have lockfiles in this repository." - echo "Two dependency graphs that can disagree is real drift. Pick one — preferring the" - echo "highest tier present — and delete the others' lockfiles." - exit 1 - fi - - echo "✅ Single package manager in use." diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 996fbf0..b75f617 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -1,5 +1,24 @@ == Changelog +== 2026-10-19 — runtime-policy.yml folded into governance + +* `.github/workflows/runtime-policy.yml` is removed, with its + `actions.lock` record, as rsr-template-repo#244 does at the source. This + repository's copy still said Deno was retired. It failed any repository + holding `bun.lock`, `deno.lock` and `pnpm-lock.yaml` together, which is + what `js-runtimes regen` writes. Governance already covers the check. + "Language / package anti-pattern policy" fails any tracked + `package-lock.json`, and the "JS runtimes" jobs check each lock against + `package.json` (standards cicd-regularisation spec §14). +* `scripts/validate-template.sh` and + `tests/workflows/validate_workflows_test.sh` require `governance.yml` in + its place. `tests/workflows/runtime_policy_migration_test.sh` checks both + validators reject a tree without `governance.yml`, and + `actions_lock_sha_refs_test.sh`, which Estate Rules runs, requires the + workflow and its lock record to stay gone and runs that test. + `tests/workflows/runtime_policy_retirement_test.sh`, also in Estate Rules, + restores the workflow or its lock record in each way the gate must catch + and checks the gate rejects every one. == 2026-10-19 — lock records carry tag refs; standards re-pinned to `75bd599` diff --git a/scripts/validate-template.sh b/scripts/validate-template.sh index 06222d4..09f0de8 100755 --- a/scripts/validate-template.sh +++ b/scripts/validate-template.sh @@ -222,7 +222,11 @@ REQUIRED_WORKFLOWS=( # none of the 55 repos carrying it ever adopted Bun. This list was not # updated, so it has required two files the template deliberately no longer # ships — which is why validate-template.sh has been red since that merge. - "runtime-policy.yml" + # runtime-policy.yml is folded into governance in turn (standards + # cicd-regularisation spec §14): "Language / package anti-pattern policy" + # fails any tracked package-lock.json and the "JS runtimes" jobs check each + # lock against package.json. So governance.yml is what this list requires. + "governance.yml" "secret-scanner.yml" ) diff --git a/tests/workflows/actions_lock_sha_refs_test.sh b/tests/workflows/actions_lock_sha_refs_test.sh index 986f94e..34d0c33 100755 --- a/tests/workflows/actions_lock_sha_refs_test.sh +++ b/tests/workflows/actions_lock_sha_refs_test.sh @@ -21,6 +21,14 @@ command -v jq >/dev/null || fail "jq is required" yq --version | grep -q 'github.com/mikefarah/yq/' || fail "mikefarah yq v4 is required" yq -o=json '.' "$LOCK" > "$FIXTURE/lock.json" +# Folding runtime policy into governance must remove both the workflow and +# its lock entry. A valid, fully locked legacy workflow would pass sync alone. +[[ ! -e "$WF_DIR/runtime-policy.yml" && ! -L "$WF_DIR/runtime-policy.yml" ]] \ + || fail "runtime-policy.yml must remain folded into governance" +jq -e '.workflows | has(".github/workflows/runtime-policy.yml") | not' \ + "$FIXTURE/lock.json" >/dev/null || fail "runtime-policy.yml must not retain a lock entry" +echo "PASS: retired runtime-policy workflow and lock entry are absent" + # Use the standards pin actually called by governance, so a later standards # update does not require copying its commit into this test. STANDARDS=$(jq -er ' @@ -83,6 +91,11 @@ while IFS='|' read -r action tag_ref owner_id repo_id; do pinned_record "$key" "$FIXTURE/lock.json" \ || fail "$key must have a symbolic ref and its own SHA as commit" + # Another nonempty symbolic ref also passes pinned_record, but would no + # longer identify the tag assigned to this action by the relock operation. + jq -e --arg key "$key" --arg tag "$tag_ref" ' + .dependencies[$key].ref == $tag + ' "$FIXTURE/lock.json" >/dev/null || fail "$key must retain its relocked tag $tag_ref" jq -e --arg key "$key" --argjson owner "$owner_id" --argjson repo "$repo_id" ' .dependencies[$key] | .owner_id == $owner and .repo_id == $repo ' "$FIXTURE/lock.json" >/dev/null || fail "$key lost its repository identity" @@ -192,3 +205,8 @@ CASES [[ "$checked" == 12 ]] || fail "expected to check all twelve changed dependency records" echo "PASS: actions.lock SHA-ref regression controls ($checked dependencies)" + +# This regression gate already runs in Estate Rules CI. Include the validators' +# migration controls so requiring governance in place of runtime-policy cannot +# regress while the workflow and its lock entry still look correct. +bash "$REPO/tests/workflows/runtime_policy_migration_test.sh" diff --git a/tests/workflows/runtime_policy_migration_test.sh b/tests/workflows/runtime_policy_migration_test.sh new file mode 100755 index 0000000..bc8bc0f --- /dev/null +++ b/tests/workflows/runtime_policy_migration_test.sh @@ -0,0 +1,200 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# PR #13: both validators must require governance in place of runtime-policy. +# Exercise the real scripts against an isolated repository, including negative +# controls. The template validator treats missing workflows as errors; the +# workflow validator reports them as recommendations and still exits zero. +# Usage: bash tests/workflows/runtime_policy_migration_test.sh +set -euo pipefail + +repo="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +scratch=$(mktemp -d) +trap 'rm -rf "$scratch"' EXIT +fixture="$scratch/repository with spaces" +workflows="$fixture/.github/workflows" + +# Report all arguments as a failure message and print any validator logs from +# $scratch to stderr. Terminates the test script with exit status 1. +fail() { + echo "FAIL: $*" >&2 + for log in "$scratch/template.log" "$scratch/workflows.log"; do + if [[ -f "$log" ]]; then cat "$log" >&2; fi + done + exit 1 +} + +# Supply the unrelated structure checks with a minimal complete repository. +# Copy the real deed readers so an early setup error cannot look like the +# expected rejection of a missing governance workflow. +mkdir -p "$workflows" "$fixture/.machine_readable" "$fixture/scripts" \ + "$fixture/src/interface/Abi" "$fixture/src/interface/ffi/src" \ + "$fixture/src/interface/ffi/test" "$fixture/src/interface/generated/abi" \ + "$fixture/docs/developer" "$fixture/docs/governance" +cp "$repo/scripts/deed-field.sh" "$repo/scripts/check-no-a2ml.sh" "$fixture/scripts/" +cat > "$fixture/fixture_chora.deed" <<'DEED' +(repo-deed + (status :phase active) + (maturity :level experimental) + (meta :type library) + (ecosystem :project "fixture") + (anchor :purpose "workflow validation fixture") + (policies (maintenance-axes :corrective-first #t))) +DEED +for file in README.adoc EXPLAINME.adoc LICENSE Justfile AUDIT.adoc \ + TOPOLOGY.adoc CONTRIBUTING.md docs/developer/ABI-FFI-README.adoc \ + src/interface/Abi/Types.idr src/interface/Abi/Layout.idr \ + src/interface/Abi/Foreign.idr src/interface/abi.ipkg \ + src/interface/ffi/build.zig src/interface/ffi/src/main.zig \ + src/interface/ffi/test/integration_test.zig; do + printf '%s\n' '# SPDX-License-Identifier: MPL-2.0' > "$fixture/$file" +done + +# Compiler calls are outside this regression's scope. Bash functions keep the +# test independent of locally installed compilers and create no build outputs. + +# Stub the Zig compiler: return 0 only for the single argument "build"; +# return 1 for any other invocation without compiling or writing files. +zig() { [[ $# == 1 && $1 == build ]]; } + +# Stub Idris2: return 0 only for "--typecheck src/interface/abi.ipkg"; +# return 1 for any other invocation without invoking the real compiler. +idris2() { [[ $# == 2 && $1 == --typecheck && $2 == src/interface/abi.ipkg ]]; } +export -f zig idris2 + +# Write a minimal workflow with an SPDX header and name to the path in $1, +# overwriting any existing file. Returns the status of the printf command. +write_workflow() { + printf '%s\n' '# SPDX-License-Identifier: MPL-2.0' 'name: Fixture' > "$1" +} + +# Keep the fixture's contract independent of the validators' arrays: deriving +# it from their implementation would also copy a regressed requirement. +for workflow in hypatia-scan codeql scorecard quality mirror instant-sync \ + guix-policy security-policy wellknown-enforcement workflow-linter \ + governance secret-scanner; do + write_workflow "$workflows/$workflow.yml" +done +cp "$workflows/governance.yml" "$scratch/governance.yml" + +# Validate the current $fixture against the expected governance presence. +# Arguments: $1 case label, $2 "yes" or "no" for governance.yml presence, +# $3 "both" (default) or "template" to select the validators to check. +# Writes validator logs under $scratch and prints PASS, returning 0 on success; +# calls fail to print diagnostics and exit 1 on any unexpected result. +run_case() { + local label=$1 governance_present=$2 template_status=0 workflow_status=0 + local validators=${3:-both} + local expected_status=0 expected_errors=0 + if [[ "$governance_present" == no ]]; then + expected_status=1 + expected_errors=1 + fi + + # Verbose mode exposes which required workflow was actually resolved. + bash "$repo/scripts/validate-template.sh" "$fixture" 1 \ + > "$scratch/template.log" 2>&1 || template_status=$? + [[ "$template_status" == "$expected_status" ]] \ + || fail "$label: template validator exited $template_status, expected $expected_status" + [[ $(grep -c 'ERROR' "$scratch/template.log" || true) == "$expected_errors" ]] \ + || fail "$label: template validator reported unrelated errors" + + if [[ "$governance_present" == yes ]]; then + grep -Fq 'Workflow found: governance.yml' "$scratch/template.log" \ + || fail "$label: template validator did not recognize governance" + grep -Fq 'Validation PASSED' "$scratch/template.log" \ + || fail "$label: template validator did not reach its passing summary" + else + grep -Fq 'Required workflow missing: governance.yml' "$scratch/template.log" \ + || fail "$label: template rejection did not identify governance" + grep -Fq 'Validation FAILED' "$scratch/template.log" \ + || fail "$label: template validator did not reach its failing summary" + fi + if grep -Eq '(Required workflow missing:|Workflow found:) runtime-policy\.yml' \ + "$scratch/template.log"; then + fail "$label: template validator still requires runtime-policy.yml" + fi + if [[ "$validators" == template ]]; then + echo "PASS: $label (template validator)" + return + fi + + bash "$repo/tests/workflows/validate_workflows_test.sh" "$workflows" \ + > "$scratch/workflows.log" 2>&1 || workflow_status=$? + [[ "$workflow_status" == 0 ]] \ + || fail "$label: workflow recommendations unexpectedly failed validation" + grep -Fq 'Workflow validation PASSED' "$scratch/workflows.log" \ + || fail "$label: workflow validator did not reach its summary" + local expected_found=12 + if [[ "$governance_present" == no ]]; then expected_found=11; fi + grep -Fq "Found $expected_found/12 required workflows" "$scratch/workflows.log" \ + || fail "$label: workflow validator reported an unexpected required-workflow count" + + if [[ "$governance_present" == yes ]]; then + grep -Fq 'Found: governance.yml' "$scratch/workflows.log" \ + || fail "$label: workflow validator did not recognize governance" + if grep -Fq 'Missing: governance.yml' "$scratch/workflows.log"; then + fail "$label: workflow validator also reported governance missing" + fi + else + grep -Fq 'Missing: governance.yml' "$scratch/workflows.log" \ + || fail "$label: workflow validator did not recommend governance" + if grep -Fq 'Found: governance.yml' "$scratch/workflows.log"; then + fail "$label: workflow validator accepted a substitute for governance" + fi + fi + + if grep -Eq '(Missing:|Found:) runtime-policy\.yml' "$scratch/workflows.log"; then + fail "$label: workflow validator still requires runtime-policy.yml" + fi + echo "PASS: $label (both validators)" +} + +run_case 'governance alone satisfies the migrated requirement' yes + +rm "$workflows/governance.yml" +run_case 'neither workflow leaves governance missing' no + +write_workflow "$workflows/runtime-policy.yml" +run_case 'legacy runtime policy cannot substitute for governance' no + +cp "$scratch/governance.yml" "$workflows/governance.yml" +run_case 'governance is recognized when a legacy workflow is also present' yes + +rm "$workflows/runtime-policy.yml" "$workflows/governance.yml" +mkdir "$workflows/governance.yml" +# The workflow validator's generic scan also treats named directories as +# malformed YAML files; isolate this presence boundary to template validation. +run_case 'a directory named governance.yml is not a workflow file' no template +rmdir "$workflows/governance.yml" + +cp "$scratch/governance.yml" "$workflows/governance.yaml" +run_case 'governance.yaml does not satisfy the required governance.yml path' no +rm "$workflows/governance.yaml" + +# The presence check must resolve the exact top-level path, even though the +# workflow validator also scans nested files for generic formatting checks. +mkdir "$workflows/nested" +cp "$scratch/governance.yml" "$workflows/nested/governance.yml" +run_case 'nested governance cannot satisfy the top-level requirement' no +rm -r "$workflows/nested" + +cp "$scratch/governance.yml" "$workflows/Governance.yml" +run_case 'governance workflow names are case-sensitive' no +rm "$workflows/Governance.yml" + +# Match the validators' regular-file contract: a dangling link is missing, +# while a link resolving to a regular workflow file satisfies the requirement. +# As with the directory case, the generic workflow scan rejects the unreadable +# link separately, so isolate the missing-file boundary to template validation. +ln -s missing-governance.yml "$workflows/governance.yml" +run_case 'a dangling governance symlink is missing' no template +rm "$workflows/governance.yml" + +cp "$scratch/governance.yml" "$workflows/governance-target.yml" +ln -s governance-target.yml "$workflows/governance.yml" +run_case 'a governance symlink resolving to a workflow file is accepted' yes +rm "$workflows/governance.yml" "$workflows/governance-target.yml" + +cp "$scratch/governance.yml" "$workflows/governance.yml" +run_case 'restoring governance restores passing validation' yes diff --git a/tests/workflows/runtime_policy_retirement_test.sh b/tests/workflows/runtime_policy_retirement_test.sh new file mode 100755 index 0000000..c7af8b5 --- /dev/null +++ b/tests/workflows/runtime_policy_retirement_test.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# Exercise PR #13's retirement and relocked-tag guards through the real gate. +# A synchronized legacy workflow must still fail the retirement requirement. +# Usage: bash tests/workflows/runtime_policy_retirement_test.sh +# Requires the gate's existing dependencies: mikefarah yq v4, jq and GNU awk. +set -euo pipefail + +repo="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +gate="$repo/tests/workflows/actions_lock_sha_refs_test.sh" +scratch=$(mktemp -d) +trap 'rm -rf "$scratch"' EXIT +workflows="$scratch/workflows with spaces" +mkdir -p "$workflows" +cp -a "$repo/.github/workflows/." "$workflows/" +original_lock="$repo/.github/workflows/actions.lock" + +# Print the captured gate output so a setup failure cannot masquerade as an +# expected rejection from the specific guard being tested. +fail() { + echo "FAIL: $*" >&2 + if [[ -f "$scratch/gate.log" ]]; then cat "$scratch/gate.log" >&2; fi + exit 1 +} + +# Exercise the complete passing gate before introducing one defect at a time. +bash "$gate" "$workflows" > "$scratch/gate.log" 2>&1 \ + || fail 'unmodified workflows must pass the regression gate' +grep -Fq 'PASS: restoring governance restores passing validation' "$scratch/gate.log" \ + || fail 'the lock regression gate must run the governance migration controls' +echo 'PASS: current workflows and lock pass the complete regression gate' + +# Require the intended exit status and diagnostic, rather than accepting any +# failure (for example, an unrelated lock-sync or fixture error). +reject_case() { + local label=$1 diagnostic=$2 status=0 + bash "$gate" "$workflows" > "$scratch/gate.log" 2>&1 || status=$? + [[ "$status" == 1 ]] || fail "$label: expected exit 1, got $status" + grep -Fq "FAIL: $diagnostic" "$scratch/gate.log" \ + || fail "$label: the gate did not identify the intended regression" + echo "PASS: $label rejected by the regression gate" +} + +workflow_error='runtime-policy.yml must remain folded into governance' +lock_error='runtime-policy.yml must not retain a lock entry' + +printf '%s\n' '# SPDX-License-Identifier: MPL-2.0' 'name: Retired workflow' \ + > "$workflows/runtime-policy.yml" +reject_case 'restored regular workflow' "$workflow_error" +rm "$workflows/runtime-policy.yml" + +mkdir "$workflows/runtime-policy.yml" +reject_case 'directory occupying the retired workflow path' "$workflow_error" +rmdir "$workflows/runtime-policy.yml" + +ln -s governance.yml "$workflows/runtime-policy.yml" +reject_case 'retired workflow symlink to an existing workflow' "$workflow_error" +rm "$workflows/runtime-policy.yml" + +# -e alone cannot detect this path; the explicit -L guard must also hold. +ln -s missing-workflow.yml "$workflows/runtime-policy.yml" +reject_case 'dangling retired workflow symlink' "$workflow_error" +rm "$workflows/runtime-policy.yml" + +# A key is stale regardless of its value, including a null value that a +# truthiness check could overlook. Preserve the lock generator's formatting. +for value in '[]' 'null' "['actions/checkout@v7.0.1']"; do + awk -v entry=" '.github/workflows/runtime-policy.yml': $value" ' + { print } + /^workflows:$/ { print entry } + ' "$original_lock" > "$workflows/actions.lock" + reject_case "retired lock entry with value $value" "$lock_error" +done + +# Both sides can be restored consistently, so lock synchronization alone is +# insufficient. This fixture passes the real sync gate before being rejected. +awk ' + { print } + /^workflows:$/ { print " \047.github/workflows/runtime-policy.yml\047: []" } +' "$original_lock" > "$workflows/actions.lock" +printf '%s\n' '# SPDX-License-Identifier: MPL-2.0' 'name: Retired workflow' \ + > "$workflows/runtime-policy.yml" +bash "$repo/scripts/check-lock-sync.sh" "$workflows" > "$scratch/gate.log" 2>&1 \ + || fail 'the synchronized legacy fixture must pass lock synchronization' +reject_case 'synchronized restored workflow and lock entry' "$workflow_error" +rm "$workflows/runtime-policy.yml" + +# A different symbolic ref still satisfies the SHA-ref safety predicate but +# must fail the additional exact-tag assertion introduced by this PR. Select +# the first action checked by the gate so this control stays fast. +cache_key=$(yq -o=json '.' "$original_lock" | jq -er ' + [.dependencies | keys[] | select(test("^actions/cache@[0-9a-f]{40}$"))] + | select(length == 1) | .[0] +') +cache_tag=$(yq -o=json '.' "$original_lock" | jq -er --arg key "$cache_key" \ + '.dependencies[$key].ref') +awk -v header=" '$cache_key':" ' + $0 == header { in_record = 1; print; next } + in_record && /^ ref:/ { + print " ref: \047unexpected-symbolic-ref\047" + in_record = 0 + next + } + { print } +' "$original_lock" > "$workflows/actions.lock" +reject_case 'different nonempty symbolic action ref' \ + "$cache_key must retain its relocked tag $cache_tag" diff --git a/tests/workflows/validate_workflows_test.sh b/tests/workflows/validate_workflows_test.sh index 4e6e321..88553df 100755 --- a/tests/workflows/validate_workflows_test.sh +++ b/tests/workflows/validate_workflows_test.sh @@ -99,10 +99,10 @@ REQUIRED_WORKFLOWS=( "security-policy.yml" "wellknown-enforcement.yml" "workflow-linter.yml" - # Retired in #14, replaced by runtime-policy.yml — see the note in - # scripts/validate-template.sh. Keeping the old names here required two - # files the template no longer ships. - "runtime-policy.yml" + # npm-bun-blocker.yml and ts-blocker.yml were retired in #14 for + # runtime-policy.yml, which governance has since absorbed — see the note in + # scripts/validate-template.sh. + "governance.yml" "secret-scanner.yml" )