Repository navigation
fix: harden the test suite, fix plane/mount/credential defects, and add the improvement plan - #105
Conversation
- Stop the run on a Node below engines.node, before collecting a file. Below the floor, failures came from the engine (node:sqlite, Intl.DurationFormat, ICU strings) and looked like code regressions. .node-version names the Node CI uses. - Add a CI job that runs the unit and mock suites on Node 24, the floor engines.node promises but no job ran. - The zh fallback duration unit is now 分钟, the platform's own narrow output, so the meter row reads the same with or without Intl.DurationFormat. The DurationFormat tests restore the engine as they found it, rather than leaving a stand-in installed or asserting the engine ships the constructor. - Move the node:sqlite store fixture out of the helpers that the jsdom tests share, into test/support/opencode-store-fixture.ts. - Add an architecture test that walks the client bundle's runtime import graph from settings-page.tsx. It forbids node built-ins, the cordis runtime, pi, and process.env. Checked by planting a violation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
…n discovery - A rejected internal-route mount ran from a `void` IIFE with no catch, so a Google bridge failure became an unhandled rejection, which Node's default mode answers by exiting the process. mountInternalRoutes now owns the mount and its withdrawal, and reports a rejection. - Three early exits in registerResponsesProvider (internalRoutes off, every route declared, no llm-pi-ai) answered `undefined` after the Gemini bridges had already registered. That left them with no disposer, and the next reload failed DUPLICATE_ADAPTER. They now hand back the bridges' disposer. - A mount that failed to start, or that captured nothing, kept its fiber and every route it had registered. Both paths now release the routes and dispose the fiber. - Model discovery was registered on every activation, and the host's duplicate refusal was swallowed, so the first activation's settings answered for the life of the process. It is now registered once per root and answers from the latest activation's settings. Each namespace registers on its own, so one refusal no longer skips the rest. - Move three doc blocks back above the functions they describe. Each fix has a regression test. The two mount tests fail without the fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
…e the suite Request path and meter: - Price a turn on its route's plane. Go-first lookups priced every Zen turn on deepseek-v4-pro at 0.66/1.98 instead of 1.74/3.48. - The listing and model-info patches now act only on the routes this plugin claims. Before, a direct anthropic or openai route lost rows the OpenCode routing called "unservable", and a refused foreign id was answered with OpenCode's catalog. - A relay's Go /models is answered with the Go catalog. The enrichment re-checked the URL against the default gateway markers and fell back to Zen. - The free-model check reads a Request-carried body. That is how DSH sends a turn, and every keyless free turn was reported as a missing credential. The body is now read once for both uses. - The meter's session fallback is the session that most recently ran a turn, not the most recently created one. - A captured key's tier comes from the observed URL, then the route, and never the key's prefix. OpenCode issues oc_sk_ keys for Go, so a working Go key was filed under Zen. An OpenAI sk- key on a claimed relay was filed under Go. Suite isolation: - The unit and mock suites refuse non-loopback requests. Each unit run sent 54 requests to models.opencode.ai through the catalog's background revalidation, so on a networked runner a result could depend on the vendor's live catalog. - Captured keys, session spend and the live catalog are reset after every test (resetCatalogState is new). Shuffled runs failed catalog cases on seeds 42 and 1234. test:shuffle runs the suite in random order, and the Node 24 CI job uses it. Each behaviour change has a test that fails without it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
…egration - The card's store-inspection row called remote.opencodeStore.inspect, but no client contribution carried that namespace's descriptor. The row reported "unavailable" on a host that had the service. storeRemote now carries it, with a whitelist codec, and the client mounts it. - The host/client parity test missed this because it matched only a string literal in super(ctx, "name"), and the store passes a constant. It now resolves constants, and checks every declared Remote method against a client descriptor, not just every namespace. - inspectStore parses through the whitelist instead of a cast, so a field the host did not mean to send (a token) is dropped. An ok:false envelope reports the remote's own message. - The credential reader's legacy-row fallback was uncorrelated: one flagged opencode-go row hid every legacy opencode row, and the Zen key disappeared. The fallback is now per integration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
… keeps - AGENTS.md's repo map described nine modules this build does not have (the Console login, token store, org cache and sign-in row). They are replaced with one line saying sign-in is v2 work. The go-limits duplication note is gone, because attachAllowance calls allowanceFor. adapter-replacement.ts is marked as not wired. The client-bundle line names the boundary test. - CONTRIBUTING.md documents the engine floor, test:shuffle and test:mock, the offline guard, the per-test state reset, where host-only fixtures live, and that a regression test must fail without its fix. - The no-credential warning no longer tells the reader to "sign in through the settings card", which this build does not have. It points at readOpencodeStore instead, and a test asserts the advice. - CLIENT_BUDGET is raised from 128 to 132 KB for the store descriptor and its parser (about 1.2 KB of source, not a dependency), following the gate's own instruction. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
docs/improvement-plan.md groups the review's findings by cause and orders the fixes in nine phases after this one. Each phase lists its problems, its steps, the test that has to fail first, its acceptance criteria and any decision the owner needs to make. Findings are marked "measured" (reproduced) or "traced" (read in the code). Among the measured ones: the picker and dispatch derive reasoning levels differently for 9 of 95 catalog models. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
Replace docs/improvement-plan.md with the plan of record. It now holds: - the owner's decisions (D1-D11); - what the DeepSeek Harness host supports, with references into packages/llm, llm-pi-ai, session-controller, gateway and ui-settings-models; - the defects this audit found that are not yet fixed; - the target architecture and the phases, one PR each, with a status table that each PR updates. The decisions that change the code: - Settings offers the whole catalog, and the composer offers only the declared models. - The plugin never writes the user's profile and follows the user's configuration at dispatch. - The adapter patch exists to merge provider modes and nothing else. It is generalized to every mode, and its catalog fill is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
Investigation of the host's source and of the pi catalog it pins (1.1.0) changed the routing design. - llm-pi-ai resolves `api` and `baseUrl` per model, and pi's installed `opencode` catalog already spans Anthropic, Google, Completions and Responses with per-protocol endpoints. One route serves every mode for every model pi knows. - The plugin fills only three gaps: models newer than pi's catalog (including all Gemini on Go), endpoint drift, and profile-level `api`/`baseURL` collapsing the modes. - The new design is one unregistered catalog adapter per plane, keyed by the user's route id, and the adapter patch (merge-modes) delegates only gap models to it. That deletes the hidden routes, the listing hide, the stream redirect, the child mount and the Gemini bridge registration. - A real-host test harness (P-T1) comes before any of it. - One upstream change (per-entry `api`/`baseURL` in llm-pi-ai, U1) would remove the routing layer entirely. It would also let Settings save a model pi doesn't know, which pi-ai's strict write validation rejects today. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
pi-ai registers exactly the providers in its resolved config, and the fiber keeps whatever the internal/config waterfall returns, while the config editor writes the raw value. A prepended listener that resolves pi-ai's config without opencode/opencode-go therefore keeps pi-ai from claiming them, without touching the user's file, and the plugin registers its own PiAiAdapter under the same ids through the public API. Measured on the real host with pi 1.1.0 and the harness's patch: both routes held by the plugin, pi-ai's other routes unchanged, every protocol on both planes (Gemini on Go included) sent to its own endpoint, a catalog-unknown model id saved, listed and dispatched, and both routes handed back to pi-ai on release. Replaces the upstream asks (no upstream changes) and the save-gate design, which is kept only as the fallback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
A nine-lens review of the routing design, with two independent skeptics per finding (78 of 82 upheld), changed five things before any code moves: - Settings reads pi-ai's resolved config, so a read-only wrapper on settings.describe puts the plane entries back into the displayed value; writes were never affected, the Models page edits by path. - The route claim gets an explicit lifecycle: commits are told from validations by identity, reconcile runs on commit, mount and teardown are ordered, and pi-ai restarts at most once per transition. - The plugin's adapter is built from public pieces only, with pi-ai's credential rule and no captured keys on the adapter path. - The protocol comes from the catalog map, never from a guess at the model's name; metadata and levels are catalog first. - Releases stay on 1.x, phases land as stacked pull requests, and a real host harness plus a binary e2e job pin the behaviour first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
The vendor added kimi-k3.1-pro to the Go plane, so `catalog freshness` went red on every branch of this stack. This takes the two generated files exactly as the nightly refresh produced them (#111), with no hand edits, and updates the counts that hard-code the catalog's size: the Go shim test (28 -> 29), the plane table in redirect-planes.md (Go served 28 -> 29, the no-SDK Go cell 17 -> 18), and the Go completions list in both READMEs. The listed half of "29 served / 33 listed" is models.dev's count, inferred from the one added entry, because models.dev cannot be reached from here. It no-ops once main carries #111. P-T2 turns these counts into invariants so a refresh stops needing this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
|
The vendor added Go
I merged the commit up through #106 → #107 → #108 → #109 → #110 → #112. On each branch, the unit suite has the same test count as before and passes. The port becomes a no-op once Generated by Claude Code |
The vendor renamed the Go plane's one Gemini model (dropping its contributor suffix, halving its price and publishing an allowance for it where the limits table had none) and added a free model with no SDK, so `catalog freshness` went red on every branch of this stack. This takes the two generated files exactly as the nightly refresh produced them (#111), with no hand edits, and follows them: - the Go Gemini fixtures in six test files (attachment-services, catalog, go-google, go-google-prepare, models-discovery, responses-routes) now name the renamed id, since each means "the Go Gemini model the catalog serves"; - the usage panel test built its unknown allowance from the table's one unpriced row, which the vendor has now priced, so it builds the `Number.NaN` allowance the generator emits itself; - the Go shim count in catalog.test.ts (29 -> 30); - redirect-planes.md: Go 29 -> 30 served, the no-SDK Go cell 18 -> 19, and "0 of 29" -> "0 of 30" in the prose; - both READMEs: the renamed Gemini id, and the new model in the Go completions list, since it declares no SDK. The listed half of "30 served / 34 listed" is models.dev's count, inferred from one rename (no change) and one addition (+1), because models.dev cannot be reached from here. The dated S0 measurement row in improvement-plan.md keeps the id it was measured with. It no-ops once main carries #111. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
|
That is the gateway's own "model is unavailable / rate limited" answer for free models. The commit touches neither model; it changes only the generated tables, test fixtures and docs. The same suite passed on this branch's previous head. I re-ran the failed job once to tell a transient outage from a free model being retired upstream. If it fails the same way, the suite's free-model cases need to pick a model the gateway still serves rather than a fixed id. That work belongs to the stack's later phases (P-T2 removes hard-coded vendor ids from the suites), and I'll report it there rather than widen this PR. Generated by Claude Code |
The live routing suite picks its free-tier subjects from the catalog by asking the gateway, but it counted "400 Upstream request failed: Model is unavailable" as an observable answer. The upstream withdrew nemotron-3-ultra-free while the catalog still lists it, so the picker chose it and two cases asserted on a model the gateway no longer serves; the reachability case also failed on a 429 throttle. A withdrawn model (that 400, or a 410) is now skipped like a throttled one. The free-tier routing case only accepts a candidate whose answer is the free-tier gate it asserts, so an unmetered model cannot become its subject, and the reachability walk moves past gated, withdrawn and throttled candidates before it reports. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
The vendor moves its model catalog, its Go limits page and its CLI version almost daily. Each move turned every open pull request red, although none of them caused it: vendor drift is not a regression of the change under test. A pull request now fails a freshness check only when its diff changes that check's generator code: the script, every local module it loads, transitively, and the pinned version file for the CLI check. scripts/freshness-scope.ts derives those inputs from the sources and makes the call, so a new import moves them without a list to edit. Any other pull request reports drift as a warning, as a push to main already did. The offline check that every copy of the CLI version agrees with the pin still gates every pull request, since only the repository can break it. The gates fail closed: a lost scope answer gates rather than skips. The nightly run still regenerates the two tables and proposes the refresh as its own pull request, and warns on the CLI version. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
|
059236a — The repository owner chose this. The vendor moves its catalog, its Go limits page and its CLI version almost daily, and each move turned every open pull request red although none of them caused it. What a pull request is now gated on. Each freshness check ( What it only reports. On any other pull request, vendor drift becomes a warning with exit 0, the same as a push to main already got. That includes a pull request that only carries a refresh. A generated file edited without its generator gets a warning that names the file. The nightly run still regenerates the two tables and opens the refresh as its own pull request, and it warns on the CLI version. Tests that pin both paths (
The full gate passed before the push: check, unit suite, mock suite, build, bundle gate. It reaches every stacked PR through the sync that follows. Note: these reviews ran on the general model because the dedicated reviewer was unavailable: review:rules#2, review:correctness#2, verify#2.1, review:rules#3, review:correctness#3, verify#3.1. Generated by Claude Code |
The vendor retired `deepseek-v4-flash` from the Zen plane (it stays on Go). This pull request changes the catalog generator's own code, so under the freshness scoping it carries the refresh itself. This takes `src/catalog-data.ts` exactly as the nightly refresh produced it (#111), with no hand edits; `src/go-limits-data.ts` did not change. It then updates the counts that still hard-code the catalog's size: - the Zen shim count in catalog.test.ts (82 -> 81); - redirect-planes.md: Zen served 82 -> 81, the no-SDK Zen cell 25 -> 24, and "0 of 82" -> "0 of 81" in the prose; - protocol-routing-and-merge.md: the per-protocol shares now read "of 81", with completions at 24; - both READMEs: the no-SDK row is 24 and the active total is 81. The Chinese table had read 27 before this change, and now matches the English one. "127 listed" is unchanged, because the vendor retires a model by marking it `status: "deprecated"`, which keeps it listed. This could not be confirmed against models.dev, which cannot be reached from here. It no-ops once main carries #111. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
The walk's fixture held the text `await import("../src/lazy.ts")`. The
test tree's own architecture gate, which starts on the next branch of
this stack, reads a dynamic `src` import anywhere in a test file's text,
including inside a string. It counted that fixture line as a test
bypassing `test/support/src.ts`, so every branch above this one failed
the gate once this pull request was merged into it.
The case is about the dynamic form, not about `src`, so the import now
names `./lazy.ts` beside the script. The walk resolves it from the
importing file's own directory, which is the same rule, and the expected
inputs follow it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
Ported from the stack's base (d13cbe2 on #105) so this pull request's own `catalog freshness` goes green now, rather than waiting for the base to be merged up. It no-ops once the merge from P-R1 brings the same change. It takes `src/catalog-data.ts` exactly as the nightly refresh produced it (#111). The counts that restate the catalog's size follow it, on this branch's own wording of those passages: - the Zen shim count in catalog.test.ts (82 -> 81); - redirect-planes.md: Zen served 82 -> 81, the no-SDK Zen cell 25 -> 24, and "0 of 82" -> "0 of 81"; - protocol-routing-and-merge.md: the per-protocol shares now read "of 81", with completions at 24; - both READMEs: the no-SDK row is 24, and the active total is 81. "127 listed" is unchanged, because a model the vendor retires stays listed as `status: "deprecated"`. (cherry picked from commit d13cbe2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
Three documentation defects the synced base carries into P-R1. None was caused by the merge (each is identical on the merge-base and on both parents), and each wants the same change on the lowest branch (#105) and, for the README date, on P-R2's cherry-pick of d13cbe2. - PRE-1, docs/redirect-planes.md: "The Go plane serves 12 models on shapes other than completions" restores the count its own table gives (5 openai + 5 anthropic + 1 google = 11, the Go shim's 30 less its 19 no-SDK models). The 12 was right when written (6 + 6) and a later refresh moved the cells but not the sentence. test/catalog.test.ts now holds the sentence to the table's non-completions Go sum, so the next refresh cannot strand it again. - PRE-2, README.md and README.zh-CN.md: the 81-model count is dated 2026-10-27, the refresh that produced it (d13cbe2); on 2026-10-23 the count was 82, so the date no longer matched the number. - PRE-3, README.md and README.zh-CN.md: both install snippets name "^1.4.1" (package.json's version), where they had drifted to ^0.14.0 and ^0.12.0, neither of which can resolve to a 1.x release. test/readme-parity.test.ts pins the two snippets to one range on package.json's major. Both new assertions were checked against the stale values: each fails on them and passes on the corrected text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
The README's 81-model count kept the date of the 82-model count it replaced (2026-10-23); it now carries the date of the refresh that produced it (2026-10-27). Also corrected, all present-tense statements the catalog had moved past: - both README install snippets named ^0.14.0 and ^0.12.0, ranges that cannot resolve to any 1.x release; both now name package.json's ^1.4.1; - redirect-planes.md said the Go plane serves 12 models on shapes other than completions; its own table gives 11 (30 less 19 with no SDK); - engineering-notes.md tied a general rule to "the 82" models the shim ships; it now says "the models it ships", which a refresh cannot strand. P-R1 carries the same corrections (dec00c8) with tests that hold the README range and the Go sentence to their sources. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
Ported from P-R2 (29b43ca, itself from d13cbe2 on #105) so this pull request's own `catalog freshness` goes green now, rather than waiting for P3 to carry it up. It no-ops once the merge from P3 brings the same change. It takes `src/catalog-data.ts` exactly as the nightly refresh produced it (#111), and the counts the docs restate follow it (Zen 81 served, no-SDK 24, "of 81"). test/catalog.test.ts keeps this branch's invariants, which hold the shim to its own rules rather than to a count, so it needed no change. (cherry picked from commit 29b43ca) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
…l-bctz9n Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
The suite's setup file resets the live catalog after every test, so main's once-per-file install was gone by the second case and three cases failed. Installing before each one keeps the file independent of run order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
The copy scan's file filter lived in the top-level script, where nothing could exercise it. It now sits in scripts/vendor-version.ts beside the other rules, with cases of its own, and a test keeps its list equal to freshness-scope's. Because the script imports that module, it is now an input of the cli:version check, and the strict input list says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
The live suite's local modelGone rule gave way to main's verdict module in the merge. Its one shape main lacked, a 400 whose body says the upstream withdrew the model, now lives in retiredAtTheGateway with a unit case, so the free-tier walk and the subject pickers skip it instead of failing on it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
Main's test-system page predates the offline guard, the per-test reset and the engine floor: it placed the store fixture in the shared helpers and called the plane-lookup world per file. It now says where the store fixture lives and why, that a live-catalog world is installed per test, and what the CLI-version rules cover. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
Taking main's usableAsSubject rule in the merge dropped the picker's accept argument, so the free-tier case could pick an unmetered model and assert the gate on a 200. The case's own narrowing now applies after the shared rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
Main moved to 1.8.0 and onto the harness's alpha track while the stack was open, and landed several things the plan had scheduled. The plan of record now says so: D14' and D22' replace the pi 1.1.0 and rc.2 pins, D17 restates pi-ai's new defaults, D23 records the freshness scoping, D24 and D25 adopt main's debug gate and tool-fallback plane, and D9, D16 and D19 stand with notes. F10 is gone on pi 1.3. A new phase P-R3 (honest refusals) follows P-T3, each phase has its restack adjustment, the status table shows the restack, and the two community plugins' lessons are a ranked table with what not to adopt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
Main and this branch each raised the tripwire from 128 to 132 KB, each measured without the other's surface. Merged, the client is 135604 bytes, about 0.4 KB over; the owner's standing rule for growth like this is to raise the ceiling, not cut content. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
Close the restack review's findings on #105. Every change is prose, a comment or a log line; no behaviour moves, so no test is added. - ci.yml: the pnpm/action-setup comment no longer quotes a pnpm version; it names the `packageManager` field, which main's bump had made stale. release.yml carries the same stale sentence on main and is left to a main-side follow-up. - scripts/freshness-scope.ts: the GENERATED_OUTPUTS and dataInputs docs name scripts/vendor-version.ts (GENERATED_TABLES, PINNED_VERSION), where the copy and the regular expression now live. - test/e2e/protocol-routing.e2e.ts: the free-tier walk's report names the causes that can reach it (gated, withdrawn, refused for the account, never answered); a throttled walk skips, so "throttled" is gone. - README pair: the Chinese file now says what the English says about 403 FreeTierError (the headers are not its cause and the patch does not open the free tier), carries the Auto Review route clause and the two paragraphs on the API-key-only build and the host restart; the English yaml fence drops the nonexistent hiddenModels key. - README pair: the compatibility section restates the typert peer range package.json declares and what its two clauses admit. - docs/improvement-plan.md: F2, G1 and G2 carry the pi 1.1.0 restack note; the sources line names both harness tags; the P1a-P2 test matrix says the wrapper is retired; P0's status row counts ten defect fixes. - docs/protocol-routing-and-merge.md, docs/redirect-planes.md: the count tables are dated 2026-10-31 (the shipped shim), apart from the documents' measurement stamps; both READMEs name `catalog:shim -- --write` as the regeneration command. - AGENTS.md: the What-moved table names src/utils/retry.ts; the coverage line points at vite.config.ts instead of quoting stale thresholds. - CONTRIBUTING.md: the restart check names CARD_FIELDS rather than a field count. - docs/testing.md: rule 1 counts an upstream's 400 "Model is unavailable" as a retirement, and rule 2 says the body, not the status, makes it one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
Close the second review round's findings on the restacked P0. Docs only; no generated file is touched. - both-kept-2-R2-1: docs/deep-dive.md no longer names the retired ids the shim was said to suppress (main's regenerated shim serves one of them on Zen). It states the rule instead: a retired model is a row the registry marks deprecated, the generator drops it per plane, and ids are not restated; the enrichment item says "registry-deprecated rows". - rules-docs-2-R2-1: the PR body is replaced at push time from the updated draft, with the verification rows re-measured on this head and a restack section (what merged from main, what this PR dropped, the main-side follow-ups). Nothing is pushed here. - rules-docs-2-R2-2: the plan's defects table is retitled to where each defect now stands; the four rows main fixed are struck with the fix named, the purge row keeps only what is still open (a RemoteError object shows the generic message, P1a), and D19 records that the README's hiddenModels was removed in P0. - rules-docs-2-R2-3: README.zh-CN.md's merge rule follows the English and the code: a row is dropped only when the registry marks it deprecated, not when a per-provider list names it. One sentence, so the pairing gate's counts are unchanged. - rules-docs-2-R2-4: CONTRIBUTING.md no longer contradicts the repository: warnings are reported and gate in P12 (as its own command list says); a manual release creates the GitHub Release and never touches CHANGELOG.md; the release step points at the "Verified on DeepSeek Harness" line in both READMEs; the e2e section lists all five files. - rules-docs-2-R2-5: both READMEs drop the test total nothing pinned; engineering-notes.md's "every count" paragraph records it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
The third review round of the P0 restack. Each outcome: - The meter mock-ups in both READMEs and both quota-meter guides paired one model with another model's monthly allowance. They now name the model whose row the figures are, and the panel mock-up's rate line prints that model's catalog rates in the format the panel uses. A new catalog test reads every "<id> Go $a · Plus $b" figure in the four documents back against the generated allowance table; it failed on the old mock-ups and holds a known minimum of matches, so a regex that stops matching cannot pass as clean. - engineering-notes: the "deeper half" measurement is scoped to the Go plane it was taken on. Both ids are Zen rows and neither has a row in the Go catalog, which is what the plane-aware lookup misses; the sentence no longer claims they are absent from both shims. - deep-dive: the two "up to" figures for context window and max output are dropped, per the same file's rule that counts which move with the vendor are not restated. - docs/README.md indexes every document in the directory: the test system guide, upstream findings, the CLI API note, the parity plan, and the plugin page pair, whose two languages the opening sentence now names. No generated file moves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
|
Restacked onto Conflicts and how they were resolvedRules and docs
Build and dependencies
Source
Tests
Follow-up commits
Dropped, because
|
| Round | Found | Upheld | Fixed |
|---|---|---|---|
| 1 | 12 | 11 | all 11 (stale comments and figures, README pair drift, PR body, plan's pi claims, docs dates, repo map, testing guide) |
| 2 | 6 | 6 | all 6 (deep-dive Muse Spark line, plan's fixed-defects table and D19, Chinese merge rule, CONTRIBUTING contradictions, unpinned test count; the PR body in this update) |
| 3 | 9 | 7 | all 7: five in the tree (engineering-notes ids, meter mock-ups now pinned by a test against the generated allowances, deep-dive figure, docs index), two in this PR body (phase count 21) |
Live re-measures still owed (CI)
- Catalog freshness job: confirm
main's 2026-10-31 catalog and allowance blobs are current; on drift, the nightly refresh regenerates them and the counts (80 Zen / 23 no-SDK / 30 Go) are re-derived from the new shim. - The registry-listed counts in
docs/redirect-planes.md(130 Zen / 37 Go) can only be confirmed by the live job. - Live
protocol-routing: confirm the free-tier and subject-picking cases still find a subject with the new busy-skip and the 400 retirement.
Reviews on the general model because the dedicated reviewer was unavailable: none.
Generated by Claude Code
|
Nothing in the branch is wrong: it carries main's generated files exactly. The fix is the regenerated Generated by Claude Code |
The vendor added a free Zen model, `pulse-alpha-free`, after main's 2026-10-31 regeneration, and `catalog freshness` gates this pull request because it changes the generator's code. `src/catalog-data.ts` is taken exactly as the nightly refresh produced it (0f22c3e on chore/generated-refresh), with no hand edits; `src/go-limits-data.ts` did not move. The counts that restate the catalog follow it: - Zen 80 -> 81 served, no SDK 23 -> 24, free 8 -> 9 (catalog.test.ts, redirect-planes.md, protocol-routing-and-merge.md, both READMEs); - the registry's listed total 130 -> 131, since an added model is listed as well as served; - the count dates move to 2026-11-03. It no-ops once main carries the same refresh. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
The paragraph under "Every count in the documentation, checked against the data" stated an earlier shim's counts in the present tense and said every live count in the docs agreed with them, which stopped being true when the shim was regenerated. It now records the sweep as done against the shim of that day, restates no number (a number there is one no test reads), and points to redirect-planes.md sections 1 and 2, the dated tables test/catalog.test.ts reads back against the shipped shim. Outcomes: - fixed: rules-docs-1-P2-DOC-1. The line is main's and every branch below P2 still carries it; landing it here carries it to every branch above through the cascade, and the bottom-of-stack fix is recorded as a follow-up for #105's docs sweep. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
main is #105 squashed (aeb0dec), whose tree equals this branch's parent 798ba2e exactly, so the merge records the squash without changing a file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
Phase 0 of the architecture review. It does three things:
docs/improvement-plan.md, the reviewed plan for everything after this PR.The suite
node:sqlite,Intl.DurationFormat, ICU strings) that looked like regressionsengines.nodeand names both versions;.node-versionpins Node 26enginespromises Node 24, but no job ran itmodels.opencode.ai(the catalog's background refresh), so a networked runner could swap the catalog mid-testtest/support/offline.tsrefuses every non-loopback request in the unit and mock suites; e2e keeps the networktest/support/reset-process-state.tsresets all three after every test;pnpm run test:shufflearchitecture.test.tswalks the bundle's import graph and fails onnode:*, cordis, pi orprocess.env(checked by planting a violation)Defects fixed
Every fix has a regression test, and each test was run against the old code and failed.
mountInternalRoutesnow owns the mount and reports the failure.DUPLICATE_ADAPTER. A mount that failed to start kept its fiber and its routes.anthropicoropenairoute./modelswas answered with the Zen catalog.Requestbody, which is how DSH sends a turn, so every keyless free turn was reported as a missing credential.oc_sk_for Go too, so a working Go key was filed under Zen, and an OpenAIsk-key on a claimed relay was filed under Go.opencodeStore, which the client never mounted. The descriptor is now added, with a whitelist codec.Reviewer notes
tierForRequestno longer takes the key. Three tests that pinned the prefix rule were rewritten to the ruledocs/engineering-notes.mdrecords as measured: the observed URL decides, then the route.CLIENT_BUDGETis raised to 136 KB for the store descriptor and its parser, on top of whatmainadded since. That is about 1.2 KB of source, not a dependency, and is the gate's own "raise it" case.AGENTS.mdno longer describes nine login modules that do not exist.CONTRIBUTING.mddocuments the new test rules.The plan
docs/improvement-plan.mdis the plan of record. A nine-lens review, with two independent skeptics per finding (78 of 82 upheld), is folded into it. It contains:main);The plugin owns
opencodeandopencode-go.internal/configfilter keeps pi-ai from claiming the two routes, and the plugin registers its own adapter under the same ids. It never deletes another plugin's registration.settings.describekeeps the Models page showing both providers.Decisions:
internalRoutes,resolveRoutedKey, three plugin settings and the deprecated@viztor/dsh-opencodewrapper go.Before routing changes, two harnesses pin today's behaviour:
dshoffline, for the pinned pi (1.3.0 + the harness's patch) and whatever the registry resolves for the published wrapper.Restacked onto main 1.8.0
This branch was cut from
main1.4.1.main(1.8.0) is merged in once, here; every PR above merges its parent.main1.8.0. Suite hardening (engine floor, offline guard, per-test reset and shuffle, client-boundary gate); the lifecycle, mount and credential fixesmainlacks (the free-model check on aRequestbody, now withmain's URL plane; the relay's Go listing; the key tier decided by the URL; the meter's session fallback; the store-inspection remote mount); and D23's freshness scoping with--copies, with the generated-table skip inscripts/vendor-version.ts, which joins the strict input list.mainas-is: the regenerated catalog and allowance tables (chore(catalog): regenerate for the Argon rename and the Go plan's new allowances #132; the generated files aremain's blobs, and every count in the docs is re-derived from that shim, dated 2026-10-31), the per-plane pricing and thedebugFilerule (fix: the plane decides the price, the flag and the refusal #133, recorded as D24), the wrapper forwarder (fix(publish): the retired alias must LINK, not fail on every load #135; P2 retires the wrapper again, D9),readAccount(fix(usage): answer the whole opencodeGoUsage contract, for the sibling page and the client #137),coreToolDecisionand the catalog fixtures (feat(test): a fixture harness for the worlds the vendor has not produced #138, D25), the docs-pairing gate andtest/e2e/verdict.ts(feat(test): gate the language pairs, and lift the live-run rules out of the e2e #140), the mock's base coverage (test(mock): the mock must serve every base the plugin can produce #142), the Go-limits HTML reader (test(scripts): the Go-limits HTML reader, which is the half that broke #143), the CLI-version rules inscripts/vendor-version.ts(test(scripts): the CLI-version gate's rules, which have passed while wrong twice #144, now on D23's strict list), the testing guide (docs(testing): the scripts' pure halves, and why they moved out #145) and the plugin page (feat(plugin-page): a curated page and a featured image, instead of the whole README #146–feat(plugin-page): the page is a picture, like the upstream plugins' #150).mainalready has them: the three catalog refreshes, the pricing fix, the local copies of the e2e verdicts (only an upstream's400 "Model is unavailable"is ported intoverdict.ts, with aprobe-verdictrow), the usage-panel test, and the remote-count assertionmain'sreadAccountinvalidated.plane-lookupinstalls its world before each test; the generated-table skip moved intovendor-version.tswith a test row; the client bundle ceiling is 136 KB for the store descriptor on top of whatmainadded.src/catalog-data.tsandsrc/go-limits-data.ts(main's 2026-10-31 blobs) are current. If it reports drift, the nightly refresh regenerates them, and the counts in the READMEs, the docs and the tests (80 Zen, 23 without an SDK, 30 Go) are re-derived from the new shim;docs/redirect-planes.md(130 Zen, 37 Go) come frommainand from the live registry, not the shim, so only the live job can confirm them;protocol-routingjob: the free-tier walk now skips when every candidate was busy and reads a400 "Model is unavailable"as a retirement; confirm the free-tier and subject-picking cases still find a subject.Verification
All run locally on this head.
mainwas measured the same way, on an export oforigin/mainagainst this branch's install.pnpm run checkmainand the pre-restack branchpnpm exec vp test runmain: 83 files, 1236)pnpm exec vp test run --sequence.shuffle --sequence.seed=<n>pnpm exec vp test run --config vitest.mock.config.tspnpm run build && node --experimental-strip-types scripts/check.tspnpm exec vp test run --coveragepnpm run cli:versionvp test run --sequence.shuffle --sequence.seed=7, then the mock suiteThe live suites (keyed probes, the e2e jobs) run in CI, where the keys are.
Follow-ups
main:release.ymlstill quotes a pnpm version in the commentci.ymlno longer does.0.2.1" line waits for P-T1b's binary e2e on the alpha host.🤖 Generated with Claude Code
https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD