Files
OmniRoute/tests/unit
MumuTW 1d44ee00f8 refactor(sse): extract combo dispatch prelude into combo/dispatchPrelude.ts (#8582)
* refactor(sse): extract combo dispatch prelude into combo/dispatchPrelude.ts

Pure move, no behaviour change. First of ~7 PRs decomposing the combo.ts
god-file (#3501).

handleComboChat evaluates a series of dispatch branches before it ever
reaches target resolution or the sequential attempt loop. None of them
iterate targets in priority order or need the failover/retry/credential
gate machinery that follows, so they move to a leaf:

  - context-cache pin routing (Fix #679), including the
    pinIsDurablyUnhealthy / isPinnedModelDurablyUnhealthy health gate
  - fusion panel dispatch + the #6455 misconfiguration warn
  - pipeline chaining
  - nested combo-ref execute-mode runtime-unit dispatch

Only the chaos and round-robin hand-offs stay inline (11 and 13 lines);
extracting those would be pure indirection.

open-sse/services/combo.ts 3642 -> 3341 (-301)
open-sse/services/combo/dispatchPrelude.ts: 619 (under the 800 cap)

Each helper keeps the fall-through protocol the inline blocks had: return
a Response to OWN the request, return null to fall through. A flipped
null/Response would silently bypass the whole combo strategy, so the new
tests pin both directions for every branch.

combo.ts re-exports pinIsDurablyUnhealthy so combo-pin-health-gate.test.ts
keeps resolving. The leaf takes handleComboChat as a `runCombo` parameter
instead of importing it, so combo/ keeps zero back-edges into combo.ts.

Complexity-neutral: the first cut added +3 violations (two
max-lines-per-function, one complexity) inside the new leaf, so
evaluatePinnedResponse, orderRuntimeUnits, recordRuntimeUnitStickySuccess
and buildBaseOptions were split out. check:complexity now measures 2169
and check:cognitive-complexity 956 — identical to the pristine base.

* test(sse): close the dispatch-prelude coverage holes found by mutation testing

An adversarial mutation audit of the suite added in the previous commit
found it guarded the fall-through protocol well but asserted almost
nothing about what the helpers do once they OWN the request. 5 of 12
seeded mutations survived. Worst case: deleting the pinned-model
dispatch call outright left all 12 tests green.

Three holes, now closed (8 tests -> 20):

Hole A — the honored-pin path had zero coverage. Both existing pin tests
DROP the pin, so the dispatch, the 200-but-empty quality gate, the
[408, 429, 500, 502, 503, 504] failover list and the catch(pinErr)
branch were unguarded — exactly the logic the 2026-06-21 / 2026-06-22
incident comments call load-bearing. Adds five tests over a seeded
healthy provider connection so the pin is actually honored.

Hole B — orderRuntimeUnits was only ever driven with `priority`, which
is a no-op through it. Four of five strategy branches could be deleted
with nothing failing. Adds round-robin rotation and weighted sticky
ordering tests.

Hole C — recordRuntimeUnitStickySuccess never did anything under test:
both its guards need weighted/round-robin, so an early return changed
nothing. Covered by the new sticky-batch test.

Verified by re-running the mutations rather than assuming: all 7 that
previously survived (delete-pin-dispatch, serve-despite-failed-quality,
never-fail-over-on-transient, rr-counter-not-advanced, rotation-removed,
weighted-sticky-skipped, sticky-recording-no-op) are now killed.

The first sticky-batch test I wrote was itself vacuous — asserting "same
unit twice" holds equally when the recording helper is stubbed out, since
nothing advances the counter either. It now asserts the batch runs out
and rotation resumes on the third dispatch, which is what actually
distinguishes the two.

Also restores API_KEY_SECRET in test.after; it was set at module load and
never put back, inconsistent with the DATA_DIR handling beside it.

* fix(ci): teach known-symbols gate the relocated fusion/pipeline dispatch

The combo sub-check of check:known-symbols asserts every canonical routing
strategy has a real dispatch branch. It scanned a hardcoded file list and
matched only `strategy === "..."`, so the prelude extraction tripped it twice:

  [combo] 2 estratégia(s) canônica(s) sem branch de despacho em combo.ts:
      ✗ fusion
      ✗ pipeline

Both branches are still wired — they just moved to combo/dispatchPrelude.ts and
took the early-return guard form `if (strategy !== "fusion") return null;` that
extracting a branch into a `tryXDispatch()` leaf naturally produces.

Two changes, both extending existing precedent (the list already carries the
Block J leaves for the same reason):

- register combo/dispatchPrelude.ts in comboDispatchFiles
- widen the extractor to `strategy [!=]== "..."` so the inverted guard counts

Loose `==`/`!=` stay rejected, and no `handledNotCanonical` fallout: the gate
now reports 20 canonical strategies, all 20 via despacho.

* chore(ci): register combo-dispatch-prelude test in stryker tap.testFiles

check:mutation-test-coverage --strict failed once the known-symbols fix let
Fast Quality Gates advance to it:

  ✗ 2 covering unit test(s) across 2 module(s) are missing from
    stryker.conf.json tap.testFiles
      open-sse/services/combo/comboStructure.ts
      open-sse/services/combo/rrState.ts

The new tests/unit/combo-dispatch-prelude.test.ts exercises both modules, and
both are already in stryker's mutate list, so without the registration its
mutant kills would not have counted toward the nightly mutation gate.

Note (unchanged, still out of scope): combo/dispatchPrelude.ts itself is not in
stryker's `mutate` list. Adding it would widen the nightly mutation surface,
which is a separate call from fixing this drift.

* docs(changelog): add fragment for #8582 combo dispatch prelude
2026-07-27 19:06:56 -03:00
..
2026-07-02 10:47:13 -03:00
2026-06-30 06:54:29 -03:00
2026-06-17 19:26:32 -03:00
2026-06-09 22:57:12 -03:00
2026-06-30 06:54:29 -03:00
2026-06-29 08:40:06 -03:00
2026-07-02 10:47:13 -03:00
2026-06-25 13:17:40 -03:00
2026-06-29 16:51:03 -03:00
2026-05-23 01:46:59 -03:00
2026-06-28 06:58:29 -03:00
2026-06-27 09:07:12 -03:00
2026-05-29 12:44:29 -03:00
2026-06-23 17:06:18 -03:00
2026-06-13 17:27:40 -03:00
2026-07-13 09:12:40 -03:00
2026-06-19 06:49:01 -03:00
2026-07-13 09:12:40 -03:00
2026-07-04 13:00:30 -03:00
2026-07-02 10:47:13 -03:00
2026-05-29 12:44:29 -03:00
2026-06-29 16:51:03 -03:00
2026-05-29 12:44:29 -03:00
2026-06-27 09:07:12 -03:00
2026-05-24 18:05:58 -03:00
2026-06-17 19:26:32 -03:00
2026-05-26 23:51:47 -03:00
2026-07-13 09:12:40 -03:00
2026-07-13 09:12:40 -03:00
2026-06-22 03:17:02 -03:00
2026-06-29 08:40:06 -03:00
2026-07-13 09:12:40 -03:00
2026-06-13 17:27:40 -03:00
2026-06-16 01:00:40 -03:00
2026-07-07 13:14:06 -03:00
2026-06-29 08:40:06 -03:00
2026-06-21 08:56:51 -03:00
2026-05-26 23:51:47 -03:00
2026-06-21 08:56:51 -03:00
2026-06-23 03:08:29 -03:00
2026-05-29 12:44:29 -03:00
2026-06-29 08:40:06 -03:00
2026-05-26 23:51:47 -03:00
2026-07-13 09:12:40 -03:00
2026-05-24 18:05:58 -03:00
2026-05-26 23:51:47 -03:00
2026-05-29 12:44:29 -03:00
2026-05-10 00:55:06 -03:00
2026-05-26 23:51:47 -03:00
2026-06-13 17:27:40 -03:00
2026-06-29 08:40:06 -03:00
2026-06-27 09:07:12 -03:00
2026-06-29 16:51:03 -03:00
2026-04-30 01:27:03 -03:00
2026-04-30 01:27:03 -03:00
2026-07-07 13:14:06 -03:00
2026-06-23 17:06:18 -03:00
2026-07-04 13:00:30 -03:00
2026-07-04 13:00:30 -03:00
2026-06-26 02:51:06 -03:00
2026-07-07 13:14:06 -03:00
2026-06-29 16:51:03 -03:00
2026-05-26 23:51:47 -03:00
2026-04-30 14:08:50 -03:00
2026-06-29 16:51:03 -03:00
2026-07-13 09:12:40 -03:00
2026-07-13 09:12:40 -03:00
2026-06-17 02:43:21 -03:00
2026-05-26 23:51:47 -03:00
2026-07-06 02:25:17 -03:00
2026-06-15 03:32:11 -03:00
2026-05-26 23:51:47 -03:00
2026-07-13 09:12:40 -03:00
2026-07-07 13:14:06 -03:00
2026-07-07 13:14:06 -03:00
2026-07-07 13:14:06 -03:00
2026-06-20 14:55:24 -03:00
2026-06-22 03:17:02 -03:00
2026-07-13 09:12:40 -03:00
2026-07-04 13:00:30 -03:00
2026-06-26 02:51:06 -03:00
2026-07-04 13:00:30 -03:00
2026-06-27 09:07:12 -03:00
2026-06-19 06:49:01 -03:00
2026-06-20 07:09:43 -03:00
2026-07-07 13:14:06 -03:00
2026-07-07 13:14:06 -03:00
2026-04-30 01:27:03 -03:00
2026-06-22 03:17:02 -03:00
2026-07-07 13:14:06 -03:00
2026-07-07 13:14:06 -03:00
2026-06-19 06:49:01 -03:00
2026-06-12 23:49:22 -03:00
2026-06-12 23:49:22 -03:00
2026-07-13 09:12:40 -03:00
2026-06-11 18:52:29 -03:00
2026-06-29 08:40:06 -03:00
2026-06-30 06:54:29 -03:00
2026-07-04 13:00:30 -03:00
2026-06-30 06:54:29 -03:00
2026-06-29 16:51:03 -03:00
2026-07-04 13:00:30 -03:00
2026-06-29 08:40:06 -03:00
2026-07-07 13:14:06 -03:00
2026-05-23 01:46:59 -03:00
2026-05-23 01:46:59 -03:00
2026-06-09 15:56:24 -03:00
2026-05-29 19:54:00 -03:00
2026-06-29 08:40:06 -03:00
2026-07-07 13:14:06 -03:00
2026-06-17 19:26:32 -03:00
2026-06-13 17:27:40 -03:00
2026-06-17 02:43:21 -03:00
2026-07-07 13:14:06 -03:00
2026-06-30 06:54:29 -03:00
2026-07-04 13:00:30 -03:00
2026-05-24 18:05:58 -03:00
2026-06-30 06:54:29 -03:00
2026-06-29 08:40:06 -03:00
2026-04-28 20:46:25 -03:00
2026-07-13 09:12:40 -03:00
2026-07-13 09:12:40 -03:00
2026-07-13 09:12:40 -03:00
2026-07-13 09:12:40 -03:00
2026-05-29 12:44:29 -03:00
2026-06-19 06:49:01 -03:00
2026-06-29 16:51:03 -03:00
2026-06-29 08:40:06 -03:00
2026-06-23 03:08:29 -03:00
2026-05-26 23:51:47 -03:00
2026-06-30 06:54:29 -03:00
2026-06-12 23:49:22 -03:00
2026-07-04 13:00:30 -03:00
2026-07-02 10:47:13 -03:00
2026-06-16 01:00:40 -03:00
2026-05-26 23:51:47 -03:00
2026-06-04 20:05:38 -03:00
2026-06-29 08:40:06 -03:00
2026-06-12 23:49:22 -03:00
2026-07-07 13:14:06 -03:00
2026-06-29 16:51:03 -03:00
2026-06-29 08:40:06 -03:00
2026-05-29 12:44:29 -03:00
2026-07-07 13:14:06 -03:00
2026-06-20 07:09:43 -03:00
2026-06-19 06:49:01 -03:00
2026-06-07 07:20:02 -03:00
2026-06-11 04:01:24 -03:00
2026-06-29 08:40:06 -03:00
2026-05-24 18:05:58 -03:00
2026-05-26 23:51:47 -03:00
2026-06-12 23:49:22 -03:00
2026-06-16 01:00:40 -03:00
2026-06-12 23:49:22 -03:00
2026-06-27 09:07:12 -03:00
2026-07-07 13:14:06 -03:00
2026-07-04 13:00:30 -03:00
2026-07-04 13:00:30 -03:00
2026-06-16 01:00:40 -03:00
2026-07-07 13:14:06 -03:00