Inspiration
Schema changes are one of the most common causes of silent production incidents. A developer drops a column that looks unused locally — three dashboards break the next morning, and nobody finds out until someone complains. The column looked safe. Nothing in a normal code review tells you it wasn't.
The information to prevent this already exists — it's sitting in Data-Hub's lineage graph. It's just never consulted at the moment it would actually help: when the PR is open, before the merge. koza exists to close that gap.
What it does
koza reads a SQL migration, checks Data-Hub's real lineage graph to see what actually depends on the columns being changed, classifies the risk (Safe / Low / Breaking / Critical), and generates a safe rewrite — before the change ever reaches production. For Breaking/Critical changes, it tags the affected dataset in Data-Hub and saves a Context Document recording the full analysis, permanently linking the decision back to the table it concerns.
It runs the same way in three places, all backed by the same Data-Hub instance:
- Streamlit — paste SQL, click a button, get an interactive report with real write-backs
- GitHub Actions — comments on every PR automatically, blocks merge on Critical severity
- Slack —
@koza analyze <SQL>or@koza multi <SQL>for multi-table migrations, with a conversational follow-up loop (report,severity,downstream,patch,fix)
How we built it
Core logic (SQL parsing, severity classification, lineage traversal, safe-patch generation) lives in one shared agent/ module so all three interfaces stay consistent — there's one source of truth for what counts as Breaking, not three separately maintained copies.
Multi-hop lineage ("does this column affect a table that affects a dashboard, two hops away?") was achieved by calling Data-Hub's 1-hop lineage lookup repeatedly — hop-1 results become hop-2's starting points — since the underlying helper hardcodes a 1-hop limit internally.
For GitHub Actions, since cloud runners can't reach localhost, we tunnel a local Data-Hub instance out via ngrok so CI checks run against real, live lineage data instead of a mock.
Challenges we ran into
- A parser bug that silently dropped
DROP TABLEstatements preceded by an explanatory SQL comment — the regex anchor expected the statement to start withDROP, not--. Fixed by stripping leading comment lines before matching. - Mermaid diagrams failing to render inside Streamlit tabs —
st.tabs()renders every tab's content at once (hidden tabs use CSS, not lazy loading), and Mermaid can't compute SVG layout inside a hidden container. Solved by switching to a selectbox, so the diagram component is only ever created for the currently visible table. - The ngrok tunnel dropping mid-testing, more than once. Every time, the CI check degraded gracefully to offline mode (structural-risk-only severity) instead of failing the workflow outright — tested against real network interruption, not just the happy path.
Accomplishments that we're proud of
Getting write-back to actually close the loop: koza doesn't just read Data-Hub's context, it compounds it — every Breaking/Critical verdict becomes a permanent, searchable Context Document in Data-Hub, tagged with which interface produced it (Streamlit, GitHub Actions, or Slack), so the next engineer who looks up that table sees the real history, not just the current lineage snapshot.
What we learned
That "read the catalog" and "write back into the catalog" are a fundamentally different level of integration — and that the second one is where most lineage tools stop short.
What's next for koza
- A native read path for column-level PII tags (the write path already works)
- Making Slack's analysis write back to Data-Hub too, matching Streamlit and GitHub Actions
- A native multi-hop lineage query instead of repeated 1-hop calls
Log in or sign up for Devpost to join the conversation.