Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .github/workflows/policy-guard-upstream.yml
Original file line number Diff line number Diff line change
@@ -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
47 changes: 47 additions & 0 deletions .github/workflows/promotion-contract.yml
Original file line number Diff line number Diff line change
@@ -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
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 3 additions & 1 deletion docs/CONSUMER_PROVING_RAIL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion docs/policy/develop-branch-protection.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading