Two parallel refactors building on the model-registry consolidation:
1. Generation-aware failover (model-route-failure.js, agent-end-recovery.js)
- resolveNextModelRoute now takes unitType so it knows whether the
caller is solver-pinned per ADR-0079 (autonomous-solver). When pinned,
rejects candidates whose canonicalIdFor() differs from the failed
route's canonical id — closes the latent solver-invariant violation
where kimi-coding/kimi-k2.6 could silently fail over to
ollama-cloud/kimi-k2.5:cloud (different generation).
- Cross-generation failover in non-pinned units now emits a structured
logWarning so generation downgrades are visible in traces instead of
looking like an equivalent route switch.
2. Canonical-keyed performance metrics (model-learner.js)
- .sf/model-performance.json now keys by canonical_id with an
{aggregate, by_route} sub-shape instead of fused provider/wire-model
strings. Cross-route history per model is now coherent — kimi-k2.6
reached via kimi-coding accumulates into the same aggregate as
reached via openrouter.
- Migration runs at boot: detects old shape (no 'aggregate' key in
unit-type blob values), distributes each entry into by_route,
recomputes aggregate, writes a backup to
.sf/model-performance.json.pre-canonical-backup. Unmappable route
keys land in _unmapped so nothing is dropped.
- getRouteStats(taskType, routeKey) added for per-route failover
ordering; existing getRankedModels emits canonical IDs for
cross-route strength queries.
3. Tests
- model-registry.test.ts: bundled in this commit (Swarm A's test file
was left untracked when the registry module was committed).
- model-route-failure.test.ts: 12 tests covering solver-pin guard,
same-canonical multi-route failover, generation-downgrade log emit.
- model-learner-canonical.test.ts: 17 tests covering migration
round-trip, aggregate invariant, _unmapped bucket, and zero-default
reads.
- model-learner.test.ts: one existing test updated for the new
_unmapped.by_route shape on bare model IDs.
4. Results
- Targeted tests: 147/147 across registry, route-failure, learner,
learner-canonical.
- Full npm run test:unit: 4707 pass, 0 fail, 83 skipped (no new
regressions vs pre-edit baseline of 4669).
Work parallelized across two Sonnet 4.6 sub-agents in isolated git
worktrees. Contract authored in docs/dev/drafts/model-registry-contract.md
(committed earlier in 1d753af6b) and consumed by both agents.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>