fix(focas): fail-fast at init on unimplemented backend (operator footgun)

Add IFocasClientFactory.EnsureUsable() — a config-time probe called by
FocasDriver.InitializeAsync before any background loops start. The
UnimplementedFocasClientFactory throws NotSupportedException immediately
(faulting the driver at init), eliminating the footgun where a driver on
the 'unimplemented' backend appeared Healthy then failed every read/write/
subscribe silently. WireFocasClientFactory and FakeFocasClientFactory are
no-ops. Backstop Create() throw remains in place.
This commit is contained in:
Joseph Doherty
2026-06-16 05:24:41 -04:00
parent bd8fee610b
commit 5c5aaef609
5 changed files with 71 additions and 3 deletions
@@ -282,6 +282,18 @@ public interface IFocasClientFactory
/// <summary>Creates a new FOCAS client instance.</summary>
/// <returns>A new IFocasClient instance.</returns>
IFocasClient Create();
/// <summary>
/// Config-time usability probe called by <see cref="FocasDriver.InitializeAsync"/> before
/// any background loops start. Must NOT create a live wire client. Implementations that
/// are permanently non-functional (e.g. <see cref="UnimplementedFocasClientFactory"/>)
/// throw <see cref="NotSupportedException"/> here so the driver faults immediately at
/// init rather than lazily on the first read.
/// </summary>
/// <exception cref="NotSupportedException">
/// Thrown by stub/unimplemented backends to force a fail-fast init fault.
/// </exception>
void EnsureUsable();
}
/// <summary>
@@ -292,12 +304,22 @@ public interface IFocasClientFactory
/// </summary>
public sealed class UnimplementedFocasClientFactory : IFocasClientFactory
{
private const string Message =
"FOCAS driver backend is 'unimplemented'. Switch to 'Backend: \"wire\"' in driver config " +
"once the CNC is provisioned — see docs/drivers/FOCAS.md.";
/// <summary>
/// Config-time probe — throws immediately so <see cref="FocasDriver.InitializeAsync"/>
/// faults the driver before any background loops start. This prevents the footgun where
/// the driver appears Healthy at init but every read/write/subscribe fails.
/// </summary>
/// <exception cref="NotSupportedException">Always thrown.</exception>
public void EnsureUsable() => throw new NotSupportedException(Message);
/// <summary>Creates a new client instance (always throws NotSupportedException).</summary>
/// <returns>Never returns; always throws NotSupportedException.</returns>
/// <exception cref="NotSupportedException">Always thrown to indicate backend is not yet provisioned.</exception>
public IFocasClient Create() => throw new NotSupportedException(
"FOCAS driver backend is 'unimplemented'. Switch to 'Backend: \"wire\"' in driver config " +
"once the CNC is provisioned — see docs/drivers/FOCAS.md.");
public IFocasClient Create() => throw new NotSupportedException(Message);
}
/// <summary>