Skip to content

fix(quality): drain the 2026-09-22 release-tip base-reds - #14520

Closed
diegosouzapw wants to merge 18 commits into
release/v3.8.51from
fix/release-v3.8.51-basereds-0922
Closed

diegosouzapw wants to merge 18 commits into
release/v3.8.51from
fix/release-v3.8.51-basereds-0922

Conversation

@diegosouzapw

@diegosouzapw diegosouzapw commented Sep 22, 2026 •

Copy link
Copy Markdown
Owner

Drains the gates that were red on the pure release/v3.8.51 tip, so PRs opened against it stop inheriting failures they did not cause.

Every red here was reproduced on origin/release/v3.8.51 before being touched (merge-gates §3 discriminator), and for each one the commit that introduced it was identified before deciding whether the test or the production code was the stale side. None of these come from a boarded PR.

Static gates

Gate Red Fix
check:docs-counts migration count said 178, tree has 179 bump README.md, AGENTS.md, llm.txt + 66 i18n mirrors
check:mutation-test-coverage 8 covering tests unregistered in tap.testFiles 6 additive entries (2 already present)
check-api-typecheck TS2769 ×4 + TS18047 ×4 — open-sse/executors/auggie.ts constrain the generic to the 3-tuple spawn()'s overloads key on
check-api-typecheck TS2698 — src/lib/combos/intelligentRouting.ts keep isRecord()'s narrowed value, not just its boolean
check-api-typecheck TS2459 ×2 + TS2724 — src/app/api/v1/combos/projectCombo.ts #14271 imported three types from a module that only import types them; import from combo/types.ts
docs-sync docs/i18n/bs/llm.txt was born saying 178 same bump

Unit reds

One production regression, found by chasing a red instead of adjusting it:

#14252 plugged the full stripInternalBodyFields() into the shared pre-executor boundary. That helper removes two classes of marker — the _omniroute* ones (already consumed by routing, the PR's legitimate target) and the four in INTERNAL_BODY_FIELDS, which are read by the executors themselves inside transformRequest(). Since the boundary runs before transformRequest, every native Responses passthrough was silently demoted to the translated path: Codex lost the client's metadata and took the Responses allowlist, and the same applied to _nativeXaiResponsesPassthrough and _claudeCodeRequiresLowercaseToolNames. No error, just a different upstream payload.

Split into stripInternalOmnirouteMarkers() (prefix-only) for the pre-executor boundary; the full strip stays at executor egress (base.ts:1356, dario.ts, ninerouter.ts), which runs after the markers are read. Two regression tests added.

Tests that pinned behavior a merged PR deliberately changed — each realigned to still prove what it was written for, with the causing PR cited in a comment. No assertion was dropped or weakened; several cases gained assertions:

Test Caused by Realignment
provider-translate-path-golden #14316 DeepSeek → Chat Completions regenerate; diff is exactly those 3 lines
chatcore-translation-paths ×3, chat-route-coverage ×2 #14316 select the openai-responses alternate per connection, as an operator does
combo-builder-options-route #14143 (fixes #14135) a provider node qualifies models under its prefix alias (gd/), not the raw node id
provider-node-reserved-prefix #12474 (Lyceum) tally said "413 → 414" but the set was already 414 at its parent; real move is 414 → 415
specialty-model-catalog-routes, image-model-not-in-chat-catalog-6457, model-token-limit-catalog #14216 Codex GPT-5.6 image models now carry their own catalogId; + a new assertion that the image row must not reappear under the chat id
qwen38-max-bare-id-alias #14181/#14242 the two ids are now distinct models; pins the invariant the file exists for (neither falls through to default: 128000) + that the preview must not re-alias the bare id
opencode-executor #14230 + #12633 Zen's GPT-5.6 family moved to /v1/responses, which authenticates with x-api-key
domain-branch-hardening ×2 #14276 a window with no reported fraction is UNKNOWN, not "0% remaining" — it must not reach the exhaustion threshold

Three catalog suites also pin CATALOG_BUILD_TIMEOUT_MS, exactly as 9147-catalog-eventloop-yield, 12058-models-catalog-canonical-self-aliased and models-catalog-route already do: every case there resets the cache, so each request is a cold full-catalog build, and the 8s production bound answers 503 catalog_build_timeout on a loaded runner before a single catalog-shape assertion runs. Build latency is not what those tests cover.

Worth a separate issue: that 8s cold-build budget is getting tight at 460 models / 360 providers. Six test files now pin around it. If the catalog keeps growing, the 503 will start showing up in real deployments on the first request after cache expiry.

Not included

check-translation-drift is still red on the tip (8 drifted sources, all from merged PRs). The refresh is running against the translation backend and will land separately — it is 528 files and does not belong in this PR.

No suppressions added, no baseline widened.


Three more found by this PR's own CI (all reproduce on the pure tip)

  • check:deps — @opencode/plugin is not in the dependency allowlist. fix(opencode-plugin-v2): port catalog publishing to stable provider contract #14370 ported the opencode plugin to the v2 contract, which lives under a different scope from the already-approved @opencode-ai/plugin. A scope change is exactly the slopsquatting shape that gate exists to catch, so it was verified before allowlisting: npm shows both under the same maintainer (thdxr <d@ironbay.co>, who also publishes @opencode-ai/plugin), @opencode/plugin created 2026-09-02. Legitimate v2 scope of the same publisher; the verification is recorded in the allowlist's _justifications entry.
  • hard-session-lease-bypass-inventory — fix(sse): retry empty translated stream turns through the normal path #14213's bounded empty-turn retry added a credential-resolution site in open-sse/handlers/chatCore.ts the frozen inventory did not list. Inventoried as class B: it resolves through the normal getProviderCredentials path and does not reach past the lease, so it is inventoried rather than exempted.
  • check:file-size — tests/unit/chatcore-translation-paths.test.ts 3546 → 3564. This one is this PR's own growth: the shared DEEPSEEK_RESPONSES_CREDENTIALS const plus the comment recording why the connection shape changed and that no assertion did. Rebaselined with that attribution rather than condensing the explanation away.

i18n drift — resolved elsewhere

While this PR was in flight another session landed #14532, refreshing the mirrors of the same 8 drifted sources. The refresh this session had running was therefore stopped and its output discarded rather than pushed on top. One source (docs/reference/FEATURE_FLAGS.md) has drifted since, and docs/reference/ENVIRONMENT.md re-drifts when this PR's env-var row lands — both are follow-up refreshes, not blockers for this drain.

Four gates were red on the pure release tip, so every PR opened against it
inherited them:

- Docs counts: the migration count moved to 179 (#13222's open-wa seed and
  #12814's proxy_logs_proxy_name landed) but README.md, AGENTS.md and llm.txt
  (+ 65 i18n mirrors) still said 178. Bumped in place — these are protected
  agent-instruction surfaces, so contributor PRs must not carry the change.

- Mutation coverage: eight covering unit tests across four mutated modules were
  missing from stryker.conf.json tap.testFiles, so their mutant kills did not
  count. Registered additively (six new entries; two were already present).

- open-sse/executors/auggie.ts (TS2769 x4, TS18047 x4): buildAuggieSpawnOptions
  typed its stdio parameter as "readonly string[]", which matches none of
  spawn()'s overloads — and without the tuple overload TypeScript treats
  child.stdout / child.stdin as possibly null at every use. Constrained the
  generic to the 3-tuple all four call sites actually pass.

- src/lib/combos/intelligentRouting.ts (TS2698): applyIntelligentRoutingConfigPatch
  kept only isRecord()'s boolean result, so "patch.weights" was still "unknown"
  at the spread. Keeps the narrowed value instead.

No suppressions, no baseline widening. Validated: check-docs-counts-sync clean,
check:mutation-test-coverage "No drift", tsconfig.typecheck-api.json reports no
auggie errors, intelligent-routing-weight-edit.test.ts 4/4.
…lidated (Refs #13866)

Both tests were red on the pure release tip and both pin behavior that a
merged PR deliberately changed, so the snapshot is the stale side:

- tests/snapshots/provider/translate-path.json: #14316 moved DeepSeek off the
  Responses API back to Chat Completions (format openai-responses -> openai,
  /responses -> /chat/completions). Regenerated with UPDATE_GOLDEN=1; the diff
  is exactly those three lines, no other provider moved.

- tests/unit/combo-builder-options-route.test.ts: #14143 (fixes #14135) makes a
  provider node with a prefix alias qualify its models as "<alias>/<model>",
  because the raw internal node id never routed. The test still expected the
  node id. Aligned to "gd/gpt-custom", matching the "oc/" assertion the test
  right above already makes for opencode.

Validated: provider-translate-path-golden 3/3, combo-builder-options-route 3/3.
#14252 ("strip _omniroute* markers at shared pre-executor boundary") wired
stripInternalBodyFields() into normalizeAttemptBody(). That helper removes two
classes of marker: the `_omniroute*` prefix class (consumed by routing before
dispatch — the ones #14252 was actually about) AND the four keys in
INTERNAL_BODY_FIELDS, which are consumed by the EXECUTORS inside
transformRequest():

  _nativeCodexPassthrough                      CodexExecutor.transformRequest
  _nativeXaiResponsesPassthrough               XaiExecutor.transformRequest
  _nativeOpenAICompatibleResponsesPassthrough  passthrough dispatch
  _claudeCodeRequiresLowercaseToolNames        BaseExecutor

normalizeAttemptBody() runs BEFORE transformRequest(), so every native Responses
passthrough was silently demoted to the translated path: CodexExecutor no longer
saw its marker, fell through to the RESPONSES_API_ALLOWLIST, and dropped the
client's top-level `metadata` (plus the passthrough instructions handling). No
error — just a quietly different upstream payload. Red on the release tip:
"chatCore keeps Responses-native Codex payloads in native passthrough mode".

Fix: split out stripInternalOmnirouteMarkers() (prefix-only) and call that at
the pre-executor boundary, preserving #14252's intent. The executor-consumed
markers keep being removed at the executor-egress boundary (base.ts, dario.ts,
ninerouter.ts, applyFingerprint), which runs after transformRequest has read
them. stripInternalBodyFields() itself is unchanged in behavior.

Tests: two regressions in strip-internal-omniroute-markers.test.ts — the helper
level (prefix strip keeps the executor markers) and the real boundary
(prepareUpstreamBody preserves _nativeCodexPassthrough and the client metadata
while still dropping _omnirouteSkipContextRelay).
#14316 (5f67e11, "fix(deepseek): default to OpenAI Chat Completions instead of
Responses API") flipped the DeepSeek registry entry from
format: "openai-responses" / baseUrl .../responses to format: "openai" /
baseUrl .../chat/completions, because DeepSeek's public API is Chat Completions
and the Responses path 400s on multi-turn tool calls that do not echo
reasoning_text. The change was deliberate and correct; it moved
"openai-responses" into `alternateFormats` ("Responses-compatible") so an
operator can still select it per connection, and it updated
alternate-formats.test.ts — but not these five fixtures, which still assumed the
old default and so went red on the release tip:

  chatcore-translation-paths.test.ts
    - carries Chat reasoning_content into official DeepSeek Responses input
    - replays nonstream DeepSeek Responses reasoning across a Chat tool turn
    - replays streamed DeepSeek Responses reasoning across a Chat tool turn
  chat-route-coverage.test.ts
    - applies task-aware routing when a semantic override is enabled
    - defaults a Combo's incompatible reasoning fallback to drop

All five are regressions about the DeepSeek *Responses* wire path (reasoning
replay, Responses-shaped upstream `input`, opaque-reasoning drop), so they now
select that protocol the way an operator does — providerSpecificData.targetFormat
= "openai-responses", resolved by resolveAlternateFormat() — instead of leaning
on a default that no longer exists. Every assertion is unchanged: the tests still
prove the same behavior, just on the connection shape that actually reaches it.
…Refs #13866)

`provider-node-reserved-prefix` has been red on the release tip since #12474
merged. That PR registered Lyceum and annotated the running tally as
"413 -> 414", but the set was ALREADY 414 at its parent commit, so adding
"lyceum" moved it to 415 while the assertion stayed at 414.

Verified by dumping RESERVED_PROVIDER_PREFIXES at 2a33528~1 (414 members) and
at the tip (415) and diffing the two: "lyceum" is the one and only member added,
and nothing was removed. So the count is right and the annotation's starting
point was wrong — no provider is missing from the walk.

Validated: provider-node-reserved-prefix 21/21.
`qwen38-max-bare-id-alias.test.ts` asserted `MODEL_SPECS["qwen3.8-max"] ===
undefined`, guarding against a second source of truth for what was then the SAME
model published under two ids (the preview row carried `aliases:
["qwen3.8-max"]`).

That premise expired in #14181/#14242 (83a6e9a, "stop rewriting GA qwen3.8-max to
preview on opencode-go"): OpenCode Go ships a GA `qwen3.8-max` that answers 401 on
the `-preview` id, so the two ids are now two DISTINCT models and the commit
deliberately gave the GA one its own MODEL_SPECS row while dropping the alias from
the preview row. The production change is correct; the assertion is the stale side.

Replaced it with the invariant this file actually exists to protect — NEITHER id may
fall through to contextManager's `default: 128000` — plus a guard that the preview
row does not re-declare the bare id as an alias, which is what would silently
collapse the two rows back into one. No assertion was removed or weakened.

ℹ pass 5 / ℹ fail 0 (node --import tsx/esm --test)
…og suites

Three `/v1` catalog suites answered 503 `catalog_build_timeout` instead of 200 and
died before evaluating a single catalog-shape assertion:

- specialty-model-catalog-routes.test.ts — `listedIds()` aborts on
  `assert.equal(response.status, 200)`
- image-model-not-in-chat-catalog-6457.test.ts — same status check
- model-token-limit-catalog.test.ts — the 503 body has no `data`, so `getModel()`
  throws `Cannot read properties of undefined (reading 'find')`

Root cause is not a catalog defect: every case in these files resets the catalog
cache (or bumps its generation via an override write), so each request is a COLD
build of the full catalog — 460 rows across 42 image providers — and the cold path is
bounded by `CATALOG_BUILD_TIMEOUT_MS` (#12627, default 8000ms). A cold tsx build
already costs ~7s on an idle box; measured here it takes 11.7–22.9s, so the bound
trips deterministically under runner contention. Verified this is not #14216's
`parseImageModel()` call in the image loop: 20 full passes over all 268 image models
cost 105ms total.

Pinned the bound out of the way exactly like the three sibling suites that hit this
before — 9147-catalog-eventloop-yield, 12058-models-catalog-canonical-self-aliased
and models-catalog-route — each carrying the same rationale. No assertion was
changed, removed or weakened; on the contrary, the assertions now actually run
instead of being short-circuited by the status check.
…og ids

#14216 (4970f5f, "separate Codex GPT-5.6 image catalog ids") gave the three Codex
GPT-5.6 image models a `catalogId` — `gpt-5.6-sol-image`, `gpt-5.6-terra-image`,
`gpt-5.6-luna-image` — because the callable upstream id collided with the chat
surface of the same model. `imageProviderCatalogEntries()` now publishes the
`catalogId`, while `parseImageModel()`/`findImageModelConfig()` map it back to the
callable `gpt-5.6-sol` on dispatch and for the supported/hidden checks. The
production change is deliberate and correct; three suites still asserted the old
shared ids:

- specialty-model-catalog-routes.test.ts — the image-route catalog listed
  `codex/gpt-5.6-{sol,terra,luna}-image`, not the bare ids.
- image-model-not-in-chat-catalog-6457.test.ts — the #7004 case required the image
  row "under the same model id"; the two rows now live under their own ids.
- model-token-limit-catalog.test.ts — `getModel("codex/gpt-5.6-sol")` now resolves
  the CHAT row (`type` undefined), so `assert.equal(canonical?.type, "image")` failed
  on a row that is no longer the specialty one.

Updated each to the new public ids and kept every invariant they were written for.
Two assertions were added rather than removed: the image row must NOT reappear under
the bare chat id (6457), and a context override targeting the chat id must not bleed
into the image row (token-limit) — both are the point of the #14216 split.
Both were red on the pure release tip and both pin behavior a merged PR changed
on purpose.

- opencode-executor "omits accept header when stream is false": #14230 routed the
  whole opencode-zen GPT-5.6 family to the Responses API
  (targetFormat:"openai-responses"), and #12633 established that Zen's
  /v1/responses endpoint authenticates with `x-api-key` rather than Bearer —
  unlike /chat/completions on the same host. The header therefore moved
  legitimately; the absent Accept header this case is actually about is unchanged.

- domain-branch-hardening "quotaCache covers empty quotas…" (2 assertions): #14276
  made a window whose fraction the upstream never reported — `total: 0`, or an
  Infinity percentage — UNKNOWN instead of "0% remaining", so it can no longer
  reach the exhaustion threshold. That fix exists because Vertex spend telemetry
  was locking whole accounts out. The percentages still read 0/100 (the documented
  placeholder) and both branches the case exists to cover are still exercised.

No assertion was dropped. Validated: opencode-executor 59/59,
domain-branch-hardening 6/6, quota-cache-unknown-limits 6/6.
…efs #13866)

`check-api-typecheck` has been red on the release tip since #14271 merged:

  ✗ src/app/api/v1/combos/projectCombo.ts TS2459 (baseline 0, live 2)
  ✗ src/app/api/v1/combos/projectCombo.ts TS2724 (baseline 0, live 1)

#14271 pulled `ComboCollectionLike`, `ComboLike` and `ResolvedComboTarget` from
`combo/comboStructure.ts`, which only `import type`s them from `combo/types.ts`
and never re-exported them. The value import (`resolveNestedComboTargets`) is
correct and stays; only the three types move to `combo/types.ts`.

Nothing runtime changes — the types were erased either way, which is why the PR
merged green under `typecheck:core`: that project does not cover `open-sse/`, so
the unresolved type references only surfaced in the API-route typecheck.
`check:env-doc-sync` (part of `check:docs-all`, blocking) has been red on the
release tip since #14236:

  ✗ In code but missing from .env.example: 1
     - DEEP_HEALTH_CHECK_ENABLED

#14236 added the opt-in deep health probe behind that flag
(`src/app/api/monitoring/health/route.ts`) but did not document it. Added to
.env.example and docs/reference/ENVIRONMENT.md, stating both halves of the gate:
the flag must be exactly "1", and an anonymous caller never triggers a probe with
or without it.

Validated: check:env-doc-sync in sync, check:docs-all exit 0.
… (Refs #13866)

All three reproduce on the pure release tip; none is this PR's defect except the
last, which is this PR's own measured growth.

- check:deps — "@opencode/plugin" is not in the dependency allowlist. #14370
  ported the opencode plugin to the v2 contract, which lives under a DIFFERENT
  scope from the already-approved "@opencode-ai/plugin". That scope change is
  exactly the slopsquatting shape this gate exists to catch, so it was verified
  before being allowlisted: npm shows both packages under the same maintainer
  (thdxr <d@ironbay.co>, who also publishes @opencode-ai/plugin), with
  @opencode/plugin created 2026-09-02. Legitimate v2 scope of the same publisher.
  The verification is recorded in the allowlist's _justifications entry.

- hard-session-lease-bypass-inventory — #14213's bounded empty-turn retry added a
  credential-resolution site in open-sse/handlers/chatCore.ts that the frozen
  inventory did not list. Inventoried as class B: it resolves through the normal
  getProviderCredentials path (its own log line says so) and does not reach past
  the lease, so it is inventoried rather than exempted.

- check:file-size — tests/unit/chatcore-translation-paths.test.ts 3546 -> 3564,
  this PR's own growth from the #14316 realignment: one shared credentials const
  plus the comment recording why the connection shape changed and that no
  assertion did. Rebaselined with that attribution; nothing was condensed away.

Validated: check-deps 13/13, hard-session-lease-bypass-inventory 3/3,
check:file-size OK (both halves).
@diegosouzapw

Copy link
Copy Markdown
Owner Author

Status check from the base-red sweep (2026-09-24): every item this PR carries now has another home, and the branch conflicts with release/v3.8.51.

Item here Where it is now
migration count in README / AGENTS.md / llm.txt + mirrors landed via #14714 (count is now 183, not the 179 this PR writes)
auggie.ts / projectCombo.ts / intelligentRouting.ts typecheck, stryker entries, catalog guards #14511 (rebased, mergeable)
@opencode/plugin dependency allowlist #14511

Leaving it open for the maintainer to decide; nothing was pushed here.

@diegosouzapw

Copy link
Copy Markdown
Owner Author

Fechando como coberta pela #14511, mergeada na release/v3.8.51 (drenou as mesmas base-reds: genérico de stdio do auggie, imports do projectCombo, strip de _native*Passthrough e os guards de catálogo/prefixo/qwen/opencode/quota). O conteúdo que só esta PR tinha já entrou pelas #14501, #14503, #14508 e #14516. (merge-batch 2026-09-24)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant