Skip to content

WW-69 design forks — recorded during build

Genuine ambiguities in SPEC-WW69.md where I picked a defensible answer and moved on, plus one literal-reading deviation. Flagging all for review.

1. The 8th "call site" (LlmStudyContentVerifier) is NOT wired to the router

The spec lists LlmStudyContentVerifier among the "8 taskClass call sites" to switch from selector.Default to router.SelectAsync(taskClass). On inspection, this class doesn't hold a selector at all — it takes a plain ILlmHarness via constructor injection, and the harness is chosen at DI-registration time in Program.cs (sp.GetRequiredService<ILlmHarnessSelector>().ById(verifierProvider), config key WorkWingman:StudyContentVerification:Provider, default "codex"). This is a genuine provider pin (config-driven, deliberately NOT the app default) — its whole purpose is model-vs-model cross-checking, so routing it dynamically by cost class could put the SAME model on both sides of the check. Per the spec's own exception ("If a call site currently pins a specific provider for correctness ... keep that pin — add the router only where .Default was used"), I left this one untouched. Only 7 of the 8 named sites actually used .Default; all 7 are wired.

Deviation, with reason: literal reading of the spec would wire all 8; I wired 7 and left LlmStudyContentVerifier on its existing config-driven pin, because routing it would defeat its cross-model-check purpose and it never used .Default to begin with.

2. RoutingDecision.RuleSource = "UserOverride" — precedence when it coexists with other signals

The spec defines four RuleSource values but doesn't fully specify precedence when more than one applies at once (e.g., userOverride: true AND the rule was user-edited; or userOverride: true AND routing is disabled for that taskClass). I implemented this precedence in LlmRouter.SelectAsync, evaluated in order:

  1. GovernorForced — wins unconditionally whenever the governor's tier is Red/Blackout or PreferCheapOrLocal is true, matching the binding "governor always wins" rule literally.
  2. Default — routing disabled for this taskClass (RoutingRule.Enabled == false); the configured default harness is used regardless of any userOverride flag.
  3. UserOverride — the caller passed userOverride: true and neither of the above applied.
  4. UserRule vs Default — compares the stored rule's PreferredCostClass against RoutingRuleDefaults.Seed[taskClass]; equal → Default (never edited), different → UserRule.

This is provisional — if the real UX never surfaces a caller-side userOverride flag independently of the governor's own Blackout cost-warning flow, UserOverride may end up rarely/never observed in practice. It's wired and tested (LlmRouterTests.SelectAsync_UserOverrideRequested_...) either way.

3. LlmCostClass.Cheap has no harness that classifies into it today

Per the spec's literal cost-ranking algorithm (Kind=="local" → Local; provider-id ordinal map → Mid/Strong; unknown → Mid), nothing maps to Cheap — there's no cheap-key provider in the harness fleet yet. A rule that prefers Cheap (JobFitNarrative, ResearchGrounding) always walks up the chain to whatever's cheapest AVAILABLE (typically Mid today). This is intentional per the spec as written, not a bug — Cheap exists as a slot for a future cheap-key harness (e.g. a "-mini" tier) to occupy without a router change. Documented in LlmCostClassifier's XML doc and covered by LlmRouterTests.SelectAsync_FallsBackUpTheChain_WhenPreferredClassUnavailable.

4. RoutingDecision.GovernorTier/.Reason record the ORIGINAL (pre-downgrade) governor decision

When the governor forces a downgrade, LlmRouter does NOT re-query the governor against the newly-chosen (often local/unmetered) harness for the persisted record — it keeps the tier/reason from the decision that TRIGGERED the downgrade. Re-querying against the new harness would usually report an unrelated Green state (a fresh Ollama harness has no plan settings) and obscure why forcing happened. This felt like the more honest provenance choice; flagging in case the intent was "the tier of whoever ends up serving the call."

5. RoutingRulesGrid built as its own standalone Angular component

The spec names RoutingRulesGrid as a component deliverable; the existing AI-efficiency UI is otherwise all inlined into the monolithic Metrics component. I built it as a genuinely separate, independently unit-tested component (features/metrics/routing-rules-grid/) rather than inlining its logic into Metrics, both to match the literal naming and to keep its PUT/error-state tests isolated. It's imported into Metrics's imports array and slotted via <app-routing-rules-grid /> inside the "Keys & Plans" card, in a new "Routing rules" subsection.

6. Live-Ollama smoke tests needed no CI exclusion after all

The gate instructions anticipated needing --filter "FullyQualifiedName!~LiveSmoke&FullyQualifiedName!~LocalRagLive" for a flaky live-Ollama test. I ran the full suite with no filter on this box — LocalRagLiveSmokeTests already self-skips cleanly when Ollama isn't reachable (its own early-return, not a build-time exclusion), so dotnet test tests/WorkWingman.Tests with zero filter went green at 3339/3339. No deviation was needed; noting it since the gate explicitly asked me to say so either way.