koder_shadow_kit

  • Path: engines/sdk/koder_shadow_kit
  • Kind: Go SDK library (dependency-free; stdlib only)
  • Spec: meta/docs/stack/rfcs/stack-RFC-011-shadow-parallel-run-validation.kmd (ratified 2026-06-06)
  • Backlog prefix: SHADOW

Role in the stack

The shadow / parallel-run validation harness — Tier-1 of stack-RFC-011. It lets a risky new backend implementation run in shadow of the authoritative one against production traffic: the authoritative path is always what the caller gets, while the candidate (shadow) path runs in parallel and is compared, never trusted, until parity is proven. This retires the gated test window (which serializes work across the Stack) as the default mechanism for data-risky changes — validation becomes continuous and non-blocking.

It operationalizes always-on.kmd R3.1 (expand→migrate→contract) by adding the automated read-compare + promotion gate that the manual discipline lacked, and its promotion gate mirrors the G1/G2 parity gates of self-hosted-first.kmd.

Invariants (RFC §5)

  • I1 Authoritative independence — the candidate never blocks, delays, or fails

    the authoritative call. It runs asynchronously on a context.WithoutCancel context, panic-guarded; its errors/panics are logged, never surfaced.

  • I2 Shadow never trustedCompare always returns the control value,

    even on divergence. Promotion (flipping authority) is a separate gated decision.

  • I3 Side-effect-free only — shadow only PURE storage/transform paths, never

    operations with external side effects (email, billing, third-party writes).

API surface

Symbol Purpose
Harness + New(Config, …Option) Non-generic config/sinks holder. Config{Enabled, SampleRate} (zero value = disabled pass-through).
Compare[T](ctx, h, name, control, candidate, cmp) (T, error) Read-style: runs control sync (returns it); when enabled+sampled+control-ok, runs candidate async and compares.
(*Harness) Shadow(ctx, name, control, candidate) error Write-style: control authoritative; candidate mirrored best-effort async (only on control success).
Comparator[T] + EqualDeepEqualSemantic(SemanticOptions) Comparison. Semantic = JSON-canonical recursive compare with IgnorePaths (dot-path + * wildcard), OrderInsensitive, FloatTolerance.
Kinds KindEqualKindValueMismatchKindShapeMismatch Bounded divergence taxonomy — safe as a metric label.
Gate + NewGate(PromotionPolicy{MinSamples, MinElapsed}, …)Verdict() PromotionVerdict Telemetry-driven promotion gate (G-parity: 0 divergence over ≥N samples AND ≥T soak). WithGate(g) auto-feeds it from Compare.
Options WithOnDivergence, WithSampler, WithLogger, WithGate, WithClock (gate).
Metric-name constants MetricDivergenceTotal (shadow_divergence_total{component,divergence_kind}), MetricShadowReadTotal — the obs contract; the consumer emits via OnDivergence (kit stays dep-free).

Adoption sketch

gate := shadow.NewGate(shadow.DefaultPromotionPolicy())
h := shadow.New(shadow.Config{Enabled: flag, SampleRate: 0.05},
    shadow.WithGate(gate),
    shadow.WithOnDivergence(func(d shadow.Divergence) {  }))

val, err := shadow.Compare(ctx, h, "store.read",
    func(ctx) (Row, error) { return oldStore.Read(ctx, k) },   // authoritative
    func(ctx) (Row, error) { return newStore.Read(ctx, k) },   // candidate
    shadow.Semantic[Row](shadow.SemanticOptions{IgnorePaths: []string{"updated_at"}}))
// val/err are always the authoritative result; divergences flow to the gate/sink.
// Promote (flip authority) only when gate.Verdict().Ready.

Status

  • Tier-1 (this module) — Phase 1 COMPLETE (SHADOW-001..003, 2026-06-06):

    core harness, semantic comparator, promotion gate. Verified off-laptop (dev-linux-id): build/vet clean, go test -race green (21 tests).

  • SHADOW-004 dogfood — mechanism proven in-package (dogfood_test.go); the

    live soak is gated_by: evidence-soak (needs a chosen prod consumer).

  • Tier-2 (kdb-native, RFC §4.2) — SHADOW-005, pending in infra/data/kdb

    (Rust): a shadow applier materializes a new storage format from the same WAL (reusing the RFC-008 WalApplier, #776) and compares — authoritative write path untouched, zero hot-path cost, zero data-loss risk.

  • Phase 3 — SHADOW-006: update always-on.kmd so CONTRACT-class/data-risky

    changes default to the shadow gate; the window becomes the exception.

Primary couplings

  • engines/sdk/go/obs — the consumer wires OnDivergence + the sample counter to

    the obs SDK (REDmetricslogs). The kit itself takes no obs dependency.

  • infra/data/kdb (RFC-008 WAL) — the Tier-2 substrate (#005).
  • always-on.kmd, self-hosted-first.kmd — the policies this primitive

    operationalizes.