Skip to content

feat(catalog): browser-safe model-catalogue subpath#186

Merged
drewstone merged 1 commit into
mainfrom
feat/browser-safe-catalog
Jul 14, 2026
Merged

feat(catalog): browser-safe model-catalogue subpath#186
drewstone merged 1 commit into
mainfrom
feat/browser-safe-catalog

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

Fleet fix at the source for the client-bundle-poison class (third occurrence: legal chat route → legal #256, tax home composer → tax #372).

./runtime is a server barrel: alongside buildCatalog it re-exports createAgentRuntime, which imports @tangle-network/agent-runtime (node:util promisify, child_process execFile). Any client component importing a /runtime VALUE ships Node-only code into its route bundle and the module throws on load.

  • New additive ./catalog subpath re-exports only the pure pipeline (buildCatalog, fetchModelCatalog, normalizeModelId, types). No existing export moves — zero consumer breakage (invariant: additive subpaths).
  • tests/catalog-browser-safe.test.ts walks the source import graph from the subpath and fails on any node builtin / agent-runtime edge, so the subpath can't silently regain a server dependency.

Products should import catalogue values from ./catalog in client code (server code may keep using ./runtime). Follow-up migration for legal/tax picker hooks once this releases.

@tangletools tangletools left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Auto-approved drewstone PR — 0111d9da

This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.

tangletools · auto-approval · reason: drewstone_author · 2026-07-14T02:12:58Z

./runtime is a server barrel — importing any VALUE from it in client
code ships @tangle-network/agent-runtime (node:util, child_process)
into the route bundle, which throws on module load. This broke the
legal-agent chat route and the tax-agent home composer. ./catalog
re-exports only the pure catalogue pipeline; a source-graph test
fails loud if the subpath ever reaches a node builtin.
@drewstone
drewstone force-pushed the feat/browser-safe-catalog branch from 0111d9d to 5300593 Compare July 14, 2026 02:13

@tangletools tangletools left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Auto-approved drewstone PR — 53005936

This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.

tangletools · auto-approval · reason: drewstone_author · 2026-07-14T02:13:33Z

@tangletools tangletools left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Value Audit — sound

Verdict sound
Concerns 0 (none)
Heuristic 0.0s
Duplication 0.0s
Interrogation 932.4s (2 bridge agents)
Total 932.4s

💰 Value — error

value agent produced no parseable value-audit JSON.

  • Model: opencode/deepseek/deepseek-v4-pro
  • Bridge attempts: 3
  • Bridge error: opencode/kimi-for-coding/k2p7: bridge stream ended without value-audit content; opencode/zai-coding-plan/glm-5.2: bridge stream ended without value-audit content; opencode/deepseek/deepseek-v4-pro: Bridge returned 503: {"error":{"message":"cli-bridge admission timed out after 30000ms","type":"admission_rejected","reason":"queue_timeout","admission":{"active":20,"queued":2,"maxActive":20,"maxQueue"

🎯 Usefulness — sound

Additive browser-safe ./catalog subpath re-exports the pure catalogue pipeline out of the server-only ./runtime barrel, with a source-graph regression guard — fixes a real recurring client-bundle-poison class with zero consumer breakage.

  • Integration: Wired correctly at all three seams: tsup.config.ts:9 adds the entry, package.json:61-65 adds the export map, src/catalog/index.ts re-exports from the shared leaf ../runtime/model-catalog. Verified the premise: src/runtime/index.ts:17 barrel-exports ./loop, and src/runtime/loop.ts:31 re-exports from @tangle-network/agent-runtime (Node-only), so any VALUE import from ./runtime poisons a client bundl
  • Fit with existing patterns: Matches the codebase's dominant pattern exactly. tsup.config.ts already has ~40 subpaths following X/index: src/X/index.ts, and package.json exports maps each one identically (types/import/default triple). This is the canonical 'additive subpath' shape the repo uses for every new capability — not a competing design. The split-source approach (catalog re-exports from ../runtime/model-catalog rath
  • Real-world viability: Holds up under realistic use. Verified src/runtime/model-catalog.ts has zero imports at all (rg ^import|^from returns nothing) — it is a pure data-transformation module (filter/dedupe/rank/feature over RouterModel[] plus a fetch+TTL cache), so the subpath cannot accidentally drag a Node edge through a transitive import today. The regression guard in tests/catalog-browser-safe.test.ts is genuine,
  • Model: opencode/zai-coding-plan/glm-5.2
  • Bridge attempts: 1

No concerns — sound change, no better or existing approach found. ✅


What this audit checks

It judges the change on its merits — not whether it was tasked out in an issue. Unticketed, fast-moving work is fine; the question is whether the change is good and whether a better or existing approach should be used instead.

Pass What it asks
Heuristic Vague title? Whitespace-only or cruft-bearing diff? (content signals only)
Duplication Do added function/class names already exist elsewhere in the repo?
Value Audit What does it do? What goal does it achieve? Is it good? Better architecture or already-exists?
Usefulness Audit Does it integrate and fit? Will it hold up in real use and actually get used?

Findings are concerns, not blocks — the human reviewer decides what to do with them.

value-audit · 20260714T024710Z

@tangletools

Copy link
Copy Markdown

⚠️ Review Interrupted — 53005936

The review runner stopped before publishing a final verdict: no_active_process.

State Detail
Interrupted no active process

No review verdict was produced for this run. Trigger a fresh review on the current PR head if the PR is still open.

tangletools · #186 · model: kimi-for-coding · updated 2026-07-14T03:01:46Z

@tangletools tangletools left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Auto-approved drewstone PR — 53005936

This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.

tangletools · auto-approval · reason: drewstone_author · 2026-07-14T03:07:11Z

@tangletools tangletools left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Value Audit — sound-with-nits

Verdict sound-with-nits
Concerns 3 (3 weak-concern)
Heuristic 0.0s
Duplication 0.0s
Interrogation 306.2s (2 bridge agents)
Total 306.2s

💰 Value — sound-with-nits

Adds an additive ./catalog subpath re-exporting only the pure model-catalogue pipeline so client bundles stop pulling agent-runtime (node:util/child_process) through the ./runtime barrel — correct fix, follows the additive-subpath grain, with a static import-graph guard test; only nit is the doc

  • What it does: Adds a new additive ./catalog subpath (src/catalog/index.ts) that re-exports only the pure catalogue pipeline — buildCatalog, fetchModelCatalog, normalizeModelId, __resetCatalogCache, and the CatalogModel/ModelCatalog/RouterModel types — from src/runtime/model-catalog.ts. Wires it in tsup.config.ts:9 and package.json:61-65. Adds tests/catalog-browser-safe.test.ts, a stati
  • Goals it achieves: Give client components (model pickers, chat composers) a browser-safe import target so they stop transitively pulling @tangle-network/agent-runtime — which uses node:util promisify and child_process execFile — into client route bundles, where the module throws on load. This is the fleet root-cause fix for the recurring client-bundle-poison class (legal chat route #256, tax home composer
  • Assessment: Sound on its merits. The diagnosis is verifiable: src/runtime/index.ts re-exports ./loop and ./certified-delivery, and those files import @tangle-network/agent-runtime at src/runtime/loop.ts:31,42 and src/runtime/certified-delivery.ts:30 — so any client component importing a single VALUE from ./runtime (including buildCatalog) drags the whole agent-runtime transitive graph into its
  • Better / existing approach: Partially. The materially cleaner root-cause fix would be to make ./runtime actually substrate-free again — peel createAgentRuntime/runAppToolLoop/certified-delivery into a separate server-only subpath (e.g. ./runtime/server) so the existing ./runtime barrel matches its documented contract. That is a BREAKING change for every consumer currently importing the tool-loop from ./runtime, s
  • Model: opencode/zai-coding-plan/glm-5.2
  • Bridge attempts: 2
  • Bridge warning: opencode/kimi-for-coding/k2p7: bridge stream ended without value-audit content

🎯 Usefulness — sound-with-nits

Clean additive subpath that gives client code a Node-free import target for catalogue values; fits the repo's established subpath invariant and ships with a real regression guard.

  • Integration: Wires correctly through all three required seams — package.json ./catalog export (package.json:61-65), tsup entry catalog/index (tsup.config.ts:9), and src/catalog/index.ts re-exporting the pure set from ../runtime/model-catalog. The published source it re-exports (src/runtime/model-catalog.ts:1-291) is genuinely pure: only fetch + pure data transforms, no node: or agent-runtime imports (verif
  • Fit with existing patterns: Fits the codebase grain exactly. The README's module map codifies the invariant: 'Additive subpaths. New capability = new ./subpath (entry in tsup.config.ts + exports in package.json + root barrel). Never a breaking change to an existing export.' tsup.config.ts already has 50+ sibling subpaths following this exact pattern. No existing browser-safe equivalent competes: web-react exports components,
  • Real-world viability: The cure is correct for the failure class: bundlers cannot reliably tree-shake transitive export * barrels that pull server-only modules, so a thin re-export subpath with its own dist entry is the right structural fix, not a sideEffects:false tweak. The pipeline holds under realistic use — fetchModelCatalog has a 5-min TTL cache and serves stale-on-error (src/runtime/model-catalog.ts:256-286),
  • Model: opencode/zai-coding-plan/glm-5.2
  • Bridge attempts: 1

💰 Value Audit

🟡 Docs still claim ./runtime is substrate-free / browser-safe; this PR formalizes the drift instead of reconciling it [maintenance] ``

AGENTS.md:18 ('Substrate-free is a feature. /runtime, /web, /crypto, /redact import nothing — they are pure mechanism') and README.md:14,18 ('substrate-free modules (/runtime, /web, …)' / 'the substrate-free modules use only Web-standard APIs — fetch, Web Crypto, TextEncoder') both state that /runtime is browser-safe. In practice src/runtime/loop.ts:31,42 and src/runtime/certified-delivery.ts:30 import @tangle-network/agent-runtime, so the barrel is NOT substrate-free — w

🎯 Usefulness Audit

🟡 Guard test resolves only .ts, silently skips .tsx / index directories [robustness] ``

tests/catalog-browser-safe.test.ts:31-34 appends ${base}.ts only when the spec lacks an extension. If the catalogue pipeline ever imports a local .tsx (e.g. a React adapter) or a bare directory spec that resolves to index.ts, the walk will not traverse into it and a forbidden edge could hide. The catalogue is pure data today so this is hypothetical, but cheap to harden: probe .ts/.tsx/.d.ts and resolve directory specs to index.ts. Not gating.

🟡 Catalogue source still physically lives under src/runtime/ [ergonomics] ``

src/runtime/index.ts:1 re-exports model-catalog, so the source file sits in src/runtime/ despite /runtime being documented (README module map) as the bounded tool-loop + streamTurn adapter — catalogue is a different concern. The minimal-touch choice (re-export from new subpath, don't move source) is defensible for a fleet fix and respects the additive invariant, but the architecturally cleaner long-term shape is to invert ownership: make src/catalog/ the home and have /runtime re-export from it


What this audit checks

It judges the change on its merits — not whether it was tasked out in an issue. Unticketed, fast-moving work is fine; the question is whether the change is good and whether a better or existing approach should be used instead.

Pass What it asks
Heuristic Vague title? Whitespace-only or cruft-bearing diff? (content signals only)
Duplication Do added function/class names already exist elsewhere in the repo?
Value Audit What does it do? What goal does it achieve? Is it good? Better architecture or already-exists?
Usefulness Audit Does it integrate and fit? Will it hold up in real use and actually get used?

Findings are concerns, not blocks — the human reviewer decides what to do with them.

value-audit · 20260714T031237Z

@tangletools

Copy link
Copy Markdown

✅ No Blockers — 53005936

Review health 100/100 · Reviewer score 80/100 · Confidence 80/100 · 5 findings (5 low)

glm: Correctness 80 · Security 80 · Testing 80 · Architecture 80

Reviewer score is advisory once the run is complete and the verdict has no blockers.

Full multi-shot audit completed 4/4 planned shots over 4 changed files. Global verifier still owns final merge decision.

🟡 LOW No regression test pins the browser-safe property the PR exists to fix — src/catalog/index.ts

The motivation (per the comment) is a recurring regression: client bundles pulling Node-only code through /runtime (#256, #372). The fix ships a new subpath but adds no test that imports dist/catalog/index.js and asserts the resolved graph contains no node:-prefixed modules (or that the entry's shared chunk lacks child_process/node:util). A one-line assertion on the built artifact would lock the invariant the PR is paying for. model-catalog.ts itself has no test file today, so this is a pre-existing gap, not a regression — but the new entry is the natural place to anchor the property.

🟡 LOW Test-only __resetCatalogCache exported through the public browser-safe subpath — src/catalog/index.ts

The subpath comment claims it re-exports 'ONLY the pure catalogue pipeline', but __resetCatalogCache (model-catalog.ts:289, marked 'Test-only: clear the catalogue cache') is a test escape hatch, not pipeline logic. Already exposed via ./runtime and the root barrel so this is not a new leak — but it slightly undercuts the 'pure pipeline only' framing. Consider re-exporting just the 3 production fns + 3 types here and letting tests import the reset from ./runtime.

🟡 LOW FORBIDDEN list omits most Node builtins as bare specifiers — tests/catalog-browser-safe.test.ts

Only child_process, fs, util are listed as bare-specifier builtins; path, os, crypto, stream, http, buffer, net, dns, zlib, etc. are NOT caught. The /^node:/ entry covers the prefixed form (node:crypto), but a bare import { randomBytes } from 'crypto' would pass the guard while breaking the browser bundle. The codebase convention uses node: prefixes today (this very file uses node:fs/node:path), so practical risk is low, but the guard's comment promises 'fail loud on the first unsafe edge' and this is a hole in that promise. Fix: replace the explicit bare list with a check against module.builtinModules (Node API) or a hardcoded full list of ~40 builtins.

🟡 LOW Import resolver hardcodes .ts, misses .tsx/.js/directory barrels — tests/catalog-browser-safe.test.ts

When resolving a relative spec the walker appends .ts only if the path doesn't already end in .ts. If any reachable file is .tsx (React surfaces) or .js, or if a spec targets a directory (barrel index.ts), the walk either pushes a nonexistent path (→ readFileSync throws ENOENT, test errors opaquely) or silently skips traversal past that node, hiding unsafe imports downstream. Not a current issue — the catalog graph is one edge deep and all .ts — but the guard will silently miss edges if the catalog grows a .tsx/.js file or a sub-directory. Fix: probe [base, base+'.ts', base+'.tsx', base+'.js', base+'/index.ts', ...] and push the first that exists.

🟡 LOW Regex only matches from '...' imports, misses dynamic/side-effect forms — tests/catalog-browser-safe.test.ts

/from\s+['"]([^'"]+)['"]/g catches import {x} from 'y' and export ... from 'y' but misses import('dynamic'), bare side-effect import 'polyfill', and require('...'). A dynamic import('node:fs') or require('child_process') added to the catalog pipeline would bypass the guard. Static text analysis will always have this limit versus a TS-aware walker (ts-morph / esbuild meta), but the gap should at least be documented as a known blind spot, or the regex widened to also match import\s*\( and require\s*\(.


tangletools · 2026-07-14T03:18:00Z · trace

@tangletools tangletools left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Approved — 5 non-blocking findings — 53005936

Full multi-shot audit completed 4/4 planned shots over 4 changed files. Global verifier still owns final merge decision.

Full immutable report for this review: trace

Summary comment for this run: full summary


tangletools · 2026-07-14T03:18:00Z · immutable trace

@drewstone
drewstone merged commit e2794a7 into main Jul 14, 2026
1 check passed
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