Skip to content

fix(providers): restore the runtime-agnostic exports the browser barrels dropped - #795

Merged
sroussey merged 3 commits into
claude/wonderful-turing-rjtcnx-ai-typesfrom
claude/optimistic-goldberg-onotd9-browser-barrel-parity
Aug 15, 2026
Merged

fix(providers): restore the runtime-agnostic exports the browser barrels dropped#795
sroussey merged 3 commits into
claude/wonderful-turing-rjtcnx-ai-typesfrom
claude/optimistic-goldberg-onotd9-browser-barrel-parity

Conversation

@sroussey

Copy link
Copy Markdown
Collaborator

Stacked on #717 (claude/wonderful-turing-rjtcnx-ai-types), not on main.

#717's 24 manifest edits are correct and are not touched here. What they do is turn an
invisible gap into a hard error: once a customConditions: ["browser"] consumer is
type-checked against the browser declarations, every symbol a browser barrel forgot to
re-export becomes TS2305. Four providers have such omissions. This PR closes them, removes
the one class of nominal browser entry that has no split at all, and adds the guards that
would have caught both.

Why this is stacked on #717 rather than a follow-up

Shipping them apart means one released version in which assertToolChoiceHonored,
DeepSeek_ModelSearch_Stream, registerOpenAiImageValidator, OpenAI_ModelSearch_Stream,
Xai_ModelSearch_Stream and OpenRouter's whole capability/model-search surface are
uncompilable in the browser with no substitute#717 makes the routing correct, and the
barrels are what the correct routing then points at. In-repo consumers already break:
packages/test/src/test/ai-provider-api/DeepSeek_ToolCalling.test.ts:9-11,
OpenAI_ImageValidation.test.ts:8, and ai-provider/provider-model-search.test.ts:10-16
import these symbols from @workglow/<p>/ai.

M1(b) — the omitted exports, per provider

The evidence these omissions are accidental is stronger than "no node: imports" (also true —
grep 'from "node:"' over all five srctrees returns zero): **every omitted module is already compiled into that provider's browser bundle via the runtime entry.** E.g.providers/openai/src/ai/registerOpenAi.ts:10importsregisterOpenAiImageValidator, and registerOpenAiIS inindex.browser.ts— so it is indist/ai.browser.jstoday and only itsexport *` was missing.

provider module restored symbols
deepseek common/DeepSeek_Client (widened from 2 names to the node barrel's 5) assertNotTruncatedByReasoning, DEEPSEEK_DEFAULT_REASONING_ALLOWANCE, resolveMaxTokens (plus the 2 already there)
deepseek common/DeepSeek_ModelSearch DeepSeek_ModelSearch_Stream
deepseek common/DeepSeek_ToolCalling (named form) DeepSeekToolChoiceNotHonoredError, assertToolChoiceHonored, isForcingToolChoice
openai common/OpenAI_ImageValidation registerOpenAiImageValidator
openai common/OpenAI_ModelSearch OpenAI_ModelSearch_Stream
xai common/Xai_ModelSearch Xai_ModelSearch_Stream
openrouter common/OpenRouter_Capabilities OPENROUTER_RUN_FN_SPECS, openRouterWorkerRunFnSpecs, deriveCapabilitiesFromMeta, inferOpenRouterCapabilities
openrouter common/OpenRouter_ModelSearch OpenRouterRawModel, OPENROUTER_FALLBACK_MODELS, fetchOpenRouterModels, mapOpenRouterModels, OpenRouter_ModelSearch_Stream

DeepSeek_ToolCalling keeps the named form the node barrel uses rather than a export *.
That is what holds DeepSeek_ToolCalling_Stream out of the main-thread barrel on both
platforms — matching index.ts exactly.

_testOnly is deliberately not added anywhere. It is @internal, exists for
@workglow/test alone, and belongs behind a ./test entry the way packages/ai already did
it. After this PR it is the only symbol a browser consumer loses.

Two claims from the original review, refuted — please don't "fix" these

  1. ollama has no gap. The "every *_ModelSearch is missing from the browser barrel" claim
    does not hold here: providers/ollama/src/ai/common/Ollama_ModelSearch.ts exists but is
    exported by neither barrel (it is consumed internally by Ollama_JobRunFns /
    Ollama_JobRunFns.browser, which register Ollama_ModelSearch_Stream as a run-fn). The two
    barrels already agree; ollama's only delta is _testOnly. No change made.
    providers/ollama/src/ai-runtime is also untouched — its runtime.ts vs
    runtime.browser.ts split is genuine (import("ollama") vs import("ollama/browser")),
    cactus-class.

  2. The nine packages/workglow shims are correctly identical. They contain
    export * from "@workglow/openai/ai" — a bare specifier, re-resolved under the
    consumer's conditions at every hop, so the split really happens one layer down in the
    provider's exports map. The two files being identical is the mechanism. Collapsing the
    browser shim onto export * from "./openai" would resolve the bare specifier once, under
    the shim's own condition, and destroy the split. Left alone, and the new guard has a
    dedicated negative fixture documenting this.

M2(i) — the four genuinely nominal browser entries

providers/llamacpp-server and providers/stable-diffusion-server each had a
src/ai.browser.ts byte-identical to src/ai.ts and a src/ai-runtime.browser.ts
byte-identical to src/ai-runtime.ts, all four naming relative specifiers
(./ai/index, ./ai/runtime). A relative specifier is resolved once, by the importing file's
own path — --target=browser changes the compile target, not the resolver, and a manifest's
browser field applies to bare specifiers only. So both entries already pulled in the same
graph and the declaration split was nominal: two .d.ts files kept equal by hand.

Each .browser.ts now re-exports its node peer. Cycle safety was checked: under
moduleResolution: "bundler" a file (./ai.ts) beats a directory (./ai/index.ts), bun
agrees, ai.ts re-exports ./ai/index and never ./ai.browser, and nothing in the toolchain
(examples/web/vite.config.ts, bunfig.toml, bun's --target=browser) substitutes
X.browser.ts for X.ts on a relative specifier. Each file keeps a one-line comment saying a
future .browser.ts resolver plugin would break this.

The browser condition is kept for both packages — --target=browser still produces a
genuinely different bundle, so the entry earns its keep. Confirmed by build: all four bundles
are byte-identical before and after (see table below).

Bundle-size cost — measured, and the plan's prediction was wrong in an interesting way

bun run build:packages at the base commit vs. after, dist/ai.browser.js:

provider before after Δ raw gzip before gzip after Δ gzip
deepseek 7,842 13,028 +5,186 2,680 4,155 +1,475
openai 10,300 12,941 +2,641 3,400 4,054 +654
xai 6,319 8,427 +2,108 2,256 2,803 +547
openrouter 8,786 11,358 +2,572 2,694 3,333 +639
llamacpp-server 28,270 28,270 0 5,855 5,839 −16
stable-diffusion-server 15,368 15,368 0 3,560 3,536 −24

Corrections to what the plan predicted:

  • The SDK was already there. grep -o 'import("[^"]*")' finds import("openai") in the
    baseline ai.browser.js of all four providers, reached through registerDeepSeek /
    registerOpenAi / registerXai / registerOpenRouter. getClient is not pulled in by
    this change — it was already in every browser bundle.
  • No code-splitting happens, and none is needed. bun emits no lazy chunk (ls dist/*.js
    is unchanged: ai.js, ai.browser.js, ai-runtime.js, ai-runtime.browser.js and nothing
    else). Under --packages=external the await import("openai") stays a literal dynamic
    import of a bare specifier, which the downstream bundler defers — so the SDK never
    contributes eager weight either way.
  • The whole delta is the restored modules' own source, and it is eager. Isolated by
    rebuilding openai with only OpenAI_ImageValidation added: 10,300 → 10,331 (+31 bytes,
    effectively the plan's "literal zero"); adding OpenAI_ModelSearch on top accounts for the
    remaining +2,610.
  • OpenRouter is not free. The plan predicted "adds nothing (uses global fetch, no SDK)".
    It adds +2,572 raw / +639 gzip — its own capability-inference and model-mapping code,
    including the OPENROUTER_FALLBACK_MODELS literal. Correct, but not zero.

Worst case is deepseek at +1.5 KB gzipped, for symbols a browser consumer currently cannot
import at all.

M2(ii) + M1(b)'s regression net

  • duplicateBrowserEntryViolations (ExportTypesPairing.test.ts, beside
    browserSplitViolations): reports a src/*.browser.ts identical to its src/<stem>.ts
    peer whose specifiers are all relative. The relative/bare distinction is the entire rule
    and is documented in full in the doc comment, including why the packages/workglow shims
    are correctly identical. No violation survives after M2(i), so both branches carry fixtures:
    a positive pair (export * from "./ai/index" on both → one violation), a negative pair
    (export * from "@workglow/openai/ai" on both → zero violations, commented with the
    refutation), a differing-graph pair, and one proving a comment cannot silence the rule.

  • packages/test/src/test/util/ExportBarrelParity.test.ts (new): parses each provider's
    src/ai/index.ts + src/ai/index.browser.ts into a surface set and asserts
    node \ browser equals a pinned INTENTIONAL_NODE_ONLY fixture (each of the five providers
    ["_testOnly"]), with a comment explaining why _testOnly is node-only and pointing at
    the ./test migration. Includes a staleness check (a pin that no longer describes a real
    difference fails), mirroring ALLOWED_MISMATCHES. Source-only — no build, works under
    use-source. The parser is regex-based, so an unclassifiable statement is reported, not
    skipped, matching how buildEntryViolations reports an underivable dist stem.

  • LOW, findBrowserBlock: browserSplitViolations did a top-level ?.browser lookup
    while its sibling nodeImportTarget recurses, so a nested
    { import: { browser: {…}, default: … } } was invisible — every rule keyed on the branch
    behaved as if no browser condition existed. Replaced with a walker; fixture added for the
    no-implementation case reached that way.

  • SKIPPED, as planned: hardening buildEntryViolations' regex against glob/variable-driven
    entry lists. Fixing it properly means executing the build, and it is already fenced by
    GLOB_BUILT_PACKAGES.

Verification

$ bun scripts/test.ts util vitest
 Test Files  55 passed (55)
      Tests  810 passed | 10 skipped (820)
   Duration  177.72s
exit=0

$ bun scripts/test.ts provider-api provider vitest
 Test Files  3 failed | 87 passed | 3 skipped (93)
      Tests  10 failed | 1021 passed | 61 skipped (1093)

All 10 failures are live OpenAI integration tests
(OpenAI_Generic.integration.test.ts, OpenAI_ImageGeneration.integration.test.ts,
OpenAI_UsageAccounting.integration.test.ts) failing on billing, unrelated to this change:

Error: OpenAI Responses 429: {"message":"You have no credits remaining. ...",
  "type":"insufficient_quota","code":"credit_balance_exhausted"}

The three consumer tests this PR unblocks pass:

$ bunx vitest run --project test src/test/ai-provider-api/DeepSeek_ToolCalling.test.ts \
    src/test/ai-provider-api/OpenAI_ImageValidation.test.ts
 Test Files  2 passed (2)      Tests  11 passed (11)

$ bunx vitest run --project test src/test/ai-provider/provider-model-search.test.ts
 Test Files  1 passed (1)      Tests  10 passed (10)

Lint (the repo has no root lint script; each package carries one) — clean, no output, on
every changed package:

$ for p in providers/{deepseek,openai,xai,openrouter,llamacpp-server,stable-diffusion-server} packages/test; do (cd $p && bun run lint); done
$ eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0   # x7, all silent

Build:

$ bun run build:packages
 Tasks:    81 successful, 81 total
  Time:    49.415s
exit=0

The guards actually guard

Deleting the one added Xai_ModelSearch line:

AssertionError: expected [ Array(1) ] to deeply equal []
+ [
+   "providers/xai: providers/xai/src/ai/index.ts exports * from \"./common/Xai_ModelSearch\"
+    but providers/xai/src/ai/index.browser.ts does not",
+ ]

Restoring providers/llamacpp-server/src/ai.browser.ts to its duplicated form:

FAIL  workspace exports maps > re-exports rather than duplicates a browser entry that has no split
+ [
+   "providers/llamacpp-server/src/ai.browser.ts is identical to providers/llamacpp-server/src/ai.ts
+    and names only relative specifiers (\"./ai/index\"), so both entries resolve the same module
+    graph and the declaration split is nominal — re-export the node entry from it instead of
+    duplicating it",
+ ]

Both restored afterwards.

tsgo probe — #717's own methodology, reproduced

A throwaway file (not committed) importing the two symbols, compiled twice under
moduleResolution: "bundler", once with customConditions: ["browser"] and once without:

import { assertToolChoiceHonored, DeepSeek_ModelSearch_Stream } from "@workglow/deepseek/ai";
void assertToolChoiceHonored;
void DeepSeek_ModelSearch_Stream;

Before M1(b) (deepseek's browser barrel reverted, rebuilt):

=== BEFORE M1(b): NODE ===
exit=0
=== BEFORE M1(b): BROWSER ===
probe.ts(1,10): error TS2305: Module '"@workglow/deepseek/ai"' has no exported member 'assertToolChoiceHonored'.
probe.ts(1,35): error TS2724: '"@workglow/deepseek/ai"' has no exported member named 'DeepSeek_ModelSearch_Stream'. Did you mean 'DeepSeekModelSchema'?
exit=1

After M1(b):

=== NODE (no customConditions) ===
exit=0
=== BROWSER (customConditions: ["browser"]) ===
exit=0

No tsgo-spawning vitest test was added — it needs a built dist, is slow, and has no
precedent in the repo.

Release metadata

The repo uses bunset, not changesets (there is no .changeset/), and CHANGELOG.md files
are generated from conventional-commit subjects — none were hand-edited. Once M1(b) lands, the
only symbols leaving the browser type surface are _testOnly (internal) and ollama's genuine
platform split, so a patch bump is honest and the normal bunset --patch --all path
applies. The three commit subjects are the changelog entries:

  • fix(providers): restore the runtime-agnostic exports the browser barrels dropped
  • refactor(providers): re-export the node entry from the browser entry where no split exists
  • test(exports): flag a duplicate browser entry and pin browser barrel parity

Suggested description fix for #717

Not edited by this PR — for the author to apply:

  • It says 25 branches; the actual count in the diff is 24.
  • It says "10 in workglow including ./worker"; there are 9 packages/workglow
    shims (anthropic, deepseek, google-gemini, hf-inference, hf-transformers,
    ollama, openai, openrouter, xai). ./worker was already correct on main and is
    untouched by fix(providers): point each export condition's types at its own declaration #717.
  • It says "Three assertions"; ExportTypesPairing.test.ts as merged carries 8 tests
    in its workspace exports maps suite (finds branches, pairs types, source entries, types
    ordering, browser split, condition ordering, build entries, glob exemption) plus the
    fixture suites.
  • Worth adding: once this parity fix lands, a customConditions: ["browser"] consumer loses
    only _testOnly relative to the node surface.

Generated by Claude Code

claude added 3 commits August 15, 2026 08:53
…els dropped

Five providers keep a hand-maintained `src/ai/index.browser.ts` beside
`src/ai/index.ts`. Four of them omitted modules that carry no
platform-specific code, so a `customConditions: ["browser"]` consumer
could not import them at all.

The omissions were invisible until the exports-map fix in #717 routed
browser consumers to the browser declarations; they now read as `TS2305`.
That they are accidental is evidenced by the bundles themselves: every
omitted module is ALREADY compiled into that provider's browser bundle
via the runtime entry (e.g. `registerOpenAi`, which IS in the browser
barrel, imports `registerOpenAiImageValidator`), and no `src` tree of the
five contains a single `node:` import. Only the `export *` line was
missing.

Restored, per provider:

- deepseek: `assertNotTruncatedByReasoning`,
  `DEEPSEEK_DEFAULT_REASONING_ALLOWANCE`, `resolveMaxTokens`
  (`DeepSeek_Client`); `DeepSeek_ModelSearch_Stream`
  (`DeepSeek_ModelSearch`); `DeepSeekToolChoiceNotHonoredError`,
  `assertToolChoiceHonored`, `isForcingToolChoice`
  (`DeepSeek_ToolCalling`)
- openai: `registerOpenAiImageValidator` (`OpenAI_ImageValidation`);
  `OpenAI_ModelSearch_Stream` (`OpenAI_ModelSearch`)
- xai: `Xai_ModelSearch_Stream` (`Xai_ModelSearch`)
- openrouter: `openRouterWorkerRunFnSpecs`, `deriveCapabilitiesFromMeta`,
  `inferOpenRouterCapabilities`, `OPENROUTER_RUN_FN_SPECS`
  (`OpenRouter_Capabilities`); `OpenRouterRawModel`,
  `OPENROUTER_FALLBACK_MODELS`, `fetchOpenRouterModels`,
  `mapOpenRouterModels`, `OpenRouter_ModelSearch_Stream`
  (`OpenRouter_ModelSearch`)

`DeepSeek_ToolCalling` keeps the NAMED form the node barrel uses, which
is what holds `DeepSeek_ToolCalling_Stream` out of the main-thread barrel
on both platforms.

ollama is deliberately unchanged: its `Ollama_ModelSearch` is exported by
NEITHER barrel, so the two agree, and `_testOnly` is its only delta.
`_testOnly` stays node-only everywhere — it is `@internal`, for
`@workglow/test` alone, and belongs behind a `./test` entry the way
`packages/ai` already did it.

Co-Authored-By: Claude <noreply@anthropic.com>
…where no split exists

`providers/llamacpp-server` and `providers/stable-diffusion-server` each
carried a `src/ai.browser.ts` byte-identical to `src/ai.ts` and a
`src/ai-runtime.browser.ts` byte-identical to `src/ai-runtime.ts`, all
four naming RELATIVE specifiers (`./ai/index`, `./ai/runtime`).

A relative specifier is resolved once, by the importing file's own path,
and nothing in this toolchain substitutes `X.browser.ts` for `X.ts` on
one: `--target=browser` changes the compile target, not the resolver, and
a manifest's `browser` field applies to bare specifiers. So both entries
already pulled in the same module graph — the declaration split was
nominal, two `.d.ts` files kept equal only by hand.

Each `.browser.ts` now re-exports its node peer, which cannot drift.
These packages keep their `browser` condition: `--target=browser`
produces a genuinely different bundle, so the entry earns its keep even
though the source graph is shared. Bundle output is unchanged, byte for
byte, for all four entries.

The nine `packages/workglow/src/*.browser.ts` shims look like the same
shape and are deliberately left alone — they re-export a BARE specifier
(`@workglow/openai/ai`), which is re-resolved under the consumer's own
conditions at every hop, so the two identical files land on different
modules and their being identical IS the mechanism.

Co-Authored-By: Claude <noreply@anthropic.com>
…parity

Three guards, all source-only (no build, works under `use-source`):

- `duplicateBrowserEntryViolations` (ExportTypesPairing.test.ts) reports
  a `src/*.browser.ts` identical to the `src/<stem>.ts` beside it whose
  specifiers are ALL relative. The relative/bare distinction is the whole
  rule: a relative specifier resolves once, so both entries are the same
  module graph and the split is nominal; a bare one is re-resolved under
  the consumer's conditions at every hop, so two identical files land on
  different modules — which is why the `packages/workglow` shims are
  correctly identical and must never be reported. Both branches carry a
  fixture, since no violation survives in the tree.

- `ExportBarrelParity.test.ts` parses the top-level re-exports of each
  provider's `src/ai/index.ts` and `src/ai/index.browser.ts` and asserts
  `node \ browser` equals a pinned `INTENTIONAL_NODE_ONLY` fixture
  (`_testOnly` for each of the five providers), with a staleness check so
  a pin that stops describing a real difference fails. The parser is
  regex-based, so an unclassifiable statement is REPORTED rather than
  skipped — the same call `buildEntryViolations` makes for an underivable
  dist stem.

- `findBrowserBlock` replaces a top-level `?.browser` lookup in
  `browserSplitViolations`, which disagreed with its own recursing
  sibling `nodeImportTarget`: a `{ import: { browser: {…}, default: … } }`
  resolved its node target through the recursion while the browser block
  sat one level down, invisible to every rule keyed on it. Fixture added
  for the no-implementation case reached that way.

Co-Authored-By: Claude <noreply@anthropic.com>
@sroussey
sroussey merged commit 6efb8a2 into claude/wonderful-turing-rjtcnx-ai-types Aug 15, 2026
10 of 11 checks passed
sroussey added a commit that referenced this pull request Aug 16, 2026
…els dropped (#795)

* fix(providers): restore the runtime-agnostic exports the browser barrels dropped

Five providers keep a hand-maintained `src/ai/index.browser.ts` beside
`src/ai/index.ts`. Four of them omitted modules that carry no
platform-specific code, so a `customConditions: ["browser"]` consumer
could not import them at all.

The omissions were invisible until the exports-map fix in #717 routed
browser consumers to the browser declarations; they now read as `TS2305`.
That they are accidental is evidenced by the bundles themselves: every
omitted module is ALREADY compiled into that provider's browser bundle
via the runtime entry (e.g. `registerOpenAi`, which IS in the browser
barrel, imports `registerOpenAiImageValidator`), and no `src` tree of the
five contains a single `node:` import. Only the `export *` line was
missing.

Restored, per provider:

- deepseek: `assertNotTruncatedByReasoning`,
  `DEEPSEEK_DEFAULT_REASONING_ALLOWANCE`, `resolveMaxTokens`
  (`DeepSeek_Client`); `DeepSeek_ModelSearch_Stream`
  (`DeepSeek_ModelSearch`); `DeepSeekToolChoiceNotHonoredError`,
  `assertToolChoiceHonored`, `isForcingToolChoice`
  (`DeepSeek_ToolCalling`)
- openai: `registerOpenAiImageValidator` (`OpenAI_ImageValidation`);
  `OpenAI_ModelSearch_Stream` (`OpenAI_ModelSearch`)
- xai: `Xai_ModelSearch_Stream` (`Xai_ModelSearch`)
- openrouter: `openRouterWorkerRunFnSpecs`, `deriveCapabilitiesFromMeta`,
  `inferOpenRouterCapabilities`, `OPENROUTER_RUN_FN_SPECS`
  (`OpenRouter_Capabilities`); `OpenRouterRawModel`,
  `OPENROUTER_FALLBACK_MODELS`, `fetchOpenRouterModels`,
  `mapOpenRouterModels`, `OpenRouter_ModelSearch_Stream`
  (`OpenRouter_ModelSearch`)

`DeepSeek_ToolCalling` keeps the NAMED form the node barrel uses, which
is what holds `DeepSeek_ToolCalling_Stream` out of the main-thread barrel
on both platforms.

ollama is deliberately unchanged: its `Ollama_ModelSearch` is exported by
NEITHER barrel, so the two agree, and `_testOnly` is its only delta.
`_testOnly` stays node-only everywhere — it is `@internal`, for
`@workglow/test` alone, and belongs behind a `./test` entry the way
`packages/ai` already did it.

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor(providers): re-export the node entry from the browser entry where no split exists

`providers/llamacpp-server` and `providers/stable-diffusion-server` each
carried a `src/ai.browser.ts` byte-identical to `src/ai.ts` and a
`src/ai-runtime.browser.ts` byte-identical to `src/ai-runtime.ts`, all
four naming RELATIVE specifiers (`./ai/index`, `./ai/runtime`).

A relative specifier is resolved once, by the importing file's own path,
and nothing in this toolchain substitutes `X.browser.ts` for `X.ts` on
one: `--target=browser` changes the compile target, not the resolver, and
a manifest's `browser` field applies to bare specifiers. So both entries
already pulled in the same module graph — the declaration split was
nominal, two `.d.ts` files kept equal only by hand.

Each `.browser.ts` now re-exports its node peer, which cannot drift.
These packages keep their `browser` condition: `--target=browser`
produces a genuinely different bundle, so the entry earns its keep even
though the source graph is shared. Bundle output is unchanged, byte for
byte, for all four entries.

The nine `packages/workglow/src/*.browser.ts` shims look like the same
shape and are deliberately left alone — they re-export a BARE specifier
(`@workglow/openai/ai`), which is re-resolved under the consumer's own
conditions at every hop, so the two identical files land on different
modules and their being identical IS the mechanism.

Co-Authored-By: Claude <noreply@anthropic.com>

* test(exports): flag a duplicate browser entry and pin browser barrel parity

Three guards, all source-only (no build, works under `use-source`):

- `duplicateBrowserEntryViolations` (ExportTypesPairing.test.ts) reports
  a `src/*.browser.ts` identical to the `src/<stem>.ts` beside it whose
  specifiers are ALL relative. The relative/bare distinction is the whole
  rule: a relative specifier resolves once, so both entries are the same
  module graph and the split is nominal; a bare one is re-resolved under
  the consumer's conditions at every hop, so two identical files land on
  different modules — which is why the `packages/workglow` shims are
  correctly identical and must never be reported. Both branches carry a
  fixture, since no violation survives in the tree.

- `ExportBarrelParity.test.ts` parses the top-level re-exports of each
  provider's `src/ai/index.ts` and `src/ai/index.browser.ts` and asserts
  `node \ browser` equals a pinned `INTENTIONAL_NODE_ONLY` fixture
  (`_testOnly` for each of the five providers), with a staleness check so
  a pin that stops describing a real difference fails. The parser is
  regex-based, so an unclassifiable statement is REPORTED rather than
  skipped — the same call `buildEntryViolations` makes for an underivable
  dist stem.

- `findBrowserBlock` replaces a top-level `?.browser` lookup in
  `browserSplitViolations`, which disagreed with its own recursing
  sibling `nodeImportTarget`: a `{ import: { browser: {…}, default: … } }`
  resolved its node target through the recursion while the browser block
  sat one level down, invisible to every rule keyed on it. Fixture added
  for the no-implementation case reached that way.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
@sroussey
sroussey deleted the claude/optimistic-goldberg-onotd9-browser-barrel-parity branch August 24, 2026 18:48
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