Deprecate a contract with the state machine
A contract doesn’t just get deleted. Other contracts read from it,
lineage points at it, and a catalog table backs it — so removing it is a
governed lifecycle move, not a rm. Kirimana models that lifecycle as a
state machine with a closed menu of transitions, so “this dataset is
going away” becomes an explicit, auditable state that downstream
consumers can see and plan against.
The canonical lifecycle state
Every contract carries a single canonical lifecycle state,
kiri.lifecycle.state, whose value is one of a fixed set:
draft → bronze → silver → gold → deprecated
That is the promotion journey: a contract is drafted, promoted through
the medallion layers as it earns trust, and eventually deprecated when
it should no longer be used. deprecated is a first-class state on the
same axis, not a separate flag.
kiri.lifecycle.state is the canonical home for state. An older
kiri.medallion.state path still parses but is a deprecated alias that
resolves to it — new contracts author kiri.lifecycle.state, and
kiri contract lint flags the alias.
Two things are worth keeping distinct from lifecycle state:
- Medallion layer (
kiri.medallion.layer) is the destination — which layer the contract materialises to. A deprecated gold contract islifecycle.state = deprecated,medallion.layer = gold: the table still exists in gold, but no new silver→gold transformation runs for it. - Active (
kiri.medallion.active) is orthogonal. A contract can be insilverstate butactive: false— paused from apply runs without being demoted. Pausing execution and deprecating a contract are different acts.
Transitioning states
You move a contract along the lifecycle with a single command rather than hand-editing the state field:
kiri contract promote dim_customer --to deprecated
--to is a closed menu — draft, bronze, silver, gold, or
deprecated. Nothing else is accepted, so a typo or an invented state
is rejected at the boundary rather than written into a contract. To
pause a contract’s execution while deprecating it, flip active in the
same move:
kiri contract promote dim_customer --to deprecated --inactive
--inactive pauses runtime execution; --active resumes it; omit both
to leave activation unchanged. Lifecycle transitions themselves are
validated — kiri.lifecycle.phase transitions follow a state machine
(proposed → beta → active → deprecated → sunset) with no backwards
jumps, and the semantic validator enforces it at kiri contract validate, kiri plan, kiri apply, and in CI.
Retirement and the ODCS status gate
Underneath the finer lifecycle metadata, ODCS status is the coarse
gate: draft / active / deprecated / retired. retired is the
end of the line — the contract is not merely discouraged, it is gone.
Kirimana ties one rule to it: a contract at ODCS status = retired
must declare a kiri.lifecycle.migration_target — the contract ID
consumers should move to — or an explicit none. You cannot retire a
contract and leave its consumers with nowhere to go without saying so on
the record.
Supporting metadata rounds out a graceful sunset:
customProperties:
- property: kiri.lifecycle.state
value: deprecated
- property: kiri.lifecycle.sunset_date
value: 2026-12-31
- property: kiri.lifecycle.migration_target
value: io.acme.sales.gold.dim_customer_v2
sunset_date is the date the contract will be removed; migration_target
is where to go instead. Both travel with the contract, so a consumer
reading it sees the deprecation, the deadline, and the replacement in
one document.
The review-state machine
Lifecycle state is per-contract. There is a second, distinct state
machine that is per-attribute — per column, per metric, per reporting
goal — tracking the governance review of that specific attribute:
kiri.attribute.review_state, whose canonical values are new,
in_progress, submitted, approved, and rejected. It answers a
different question than lifecycle: not “where is this contract in its
promotion journey” but “has a human approved this individual attribute
for contract binding.” The two are deliberately not interchangeable —
an approved attribute can live on a contract that is itself deprecated.
The review flow moves an attribute from discovery to binding through a closed set of transitions:
(discovery) ──► new ──► in_progress ──► submitted ──┬──► approved
└──► rejected
approved ──► in_progress (governance re-edit)
rejected ──► in_progress (steward re-opens)
A newly discovered attribute starts at new; a steward takes it to
in_progress, submitted opens the review, and a reviewer lands it
at approved or rejected. new → submitted and new → approved
are forbidden — discovery never self-approves. Both terminal states
can reopen to in_progress (a governance re-edit or a steward
re-opening a rejection), so nothing is deleted and the next
schema-discovery run doesn’t re-queue a decision the organisation
already made. Every menu of transitions is closed: an attribute can’t
jump to a state the machine doesn’t allow from where it is.
What deprecation does downstream
Marking a contract deprecated isn’t cosmetic — it changes what the
rest of the platform reports about it:
- Consumers. Lineage records which contracts depend on this one.
kiri.lineage.downstreamlists them (inferred from others’ declaredupstream), andkiri.cost.consumer_allowlistbounds who may read it. When a contract deprecates, every downstream contract is by definition depending on something on its way out — surfaced so owners can migrate to themigration_targetbefore thesunset_date. - Lineage. The contract stays in the graph while deprecated — its edges don’t vanish — so you can still trace what fed it and what it fed. Deprecation is a visible node state, which is the point: a consumer walking lineage sees the deprecation rather than discovering a broken dependency at apply time.
- Catalog. The physical table persists. A deprecated gold contract’s table still exists in the catalog; what stops is new transformation runs promoting fresh data into it. When you push contract metadata to Unity Catalog, the lifecycle state and deprecation annotations project onto the catalog object, so a consumer browsing the catalog — not just the contract repo — sees the same “deprecated, migrate to X by Y” signal.
Where next
- Write your first data contract — the lifecycle every contract starts in.
- Handle schema drift — the other side of keeping contracts and live tables honest with each other.