The Typology · jinflow

Every value types itself at the door.

A number stops being just a number. It becomes a typed stream the platform can reason about, refuse to misuse, and explain to a human — from the CSV it arrived in to the signal that reads it.

7 build-time gates declared, never inferred 58 tests

Why this is not ordinary

Almost every analytical tool strips a value's type at the CSV boundary and asks the user to remember. A column arrives as text, gets cast somewhere in the SQL, and its meaning — is this a measurement? a currency? can I sum it? — lives only in the author's head. When a bad value slips in, it surfaces three layers downstream as a suspicious number with no explanation.

jinflow refuses that. The Typology restores what metrology has known for 150 years: a value is not a number — it is a number + kind + unit + uncertainty + reference standard, and a platform that calls itself transparent should not throw four of those five away. The gates make the restoration enforceable rather than aspirational — they turn "please type your data" into "the build will not proceed on a lie."

Two gates, and a checksum between them

A semantic type is not a label at one place — it is the identity of a data stream, invariant under storage, carried through every stage, changing only under a declared conversion. So it is declared at two gates, with the river running between.

Ingestion gate
"arrives as °C"
csv_schemas · stream:
the river
renamecast aggregaterecode
each edge declared + typed
Gold gate
"is SSC in g/L,
averageable"
column_types:

the type carried through the edges must equal the type declared at Gold — disagree, and the build stops and names the exact stage

Declaring the same value twice looks redundant — and in the best case it is. But the redundancy is a checksum, like double-entry bookkeeping: the value isn't that the two books agree by construction, it's that a mechanical reconciliation proves they agree.

The seven gates

Seven checks run inside jin make, each catching a distinct class of untruth. Together they are a compiler for data semantics — every declaration reaches DuckDB proven correct, or is refused first.

typology-legalitypre-dbt
an illegal Gold type — bad kind, illegal Codex binding, bad rollup
enforced
ingestion-legalitypre-dbt · streamcheck
an illegal stream: — or a quantity resting on a string floor
ramp
gate-reconciliationpre-dbt · streamreconcile
a passthrough that isn't one — a rename or drift under passthrough: true
ramp
value-conformancepost-canonicalise · streamconform
a delivered value that doesn't conform — non-numeric quantity, category outside its vocabulary
ramp
gate-lineagepost-dbt · streamlineage
a hidden conversion under a passthrough — names + types match but the SQL secretly transforms
ramp
gate-sourcecitepost-dbt · sourceciteverify
a source-cite that lies about the SQL — declared rename where the SQL aggregates
ramp
typologycompilepost-dbt · the bake
writes the inspector tables the Column Passport reads at the point of use
bake

Each gate ships on a ramp: it warns while the discipline rolls out, and flips to stop only when a tenant opts in. A ramp gate can never abort a release on a data-quality finding — the fix for dirty data belongs upstream, and the gate's job is to point at it.

Where it meets real, dirty water

A synthetic tenant is a passthrough — the source column is the Gold column, unchanged. A real hospital feed is not: it renames, casts, aggregates to a new grain, and recodes on the way to Gold. So the reconciler grew a second model — it classifies each source→Gold edge and checks the transformation that edge kind is allowed to perform.

case_token:     derived_from: { column: sejour_id,       op: rename }
cost_center_id: derived_from: { column: cc_demandeur,    op: aggregate, agg: mode }
start_time:     derived_from: { column: prestation_date, op: aggregate, agg: min }
case_type:      derived_from: { column: admission_code,  op: recode, mapping: opale_admission_map }
billing_id:     # minted surrogate — born, self-rooted, no cite

Aggregation reconciles against rollup, not equality

You can't equate N source rows to one value — but you can check the aggregation is legal for the type. sum demands an additive quantity; mean an averageable one; min/max an orderable kind; mode a representative. This makes rollup load-bearing: declaring agg: sum on a concentration is now a build error.

A recode may change the kind only via a cited mapping

A Codex standard or a pack-level mapping id. An unlicensed type change is refused — exactly as a passthrough that secretly transforms is refused.

The Bronze SQL already encodes it, and sqlglot reads it

A scaffolder parses the compiled dispatch, traces each column through the CTE chain to its source, and proposes the cite — turning 86 hand-declarations into a review. The same reader becomes the verifier: the declared op must match what the SQL does.

The proof

This is not theoretical. The first time the value gate ran on a real hospital's OPALE delivery, it found —

Case file · a live OPALE tenant · first run
156,589

non-conforming boolean values. The Excel export ships French Oui/Non and a coded G/O where the type says boolean — values that would have cast to silent NULLs three layers down and skewed every material-flow number.

caught at the door fixed in canonicalisation re-verified: 0

An invisible corruption, turned into a two-line fix — because the gate demanded the value be what it claimed to be, on the real rows, before anything downstream trusted it.

What you get

1

Data quality caught at the door

Not archaeology three layers down. A bad value is a warning on the row it arrived in, with the column named.

2

A wrong number becomes a build error with an address

The reconciliation localizes a conversion bug to the exact stage where a type stopped matching. No more "somewhere in Gold."

3

You cannot lie about how a value aggregates

Sum a concentration, average an ordinal, min a category — refused. The physics of extensive vs intensive quantities is encoded once and checked everywhere.

4

Declared, never inferred

No column is typed because its name ends in _km2; no edge is guessed by matching names. Every assertion is a human's, and every assertion is verified against its source of truth.

5

The knowledge store is self-describing

Types travel with the AFS baked into every snapshot. The receiver needs no ambient knowledge to know what the values mean — the difference between shipping data and shipping science.

6

It scales, because authoring is a review

Two scaffolders propose the declarations from the source of truth; a human reviews and commits; the build reads literally. 145 columns, 86 columns — proposed, not transcribed.

7

It makes the invisible legible to a person

The Column Passport surfaces the same declarations the gates enforce — kind, unit, standard, how it rolls up, and why the platform refuses to group it. Enforcement and explanation, one schema.

Where it stands

214 / 146
riverflow/delta — Gold + ingestion, fully reconciled & value-conforming. The reference.
460 · 0
A live OPALE tenant — Gold 100%; reference ingestion live; 0 non-conforming across 3.4M real values.
7 gates
wired into make, all on the ramp; 58 tests lock the contract.

The extraordinary part isn't any single gate. It's that the whole path — CSV to signal — is now one continuous, enforced, human-legible chain of meaning. The data stopped being a pile of numbers we hope are right. It became a stream we can prove things about.