Skip to content

CI: stop charging documentation changes for a .NET restore - #1712

Merged
erikdarlingdata merged 2 commits into
devfrom
feature/ci-docs-fastpath
Jul 26, 2026
Merged

CI: stop charging documentation changes for a .NET restore#1712
erikdarlingdata merged 2 commits into
devfrom
feature/ci-docs-fastpath

Conversation

@erikdarlingdata

Copy link
Copy Markdown
Owner

Makes non-executable changes stop paying for a .NET restore in the required build job, while keeping the check reporting so it can never go missing and block a merge.

First, a correction to the brief's premise

The brief said the root catch-all drags CHANGELOG.md into full builds. It does not — root is four specific files (PerformanceMonitor.sln, global.json, nuget.config, .github/workflows/build.yml) and no glob there matches a .md file. I checked before changing it, so root is untouched.

I also checked the claim empirically rather than by reading, and markdown-only changes were already fast-pathing correctly:

PR Files build
#1707 CHANGELOG.md, tools/ag-fixture/VALIDATION.md — pure .md 1m43s
#1708 CHANGELOG.md + DarlingSelfAlertEvaluator.cs 6m58s

#1708 looked like a docs PR and is what makes the problem seem worse than it is — it contains a .cs file, so its 6m58s was a legitimate build. #1707 is the true control: 1m43s, which is actions/checkout plus the paths filter and nothing after it. The existing code: ['**', '!**/*.md'] filter was already doing its job.

The real gap is non-markdown documentation. LICENSE, CITATION.cff, .gitignore, .gitattributes, docs/signpath_logo.svg, and everything under Screenshots/ are not .md, so they matched the ** catch-all, set code=true, and paid a six-project --locked-mode restore to compile nothing. That is what this PR fixes.

The gate

An explicit allowlist of non-executable content, not a subtraction of known-code paths:

**/*.md   LICENSE   CITATION.cff   .gitignore   .gitattributes   docs/**   Screenshots/**

Allowlist direction matters: an unfamiliar new file type defaults to being treated as code, which is the safe way to be wrong. A subtraction would default new file types to "documentation" and silently stop building them.

Deliberately not in the allowlist, per the brief and verified against what each job consumes: *.sql (the installer and sql-validation.yml compile it), *.yml (workflows), *.csproj / *.props / packages.lock.json (build inputs), and *.cs however comment-only a change looks — an XML doc comment still recompiles, and the compiler is what proves it still builds.

Screenshots/** and CITATION.cff are additions beyond the brief's list. Both are unambiguously non-executable and both are real categories in this repo (Screenshots/ is PNGs; CITATION.cff is citation metadata). Flagging them explicitly since they were my call, and either is a one-line removal.

Two guards against silently under-building

Never on release, never on a push to dev/main. Those restore unconditionally. Worth being precise about the scope: this only forces the restore back on — the per-product build/test/publish steps stay path-gated exactly as they were, so push times for code commits are unchanged. I did not make dev pushes rebuild everything; that would have been a much larger behavioural change than this job asked for, and it would slow every push.

Both jobs now say what they did and why. The build job emits a ::notice:: naming the reason (release / branch push / docs-only / code changed) and, on the fast path, lists every file it classified as documentation via list-files: shell. The Darling PostgreSQL tests job gets the same treatment — its gate was already correct for documentation, but a job reporting success having quietly run nothing is indistinguishable from one that tested everything, and now it isn't.

Scope notes

  • darling-pg's gate is unchanged — it was already correct (Darling/** minus Darling/**/*.md), so a docs-only change already no-ops every step. It gained only the reporting step. Note its filter deliberately includes .github/workflows/build.yml, which is why this PR runs the full TimescaleDB suite against itself rather than skipping.
  • sql-validation.yml untouched — not a required check and already paths-scoped, as specified.
  • The docs allowlist and the code filter's exclusions are the same list stated twice, because paths-filter has no "not in that other filter" operator. They carry a comment to keep them in step, and I added a check that they mirror exactly.

Validation

YAML parses (yaml.safe_load)                      OK
docs allowlist vs code exclusions                 IN SYNC (7/7, verified programmatically)
docs allowlist contains no build input            clean (no .sql/.yml/.csproj/.props/.cs/.json)
both new bash blocks                              bash -n syntax OK
steps moved onto the new gate                     3 (Setup .NET, Restore, Get version)
steps left on the old `code != 'false'` gate      0

Per step 4 of the brief, this PR is itself code (.yml), so it correctly takes the full path — including the Darling PG suite, since build.yml is in that job's filter. After it merges I'll open a throwaway docs-only PR (a one-sentence README edit), confirm the build check completes fast and green with the fast-path notice visible in its log, post the timing as a comment here, and close the throwaway.

🤖 Generated with Claude Code

Non-executable changes now skip .NET setup, restore and versioning in the
required build job. The job still RUNS, so the check reports a result and
cannot go missing and block a merge.

The gate is an explicit ALLOWLIST of non-executable content, not a
subtraction of known-code paths: **/*.md, LICENSE, CITATION.cff,
.gitignore, .gitattributes, docs/**, Screenshots/**. An unfamiliar new
file type therefore defaults to being treated as code, which is the safe
direction to be wrong in.

Deliberately NOT in the allowlist: *.sql (the installer and
sql-validation compile it), *.yml (workflows), *.csproj / *.props /
packages.lock.json (build inputs), and *.cs however comment-only a change
looks - an XML doc comment still recompiles, and the compiler is what
proves it still builds.

Two guards against silently under-building:

- The fast path never engages on a release event, or on a push to
  dev/main. Those restore unconditionally. Note this only forces the
  RESTORE back on; the per-product build/test steps stay path-gated
  exactly as before, so push times are unchanged for code commits.
- Both jobs emit a ::notice:: naming why they took the path they took,
  and the build job lists the files it classified as documentation. A job
  reporting success having quietly run nothing is otherwise
  indistinguishable from one that tested everything.

On what this actually fixes: markdown-only changes were ALREADY skipping
every heavy step - #1707 was pure .md and its build ran in 1m43s, which
is checkout and the paths filter with nothing after it. The real gap was
non-markdown documentation, which matched the '**' catch-all in the code
filter and paid a six-project locked-mode restore to compile nothing. The
remaining ~1m45s floor is actions/checkout on a Windows runner and is not
something this gate can remove.

The docs allowlist and the code filter's exclusions are the same list
stated twice - paths-filter has no "not in that other filter" operator -
so they carry a comment to keep them in step.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@erikdarlingdata
erikdarlingdata disabled auto-merge July 26, 2026 20:24
@erikdarlingdata
erikdarlingdata merged commit 02593dd into dev Jul 26, 2026
4 checks passed
@erikdarlingdata
erikdarlingdata deleted the feature/ci-docs-fastpath branch July 26, 2026 21:06
pull Bot pushed a commit to ehtick/PerformanceMonitor that referenced this pull request Jul 29, 2026
…rrors it

Ratifies erikdarlingdata#1712's two flagged judgment calls (CITATION.cff and the
Screenshots/ entry stay on the documentation allowlist - citation
metadata and screenshot images cannot affect any build.yml job) but
replaces the bare directory globs docs/** and Screenshots/** with
extension-explicit patterns for markdown, SVG, and image files. Both
directories hold exactly those types today, so nothing classified as
documentation before this commit stops being documentation - but a .sql
or script dropped into either directory tomorrow now defaults to being
CODE, which restores the allowlist's own stated principle (an unfamiliar
file type should default to the safe direction) that a directory-wide
grant quietly undermined.

check-version-bump.yml gets the same allowlist as exclusions: its gate
still said *.md-only, so a LICENSE or .gitignore correction in a dev to
main PR would demand a version bump while build.yml calls the same diff
documentation. The two filters now state the same policy and say so.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pull Bot pushed a commit to ehtick/PerformanceMonitor that referenced this pull request Jul 29, 2026
The 2026-07-26 08:02 action-pin bump moved dorny/paths-filter v3 to v4.
v4 evaluates every filter pattern as an INDEPENDENT predicate under the
default predicate-quantifier 'some' (a filter is true when any changed
file matches at least one rule), so a bare '!**/*.md' line stopped
being a subtraction and became its own rule: 'any file that is not
markdown'. Every area filter ending in that line went true for ANY
non-markdown change anywhere in the repo, and the code: filter ('**'
plus exclusions) could never be false again.

Measured consequences, straight from run logs:
- darling-pg ran the full TimescaleDB suite on every PR since the bump,
  including md-only ones. Run 30218459544 (PR erikdarlingdata#1709, two .md files):
  'Filter darling = true, Matching files: CHANGELOG.md' - CHANGELOG.md
  matched '!Darling/**/*.md' by virtue of not being a Darling markdown.
- A single root .gitignore edit built and tested every product: run
  30219202642 (throwaway PR erikdarlingdata#1714) lit lite, dashboard, AND darling,
  each reporting 'Matching files: .gitignore' via its '!**/*.md' rule.
- The erikdarlingdata#1712 docs fast path shipped unable to engage: every changed file
  matches '**', so code: was always true and setup/restore always ran.
  Its measured 1m43s md-only runs were real but came from the area
  filters (markdown matches no include), not from the fast path.

The fix keeps v4 (v3 is on the deprecated-runtime track) and removes
every bare negation instead:
- Area filters state the markdown carve-out INSIDE each include as an
  extglob - 'Darling/**/!(*.md)' - where quantifier semantics cannot
  detach it. The positive-only docs allowlist needed no change.
- The uninvertible code: filter becomes an all: counter. The classify
  step decides docs-only by all_count == docs_count, and additionally
  refuses to engage while any area filter is lit, because an area build
  step with restore skipped would run dotnet build --no-restore against
  nothing. Counts and areas derive from the same allowlist today; the
  veto exists for the day someone edits one and not the other.
- check-version-bump.yml had the identical '**'-plus-exclusions shape,
  so its md-only skip had also silently stopped working. Same counter
  fix, same allowlist, and its gate now says why.

Validation: probe commit on throwaway PR erikdarlingdata#1714 (this build.yml plus a
Darling .txt probe and a Darling .md probe) makes the filter log a
per-file truth table; results recorded in the CI-intelligence PR body.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant