Skip to content

v0.1.6 — dbt-shop polish

Choose a tag to compare

@db-tycoon-stephen db-tycoon-stephen released this 18 May 17:30
· 32 commits to main since this release
a15a563

Tycoon v0.1.6

Released: 2026-05-18

Headline

v0.1.6 is the dbt-shop polish release. The headline is a portable
semantic-layer surface; the supporting work makes the dbt-side
of tycoon work the way an experienced dbt user would expect.

The four themes worth opening with:

  • Open Semantic Interchange (OSI) scaffolding. A new
    tycoon semantics namespace emits dbt_project/semantic/osi.yaml
    conforming to OSI v0.1.1
    the vendor-neutral spec backed by dbt Labs, Snowflake, Databricks,
    ThoughtSpot, BlackRock, and RelationalAI. dbt MetricFlow stays the
    source of truth; OSI is a portable, published view downstream
    tools can consume directly. Conservative scaffolding — datasets
    and dimensional fields land; metrics are left empty for the user
    to fill in (OSI metrics are SQL expressions tycoon can't guess).
    Sentinel-protected with --force, same pattern as data analyze.
    Includes a semantics doctor subcommand with a hard exit for CI
    and a non-fatal row in tycoon doctor.

  • First-class dbt profile handling. Three new flags
    (--profile / --profiles-dir / --target) on every
    dbt-touching command, with names matching dbt's CLI exactly.
    Resolution order matches dbt too: CLI flag → tycoon.yml
    <dbt_project_dir>/profiles.yml$DBT_PROFILES_DIR
    ~/.dbt/profiles.yml. New top-level tycoon profiles namespace
    (list / show / doctor). Fixes a real bug along the way:
    every dbt-touching surface except register dbt had
    profiles_dir hardcoded to a path relative to the tycoon source
    tree — broken for installed users since v0.1.0.

  • Fivetran metadata read-out. When stack.ingestion = fivetran
    in tycoon.yml, the new tycoon data fivetran sync and
    tycoon data fivetran list commands pull connector + sync
    metadata from the Fivetran Metadata API
    into .tycoon/metadata.duckdb. Tycoon still doesn't run
    Fivetran — this just lights up the existing observability
    surfaces (tycoon data status, tycoon doctor, the
    _tycoon_dlt_usage panel) for Fivetran-managed pipelines.

  • Two CI-escaped bugs fixed — and the test gate that catches
    the next one.
    v0.1.5 shipped to PyPI with rest_api ingest
    completely broken (the README's PokéAPI quickstart didn't work)
    and every install-extra hint silently stripped its [extra]
    name in the rendered Rich output. Both fixed in v0.1.6. Closed
    the gate behind them: a new recipe-doctest harness runs marked
    markdown blocks as subprocesses on every PR, plus a demo-arc
    CLI test that shells out to the real tycoon binary instead of
    Typer's in-process runner.

What landed

tycoon semantics — OSI scaffolding (#28)

# Scaffold OSI YAML from your dbt marts.
tycoon data transform run                # ensure marts exist
tycoon semantics scaffold

# Validate against the vendored OSI v0.1.1 schema (CI-friendly).
tycoon semantics doctor

Output lives at dbt_project/semantic/osi.yaml (project-level, one
file with all metrics) with a # @generated by tycoon semantics
sentinel — re-runs skip files where the user has taken ownership;
pass --force to override.

The conservative-aggregation call was deliberate: OSI metrics are SQL
expressions like SUM(orders.amount), and tycoon can't reasonably
guess intent from a numeric column. Numeric columns become OSI fields
with no dimension: attribute (measure-eligible bases for the user's
hand-written metrics). Datasets and dimensional fields scaffold
automatically.

Path B (Nao consumes OSI directly) is deferred until dbt-core ships
native OSI export. Path C (Rill metrics_views from the OSI source)
is deferred until Rill joins the OSI consortium.

Opt-in auto-trigger: transform.auto_osi_scaffold: true in
tycoon.yml re-runs semantics scaffold after every successful
dbt run / build. Default is off in v0.1.6; planning to flip on in
v0.1.7+ once the surface settles.

tycoon profiles + dbt profile flags (#27)

# Discover what profiles exist across known dbt locations.
tycoon profiles list

# Show a profile with secrets redacted.
tycoon profiles show prod

# Validate active profile + adapter against stack.warehouse.
tycoon profiles doctor

# Override profile resolution on any dbt-touching command.
tycoon data transform run \
  --profile analytics \
  --profiles-dir ~/.dbt \
  --target prod

Flag names match dbt's CLI exactly so muscle memory transfers. The
new central helper at src/tycoon/dbt_profiles.py is the single
resolution path — tycoon data transform, the Dagster
DbtCliResource, and the FastAPI /run/dbt route all route through
it. Previously only tycoon register dbt resolved profiles
correctly; the others hardcoded profiles_dir to a path relative
to the tycoon install tree, which was broken for any user installing
from PyPI.

tycoon doctor includes a non-fatal "dbt profile" row that catches
the most common misconfig: duckdb-vs-snowflake adapter mismatches
between stack.warehouse and the resolved profile's adapter type.

New recipe: docs/recipes/existing-dbt-profile.md
— for users with an existing profiles.yml somewhere on disk.

Fivetran metadata read-out (#26)

# tycoon.yml
stack:
  ingestion: fivetran
  ingestion_managed: false
  ingestion_metadata:
    api_key: ${FIVETRAN_API_KEY}
    api_secret: ${FIVETRAN_API_SECRET}
    group_id: my_group
tycoon data fivetran sync   # mirror connector metadata into .tycoon/metadata.duckdb
tycoon data fivetran list   # human-readable connector list

Each sync writes one snapshot row per connector keyed on
(connector_id, captured_at). The new fivetran_connectors table
lights up tycoon data status (Fivetran panel) and tycoon doctor
(API-credential validation).

Scope simplification from #26's original spec: dropped the proposed
[fivetran] install extra. httpx is already a base dependency
(LM Studio probe + auto-detect), so a [fivetran] extra would have
added zero install footprint and just confused users. The Fivetran
code paths gate on stack.ingestion = fivetran instead — same way
Airbyte / Meltano / "external" already do.

Out of scope: schema-level column metadata (let dbt source freshness
handle that), triggering syncs from tycoon (read-only), Airbyte
Cloud / Stitch / Meltano Cloud (separate issues if/when).

Originally targeted v0.2.x; pulled forward because the surface is
small and the dbt-shop persona this serves overlaps directly with
the profile-handling work above.

Recovery paths: register dbt --create (#34) + ask init (#37)

Two late-cycle pickups that fill the same gap: users who picked
Skip on a wizard prompt during tycoon init previously had no
in-CLI recovery path — they'd hand-write a dbt_project.yml or
hand-edit nao_config.yaml. Both now have first-class commands.

tycoon register dbt --create (#34) bootstraps a fresh dbt
project at ../<project>-dbt (or any path you pass) wired to the
active tycoon warehouse:

tycoon register dbt --create
# → ../my-demo-dbt/dbt_project.yml + profiles.yml
# → tycoon.yml updated with the new dbt_project_dir

Same scaffolder the init wizard uses (_scaffold_dbt_project).
DuckDB and MotherDuck warehouses only; refuses to overwrite an
existing dbt_project.yml. Marks stack.transformation_managed: true
since tycoon owns the project. Cloud warehouses still go through
plain tycoon register dbt <path> after hand-authoring a profile.

tycoon ask init (#37) is the post-config write step for
Nao — idempotent, no prompts. Reads ask.llm from tycoon.yml,
writes .tycoon/nao/nao_config.yaml, refreshes AGENTS.md. Three
concrete cases this fixes:

  1. Hand-edited tycoon.yml — switching ask.llm.provider from
    lm-studio to ollama previously required re-running
    tycoon register llm, which re-prompted for things the user
    didn't want to change. ask init reads the YAML as-is.
  2. Cloned project from a teammate.tycoon/nao/ is gitignored
    (chat history + row previews + PII). Cloning leaves no Nao config.
  3. register llm half-succeededtycoon.yml written, chained
    setup_ask_stack() raised mid-call. ask init is the targeted fix.

History note: tycoon ask init was removed in v0.1.5 because it
had been a confusing alias for register llm (took LLM provider
arguments, prompted). The v0.1.6 re-introduction is a different
contract: no LLM args, no prompts, takes whatever's in tycoon.yml
as gospel. The _STALE_SUBSTRINGS sentinel in
tests/test_cli_surface.py was updated to allow the re-added
surface. Internals refactor: setup_ask_stack() now delegates to a
new _init_nao_project() helper that all three init paths
(tycoon init, register llm, ask init) share.

Pairs with #38tycoon doctor now distinguishes "no LLM
configured at all" from "LLM configured but Nao bootstrap incomplete"
and suggests the right recovery command for each.

Test resilience: recipe doctests + demo-arc gate (#40)

Two CI-escaped bugs landed mid-v0.1.6 cycle: #32 (rest_api
ingest broken for the README quickstart) and the Rich [extra]
bracket strip (every install hint silently dropped its [ask] /
[docs] / [dagster] suffix). Both fixed; both surface in
release-blocking ways that no in-process test could see.

v0.1.6 closes the gate behind them with two complementary additions:

  • tests/test_recipe_doctests.py — walks README.md and
    docs/recipes/*.md for marked code blocks and executes each via
    bash -e -o pipefail in a fresh tmp dir. Marker grammar:

    <!-- tycoon-test: mode=offline -->
    ```bash
    tycoon init --template csv-import --name demo
    tycoon data sources run files
    ```
    

    mode=offline (default) runs on every PR; mode=online is
    gated on --run-online and runs nightly. README's csv-import
    quickstart got the first offline marker. The PokéAPI quickstart
    stays interactive for now; #44
    is filed for the non-interactive sources add flags that would
    let it come back as a mode=online block.

  • tests/test_e2e_demo_arc.py — a subprocess-driven test that
    shells out to the real tycoon binary on PATH (rather than
    Typer's in-process CliRunner like test_templates_e2e.py).
    Catches PATH / venv resolution, Rich rendering bugs, stdout/stderr
    framing, and console-script wiring drift that in-process tests
    can't see. Runs the csv-import demo arc end-to-end in ≤60s.

Both wired into ci.yml (default pytest run). Nightly-e2e
extended with --run-online for the recipe-doctest pass.

Subsumes #33 (README quickstart not gated on a real run).

Bug fixes worth calling out

  • rest_api ingestion was completely broken (#32). The flat
    source-config shape tycoon stores in tycoon.yml was being cast
    directly to dlt's RESTAPIConfig, which expects base_url
    wrapped under client and resources as a list (not a
    comma-separated string). Tycoon's fastest-path quickstart from
    the README was non-functional in v0.1.5. Normalized inside
    _build_rest_api_source, with a regression test that exercises
    the actual rest_api_source build (not just the schema). The
    new recipe-doctest harness will catch the next one of its kind
    in the default suite.
  • Install hints stripped the [extra] name from output. Every
    error() / warn() / info() message that suggested
    pip install 'database-tycoon[ask]' (and similar for [docs]
    / [dagster]) rendered as pip install 'database-tycoon'
    Rich was parsing [ask] as a style tag, finding no such style,
    and silently stripping the brackets. Users hitting these errors
    copied the broken command verbatim and missed the extra. Fixed
    in seven user-facing strings (tycoon ask chat,
    tycoon register llm, tycoon init, tycoon start --only dagster,
    tycoon docs serve/build); locked in with a regex-based
    regression test in tests/test_cli_surface.py.
  • register dbt --create produced a project that failed
    transform run standalone.
    The scaffolded profiles.yml
    ATTACHes data/raw.duckdb read-only, but a user running
    tycoon data transform run before any tycoon data sources run
    (no ingestion yet → no raw.duckdb on disk) saw dbt fail with
    Cannot open database in read-only mode. Surfaced by the new
    e2e test for the #34 recovery path. Fix: _scaffold_dbt_project
    now pre-creates an empty DuckDB file at the raw path if missing.

Dependency hygiene

Drop two redundant pins flagged in v0.1.5's
docs/reference/dependencies.md:

  • websockets==16.0 from [server] — pulled transitively by
    uvicorn[standard]==0.46.0; tycoon never import websockets
    (uses fastapi's WebSocket class).
  • ibis-framework[duckdb]==12.0.0 from [ask] — pulled
    transitively by nao-core; zero import ibis sites in src/
    or tests/.

Plus nao-core 0.1.8 → 0.1.11 (patch-level fixes from the
nao-core team).

No behavior change for users; just removes our redundant declarations.

Out of scope (deferred)

  • #14 — v0.2.0 init wizard redesign (hard breaking change; its
    own cycle)
  • v0.1.7 layer-aware data model — scoped in
    docs/v0.1.7-* design notes (commits 2b24dc2, 3ff3b1d)
  • Snapshot harness for rendered CLI output (#41) — Tier 2D
    from the #39 test-resilience analysis. Defensible follow-on to
    #40; deferred to v0.1.7.
  • Non-interactive sources add flags (#44) — needed to bring
    the PokéAPI quickstart back as an online recipe doctest.

Upgrade notes

  • No breaking config changes. tycoon.yml written by v0.1.5
    loads cleanly in v0.1.6.
  • tycoon ask init was removed in v0.1.5 and is now re-added with
    a different contract (no LLM args, no prompts). Any script
    that called the v0.1.4-and-earlier ask init with provider
    arguments should be updated to call tycoon register llm for
    the args-and-prompts path; the new ask init is the args-free
    bootstrap step.
  • Recipe authors: see tests/README.md
    for the new <!-- tycoon-test: mode=offline|online --> marker
    convention if you want your blocks gated by CI.

Contributors