fix(dbt-tools): move to dbt-integration 0.3.17 so the dbt adapter starts under Bun

Merged
RA
17h ago
+572-640

Issue for this PR

Closes #1367

Type of change

  • Bug fix

What does this PR do?

Scope: No changes beyond what the 0.3.17 bump requires, plus reading the stale-manifest flag. What the bump requires includes dbt_lineage skipping the adapter start while dbt-integration has no getModelLineage.

Moves packages/dbt-tools from @altimateai/dbt-integration 0.2.14 to ^0.3.17. 0.2.x starts its Python child through python-bridge, whose bluebird.promisifyAll(child_process) throws under Bun (this.#stdin), so the dbt-first path of sql_execute never ran. 0.3.x vendors the bridge over a plain fd-3 pipe; inside a single-file executable it runs its script from a temporary directory that it removes on exit. 0.3.17 also pipes the bridge's stdout and stderr to the adapter's terminal, so dbt's log lines stay off the TUI, run --format json and mcp serve. python-bridge and bluebird leave the lockfile.

  • adapter.ts: 0.3.x's adapter takes a UnitTestParser and a SemanticModelParser; the Fusion integration no longer takes the cloud-variant detector. dbt Cloud keeps it and also gets the dependencies of the dbt Core command engine that every variant but fusion-stable runs on.
  • adapter.ts / index.ts: 0.3.17 logs the bridge's stderr at debug, which the terminal here dropped. A crash at bridge startup is told only there, so those lines go in the log buffer, and a failed start returns them (logs in the JSON, stderr with --format text). diagnose() loses its python-bridge case, which 0.3.x can't reach.
  • stale-manifest.ts, graph.ts, schema-verify.ts: after a failed project check, 0.3.17 can answer from the manifest an earlier parse left on disk and sets ParsedManifest.stale. children, parents and schema-verify then lead with a stale_manifest note saying so, with its age; failure-text.ts puts it first in a --format text error too. Answers on a current manifest don't change.
  • dbt-lineage.ts / register.ts: 0.3.17 starts under Bun but has no getModelLineage, so a call with write access would run dbt deps and a parse, wait up to 120 s, then answer from the manifest anyway. It now answers from the manifest at once. The check reads the installed class, so it opens by itself in a release that has the method.
  • copy-python.ts / release.yml / publish.ts: the bridge finds node_python_bridge.py from the bundle's own location (fileURLToPath(import.meta.url)), so the __dirname patch is gone. The build and release smoke check fail if those references go or a build-time path appears.
  • Tests: build-adversarial.test.ts tested the removed patch; its other checks move to build-integrity.test.ts, plus a test that runs the package in the publish.ts layout from another directory. adapter-bun.test.ts runs the CLI from source under Bun in core, corecommand and dbt Cloud modes and checks that each spawns the bridge, with a stand-in python that prints a marker on each stream. The dbt Cloud cases get a stand-in dbt Cloud CLI, because 0.3.17 starts dbt Cloud mode only on a dbt that says it is that CLI. stale-manifest.test.ts covers the note. dbt-first-execution.test.ts no longer skips its adapter test on the python-bridge error. The dbt_lineage tests stub the lineage check, and one checks the guard.

Claims

  • C1: Under Bun, the dbt-first path of sql_execute starts the dbt adapter and runs the query through dbt, from source and from the compiled binary.
  • C2: The compiled binary runs the bridge from a real directory, not /$bunfs. No Python process and no temporary bridge directory is left after the call.
  • C3: altimate-dbt, laid out as publish.ts ships it and run by Node from another directory, starts the node_python_bridge.py next to its own bundle. The build and the release smoke check fail if a build-time path is baked into the bundle.
  • C4: adapter.ts builds the adapter with what 0.3.x's constructors take: both new parsers, the cloud-variant detector for dbt Cloud only, and dbt Cloud's Core command engine dependencies.
  • C5: No line the bridge prints is written to the host's stdout or stderr as it arrives. Its stderr lines go to altimate-dbt's log buffer, which leaves only inside an error answer (C6); opencode never reads that buffer. mcp serve --workspace-tools writes only protocol frames to stdout through a sql_execute call.
  • C6: When the bridge fails at startup, what it printed on stderr reaches the CLI's caller: in logs in the JSON answer, on stderr with --format text.
  • C7: After a failed project check, an answer from children, parents or schema-verify built from a manifest an earlier parse left, its errors included, says so and gives the manifest's age, in the JSON answer and with --format text. On a current manifest those answers are unchanged.
  • C8: While dbt-integration has no getModelLineage, dbt_lineage starts no dbt adapter. sql_execute's dbt path doesn't change.

Residuals

  • R1: When the adapter can't start (no dbt in the venv, a broken project), sql_execute still falls back to the configured warehouse without saying why, as on main.
  • R2: With a stale manifest, children and parents answer { stale_manifest, models } instead of a list, so the note leads a long answer. Nothing in this repo parses them; agents read them.
  • R3: children's and parents' dbt ls fallback is a live answer, so it carries no note.
  • R4: diagnose() in index.ts doesn't match 0.3.17's interpreter-not-found message and would give the default fix text. The CLI's prerequisite check reports a missing interpreter before that point.
  • R5: The note goes on every schema-verify answer while the manifest is stale, so build's per-model summary would repeat it for each mismatch, and a schema-verify with no --model carries it. build writes that summary only after a successful project-wide dbt build.

How did you verify your code works?

This branch's columns were run on the published @altimateai/dbt-integration 0.3.17 from npm.

sql_execute through the tool registry (debug agent build --tool sql_execute, no model turn) in a dbt-core 1.11.7 / DuckDB project with no warehouse configured, select count(*) as n from {{ ref("customers") }}, which only dbt can run:

beforethis branch
compiled binaryv0.12.8: falls back, "No warehouse configured"n = 100 via dbt (core, corecommand)
from source (Bun)main: falls back, samen = 100 via dbt

The binary left no Python process or temporary directory, and neither stream had a dbt log line.

Behaviourdbt-integration 0.3.150.3.17, without this PR's handlingthis branch
mcp serve --workspace-tools, non-frame stdout lines over a sql_execute call120
Traceback at bridge startupon stderrnowherein logs; on stderr with --format text
children, parents with a broken profile and a 2-hour-old manifest (core, corecommand)a list, exit 0, nothing about its ageleads with "…written 2 hours ago"
schema-verify on the same projectits error, nothing about the manifest's agethe same error, with the note
dbt_lineage with write access (jaffle, from source)starts the adapter, 7.9–8.0 s, adds "dbt integration not used: dbt-integration has no getModelLineage"no adapter, 4.1–5.0 s, no note
  • altimate-dbt in the publish.ts layout under Node 22 starts the bridge next to its own bundle.
  • dbt-tools: typecheck clean, 112 pass, 0 fail, the same with no dbt, dbt-core or a dbt Cloud CLI on PATH, and with Node 22 on PATH. opencode: typecheck clean; its 28 dbt test files pass, 578 with no dbt on PATH and 583 with dbt-core, which runs the real-dbt validators. dbt-first-execution passes 10 of 10 against a real project in core and corecommand.
  • Each new test fails with its fix removed: the stderr lines, the failed start's logs, the listener order, the note on each command and in a text error, and the dbt_lineage guard.

Cost side effects

None. sql_execute still sends one query per call; in a dbt project it now runs through dbt's adapter instead of falling back to the configured warehouse. dbt_lineage starts no adapter, so it runs no dbt deps or parse.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR
  • I checked for cost side effects (model calls, queries or cloud resources that grow with user data) and described them, or wrote "none"

Appendix: complexity delta

Appendix — complexity delta (dbt-integration 0.3.17 bump)

3aa43a81a3 → 88f730fe99 · only functions this diff touches · advisory, not a gate.

FunctionFileCognitiveCCNStatus
verify L35packages/dbt-tools/src/commands/schema-verify.tsnew → 26new → 30new ≥15 — needs decomposition
fromAdapter L213packages/opencode/src/altimate/tools/dbt-lineage.ts17 → 18 ▲13 → 14 ▲hard to follow
main L114packages/dbt-tools/src/index.ts15 → 13 ▼29 → 28 ▼▼ improved — moderate
output L68packages/dbt-tools/src/index.ts14 → 11 ▼12 → 11 ▼▼ improved — moderate
diagnose L39packages/dbt-tools/src/index.ts5 → 4 ▼12 → 9 ▼▼ improved — trivial
schemaVerify L29packages/dbt-tools/src/commands/schema-verify.ts26 → 1 ▼30 → 2 ▼▼ improved — needs decomposition → trivial

Summary: 14 touched · 2 rose · 5 improved · 7 new (max cognitive 26) · net cognitive Δ +6

🎯 Where the weight sits (flagged functions):

  • verify (packages/dbt-tools/src/commands/schema-verify.ts): densest branching L85–95 (~12 branch points, nesting to depth 2; heuristic)

Disposition: for each flagged row that lands ≥15 — reduce it, or record the residual (Rn) that justifies the shape. One line each. Flagged rows under 15 are attention markers only.

ℹ️ How to read these numbers

Cognitive (Sonar spec) counts breaks in linear reading flow — each if/loop/catch/ternary/boolean-operator switch adds 1, and nesting makes every further break cost more. It approximates how much you must hold in your head to follow the function: 0–5 trivial · 6–10 easy · 11–15 moderate (15 = Sonar's recommended per-function cap) · 16–25 hard to follow · >25 needs decomposition.

CCN (cyclomatic) counts independent paths — also the minimum number of test cases for full branch coverage of the function.

Only functions this diff touches are measured, as deltas — pre-existing complexity is not counted against this change. Rising numbers aren't automatically wrong; they're where review attention should go. Test files excluded.

verify is schemaVerify's body, moved unchanged so every answer gets the note. fromAdapter's +1 is the guard for C8.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VEmKQx8gR7vd6ettA3mWjP

Summary by CodeRabbit

  • New Features
    • Added support for parsing dbt unit tests and semantic models.
  • Bug Fixes
    • Improved the CLI’s ability to locate its Python bridge when launched from a different directory.
    • Strengthened package checks to catch missing bridge files and unintended references to local build environments.
    • Verified that installed bundles can start correctly and that repeated builds produce consistent output. These checks cover dbt Cloud and Core Command Engine configurations.