Skip to content

fix: make the required check measure what we actually depend on (#1472, #1491, #1568) - #1575

Merged
rbuergi merged 2 commits into
mainfrom
fix/ci-gate-hardening
Aug 14, 2026
Merged

rbuergi merged 2 commits into
mainfrom
fix/ci-gate-hardening

Conversation

@rbuergi

@rbuergi rbuergi commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Three filings, one theme: the required status check reported more confidence than it had.

#1472 — a shard that never ran left the check green

A codeload outage made setup-dotnet unfetchable and shard 0 never started. The five surviving shards produced trx with no failures, the existing evidence gate saw a non-zero trx count, and Consolidate test results — the repo's only required check — reported success on five sixths of the suite. mergeStateStatus was merely UNSTABLE, which does not block.

"No results because the shard never started" and "no failures" are different inputs, and counting failures cannot tell them apart.

  • New Every shard must have reported step compares reporters against the statically-known matrix size (CI_SHARD_TOTAL: 6) and names the missing shards.
  • New Fail if any shard job did not succeed step covers the same defect from the other side (a shard that failed to start, was cancelled, or died before uploading).
  • 🚨 Neither uses an if: on artifact presence and neither is continue-on-error. The only condition is the reuse path — a run mode, not a property of the evidence.

#1491 — three honest greens, none covering the tree that went red

A run that reuses a green tree marker concludes success without executing anything. So "last green main run" ≠ "last main run that executed the tests", and git log <last-green>..<red> produced a window containing exactly one PR — the wrong one.

  • Marker refs carry provenance: refs/ci-green/<tree>/<epoch>-<run_id>. Readers split on the first -; legacy bare-epoch markers still parse, and the prune parser was updated to match.
  • A reusing run's summary names the run that actually gathered the evidence, and says plainly that this run executed no tests.
  • A main run whose suite genuinely ran and passed records refs/ci-executed/main/<epoch>-<run_id> at its commit.
  • A red run then STATES its bisect window from that ref (git log <sha>..<red>), instead of leaving it to be reconstructed from a run list that cannot distinguish the two kinds of green.

Neither weakens the reuse — it stays a real ~22 min saving and it is correct. They make the diagnostic honest about what a given green covers.

#1568 — a red Clients workflow stopped all image publication

PR #1555 merged with the entire Clients workflow red, including Portal-next (typecheck + test + build) — the same npm run build that main-cd's portal-next image leg runs. promote is all-or-nothing, so CD published nothing for ~1 h and a production 42P18 fix was stranded.

Clients cannot simply be marked required: it is path-filtered, so on a server-only PR it never runs, and a legitimately-skipped required check blocks merges forever (exactly why Build solution (once) and the shards are not required). So this adds the shape the issue itself prescribes — a small always-runs job that reports the filtered workflow's result:

  • clients-gate resolves the Clients workflow's conclusion for the PR head sha and fails on anything but success/skipped.
  • Zero runs ⇒ the path filter did not match, i.e. this PR changes none of those inputs. That is a fact about the diff, categorically different from the skip-trapdoor AGENTS.md rules out (an if: asking whether a secret is set). The job always runs, always reports, and never tests its own inputs.
  • 🚨 A zero is only believed after a 180 s creation-grace window, so it cannot race workflow-run creation — an immediate zero read as "not applicable" would have silently re-opened the hole.

The "narrower alternative" in the issue — main-cd failing loudly — already exists (alert-on-failure files a ci-failure issue, verify-images asserts the complete four-image set). The missing half was the merge gate; that is what this adds.

Verification

  • actionlint clean against the origin/main baseline — the one remaining SC2016 info is pre-existing (same step, unchanged).
  • bash -n over every run: block in the workflow (GH expressions stripped) — clean.
  • YAML parses; job graph is precheck, preflight, build, test, plugin-gate, clients-gate, doc-gate, licence-gate → collect-results.
  • This PR touches only dotnet-test.yml, so clients-gate exercises its own not-applicable branch on this very run.

No What's New entry: CI-internal, no user-visible effect.

Fixes #1472, #1491, #1568

🤖 Generated with Claude Code

https://claude.ai/code/session_01KmBFaHTqhy7h742wkAaJKj

…d on

Three ways the one required status check reported more confidence than it had.

#1472 — a shard that never ran left the check green. On 2026-08-14 a codeload
outage made setup-dotnet unfetchable and shard 0 never started; the five
surviving shards produced trx with no failures, the evidence gate saw a
non-zero trx count, and `Consolidate test results` reported success on five
sixths of the suite. "No results because it never started" and "no failures"
are different inputs and counting failures cannot tell them apart. The
consolidation now counts REPORTERS against the statically-known matrix size
(CI_SHARD_TOTAL) and names the missing shards, plus a direct check on the
matrix job's own result. No `if:` on artifact presence, no continue-on-error —
the only condition is the reuse path, which is a run mode, not evidence.

#1491 — three honest greens, none covering the tree that went red. A run that
reuses a green marker concludes success without executing anything, so "last
green main run" is not "last main run that executed the tests", and the natural
`git log <last-green>..<red>` window pointed at the wrong PR. Marker refs now
carry provenance (`refs/ci-green/<tree>/<epoch>-<run_id>`; readers split on the
first `-`, legacy bare-epoch markers still parse), a reusing run names the run
that actually gathered the evidence, and a main run whose suite genuinely ran
and passed records `refs/ci-executed/main/<epoch>-<run_id>` at its commit. A
red run then STATES its bisect window from that ref instead of leaving it to be
reconstructed from a run list that cannot distinguish the two kinds of green.

#1568 — PR #1555 merged with the entire Clients workflow red, including
Portal-next, which is the same `npm run build` main-cd's portal-next image leg
runs. promote is all-or-nothing, so CD published nothing for ~1h and a
production 42P18 fix was stranded. Clients cannot simply be marked required: it
is path-filtered, and a legitimately-skipped required check blocks merges
forever. So a new always-runs `clients-gate` job reports that workflow's
verdict into the required check. Zero runs means the path filter did not match
— a fact about the diff, not missing evidence — and is only believed after a
180s creation-grace window so it cannot race run creation.

main-cd's loud-failure half of #1568 already exists (alert-on-failure files a
ci-failure issue, verify-images asserts the complete set); the missing half was
the merge gate, which this adds.

Verified: actionlint clean against the origin/main baseline (the single
remaining SC2016 is pre-existing), `bash -n` over every run: block, YAML parses.

Fixes #1472
Fixes #1491
Fixes #1568

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmBFaHTqhy7h742wkAaJKj
Copilot AI lite review requested due to automatic review settings August 14, 2026 14:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR tightens the repo’s merge gate (Consolidate test results, the only required check) so it reflects the real evidence we depend on, and makes CI diagnostics more trustworthy when runs reuse prior greens or when filtered workflows don’t run.

Changes:

  • Adds explicit shard coverage assertions so “no failures” cannot be mistaken for “every shard ran and reported” (#1472).
  • Records and surfaces provenance for green-tree reuse, and records “last main commit whose tests actually executed” to produce a correct bisect window on red runs (#1491).
  • Introduces an always-runs clients-gate job that folds the path-filtered Clients workflow result into the required check (#1568).

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread .github/workflows/dotnet-test.yml
@github-actions

github-actions Bot commented Aug 14, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

728 tests  ±0   728 ✅ ±0   7m 17s ⏱️ - 2m 40s
 10 suites ±0     0 💤 ±0 
 10 files   ±0     0 ❌ ±0 

Results for commit 9336744. ± Comparison against base commit 179d66b.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 14, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

1 925 tests  ±0   1 733 ✅ ±0   4m 1s ⏱️ - 1m 5s
   11 suites ±0     192 💤 ±0 
   11 files   ±0       0 ❌ ±0 

Results for commit 9336744. ± Comparison against base commit 179d66b.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 14, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

1 924 tests   - 88   1 914 ✅  - 90   6m 57s ⏱️ +21s
   11 suites ± 0      10 💤 + 2 
   11 files   ± 0       0 ❌ ± 0 

Results for commit 9336744. ± Comparison against base commit 179d66b.

This pull request removes 436 and adds 348 tests. Note that renamed tests count towards both.
MeshWeaver.AI.Test.KernelStdoutCaptureTest ‑ CapturingTextWriter_AsyncLocal_Isolates_Concurrent_Captures
MeshWeaver.AI.Test.KernelStdoutCaptureTest ‑ CapturingTextWriter_Outside_Scope_Falls_Back_To_Original
MeshWeaver.AI.Test.KernelStdoutCaptureTest ‑ CapturingTextWriter_Routes_Error_Stream_To_Error_Target
MeshWeaver.AI.Test.KernelStdoutCaptureTest ‑ LoggerTextWriter_Buffers_Until_Newline
MeshWeaver.AI.Test.KernelStdoutCaptureTest ‑ LoggerTextWriter_Dispose_Flushes_Pending_Buffer
MeshWeaver.AI.Test.KernelStdoutCaptureTest ‑ LoggerTextWriter_Logs_At_Configured_Level
MeshWeaver.AI.Test.KernelStdoutCaptureTest ‑ LoggerTextWriter_Splits_On_Each_Newline
MeshWeaver.AI.Test.KernelStdoutCaptureTest ‑ LoggerTextWriter_Strips_CarriageReturn
MeshWeaver.AI.Test.KernelStdoutCaptureTest ‑ LoggerTextWriter_WriteLine_Flushes
MeshWeaver.AI.Test.MarkdownReferenceExtractorTest ‑ ExtractReferences_AgentCommand_IsFiltered
…
MeshWeaver.AI.Test.KernelScriptReferencesSharingTest ‑ GetReferences_ReturnsSameInstances_AcrossSessions
MeshWeaver.AI.Test.KernelScriptReferencesSharingTest ‑ ResolveMissingAssembly_ReturnsSharedInstance_WithoutRematerializing
MeshWeaver.AI.Test.LanguageModelNodeTypeTest ‑ Constants_NamespaceAndNodeType_AreStable
MeshWeaver.AI.Test.LanguageModelNodeTypeTest ‑ Provider_EmptyCatalog_EmitsOnlyAccessPolicy
MeshWeaver.AI.Test.LanguageModelNodeTypeTest ‑ Provider_MissingSection_NoCrash_NoNodes
MeshWeaver.AI.Test.LanguageModelNodeTypeTest ‑ Provider_ModelDefinition_CarriesIdProviderOrder
MeshWeaver.AI.Test.LanguageModelNodeTypeTest ‑ Provider_OneSection_OneNodePerModel
MeshWeaver.AI.Test.LanguageModelNodeTypeTest ‑ Provider_Policy_IsPublicRead_WithLiftedWriteCaps
MeshWeaver.AI.Test.LanguageModelNodeTypeTest ‑ Provider_SectionWithEmptyOrWhitespaceModels_SkipsThem
MeshWeaver.AI.Test.LanguageModelNodeTypeTest ‑ Provider_TwoSourcesShareModelId_FirstWins
…

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 14, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

   11 files  ± 0     11 suites  ±0   5m 58s ⏱️ -16s
1 836 tests +90  1 733 ✅ +92  103 💤  - 2  0 ❌ ±0 
2 179 runs  +90  2 076 ✅ +92  103 💤  - 2  0 ❌ ±0 

Results for commit 9336744. ± Comparison against base commit 179d66b.

This pull request removes 348 and adds 438 tests. Note that renamed tests count towards both.
MeshWeaver.AI.Test.KernelScriptReferencesSharingTest ‑ GetReferences_ReturnsSameInstances_AcrossSessions
MeshWeaver.AI.Test.KernelScriptReferencesSharingTest ‑ ResolveMissingAssembly_ReturnsSharedInstance_WithoutRematerializing
MeshWeaver.AI.Test.LanguageModelNodeTypeTest ‑ Constants_NamespaceAndNodeType_AreStable
MeshWeaver.AI.Test.LanguageModelNodeTypeTest ‑ Provider_EmptyCatalog_EmitsOnlyAccessPolicy
MeshWeaver.AI.Test.LanguageModelNodeTypeTest ‑ Provider_MissingSection_NoCrash_NoNodes
MeshWeaver.AI.Test.LanguageModelNodeTypeTest ‑ Provider_ModelDefinition_CarriesIdProviderOrder
MeshWeaver.AI.Test.LanguageModelNodeTypeTest ‑ Provider_OneSection_OneNodePerModel
MeshWeaver.AI.Test.LanguageModelNodeTypeTest ‑ Provider_Policy_IsPublicRead_WithLiftedWriteCaps
MeshWeaver.AI.Test.LanguageModelNodeTypeTest ‑ Provider_SectionWithEmptyOrWhitespaceModels_SkipsThem
MeshWeaver.AI.Test.LanguageModelNodeTypeTest ‑ Provider_TwoSourcesShareModelId_FirstWins
…
MeshWeaver.AI.Test.InstallAssembliesHubConfigTest ‑ AttributeCarriedHubConfigurations_AreFoldedIntoTheBuilder
MeshWeaver.AI.Test.InstallAssembliesHubConfigTest ‑ InstallAssemblies_FoldsTheProviderPackAttributes_OfLoadedAssemblies
MeshWeaver.AI.Test.KernelStdoutCaptureTest ‑ CapturingTextWriter_AsyncLocal_Isolates_Concurrent_Captures
MeshWeaver.AI.Test.KernelStdoutCaptureTest ‑ CapturingTextWriter_Outside_Scope_Falls_Back_To_Original
MeshWeaver.AI.Test.KernelStdoutCaptureTest ‑ CapturingTextWriter_Routes_Error_Stream_To_Error_Target
MeshWeaver.AI.Test.KernelStdoutCaptureTest ‑ LoggerTextWriter_Buffers_Until_Newline
MeshWeaver.AI.Test.KernelStdoutCaptureTest ‑ LoggerTextWriter_Dispose_Flushes_Pending_Buffer
MeshWeaver.AI.Test.KernelStdoutCaptureTest ‑ LoggerTextWriter_Logs_At_Configured_Level
MeshWeaver.AI.Test.KernelStdoutCaptureTest ‑ LoggerTextWriter_Splits_On_Each_Newline
MeshWeaver.AI.Test.KernelStdoutCaptureTest ‑ LoggerTextWriter_Strips_CarriageReturn
…

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 14, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

1 464 tests  ±0   1 464 ✅ ±0   6m 32s ⏱️ ±0s
   11 suites ±0       0 💤 ±0 
   11 files   ±0       0 ❌ ±0 

Results for commit 9336744. ± Comparison against base commit 179d66b.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 14, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

2 553 tests  ±0   2 552 ✅ ±0   8m 41s ⏱️ +10s
   12 suites ±0       1 💤 ±0 
   12 files   ±0       0 ❌ ±0 

Results for commit 9336744. ± Comparison against base commit 179d66b.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 14, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

    66 files  ±0      66 suites  ±0   39m 27s ⏱️ - 3m 31s
10 430 tests +2  10 124 ✅ +2  306 💤 ±0  0 ❌ ±0 
10 773 runs  +2  10 467 ✅ +2  306 💤 ±0  0 ❌ ±0 

Results for commit 9336744. ± Comparison against base commit 179d66b.

♻️ This comment has been updated with latest results.

Copilot review on #1575: the clients gate collapsed a gh api failure
(auth / rate limit / network) into total_count=0, which after the grace
window reported 'not applicable' — a verdict it had never obtained. That
is the exact pass-on-no-evidence trapdoor this job exists to close, one
level up.

Fetch failure and a genuine zero are now tracked separately: a failed
query is retried inside the poll, and if the query never succeeds once,
the job fails RED naming the last error. Only a SUCCESSFUL query
returning zero runs can reach the not-applicable verdict.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmBFaHTqhy7h742wkAaJKj
@rbuergi
rbuergi merged commit 1eda58b into main Aug 14, 2026
22 checks passed
@rbuergi
rbuergi deleted the fix/ci-gate-hardening branch August 19, 2026 10:30
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.

A shard that never ran leaves the required check green — consolidation counts failures, not whether every shard reported

2 participants