Phase 6.1 Stream A follow-up - DriverInstance.ResilienceConfig JSON column + parser + wire-in #103

Merged
dohertj2 merged 1 commits from phase-6-1-stream-a-resilience-config into v2 2026-04-19 12:23:49 -04:00
Owner

Closes Stream A.2 per-instance override binding that was deferred when A.1 shipped in PR #78.

Summary

  • New DriverInstance.ResilienceConfig nvarchar(max) column + SQL ISJSON check constraint + EF migration.
  • DriverResilienceOptionsParser.ParseOrDefaults(tier, json, out diag) — pure fn layering partial per-capability + bulkhead overrides on tier defaults. Malformed JSON falls back gracefully with a diagnostic. Case-insensitive keys; unknown capabilities surface diagnostic + skip.
  • OtOpcUaServer + OpcUaApplicationHost gain optional tierLookup (type → DriverTier) + resilienceConfigLookup (driverInstanceId → JSON) ctor params. Existing tests constructing without these are unchanged.
  • SchemaComplianceTests expected-CK list gains CK_DriverInstance_ResilienceConfig_IsJson.

Test plan

  • 13 new parser tests covering every branch (null / whitespace / malformed / empty-object / per-capability override / partial policy / bulkhead override / unknown-capability skip / case-insensitive keys + caps / every-tier-empty-JSON round-trip).
  • Full solution dotnet test: 1215 passing (was 1202, +13).

🤖 Generated with Claude Code

Closes Stream A.2 per-instance override binding that was deferred when A.1 shipped in PR #78. ## Summary - New `DriverInstance.ResilienceConfig` nvarchar(max) column + SQL `ISJSON` check constraint + EF migration. - `DriverResilienceOptionsParser.ParseOrDefaults(tier, json, out diag)` — pure fn layering partial per-capability + bulkhead overrides on tier defaults. Malformed JSON falls back gracefully with a diagnostic. Case-insensitive keys; unknown capabilities surface diagnostic + skip. - `OtOpcUaServer` + `OpcUaApplicationHost` gain optional `tierLookup` (type → DriverTier) + `resilienceConfigLookup` (driverInstanceId → JSON) ctor params. Existing tests constructing without these are unchanged. - SchemaComplianceTests expected-CK list gains `CK_DriverInstance_ResilienceConfig_IsJson`. ## Test plan - [x] 13 new parser tests covering every branch (null / whitespace / malformed / empty-object / per-capability override / partial policy / bulkhead override / unknown-capability skip / case-insensitive keys + caps / every-tier-empty-JSON round-trip). - [x] Full solution `dotnet test`: 1215 passing (was 1202, +13). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
dohertj2 added 1 commit 2026-04-19 12:23:38 -04:00
Closes the Phase 6.1 Stream A.2 "per-instance overrides bound from
DriverInstance.ResilienceConfig JSON column" work flagged as a follow-up
when Stream A.1 shipped in PR #78. Every driver can now override its Polly
pipeline policy per instance instead of inheriting pure tier defaults.

Configuration:
- DriverInstance entity gains a nullable `ResilienceConfig` string column
  (nvarchar(max)) + SQL check constraint `CK_DriverInstance_ResilienceConfig_IsJson`
  that enforces ISJSON when not null. Null = use tier defaults (decision
  #143 / unchanged from pre-Phase-6.1).
- EF migration `20260419161008_AddDriverInstanceResilienceConfig`.
- SchemaComplianceTests expected-constraint list gains the new CK name.

Core.Resilience.DriverResilienceOptionsParser:
- Pure-function parser. ParseOrDefaults(tier, json, out diag) returns the
  effective DriverResilienceOptions — tier defaults with per-capability /
  bulkhead overrides layered on top when the JSON payload supplies them.
  Partial policies (e.g. Read { retryCount: 10 }) fill missing fields from
  the tier default for that capability.
- Malformed JSON falls back to pure tier defaults + surfaces a human-readable
  diagnostic via the out parameter. Callers log the diag but don't fail
  startup — a misconfigured ResilienceConfig must not brick a working
  driver.
- Property names + capability keys are case-insensitive; unrecognised
  capability names are logged-and-skipped; unrecognised shape-level keys
  are ignored so future shapes land without a migration.

Server wire-in:
- OtOpcUaServer gains two optional ctor params: `tierLookup` (driverType →
  DriverTier) + `resilienceConfigLookup` (driverInstanceId → JSON string).
  CreateMasterNodeManager now resolves tier + JSON for each driver, parses
  via DriverResilienceOptionsParser, logs the diagnostic if any, and
  constructs CapabilityInvoker with the merged options instead of pure
  Tier A defaults.
- OpcUaApplicationHost threads both lookups through. Default null keeps
  existing tests constructing without either Func unchanged (falls back
  to Tier A + tier defaults exactly as before).

Tests (13 new DriverResilienceOptionsParserTests):
- null / whitespace / empty-object JSON returns pure tier defaults.
- Malformed JSON falls back + surfaces diagnostic.
- Read override merged into tier defaults; other capabilities untouched.
- Partial policy fills missing fields from tier default.
- Bulkhead overrides honored.
- Unknown capability skipped + surfaced in diagnostic.
- Property names + capability keys are case-insensitive.
- Every tier × every capability × empty-JSON round-trips tier defaults
  exactly (theory).

Full solution dotnet test: 1215 passing (was 1202, +13). Pre-existing
Client.CLI Subscribe flake unchanged.

Production wiring (Program.cs) example:
  Func<string, DriverTier> tierLookup = type => type switch
  {
      "Galaxy" => DriverTier.C,
      "Modbus" or "S7" => DriverTier.B,
      "OpcUaClient" => DriverTier.A,
      _ => DriverTier.A,
  };
  Func<string, string?> cfgLookup = id =>
      db.DriverInstances.AsNoTracking().FirstOrDefault(x => x.DriverInstanceId == id)?.ResilienceConfig;
  var host = new OpcUaApplicationHost(..., tierLookup: tierLookup, resilienceConfigLookup: cfgLookup);

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dohertj2 merged commit fdd0bf52c3 into v2 2026-04-19 12:23:49 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#103