# Follow-up #13 — Plumb per-instance ResilienceConfig through the deploy artifact

> **Status:** ✅ IMPLEMENTED (`fix/archreview-crit13-resilience-config-artifact` `75403caa`, off the crit10 branch)
> · **Surfaced by:** task #10's residual sub-finding (the invoker got tier defaults only) · **Task:** TaskCreate
> #13 · **Severity:** Medium (a silently-ignored authored config — the review's "unit-green ≠ wired" class) ·
> **Effort:** M. See [`STATUS.md`](STATUS.md) + [`FOLLOWUP-10`](FOLLOWUP-10-resilience-dispatch-gap.md).

## The finding

Task #10 wired the resilience pipeline into dispatch but the invoker only ever got the driver type's **tier
defaults** — `DriverCapabilityInvokerFactory.Create` passed `resilienceConfigJson: null`. The per-instance
`DriverInstance.ResilienceConfig` JSON column was therefore **silently ignored at runtime**.

**Key discovery:** the *write* side was already complete — the AdminUI `DriverResilienceSection` authors it, every
driver page persists it to the entity (create + update), and `ConfigComposer.SnapshotAndFlattenAsync` serializes
the **whole** `DriverInstance` entity, so `ResilienceConfig` was **already in the artifact JSON**. Only the driver
node's *read* path dropped it: `DriverInstanceSpec` didn't carry the column and the factory hard-coded null. So
#13 is pure read-path plumbing — a genuine dead-config-to-live fix, not a new feature.

## What shipped

- **`DriverInstanceSpec`** gains `string? ResilienceConfig`; **`DeploymentArtifact.TryReadSpec`** reads the column
  (lenient `ReadString`, like the other fields).
- **`IDriverCapabilityInvokerFactory.Create`** takes `string? resilienceConfigJson`; the concrete factory parses
  it via `DriverResilienceOptionsParser.ParseOrDefaults` (layering onto the tier), logs any parse diagnostic
  (malformed JSON / unknown capability / misapplied Tier-C recycle) as a warning, and **never throws** — a bad
  config degrades to tier defaults. `NullDriverCapabilityInvokerFactory` ignores the arg. Host DI passes a logger.
- **`DriverHostActor.SpawnChild`** threads `spec.ResilienceConfig` into `Create`.
- **Invalidate-on-change.** The pipeline cache keys on `(instance, host, capability)` and **ignores options on a
  cache hit**, so a fresh invoker with changed options would otherwise keep serving the stale pipeline. `Create`
  now calls `DriverResiliencePipelineBuilder.Invalidate(driverInstanceId)` first (no-op on first spawn; drops the
  stale pipelines on a respawn). Invalidation is per-instance — a sibling's warm pipeline survives.
- **`DriverSpawnPlanner`** treats a `ResilienceConfig` change (incl. `null → json`) as a **stop + respawn** (the
  invoker + its resolved options are bound to the child at spawn — the only way a change takes effect is to rebuild
  the child). A **pure `DriverConfig` change stays an in-place delta** (no reconnect) — the resilience pipeline is
  untouched. Snapshot (`DriverChildSnapshot`) carries `ResilienceConfig` for the diff.

## Design note — respawn vs. in-place swap

A ResilienceConfig-only change **reconnects the driver** (respawn). This is the simplest correct wiring: the
invoker/options are immutable for a child's lifetime (a clean invariant) and it reuses the existing stop+spawn
machinery. The alternative — an in-place invoker swap via a new actor message + mutable field — avoids the
reconnect but is materially more code for a rare operator-tuning op. Documented as a possible future optimization;
respawn is the v1.

## Verification (deterministic)

- **Override reaches execution** (`DriverCapabilityInvokerFactoryTests`): a `Read → retryCount:0` override
  suppresses the tier-A Read retry (1 attempt); a **control** with no config retries (>1) — proving the single
  attempt is the override, not a fluke.
- **Invalidate-on-create**: re-creating an instance's invoker drops its cached pipelines (count → 0); a sibling
  instance's pipeline survives (scoped).
- **Malformed config** logs a warning + still yields a working (tier-default) invoker.
- **Planner** (`DriverSpawnPlannerTests`): ResilienceConfig change → respawn (incl. `null → json`); pure
  DriverConfig change with unchanged resilience → delta.
- **Artifact** (`DeploymentArtifactTests`): the column is carried onto the spec / omitted → null.
- Core.Tests 243 (+5), Runtime.Tests 363 (+5), Host builds clean (0 warnings — dispatch sites untouched).

## Residual

None functional. A live rig check would fold into task #12 (the #10 live gate) — deploy a driver with a non-default
ResilienceConfig and confirm the observed retry/breaker behavior matches the override — but the deterministic tests
already prove the override reaches execution.
