Files
reverse-skill/.github/workflows/ci.yml
T
Edison 06a6a5e6bf test(ci): wire remaining unwired suites (#97 rebase) (#111)
* test(ci): wire remaining unwired suites (rebase of #97)

Run test-p0-friction.ps1 on Windows PS 5.1 routing-tests.
Run case-review/tests/test_review_case.py in Linux case-contract.
Title-safety is already on main; do not duplicate that step.

* docs(changelog): note remaining CI suite wiring
2026-08-22 13:36:46 +08:00

343 lines
14 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# reverse-skill CI:路由回归 + 结构一致性 + 供应链 pin gate + 冒烟
# 矩阵:windows-latest(原生 powershell 5.1) + ubuntu-latest(pwsh + powershell shim)
# 触发:所有分支(含 fork 的改进分支),PR 也触发
name: CI
on:
push:
pull_request:
jobs:
routing-tests:
name: routing tests (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [windows-latest, ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# 脚本内部以 `powershell` 调用子进程;Linux runner 只有 pwsh,做个 shim
- name: powershell shim (linux)
if: runner.os == 'Linux'
shell: bash
run: sudo ln -sf "$(command -v pwsh)" /usr/local/bin/powershell
- name: Routing regression (benchmark)
shell: pwsh
run: ./skills/scripts/test-routing.ps1
- name: Routing coherence + supply-chain pin gate
shell: pwsh
run: ./skills/scripts/verify-routing-coherence.ps1
- name: Bootstrap supply-chain regression
shell: pwsh
run: ./skills/scripts/test-bootstrap-supply-chain.ps1
- name: Bootstrap supply-chain regression (Windows PowerShell 5.1)
if: runner.os == 'Windows'
shell: powershell
run: ./skills/scripts/test-bootstrap-supply-chain.ps1
- name: Parse contracts (route-scope + IDA lock)
shell: pwsh
run: ./skills/scripts/test-parse-contracts.ps1
- name: Journal PR title safety
shell: pwsh
run: ./skills/scripts/test-workflow-title-safety.ps1
- name: P0 friction regression (Windows PowerShell 5.1)
if: runner.os == 'Windows'
shell: powershell
run: ./skills/scripts/test-p0-friction.ps1
- name: Client-neutral bootstrap/discovery (Windows PowerShell 5.1)
if: runner.os == 'Windows'
shell: powershell
run: ./skills/scripts/test-client-neutral-bootstrap.ps1
- name: Offline sample case contract (Windows PowerShell 5.1)
if: runner.os == 'Windows'
shell: powershell
run: |
$scratch = Join-Path $env:RUNNER_TEMP ("reverse-skill-offline-" + [guid]::NewGuid().ToString('n'))
New-Item -ItemType Directory -Force -Path $scratch | Out-Null
$sample = Join-Path $scratch 'sample.apk'
Set-Content -Path $sample -Value 'fixture' -Encoding ASCII
./skills/scripts/case-init.ps1 `
-Hint "offline apk" `
-CaseName "offline-sample" `
-ProjectRoot $scratch `
-Preset offline-sample `
-Sample $sample
$scope = Join-Path $scratch 'work/offline-sample/scope.md'
$raw = Get-Content $scope -Raw
if ($raw -notmatch '(?m)^- mode: offline\r?$') { throw 'offline sample did not keep offline network mode' }
if ($raw -notmatch '(?m)^- ready_for_act: true\r?$') { throw 'offline sample did not become ready_for_act' }
./skills/scripts/case-guard.ps1 -CaseRoot (Join-Path $scratch 'work/offline-sample')
try {
./skills/scripts/case-init.ps1 `
-Hint "missing offline apk" `
-CaseName "missing-sample" `
-ProjectRoot $scratch `
-Preset offline-sample `
-Sample (Join-Path $scratch 'missing.apk')
throw 'missing offline sample was accepted'
} catch {
if ($_.Exception.Message -eq 'missing offline sample was accepted') { throw }
}
./skills/scripts/case-init.ps1 `
-Hint "pending offline apk" `
-CaseName "force-auth" `
-ProjectRoot $scratch `
-Sample $sample
& powershell -NoProfile -ExecutionPolicy Bypass -File ./skills/scripts/case-guard.ps1 `
-CaseRoot (Join-Path $scratch 'work/force-auth') `
-Force
$guardExit = $LASTEXITCODE
if ($guardExit -eq 0) { throw '-Force bypassed auth.status hard gate' }
$global:LASTEXITCODE = 0
- name: Smoke (verify + parse + quick route)
shell: pwsh
run: ./skills/scripts/smoke.ps1
- name: INDEX.md up-to-date check
shell: pwsh
run: ./skills/scripts/extract-summaries.ps1 -Check
- name: All JSON manifests valid
shell: pwsh
run: |
Get-Content skills/scripts/bootstrap-manifest.json -Raw -Encoding UTF8 | ConvertFrom-Json | Out-Null
Get-Content kali/scripts/bootstrap-manifest.json -Raw -Encoding UTF8 | ConvertFrom-Json | Out-Null
Get-Content skills/config/routing.json -Raw -Encoding UTF8 | ConvertFrom-Json | Out-Null
Get-Content skills/tests/routing-benchmark.json -Raw -Encoding UTF8 | ConvertFrom-Json | Out-Null
Write-Host "All JSON valid"
- name: Doc fact tables vs manifest (verify-doc-facts)
shell: pwsh
run: ./skills/scripts/verify-doc-facts.ps1
sh-syntax:
name: shell script syntax check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: bash -n all .sh
shell: bash
run: |
set -e
while IFS= read -r f; do
bash -n "$f"
echo "syntax OK: $f"
done < <(git ls-files '*.sh')
- name: Client-neutral bootstrap/discovery (Bash)
shell: bash
run: bash skills/scripts/test-client-neutral-bootstrap.sh
- name: Structured routing parity (Bash)
shell: bash
run: |
set -euo pipefail
bash skills/scripts/test-routing.sh
bash skills/scripts/test-bootstrap-manifest.sh
scratch="$(mktemp -d)"
trap 'rm -rf "$scratch"' EXIT
# Fresh Linux journey: no pwsh required, artifacts stay in caller project.
caller="$scratch/caller-project"
mkdir -p "$caller"
printf 'fixture' > "$scratch/sample.apk"
(
cd "$caller"
bash "$GITHUB_WORKSPACE/skills/scripts/master-route.sh" --hint "offline apk"
bash "$GITHUB_WORKSPACE/skills/scripts/case-init.sh" \
--hint "offline apk" \
--case-name "caller-default" \
--preset offline-sample \
--sample "$scratch/sample.apk"
)
test -f "$caller/work/caller-default/scope.md"
grep -Eq '^- project_root: .*/caller-project$' "$caller/work/caller-default/scope.md"
grep -Eq '^- mode: offline$' "$caller/work/caller-default/scope.md"
grep -Eq '^- ready_for_act: true$' "$caller/work/caller-default/scope.md"
bash skills/scripts/case-guard.sh --case-root "$caller/work/caller-default"
test ! -e "$GITHUB_WORKSPACE/work/caller-default"
if bash skills/scripts/case-init.sh \
--hint "missing offline apk" \
--case-name "missing-sample" \
--project-root "$caller" \
--preset offline-sample \
--sample "$scratch/missing.apk"; then
echo "missing offline sample was accepted" >&2
exit 1
fi
# Compatibility: legacy --package-root still pins the work root.
bash skills/scripts/case-init.sh \
--hint "authorized web review" \
--case-name "network-default" \
--package-root "$scratch/project" \
--auth-granted \
--target-url "https://example.test/"
grep -Eq '^- mode: authorized_target_only$' "$scratch/project/work/network-default/scope.md"
grep -Eq '^- ready_for_act: true$' "$scratch/project/work/network-default/scope.md"
bash skills/scripts/case-guard.sh --case-root "$scratch/project/work/network-default"
if bash skills/scripts/case-init.sh \
--hint "offline apk" \
--case-name "../case-escape" \
--package-root "$scratch/project" \
--preset offline-sample \
--sample "$scratch/sample.apk"; then
echo "case-init accepted an unsafe case name" >&2
exit 1
fi
if bash skills/scripts/case-init.sh \
--hint "authorized web review" \
--case-name "invalid-network" \
--package-root "$scratch/project" \
--auth-granted \
--network-profile "internet" \
--target-url "https://example.test/"; then
echo "case-init accepted an unsupported network profile" >&2
exit 1
fi
bash skills/scripts/case-init.sh \
--hint "authorized web review" \
--case-name "uppercase-network" \
--package-root "$scratch/project" \
--auth-granted \
--network-profile "AUTHORIZED_TARGET_ONLY" \
--target-url "https://example.test/"
grep -Eq '^- mode: authorized_target_only$' "$scratch/project/work/uppercase-network/scope.md"
bash skills/scripts/case-init.sh \
--hint "pending review" \
--case-name "guard-section" \
--package-root "$scratch/project" \
--target-url "https://example.test/"
cat >> "$scratch/project/work/guard-section/scope.md" <<'FAKE_FIELDS'
## notes
- status: granted
- mode: authorized_target_only
- ready_for_act: true
FAKE_FIELDS
if bash skills/scripts/case-guard.sh --case-root "$scratch/project/work/guard-section"; then
echo "case-guard accepted fields outside their contract sections" >&2
exit 1
fi
# --force is compatibility-only and must not bypass the hard auth gate.
(
cd "$caller"
bash "$GITHUB_WORKSPACE/skills/scripts/case-init.sh" \
--hint "pending offline apk" \
--case-name "force-auth" \
--sample "$scratch/sample.apk"
)
if bash skills/scripts/case-guard.sh --case-root "$caller/work/force-auth" --force; then
echo "case-guard --force bypassed auth.status hard gate" >&2
exit 1
fi
- name: PowerShell syntax check (PSParser, all tracked .ps1)
shell: pwsh
run: |
$files = git ls-files '*.ps1'
if (-not $files) { Write-Host 'No .ps1 files tracked'; exit 0 }
$failed = 0
foreach ($f in $files) {
$tokens = $null
$errors = $null
[System.Management.Automation.Language.Parser]::ParseFile((Resolve-Path $f), [ref]$tokens, [ref]$errors) | Out-Null
if ($errors -and $errors.Count -gt 0) {
$failed++
Write-Host "::error file=$f::$($errors.Count) parse error(s)"
foreach ($e in $errors) {
Write-Host (" L{0}:{1} {2}" -f $e.Extent.StartLineNumber, $e.Extent.StartColumnNumber, $e.Message)
}
} else {
Write-Host "syntax OK: $f"
}
}
if ($failed -gt 0) { exit 1 }
- name: Non-ASCII .ps1 must carry a UTF-8 BOM (Windows PowerShell 5.1 safety)
shell: pwsh
run: |
# PS 5.1 parses a BOM-less .ps1 as the system ANSI codepage, garbling
# Chinese/em-dash string literals. Every non-ASCII script MUST have a BOM.
$files = git ls-files '*.ps1'
if (-not $files) { Write-Host 'No .ps1 files tracked'; exit 0 }
$bad = 0
foreach ($f in $files) {
$bytes = [System.IO.File]::ReadAllBytes((Resolve-Path $f))
$hasNonAscii = $false
foreach ($b in $bytes) { if ($b -ge 0x80) { $hasNonAscii = $true; break } }
if (-not $hasNonAscii) { continue }
$hasBom = $bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF
if ($hasBom) {
Write-Host "BOM OK: $f"
} else {
$bad++
Write-Host "::error file=$f::non-ASCII .ps1 lacks a UTF-8 BOM; add one so literals survive Windows PowerShell 5.1"
}
}
if ($bad -gt 0) { Write-Host "$bad file(s) need a UTF-8 BOM"; exit 1 }
Write-Host 'All non-ASCII .ps1 carry a UTF-8 BOM'
leak-scan:
name: field-journal leak scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Scan field-journal for un-anonymized secrets
shell: pwsh
run: ./skills/scripts/scan-leaks.ps1 -Path skills/field-journal
case-contract:
name: case contract test (ctf-demo)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Review examples/ctf-demo under strict contract
shell: bash
run: python3 skills/case-review/scripts/review_case.py examples/ctf-demo --verify-hashes --strict
- name: review_case.py unit tests
shell: bash
run: python3 skills/case-review/tests/test_review_case.py
version-check:
name: version consistency
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: VERSION matches latest CHANGELOG release
shell: pwsh
run: |
$v = (Get-Content VERSION -Raw).Trim()
$cl = Get-Content CHANGELOG.md -Raw
$m = [regex]::Match($cl, '(?m)^## \[(\d+\.\d+\.\d+)\]')
if (-not $m.Success) { Write-Error 'No version header found in CHANGELOG'; exit 1 }
$latest = $m.Groups[1].Value
if ($v -ne $latest) {
Write-Error "VERSION ($v) does not match latest CHANGELOG release ($latest)"
exit 1
}
Write-Host "Version OK: $v"