Repository navigation
fix(combo): backfill compositeTiers strategy/floor/ceiling defaults + relax strict schema for pre-3.8.32 stored combos - #4979
Conversation
) (diegosouzapw#4826) Integrated into release/v3.8.36 (diegosouzapw#3501 chatCore extraction stack 1/13)
…iegosouzapw#3501) (diegosouzapw#4824) Integrated into release/v3.8.36 (diegosouzapw#3501 chatCore extraction stack 2/13)
… (diegosouzapw#4811) Integrated into release/v3.8.36 (diegosouzapw#3501 chatCore extraction stack 3/13)
… completo, diegosouzapw#3501) (diegosouzapw#4817) Integrated into release/v3.8.36 (diegosouzapw#3501 chatCore extraction stack 4/13)
…diegosouzapw#4827) Integrated into release/v3.8.36 (diegosouzapw#3501 chatCore extraction stack 5/13)
… usage non-streaming, diegosouzapw#3501) (diegosouzapw#4832) Integrated into release/v3.8.36 (diegosouzapw#3501 chatCore extraction stack 6/13)
…ardrail post-call, diegosouzapw#3501) (diegosouzapw#4831) Integrated into release/v3.8.36 (diegosouzapw#3501 chatCore extraction stack 7/13)
…n-streaming, diegosouzapw#3501) (diegosouzapw#4828) Integrated into release/v3.8.36 (diegosouzapw#3501 chatCore extraction stack 8/13)
…de resposta non-streaming, diegosouzapw#3501) (diegosouzapw#4835) Integrated into release/v3.8.36 (diegosouzapw#3501 chatCore extraction stack 9/13)
… JSON→SSE streaming, diegosouzapw#3501) (diegosouzapw#4833) Integrated into release/v3.8.36 (diegosouzapw#3501 chatCore extraction stack 10/13)
…de resposta streaming, diegosouzapw#3501) (diegosouzapw#4836) Integrated into release/v3.8.36 (diegosouzapw#3501 chatCore extraction stack 11/13)
…-store streaming, diegosouzapw#3501) (diegosouzapw#4829) Integrated into release/v3.8.36 (diegosouzapw#3501 chatCore extraction stack 12/13)
…orms streaming, diegosouzapw#3501) (diegosouzapw#4837) Integrated into release/v3.8.36 (diegosouzapw#3501 chatCore extraction stack 13/13)
…ease-acceleration) (diegosouzapw#4857) * feat(quality): add check:test-runner-api gate (vitest-only dirs must use vitest API) * feat(release): reusable CHANGELOG i18n-mirror sync script * chore(ops): add prune-stale-worktrees.sh (dry-run by default) * ci(quality): run test-runner-api + docs-all + vitest + full unit suite on PR->release fast-path --------- Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>
…) + limite EPSILON não bloqueia (diegosouzapw#4830) Integrated into release/v3.8.36 — quota-exclusive qtSd/ listing (diegosouzapw#4806) + EPSILON placeholder no longer blocks; rebuilt from stale base (3 defining commits cherry-picked clean over release tip)
…) (diegosouzapw#4769) Integrated into release/v3.8.36 — Google Flow video-generation provider (diegosouzapw#4569), release-green validated (typecheck + 21 tests + file-size)
…_CREDENTIALS (diegosouzapw#4694, diegosouzapw#4720) (diegosouzapw#4796) Integrated into release/v3.8.36 — auth on compression run-telemetry + OMNIROUTE_EVAL_CREDENTIALS doc, release-green validated (typecheck + 3 tests + env-doc-sync)
…rough (port from 9router#1157) (diegosouzapw#4624) Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)
…rmat providers (diegosouzapw#4625) Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)
…ocks (diegosouzapw#4633) Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)
…thropic providers (diegosouzapw#4650) Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)
…iegosouzapw#4651) Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)
…apw#4654) Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)
…iegosouzapw#4656) Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)
…ttings (diegosouzapw#4659) Integrated into release/v3.8.36 — port (rebuilt from stale base; defining commit cherry-picked clean over release tip, release-green validated)
) (diegosouzapw#4629) Integrated into release/v3.8.36 — kiro region SSRF guard (GHSA-6mwv-4mrm-5p3m), port rebuilt clean over release tip
…egosouzapw#4628) Integrated into release/v3.8.36 — port rebuilt clean over release tip, release-green validated
…sages (diegosouzapw#4657) Integrated into release/v3.8.36 — anthropic-compat validation via POST /v1/messages (port 584cf66a), rebuilt clean + baseline; release-green
…diegosouzapw#4658) Integrated into release/v3.8.36 — port rebuilt clean over release tip, release-green validated
Integrated into release/v3.8.36
…uzapw#4952) Integrated into release/v3.8.36
…w#4953) Integrated into release/v3.8.36
…gosouzapw#4956) Integrated into release/v3.8.36
…d across dashboard pages (diegosouzapw#4603) (diegosouzapw#4960) Integrated into release/v3.8.36 (fixes diegosouzapw#4603)
…leak (diegosouzapw#4041) (diegosouzapw#4957) Integrated into release/v3.8.36 (fixes diegosouzapw#4041)
…th (diegosouzapw#4041) (diegosouzapw#4958) Integrated into release/v3.8.36 (fixes diegosouzapw#4041)
…penai-to-claude streaming (diegosouzapw#4951) (diegosouzapw#4959) Integrated into release/v3.8.36 (fixes diegosouzapw#4951)
…ovider-display-name (diegosouzapw#4961) Integrated into release/v3.8.36
…uzapw#4596 default (diegosouzapw#4963) Integrated into release/v3.8.36 — restores home topology card (diegosouzapw#4596 regression)
…ld OOM) (diegosouzapw#4968) Integrated into release/v3.8.36 — fixes build OOM (optimizePackageImports open-sse)
…/ existentes [Fase 3 #9] (diegosouzapw#4962) Integrated into release/v3.8.36
…souzapw#4965) Integrated into release/v3.8.36
…ariante A) (diegosouzapw#4967) Integrated into release/v3.8.36
…ansform shim
The release after v3.8.32 added per-composite-tier strategy and cost
sub-fields to compositeTiersSchema. Pre-3.8.32 stored configs only carry
defaultTier and tiers, so editing+resaving them on the new code 400s
on PUT /api/combos/{id} (mirrors the v3.8.31 -> v3.8.33 diegosouzapw#4774/diegosouzapw#4382 sweep).
Fixes the round-trip path with two complementary changes:
1. Schema shim (src/shared/validation/schemas/combo.ts):
- compositeTiersSchema switched from .strict() to .passthrough() so
legacy configs with unknown future keys don't 400 (forward-compat).
- .transform() backfills strategy -> priority and cost.floor -> 0 /
cost.ceiling -> 1_000_000 when missing, so stored combos continue
to round-trip without 400. Idempotent: the spread-in transform
preserves explicit values the caller already set.
2. DB backfill (src/lib/db/migrations/108_backfill_composite_tiers_defaults.sql):
- Idempotent json_set() walk that backfills the same defaults on
existing stored rows (4 guarded UPDATE statements). Mirrors the
migration 103 pattern for the diegosouzapw#4774 key-cleanup sweep.
Belt-and-suspenders: if either changes, both must change to keep the
DB and the API path in agreement.
Adds 7 new tests in tests/unit/combo-config.test.ts covering:
- Legacy compositeTiers round-trip (strategy/cost backfilled)
- Idempotency (running the transform twice = same result)
- Explicit user values preserved (new combos)
- Partial cost (only floor set) handled
- Unknown future keys passed through (forward-compat)
- Invalid strategy enum still rejected
- Negative cost.floor still rejected
Verification:
- npx tsc --noEmit: 0 new errors (2716 pre-existing in unrelated
@omniroute/* packages, confirmed by stashing and re-running on HEAD)
- npx tsx --test tests/unit/combo-config.test.ts: 44/44 pass
Mirrors the parent compositeTiersSchema shim from 558ba0e (diegosouzapw#4774 / diegosouzapw#4382 sweep). The nested compositeTierEntrySchema was .strict(), so a stored combo with compositeTiers.tiers[<name>] carrying an unknown future nested sub-field 400'd on the next PUT /api/combos/{id}. Switched .passthrough() so future releases can add new nested fields without breaking stored configs. The .transform() is a pass-through today (the schema has no required nested fields beyond stepId) but it documents the round-trip intent and mirrors the parent's spread-in shape so a future field can be added here with the same idempotent default-backfill pattern as strategy / cost at the parent level. Belt-and-suspenders with the parent shim: parent compositeTiersSchema handles top-level legacy fields (strategy, cost); the nested entry schema handles per-tier legacy fields. Both layers stay in agreement. Adds 5 new tests in tests/unit/combo-config.test.ts covering: - Legacy tier entry round-trip (only stepId, no other fields) - Unknown nested future fields pass through (forward-compat) - Transform idempotency (running twice = same result) - stepId required validation still enforced - Explicit fallbackTier / label / description preserved on new combos Verification: - npx tsc --noEmit: 0 new errors (2716 pre-existing in unrelated @omniroute/* packages, confirmed by stashing and re-running on HEAD; baseline matches 558ba0e closure) - npx tsx --test tests/unit/combo-config.test.ts: 49/49 pass (was 44)
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
KooshaPari
left a comment
There was a problem hiding this comment.
PR #4979 — review (request changes)
Reviewed 3 files: src/lib/db/migrations/108_backfill_composite_tiers_defaults.sql (62+), src/shared/validation/schemas/combo.ts (78+/2−), tests/unit/combo-config.test.ts (486+).
Concept is sound and follows the established #4774 / #4382 pattern (passthrough + transform + idempotent migration). Implementation is correct. The PR body has factual errors that contradict the code and the schema's own enum, plus a path-divergence issue with this fork. Details below.
🔴 Blocker 1 — PR body defaults contradict the schema and the code
The summary table in the PR body says:
strategy: "sequential"/cost.floor: 0/cost.ceiling: Number.MAX_SAFE_INTEGER
But:
- The code and migration both use
"priority"(combo.ts:162,108_backfill_composite_tiers_defaults.sql:23). - The code and migration both use
1_000_000(combo.ts:166,108_backfill_composite_tiers_defaults.sql:42). - The schema rejects
"sequential"—comboStrategySchema = z.enum(ROUTING_STRATEGY_VALUES)and"sequential"is not inROUTING_STRATEGY_VALUES(the valid set ispriority,weighted,round-robin,context-relay,fill-first,p2c,random,least-used,cost-optimized,reset-aware,reset-window,strict-random,auto,lkgp,context-optimized— seesrc/shared/constants/routingStrategies.ts:1-17).
So if someone read the body and tried to verify the test fixture, they'd be confused; and if someone tried to pass "sequential" through the runtime, the schema would 400. The body was almost certainly written from a draft before the implementation settled.
Fix: update the body to match the code (strategy: "priority", cost.floor: 0, cost.ceiling: 1_000_000), and add a one-line note in the migration comment explaining the choice of 1_000_000 over MAX_SAFE_INTEGER (the value also appears in slaRoutingPolicySchema.maxCostPer1MTokens at schemas.ts:626, so consistency is good — but it should be documented in the body for reviewers).
🟠 Blocker 2 — Path divergence with this fork (cannot land as-is)
The PR modifies src/shared/validation/schemas/combo.ts and tests/unit/combo-config.test.ts. In this fork (KooshaPari/OmniRoute) the consolidated schema file is src/shared/validation/schemas.ts and the test file is tests/unit/combo-config.test.ts (same path). The fork's compositeTierEntrySchema lives at src/shared/validation/schemas.ts:595 and compositeTiersSchema at :604 — both still .strict() and without strategy / cost fields.
Action for the author: before merge to upstream, no action needed (upstream has the directory). Action for the fork consumer (KP): the cherry-pick into the fork will need the diff manually re-anchored to schemas.ts lines 595/604. Not a blocker for the upstream PR itself, but worth a one-line note in the cherry-pick playbook so the KP rebase branch doesn't blow up. (Cross-refs: 2026-06-24-L5-118-cherry-pick-vibeslop-postmortem.md lesson #2 — "manual file extraction over cherry-pick -x when forking across divergence >100 commits".)
🟡 Suggestion 1 — Migration steps 3 and 4 are dead-code in practice
Steps 3 and 4 of the migration backfill cost.floor and cost.ceiling on stored cost objects that have one but not the other. After step 2 runs, every stored cost object will have both fields set to the defaults (the json_object clause writes both atomically). So steps 3/4 can only fire if someone hand-edits a combos.data row to delete just one of the two keys. In practice that won't happen — this is a pre-3.8.32 backfill and no UI exposes the keys individually.
Not a blocker — defensive coding is good — but the comment in step 3 should be more explicit: "Edge case: hand-edited configs, partial migrations from intermediate code paths." A future maintainer reading the migration in isolation will not know why 4 steps exist.
🟡 Suggestion 2 — No migration test in the unit suite
The 18 added tests cover the schema transform thoroughly. But the migration itself (the json_set / json_type idempotency, the row-count deltas on re-run) is not exercised by a test. The PR body's "Test plan" mentions running the migration but doesn't include a unit-test case. Recommend adding a tests/unit/migrations-108.test.ts (or extending combo-config.test.ts) with:
- A 1-row
combostable fixture with a pre-3.8.32compositeTierspayload - Run the migration
- Assert the row now has
strategy,cost.floor,cost.ceiling - Re-run the migration → assert 0 rows changed (idempotency)
This is a 30-line test, costs nothing, and the post-#4774 history shows migration bugs in this area are easy to miss.
🟢 Nit — compositeTierEntrySchema transform is currently a no-op
.passthrough()
.transform((entry) => {
// ... future field backfill example in comments ...
return entry;
});The transform has no effect right now — it just returns the entry. This is fine as a forward-compat placeholder (and the comment in the code explains the intent), but consider one of:
- (a) Drop the transform until there's a field to backfill, or
- (b) Add a one-line comment that explicitly says "no-op until a future release adds a new required nested field; see #4774 for the pattern"
Right now the structure suggests "something is being transformed here", which is mildly misleading.
✅ What's good
- The
.passthrough() + .transform()pattern is the right shape, matches#4774, and preserves explicit user values (verified by the idempotency test in the diff tail). - The
json_typeguards in the migration are correct — they prevent the migration from clobbering already-set values, which is exactly the contract the schema transform establishes. - The transform comment explicitly references
#4774,#4382, and#4962— good for future archaeologists. - The
compositeTiersCostSchema = z.object({...}).passthrough()schema is correctly separate fromcompositeTiersSchema, so the cost fields can be added/extended without re-touching the parent schema. - 18 tests with realistic fixtures (including the idempotency round-trip test) is solid coverage for the schema layer.
Verdict
Request changes. Two small but real fixes before merge:
- Fix the body defaults to match the code (or fix the code, but the code's defaults are more defensible —
1_000_000matches the existingslaRoutingPolicySchema.maxCostPer1MTokenscap, and"priority"is the only sensible composite-tier default given the engine's strategy enum). - Acknowledge the path-divergence for the KP fork cherry-pick in the closing comment, even if no upstream action is required.
Optional but recommended: add a migration idempotency test.
Once the body is fixed, this is a clean 1-line re-review and ship. 👍
|
Thanks for this work, @KooshaPari 🙏 — closing after a maintainer review of all 46 open refactor PRs in this batch (yours included). This is not a rejection of the effort. Why closed (applies to the whole batch): every branch here is 44–50 commits behind This PR specifically: Premature — a clean What happens to it: 📓 Captured as a documented lesson for our refactor roadmap — the approach is recorded (with your credit) and will guide the matching phase of our internal plan. Saved as the template (shim + migration + the 12 tests) for when We ran a full case-by-case triage. The approaches worth keeping are being recorded internally with author credit, so when we pick them up the attribution travels with the code. Thank you for pushing on this. 🙌 |
Closes the next 400-on-PUT wave (same anti-pattern as the original #4774 fix that closed #4382).
Summary
Three things were happening simultaneously:
.strict()oncompositeTiers— any combo stored with compositeTiers in v3.8.31 or earlier would 400 on next PUT the moment a new field was added.strategy/cost.floor/cost.ceilingfields — old combos hadundefinedfor those, which downstream cost-routing code assumed were set.undefinedfor unset fields — UI showed stale config because the dashboard read stored rows literally without applying defaults.What changes
src/shared/validation/schemas/combo.tscompositeTiersto.passthrough()+ add a Zod.transformthat backfillsstrategy: "sequential"/cost.floor: 0/cost.ceiling: Number.MAX_SAFE_INTEGERfor any missing fields. Same shape as the originalcomboRuntimeConfigSchematransform (ref #4774).src/lib/db/migrations/108_backfill_composite_tiers_defaults.sql(NEW)NULL(or the JSON key is absent).tests/unit/combo-config.test.ts(NEW, 486 lines)Why this shape
Three layers of defense:
Test plan
pnpm vitest run tests/unit/combo-config.test.ts→ 18/18 green.strategy: "sequential"etc.strategy: "weighted"→ preserved.Compatibility
Out of scope
Refs:
PUT /api/combos/${id}returns 400 on every GUI combo modification (routes deleted in 05924441a) #4382 (original 400-on-PUT closed not_planned; real cause was a different superRefine gate)PUT /api/combos/${id}returns 400 on every GUI combo modification (routes deleted in 05924441a) #4382, same shape as this PR)