Files
OmniRoute/tests/unit/model-lifecycle-degradation-map.test.ts
Paco Cartones e09cb5a768 chore(lifecycle): gate DEFAULT_DEGRADATION_MAP against retired ids (#12535)
Third hand-maintained table naming model ids and the only one outside the retired-model gate — extending `check-model-lifecycle.mjs` to cover it is the durable fix, and the three retired rows it flushed out were already dead code behind the 410 `model_shutdown` answer.

---

Validated in one consolidated worktree cut from `release/v3.8.51`, boarded with the rest of this batch — zero conflicts between the 19 PRs.

- `typecheck:core` clean; `check:dashboard-typecheck` OK (206 pre-existing, all within the frozen baseline); `check:changelog-integrity` OK
- complexity 2802 / baseline 3218 and cognitive-complexity 1267 / baseline 1437 — both under baseline
- 226 of 228 focused assertions green across the batch's 23 test files. The 2 remaining belong to #12551, which is held separately.

Two batch-owned defects were found and fixed in flight, both pure base drift: `173_xp_action_counts.sql` collided with `173_call_logs_video_content_removed.sql` (renumbered to 176 on #12651 — it aborted every DB open, which is what 53 of the first run's failures were), and the feature-flag catalog was missing the `SERVER_OWNED_TOOL_LOOP_ENABLED` row the base gained after #12552 was written.

⚠️ base-red inherited: #12732 — `Docs Gates`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` reproduce on the pure tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and `open-sse/utils/stream.ts` at 3115 > frozen 3098, which this batch does not touch).

Thanks @pacocartones — the `file:line` citations and the explicit out-of-scope notes on every one of these made a 19-PR batch reviewable in one pass.
2026-09-11 17:42:16 -03:00

57 lines
2.3 KiB
TypeScript

/**
* Follow-up to #11503 / #11507: `DEFAULT_DEGRADATION_MAP` (backgroundTaskDetector.ts) is the
* third hand-maintained routing table that names model ids, and it was outside the
* retired-model gate. A retired *source* is a dead row — `checkLifecycle` answers 410
* `model_shutdown` before `resolveBackgroundTaskRedirect` runs — and a retired *target*
* is normally rejected with 410 when lifecycle validation runs again after the redirect,
* unless alias resolution maps it to an accepted id.
*
* Table-driven over the production default map and the checked-in lifecycle snapshot, mirroring
* `model-deprecation-aliases-11503.test.ts`, so a new dead row fails by name.
*/
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { getDefaultDegradationMap } from "../../open-sse/services/backgroundTaskDetector.ts";
import { isVendorRetiredId } from "../../open-sse/services/modelLifecycle.ts";
const lifecycle = JSON.parse(
readFileSync(
fileURLToPath(new URL("../../config/quality/model-lifecycle.json", import.meta.url)),
"utf8"
)
) as { retired: Record<string, { status: string }> };
const retiredIds = new Set(
Object.entries(lifecycle.retired)
.filter(([, entry]) => entry.status === "retired")
.map(([id]) => id.toLowerCase())
);
describe("DEFAULT_DEGRADATION_MAP names no retired model id", () => {
const rows = Object.entries(getDefaultDegradationMap());
it("has rows to check", () => {
assert.ok(rows.length > 0);
});
for (const [source, target] of rows) {
it(`degrades from ${source}, an id the vendor has not retired`, () => {
assert.ok(
!retiredIds.has(source.toLowerCase()),
`"${source}" → "${target}" is dead: the vendor has retired "${source}", so checkLifecycle rejects the request before the background redirect runs`
);
assert.equal(isVendorRetiredId(source), false);
});
it(`degrades ${source} to ${target}, an id the vendor has not retired`, () => {
assert.ok(
!retiredIds.has(target.toLowerCase()),
`"${source}" → "${target}" forwards background tasks to "${target}", which the vendor has retired`
);
assert.equal(isVendorRetiredId(target), false);
});
}
});