Skip to content

test(readiness): fix the four identifiable readiness-gate flakes at the root - #335

Merged
IanFrelinger merged 3 commits into
masterfrom
test/readiness-flakes
Aug 17, 2026
Merged

test(readiness): fix the four identifiable readiness-gate flakes at the root#335
IanFrelinger merged 3 commits into
masterfrom
test/readiness-flakes

Conversation

@IanFrelinger

Copy link
Copy Markdown
Owner

Production-readiness audit, H10. Ledger: https://claude.ai/code/artifact/15fa4198-97b8-4da6-bea0-0c24d364a7f3

Why

The full-platform-readiness gate has been red on ~43% of master pushes from identifiable flakes, plus a fourth one on macOS in run 31982502428. Each had a real cause; none is fixed with a retry-until-green. Signatures are listed so future reds can be matched.

What

1. EndpointHealthMonitorTests (Nexo.Tests.Orchestration) — signature: Expected registry.Updates to contain a single item, but the collection is empty. A fixed Task.Delay(300) raced a real gRPC round-trip / connection refusal, and StopAsync cancelled the probe before UpdateHealth ran. Now a bounded 50 ms poll (<= 5 s) on registry.Updates before StopAsync; assertions unchanged. Also adds ExecuteAsync_WithoutGrpcTransport_StaysIdle_AndDoesNotTouchRegistry for the product change in (3).

2. AgentTransportServiceImplReflectionGapCoverageTests (Nexo.Tests.Transport) — signature: Expected process.WaitForExit(10000) to be true / IOException from Directory.Delete. The SAN cert was produced by shelling out to openssl (PATH-dependent, 10 s cap, undisposed Process, temp-dir delete masking the timeout). Now generated in-process with CertificateRequest + SubjectAlternativeNameBuilder (mirrors HttpBarrierContextMiddlewareTests). No openssl at all.

3. API host DI under non-Full profile — signature: InvalidOperationException: Unable to resolve service for type 'Nexo.Transport.Grpc.GrpcAgentTransport' while attempting to activate 'Nexo.Runtime.Routing.EndpointHealthMonitor' (Hosting failed to start).

  • Test cause: AutonomyCompositionTests, McpA2AProtocolIngressProdStyleTests and HostingE2ESmokeTests set the process-wide NEXO_DEPLOYMENT_PROFILE outside [Collection("EnvironmentVariables")] while xunit runs collections in parallel. Fix: the airgapped autonomy test moved to AutonomyCompositionAirGappedProfileTests (same file, in the collection, uses EnvironmentVariableScope); the MCP refusal moved to the new Tests/VirtualProduction/AirGappedProfileApiHostProdStyleTests (a class can be in only one collection); HostingE2ESmokeTests joined the collection.
  • Product cause: AddNexoRuntimeRouting registers EndpointHealthMonitor unconditionally while the kernel registers GrpcAgentTransport only when the profile includes runtime transport — every host that calls AddNexoRuntimeRouting + AddNexo (API, CLI, daemon, fleet host) failed DI under airgapped/edge/system. Fix in src/Nexo.Runtime/Routing/EndpointHealthMonitor.cs: a transport-less constructor; DI picks the existing 4-arg constructor whenever the transport is registered (unchanged, null guard kept) and falls back otherwise, in which case ExecuteAsync logs once and stays idle. This is the kernel's profile condition evaluated at resolution time, so it holds for every host, not just Nexo.API. New ProdStyle test boots the real Nexo.API host under airgapped, hits /health, asserts the monitor is registered and the transport is not — verified failing with the exact signature above before the fix.

4. CertifiedBrickHotSwapHostTests.SecondSwap_ReplacesGeneration_AndOldContextIsCollected — signature: Expected second.PreviousGenerationCollected to be true ... but found False (provenance shows GenerationLeakSuspected). WaitForContextRelease ran 10 back-to-back GC passes on the thread that is often the one that just completed the last invocation of the retiring generation; its frames still root that invocation's state machine (and a brick instance from the old context) until they unwind, and no number of collections frees a stack-rooted object. Fix in CertifiedBrickHotSwapHost: WaitForContextReleaseAsync — up to 20 passes with a real Task.Delay(10 ms) yield between passes. The retry lives in the host, not the test: the asserted flag is what the host observed, so a test-side sweep could never turn it true (comment added at the assertion; the assertion is kept).

5. VirtualProductionNcrRoutingTests (macOS run 31982502428) — signature: HttpListenerException: Address already in use from HttpListener.Close() in RunPodLoopbackApiServer.Dispose. The helper already binds an ephemeral port; on macOS/Linux the managed HttpListener re-binds the prefix's endpoint during teardown and fails while the just-released port is in TIME_WAIT. Close() was the one unguarded call: now catches HttpListenerException/ObjectDisposedException (teardown only). VirtualProductionNcrRoutingHost.DisposeAsync restores its env overrides in a finally, and the test class joined [Collection("EnvironmentVariables")] (it sets NEXO_TOTAL_VRAM_BYTES/NEXO_AVAILABLE_VRAM_BYTES).

Verification

  • Built Nexo.Tests.Orchestration, Nexo.Tests.Transport (net8.0) and Nexo.Tests.Infrastructure (net8.0 + net9.0): 0 warnings, 0 errors.
  • EndpointHealthMonitorTests 7/7; cert tests 9/9.
  • net9.0: HostingDeploymentProfileTests + HostingE2ESmokeTests + moved airgapped tests + AirGappedProfileApiHostProdStyleTests + CertifiedBrickHotSwapHostTests + VirtualProductionNcrRoutingTests + McpA2AProtocolIngressProdStyleTests + ApiDevelopmentHostDiTests + RuntimeServiceCollectionExtensions*: 83/83; wider HotSwap|Autonomy|Ncr|Routing sweep 151/151.
  • net8.0 subset (non-VirtualProduction): 75/75.
  • Negative check: with the EndpointHealthMonitor change stashed, Airgapped_profile_api_host_starts_with_an_idle_endpoint_health_monitor fails with the signature in (3).

Note: the readiness gate is path-filtered and has no PR trigger; dispatch full-platform-readiness-gate.yml on this branch to prove it.

🤖 Generated with Claude Code
Reviewer follow-ups applied on top: the air-gapped refusal test is added back to mcp-a2a-gate.yml's filter (it moved classes); a stale hot-swap comment fixed. The reviewer's concern that AirGappedProfileApiHostProdStyleTests (EnvironmentVariables collection) could overlap Integration-collection host boots does not hold: both collections are DisableParallelization = true, and xunit 2.9 runs non-parallel collections after the parallel ones, one at a time.

PlzTouchGrassand others added 2 commits August 16, 2026 21:32
…he root
The full-platform-readiness gate was red on roughly 43% of master pushes from
four flakes that each had a real cause; none of them needed a retry loop until
green. Signatures, so future reds can be matched:
1. EndpointHealthMonitorTests (Nexo.Tests.Orchestration)
Signature: "Expected registry.Updates to contain a single item, but the
collection is empty" in ExecuteAsync_HealthyProbe_UpdatesRegistryTrue /
ExecuteAsync_UnhealthyProbe_* .
Cause: a fixed Task.Delay(300) before StopAsync, while the first probe is a
real gRPC round-trip (or a connection refusal) whose latency depends on the
host; StopAsync then cancels the probe before UpdateHealth ran.
Fix: bounded poll (50 ms, <= 5 s) on registry.Updates before StopAsync;
assertions unchanged.
2. AgentTransportServiceImplReflectionGapCoverageTests (Nexo.Tests.Transport)
Signature: "Expected process.WaitForExit(10000) to be true" or an
IOException from Directory.Delete in CreateCertificateWithSan.
Cause: the SAN certificate was produced by shelling out to openssl (PATH
dependent, 10 s cap, Process never disposed, and the temp-dir delete in
finally masked the timeout).
Fix: generate the certificate in-process with CertificateRequest +
SubjectAlternativeNameBuilder (same shape as HttpBarrierContextMiddleware
tests). No openssl at all.
3. ApiDevelopmentHostDiTests / API host under a non-Full profile
Signature: "System.InvalidOperationException: Unable to resolve service for
type 'Nexo.Transport.Grpc.GrpcAgentTransport' while attempting to activate
'Nexo.Runtime.Routing.EndpointHealthMonitor'." (Hosting failed to start).
Cause (test): AutonomyCompositionTests, McpA2AProtocolIngressProdStyleTests
and HostingE2ESmokeTests set the process-wide NEXO_DEPLOYMENT_PROFILE
outside the serialized "EnvironmentVariables" collection while xunit runs
collections in parallel, so a concurrent API host build saw
airgapped/edge.
Cause (product): AddNexoRuntimeRouting registers EndpointHealthMonitor
unconditionally while the kernel registers its GrpcAgentTransport dependency
only for profiles with runtime transport (Full/Server) -- every host that
calls AddNexoRuntimeRouting + AddNexo (API, CLI, daemon, fleet host) failed
DI under airgapped/edge/system.
Fix (test): the env-setting tests now live in [Collection(
"EnvironmentVariables")] (AutonomyCompositionAirGappedProfileTests; the MCP
refusal moved to AirGappedProfileApiHostProdStyleTests since a class can be
in only one collection; HostingE2ESmokeTests joined the collection).
Fix (product): EndpointHealthMonitor gained a transport-less constructor and
stays idle when no GrpcAgentTransport is registered -- the same condition
the kernel gates the transport on, evaluated at resolution time, so it holds
for every host, not just Nexo.API. Existing 4-arg constructor and its null
guard are unchanged; DI picks it whenever the transport is registered.
New ProdStyle test boots the real Nexo.API host under airgapped and hits
/health (verified failing with the signature above before the fix).
4. CertifiedBrickHotSwapHostTests.SecondSwap_ReplacesGeneration_AndOldContextIsCollected
Signature: "Expected second.PreviousGenerationCollected to be true because
the drained generation-1 context must be collected once its bricks are
dropped, but found False" (provenance shows GenerationLeakSuspected).
Cause: WaitForContextRelease ran 10 back-to-back GC passes on the thread
that is often the one that just completed the last invocation of the
retiring generation; that thread's frames still root the finished
invocation's state machine (and a brick instance from the old context)
until they unwind, and no number of collections frees a stack-rooted object.
Fix: WaitForContextReleaseAsync -- up to 20 passes with a real
Task.Delay(10 ms) yield between passes, which gives the stack back so the
next pass succeeds. The retry has to live in the host, not the test: the
flag the test asserts is what the host observed, so a test-side sweep can
never turn it true (comment added at the assertion; the assertion is kept).
5. VirtualProductionNcrRoutingTests (macOS, run 31982502428)
Signature: "System.Net.HttpListenerException : Address already in use" from
HttpListener.Close() in RunPodLoopbackApiServer.Dispose during teardown of
NcrCapabilityPoller_exposes_environment_hardware_profiler_snapshot.
Cause: the helper already binds an ephemeral port (TcpListener probe then
release); on macOS/Linux the managed HttpListener re-binds the prefix's
endpoint while tearing it down and fails when the just-released port is in
TIME_WAIT. Close() was the one unguarded call.
Fix: Close() now swallows HttpListenerException/ObjectDisposedException
(teardown only; assertions run before it); VirtualProductionNcrRoutingHost
restores its process-wide env overrides in a finally, and the test class
joined the "EnvironmentVariables" collection because it sets
NEXO_TOTAL_VRAM_BYTES / NEXO_AVAILABLE_VRAM_BYTES.
Verification: built Nexo.Tests.Orchestration, Nexo.Tests.Transport (net8.0)
and Nexo.Tests.Infrastructure (net8.0 + net9.0), 0 warnings; ran
EndpointHealthMonitorTests (7/7), the cert tests (9/9),
HostingDeploymentProfileTests + HostingE2ESmokeTests + the moved airgapped
tests + AirGappedProfileApiHostProdStyleTests + CertifiedBrickHotSwapHostTests
+ VirtualProductionNcrRoutingTests + McpA2AProtocolIngressProdStyleTests +
ApiDevelopmentHostDiTests + RuntimeServiceCollectionExtensions* on net9.0
(83/83) and the net8.0-compilable subset (75/75); wider HotSwap|Autonomy|Ncr|
Routing sweep on net9.0 151/151.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…er; fix a stale comment
The air-gapped MCP/A2A enable-refusal test moved out of McpA2AProtocolIngressProdStyleTests
into AirGappedProfileApiHostProdStyleTests (a class can be in only one xunit collection), so
mcp-a2a-gate.yml's name filter no longer ran it. Add the new class to the filter.
Also: both the EnvironmentVariables and Integration collections are DisableParallelization=true;
in xunit 2.9 non-parallel collections run after the parallel ones, one at a time, so an
env-setting host boot in one never overlaps a host boot in the other.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cursor

cursorBot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@IanFrelinger
IanFrelinger merged commit faa0f7e into masterAug 17, 2026
15 checks passed
@IanFrelinger
IanFrelinger deleted the test/readiness-flakes branch August 17, 2026 02:35
IanFrelinger pushed a commit that referenced this pull request Aug 17, 2026
…rograms historical; document spikes/
docs/DocsIndex.md: Start Here #1 is the tester quickstart, README #2,
GettingStarted #3; IntegratorGuide and consumer-template/CONSUMING.md are
listed for the first time; RELEASE.md, the setup-matrix scripts and the
bootstrap helpers move out of the numbered list into an "escape hatches"
paragraph (RELEASE stays under Operator); a new "Trust loop / certification
(experimental, hold-mode)" group makes docs/trust-loop/*, the evidence
ledger, governed-pipeline, SELF-EXTEND-AUDIT, the sample objective and
spikes/ reachable; "Planning & Roadmap" becomes "Planning history
(historical as of 2026-08-16)" because it described a finished program.
Stale status docs are date-stamped rather than deleted:
- ProtocolIntegration-MCP-A2A.md said the Nexo.API wiring was "planned"; it
landed in #269/#270 (Program.cs registers all four adapters and maps
/api/mcp and /api/a2a/{agentId}; IngressCatalog rows McpServer/A2AAgents).
- ci-pre-existing-failures.md said the Full Platform Readiness Gate was RED
as of 06-21; it went green with #317-#320/#335. Kept because
docs/planning/* still cite it.
- GapAnalysis.md and NorthStarGapAnalysis.md predate the trust loop; both get
a historical banner naming their still-open rows and pointing at the
ledger, ProjectTiers and CHANGELOG.
GettingStarted.md #5 contradicted DocsIndex on "installer" scripts (the
scripts/install/* helpers exist but bootstrap the container lane); it now
says so and points first-run readers at the quickstart. The pointer to a
README section "First Successful Pipeline Run" (which does not exist) now
names the sections that do. IntegratorGuide.md recommended `dotnet build
Nexo.sln` (the docs guard forbids it in README); it now builds
Nexo.Kernel.sln and the CLI project, with hello-brick as the smallest test
example, and the compatibility matrix names the solutions that actually
apply.
spikes/README.md: the 27 tracked files under spikes/ were cited as PASS
evidence by the ledger but undocumented. It says what each spike is, every
flag of the flight runner, which ledger rows cite each spike, and that none
of it is a supported entry point.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto 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.

2 participants

@IanFrelinger@PlzTouchGrass