Run a contract-approval review
Before an attribute is trusted enough to bind into a contract, a human has to have looked at it and said so. Kirimana models that as a review-state machine on every catalog column: the attribute moves through a small, closed set of states, each transition is recorded with an actor and a reason, and discovery is never allowed to promote an attribute on its own. This page covers the state machine, the verbs that drive it, how many approvals a change needs, and the audit trail it leaves.
The attribute review-state machine
Every catalog column carries a review_state, orthogonal to the
governing contract’s medallion state. Medallion answers “which
data-platform layer is this in?”; review state answers “has a human
approved this attribute for contract binding?”. The states and the
allowed transitions between them are a closed menu — no other move is
possible:
(discovery) ──► new ──► in_progress ──► submitted ──┬──► approved
│
└──► rejected
approved ──► in_progress (governance re-edit)
rejected ──► in_progress (steward re-opens)
- new — where discovery lands a column. Discovery stamps provenance and nothing more.
- in_progress — a steward is editing metadata (classification, description, PII flags).
- submitted — the review is open, like a PR waiting on review.
- approved — a human has signed off; the column may bind into a contract.
- rejected — a reviewer declined it, with a required reason.
The critical invariant: new → submitted and new → approved are
forbidden. Discovery never auto-promotes an attribute. A machine can
populate the queue; only a human moves the needle. And both approved
and rejected are re-openable back to in_progress — governance can
re-edit an approved attribute, and a steward can re-open a rejected
one — so a decision is never a dead end, but every re-open is itself a
recorded transition.
The verbs
kiri attr drives the machine. A column is referenced as
<asset_urn>#<column_name>:
# Triage the queue — what did the last sync discover?
kiri attr list --state new --domain sales
# Edit metadata while in_progress (moves new → in_progress).
kiri attr edit sales.orders#customer_email \
--classification confidential \
--description "Customer email, PII"
# Open the review (in_progress → submitted).
kiri attr submit sales.orders#customer_email
# Sign off (submitted → approved).
kiri attr approve sales.orders#customer_email
# ...or decline, with a required reason (submitted → rejected).
kiri attr reject sales.orders#customer_email \
--reason "email belongs on the contact table, not orders"
# Re-open either terminal state back to in_progress.
kiri attr reopen sales.orders#customer_email
Every verb takes --actor to override the identity recorded in the
history (it defaults to the environment). kiri attr show and
kiri attr history print one column with its full review history.
The same write path backs the web app’s Catalog page, so a review
done in the UI and one done at the CLI land identical transitions and
history entries — there is one review engine, two surfaces.
kiri attr reject requires --reason, and the requirement is not
cosmetic: a rejection with no stated reason is forensically useless.
The machine refuses the transition without one.
Approval counts scale with sensitivity
How many approvals a contract change needs is resolved separately,
at PR time, and it rises with what the change does.
kiri contract approval-count computes the required number from the
diff: a base count per touched domain, more when the change raises a
classification to restricted, and more again when it introduces a
new cross-domain consumer. A routine edit inside one domain needs the
domain’s base count; a change that raises restricted or crosses a
domain boundary automatically needs elevated sign-off. See PR-time
governance gates for wiring this into
CI.
The number of humans who must approve, and the roles allowed to
approve at all, come from RBAC: the
approve and promote_gold capabilities are approver-and-above, and
promotion to gold is scoped to the owning domain.
A separate flow: gated AI queries
Do not confuse attribute review with the AI-query approval flow,
which is a different surface with its own verbs. When the AI query
gate decides a query needs a human before it runs, it opens an
approval request; operators vote on it with kiri approve:
kiri approve list --workspace ws-prod
kiri approve show <request-id>
kiri approve grant <request-id> --as alice@acme.com
kiri approve reject <request-id> --as bob@acme.com --rationale "too broad"
Here a single reject vote blocks the request (fail-fast — one objection is enough), and the required approve count is snapshotted at issue time so a later policy change does not retro-affect an open request. This flow governs query execution, not attribute review — keep the two mental models distinct. The rest of this page is about attribute review.
The audit trail
Every successful attribute transition appends an immutable entry to
the column’s review_history: the from-state, the to-state, the
actor, the timestamp, and the reason. That tuple is the forensic
record — who moved this column from which state to which, when, and
why. Because re-opens are transitions too, an attribute that was
approved, re-edited, and re-approved carries every step, not just the
latest verdict.
# The full decision history for one column.
kiri attr history sales.orders#customer_email
This history is what an auditor reads to answer “prove this sensitive
column was reviewed by a human before it went into a governed
contract”. The closed-menu transitions guarantee the story is
coherent — there is no way to reach approved except through
submitted, and no way to reach submitted except through a human
edit — and the append-only history guarantees the story is complete.
Putting it together
The review loop is short and hard to cheat. Discovery fills the queue
with new columns. A steward edits and submits. An approver signs off
or rejects with a reason. The contract change carrying the result goes
through a PR whose required approval count scales with its
sensitivity. And every step, including every re-open, is recorded on
the column itself. Nothing reaches a governed contract without a named
human having approved it, and nothing that human did is invisible
after the fact.