Skip to content

fix: harden the test suite, fix plane/mount/credential defects, and add the improvement plan - #105

Merged
viztor merged 29 commits into
mainfrom
claude/vigilant-mendel-bctz9n
Nov 3, 2026
Merged

viztor merged 29 commits into
mainfrom
claude/vigilant-mendel-bctz9n

Conversation

@viztor

@viztor viztor commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Phase 0 of the architecture review. It does three things:

  • makes the suite deterministic: right engine, offline, independent of test order;
  • fixes ten confirmed defects, each with a test that fails without its fix;
  • adds docs/improvement-plan.md, the reviewed plan for everything after this PR.

The suite

Before After
Below Node 24, three cases failed for engine reasons (node:sqlite, Intl.DurationFormat, ICU strings) that looked like regressions the run refuses to start below engines.node and names both versions; .node-version pins Node 26
engines promises Node 24, but no job ran it a new CI job runs the unit suite shuffled, plus the mock suite, on Node 24
the "offline" unit suite made 54 requests per run to models.opencode.ai (the catalog's background refresh), so a networked runner could swap the catalog mid-test test/support/offline.ts refuses every non-loopback request in the unit and mock suites; e2e keeps the network
shuffled runs failed on seeds 42 and 1234 (captured keys, session spend and the live catalog leaked between tests) test/support/reset-process-state.ts resets all three after every test; pnpm run test:shuffle
the client-bundle boundary was guarded only by a size tripwire architecture.test.ts walks the bundle's import graph and fails on node:*, cordis, pi or process.env (checked by planting a violation)
the host/client parity test grepped for a string literal, so a namespace passed as a constant slipped through it resolves constants and checks every declared Remote method

Defects fixed

Every fix has a regression test, and each test was run against the old code and failed.

  • Host crash risk. A rejected internal-route mount escaped as an unhandled rejection. mountInternalRoutes now owns the mount and reports the failure.
  • Leaked registrations. Three early exits leaked the Google bridge registrations, so the next reload failed with DUPLICATE_ADAPTER. A mount that failed to start kept its fiber and its routes.
  • Reloads ignored. Model discovery answered from the first activation's settings for the life of the process. Phase P3 deletes that registration anyway, because Settings never queries it.
  • Foreign providers. The listing and model-info patches rewrote providers this plugin does not claim, such as a direct anthropic or openai route.
  • Relay listing. A relay's Go /models was answered with the Zen catalog.
  • Free models. The free-model check never read a Request body, which is how DSH sends a turn, so every keyless free turn was reported as a missing credential.
  • Meter session. The meter's session fallback picked the newest session, not the one that just ran.
  • Key tiers. A key's tier was read from its prefix. OpenCode issues oc_sk_ for Go too, so a working Go key was filed under Zen, and an OpenAI sk- key on a claimed relay was filed under Go.
  • Store inspection. The store-inspection row called opencodeStore, which the client never mounted. The descriptor is now added, with a whitelist codec.
  • Store reader. The credential reader's legacy-row fallback was not per integration, so one flagged Go row hid the Zen key.

Reviewer notes

  • tierForRequest no longer takes the key. Three tests that pinned the prefix rule were rewritten to the rule docs/engineering-notes.md records as measured: the observed URL decides, then the route.
  • CLIENT_BUDGET is raised to 136 KB for the store descriptor and its parser, on top of what main added since. That is about 1.2 KB of source, not a dependency, and is the gate's own "raise it" case.
  • The no-credential warning no longer says "sign in through the settings card", which this build does not have.
  • AGENTS.md no longer describes nine login modules that do not exist. CONTRIBUTING.md documents the new test rules.

The plan

docs/improvement-plan.md is the plan of record. A nine-lens review, with two independent skeptics per finding (78 of 82 upheld), is folded into it. It contains:

  • the owner's decisions (D1–D25);
  • what the DeepSeek Harness host supports, read from its source (F1–F20);
  • the defects this audit found, and where each now stands (several are fixed on main);
  • the routing design, measured on the real host (pi 1.1.0 then; the pinned cell is now 1.3.0 with the alpha harness's patch, D14');
  • 21 phases (P1a through P12, including the new P-R3), landing as stacked pull requests on this one, with a status table each phase updates.

The plugin owns opencode and opencode-go.

  • An outermost internal/config filter 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.
  • That adapter serves every protocol per model, any catalog model, and any custom id.
  • The user's file is never written. A read-only wrapper on settings.describe keeps the Models page showing both providers.

Decisions:

  • Protocol. The catalog map decides it, never a guess at the model's name.
  • Precedence. Catalog first: the user's row, then the catalog, then pi.
  • Reasoning. A model whose reasoning is only on/off is offered as Off / On. Old Minimal/Xhigh/Max selections on such models break, and the release notes say so.
  • Releases. Everything ships as 1.x minors. internalRoutes, resolveRoutedKey, three plugin settings and the deprecated @viztor/dsh-opencode wrapper go.

Before routing changes, two harnesses pin today's behaviour:

  • a real-host test harness (a bare cordis/llm/llm-pi-ai tier, and a boot tier with ConfigEditor and Settings);
  • a CI job that boots the published dsh offline, 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 main 1.4.1. main (1.8.0) is merged in once, here; every PR above merges its parent.

Verification

All run locally on this head. main was measured the same way, on an export of origin/main against this branch's install.

Node Step Command Result
26 lint, format, types pnpm run check 0 errors; 23 warnings, the same count as main and the pre-restack branch
26 unit suite pnpm exec vp test run 85 files, 1315 tests pass (main: 83 files, 1236)
26 shuffled pnpm exec vp test run --sequence.shuffle --sequence.seed=<n> seeds 1 and 42 pass
26 gateway mock pnpm exec vp test run --config vitest.mock.config.ts 52 tests pass
26 build and bundle gate pnpm run build && node --experimental-strip-types scripts/check.ts pass; consumer install loads both halves; client bundle 135,604 B under the 139,264 B ceiling
26 coverage pnpm exec vp test run --coverage 96.41% statements, 92.05% branches, 96.25% functions, 96.4% lines; above the ratchet
26 CLI version pnpm run cli:version the pin is the published version and every copy agrees
24 floor vp test run --sequence.shuffle --sequence.seed=7, then the mock suite pass

The live suites (keyed probes, the e2e jobs) run in CI, where the keys are.

Follow-ups

  • main: release.yml still quotes a pnpm version in the comment ci.yml no longer does.
  • The README's "Verified on DeepSeek Harness 0.2.1" line waits for P-T1b's binary e2e on the alpha host.
  • Three rows of the Chinese README (the protocol count, the Go Google route, the quota-ring row) are rewritten by P-R1's docs sweep, which replaces the routing prose they describe.

🤖 Generated with Claude Code

https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD

claude added 10 commits October 22, 2026 22:24
- 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

viztor commented Oct 24, 2026

Copy link
Copy Markdown
Owner Author

813e6f3 ports the nightly catalog refresh (#111) into this branch, so this diff now touches src/catalog-data.ts and src/go-limits-data.ts. Both files are exactly what the generator wrote; neither was edited by hand.

The vendor added Go kimi-k3.1-pro, which turned catalog freshness red on every branch of the stack. On main, #111's own test job is red as well, because three assertions and two docs hard-code the catalog's size. The commit updates those counts here:

  • the Go shim test: 28 → 29;
  • docs/redirect-planes.md: Go served 28 → 29, and the no-SDK Go cell 17 → 18;
  • the Go completions list in both READMEs.

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 main carries the refresh. If #105 merges first, #111 has nothing left to add and can be closed. P-T2 turns these counts into invariants, so later refreshes stop needing this.


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

viztor commented Oct 26, 2026

Copy link
Copy Markdown
Owner Author

2ebaffc ports today's regenerated vendor tables into this branch, the same way 813e6f3 did, so catalog freshness is green again. In the Go plane, gemini-4-argon-contributor became gemini-4-argon and is now priced, and iceman-contributor-free was added. The fixtures, counts and README lists that named the old id moved with it.

e2e · live gateway failed on the same commit because of the vendor's live state. Three cases failed:

  • Two get 400 Upstream request failed: Model is unavailable for the free nemotron-3-ultra-free on completions.
  • One gets a 429 rate limit from the free beam-free.

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

viztor commented Oct 27, 2026

Copy link
Copy Markdown
Owner Author

059236a — ci: gate freshness only on a pull request's own generator changes

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 (catalog:shim, limits:shim, cli:version) fails a pull request only when the diff touches one of that check's inputs: the generator script and every local module it loads, transitively (type-only imports excluded), plus src/identity.ts for the CLI check. scripts/freshness-scope.ts derives those inputs from the sources, so a new import moves them without a list to edit. The offline check that every copy of the CLI version agrees with the pin still gates every pull request, because only the repository can break it. The gates fail closed: if the scope answer goes missing, the gate runs; the check is not skipped.

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 (test/freshness-scope.test.ts):

  • the decision, per check: a pull request touching its inputs gates; one carrying only a refresh reports; an unrelated diff or an empty diff reports
  • the CI wiring, parsed from ci.yml: for each check, a pull request runs exactly one of its gate and its report, gating unless the answer is report; the scope step runs before every step that reads its outputs; the offline copy scan gates whatever the scope
  • the scope step's own shell and the CLI it calls are run for real: a generator change gates and a refresh reports, also when the CLI is reached through a symlink
  • the walk over the real generators finds each parser behind its generator, never counts a generated file as an input, and counts a JSON import as a leaf input

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
viztor pushed a commit that referenced this pull request Oct 27, 2026
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
viztor pushed a commit that referenced this pull request Oct 27, 2026
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
viztor pushed a commit that referenced this pull request Oct 27, 2026
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
claude added 10 commits November 2, 2026 19:30
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

viztor commented Nov 3, 2026

Copy link
Copy Markdown
Owner Author

Restacked onto main 1.8.0. This branch now carries main 1.8.0, merged in once here (this PR is the parent of the stack; every PR above merges this one). Pushed head: 7cd12d3b7caab0c5379a4c30a4bdddc1396c98bd.

Conflicts and how they were resolved

Rules and docs

  • AGENTS.md: main's go-limits bullet (one lookup, allowance row label); main's login bullet plus the branch's credentialOrg remnant sentence; the branch's remote-timing line (now also naming main's opencodeGoUsage lookup message) and its web-client line kept.
  • README.md / README.zh-CN.md: Go model lists and the no-SDK count from main, re-derived from main's shim (Zen 80 = 23/31/17/8/1, Go 30 = 19/5/5/1); count paragraph dated 2026-10-31; install floor ^1.8.0 in both; the Chinese no-SDK row corrected to 23.
  • docs/protocol-routing-and-merge.md: "of 80", 23 completions.
  • docs/redirect-planes.md: 80 served / 130 listed and 30 / 37 (listed counts are main's, from the live registry); 23 no-SDK; Go non-completions 11 = 30 − 19, re-checked.

Build and dependencies

  • package.json: main's ultracite and vite-plus entries plus the branch's yaml.
  • pnpm-lock.yaml: main's lockfile, then a fresh install; the only delta from main is es-module-lexer and yaml; frozen install passes.
  • scripts/check-vendor-version.ts: main's pure-half structure with the branch's generated-table skip, --copies and error line; a follow-up moved the skip into scripts/vendor-version.ts.
  • scripts/check.ts: both 128 → 132 KB paragraphs kept; a follow-up raised the ceiling to 136 KB after measuring the merged client (135,604 B).

Source

  • src/fetch-patch.ts: the body read once from a Request (branch) and the plane from the URL (main).
  • src/stream-hook.ts: comment only, main's wording made model-free.
  • src/catalog-data.ts, src/go-limits-data.ts: reset to main's generated blobs exactly.

Tests

  • test/catalog.test.ts: Zen length 80.
  • test/e2e/protocol-routing.e2e.ts: main's verdict.ts and usableAsSubject taken, the local verdicts deleted; the free-tier walk asks "busy" before the refusals and skips when every candidate was busy; follow-ups restored the subject picker's narrowing and ported 400 "Model is unavailable" into verdict.ts.
  • test/settings-page.test.tsx: the fixed remote count dropped (main's readAccount adds a descriptor).
  • test/usage-panel.test.tsx: main's two tests supersede the branch's.

Follow-up commits

15e6236 plane-lookup installs its world before each test · 2454609 the generated-table skip lives in the CLI-version rules, with cases · beae0ca upstream 400 "Model is unavailable" read as a retirement · 1d4aaab testing guide describes the suite this branch keeps · 4b0ebea subject picker keeps the free-tier narrowing · 28fee8d plan restacked (D14', D22', D17 defaults, D23/D24/D25, F10 gone, P-R3 after P-T3) · 8b9da84 client ceiling 136 KB.

Dropped, because main already does it

Review rounds

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

viztor commented Nov 3, 2026

Copy link
Copy Markdown
Owner Author

catalog freshness is red on 7cd12d3. The vendor added a Zen model, pulse-alpha-free (80 → 81), after main's 2026-10-31 regeneration. This pull request changes the catalog generator's code, so under the D23 scoping it gates on that drift rather than only reporting it.

Nothing in the branch is wrong: it carries main's generated files exactly. The fix is the regenerated src/catalog-data.ts. It cannot be produced from the machine doing this restack, because the vendor registry is unreachable from there, and no regenerated file exists yet to port. The nightly refresh job on main (04:37 UTC) will regenerate it. I will port that file into this branch the same way as the earlier refreshes: the file taken unchanged, the counts that restate it re-derived, and the change merged up the stack.


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
viztor pushed a commit that referenced this pull request Nov 3, 2026
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
@viztor
viztor merged commit aeb0dec into main Nov 3, 2026
4 checks passed
viztor pushed a commit that referenced this pull request Nov 3, 2026
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants