ci: restore a real integration gate; replace the escape hatch that never worked - #124
Merged
Merged
Conversation
bfarmer67
force-pushed
the
devs/bfarmer/opensearch-connection-settings-hook
branch
from
August 26, 2026 17:31
80a9fdf to
2f23076
Compare
bfarmer67
force-pushed
the
devs/bfarmer/restore-ci-integration-tier
branch
from
August 26, 2026 17:32
06a3bd7 to
df354fa
Compare
bfarmer67
force-pushed
the
devs/bfarmer/opensearch-connection-settings-hook
branch
from
August 26, 2026 19:36
2f23076 to
83bc0ba
Compare
bfarmer67
force-pushed
the
devs/bfarmer/restore-ci-integration-tier
branch
4 times, most recently
from
August 26, 2026 21:24
025d43f to
1742886
Compare
bfarmer67
force-pushed
the
devs/bfarmer/opensearch-connection-settings-hook
branch
from
August 26, 2026 21:52
83bc0ba to
b32e1e5
Compare
bfarmer67
force-pushed
the
devs/bfarmer/restore-ci-integration-tier
branch
from
August 26, 2026 21:52
1742886 to
e202e0c
Compare
bfarmer67
force-pushed
the
devs/bfarmer/opensearch-connection-settings-hook
branch
from
August 26, 2026 21:53
b32e1e5 to
524d587
Compare
bfarmer67
force-pushed
the
devs/bfarmer/restore-ci-integration-tier
branch
from
August 26, 2026 21:53
e202e0c to
3a8f357
Compare
…ver worked Since 7ff3808 (twelve days before 3.1.0) this repo has had zero integration coverage in CI, and no working way to get it back. Both defects fixed earlier in this stack shipped through that gap. How it got there. That commit tagged every container-spinning class [TestCategory("LocalOnly")] for sound reasons -- the heavy suite does not gate the NuGet publish, and the recurring infra flakes (Couchbase GSI indexer variance, Azure Front Door blocking MCR under ~36 simultaneous base-image pulls) all live there. But the CI job filtered NEGATIVELY on TestCategory!=LocalOnly, so the filter then matched nothing, and `dotnet test` fails on a no-match. Rather than reconcile filter and categories, the whole job was gated behind repo var RUN_HEAVY_INTEGRATION. Three consequences, none intended: - The variable was never created. The repo has one Actions variable, SOLUTION_NAME. The condition is false by absence. - The documented way back does not work. Both the workflow comment and the commit message claim re-enabling needs "no code change". Measured: the job it re-enables still carries TestCategory!=LocalOnly, so setting the variable to true un-skips a matrix whose every cell matches zero tests and fails on the no-match -- exactly the failure the gate was added to avoid. - A negative filter cannot fail loudly. It reads identically whether the suite is passing, empty, or entirely untagged. Every integration class was then measured against explicit criteria rather than assumed heavy. Every integration test in the repository now runs automatically: - 110 tests on every PR (~2 min), selected POSITIVELY on TestCategory=Gating: opensearch 83, mongodb 10, postgres 9, aerospike 6, multi-provider 2. All verified green locally and on GitHub-hosted runners. No Docker image builds in the PR path, so the MCR/AFD failure class is removed there rather than mitigated. One TFM: these assert behavior against a real server, which does not vary by target framework, and the unit + wire tiers already cover all three. - 27 tests on every push to main: the LocalOnly remainder -- 5 runner classes plus CliBinaryEndToEndTests (Docker image builds), 2 multi-node classes (3 JVMs), and 3 Couchbase squash classes. Couchbase is the only exclusion on speed: its six tests pass, repeatedly, but take 5-6.5 minutes, five to six times every other cell combined, because IsolatedCouchbaseContainer waits out the GSI indexer's initial rebalance (the code allows up to 12 minutes for it). A guard step runs --list-tests with the same filter before any container starts and fails by name if a cell matches zero tests, so losing coverage costs a red build instead of silence. RUN_HEAVY_INTEGRATION is removed. heavy_integration_tests.yml runs the LocalOnly suite post-merge and on demand, selecting positively on TestCategory=LocalOnly so it runs exactly what the PR gate does not. Post-merge rather than manual-only because "run it locally" is a convention, and conventions do not catch regressions; tying it to a push detects a break within one commit and attributes it to that commit. Not a nightly cron: a scheduled run that fails on a day nobody changed anything produces no signal, which is the mistake multi_node_tests.yml already corrected. Docs updated alongside: - ADR-0031 records the three tiers (unit / wire / integration), the membership criteria, the measured per-cell numbers, and the three triggers. Amends ADR-0010's two-tier framing and names the wire tier it omitted. - CONTRIBUTING gains the tier table and "reach for the wire tier first". - docs/site/troubleshooting.md gains symptom entries for both shipped defects -- the OpenSearch "Index name is null" serialization failure and the silent MongoDB squash no-op -- since users on 3.0.0/3.1.0 will search for exactly those. - docs/site/couchbase.md documents the pinned ledger serializer. - docs/site/advanced.md gains the two ledger wire-contract rules for authors of custom record stores, plus the wire-test guidance. - The Jekyll site is ASCII-only, matching every other page. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
bfarmer67
force-pushed
the
devs/bfarmer/restore-ci-integration-tier
branch
from
August 26, 2026 21:55
3a8f357 to
249092c
Compare
bfarmer67
changed the base branch from
devs/bfarmer/opensearch-connection-settings-hook
to
main
August 26, 2026 21:55
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
Since
7ff3808, twelve days before 3.1.0, this repo has had zero integration coverage in CI and no working way to restore it. Both defects fixed earlier in this stack shipped through that hole.That commit tagged every container-spinning class
[TestCategory("LocalOnly")]for good reasons. But the CI job filtered negatively onTestCategory!=LocalOnly, so the filter then matched nothing, anddotnet testfails on a no-match. Instead of reconciling the filter with the tags, the job was disabled behind a repo variable.Three things went wrong, all verified rather than inferred:
SOLUTION_NAME. The condition is false by absence.RUN_HEAVY_INTEGRATION=trueun-skips a matrix that still carriesTestCategory!=LocalOnly, so every cell matches zero tests and fails - the exact failure the gate was added to avoid. Measured:What this delivers
115 integration tests gate every PR, ~2 minutes, selected positively on
TestCategory=Gating:Verified green on GitHub-hosted runners across four separate dispatched runs, alongside unit tests on net8/9/10.
Every class was measured against explicit criteria rather than assumed heavy: shared assembly-fixture container only, no Docker image build, not multi-node, cell finishes in about a minute, observed green. No Docker image builds in the PR path, so the MCR/Azure-Front-Door throttle failure class is removed there rather than mitigated.
The zero-match trap cannot recur. A guard step runs
--list-testswith the same filter before any container starts and fails by name if a cell matches nothing. Losing coverage now costs a red build instead of silence.Every test is accounted for
136 integration tests, partitioned with nothing orphaned:
GatingLocalOnlyMultiNodeFlakyRUN_HEAVY_INTEGRATIONis deleted, replaced by a Heavy Integration Tests workflow with workingworkflow_dispatchinputs for provider and framework. It selects positively onTestCategory=LocalOnly, so it runs exactly what the PR gate does not. Where a provider has nothing left in that category (opensearch, multi-provider - everything got promoted), the job skips with a notice rather than failing. That asymmetry is deliberate: an empty match in the PR gate means coverage vanished and must be loud; in the heavy suite it means coverage was promoted and is a success.What I found by measuring, and what I backed out
I built this to also run post-merge on every push to
main, on the reasoning that "run them locally" is a convention and conventions do not catch regressions. I removed that trigger before shipping, because measuring the suite first showed it is not dependably green:AerospikeRunnerTest.Should_Succeed_WhenRunningUpTwicefailed in a batch run (52s) and passed standalone (12s) minutes later. Intermittent; cause not isolated, plausibly container-teardown timing from the preceding provider.Should_Fail_WhenMigrationHasLockin the Aerospike, MongoDB and Couchbase runner suites asserts on a race - it starts concurrent runner containers and requires one to observe lock contention, which does not happen on a fast host. Aerospike and MongoDB were measured failing with no defect present. Now quarantined as[TestCategory("Flaky")].Notably, the Postgres equivalent of that test was already commented out by an earlier author. Same conclusion, reached silently. The quarantine makes it explicit and records the intended fix.
Enabling post-merge on a suite that is not dependably green produces a signal people learn to ignore, and an ignored signal is worse than an absent one - which is the same argument this PR's ADR makes for keeping the PR gate small and trustworthy. The trigger is three commented lines in the workflow, to be enabled once the intermittency is fixed. Tracked in ADR-0031's Consequences rather than dropped.
So this PR is honest but unfinished on that axis: the PR gate is real and verified; the heavy suite has a working trigger but still needs a human to pull it.
Docs
Flakybar ("debt, not a category for tests that annoy me"), and "reach for the wire tier first".Index name is null for the given type; the MongoDB one has no error text at all, so it is indexed by its symptom.