Skip to main content

ADR-0005: Wave E narrowed to residuals — cascade evaluator already shipped under Wave D numbering

Status: Accepted Date: 2026-04-17 Decision: Close issue #586 as narrow-scope "residuals only" (benchmark + 2 observability metrics + optional view shim) because PR #564 already delivered the cascade evaluator, migration 063, and the 4 cascade RPCs under Wave D numbering before HLD v1.1 re-planned the wave taxonomy. Related: PRD #549, HLD #556 (v1.1 Wave E scope), LLD #560 addendum, Issues #564, #586. Founder direction: #586 comment 4266876785.

Context

PRD #549 / HLD #556 organised governance delivery into waves. In the v1.0 plan, the 4-layer cascade evaluator (platform / org / org_unit walk / member) lived inside "Wave D". PR #564 (merged 2026-04-16) delivered exactly that:

  • internal/context/store/migrations/063_policy_cascade.{up,down}.sqlorg_unit_governance_policies + member_governance_policies + RLS + indexes.
  • internal/governance/cascade.goEvaluateCascade walker with 4-layer precedence + sticky-deny aggregation + CascadeTrace.
  • internal/governance/cascade_query.goPGCascadeStore single-SQL-round-trip loader over org_units.ancestor_path.
  • internal/governance/cascade_test.go — 16-row truth table + sticky-deny + closest-ancestor + clearance-floor coverage.
  • 4 new Connect RPCs (ListOrgUnitPolicies, PutOrgUnitPolicy, PutMemberPolicy, EvaluateDryRun).
  • DefaultEngine.checkCascade wired via WithCascadeStore in cmd/context-engine/main.go.

When HLD v1.1 landed (2026-04-17), it renumbered the same work into "Wave E" and opened issue #586 to track it — without knowing #564 had already shipped the core of the wave. Founder direction (Option B) narrowed #586 to the remaining residuals rather than re-opening #564's scope or reverting it.

Decision

Narrow #586 to the three residuals from HLD v1.1 §3.4 / §6 that are NOT already on main:

  1. Benchmark (internal/governance/cascade_bench_test.go) — BenchmarkCascade_AncestorPathWalk_Depth10 + variable-depth sweep, proving the in-memory walker stays well under the HLD §6 "p95 < 5ms @ depth 10" SLO. End-to-end (SQL + walk) SLO compliance remains observable via the new duration histogram.
  2. Metrics (internal/governance/cascade_metrics.go):
    • governance_cascade_evaluations_total{verdict,levels_walked} — counter, labels capped at 3 × 5 = 15 series.
    • governance_cascade_duration_seconds — histogram bucketed 0.5ms → 256ms, recorded at checkCascade entry so it includes the LoadCascade SQL round-trip (matches the SLO the HLD specifies). Pattern mirrors internal/orgunit/dualwrite/reconciler.go OpenDrift gauge which the architect flagged on PR #600 as the reference.
  3. v_team_gov_policies shim viewSKIPPED. A grep -r "v_team_gov_policies" over the tree returns zero callers in Go, SQL migrations, or docs. The HLD v1.0 acceptance criterion was speculative ("until Wave J"); no consumer emerged and Wave F / J plans no longer reference it. Adding an unused view would be shelfware per docs/retrospectives/2026-04-15-shelfware-pattern.md.

Delivered in #564 (closed by narrowing #586)

HLD v1.1 §3.4 / §6 requirementDelivery
Migration 063: org_unit_governance_policies + member_governance_policies + RLS + indexesPR #564
4-layer cascade walker with ancestor_path traversalinternal/governance/cascade.go (PR #564)
Single-SQL-query loader over ancestor_pathPGCascadeStore (PR #564)
Precedence + sticky-deny + clearance-floor semanticscascade_test.go (PR #564)
CheckResponse.conflict_id reserved tag; new cascade RPCsPR #564 + Wave E conflict wiring (PR #565)
Backwards-compat: zero org_unit policies → legacy 3-layer behaviourGuarded by cascadeStore != nil && MemberID != "" in engine.go (PR #564)
Conflict detection surface (ConflictRecorder seam + opposing-verdict detector)Delivered in #565 (Wave E Conflict service) on top of the cascade foundation

Residuals delivered in this PR

HLD v1.1 line itemDelivery
Benchmark at ancestor depth 10internal/governance/cascade_bench_test.go
Cascade observability metricsinternal/governance/cascade_metrics.go + wire-up in checkCascade
ADR recording the renumbering + Option B narrowingThis document
v_team_gov_policies shimSKIPPED — zero consumers, would be shelfware

Why this is acceptable

  • No code regression: every HLD v1.1 §3.4 / §6 acceptance criterion is now on main (either via #564 or this PR), or deliberately dropped with reason (the view).
  • Audit trail is preserved: #564's merge commit remains in main; #586 is closed as narrowed, with this ADR + PR as the bridge documenting the wave renumbering.
  • Future maintenance is unaffected: the evaluator, RPCs, and migration carry their Wave-E-era names even though the commit predates the renumbering. No compat shim required.
  • Observability parity with HLD §6 SLO: the new governance_cascade_duration_seconds histogram measures end-to-end (SQL + walk), which is what the "p95 < 5ms at depth 10" line explicitly targets. The standalone bench proves the walker alone is 3.6µs at depth 10 (~1,400× headroom).

Alternatives considered

  • Re-implement the cascade under #586 and supersede #564 — rejected: cost without benefit; same code would land twice and #564 already has architect + QA approval.
  • Revert #564 and re-merge under #586 — rejected: destroys audit trail, breaks every downstream branch that depends on the cascade evaluator (notably #565 Wave E Conflict service already shipped on top).
  • Ship the v_team_gov_policies view anyway for forward-compat — rejected: no consumer identified in the tree or in planned Wave F / J docs; would be shelfware.

Follow-ups

None required. Wave F begins from main as-is.