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.
The typed river
Section titled “The typed river”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 Broadcast and the Run
Section titled “The Broadcast and the Run”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 terminal comes inside
Section titled “The terminal comes inside”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 cloud runner closes its shape
Section titled “The cloud runner closes its shape”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.
The housekeeping that mattered
Section titled “The housekeeping that mattered”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.
The names get plainer
Section titled “The names get plainer”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.
The through-line
Section titled “The through-line”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.
The trail
Section titled “The trail”A selection of the week’s commits, for the record:
dcee3766— CLI bottombar cloud path — proxy the jinflow-worker daemonf33425df— record type_conversions as-shipped + consumer wired + V1 polymorphic-grain flag117aa460— 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 columnsb6261ae3— reframe Phase-2 as typed-edge graph in the extended type compile695f5abc— evolve conversations persist as notes (kind=‘conversation’)b1ff214a— Sense 54 Phase 2 — entity aggregate inherits type/unit from the Typology40685e16— wire typologycompile into make (Sense 54 Phase 1)0012fcc2— mark Rail spec Phase 1 landed + note standard-per-hop baking gap9633fb30— Drill Floor Phase 1 — the Refinement Rail (Axis B)afeca7f3— CLI bottombar — a toggleable terminal rail in the Explorer