Kind governance
needs and produces name kinds — a vocabulary governed before matching exists, so two teams' meanings of one word collide at connect time, not in production.
Why
needs and produces name KINDS — array, finding, analysis.summary.
Two teams declaring array to mean different things is the stringly-typed
failure rebuilt one level above ids, and it is not hypothetical: the
reference application contains two live worlds — a Monitor estate and a
storage simulation — whose array are entirely different objects.
Retrofitting a namespace onto strings in the wild is the migration that
never completes, so the vocabulary is governed before matching exists.
The family split, applied again: the library owns the law — kinds are opaque strings, matching is exact identity, an unknown kind is a refusal — the mounted catalog owns the world: which kinds exist and what each means.
import { createActionRuntime, declareKinds } from 'hcifootprint';
// Teams own their files; the runtime owns the merge.
const estateKinds = { array: { docs: 'a Monitor estate array' }, finding: {} };
const storageKinds = { 'powermax.array': { docs: 'a PowerMax frame' } };
const runtime = createActionRuntime({
kinds: declareKinds(estateKinds, storageKinds),
});
// A duplicate REFUSES, naming both contributors — never last-writer-wins:
// declareKinds(estateKinds, { array: {} })
// → "kind 'array' is declared twice — by contribution 0 and again by 1…"
runtime.kindGovernance();
// { mounted: true, fingerprint: '…', kindsSeen: […], ungoverned: [] }The decisions, and why each
- Connect-time enforcement. An unknown kind refuses when the connection is made — where the developer is looking — not at match time three layers later.
- Two methods, deliberately.
has()anddescribe()are the whole interface. Enumeration lives on the concrete default only, so a future remote adapter is never forced to promise a listing it cannot give synchronously;assignable()(subtyping) is refused until a real consumer produces the failure that justifies it. - A mounted catalog is immutable — one argument twice. A kind cannot vanish after its check passed (honesty), and an immutable catalog is memoizable: consulted once per kind, ever, so an adapter can never cost the offer-serving path (performance).
revisionrides the fingerprint. Names are forever; meanings evolve. Two sides holding different meanings of one name should refuse loudly at a seam instead of matching silently — the fingerprint exists now so wires can start carrying it.- Unmounted is a VISIBLE state. With no catalog, declarations are
accepted and reported ungoverned by
kindGovernance()— never silently unchecked, because an unarmed check indistinguishable from a passing one is the disease this family keeps curing.