Skip to content

refactor(core): own provider retirement outside the metadata-built registry - #5018

Open
bytelazy wants to merge 2 commits into
apache:mainfrom
bytelazy:refactor/core-provider-retirement
Open

bytelazy wants to merge 2 commits into
apache:mainfrom
bytelazy:refactor/core-provider-retirement

Conversation

@bytelazy

@bytelazy bytelazy commented Sep 8, 2026

Copy link
Copy Markdown

Summary

isRetiredProvider lived in provider-registry.ts, which is built from the generated models.dev tables. The Desktop first screen imported it through command-palette-commands.ts, so the whole model-metadata snapshot stayed on the static startup path — the surviving chain flagged in the docs/model-metadata-firstscreen-optimization.md audit under #4711.

This extracts the predicate into a new metadata-free module, packages/core/src/provider-retirement.ts, backed by a small RETIRED_PROVIDER_TYPES list. Its only provider-registry import is import type (erased at build time), so importing it no longer pulls the generated tables. The startup-path importer (command-palette-commands.ts) and the two defaults.retired readers (model-catalog.ts, provider-auth.ts) are re-pointed at it; the retired flag is dropped from ProviderDefaults and the claude-subscription entry.

Refs #4711

Verification

  • Static import-graph trace from apps/desktop/src/renderer/main.tsx (value imports only; import type erased; dynamic import() treated as a lazy boundary): the AppShellOverlays → … → command-palette-commands → provider-registry chain no longer reaches provider-registry0 chains after this change (was 1). Tracing from command-palette-commands.ts itself: 0 chains to provider-registry.
  • model-metadata.generated.ts reachability from the renderer entry is unchanged by this PR: a pristine main worktree and this branch both report the same 2 chains (via core/settings → model-thinking → model-metadata, and via features/connection-settings → provider-add-model-dialog → llm-connections → provider-registry). This PR neither adds nor removes any of them.

    Correction: an earlier version of this line claimed "0 chains". That was a measurement artifact, not a fact: model-metadata.generated.ts is generated by scripts/sync-model-metadata.mjs and gitignored, so it is absent from a fresh checkout, and a disk-resolving import tracer silently reports zero chains to a file that isn't there. With the generated file present in both trees the counts match.

  • Contract test provider-catalog-contract.test.ts now pins RETIRED_PROVIDER_TYPES as the source of truth and asserts isRetiredProvider agrees with the registry's retired set; a name that is retired but not registered (or vice versa) fails there rather than at send time.
  • New unit test provider-retirement.test.ts (5 cases) covers the predicate's purity and the unknown/active/retired cases. Verified by compiling to a temp directory and running the emitted JavaScript (what test:dist does): compile exit 0, suite 5 pass / 0 fail.
  • Import specifier: verified A/B with TypeScript 5.9.3 under the repo's moduleResolution: "Bundler" — a .ts specifier reproduces TS5097 (exit 2), the .js specifier is clean (exit 0). This was the P1 found in review and fixed in 69e0ea6.
  • Swept for remaining defaults.retired / retired: true literals on ProviderDefaults — none left.

Correction to an earlier version of this description: it claimed the unit suite ran "5 pass, 0 fail" under node --test --experimental-strip-types. That passed only because the test imported ../provider-retirement.ts; that command resolves .ts directly and never exercises the compiler path build/typecheck use, so it masked a build break (TS5097) that review caught. With the correct .js specifier that command fails with ERR_MODULE_NOT_FOUND here, because the repo's suites run against compiled dist/. The emitted-JavaScript run above replaces that claim.

Checks I did not run: the repo's own npm --workspace @maka/core run build / typecheck / test:dist, lint, format, or the full suite. This environment cannot run npm ci (network-limited), so verification used a standalone TypeScript 5.9.3 against the repo's compiler options plus static import-graph analysis — not byte-level bundle evidence. CI shows action_required (first-time fork PRs need a maintainer to approve the workflow run), so no CI check has validated this branch yet.

A separate static chain still reaches provider-registry from the first screen (ui → maka-uri → settings → onboarding → llm-connections → provider-registry). This is intentionally not closed here and is recorded as open in the doc's audit note — it is the buildChatModelChoices/modelMenuGroups seam the doc's "Desired outcome" section already names, not the chain this change targets.

AI use

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: Claude (claude-opus-4-8) authored the refactor, tests, and PR description. Both commits carry a Generated-by: Claude (claude-opus-4-8) trailer.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

The ProviderDefaults type loses its retired?: true field. Runtime behavior is unchanged: isRetiredProvider returns the same answers (it still resolves claude-subscription as retired), and the two readers that checked defaults.retired now call the predicate. No external API beyond that type field changes.

@github-actions github-actions Bot added the effort/M Under 500 readable lines label Sep 8, 2026
@bytelazy
bytelazy force-pushed the refactor/core-provider-retirement branch from fbed8aa to 5dc0f66 Compare September 8, 2026 09:25

@me2seeks me2seeks left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review (Command Code) — not an approval

The extraction itself is sound: I checked that providerDefaultsOf(input.providerType) is the same input in both rewritten call sites (provider-auth.ts:59, model-catalog.ts:198), so defaults.retired === trueisRetiredProvider(providerType) is behavior-preserving, and the registry re-export keeps existing importers working. One problem blocks the build.

P1 (Must-Fix) — the new test file does not compile; @maka/core typecheck and build fail on this revision.

packages/core/src/__tests__/provider-retirement.test.ts:25 imports with a .ts extension:

} from '../provider-retirement.ts';

The repository's compiler options do not allow that. tsconfig.base.json sets "moduleResolution": "Bundler" and does not enable allowImportingTsExtensions — which in any case requires noEmit or emitDeclarationOnly, so it cannot be turned on for a package that emits to dist/. Every other import in the tree uses the emitted .js specifier (there are zero .ts-extension imports on main).

Evidence, running the package's own configuration on the reviewed revision:

$ npx tsc -p packages/core/tsconfig.json --noEmit
packages/core/src/__tests__/provider-retirement.test.ts(25,8): error TS5097: An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled.

That is the only error reported, so the rest of the package is clean. npm --workspace @maka/core run build and run typecheck therefore fail, and because test:dist runs the compiled output, the new suite cannot run. No CI check has reported on this branch, so nothing else caught it.

Smallest sound fix: use the emitted specifier, from '../provider-retirement.js'. Verified locally — with that single change npx tsc -p packages/core/tsconfig.json --noEmit exits 0.

Review-relevant risks. None identified beyond the build break: no public contract, wire shape, security boundary, dependency, licensing, or release effect was found in the diff. The new RETIRED_PROVIDER_TYPES ownership and the contract test that pins it against the registry are an improvement over the implicit retired flag.

Required conclusion.

  1. Optimal for the actual problem? Yes — the predicate moves to a metadata-free module and the duplicated retired flag is removed; that is the smallest change that cuts the static chain.
  2. Production code that can be deleted? Done in this PR — the retired field and the registry-local predicate are gone, and nothing else still reads .retired.
  3. Low-quality tests to delete or replace? none identified. The new suite is small but meaningful, and the contract test now ties the retired list to the registry.
  4. Deeper refactor required? No.
  5. Ready to merge? No — not until the import specifier is fixed; the package does not compile as pushed.
  6. Residual risks / verification gaps: the doc's audit note states a second static chain remains (main.tsx → @maka/ui → maka-uri → core/settings → … → provider-registry); that is recorded, not fixed, and is out of scope here. I did not run the full suite; the typecheck above was run directly.

Approval boundary. This is automated review; it is not an approval. Per CONTRIBUTING.md, the merge decision requires an independent human review. No approve was submitted.

@bytelazy bytelazy left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — P1 confirmed and fixed in 69e0ea6.

You're right on every point, and the finding also exposes a bad verification claim in my PR description, which I want to correct explicitly rather than quietly fix.

The bug. provider-retirement.test.ts:25 imported ../provider-retirement.ts. I reproduced TS5097 under the repo's resolution mode and confirmed the fix, A/B with TypeScript 5.9.3:

  • .ts specifier → error TS5097: An import path can only end with a '.ts' extension when 'allowImportingTsExtensions' is enabled. (exit 2)
  • .js specifier → clean (exit 0)

I also confirmed there are zero other .ts-extension imports in the tree, so this was mine alone, and that allowImportingTsExtensions is not a viable escape here since it requires noEmit/emitDeclarationOnly and @maka/core emits to dist/. Fixed to ../provider-retirement.js.

Correcting my own verification claim. My PR description said the unit test ran "5 pass, 0 fail". That was true only because of the wrong specifier: I ran node --test --experimental-strip-types, which resolves .ts directly and never invokes the compiler path build/typecheck use. With the correct .js specifier that same command actually fails with ERR_MODULE_NOT_FOUND, because the repo's suites run against compiled dist/ and I have no build in this environment. So my green signal was masking the break you found — the specifier error was load-bearing for my own evidence. That is a real gap in how I verified, not just a typo.

To verify properly this time I compiled both files to a temp directory and ran the emitted JavaScript, which is what test:dist does: compile exits 0, suite passes 5/5.

Still not run by me: the repo's own npm --workspace @maka/core run build / typecheck / test:dist, plus lint and format. This environment cannot npm ci (network-limited), so I verified with a standalone TypeScript 5.9.3 against the repo's compiler options rather than the workspace scripts. No CI has reported on this branch yet — it shows action_required, since first-time fork PRs need a maintainer to approve the workflow run. Worth having that run before anyone relies on my numbers.

The second static chain you noted (main.tsx → @maka/ui → maka-uri → core/settings → … → provider-registry) remains out of scope here and is recorded as open in the doc's audit note, as you read it.

@me2seeks me2seeks left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up (Command Code) — verified resolved

I re-checked the head after your push.

The P1 build break is fixed. packages/core/src/__tests__/provider-retirement.test.ts now imports '../provider-retirement.js', which is the emitted specifier the package config requires. That was the only error npx tsc -p packages/core/tsconfig.json --noEmit reported, so the package typechecks again and the new suite can run. Nothing else in my review is outstanding.

Thanks for the quick turnaround. (Automated review; not an approval, and not a substitute for independent human review under CONTRIBUTING.md.)

…gistry

`isRetiredProvider` lived in `provider-registry.ts`, which is built from the
generated models.dev tables. The Desktop first screen imported it through
`command-palette-commands.ts`, so the whole model-metadata snapshot stayed
on the static startup path — the surviving chain flagged in the
`docs/model-metadata-firstscreen-optimization.md` audit under apache#4711.

Extract the predicate into a new metadata-free module,
`packages/core/src/provider-retirement.ts`, backed by a small
`RETIRED_PROVIDER_TYPES` list. Its only `provider-registry` import is
`import type`, erased at build time, so importing it no longer pulls the
generated tables. Re-point the startup-path importer
(`command-palette-commands.ts`) and the two `defaults.retired` readers
(`model-catalog.ts`, `provider-auth.ts`) at the new module, and drop the
`retired` flag from `ProviderDefaults` and the `claude-subscription` entry.

The provider-catalog contract test now pins `RETIRED_PROVIDER_TYPES` as the
source of truth and asserts `isRetiredProvider` agrees with it, so a name
that is retired but not registered (or vice versa) fails there rather than
at send time. A focused unit test covers the predicate's purity contract.

Static import-graph trace from `apps/desktop/src/renderer/main.tsx`: the
`AppShellOverlays → … → command-palette-commands → provider-registry` chain
no longer reaches `provider-registry` (0 chains); the separate
`ui → maka-uri → settings → onboarding → llm-connections → provider-registry`
chain remains and is recorded as open in the doc's audit note — that is the
`buildChatModelChoices`/`modelMenuGroups` seam the "Desired outcome" section
already names, not the chain this change targets.

No build was run and no bundle output was measured; the verification here is
static import-graph analysis plus the contract/unit tests.

Generated-by: Claude (claude-opus-4-8)
The new test imported `../provider-retirement.ts`. The repo sets
`moduleResolution: "Bundler"` without `allowImportingTsExtensions` (which
would require `noEmit`/`emitDeclarationOnly` and so cannot be enabled for a
package that emits to `dist/`), so `tsc -p packages/core/tsconfig.json`
fails with TS5097 and both `build` and `typecheck` break. There are zero
other `.ts`-extension imports in the tree.

Use `../provider-retirement.js`, matching every other import in the repo.

Verified with TypeScript 5.9.3 under the repo's resolution mode: the `.ts`
specifier reproduces `TS5097` (exit 2) and the `.js` specifier typechecks
clean (exit 0). Compiling both files to a temp dir and running the emitted
JavaScript — what `test:dist` does — passes 5/5.

My original "5 pass" claim came from `node --test --experimental-strip-types`,
which resolves `.ts` directly and therefore never exercised the compiler path
the build uses; the wrong specifier was the only reason it passed.

Generated-by: Claude (claude-opus-4-8)
@bytelazy
bytelazy force-pushed the refactor/core-provider-retirement branch from 69e0ea6 to 2e8dc11 Compare September 14, 2026 06:33

@bytelazy bytelazy left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for re-checking — and noting it for the record.

Since that follow-up, main moved and this branch went to dirty, so I rebased onto f109ccde. Force-pushed as 2e8dc112; mergeable_state is back to blocked (approval only). Two conflicts, both from #5225 (refactor(models): unify connection-scoped model configuration) touching the same hunks:

  • provider-registry.ts — upstream removed relayModelProfiles from ProviderDefaults. Kept upstream's removal and applied my change (drop retired, leave the ownership comment).
  • model-catalog.ts — upstream reworked that call (relayModelProfilesmodelOverrides, and a new savedModelIds derivation). Kept upstream's logic verbatim and changed only defaults.retired === trueisRetiredProvider(connection.providerType).

Re-verified after the rebase rather than carrying the old results forward: typecheck of both files clean (exit 0), and compiling to a temp dir and running the emitted JavaScript passes 5/5. Swept again for defaults.retired / retired: true on ProviderDefaults — none left. Both commits keep their Generated-by trailer.

One correction to my PR description, which I've now fixed there. It claimed model-metadata.generated.ts is unreachable from the renderer entry (0 chains) and that this PR leaves that unchanged. The "unchanged by this PR" half is right, but 0 chains was wrong — and the way I got it is worth stating, since it's a trap in this repo: model-metadata.generated.ts is generated by scripts/sync-model-metadata.mjs and gitignored, so a fresh checkout doesn't have it. My tracer resolves specifiers against files on disk, so in any tree where that file is absent it silently reports zero chains. I briefly read that as a regression my PR had introduced; once I copied the generated file into a pristine main worktree, main reported the same 2 chains my branch does:

main.tsx → @maka/ui → maka-uri → core/settings → core/model-thinking → core/model-metadata → model-metadata.generated
main.tsx → composition/desktop-feature-services → features/connection-settings → provider-add-model-dialog → core/llm-connections → core/provider-registry → model-metadata.generated

So the pre-existing reachability is broader than the doc's audit note records — the second chain runs through connection-settings, which the note doesn't mention. That's upstream state, not something this PR changes or claims to fix; I've corrected the description to say "unchanged by this PR" without the false zero, and left the doc note alone rather than widen its scope inside a refactor PR.

The command-palette-commands → provider-registry claim — the one this PR is actually about — does still hold after the rebase: 0 chains (was 1). That one doesn't depend on the generated file, since it terminates at provider-registry.ts, which is committed.

Still not run by me: the repo's own build / typecheck / test:dist, lint, or format (this environment can't npm ci), and CI still shows action_required pending a maintainer approving the workflow run for a first-time fork PR.

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

Labels

effort/M Under 500 readable lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants