release: v0.2.0 — pipeline city - #209
Merged
Merged
Conversation
`tycoon city` renders the project's catalog in the browser: schemas become districts, tables become buildings, lineage becomes the road network. The renderer ships inside this wheel as `src/tycoon_city`. An extra was tried first and removed: `[project.optional-dependencies]` can only add *distributions*, never gate files out of our own wheel, so every user downloads the same bytes whether or not they ask for the feature. Keeping the renderer out would have meant a second distribution on PyPI, and the 1.7 MB it saves is 0.75% of a 227 MB install next to pandas, duckdb and babel. Not worth a second release process. `sqlglot>=30.15.0` therefore joins the runtime dependencies rather than an extra. This is a floor, not a new install: dbt-core (via metricflow) and dlt already depended on it, so it has always been present -- the pin states the version the renderer needs instead of inheriting whatever those two resolve to. THIRD-PARTY.md comes across with the code, because the wheel now redistributes three.js and zod compiled into web_dist. The import stays inside `city_cmd` rather than at module scope. That is a startup cost argument, not an optionality one: the renderer pulls in duckdb and sqlglot, and no other command should pay for that because this module was registered. An ImportError there now means a damaged install, so the message reports the underlying error rather than advice to install an extra that does not exist. The project-root walk-up is reimplemented locally rather than borrowed from tycoon.config. That module builds a cwd-bound singleton at import (config.py:149), so importing it validates tycoon.yml as a side effect -- a malformed file makes the import itself raise -- while the public load_config() raises SystemExit on a newer schema_version. Neither is an acceptable failure mode for a command that only needs a directory path, and the singleton's root is fixed at import time, so it goes stale under any chdir. Tests read city.py's source to keep both imports out. Filesystem paths are resolved to absolute before being handed over. `webserve.main` dispatches argv[0] == "demo" to the demo-catalog generator before argparse runs, so `tycoon city --path demo` would have served the built-in sample catalog instead of a user's directory named `demo` -- silently, with their own data nowhere in sight. An absolute path cannot collide. Resolving unconditionally would have broken MotherDuck: `md:my_db` and `md:_share/<name>/<uuid>` are URIs, not paths, and pathlib would have mangled them into /cwd/md:my_db. The resolution skips them, and --path is typed str rather than Path since a URI has no business in pathlib. Both branches are mutation-tested; a no-op control mutant survives. No `tycoon-city` console script is installed. Entry points are unconditional, so one would land on $PATH for every install; the renderer is reachable as `python -m tycoon_city.webserve` when it is wanted without a tycoon project around it. Vendored sources are reformatted to this repo's pinned ruff (0.15.20), which CI checks with `ruff format --check src tests`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous commit vendored `src/tycoon_city` -- the engine, but not the factory. The renderer's own repo is being deleted, so everything that cannot be regenerated from what shipped comes across here. web/ is the important one. `src/tycoon_city/web_dist/` is *minified build output*: two files Vite emits from 47 TypeScript sources. Without web/ the front end could never be changed again, only re-served. Carrying web/src, web/e2e, vite.config.ts, tsconfig.json and the pinned package-lock.json makes the bundle reproducible, and it is: `npm install && python scripts/sync_web_bundle.py` rebuilds `index-CosJ8RPj.js` byte-identical to the committed one. That script is not incidental -- it stashes web/public (dev data) so a dev city.json cannot ride into a release. contract/fixtures/demo.duckdb had to be exempted from `.gitignore`'s `*.duckdb`, which would have dropped it silently. Nothing regenerates it, and the demo.city.json golden beside it is derived *from* it, so losing it would have cost both. tests/tycoon_city/ is the renderer's 41-file suite, carried intact but not yet collected -- see the note on `addopts`. Its paths are anchored with `parents[N]` against the old repo root and need rewiring, which is not this commit's job. Carrying it un-run beats losing it. sim/contracts.py read its two JSON schemas from `<repo>/contract/fixtures/`, four .parent hops out of the package and into the checkout. They were never in the wheel, so `import tycoon_city.sim.logistics` raised FileNotFoundError anywhere but a source tree -- in pipeline-city's own published wheel too. The schemas are now package data read through importlib.resources, verified from an installed wheel. Nothing on the `tycoon city` serve path imports logistics, which is why the command worked regardless. docs/city/ is the design record: the city.json and runs.json contracts (the Python/renderer seam), the road grammar, and the handover and log that hold the closed decisions. conventions.md is the old CLAUDE.md. scripts/spike_cluster.py defaulted --src to ~/clients/dogfood, a client path on one machine. It is now required, so a public repo neither names it nor reads it by accident. Left behind deliberately: node_modules and every build artifact (regenerable), the four .mbox patch exports and demo-tycoon/ and the spike PNGs (regenerable or spent), GEMINI.md (stale -- it still describes a `dbtycoon` module that was renamed), and .superpowers/ (agent scaffolding whose durable output was already promoted into docs/). The 185-commit history is archived separately as a git bundle rather than grafted in, since the code arrived here as a clean vendored commit and the history's value is archival. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…town_* modules Gitignored in pipeline-city, so absent from the history bundle as well as the repo -- deleting that checkout would have been their only copy. town_endings has no other caller here; the rest are worked examples for the v5 planner modules that shipped dormant. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Manifest parsing, lineage DAG derivation, source-freshness SLAs and OSI semantic models -- tycoon's own domain, not renderer scaffolding. It was gitignored in pipeline-city, so it is in neither that repo's history nor the bundle, and clearing that checkout would have been its only copy. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The bundle was sitting untracked in ~/Projects, next to the very directory it exists to outlive -- one copy, on one machine, backed by nothing. Committing it puts the renderer's 185 commits behind the same remote as the code they produced. 1.5 MB, every ref including tag v0.1.0 and feature/city-foundation (main trails that by 158 commits, so a main-only capture would have archived the wrong history). `git bundle verify` passes; the committed copy is byte-identical to the original. archive/README.md records how to restore it and, more importantly, what it does NOT contain: a bundle carries tracked history only, so the gitignored .superpowers/ records, .agents/skills/ and spikes/ are absent -- which is why those were copied into this repo directly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CONTRIBUTING told contributors to run `uvx ruff check src tests`, both as the description of the CI gate and in the pre-push checklist. `uvx` ignores the project's pin and resolves to the latest ruff, so a clean checkout reports 106 errors today against rules this codebase has never been linted for — while the pinned 0.15.20 passes clean. The documented workflow made the repo look broken to exactly the audience least able to tell that it wasn't. Switch both to `uv run`, which honours `ruff==0.15.20` from the dev group and matches what CI actually executes. Also document `ruff format --check`, which joined the lint job in PR #174 but never reached this file. This is part 4 of #201; the pre-commit rev bump and the explicit `[tool.ruff.lint] select` remain open there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…end (#71) analyze.md justified the Rill Parquet bridge by claiming SQLite-backed DuckLake catalogs hold an exclusive lock. Wrong backend. The lock belongs to the catalog's metadata database: a DuckDB-backed catalog (`ducklake:foo.ducklake`, the default) is a DuckDB file, and DuckDB locks a database file to one process. A SQLite-backed catalog (`ducklake:sqlite:foo.sqlite`) is multi-process safe — Rill 0.86 was verified reading one live during ingest with zero lock conflicts. The v0.1.3 probe that produced the original claim attached `ducklake:.../catalog.ducklake` — no `sqlite:` prefix — so it hit the DuckDB-file backend while being labelled SQLite. - analyze.md: replace the rationale with the bridge's actual merits (no extension, no attach, no live connection) plus a dated correction. - motherduck-cloud-sync.md: same claim, same fix. Not named in #71. - releases/v0.1.3.md: the "full rationale" analyze.md links to. Appended a dated correction instead of rewriting a shipped release note. CHANGELOG entry under a new [Unreleased]. The 0.1.3 CHANGELOG entry keeps its original wording — released entries are a record, and [Unreleased] now documents the correction. Refs #71 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
docs: attribute the DuckLake read-while-ingest lock to the right backend (#71)
…-command docs(contributing): point ruff commands at the pinned version (#201)
# Conflicts: # CHANGELOG.md
Both entry points were lost in the pipeline-city absorb: the contract docs' quickstart, docs/city/guide.md, and web/package.json's demo-data script all invoke tycoon-city-export, and webserve/export.cli still argparse under these prog names — but only `tycoon` was declared in [project.scripts]. Also points demo-data at the fixture DB's post-absorb home (contract/fixtures/demo.duckdb), so `npm run demo-data` can regenerate web/public from a clean checkout again. Pinned by a subprocess_e2e test, per the console-script-drift convention. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> (cherry picked from commit fa11781259a137fd969b739ddcead03e78045e57)
feat(city-engine): tycoon city — the catalog as a 3D city (v0.2.0)
Scope is PR #205 only, per the 2026-08-25 call decision — #206, #207, #196, and #191-193 hold for the next cycle. The CHANGELOG section and release notes drafted on 2026-08-16 credited all of them; both are rescoped here to what actually ships. Version pins move 0.1.11 -> 0.2.0 in pyproject.toml and src/tycoon/__init__.py; the bump previously rode #207's tip, which is not in this release. Known limitations are stated plainly in the release notes: the city is a static plan rather than a simulation, the achievements block is emitted but not yet read by the renderer, and the vendored renderer suite is not CI-gated yet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
db-tycoon-stephen
marked this pull request as ready for review
August 28, 2026 10:18
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What v0.2.0 is
Decided on the 2026-08-25 sync: v0.2.0 =
tycoon city(#205) on top of the 0.1.11 CLI, nothing else. The 3D renderer serves a project's catalog as a city — schemas as districts, tables as buildings, lineage as roads — and is CLI-first: everything the UI does maps to an existingtycooncommand.Already on this branch (ahead of
main):uvx ruffpicked up 0.16's expanded default rule set #201)Pending merge into this branch:
tycoon city+ pipeline-city absorb ← review hereShip checklist (in order)
v0.2.0v0.2.0:pyproject.toml→0.2.0,CHANGELOG.md[0.2.0] - 2026-08-28section,docs/releases/v0.2.0.md, mkdocs nav (drafted on localrelease/v0.2.0-prep, rebase onto this branch)uv build+ install smokemain→ tagv0.2.0onmain→publish.yml→ GitHub release "v0.2.0 — pipeline city"pip install database-tycoon && tycoon city --help)Explicitly not in v0.2.0 (retarget to the next milestone)
#206, #207 (city-sim / data-cli), #191 → #192 → #193 (M3 source manifest stack), #196 (agent skills), #74 (DuckLake proposal). Known city gaps tracked for the follow-up: PTC-120 / PTC-127 (renderer + web CI gating, staged fixes on
bugfix/city-ui-review-blockers).Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.