Zum Inhalt springen

Week 28 / 2026

Dieser Inhalt ist noch nicht in deiner Sprache verfügbar.

6 – 12 Jul 2026 · 190 commits · release cut

Prefix histogram — 72 feat: · 29 fix: · 55 docs: · 3 refactor: · 25 chore: · 6 test:.

If a single word had to carry this week, it would be truth — but the working kind, not the philosophical one. Three separate threads, running in parallel, all turned out to be asking the same question from different sides: how does a value say what it is and how it came to be? The Typology answered it for columns (a type is a fact, not a preference). The Drill Floor answered it for readings (show how a number was made, stage by stage). The Broadcast answered it for runs (let a build narrate itself as it happens). By Sunday the system was measurably more self-describing than it was on Monday — and most of the good arguments were about where a fact is allowed to live.


The Typology, and the week we decided what a type is

Section titled “The Typology, and the week we decided what a type is”

Sense 54 came in as a proposal and left as a working floor. The arc was worth watching because it kept getting simpler as it got more correct.

Phase 1 shipped the machinery: typologycheck.py (validate a column’s declared kind against the Codex binding_kinds, explicit-only, fail hard) and typologycompile.py (bake two descriptive tables into the tenant meta schema). Then it got wired into make, on a continue_on_error ramp so an untyped pack doesn’t halt a build while coverage climbs.

The first real decision was where the coverage artifact lives. The original design wrote a git-tracked state/typology_coverage.json side-file, on the clean principle that the KLS carries data, never declarations. That principle survived — but the artifact moved into two inspector-only KLS tables (_<tenant>.column_types, _<tenant>.typology_coverage). The forcing function was mundane and decisive: the cloud Explorer has no AFS on hand. A side-file in the repo cannot reach a browser in Frankfurt; a table riding inside the KLS can. The reframe that kept it honest is worth keeping: these tables are debug symbols, not runtime tags — the DWARF a compiler ships so a debugger can show you a type, never something the running program reads to decide behaviour. Nothing in processing, dbt, or a signal ever reads them. Only the inspector does. The moment a signal reads column_types to decide a type at query time, it becomes the runtime-type-info model we are explicitly avoiding — so the guard is written loud, in the bake code and in a DuckDB COMMENT ON TABLE.

Then, mid-week, the sharpest conversation of the arc: type is not configuration. The Cascade (Sense 43) is a config engine — values that are contextual, overridable, cascaded L0→L3. A type is the opposite. You cannot choose that a stream reads Celsius; it reads Celsius, and one truth is all there is. The subtlety that made it land came from Mig directly: it is not simply that a type is “non-overridable.” You can configure — but you can only configure the truth. Displaying a Celsius reading in Fahrenheit is fine, because the conversion preserves the fact; relabelling Celsius as Fahrenheit is a lie. The type carries the dimension, and the dimension is exactly what bounds the truthful set of configurations — commensurable conversions, precision, a choice among legal aggregations. Config does not sit above the type overriding it; it navigates the presentations the type already authorises.

That single distinction cleaned up three loose ends at once. The aggregation policy — whether a value may be summed — stopped being a cascaded config value and became what it always was: a fact of the quantity. A concentration is intensive and does not sum; a load is extensive and does. It is declared once, on the type, immutable, and named rollup: specifically so nobody confuses it with the Cascade’s unrelated config-merge policy. And the migration ramp’s old “name-based zero-day defaults” (ends_with('_at') → temporal, “40% typed for free”) were retracted outright: no name-magic in the pipeline. A column named depth_km is not km because it ends in _km; that is lying with a heuristic. Magic is allowed only when a human authors a schema — a tool may propose, a person commits, and make reads it literally. make is the analytical security boundary, so the truthful space is fixed, explicitly, before a single row is built.

The lesson, stated plainly: when a fact keeps trying to live in the config layer, you have mislabelled the fact. Move it upstream to the type and the config layer gets smaller, not bigger.


Underneath Sense 54 a bigger idea surfaced, and it is the one to carry forward: a semantic type is not a label at one site. It is the identity of a data stream — meaning, dimension, unit — invariant under storage, carried through every stage of the river, changing only under an explicit, declared conversion.

Which reframes the whole Sense. Typing a Gold column is not the goal; it is one of two gates on a stream whose type we track end to end. The type is declared at ingestion (“this stream delivers °C”) and again at Gold (“this column is SSC in g/L, averageable”), and carried through the conversions between. That looks redundant, and in the best case it is — but the redundancy is a checksum. The source type, carried through the declared conversions, must equal the independently declared Gold type. When they agree, all is well. When they disagree, the mismatch localises the conversion bug to the exact stage where the carried type stopped matching — and make stops there and names it. The silent conversion error, the suspicious number three layers downstream with no explanation, is exactly what this kills.

Phase 2 turned the idea into leverage: entity aggregates now inherit their type and unit from the Typology. A billed_amount typed as a currency quantity bound to ISO 4217 produces an aggregate that inherits CHF automatically — the same aggregate, byte-identical bake, one fewer place to restate the truth. Where a column is polymorphic (one result_value that is NTU for turbidity and °C for temperature), the compiler refuses to hand it a single entity-wide aggregate rather than guess — the right answer, loudly.


The Drill Floor, and building the consumer that proved the floor

Section titled “The Drill Floor, and building the consumer that proved the floor”

The week’s other long thread started from a plain question — how do we show disaggregation levels? — and produced a reframe that turned out to matter: jinflow does not have one kind of “level,” it has two, and they are orthogonal. There is the dimensional hierarchy (where does this number belong — basin, site, sensor), which sums on the way up. And there is the refinement chain (how made is this number — raw signal, calibrated measurement, derived variable), which changes the number’s meaning and precision at each step. Classic BI tools walk the first axis only. The second is jinflow-native, and it is exactly the L1→L2→L3 Reading contract the water pack already had.

The decision was to build the second axis first — the Refinement Rail — with the first axis kept in mind so it drops in later as a skin, not a rewrite. The abstraction under both is one idea: a level’s value arrives through a boundary policy over the level below. For the refinement axis that boundary is a transform (a standard-cited calibration); for the hierarchy axis it is an aggregation. Same protocol, different fan-in.

Phase 1 landed as real code: a level-stepper protocol, a query that reads the flattened lineage chain, and a panel that renders raw → calibrated → derived with an honest relative-uncertainty band. Honest specifically: the uncertainty is not monotonic across stages — site calibration can widen it and then tighten it — so the band shows the real story with a caption instead of a flattering smooth taper.

But the part worth remembering is how the Rail and the Typology fed each other. Building the Rail forced questions the Typology had not yet been pressed on — where does the standard-per-hop live? how does a roll-up know a column is non-additive? — and each answer sharpened the floor. The standard-per-hop, in particular, turned out not to be a Rail artifact at all: a refinement chain is a graph of typed edges (a standard-cited calibration that legally changes the dimension), which the Typology already models and, crucially, needs for its checksum. You cannot carry a type through conversions you have not recorded. So the edge graph belongs in the type compile, and the Rail is a consumer of it. By week’s end the type compile was baking _<tenant>.type_conversions — the declared edges — and the Rail was reading its standards straight from that table, its hard-coded fallback retired.

The lesson is one to institutionalise: build the consumer to prove the substrate. The Rail did more to validate the Typology than any review could have — unit_column, rollup, and the whole typed-edge idea all fell out of a real consumer needing them, before anyone thought to test for them in the abstract. Producer and consumer, written in parallel and traded back and forth, converge on a better design than either alone.

(It also left an honest V1 edge to fix: the shipped conversion edge is per-column, so for a polymorphic result_value the standard is exact for the turbidity chain but coarse for temperature — a per-property discriminator is the V2 refinement. Written down, not papered over.)


The third self-description thread was about runs. Sense 55 (The Broadcast) and Sense 56 (The Run) went from paired proposal to shipped substrate in the same week — Runs and Verbs as first-class nouns, and one wire to observe a running build: a CloudEvents envelope, an event stream make emits as it works, and per-run artifacts (manifest, events.jsonl, dag.json). Then signal-file control so a run can be steered, and a worker daemon that serves runs over HTTP + SSE — with the small, correct bug-fix that an SSE stream must close, not keep-alive, so a finished run actually reads as finished.

The framing that kept it distinct from workflow engines is worth holding: this is a data-first, emergent DAG. The shape of a run is not declared up front and then executed; it is discovered from what the data touches, and broadcast as it emerges. A Verb observed, not a workflow prescribed.


Cartography grows a second dimension — and lends the Typology a kind

Section titled “Cartography grows a second dimension — and lends the Typology a kind”

Sense 52 V2 pushed the map from points to polygons: a fill layer, custom base-tile URLs, and a legend with proper data-layer attributions. The nice bit of cross-pollination: locking the polygon decision is what forced the Typology to grow a seventh kind, geometry — a polygon is structured data with a coordinate reference system, not a narrative blob, so it earns its own kind and binds to RFC 7946 GeoJSON. Two Senses, two authors, one decision made in both places at once. When the map learned to draw a shape, the type system learned to name it.


The Explorer grew a command line. Stage 1 of the CLI-in-the-browser shipped — a /cli route and a tenant-scoped /[tenant]/cli, a toggleable bottom-bar terminal rail, local subprocess execution over SSE, gated on make:run and surfaced in the sidebar to the holders of that capability. It even renders the Sense-55 phase tree live, which is the two threads shaking hands: the terminal that runs make and the broadcast that narrates it, in the same pane. The endgame is a browser that can drive the engine, not just read its output; this is the first honest step.


The make-in-cloud work reached a tidy milestone: --build-bucket completed the four-bucket topology (afs, source, build, store, per tenant per role), and the worker ran end-to-end against S3-compatible buckets. The container-only bugs got shaken out one by one until a Fly machine could pull an AFS, build, and push a KLS entirely in the cloud. The long-term shape — git push triggers a build — is now close enough to see clearly.


Not everything was a Sense. Some of the week’s most useful work was small.

Publications. The old “sync profile” vocabulary became publication — audience-first language, a --publication flag, a jin cloud publications verb, and a transition shim so nothing breaks. And the ship dry-run learned to expand --all into a real per-tenant plan with both axes visible, which immediately surfaced two tenants with no envs: declared. Vocabulary that names the thing by what it is for pays for itself the first time you read a plan.

Codex hygiene, and a drift caught. The Codex gained water-quality standards, the GUM as the uncertainty standard, and a jinflow_naming guard. Widening UCUM to bind both quantity and category (so unit-of-record columns are legal) then exposed a quiet gap: the Explorer’s bundled standards-registry.json is generated by standardcompile.py, but nothing in the check pipeline verified it was in sync — so editing a standard without recompiling left the browser’s copy silently stale. The fix was one line of pipeline wiring: standardcompile --check now runs in check-all, and the drift class is closed. The lesson recurs at every layer this project has: a generated artifact needs a check that it matches its source, or it will drift the day you forget.

Release cadence. Five patch releases (0.56.9 → 0.56.13) went out across the week — the steady drumbeat underneath the Senses. Boring, on purpose.


A week this preoccupied with naming things honestly ended, fittingly, by renaming two of the most important nouns we have. The principal mental model — the five names a piece of work passes through — had its two forward-facing terms softened: Insight became Understanding, and Evolution became Progress.

It reads like cosmetics and it is not. Insight and evolution are slightly grand words — they promise a flash of brilliance and a sweep of change. Understanding and progress are plainer, more human, and more honest about what the system actually delivers: not epiphanies and revolutions, but a customer who now understands their data and a concrete step forward from it. The model’s own register should match the promise we can keep. It is the same instinct that turned “sync profile” into “publication” and kept rollup out of the config namespace — name the thing by what it truthfully is for the person on the other side, not by how it feels from the inside.


Step back and the week rhymes. A type that carries its truth through the whole river. A Rail that shows how a reading was made, stage by stage. A Broadcast that lets a run narrate itself as it unfolds. A Codex that grounds every unit and standard the types point at. Four different surfaces, one intent: the system should be able to say what it is and how it came to be, without a human having to reconstruct it.

And the way the work happened was itself the lesson. The best decisions were not made in one place and handed down; they were made twice, in parallel, by a producer and a consumer arguing toward the same answer. The Typology got sharper because the Drill Floor needed it to. The map and the type system grew the same kind on the same day. The right home for a fact — the type, not the config; the edge table, not the Rail; the KLS, not the side-file — was found by watching what each artifact was forced to contain once something real tried to read it.

Next week: wire the last consumers to the Typology, drive the Rail against a live water KLS, and start the second axis — the hierarchy roll-up — now that its protocol is already written. The floor is poured. Time to build on it.

A selection of the week’s commits, for the record:

  • dcee3766 — CLI bottombar cloud path — proxy the jinflow-worker daemon
  • f33425df — record type_conversions as-shipped + consumer wired + V1 polymorphic-grain flag
  • 117aa460 — Rail reads standard-per-hop from _.type_conversions (retire fallback)
  • 6a75081e — Sense 54 — type_conversions edge graph (derived_from + bake)
  • 8fc94d9e — type_conversions edge table — accept sister-7’s refinements + propose columns
  • b6261ae3 — reframe Phase-2 as typed-edge graph in the extended type compile
  • 695f5abc — evolve conversations persist as notes (kind=‘conversation’)
  • b1ff214a — Sense 54 Phase 2 — entity aggregate inherits type/unit from the Typology
  • 40685e16 — wire typologycompile into make (Sense 54 Phase 1)
  • 0012fcc2 — mark Rail spec Phase 1 landed + note standard-per-hop baking gap
  • 9633fb30 — Drill Floor Phase 1 — the Refinement Rail (Axis B)
  • afeca7f3 — CLI bottombar — a toggleable terminal rail in the Explorer
jazzisnow jinflow is a jazzisnow product
v0.64.7 · built 2026-09-20 19:48 UTC