Skip to content

fix(tapestore): survive republic→bub.tape TapeEntry drift and drop _build_llm private-API dependency#49

Closed
CorrectRoadH wants to merge 6 commits into
bubbuild:mainfrom
CorrectRoadH:fix/tapestore-otel-tape-entry-validation
Closed

fix(tapestore): survive republic→bub.tape TapeEntry drift and drop _build_llm private-API dependency#49
CorrectRoadH wants to merge 6 commits into
bubbuild:mainfrom
CorrectRoadH:fix/tapestore-otel-tape-entry-validation

Conversation

@CorrectRoadH

@CorrectRoadH CorrectRoadH commented Jun 28, 2026

Copy link
Copy Markdown

close #43 #48

Root cause

bub is migrating its tape implementation from the external republic package to a vendored bub.tape module (upstream commit 04960b1). During this transition the two modules ship structurally identical but distinct classes: bub.tape.TapeEntry and republic.TapeEntry fail each other's isinstance check.

That broke both plugins, in different ways:

Fix

An earlier revision of this PR hard-switched all imports to bub.tape / bub.runtime. That traded one breakage for another: released bub versions still ship the republic API (CI failed with ModuleNotFoundError: No module named 'bub.runtime'). The final approach makes the plugins work with both sides of the transition:

bub-tapestore-otel

  • New _compat.py module as the single place that resolves tape types: try bub.tape first, fall back to republic for older bub. Exporter, store, and tests all import from it.
  • TraceProjection.entries is now list[SkipValidation[TapeEntry]]. The field is an opaque internal payload (only accessed via duck typing: kind / payload / id / date), and the plugin cannot control which TapeEntry class the host bub constructs — so validating class identity adds no safety and re-creates the silent zero-span failure whenever plugin and host disagree. SkipValidation keeps the static type for readers and type checkers while dropping only the harmful runtime isinstance check.
  • Regression test: entries built from a foreign class must still project into a trace instead of being dropped.

bub-tapestore-sqlite

  • Keep the republic imports (that is what released bub provides here).
  • Drop the _build_llm private-API dependency: the embedding client is now built lazily via the public AnyLLM.create(provider, **settings.model_client_kwargs(provider)), typed as AnyLLM | None.

Test results

uv run --frozen --group test pytest   # same command as CI
257 passed, 2 skipped

(bub-tapestore-otel + bub-tapestore-sqlite account for 32 of those.)

Note for maintainers: CI runs on this fork PR are pending workflow approval (action_required).

🤖 Generated with Claude Code

https://claude.ai/code/session_017ZBsYdsdb8K2pvXKjFeWZY

…ve TapeEntry class drift

When bub resolves to git HEAD instead of the workspace-locked commit,
TapeEntry instances fail pydantic's isinstance check against the TapeEntry
class captured at schema-build time, causing every append to raise a
ValidationError that the exporter silently swallows — resulting in zero
exported spans.

Use SkipValidation[list[TapeEntry]] on TraceProjection.entries so pydantic
bypasses the isinstance check entirely.  The field is internal-only and
never needs runtime coercion, so skipping validation is safe and is the
explicit recommendation from the pydantic docs for this class of problem.

Adds a regression test that feeds build_tape_trace entries whose class
object differs from republic.TapeEntry to guard against future drift.

Closes bubbuild#47

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EcGvz588KPpJVkEP2eaDby
@CorrectRoadH CorrectRoadH marked this pull request as draft June 28, 2026 12:59
…uild_llm dependency

SQLiteTapeStore.__init__ imported and called bub.builtin.agent._build_llm,
a private helper that was removed in bub >= 0.3.10.dev2. This caused an
ImportError before any tape operation could run, even when the store was
used without semantic search.

Fixes:
- Remove the _build_llm call from __init__; initialize self._llm = None
- Add a module-level _build_embedding_client() that uses the public
  AnyLLM.create() and bub.ensure_config(AgentSettings) API, with a
  model_client_kwargs() fallback for newer bub versions
- Build the client lazily in _compute_embedding(), only when semantic
  search is actually invoked
- Update tests to mock _build_embedding_client in bub_tapestore_sqlite.store
  instead of the removed bub.builtin.agent._build_llm

Non-semantic store operations (append, fetch_all without query, list_tapes,
reset) now work against any bub version without touching the LLM layer.

Closes bubbuild#43

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EcGvz588KPpJVkEP2eaDby
@CorrectRoadH CorrectRoadH changed the title fix(bub-tapestore-otel): skip pydantic validation on entries to survive TapeEntry class drift fix: resolve bub version drift in bub-tapestore-otel and bub-tapestore-sqlite Jun 28, 2026
bub replaced republic with its own tape implementation (commit 04960b1).
Plugins still imported from republic, so bub.tape.TapeEntry instances
passed at runtime failed pydantic's isinstance check against
republic.TapeEntry — causing ValidationError in otel and ImportError
in sqlite (from the now-deleted _build_llm).

Changes across both plugins:
- Replace all `republic` imports with `bub.tape` / `bub.runtime`
- bub-tapestore-otel: remove SkipValidation workaround (no longer needed
  now that the TapeEntry class identity is correct)
- bub-tapestore-sqlite: replace RepublicError→BubError, ErrorKind from
  bub.runtime; rewrite _build_embedding_client to use the public
  AnyLLM.create(provider, **settings.model_client_kwargs(provider)) API;
  type _embedding_client as AnyLLM|None; simplify _compute_embedding to
  call client.aembedding directly without _core.get_client
- Remove republic>=0.5.7 from both pyproject.toml; add bub to sqlite deps

Closes bubbuild#43
Closes bubbuild#47

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EcGvz588KPpJVkEP2eaDby
@CorrectRoadH CorrectRoadH changed the title fix: resolve bub version drift in bub-tapestore-otel and bub-tapestore-sqlite fix: migrate bub-tapestore-otel and bub-tapestore-sqlite from republic to bub.tape Jun 28, 2026
@CorrectRoadH CorrectRoadH marked this pull request as ready for review June 28, 2026 13:20
@CorrectRoadH CorrectRoadH force-pushed the fix/tapestore-otel-tape-entry-validation branch from 5ccbfb8 to bab3d9d Compare June 29, 2026 04:09
CorrectRoadH and others added 3 commits June 30, 2026 13:44
…blic vs bub.tape)

bub >= 0.3.10 vendors the tape module as bub.tape and constructs TapeEntry
instances from it, while the plugin validated entries against republic's
TapeEntry. The pydantic dataclass isinstance check then rejected every entry,
and OTelTapeStore.append swallowed the ValidationError, so the exporter
silently emitted zero spans.

- import TapeEntry (and tape-store types) from bub.tape first, falling back
  to republic for older bub
- type TraceProjection.entries as list[Any]: entries are an internal payload,
  and validating their class identity turns any future drift back into the
  same silent zero-span failure

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DeHZzZ8Xows9kMUHb5RkqZ
… drop list[Any]

- move the bub.tape/republic fallback imports into a single _compat module
  instead of repeating the try/except in exporter, store, and tests
- type TraceProjection.entries as list[SkipValidation[TapeEntry]]: keeps the
  static type while still skipping pydantic's class-identity check, which is
  what the drift fix requires
- add a regression test that entries from a foreign TapeEntry class still
  project into a trace instead of being dropped

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ZBsYdsdb8K2pvXKjFeWZY
@CorrectRoadH CorrectRoadH changed the title fix: migrate bub-tapestore-otel and bub-tapestore-sqlite from republic to bub.tape fix(tapestore): survive republic→bub.tape TapeEntry drift and drop _build_llm private-API dependency Jul 7, 2026
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.

bub-tapestore-sqlite breaks against latest bub main after _build_llm removal

1 participant