Skip to main content
Private Preview·Early access by invitation.Request access →
Kirimana.
Docs · Contracts

Write your first ODCS data contract

A data contract in Kirimana is the complete, single-document source of truth for a dataset. One contract, read once, answers every operational, governance, and AI-consumption question about its data: where it comes from, what shape it has, who owns it, how sensitive it is, how fresh it must be, and what an AI assistant is allowed to do with it.

The wire format is ODCS v3 YAML — adopted straight, no forks, no parallel fields. Anything ODCS doesn’t cover is expressed through customProperties under the documented kiri.* namespace. Every kiri.* namespace is a candidate for upstreaming to the ODCS spec; the namespace is a safety valve, not a long-term home.

Anatomy of a contract

The ODCS-native fields you’ll touch in every contract:

FieldPurpose
apiVersionODCS version declaration, pinned per Kirimana release
kindAlways DataContract
idStable contract ID — UUID or reverse-DNS (com.acme.crm.customer)
nameHuman-readable name; must match the filename stem
versionSemver — MAJOR.MINOR.PATCH
statusdraft / active / deprecated / retired
domainBusiness domain (crm, finance, sales)
descriptionPurpose and limitations — write what this dataset is not for
ownerAccountable person or team; email required
schema[]Tables and their properties[] (columns)
quality[]Contract-level data-quality rules (SQL / library / custom)
sla[]Freshness, availability, retention
servers[]Where the data physically lives

On top of that, kiri.* customProperties carry the governance and runtime semantics ODCS doesn’t encode. The ones you need first:

  • kiri.classification — one of public · internal · confidential · restricted. Mandatory on every contract, and on any column whose sensitivity differs from the contract level.
  • kiri.pii.categories + kiri.pii.direct_identifier — per-column PII taxonomy (name, email, phone, national_id, health, location, …).
  • kiri.lifecycle.state — the medallion state (draft / bronze / silver / gold / deprecated); advanced with kiri contract promote.
  • kiri.sla.* — freshness (4h, 1d, 15m), a quality-threshold pass rate, and breach routing (severity + a route like slack:#data-platform-alerts).
  • kiri.ai_policy.* — whether the data may be used for AI training or inference, and where it may egress. Egress runs strictest first: internal_only (local models only), in_platform (also the data platform this contract’s data already lives on, never a third-party API), approved_providers (a named list, which may include a vendor), unrestricted. When absent, the default is the most restrictive: forbidden, internal_only. Enterprise-safe by default.
  • kiri.semantic.* — technique-neutral business meaning: entity, business_keys, grain. The same declaration projects into a Data Vault hub key, a Kimball natural key, or a flat unique constraint.

A missing namespace is a legal default, not an error. Unlisted custom properties round-trip untouched but are never relied on by the core.

A complete bronze contract

Bronze is the landing layer: 1:1 with the source, same columns, no business logic, insert-only with provenance stamps. Scaffold one with kiri contract new (interactive) or kiri contract scaffold (flag-driven), or write it by hand:

apiVersion: 3.0.0
kind: DataContract
id: io.acme.sales.bronze.customer_raw
name: customer_raw
version: 0.1.0
status: draft
domain: sales
description: >
  Bronze landing for CRM accounts. 1:1 with the source — same columns,
  same types, no business logic. Insert-only with a load_date +
  record_source stamp so we always know where each row came from.
  NOT FOR: marketing segmentation or finance reconciliation.
owner: data-platform@acme.example
schema:
  - name: customer_raw
    logicalType: table
    properties:
      - name: accountid
        logicalType: string
        primaryKey: true
        required: true
        description: Source business key from the CRM
      - name: name
        logicalType: string
        required: true
      - name: primary_contact_email
        logicalType: string
        description: PII — handled per pii + ai_policy below
        quality:
          - type: library
            rule: regex
            parameters: {pattern: "^[^@]+@[^@]+\\.[^@]+$"}
        customProperties:
          - property: kiri.pii.categories
            value: [email]
          - property: kiri.pii.direct_identifier
            value: true
      - name: load_date
        logicalType: timestamp
        required: true
        description: Wall-clock time the row landed in bronze
      - name: record_source
        logicalType: string
        required: true
        description: Provenance tag (e.g. "crm.accounts")
quality:
  - type: sql
    description: accountid is unique
    query: "SELECT count(*) - count(DISTINCT accountid) FROM {{ table }}"
    mustBe: 0
customProperties:
  - property: kiri.classification
    value: confidential
  - property: kiri.source_urn
    value: urn:kirimana:source:crm:accounts
  - property: kiri.lifecycle.state
    value: bronze
  - property: kiri.medallion.active
    value: true
  - property: kiri.sla.freshness
    value: 1d
  - property: kiri.sla.on_breach.severity
    value: high
  - property: kiri.sla.on_breach.route
    value: slack:#data-platform-alerts
  - property: kiri.ai_policy.training_data
    value: forbidden
  - property: kiri.ai_policy.inference_allowed
    value: true
  - property: kiri.ai_policy.egress
    value: approved_providers
  - property: kiri.ai_policy.approved_providers
    value: [local, anthropic]

Ownership and classification are not optional

Two governance rules bite early, on purpose:

Ownership must be real. kiri contract lint refuses placeholder owners — TODO, FIXME, an empty string, or any example.com address raise an error-severity owner-placeholder finding. Team handles (analytics-team) and real emails pass; a malformed email does not. There is no way to merge a contract nobody answers for.

Sensitive data cannot be quietly authored as harmless. Columns whose names look like PII (email, phone, ssn, birth_date, …) without an explicit kiri.pii.* annotation raise a pii-hint finding, backed by fail-closed authoring guardrails that never silently stamp a default verdict. Classification proposals from kiri inventory pre-classify merge by a strict precedence ladder in which a human operator override wins every contest — and every stamped decision writes one structured audit row. Downgrading an inferred-PII column to public therefore only happens as an explicit, audited operator decision, never as a default.

Semantic validation adds more cross-field rules: every column carrying kiri.pii.categories requires contract-level kiri.gdpr.* metadata, egress: approved_providers requires a non-empty provider list, and lifecycle transitions follow a state machine.

Lint, validate, diff — at PR time

Contracts live in git, so governance runs where the change happens:

# Structural (ODCS JSON Schema) + semantic validation of one contract
kiri contract validate contracts/customer_raw.yml --strict

# Governance lint over the whole project; markdown for a PR comment
kiri contract lint --format pr

# Semantic-aware diff of two contract versions
kiri diff contracts/customer_raw.yml /tmp/customer_raw.proposed.yml

lint exits 0 on warnings only, 1 on any error-severity finding — wire it into CI so an unowned or unclassified contract can’t reach main. kiri contract approval-count computes how many approvers a change needs (a diff that raises anything to restricted needs more), and kiri contract codeowners derives a CODEOWNERS file from the contract ownership so review routing follows the metadata.

From contract to Unity Catalog

The contract is the source of truth; the platform catalog is a materialisation of it. On Databricks:

  • kiri apply creates the physical tables and stamps the governance metadata declared in the contract.
  • kiri catalog push --target unity writes classifications, tags, and lineage annotations to Unity Catalog (dry-run by default — inspect the NDJSON, then --no-dry-run).
  • kiri contract verify-live --catalog <uc-catalog> --schema <schema> diffs the live table’s schema against the contract and fails CI on drift; kiri contract sync-from-live --propose emits a reviewable patch when the live table is the truth you want to adopt.

Edits flow one way: change the YAML, open a PR, apply. A classification edited directly in the catalog is drift, and Kirimana surfaces it rather than pulling it back.

Where next

Promote the contract along the medallion path with kiri contract promote <name> --to silver, declare business keys via kiri.semantic.*, and let kiri contract emit generate the framework artifacts that build it.

Updated 5 July 2026 · v1.0.0-beta.1