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) + +