Federation — cross-project catalogs
Federation is how one Kirimana project’s contracts become visible to other projects — other teams, other environments, other clouds — without standing up a central catalog vendor. Each team keeps its own repo, its own release cadence, and its own catalog; federation gives everyone else a read-only, versioned view of what that team publishes.
Git is the source of truth
The contracts library — sources/, contracts/, models — lives in
one git repository per project. Every platform catalog (Unity Catalog
on Databricks, or a Horizon/Purview sink) is a materialisation of
that library, never an independent authority. A classification change
lands once as a PR; kiri apply --target <env> rolls it out. Changes
made inside a catalog are drift: Kirimana surfaces them, it does not
pull them back.
Federation extends this one step: instead of only materialising into your own platform, a project can publish a snapshot of its catalog that other projects consume — same git-first discipline, across team boundaries.
Catalog snapshots
The unit of exchange is the federation export: a public-facing
JSON snapshot of one project’s contracts, written to
<project>/.kirimana/federation/export.json by default.
What the export carries:
- Identity —
schema_version,project_urn,release_sha,generated_at,catalog_etag - Per contract — URN, domain, owner, classification, AI policy,
schema (column name, type, classification,
from_columnslineage), reporting goals, release state, active flag
What it deliberately does not carry: model SQL bodies, internal
customProperties (kiri._internal.*), audit-log entries,
quality-engine results, and — critically — secrets, vault references,
and connection strings. A consumer reading the export cannot
reconstruct production credentials.
The wire format is locked to schema_version: "1"; bumps are never
silent. Writes are atomic (temp file + rename), so a consumer never
observes a half-written snapshot.
Publish one-shot:
kiri catalog publish \
--project-urn kirimana:project:<slug> \
--release-sha <git-sha>
Or auto-republish after every successful kiri apply by setting
KIRIMANA_FEDERATION_PUBLISH_ENABLED=1. The hook is opt-in, silently
skips when no catalog exists yet, and never fails your apply — a
publish failure logs a warning and the apply stays green.
Three transports, one contract
All consumers speak the same resolver protocol — resolve,
list_contracts, lineage_in, lineage_out, health — over one of
three transports. Switching transport is a one-line change; the
operation surface is identical.
| Transport | When to use it | How it stays fresh |
|---|---|---|
| In-process | Consumer runs in the same process as the producer — kiri apply, kiri contract lint, lineage queries inside one project | Reads the catalog directly; no network, always fresh |
| HTTP + ETag | Cross-team, cross-cloud. Producer mounts a small REST endpoint (GET /api/federation/v1/export, GET /api/federation/v1/contracts/{urn}) | First fetch caches body + ETag; later fetches send If-None-Match, and a 304 Not Modified short-circuits with no body |
| Filesystem-static | GitOps and air-gapped setups — the producer commits federation/<producer-slug>/export.json to a repo, consumers git pull | mtime-watched; a changed mtime triggers re-parse, an unchanged one serves the cache |
HTTP supports anonymous read or bearer-token auth (wrong or missing token → 401). Filesystem access control is just file permissions plus git access — auth lives a layer above the resolver.
Health: ok, stale, unavailable
Every resolver answers health() with a status, a cache_age_seconds,
and a message:
| Status | Meaning | Operator action |
|---|---|---|
ok | Queries served, cache fresh | Nothing |
stale | Cache still serveable, but the live source was unreachable on the last refresh | Investigate the producer endpoint or file mount; the cached snapshot keeps serving |
unavailable | Cache cold and source unreachable | Producer must publish first, or the path/URL is wrong |
One subtlety worth internalising: cache_age_seconds reflects the age
of the content, not the time since the last successful probe. A 304
revalidation does not reset it — polling frequently will not mask a
producer that stopped publishing.
Fail-closed where it matters, stale-with-marker where it doesn’t
Operations over a federated source split into two classes, and the degraded-mode behaviour differs deliberately:
Security and correctness operations fail closed. Classification checks during contract approval, AI-policy enforcement, cross-domain consumption gating — anything where acting on stale metadata could leak or misgovern data refuses to proceed when the resolver can’t prove freshness within the caller’s window (typically seconds to minutes). A lint or apply that depends on an unreachable federated source fails rather than guesses. The error names the degraded resolver so the fix is obvious.
Convenience operations serve stale data, marked. Browsing another team’s contracts, computing an impact graph for PR review, populating a recently-changed feed — these keep working from the cached snapshot, with a visible staleness warning when the cache falls outside the freshness window (hours to days is a common tolerance). You can browse during a producer outage; you just can’t approve a classification change against data that might be wrong.
Inspecting a remote catalog from the CLI
Three verbs cover the consumer’s day-to-day:
# Is the producer healthy? (http or filesystem transport)
kiri federation health --transport http --base-url https://producer.example
kiri federation health --transport filesystem --path federation/sales-team/export.json
# What does the producer publish? Filterable by domain,
# classification, and release state.
kiri federation list --base-url https://producer.example \
--domain sales --classification internal --release-state gold
# Look up one contract by URN
kiri federation resolve kirimana:contract:sales.customer:1 \
--base-url https://producer.example
Pass --token for bearer-authenticated producers; omit it for
anonymous-read ones.
Multi-team governance without a catalog vendor
Put together, this is a governance topology with no central chokepoint:
- Each team owns its contracts in its own repo and publishes a
snapshot on every release — stamped with the
release_shathat produced it, so consumers always know which commit they’re reading. - Consumers pick the transport that matches their trust boundary: in-process inside a project, HTTP across clouds, plain files in a GitOps repo for air-gapped estates.
- Classifications, AI policies, and column lineage travel with the snapshot; secrets and internals never do.
- Nothing is bidirectional. There is no merge conflict between catalogs because there is only one authority per contract: the producing team’s git repo.
If a team later changes platforms, the federation surface doesn’t move — the export format is platform-agnostic, so consumers are untouched. Your metadata library is yours, in git, in a format every adapter reads.