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}.sql—org_unit_governance_policies+member_governance_policies+ RLS + indexes.internal/governance/cascade.go—EvaluateCascadewalker with 4-layer precedence + sticky-deny aggregation + CascadeTrace.internal/governance/cascade_query.go—PGCascadeStoresingle-SQL-round-trip loader overorg_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.checkCascadewired viaWithCascadeStoreincmd/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:
- 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. - 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 atcheckCascadeentry so it includes theLoadCascadeSQL round-trip (matches the SLO the HLD specifies). Pattern mirrorsinternal/orgunit/dualwrite/reconciler.goOpenDriftgauge which the architect flagged on PR #600 as the reference.
v_team_gov_policiesshim view — SKIPPED. Agrep -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 perdocs/retrospectives/2026-04-15-shelfware-pattern.md.
Delivered in #564 (closed by narrowing #586)
| HLD v1.1 §3.4 / §6 requirement | Delivery |
|---|---|
Migration 063: org_unit_governance_policies + member_governance_policies + RLS + indexes | PR #564 |
| 4-layer cascade walker with ancestor_path traversal | internal/governance/cascade.go (PR #564) |
| Single-SQL-query loader over ancestor_path | PGCascadeStore (PR #564) |
| Precedence + sticky-deny + clearance-floor semantics | cascade_test.go (PR #564) |
CheckResponse.conflict_id reserved tag; new cascade RPCs | PR #564 + Wave E conflict wiring (PR #565) |
| Backwards-compat: zero org_unit policies → legacy 3-layer behaviour | Guarded 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 item | Delivery |
|---|---|
| Benchmark at ancestor depth 10 | internal/governance/cascade_bench_test.go |
| Cascade observability metrics | internal/governance/cascade_metrics.go + wire-up in checkCascade |
| ADR recording the renumbering + Option B narrowing | This document |
v_team_gov_policies shim | SKIPPED — 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_secondshistogram 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_policiesview 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.