From 918b0fffa8e2422d3514ac53ea65f0bed225d2d2 Mon Sep 17 00:00:00 2001
From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com>
Date: Thu, 24 Sep 2026 07:26:18 -0400
Subject: [PATCH 1/2] ci: docs-only changes skip CI (Erik's ruling, 2026-09-24)
- Add llms.txt (the exact root path, not **/*.txt) to the docs allowlist.
- Remove README.md and Lite/README.md from the lite and lite_shard filters
(kept byte-equal by CrossAppGuardCiGateTests), so a README-only PR no
longer runs the Lite suite. LiteRuntimePrerequisiteDocsTests now runs
on every code PR and in the nightly instead.
- darling-tree-guards no longer runs the whole Darling suite when the
build job's own classify step engaged the docs fast path; the build
job now publishes that decision as a job output (docs-fastpath)
alongside its existing darling-tests outcome output.
- push and merge_group no longer force the restore back on for a
docs-only change: nothing that restore validates (lock files,
Directory.Packages.props, global.json) is reachable from a change
that is entirely on the docs allowlist, since all of those sit on
the root filter which vetoes the fast path.
---
.github/workflows/build.yml | 59 ++++++++++---------
.../LiteRuntimePrerequisiteDocsTests.cs | 12 ++--
2 files changed, 38 insertions(+), 33 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index fb2f83a5b..3d65b6dc1 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -1295,6 +1296,7 @@ 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
@@ -1302,6 +1304,9 @@ jobs:
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."
@@ -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)'
diff --git a/Lite.Tests/LiteRuntimePrerequisiteDocsTests.cs b/Lite.Tests/LiteRuntimePrerequisiteDocsTests.cs
index 1a22ea81a..08a99c4f1 100644
--- a/Lite.Tests/LiteRuntimePrerequisiteDocsTests.cs
+++ b/Lite.Tests/LiteRuntimePrerequisiteDocsTests.cs
@@ -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: README.md and Lite/README.md are named
-/// explicitly in build.yml's lite filter. They have to be. Every area filter carves markdown
-/// out (dir/**/!(*.md)), 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: README.md and Lite/README.md were once
+/// named explicitly in build.yml's lite 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.
///
public sealed class LiteRuntimePrerequisiteDocsTests
{
From 66e7661bfbd431f33cd12e46641716d90f228925 Mon Sep 17 00:00:00 2001
From: Erik Darling <2136037+erikdarlingdata@users.noreply.github.com>
Date: Thu, 24 Sep 2026 07:26:59 -0400
Subject: [PATCH 2/2] probe: docs-only fast path check for #4163 (throwaway,
will be closed)
---
README.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/README.md b/README.md
index e0f86136b..4eba277da 100644
--- a/README.md
+++ b/README.md
@@ -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)
+
+