Skip to content

docs(spec): add spec 048 — lazy, trimmable control registration #1004

docs(spec): add spec 048 — lazy, trimmable control registration

docs(spec): add spec 048 — lazy, trimmable control registration #1004

Workflow file for this run

name: CI
on:
pull_request:
push:
branches: [main]
jobs:
changes:
name: Detect changes
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
outputs:
non-md: ${{ steps.filter.outputs.non-md }}
docs-templates: ${{ steps.filter.outputs.docs-templates }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Detect non-Markdown changes
id: filter
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PUSH_BEFORE: ${{ github.event.before }}
PUSH_SHA: ${{ github.sha }}
run: |
set +e
ZERO_SHA="0000000000000000000000000000000000000000"
if [ -n "$BASE_SHA" ] && [ -n "$HEAD_SHA" ]; then
# Three-dot mirrors GitHub's PR diff (merge-base..head).
files=$(git diff --name-only "$BASE_SHA...$HEAD_SHA")
rc=$?
elif [ -n "$PUSH_BEFORE" ] && [ -n "$PUSH_SHA" ] && [ "$PUSH_BEFORE" != "$ZERO_SHA" ]; then
files=$(git diff --name-only "$PUSH_BEFORE" "$PUSH_SHA")
rc=$?
else
echo "No valid diff range; defaulting to non-md=true and docs-templates=true"
echo "non-md=true" >> "$GITHUB_OUTPUT"
echo "docs-templates=true" >> "$GITHUB_OUTPUT"
exit 0
fi
if [ "$rc" -ne 0 ]; then
echo "git diff failed; defaulting to non-md=true and docs-templates=true"
echo "non-md=true" >> "$GITHUB_OUTPUT"
echo "docs-templates=true" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "Changed files:"
echo "$files"
if [ -z "$files" ] || echo "$files" | grep -qv '\.md$'; then
echo "non-md=true" >> "$GITHUB_OUTPUT"
else
echo "non-md=false" >> "$GITHUB_OUTPUT"
fi
# Spec 041 §5.2: tier-drift triggers when a template or doc-app
# changes. Templates live under docs/_pipeline/templates/, apps
# under docs/_pipeline/apps/, and the doc-pipeline CLI itself
# under src/Reactor.Cli/Docs/.
if echo "$files" | grep -qE '^(docs/_pipeline/templates/|docs/_pipeline/apps/|src/Reactor\.Cli/Docs/)'; then
echo "docs-templates=true" >> "$GITHUB_OUTPUT"
else
echo "docs-templates=false" >> "$GITHUB_OUTPUT"
fi
unit-tests:
name: Unit Tests
needs: changes
if: needs.changes.outputs.non-md == 'true'
runs-on: windows-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup .NET
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
dotnet-version: 10.0.x
- name: Restore
run: dotnet restore tests/Reactor.Tests/Reactor.Tests.csproj -p:Platform=x64
- name: Test
run: dotnet test tests/Reactor.Tests/Reactor.Tests.csproj --no-restore -p:Platform=x64 --logger "console;verbosity=normal"
integration-tests:
name: Integration Tests
needs: changes
if: needs.changes.outputs.non-md == 'true'
runs-on: windows-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup .NET
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
dotnet-version: 10.0.x
- name: Restore
run: dotnet restore Reactor.slnx
- name: Test
run: dotnet test tests/Reactor.IntegrationTests/Reactor.IntegrationTests.csproj --no-restore --logger "console;verbosity=normal"
selftests:
name: Selftests
needs: changes
if: needs.changes.outputs.non-md == 'true'
runs-on: windows-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup .NET
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
dotnet-version: 10.0.x
- name: Restore
run: dotnet restore tests/Reactor.SelfTests/Reactor.SelfTests.csproj -p:Platform=x64
- name: Test
run: dotnet test tests/Reactor.SelfTests/Reactor.SelfTests.csproj --no-restore -p:Platform=x64 --logger "console;verbosity=normal"
build-solution:
name: Build solution
needs: changes
if: needs.changes.outputs.non-md == 'true'
runs-on: windows-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup .NET
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
dotnet-version: 10.0.x
- name: Restore
run: dotnet restore Reactor.slnx
- name: Build
run: dotnet build Reactor.slnx --no-restore --configuration Release
fuzz-smoke:
name: Fuzz smoke
needs: changes
if: needs.changes.outputs.non-md == 'true'
runs-on: windows-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup .NET
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
dotnet-version: 10.0.x
# Fuzz-free pass: walk the seed corpus once through both harness bodies.
# Guards against harness rot (parser API drift, broken seed inputs) without
# needing the libfuzzer-dotnet driver or SharpFuzz instrumentation tool in
# the CI image. Using `dotnet run` keeps the invocation independent of the
# TFM / RID / Platform output-path layout, which has changed twice already.
- name: Smoke run
run: dotnet run --project tests/Reactor.Fuzz/Reactor.Fuzz.csproj -c Release -p:Platform=x64 -- smoke
vulnerable-packages:
name: Vulnerable packages
needs: changes
if: needs.changes.outputs.non-md == 'true'
runs-on: windows-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup .NET
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
dotnet-version: 10.0.x
- name: Restore
run: dotnet restore Reactor.slnx
# Fail the build on transitive vulnerable packages at High/Critical
# severity. `dotnet list package --vulnerable` exits 0 regardless of
# findings (the table is text output), so we parse the severity column
# ourselves. Matches `> Package … High` / `Critical` rows; Moderate/Low
# surface as warnings only.
- name: Check for vulnerable packages
shell: pwsh
run: |
$output = (dotnet list Reactor.slnx package --vulnerable --include-transitive 2>&1 | Out-String)
$listExit = $LASTEXITCODE
Write-Host $output
# If the tool itself failed (restore error, NU* error, broken sdk),
# we cannot trust the empty-findings result — fail loudly rather
# than silently green-light the gate.
if ($listExit -ne 0) {
Write-Error "dotnet list package --vulnerable failed (exit $listExit). Cannot verify vulnerability status."
exit $listExit
}
$bad = $output -split "`n" | Where-Object {
$_ -match '^\s*>\s+\S+' -and $_ -match '\b(High|Critical)\b'
}
if ($bad) {
Write-Error "High or Critical vulnerable package(s) detected:`n$($bad -join "`n")"
exit 1
}
Write-Host "No High/Critical vulnerable packages."
aot-selftests:
name: AOT Selftests
needs: changes
if: needs.changes.outputs.non-md == 'true'
runs-on: windows-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup .NET
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
dotnet-version: 10.0.x
# Publish the selftest host with NativeAOT. PublishAotInternal=true,
# Platform=x64, and rid=win-x64 are the canonical knobs documented in
# docs/aot-support.md and used by tests/Reactor.AppTests.Host/probe-aot-skips.ps1.
# `-o` pins the publish folder so the run step doesn't depend on the
# default TFM/RID/Platform path layout.
- name: Publish AOT host
run: >
dotnet publish tests/Reactor.AppTests.Host
-p:PublishAotInternal=true
-p:Platform=x64
-r win-x64
-c Release
-o ${{ runner.temp }}/aot-publish
--nologo
# Run the published exe directly (not via `dotnet test`) so we exercise
# the NativeAOT binary. SelfTestRunner.DefaultAotSkipPatterns gates the
# known reflection-bound failures (40 fixtures across Devtools/MCP,
# PropertyGrid auto-discovery, Issue142 XAML metadata, and two framework
# cases); any *new* failure surfaces as exit code 1 and fails the job.
# See docs/aot-support.md for the skip-list debugging workflow.
- name: Run AOT selftests
shell: pwsh
run: |
$exe = Join-Path '${{ runner.temp }}/aot-publish' 'Reactor.AppTests.Host.exe'
if (-not (Test-Path $exe)) {
Write-Error "AOT-published host not found at $exe"
exit 1
}
& $exe --self-test 2>&1 | Tee-Object -FilePath aot-selftest-output.tap
exit $LASTEXITCODE
- name: Upload TAP output
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: aot-selftest-tap
path: aot-selftest-output.tap
if-no-files-found: ignore
docs-check-tier:
name: Docs tier-drift
needs: changes
if: needs.changes.outputs.docs-templates == 'true'
runs-on: windows-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup .NET
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
dotnet-version: 10.0.x
# Spec 041 §5.2 — fast tier-drift gate. Fails when a template's
# declared tier no longer matches the §11 structural checklist. The
# narrower `check-tier` surface runs in ~seconds vs. the full
# `docs compile` (no build, capture, diagrams, or reference-gen).
# `--ci` is not yet flipped on: the existing 24 W001
# winui-ref-not-declared warnings are intentional noise on
# internals/meta pages and are being addressed separately as a
# Phase 5 lint-quality item. Errors (REACTOR_DOC_TIER_001..012)
# still fail the job. See docs/contributing/doc-pipeline.md §8.
- name: Tier-lint
run: dotnet run --project src/Reactor.Cli -- docs check-tier
docs-build:
name: Docs build
needs: changes
if: needs.changes.outputs.non-md == 'true'
runs-on: windows-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup .NET
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
dotnet-version: 10.0.x
- name: Compile docs
run: dotnet run --project src/Reactor.Cli -- docs compile --no-screenshots --ci
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: pip
cache-dependency-path: docs/requirements.txt
- name: Install MkDocs
run: pip install -r docs/requirements.txt
# Strict link/anchor check against the docs just compiled above,
# mirroring the Pages publish workflow (.github/workflows/docs.yml).
# `--strict` turns a broken cross-link or missing nav file into a
# failure, so it fails the PR instead of the post-merge publish (the
# only place --strict ran before). Folded into this job so there is a
# single docs build.
- name: mkdocs build --strict
run: python -m mkdocs build --strict