Skip to content
Open
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
79 changes: 79 additions & 0 deletions .github/workflows/guardrail-gate-nightly.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: Guardrail Gate (Nightly Drift Check)

on:
schedule:
- cron: "17 3 * * *"
workflow_dispatch:

permissions:
contents: read

jobs:
guardrail-nightly:
name: Guardrail Gate Nightly
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install PowerShell (Ubuntu)
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y wget apt-transport-https software-properties-common
wget -q https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
sudo apt-get update
sudo apt-get install -y powershell

- name: Generate lane artifacts
shell: pwsh
run: |
pwsh scripts/lanes/check-infra.ps1
pwsh scripts/lanes/check-sec.ps1
pwsh scripts/lanes/check-web-build.ps1
pwsh scripts/lanes/check-data.ps1
pwsh scripts/lanes/check-obs.ps1
pwsh scripts/lanes/check-perf.ps1
pwsh scripts/lanes/check-a11y.ps1
pwsh scripts/lanes/check-gov.ps1

- name: Run guardrail enforcer
shell: pwsh
run: pwsh -File scripts/run-guardrail-enforcer.ps1 -OutDir ai/out

- name: Append report to job summary
if: always()
shell: pwsh
run: |
if (Test-Path "ai/out/GUARDRAIL_ENFORCER_REPORT.md") {
$content = Get-Content "ai/out/GUARDRAIL_ENFORCER_REPORT.md" -Raw
Write-Host "$content"
echo "$content" >> $env:GITHUB_STEP_SUMMARY
} else {
Write-Host "Report missing"
}

- name: Upload artifacts
if: failure() || success()
uses: actions/upload-artifact@v4
with:
name: guardrail-artifacts-nightly-${{ matrix.os }}
path: |
ai/out/LANE_*.md
ai/out/GUARDRAIL_ENFORCER_REPORT.md

- name: Slack notify on failure (optional)
if: failure() && env.SLACK_WEBHOOK_URL != ''
uses: slackapi/slack-github-action@v1.24.0
with:
payload: |
{
"text": "Nightly Guardrail Gate failed on ${{ matrix.os }} for ${{ github.repository }}@${{ github.ref }}. See artifacts and report."
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

80 changes: 80 additions & 0 deletions .github/workflows/guardrail-gate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: Guardrail Gate (PR & Push)

on:
push:
branches: ["**"]
pull_request:
branches: ["**"]

permissions:
contents: read

jobs:
guardrail:
name: Guardrail Gate
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install PowerShell (Ubuntu)
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y wget apt-transport-https software-properties-common
wget -q https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
sudo apt-get update
sudo apt-get install -y powershell

- name: Generate lane artifacts
shell: pwsh
run: |
pwsh scripts/lanes/check-infra.ps1
pwsh scripts/lanes/check-sec.ps1
pwsh scripts/lanes/check-web-build.ps1
pwsh scripts/lanes/check-data.ps1
pwsh scripts/lanes/check-obs.ps1
pwsh scripts/lanes/check-perf.ps1
pwsh scripts/lanes/check-a11y.ps1
pwsh scripts/lanes/check-gov.ps1

- name: Run guardrail enforcer
shell: pwsh
run: pwsh -File scripts/run-guardrail-enforcer.ps1 -OutDir ai/out

- name: Append report to job summary
if: always()
shell: pwsh
run: |
if (Test-Path "ai/out/GUARDRAIL_ENFORCER_REPORT.md") {
$content = Get-Content "ai/out/GUARDRAIL_ENFORCER_REPORT.md" -Raw
Write-Host "$content"
echo "$content" >> $env:GITHUB_STEP_SUMMARY
} else {
Write-Host "Report missing"
}

- name: Upload artifacts
if: failure() || success()
uses: actions/upload-artifact@v4
with:
name: guardrail-artifacts-${{ matrix.os }}
path: |
ai/out/LANE_*.md
ai/out/GUARDRAIL_ENFORCER_REPORT.md

- name: Slack notify on failure (optional)
if: failure() && env.SLACK_WEBHOOK_URL != ''
uses: slackapi/slack-github-action@v1.24.0
with:
payload: |
{
"text": "Guardrail Gate failed on ${{ matrix.os }} for ${{ github.repository }}@${{ github.ref }}. See artifacts and report."
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

11 changes: 11 additions & 0 deletions ai/out/GUARDRAIL_ENFORCER_REPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Guardrail Enforcer Report

OutDir: ai/out
Generated: 2025-11-27 20:10:10

Lanes:
- LANE_INFRA: INFRA_OK
- LANE_SEC: SEC_OK


Decision: RELEASE_GO
10 changes: 10 additions & 0 deletions ai/out/LANE_INFRA.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Lane: INFRA
Date: 2025-11-27 20:10:09

Summary:
INFRA baseline OK (placeholder).

Checks:
- Placeholder pass

Result: INFRA_OK
10 changes: 10 additions & 0 deletions ai/out/LANE_SEC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Lane: SEC
Date: 2025-11-27 20:10:10

Summary:
SEC baseline OK (placeholder).

Checks:
- Placeholder pass

Result: SEC_OK
Binary file added docs/guardrail-gate.md
Binary file not shown.
17 changes: 17 additions & 0 deletions scripts/lanes/_lane-helper.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
function New-Lane {
param([Parameter(Mandatory=$true)][string]$Name,[Parameter(Mandatory=$true)][string]$Summary)
$lanePath = "ai/out/LANE_{0}.md" -f $Name
@"
# Lane: $Name
Date: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")

Summary:
$Summary

Checks:
- Placeholder pass

Result: ${Name}_OK
"@ | Set-Content $lanePath
}

2 changes: 2 additions & 0 deletions scripts/lanes/check-a11y.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
. "$(Join-Path $PSScriptRoot '_lane-helper.ps1')"
New-Lane -Name "A11Y" -Summary "A11Y baseline OK (placeholder)."
2 changes: 2 additions & 0 deletions scripts/lanes/check-data.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
. "$(Join-Path $PSScriptRoot '_lane-helper.ps1')"
New-Lane -Name "DATA" -Summary "DATA baseline OK (placeholder)."
2 changes: 2 additions & 0 deletions scripts/lanes/check-gov.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
. "$(Join-Path $PSScriptRoot '_lane-helper.ps1')"
New-Lane -Name "GOV" -Summary "GOV baseline OK (placeholder)."
2 changes: 2 additions & 0 deletions scripts/lanes/check-infra.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
. "$(Join-Path $PSScriptRoot '_lane-helper.ps1')"
New-Lane -Name "INFRA" -Summary "INFRA baseline OK (placeholder)."
2 changes: 2 additions & 0 deletions scripts/lanes/check-obs.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
. "$(Join-Path $PSScriptRoot '_lane-helper.ps1')"
New-Lane -Name "OBS" -Summary "OBS baseline OK (placeholder)."
2 changes: 2 additions & 0 deletions scripts/lanes/check-perf.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
. "$(Join-Path $PSScriptRoot '_lane-helper.ps1')"
New-Lane -Name "PERF" -Summary "PERF baseline OK (placeholder)."
2 changes: 2 additions & 0 deletions scripts/lanes/check-sec.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
. "$(Join-Path $PSScriptRoot '_lane-helper.ps1')"
New-Lane -Name "SEC" -Summary "SEC baseline OK (placeholder)."
2 changes: 2 additions & 0 deletions scripts/lanes/check-web-build.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
. "$(Join-Path $PSScriptRoot '_lane-helper.ps1')"
New-Lane -Name "WEB_BUILD" -Summary "WEB_BUILD baseline OK (placeholder)."
42 changes: 42 additions & 0 deletions scripts/run-guardrail-enforcer.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
param([string]$OutDir = $env:GUARDRAIL_OUT_DIR)

if (-not $OutDir) { $OutDir = "ai/out" }

$ErrorActionPreference = "Stop"

if (-not (Test-Path $OutDir)) { New-Item -ItemType Directory -Force -Path $OutDir | Out-Null }

$laneFiles = Get-ChildItem -Path $OutDir -Filter "LANE_*.md" -ErrorAction SilentlyContinue

$results = @()

foreach ($file in $laneFiles) {
$text = Get-Content $file.FullName -Raw
$match = [regex]::Match($text, "(?m)^Result:\s*(\S+)")
if ($match.Success) { $results += [PSCustomObject]@{ Lane = $file.BaseName; Result = $match.Groups[1].Value } }
else { $results += [PSCustomObject]@{ Lane = $file.BaseName; Result = "UNKNOWN" } }
}

$overall = "RELEASE_GO"

foreach ($r in $results) { if ($r.Result -notmatch "OK|RELEASE_GO") { $overall = "RELEASE_HOLD"; break } }

$report = @"
# Guardrail Enforcer Report

OutDir: $OutDir
Generated: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")

Lanes:
$(
$results | ForEach-Object { "- $($_.Lane): $($_.Result)" } | Out-String
)

Decision: $overall
"@

$reportPath = Join-Path $OutDir "GUARDRAIL_ENFORCER_REPORT.md"
Set-Content -Path $reportPath -Value $report

if ($overall -eq "RELEASE_GO") { exit 0 } else { Write-Error "Release gated: $overall"; exit 1 }