From 3942d506b36937e82600cf68207b2aa776c1e198 Mon Sep 17 00:00:00 2001 From: svelderrainruiz Date: Sun, 29 Mar 2026 08:24:14 -0700 Subject: [PATCH] template: add governance checks and promotion gate --- .github/workflows/policy-guard-upstream.yml | 67 +++++++++++++++++++++ .github/workflows/promotion-contract.yml | 47 +++++++++++++++ README.md | 5 +- docs/CONSUMER_PROVING_RAIL.md | 4 +- docs/policy/develop-branch-protection.json | 4 +- 5 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/policy-guard-upstream.yml create mode 100644 .github/workflows/promotion-contract.yml diff --git a/.github/workflows/policy-guard-upstream.yml b/.github/workflows/policy-guard-upstream.yml new file mode 100644 index 0000000..2b2aeb2 --- /dev/null +++ b/.github/workflows/policy-guard-upstream.yml @@ -0,0 +1,67 @@ +name: Policy Guard (Upstream) + +on: + push: + branches: + - develop + pull_request: + workflow_dispatch: + +jobs: + policy-guard: + name: policy-guard + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Validate develop branch protection contract + shell: bash + run: | + node <<'EOF' + const fs = require('fs'); + + const contractPath = 'docs/policy/develop-branch-protection.json'; + const contract = JSON.parse(fs.readFileSync(contractPath, 'utf8')); + const expectedContexts = [ + 'comparevi-consumer-smoke (ubuntu-latest)', + 'comparevi-consumer-smoke (windows-latest)', + 'render (ubuntu-latest, hosted)', + 'render (ubuntu-latest, docker)', + 'render (ubuntu-latest, mixed)', + 'render (windows-latest, hosted)', + 'render (windows-latest, docker)', + 'render (windows-latest, mixed)', + 'Policy Guard (Upstream) / policy-guard', + 'Promotion Contract / promotion-contract' + ]; + + function fail(message) { + console.error(message); + process.exit(1); + } + + if (contract.branch !== 'develop') { + fail(`Expected branch contract for develop, found ${contract.branch}`); + } + if (contract.required_status_checks?.strict !== true) { + fail('Expected strict required status checks for develop.'); + } + + const configured = [...(contract.required_status_checks?.contexts || [])].sort(); + const expected = [...expectedContexts].sort(); + if (JSON.stringify(configured) !== JSON.stringify(expected)) { + fail( + `Develop branch protection contexts drifted.\nExpected: ${expected.join(', ')}\nActual: ${configured.join(', ')}` + ); + } + + for (const workflowPath of [ + '.github/workflows/template-smoke.yml', + '.github/workflows/policy-guard-upstream.yml', + '.github/workflows/promotion-contract.yml' + ]) { + if (!fs.existsSync(workflowPath)) { + fail(`Missing workflow required by develop protection contract: ${workflowPath}`); + } + } + EOF diff --git a/.github/workflows/promotion-contract.yml b/.github/workflows/promotion-contract.yml new file mode 100644 index 0000000..9bc545d --- /dev/null +++ b/.github/workflows/promotion-contract.yml @@ -0,0 +1,47 @@ +name: Promotion Contract + +on: + push: + branches: + - develop + pull_request: + workflow_dispatch: + +jobs: + promotion-contract: + name: promotion-contract + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Validate comparevi stable pin contract + shell: bash + run: | + node <<'EOF' + const fs = require('fs'); + + function read(filePath) { + return fs.readFileSync(filePath, 'utf8'); + } + + function ensureIncludes(filePath, expected) { + const content = read(filePath); + if (!content.includes(expected)) { + throw new Error(`Expected ${filePath} to include: ${expected}`); + } + } + + const cookiecutter = JSON.parse(read('cookiecutter.json')); + const pin = cookiecutter.comparevi_tools_consumer_pin; + if (!/^v\d+\.\d+\.\d+$/.test(pin)) { + throw new Error(`Expected stable comparevi pin, found ${pin}`); + } + + ensureIncludes('.github/workflows/template-smoke.yml', `-CompareViPin '${pin}'`); + ensureIncludes('.github/workflows/template-smoke.yml', `uses: LabVIEW-Community-CI-CD/compare-vi-cli-action@${pin}`); + ensureIncludes('.github/workflows/template-smoke.yml', `pinned_ref: \`LabVIEW-Community-CI-CD/compare-vi-cli-action@${pin}\``); + ensureIncludes('tests/Test-TemplateSmokeRender.ps1', `[string]$CompareViPin = '${pin}'`); + ensureIncludes('README.md', `comparevi_tools_consumer_pin="${pin}"`); + ensureIncludes('docs/VI_HISTORY_CAPABILITY_DISTRIBUTION.md', `The default upstream consumer pin is \`${pin}\`.`); + ensureIncludes('{{ cookiecutter.repo_slug }}/README.md', `capability contract, such as \`${pin}\` or a later supported stable \`v0.6.x\``); + EOF diff --git a/README.md b/README.md index 065465c..d5c5f39 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,10 @@ Future agents should treat the canonical template repo this way: - inspect open issues first - inspect the latest supported `template-smoke` state - treat `docs/policy/develop-branch-protection.json` as the checked-in - contract for canonical `develop` required checks + contract for canonical `develop` required checks, including the governance + checks that keep branch protection and promotion-pin drift visible +- treat the canonical `production` environment as the promotion-time + acknowledgement surface; routine smoke workflows stay machine-gated - if no eligible issue exists, remain in monitoring mode - if an eligible issue exists, use that issue as the top objective diff --git a/docs/CONSUMER_PROVING_RAIL.md b/docs/CONSUMER_PROVING_RAIL.md index 1cafba9..7fbb066 100644 --- a/docs/CONSUMER_PROVING_RAIL.md +++ b/docs/CONSUMER_PROVING_RAIL.md @@ -35,8 +35,10 @@ standing-priority work. - `template-smoke` on `push`, `pull_request`, and `workflow_dispatch` is the authoritative template-self-validation surface - canonical `develop` branch protection now requires the full - `template-smoke` matrix recorded in + `template-smoke` matrix plus the canonical governance checks recorded in `docs/policy/develop-branch-protection.json` + - canonical promotions should route through the checked-in `production` + environment so human-gated release acknowledgement is explicit - generated execution-profile contract - `hosted` is the default generated profile - `docker` and `mixed` are accepted template inputs diff --git a/docs/policy/develop-branch-protection.json b/docs/policy/develop-branch-protection.json index 4ce1f74..83410b2 100644 --- a/docs/policy/develop-branch-protection.json +++ b/docs/policy/develop-branch-protection.json @@ -11,7 +11,9 @@ "render (ubuntu-latest, mixed)", "render (windows-latest, hosted)", "render (windows-latest, docker)", - "render (windows-latest, mixed)" + "render (windows-latest, mixed)", + "Policy Guard (Upstream) / policy-guard", + "Promotion Contract / promotion-contract" ] }, "required_linear_history": true,