refactor(drivers): delete the inert driver-tier recycle machinery (#522)

The tier system was documented, operator-authorable, and inert. Deleted rather than activated,
because its premise is gone rather than merely unused.

"Tier C" meant a driver running out-of-process behind an IDriverSupervisor that could restart its
Host without tearing down the OPC UA session. No such process exists anywhere: Galaxy reaches
MXAccess over gRPC to the external mxaccessgw sidecar (PR 7.2 retired the in-process
Galaxy.Host/Proxy/Shared projects) and FOCAS has run in-process since its managed wire client
landed 2026-04-24. Consistently, IDriverSupervisor had ZERO implementations and there was nothing
for one to implement against.

The issue understated the inertness. It says the Tier-C-only protections never engaged, implying
the Tier A/B parts did. They did not: nothing constructs MemoryTracking, MemoryRecycle or
ScheduledRecycleScheduler outside their own unit tests, so the whole Core/Stability recycle layer
was dead — meaning option (a), "pass real tiers", was never a flag flip. It would have meant
writing the wiring that never existed AND arming it.

Deleted: MemoryTracking, MemoryRecycle, ScheduledRecycleScheduler, IDriverSupervisor, the
vestigial DriverTypeRegistry (referenced only by its own tests), and the RecycleIntervalSeconds
knob — which the AdminUI let an operator author and the parser validated while it configured
nothing.

DriverTier itself SURVIVES and is load-bearing: DriverResilienceOptions.GetTierDefaults supplies
the real per-capability timeout/retry/breaker policies via DriverFactoryRegistry.GetTier and
DriverCapabilityInvokerFactory. Only the isolation-and-recycle layer above it is gone.

Deliberately NOT deleted:

- WedgeDetector came along in the same directory and is equally dead in production, but it is
  tier-agnostic and is not recycle machinery — it only shares the folder. Restored rather than
  swept up in a decision that was not about it.
- IDriver.GetMemoryFootprint() and FlushOptionalCachesAsync() lose their only consumer here.
  Removing them touches all 12 drivers and every test stub, so they are documented as
  consumerless and filed as #525 instead of buried in this diff.

Compatibility: a deployed ResilienceConfig blob still carrying "recycleIntervalSeconds" parses
cleanly (unknown keys are ignored — guarded by a new test, because a blob that suddenly failed to
parse would fall back to tier defaults and silently discard the operator's real overrides), and
the AdminUI's preserve-unknown-keys bag keeps the key rather than rewriting stored config on an
unrelated edit.

The 01/U-6 knob-inertness guard carried an explicit carve-out admitting RecycleIntervalSeconds was
dormant and out of scope; that carve-out is now gone, so the expected set is literally what the
test's name claims.

Note: Host.IntegrationTests has 2 failures (DriverProbeRegistrationTests.is_idempotent,
PrimaryGateFailoverTests) — verified pre-existing by reproducing both on clean master dc9d947b.

Claude-Session: https://claude.ai/code/session_015p7wGqy3YpZNCpDzTpGMKo
This commit is contained in:
Joseph Doherty
2026-07-30 04:44:04 -04:00
parent 30d0697c28
commit cbb882293b
23 changed files with 153 additions and 1145 deletions
@@ -143,13 +143,13 @@ public sealed class DriverResilienceOptionsParserTests
public void PropertyNames_AreCaseInsensitive()
{
var json = """
{ "RECYCLEINTERVALSECONDS": 3600 }
{ "CAPABILITYPOLICIES": { "Read": { "retryCount": 7 } } }
""";
var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.C, json, out var diag);
diag.ShouldBeNull();
options.RecycleIntervalSeconds.ShouldBe(3600);
options.Resolve(DriverCapability.Read).RetryCount.ShouldBe(7);
}
/// <summary>Verifies that capability name is case insensitive.</summary>
@@ -182,51 +182,31 @@ public sealed class DriverResilienceOptionsParserTests
options.Resolve(cap).ShouldBe(DriverResilienceOptions.GetTierDefaults(tier)[cap]);
}
/// <summary>Verifies that RecycleIntervalSeconds on Tier C with positive value parses and surfaces.</summary>
[Fact]
public void RecycleIntervalSeconds_TierC_PositiveValue_ParsesAndSurfaces()
{
var options = DriverResilienceOptionsParser.ParseOrDefaults(
DriverTier.C, "{\"recycleIntervalSeconds\":3600}", out var diag);
diag.ShouldBeNull();
options.RecycleIntervalSeconds.ShouldBe(3600);
}
/// <summary>Verifies that RecycleIntervalSeconds when null defaults to null.</summary>
[Fact]
public void RecycleIntervalSeconds_Null_DefaultsToNull()
{
var options = DriverResilienceOptionsParser.ParseOrDefaults(DriverTier.C, "{}", out _);
options.RecycleIntervalSeconds.ShouldBeNull();
}
/// <summary>Verifies that RecycleIntervalSeconds on Tier A or B is rejected with diagnostic.</summary>
/// <param name="tier">The driver tier to test.</param>
/// <summary>
/// <b>Backward-compatibility guard for the #522 deletion.</b> The four
/// <c>RecycleIntervalSeconds</c> tests that lived here are gone with the knob they covered — the
/// scheduled-recycle machinery it configured was constructed only in tests, and the
/// <c>IDriverSupervisor</c> that would have performed the recycle had no implementations.
/// <para>What still matters is that a <b>deployed</b> <c>ResilienceConfig</c> blob written before
/// the removal keeps parsing. The removed property must be ignored as an unknown key, NOT rejected
/// — a driver whose stored config suddenly failed to parse would fall back to tier defaults and
/// silently discard the operator's real timeout/retry overrides alongside the dead one.</para>
/// </summary>
[Theory]
[InlineData(DriverTier.A)]
[InlineData(DriverTier.B)]
public void RecycleIntervalSeconds_OnTierAorB_Rejected_With_Diagnostic(DriverTier tier)
[InlineData(DriverTier.C)]
public void A_legacy_blob_carrying_the_removed_recycle_key_still_parses_cleanly(DriverTier tier)
{
// Decision #74 — in-process drivers must not scheduled-recycle because it would
// tear down every OPC UA session. The parser surfaces a diagnostic rather than
// silently honouring the value.
var options = DriverResilienceOptionsParser.ParseOrDefaults(
tier, "{\"recycleIntervalSeconds\":3600}", out var diag);
var json = """
{ "recycleIntervalSeconds": 3600, "capabilityPolicies": { "Read": { "retryCount": 5 } } }
""";
options.RecycleIntervalSeconds.ShouldBeNull();
diag.ShouldContain("Tier C only");
}
var options = DriverResilienceOptionsParser.ParseOrDefaults(tier, json, out var diag);
/// <summary>Verifies that RecycleIntervalSeconds with non-positive value is rejected with diagnostic.</summary>
[Fact]
public void RecycleIntervalSeconds_NonPositive_Rejected_With_Diagnostic()
{
var options = DriverResilienceOptionsParser.ParseOrDefaults(
DriverTier.C, "{\"recycleIntervalSeconds\":0}", out var diag);
options.RecycleIntervalSeconds.ShouldBeNull();
diag.ShouldContain("must be positive");
diag.ShouldBeNull("an unknown key must be ignored silently, not reported as a misconfiguration");
options.Tier.ShouldBe(tier);
options.Resolve(DriverCapability.Read).RetryCount.ShouldBe(5,
"the operator's real overrides must survive alongside the dead key");
}
// ---- 01/S-6: range clamps (clamp + diagnostic, never brick) ----