Skip to content

Merge pull request #52 from RI-SE/cursor/public-surface-truth #159

Merge pull request #52 from RI-SE/cursor/public-surface-truth

Merge pull request #52 from RI-SE/cursor/public-surface-truth #159

Workflow file for this run

name: ci
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
on:
push:
branches: [main, feature/**, feat/**]
pull_request:
jobs:
required-check-drift:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- name: Required-check context drift
run: python3 scripts/check-required-contexts.py
test:
name: test (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: "1.23.x"
- name: Go test
env:
SOURCE_DATE_EPOCH: "1704067200"
run: go test ./...
- name: Fuzz (short)
if: runner.os != 'Windows'
run: |
go test ./internal/validate/ -fuzz=FuzzSafeJoin -fuzztime=5s
go test ./internal/validate/ -fuzz=FuzzTextForbidRegex -fuzztime=5s
go test ./internal/attest/ -fuzz=FuzzParseHPURLFragment -fuzztime=5s
windows-smoke:
runs-on: windows-latest
needs: test
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: "1.23.x"
- name: Build windows exe
run: |
New-Item -ItemType Directory -Force -Path bin | Out-Null
go build -o bin/curbpack.exe ./cmd/curbpack
- name: Execute exe + spaced-path demo/init
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$bin = Join-Path $env:GITHUB_WORKSPACE "bin\curbpack.exe"
& $bin doctor
$space = Join-Path $env:TEMP "curbpack smoke $(Get-Random)"
New-Item -ItemType Directory -Force -Path $space | Out-Null
& $bin demo --out $space --keep
$one = Join-Path $space "review-pack\buyer-onepager.html"
if (-not (Test-Path $one)) { throw "missing onepager at $one" }
$fix = Join-Path $env:TEMP "curb init $(Get-Random)"
New-Item -ItemType Directory -Force -Path $fix | Out-Null
Push-Location $fix
git init -q
git config user.email "ci@curbpack.local"
git config user.name "CI"
git commit --allow-empty -m init -q
& $bin init --packs house-policy
# Init stubs must stay red (scaffold overlap). Heal/init is not a greenlight.
$checkOut = & $bin check 2>&1 | Out-String
$checkCode = $LASTEXITCODE
Write-Host $checkOut
if ($checkCode -eq 0) { throw "check after house-policy init stubs must stay red (HOUSE-ANTI-PLACEHOLDER)" }
if ($checkOut -notmatch 'HOUSE-ANTI-PLACEHOLDER') { throw "missing HOUSE-ANTI-PLACEHOLDER on red init check" }
if ($checkOut -notmatch 'scaffold body overlap') { throw "missing scaffold body overlap on red init check" }
$repairOut = & $bin doctor --repair 2>&1 | Out-String
Write-Host $repairOut
if ($LASTEXITCODE -ne 0) { throw "doctor --repair failed with exit $LASTEXITCODE" }
if ($repairOut -notmatch 'repair:|Repair done|binary missing') {
throw "doctor --repair did not exercise repair path (no expected status line)"
}
& $bin share --bundle
if (-not (Test-Path "review-pack\evidence-bundle.html")) { throw "missing bundle" }
Pop-Location
exit 0
smoke:
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: "1.23.x"
- name: Build
run: ./scripts/ci-build.sh
- name: Doctor + demo sandbox
run: |
set -euo pipefail
"$GITHUB_WORKSPACE/bin/curbpack" doctor
DEMO=$(mktemp -d)
"$GITHUB_WORKSPACE/bin/curbpack" demo --out "$DEMO" --keep
test -f "$DEMO/review-pack/buyer-onepager.html"
test -f "$DEMO/.curbpack.json"
- name: Spaced-path demo/init smoke
run: |
set -euo pipefail
SPACE=$(mktemp -d "/tmp/curbpack smoke.XXXXXX")
"$GITHUB_WORKSPACE/bin/curbpack" demo --out "$SPACE" --keep
test -f "$SPACE/review-pack/buyer-onepager.html"
FIX=$(mktemp -d "/tmp/curb init.XXXXXX")
cd "$FIX"
git init -q
git config user.email "ci@curbpack.local"
git config user.name "CI"
git commit --allow-empty -m init -q
"$GITHUB_WORKSPACE/bin/curbpack" init --packs house-policy
# Init stubs must stay red (scaffold overlap). Heal/init is not a greenlight.
set +e
CHECK_OUT=$("$GITHUB_WORKSPACE/bin/curbpack" check 2>&1)
CHECK_CODE=$?
set -e
printf '%s\n' "$CHECK_OUT"
test "$CHECK_CODE" -ne 0
printf '%s\n' "$CHECK_OUT" | grep -q 'HOUSE-ANTI-PLACEHOLDER'
printf '%s\n' "$CHECK_OUT" | grep -q 'scaffold body overlap'
# doctor --repair must fail the job on error (no || true)
REPAIR_OUT=$("$GITHUB_WORKSPACE/bin/curbpack" doctor --repair 2>&1)
echo "$REPAIR_OUT"
echo "$REPAIR_OUT" | grep -Eqi 'repair:|Repair done|binary missing'
- name: Offline smoke — CRA fixture
run: |
set -euo pipefail
FIX=$(mktemp -d)
cd "$FIX"
git init -q
git config user.email "ci@curbpack.local"
git config user.name "CI"
git commit --allow-empty -m init -q
"$GITHUB_WORKSPACE/bin/curbpack" init --packs cra-baseline
# Bind annex drafts to a resolvable product token (package.json name).
# CRA-ANNEX-VII-RISK also require_tree_paths README.md (always present post-init/heal in product repos).
printf '%s\n' '# contoso-gateway' > README.md
printf '%s\n' '{"name":"contoso-gateway","version":"1.0.0","dependencies":{}}' > package.json
# Fill annex stubs past placeholders / min sizes
cat > docs/annex-vii/risk_assessment.md <<'EOF'
# Risk Assessment
## Product Overview
The contoso-gateway product forwards telemetry from clinical devices to a hospital EHR over mutually authenticated TLS.
## Identified Risks
| Risk ID | Description | Severity | Mitigation |
|---------|-------------|----------|------------|
| R-001 | Credential stuffing on admin UI | High | MFA + lockout |
## Residual Risk Statement
Residual risk is accepted by the product owner after mitigations above.
EOF
cat > docs/annex-vii/support_period.md <<'EOF'
# Support Period
## End of Support
Security updates for contoso-gateway are provided for five years from the general availability date of each major release.
## Rationale
Aligned with expected clinical deployment lifetime and spare-parts availability.
EOF
cat > docs/annex-vii/user_manual_security.md <<'EOF'
# User Manual — Security
## Secure Configuration
Disable default accounts on contoso-gateway, enforce MFA, and restrict management interfaces to the hospital VLAN.
## Product Disposal
Factory-reset the appliance, shred exported key material, and confirm cloud tenant deletion.
EOF
# CRA-ART14-PATH + CRA-ANTI-PLACEHOLDER: init stub lacks bind_repo_token
# (contoso-gateway) and matches DefaultScaffoldBody. Same product-bound
# prose as testdata/realish/cra-device/docs/incident/art14-path.md.
mkdir -p docs/incident
cat > docs/incident/art14-path.md <<'EOF'
# Art 14 reporting path
## Reporting clock (CRA Art 14)
For contoso-gateway, actively exploited or severe incidents are reported by the on-call owner using the in-repo rehearsal dated 2026-04-12. This is a file record for CRA Article 14 reporting (clock from 11 September 2026, including products already on the market). It is not a live Single Reporting Platform check and does not assert that EU Login works.
## Handling clock (not this file)
Vulnerability handling and public security contact for contoso-gateway are documented separately. They sit on a later clock than Article 14 reporting and are not this rehearsal.
## Named owner
Product security on-call for contoso-gateway owns the reporting path. Escalation is the engineering manager of record in SECURITY.md.
## Rehearsal dated artifact
Last tabletop: 2026-04-12. Record: this file plus the incident mail template under docs/incident/ (in-repo). Not a live submission.
EOF
"$GITHUB_WORKSPACE/bin/curbpack" check
"$GITHUB_WORKSPACE/bin/curbpack" prepare-release
test -f review-pack/buyer-onepager.html
test -f .github/curbpack/cache/latest_action_report.md
- name: Offline smoke — house-policy fixture
run: |
set -euo pipefail
FIX=$(mktemp -d)
cd "$FIX"
git init -q
git config user.email "ci@curbpack.local"
git config user.name "CI"
git commit --allow-empty -m init -q
"$GITHUB_WORKSPACE/bin/curbpack" init --packs house-policy --hooks --skill --ide
test -x .git/hooks/pre-commit
grep -q 'curbpack check' .git/hooks/pre-commit
! grep -q -- '--heal' .git/hooks/pre-commit
# Fail-closed message (ASCII); -aF avoids locale/binary-file grep false negatives
grep -aF 'refusing commit' .git/hooks/pre-commit
# LF-only hooks
! grep -q $'\r' .git/hooks/pre-commit
test -f .cursor/skills/curbpack/SKILL.md
test -f .vscode/tasks.json
# Init stubs must stay red (scaffold overlap). Heal/init is not a greenlight.
set +e
CHECK_OUT=$("$GITHUB_WORKSPACE/bin/curbpack" check 2>&1)
CHECK_CODE=$?
set -e
printf '%s\n' "$CHECK_OUT"
test "$CHECK_CODE" -ne 0
printf '%s\n' "$CHECK_OUT" | grep -q 'HOUSE-ANTI-PLACEHOLDER'
printf '%s\n' "$CHECK_OUT" | grep -q 'scaffold body overlap'
# bare curbpack defaults to check when inited — also honest-red on stubs
set +e
"$GITHUB_WORKSPACE/bin/curbpack"
BARE_CODE=$?
set -e
test "$BARE_CODE" -ne 0
"$GITHUB_WORKSPACE/bin/curbpack" check --form-hints || true
echo '{"name":"demo","dependencies":{"left-pad":"1.3.0"}}' > package.json
"$GITHUB_WORKSPACE/bin/curbpack" prepare-release --allow-failing-gates
test -f .github/curbpack/evidence/sbom.cdx.json
python3 - <<'PY'
import json
d=json.load(open(".github/curbpack/evidence/sbom.cdx.json"))
assert d["bomFormat"]=="CycloneDX" and d["specVersion"]=="1.5"
v=json.load(open(".github/curbpack/evidence/vex-pending.json"))
assert v["status"]=="draft_pending_attest"
print("cyclonedx+vex ok")
PY
git add -A && git -c commit.gpgsign=false commit --no-verify -m "evidence" -q
"$GITHUB_WORKSPACE/bin/curbpack" attest --allow-dirty
test -f .github/curbpack/evidence/hpurl-pointer.json
python3 - <<'PY'
import json
p=json.load(open(".github/curbpack/evidence/hpurl-pointer.json"))
assert p.get("state_hash") and p["state_hash"] in p.get("hpurl","")
print("hpurl pointer ok", p["state_hash"][:12])
PY
# Hook dry-run: still fail-closed after attest (dump on miss)
head -n 5 .git/hooks/pre-commit
grep -aF 'refusing commit' .git/hooks/pre-commit || {
echo "hook missing 'refusing commit' after attest:" >&2
wc -c .git/hooks/pre-commit >&2
cat -v .git/hooks/pre-commit >&2
exit 1
}
H1=$(python3 -c 'import json;print(json.load(open(".github/curbpack/evidence/hpurl-pointer.json"))["state_hash"])')
"$GITHUB_WORKSPACE/bin/curbpack" attest --allow-dirty >/dev/null
H2=$(python3 -c 'import json;print(json.load(open(".github/curbpack/evidence/hpurl-pointer.json"))["state_hash"])')
test "$H1" = "$H2"
- name: Scan read-only smoke (uninitialized repo)
run: |
set -euo pipefail
SCAN=$(mktemp -d)
cd "$SCAN"
git init -q
git config user.email "ci@curbpack.local"
git config user.name "CI"
git commit --allow-empty -m init -q
OUT=$("$GITHUB_WORKSPACE/bin/curbpack" scan 2>&1)
printf '%s\n' "$OUT"
printf '%s\n' "$OUT" | grep -q 'Art 14 reporting clock'
printf '%s\n' "$OUT" | grep -q 'Packs: cra-baseline'
printf '%s\n' "$OUT" | grep -q 'Read-only'
test -z "$(git status --porcelain)"
pin-guard:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0
- name: Pin guard
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
PIN="v0.5.2"
BASE="${{ github.event.pull_request.base.sha || github.event.before }}"
HEAD="${{ github.sha }}"
if [ -z "${BASE:-}" ] || [ "$BASE" = "0000000000000000000000000000000000000000" ]; then
BASE="$(git rev-parse HEAD~1 2>/dev/null || git rev-parse HEAD)"
fi
if ! git diff "$BASE" "$HEAD" -- . | grep -qE "[+-].*${PIN}"; then
echo "pin unchanged — OK"
exit 0
fi
if git log "$BASE..$HEAD" --format=%B | grep -q 'Approve-Pin-Bump:'; then
echo "pin bump approved via trailer — OK"
exit 0
fi
if gh issue list --label tabletop-evidence --state closed --limit 1 --json number --jq '.[0].number' 2>/dev/null | grep -qE '^[0-9]+$'; then
echo "closed tabletop-evidence issue present — OK"
exit 0
fi
echo "pin literal changed without tabletop-evidence or Approve-Pin-Bump trailer" >&2
exit 1
redteam-pilot:
# Stable check name for branch protection: merges to main require this green.
name: redteam-pilot
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: "1.23.x"
- name: Build
run: ./scripts/ci-build.sh
- name: Redteam pilot scoreboard (required)
run: ./scripts/redteam-pilot.sh
gauntlet:
runs-on: ubuntu-latest
needs: [test, redteam-pilot]
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: "1.23.x"
- name: Build
run: ./scripts/ci-build.sh
- name: Claim safety (docs + runtime)
run: ./scripts/claim-safety.sh
- name: Public links and social-card assets
run: python3 scripts/check-public-assets.py
- name: Release reference integrity
run: ./scripts/release-ref-test.sh
- name: Installer version tag validation
run: ./scripts/install-version-test.sh
- name: Manifest / release asset parity
run: |
set -euo pipefail
python3 - <<'PY'
import json, pathlib, re, sys
root = pathlib.Path(".")
man = json.loads((root/"scripts/install-manifest.json").read_text())
gate = json.loads((root/"scripts/release-gate.json").read_text())
assets = set(man["assets"])
assert "curbpack_windows_amd64.exe" in assets
assert man["default_version"].startswith("v")
assert gate.get("schema") == "curbpack-release-gate:1", gate
assert gate["version"] == man["default_version"], (
f"manifest default_version {man['default_version']!r} must equal "
f"release-gate version {gate['version']!r} before public pins advance"
)
for k in ("assets_verified", "checksums_verified", "install_smoke_verified", "scan_write_free_verified"):
assert gate.get(k) is True, f"release-gate {k} must be true"
rel = (root/".github/workflows/release.yml").read_text()
# release must build windows amd64 exe
assert "windows/amd64" in rel or "windows_amd64" in rel, "release.yml missing windows amd64"
assert "curbpack_windows_amd64.exe" in rel or 'windows/amd64' in rel
sh = (root/"scripts/install.sh").read_text()
assert "install-manifest.json" in sh or "default_version" in sh or "v0.5.2" in sh
assert "cd /path/to/your/git/repo" in sh and "curbpack scan" in sh
assert "curbpack demo" in sh
ps1 = (root/"scripts/install.ps1").read_text()
assert "curbpack_windows_amd64.exe" in ps1
assert "-Repair" in ps1 or "Repair" in ps1
assert "cd /path/to/your/git/repo" in ps1 and "curbpack scan" in ps1
# Action remains Linux/macOS only honesty (real assert — no or True)
action = (root/"action.yml").read_text()
assert "Linux/macOS" in action, "action.yml must state Linux/macOS runners"
assert "not Windows runners" in action, "action.yml must state not Windows runners"
print("manifest/release-gate/install parity ok", man["default_version"], sorted(assets))
PY
- name: Heal smoke + baseline ratchet + dead-ends
run: ./scripts/gauntlet-ratchet.sh
- name: Install-from-release smoke (fail closed)
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
# Pin to this tree's advertised default so advertise PRs (main still older)
# and post-merge main-default smoke both assert the correct binary.
export CURBPACK_VERSION="$(python3 -c 'import json; print(json.load(open("scripts/install-manifest.json"))["default_version"])')"
./scripts/release-smoke-install-scan.sh
- name: Dead-end — corrupt pack env
run: |
set -euo pipefail
BAD=$(mktemp -d)
mkdir -p "$BAD/broken"
echo '{not json' > "$BAD/broken/pack.json"
FIX=$(mktemp -d)
cd "$FIX"
git init -q
git config user.email "ci@curbpack.local"
git config user.name "CI"
git commit --allow-empty -m init -q
set +e
CURBPACK_PACKS_DIR="$BAD" "$GITHUB_WORKSPACE/bin/curbpack" init --packs broken
code=$?
set -e
test "$code" -ne 0
- name: Chaos dead-ends
run: ./scripts/chaos-deadends.sh "$GITHUB_WORKSPACE/bin/curbpack"