jonnify·bcs /bcs/ideas/identity-contract/the-namespace-discriminant

B1.2 · dive 1 of 4 — the typed property

A discriminant carried twice.

The namespace lives in the value — the first three bytes — and in the type, wherever the runtime can hold one. What systems may rely on: a wrong-kind identity cannot cross a declared boundary silently.

The evidence stack runs the whole gauntlet: one compiler error, a 400 issued before any handler runs, and a store-side {:error, :namespace} — the silent join retired at compile time, at the schema, and at the store, in that order.

The two carriages — the discriminant in the value, and the three layers that hold it in a type (content/bcs1.2.md). Select a carriage.
USR in the value · bytes 1–3 the type compile time · one compiler error the schema the HTTP edge · 400 before any handler the store the substrate gate · {:error, :namespace} three layers, one discriminant — each layer that misses leaves one more in front of the data
readout · Select a carriage. The namespace is carried twice — in the first three bytes of the value, and in the type wherever the runtime can hold one (content/bcs1.2.md).
§1

The evidence stack

committed outputs, quoted verbatim

Three layers, three committed proofs — compile time first, then the schema, then the store. The deliberate negative test in the cross-runtime article is one compiler error:

content/docs/One-Contract-Three-Runtimes.md · the deliberate negative testfrozen
typecheck_negative.ts(13,13): error TS2345: Argument of type 'BrandedId<"USR">'
  is not assignable to parameter of type 'BrandedId<"CRS">'.
content/docs/One-Contract-Three-Runtimes.md · the HTTP gate row, proven through injectfrozen
inject: valid/malformed/ns/miss : 200 / 400 / 400 / 404  ← the third column: a valid USR id on a CRS route, refused before the handler

The third column is the one to stare at: a structurally valid USR id on a route that requires CRS is a 400 issued by the schema compiler — the handler never runs. The fourth column is the only question a gated route leaves to the handler: known or unknown, 200 or 404. And beneath both sits the store:

content/echo_data/runtimes/elixir/bcs_rung_1_1_check.out · the store gate, G3frozen
G3 typed ok -- rejects 4/4 as :invalid; GRD id on BRL store -> {:error, :namespace}; raising twin -> NamespaceError
§2

The boundary gate, run by hand

inputs per content/vectors.json · content/docs/One-Contract-Three-Runtimes.md

A declared boundary that requires CRS. Every input below is from a committed source — the article's seed literal, the canonical vector, and two reject vectors. A pure function replays the gate: parse first, then namespace match; the readout names the verdict and the layer that issued it.

gate · The measured row reads 200 / 400 / 400 / 404 for valid / malformed / wrong-namespace / unknown (content/docs/One-Contract-Three-Runtimes.md). Select an id to replay the gate against a CRS boundary.

The wrong-namespace refusal and the malformed refusal share a status — 400 — but not a meaning: one is a parse failure, the other a type failure over a perfectly well-formed id. The gate taxonomy keeps the two refusals distinct; dive 4 reads the full vocabulary.

§3

Carriage in Elixir

content/bcs1.2.md · How

The discriminant lives in binary pattern matching: a function head of <<ns::binary-size(3), _::binary-size(11)>> with the is_branded guard makes the function-clause system do the type system's work. The ~b sigil moves literal validation to compile time — an invalid id in source fails the build, not the request. The store behind it refuses what the pattern admits but the registry forbids: a GRD id on the BRL store earns {:error, :namespace}.

§4

Carriage in Go

content/bcs1.2.md · How

Nominal where Go can hold it, constructor-enforced where it cannot: one defined type per registered namespace — type PrtID string, type AstID string — minted only by parsing constructors, so a PrtID does not assign where an AstID is required, and the conversion that would defeat it is explicit, greppable, and reviewable. Weaker than a TS brand, and stated so; the load-bearing gate in Go remains the channel edge of the owner goroutine, where every inbound id meets Parse before any map is touched.

§5

What it retires

content/bcs1.2.md

The silent join — the wrong-table id that survives until an analyst notices. With the discriminant carried twice, it dies at compile time, at the schema, and at the store, in that order; each layer that misses it leaves one more layer in front of the data. No runtime carries all three layers, and none needs to: the value carries the namespace everywhere, and each runtime holds it in the strongest type it has.

§6

References

sources · related

Sources

Related