Skip to content
Closed
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
59 changes: 31 additions & 28 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,14 @@ jobs:
# the suite when this reads `skipped`; a second list of paths there would be free to fall out
# of agreement with the list here, and area lists falling out of agreement with what a guard
# reads is the thing that job exists to stop.
#
# docs-fastpath is published the same way, from the classify step's OWN decision rather than a
# second copy of the docs allowlist: darling-tree-guards must not pay ~5 minutes running the whole
# suite on a docs-only change just because that change also left Darling.Tests `skipped` - the two
# skip reasons look identical from `outcome` alone, so the guards job needs to tell them apart.
outputs:
darling-tests: ${{ steps.darling-tests.outcome }}
docs-fastpath: ${{ steps.fastpath.outputs.engaged }}

steps:
- uses: actions/checkout@v7
Expand Down Expand Up @@ -127,18 +133,14 @@ jobs:
lite:
- 'Lite/**/!(*.md)'
- 'Lite.Tests/**/!(*.md)'
# #2489: LiteRuntimePrerequisiteDocsTests derives Lite's .NET runtime prerequisites
# from the BUILT runtimeconfig and from the publish shapes in this very file, then
# asserts these two READMEs say so - which artifact is self-contained, which needs two
# runtimes. Naming them here is not optional. Every area filter carves markdown out
# (dir/**/!(*.md)), and a docs-only PR additionally engages the fast path above, which
# skips .NET setup and restore outright - so without these two entries the guard could
# not run on a README-only change, i.e. on exactly the edit that would undo the fix.
# The cost is that a root-README edit now runs the Lite suite; that is the same trade
# the Darling entries below already take, and the alternative is a guard that quietly
# stops guarding.
- 'README.md'
- 'Lite/README.md'
# #2489, changed by Erik's 2026-09-24 ruling that docs-only changes skip CI:
# README.md and Lite/README.md used to sit here so LiteRuntimePrerequisiteDocsTests
# (which reads these two READMEs against the built runtimeconfig and this file's
# publish shapes) could still run on a README-only change. That guard now runs on
# every CODE pull request and in the nightly instead - both of which build Lite -
# so a README edit alone no longer has to pay the Lite suite to keep it exercised.
# A README-only PR takes the docs fast path below and does not build or test
# anything; the trade is deliberate and is Erik's call, not an oversight.
# Same silently-stops-guarding reason as the darling filter's Lite entries below:
# Lite.Tests/ThemeParityLiteDarlingTests.cs READS the Darling viewer's theme
# dictionaries to assert the two apps' shared brush keys still resolve to the same
Expand Down Expand Up @@ -230,6 +232,10 @@ jobs:
- 'CITATION.cff'
- '.gitignore'
- '.gitattributes'
# The exact root path, not '**/*.txt': Darling/Darling.Tests/McpToolsListBudget/*.txt
# (and Lite's copy) are test INPUTS, not documentation, and a bare extension glob
# would wave every budget-pin edit through the fast path unbuilt and untested.
- 'llms.txt'
- 'docs/**/*.{md,svg,png,jpg,jpeg,gif}'
- 'Screenshots/**/*.{md,svg,png,jpg,jpeg,gif}'
# Catch-all COUNTER, not a boolean gate: the classify step below decides
Expand All @@ -247,20 +253,21 @@ jobs:
# it off every path where a skipped restore would be a real loss:
# release — the filter step does not even run there, and a release must always
# compile and publish from a cold, fully restored tree.
# push — dev/main pushes are the integration signal for what just merged, so
# they restore unconditionally even for a docs-only commit. Cheap
# insurance: this only forces the restore back on, it does not force
# the per-product build/test steps, which stay path-gated as before.
# merge_group — a queue run is the LAST validation before its result lands on dev,
# so it takes the same always-restore path as a push.
# areas — belt and suspenders: even when the counts say docs-only, any lit
# area filter vetoes the fast path, because an area=true with restore
# skipped would run `dotnet build --no-restore` against nothing. The
# two classifications are built from the same allowlist so they cannot
# disagree today; this guard is for the day someone edits one and not
# the other.
# Everything else (pull_request) is eligible, and engages only when EVERY changed
# file is on the documentation allowlist (all_count == docs_count).
# Everything else — including push and merge_group, per Erik's 2026-09-24 ruling —
# is eligible, and engages only when EVERY changed file is on the documentation
# allowlist (all_count == docs_count). push/merge_group used to force the restore
# back on unconditionally as "cheap insurance" against NuGet drift that a lock-file
# diff would not show; that insurance buys nothing on a docs-only commit specifically
# BECAUSE it is docs-only — no lock file, Directory.Packages.props, or global.json is
# among the changed files (all of those are `root`, which is off the docs allowlist
# and vetoes the fast path via the areas guard above), so nothing this restore
# validates could have moved since the push that last validated it.
- name: Classify change for the docs fast path
id: fastpath
shell: bash
Expand All @@ -278,12 +285,6 @@ jobs:
exit 0
fi

if [ "${{ github.event_name }}" = "push" ] || [ "${{ github.event_name }}" = "merge_group" ]; then
echo "engaged=false" >> "$GITHUB_OUTPUT"
echo "::notice title=Full build::${{ github.event_name }} on '${{ github.ref_name }}' - integration runs always restore, even for a docs-only change."
exit 0
fi

echo "Changed files: ${ALL_COUNT:-0} total, ${DOCS_COUNT:-0} on the documentation allowlist. Areas: ${AREAS}"

if [ "${ALL_COUNT:-0}" -gt 0 ] && [ "${ALL_COUNT:-0}" -eq "${DOCS_COUNT:-0}" ] && [[ "${AREAS}" != *"=true"* ]]; then
Expand Down Expand Up @@ -1295,13 +1296,17 @@ jobs:
shell: bash
env:
DARLING_TESTS: ${{ needs.build.outputs.darling-tests }}
DOCS_FASTPATH: ${{ needs.build.outputs.docs-fastpath }}
WORKFLOW: ${{ steps.filter.outputs.workflow }}
run: |
set -euo pipefail

if [ "${{ github.event_name }}" = "release" ]; then
echo "run=false" >> "$GITHUB_OUTPUT"
echo "::notice title=Whole-tree guards skipped::Release event - the build job compiles and runs every suite unconditionally."
elif [ "${DOCS_FASTPATH:-}" = "true" ]; then
echo "run=false" >> "$GITHUB_OUTPUT"
echo "::notice title=Whole-tree guards skipped::The build job took the docs fast path - every changed file is on the documentation allowlist, so there is no code change for the whole-tree guards to catch. They still run on the next code PR and in the nightly."
elif [ "${DARLING_TESTS:-}" = "skipped" ]; then
echo "run=true" >> "$GITHUB_OUTPUT"
echo "::notice title=Whole-tree guards running::The build job's path filters left Darling.Tests unrun, so the guards that read the whole repository run here."
Expand Down Expand Up @@ -1413,8 +1418,6 @@ jobs:
lite_shard:
- 'Lite/**/!(*.md)'
- 'Lite.Tests/**/!(*.md)'
- 'README.md'
- 'Lite/README.md'
- 'Darling/PerformanceMonitor.Darling.Viewer/**/!(*.md)'
- 'Darling/Darling.Tests/**/!(*.md)'
- 'Darling/PerformanceMonitor.Darling.Service/**/!(*.md)'
Expand Down
12 changes: 7 additions & 5 deletions Lite.Tests/LiteRuntimePrerequisiteDocsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,13 @@ namespace Lite.Tests;
/// A framework this file has no mapping for is still a hard failure, because an undocumentable
/// prerequisite is exactly the state that produced #2489.
///
/// Note for whoever touches the CI path filters: <c>README.md</c> and <c>Lite/README.md</c> are named
/// explicitly in build.yml's <c>lite</c> filter. They have to be. Every area filter carves markdown
/// out (<c>dir/**/!(*.md)</c>), and a docs-only PR additionally engages the fast path that skips .NET
/// setup entirely — so without those entries this guard would be unrunnable on precisely the change it
/// exists to catch.
/// Note for whoever touches the CI path filters: <c>README.md</c> and <c>Lite/README.md</c> were once
/// named explicitly in build.yml's <c>lite</c> filter for exactly this reason — a docs-only PR takes
/// the fast path that skips .NET setup entirely, so a README-only change could not reach this guard
/// without them. Erik's 2026-09-24 ruling ended that trade: a README-only PR now takes the docs fast
/// path and does not run this guard at all. It still runs on every CODE pull request (which builds
/// Lite) and in the nightly, so a docs edit that quietly breaks the parity this file checks is caught
/// there instead of at PR time.
/// </summary>
public sealed class LiteRuntimePrerequisiteDocsTests
{
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -763,3 +763,5 @@ Copyright (c) 2026 Darling Data, LLC. Licensed under the MIT License. See [LICEN
## Author

Erik Darling — [erikdarling.com](https://erikdarling.com) — [Darling Data, LLC](https://darlingdata.com)

<!-- probe: docs-only fast path check, PR #4163 -->
Loading