v0.1.6 — dbt-shop polish
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 semanticsnamespace emitsdbt_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 asdata analyze.
Includes asemantics doctorsubcommand with a hard exit for CI
and a non-fatal row intycoon 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-leveltycoon profilesnamespace
(list/show/doctor). Fixes a real bug along the way:
every dbt-touching surface exceptregister dbthad
profiles_dirhardcoded 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
intycoon.yml, the newtycoon data fivetran syncand
tycoon data fivetran listcommands 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_usagepanel) for Fivetran-managed pipelines. -
Two CI-escaped bugs fixed — and the test gate that catches
the next one. v0.1.5 shipped to PyPI withrest_apiingest
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 realtycoonbinary 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 doctorOutput 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 prodFlag 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_grouptycoon data fivetran sync # mirror connector metadata into .tycoon/metadata.duckdb
tycoon data fivetran list # human-readable connector listEach 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_dirSame 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:
- Hand-edited
tycoon.yml— switchingask.llm.providerfrom
lm-studiotoollamapreviously required re-running
tycoon register llm, which re-prompted for things the user
didn't want to change.ask initreads the YAML as-is. - Cloned project from a teammate —
.tycoon/nao/is gitignored
(chat history + row previews + PII). Cloning leaves no Nao config. register llmhalf-succeeded —tycoon.ymlwritten, chained
setup_ask_stack()raised mid-call.ask initis 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 #38 — tycoon 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— walksREADME.mdand
docs/recipes/*.mdfor marked code blocks and executes each via
bash -e -o pipefailin 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=onlineis
gated on--run-onlineand 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-interactivesources addflags that would
let it come back as amode=onlineblock. -
tests/test_e2e_demo_arc.py— a subprocess-driven test that
shells out to the realtycoonbinary on PATH (rather than
Typer's in-processCliRunnerliketest_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 intycoon.ymlwas being cast
directly to dlt'sRESTAPIConfig, which expectsbase_url
wrapped underclientandresourcesas 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 actualrest_api_sourcebuild (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 aspip 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 intests/test_cli_surface.py. register dbt --createproduced a project that failed
transform runstandalone. The scaffoldedprofiles.yml
ATTACHesdata/raw.duckdbread-only, but a user running
tycoon data transform runbefore anytycoon 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.0from[server]— pulled transitively by
uvicorn[standard]==0.46.0; tycoon neverimport websockets
(uses fastapi'sWebSocketclass).ibis-framework[duckdb]==12.0.0from[ask]— pulled
transitively bynao-core; zeroimport ibissites insrc/
ortests/.
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 (commits2b24dc2,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 addflags (#44) — needed to bring
the PokéAPI quickstart back as an online recipe doctest.
Upgrade notes
- No breaking config changes.
tycoon.ymlwritten by v0.1.5
loads cleanly in v0.1.6. tycoon ask initwas 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-earlierask initwith provider
arguments should be updated to calltycoon register llmfor
the args-and-prompts path; the newask initis 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
- @db-tycoon-stephen — every PR this cycle