Write a Contract
An Invaract contract is a YAML document describing a transformation’s inputs and
outputs — shaped after the
Open Data Contract Standard (ODCS),
not a new format invented from scratch. This guide builds one up using the real contract
this repository ships, demo/contracts/invaract_output.yaml.
Start with identity and version
Section titled “Start with identity and version”id: invaract_demo_outputversion: "1.0.0"status: activeid— a stable identifier for this contract.version—MAJOR.MINOR[.PATCH], as a string ("1.0.0"or"1.0").status— free-form; defaults toactiveif omitted.
Declare inputs
Section titled “Declare inputs”Each input is a dataset the transformation reads:
inputs: - name: orders location: demo/input/sample.csv format: csv schema: fields: - name: id type: integer required: true nullable: true - name: value type: integer required: true nullable: truename— logical name, used for lookups and violation messages.location— the physical location Invaract matches against the real Spark plan (a table name or a file path). Spark reports absolutefile:URIs at runtime; a relative location declared here still matches, by suffix — see Contract Format.format— optional, e.g.csv,parquet,delta.schema.fields— the fields, each with anameandtype. A dataset whose content no contract governs (a folder of rejected text lines, say) can leaveschemaout: its columns are then not checked, and validation warns so the omission is never an accident.
Declare outputs
Section titled “Declare outputs”At least one output is required — this is what a contract actually gates:
outputs: - name: result location: demo/output/result.parquet format: parquet saveMode: overwrite schema: fields: - name: id type: integer required: true nullable: true - name: value type: integer required: true nullable: true - name: value_squared type: integer required: true nullable: true - name: value_tier type: string required: true nullable: truesaveMode (append/overwrite/ignore/error) is checked against the write’s actual
save mode when both are known. A dataset can also declare catalog: { required: true, ... }
to require it be registered in a real data catalog, not just written to a bare path — see
Require Catalog Registration. It can also
declare type: DATA_ASSET/SOURCE/CONTROL to say what it represents — a shareable
data product, an external source, or pipeline control data — see Declare Input and
Output Types.
If the contract declares more than one output, an output can also say which of the contract’s
inputs it is built from with derivedFrom: [input_name, ...]. Without it, every write is
expected to read every declared input — see Map Outputs to Their
Inputs.
Field semantics
Section titled “Field semantics”| Key | Default | Meaning |
|---|---|---|
required | false | The field must be present in the actual schema. |
nullable | !required | The field’s values may be null. |
properties | — | Nested fields, for a struct type. |
sensitivityTags | none | Governance labels (e.g. [pii, financial]) — see Reference → Contract Format’s “Sensitivity tags”. Declared on an input field, propagated to every output column derived from it — reporting only, not enforced. |
constraints | none | Static data-quality requirements (equals/oneOf/range) — see Verify Static Data Quality. Unlike sensitivityTags, this is enforced (opt-in, via staticDataQuality) — but only when the transformation’s own logic provably guarantees a violation, never merely predicts one. |
Add rules (optional)
Section titled “Add rules (optional)”rules is a list of {type, ...properties} entries. Most rule types are recorded for
future use but not yet enforced; seven are interpreted and enforced today, across two
families — three for row-level MERGE/UPDATE/DELETE statements, four for a
transformation’s overall shape:
rules: - type: merge_condition columns: [customer_id, region] - type: forbid_unconditional_delete - type: allowed_update_columns columns: [status, updated_at] - type: required_group_by columns: [customer_id] - type: forbid_cross_joinSee Enforce Row-Level DML Rules and Enforce Transformation Shape Rules for what each one checks.
Add extensions (optional)
Section titled “Add extensions (optional)”Any top-level key Invaract doesn’t recognize — and the explicit extensions map — are
preserved verbatim, not rejected:
extensions: owner: data-platform-teamThis keeps a contract additive to the wider ODCS ecosystem: fields your organization cares about (owners, SLAs, quality rules) round-trip through Invaract even though it doesn’t act on them yet.
Validate as you write
Section titled “Validate as you write”Point your editor at the contract’s JSON Schema for inline validation and autocomplete. With the YAML Language Server (the VS Code YAML extension, among others), put this comment at the top of the contract:
# yaml-language-server: $schema=https://mlltx.github.io/Invaract/schemas/contract/invaract-contract.schema.jsonid: my_contract# ...The schema is published at
https://mlltx.github.io/Invaract/schemas/contract/invaract-contract.schema.json,
from the same file the contract parser is tested against. It checks a contract’s shape (the
required keys, the field types); the parser and validator still decide the rest, such as
duplicate names. Inside a clone of this repository, the contracts under demo/contracts/ use
the relative path ../../contract/schema/invaract-contract.schema.json instead.
Full reference
Section titled “Full reference”For every field, its type, and the complete set of validator checks, see Reference → Contract Format.