* fix(api): validate request bodies with Zod in 4 routes — restores the t06 gate
The release-green verdict (#9737) lists check:route-validation:t06 as a HARD
failure and it is STILL red on the current tip: four routes call
request.json() and hand-roll `typeof x === "string"` checks instead of using
Zod, which Hard Rule #7 requires and the gate enforces (it scans source and
has no allowlist).
- src/app/api/plugins/marketplace/install (#9445): InstallBodySchema; the
400 'Missing or invalid name field' response is preserved verbatim.
- src/app/api/services/dario/admin/accounts (#8523): DeleteAccountBodySchema
for the optional { alias } DELETE body; query-param path untouched.
- src/app/api/services/dario/admin/login-start (#8523): LoginStartBodySchema;
trimming now happens in the schema, so the forward body is unchanged.
- src/app/api/services/dario/admin/import-from-omniroute (#8523):
ImportBodySchema for connectionId/alias; invalid shapes fall back to the
same 'connectionId is required' 400 as before.
All four keep their exact status codes and messages — this is a validation
mechanism swap, not a contract change (plugins route suite still 33/33).
Adds tests/unit/route-body-validation-t06.test.ts, which runs the gate's own
rule inside the unit suite so the next such route fails on ITS OWN PR instead
of surfacing weeks later in a base-red sweep. Guard verified by mutation:
renaming .safeParse( in one route makes it fail (1 fail), restored from a
pre-probe copy.
Gates: route-validation:t06, file-size, test-discovery, mutation-test-coverage,
dead-code exit 0; typecheck:core clean; eslint clean.
Refs #9737
* fix(memory): register the sqlite backend on the /api/memory/[id] route — every handler 500'd
GET/PUT/DELETE /api/memory/[id] threw `Primary backend "sqlite" not
registered` and returned 500. #8752 (MemoryBackend provider pattern) wired the
route to `@/lib/memory/manager` directly, but the registry is populated by an
import-time side effect in the module INDEX (src/lib/memory/index.ts:23,
`memoryManager.register(sqliteBackend)`). Importing the bare manager gives an
empty registry.
In production the failure is order-dependent, which is why it went unnoticed:
if /api/memory (which imports the index) is hit first in the same process, the
singleton is already populated and [id] works. Reached first — the common case
for a client that edits a known memory id — every request 500s. The sibling
route is the only other consumer and already imports the index; this was the
lone direct-manager import in src/.
- Fix: import from `@/lib/memory` (index) with a comment stating WHY the
indirection matters, so the next refactor does not simplify it back.
- Guard: tests/integration/memory-route-put.test.ts already covered this and
was failing 2/5 on the base (it only surfaced now because the integration
suite runs on the release-PR CI, not per-PR). Now 5/5.
Also fixes a test-isolation defect in the same run:
tests/integration/combo-matrix/context-relay-codex.test.ts reused one combo
name across both tests, and the control failed with `UNIQUE constraint failed:
combos.name` — resetStorage() unlinks the DB file but the previous
better-sqlite3 handle keeps writing to the same inode. Gave the control its own
combo name and parameterized the request builder; the assertion is unchanged
(it never depended on the name). 2/2.
Integration suite on this tip: 936 tests, 32m19s — under the 40min ceiling the
old verdict reported as exceeded (#9737 item 6), which the migration-135
collision was causing.
Refs #9737
---------
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
isStreamingUpstreamError used a key-presence check (parsed.error != null)
which false-positives on benign values some backends emit on every chunk
({}, '', false, 0). When opencode issues a tool-call turn, the upstream SSE
opens with role-only frames (no recognized content) and a later chunk that
carries real tool_calls content PLUS a benign empty error field. The error
gate runs BEFORE content recognizers, so that single frame short-circuits
to 'error' -> 502 'streaming upstream error'. Same combo via kilocode works
because its wire format never emits the empty error field.
Fix: isSubstantiveError() helper — only treat error as real when it carries
non-empty string, non-empty object, or explicit true. Empty object {}, empty
string '', false, and 0 are benign.
TDD: tests/unit/quality-validation-benign-error.test.ts proves tool_calls
chunk with error:{} or error:'' is valid (was 502), while a real error
{message, code} still correctly fails.
The release-green verdict (#9737) lists check:route-validation:t06 as a HARD
failure and it is STILL red on the current tip: four routes call
request.json() and hand-roll `typeof x === "string"` checks instead of using
Zod, which Hard Rule #7 requires and the gate enforces (it scans source and
has no allowlist).
- src/app/api/plugins/marketplace/install (#9445): InstallBodySchema; the
400 'Missing or invalid name field' response is preserved verbatim.
- src/app/api/services/dario/admin/accounts (#8523): DeleteAccountBodySchema
for the optional { alias } DELETE body; query-param path untouched.
- src/app/api/services/dario/admin/login-start (#8523): LoginStartBodySchema;
trimming now happens in the schema, so the forward body is unchanged.
- src/app/api/services/dario/admin/import-from-omniroute (#8523):
ImportBodySchema for connectionId/alias; invalid shapes fall back to the
same 'connectionId is required' 400 as before.
All four keep their exact status codes and messages — this is a validation
mechanism swap, not a contract change (plugins route suite still 33/33).
Adds tests/unit/route-body-validation-t06.test.ts, which runs the gate's own
rule inside the unit suite so the next such route fails on ITS OWN PR instead
of surfacing weeks later in a base-red sweep. Guard verified by mutation:
renaming .safeParse( in one route makes it fail (1 fail), restored from a
pre-probe copy.
Gates: route-validation:t06, file-size, test-discovery, mutation-test-coverage,
dead-code exit 0; typecheck:core clean; eslint clean.
Refs #9737
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
* fix(docker): complete partially traced packages in standalone co-location
Publish-to-Docker-Hub has failed on every release/v3.8.50 push since #9151
enabled publishing from active release branches: the post-build guard dies
with "Cannot find module .../@atjsh/llmlingua-2/dist/index.js" while the
co-location step right above it reports 100 packages copied.
Root cause: Next's file tracing materializes @atjsh/llmlingua-2 PARTIALLY
in the standalone (package.json lands, the dist/ payload its main points at
does not). colocateOptionals' no-clobber checked existsSync on the package
DIRECTORY, so the partial shell counted as present and the one package that
mattered was skipped forever (#9185 added the closure walk but kept the
directory-level check).
Fix: presence is now judged by entrypoint integrity — the package resolves
from inside the target tree (same contract as the Dockerfile guard). Partial
directories are completed with a file-level no-clobber merge (cpSync
force:false), so files the trace did materialize are never overwritten and
pinned instances (dist transformers 3.5.2) keep their protection.
Validation (TDD): 2 new tests in docker-llmlingua-optionals-9166.test.ts
reproduce the CI failure (partial package skipped; closure-wide early-exit
firing while a member is partial) — red on the old code, 5/5 green after.
* fix: update colocate test mock packages to match isPackageIntact entrypoint resolution
The PR's isPackageIntact check uses require.resolve to validate that
co-located packages have a usable entrypoint inside the target tree.
The pre-existing test's mock packages lacked main fields and index
files, so require.resolve failed and the idempotency assertion broke.
Update buildRoot() to give every closure package a resolvable entry
(main + index.js), mirroring what real npm packages ship.
Refs #9615
* docs(changelog): fragment for #9615
* fix(yuanbao-web): accept content field in SSE text events (upstream format change) (#8739)
Closes#8739
* fix(errorClassifier): classify ChatGPT Web SENTINEL_BLOCKED 403 as FORBIDDEN, enabling combo fallback (#8813)
Closes#8813
* fix(vertex): route Claude models to native rawPredict and respect targetFormat overrides (#8994)
Closes#8994
* fix(cursor): preserve tool context across multi-turn conversations when client lacks conversation_id (#9029)
Closes#9029
* fix(sse): move Antigravity client system content to first user message to avoid upstream 429 (#9030)
Closes#9030
* fix(combo): distinguish pre-dispatch skips from genuine failures to prevent false 503 ALL_ACCOUNTS_INACTIVE (#9630)
Closes#9630
* fix: repair stray brace in combo.ts and fix no-explicit-any types in repro-9630 test
---------
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
* test(cli): realign opencode-plugin suite to the bare-key static-catalog contract
#9178 (fix#9175) dropped the provider prefix from static-catalog model
dict keys — the correct production behavior (OC's getModel looks models up
by bare id), live-validated in the PR — but the subpackage's own suite was
not swept: 21 tests in config-shim.test.ts + provider-id-routing.test.ts
still asserted the prefixed keys, breaking opencode-plugin CI on every
living-release-PR run since the merge.
- Lookups opencode-omniroute/<raw-id> -> <raw-id>; omniroute/<combo> -> <combo>.
- #7976 anti-double-prefix invariant kept (the negative assert on the
OC-gate-prefixed key stays).
- Obsolete comment above the raw-model dict write rewritten to describe
the #9175 contract it contradicted.
- Subpackage lockfile synced to the already-bumped 0.2.1.
Validation: full subpackage suite hermetic — 287/287 pass (was 21 failing).
* fix(pr): fix changelog fragment format, login-bootstrap test assertions, and VM_DEPLOYMENT_GUIDE fabricated env vars
* fix(pr): remove YAML frontmatter from changelog fragment (validator expects bare bullet)
* fix(pr): update file-size baseline for base-red drift after merging 48 base commits
* fix(pr): rename duplicate migration 134_proxy_logs_egress_ip to 139
* docs(changelog): fragment for #9614
* Revert "fix(pr): rename duplicate migration 134_proxy_logs_egress_ip to 139"
This reverts commit 1312e1a917.
---------
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>