Skip to content

Your First Contract

Quick Start showed a passing write. This walks through making one fail — the moment that actually demonstrates what Invaract is for — using a contract already included in the repository, run directly against the real demo job.

  1. If you haven’t already:

    Terminal window
    ./dev/build
  2. demo/contracts/invaract_output_broken_example.yaml is identical to the working contract, except its output schema declares a customer_name field:

    outputs:
    - name: result
    location: demo/output/result_broken_example.parquet
    format: parquet
    schema:
    fields:
    - name: id
    type: integer
    # ...
    - name: customer_name
    type: string
    required: true
    nullable: false

    The real demo transformation (InvaractPlugin) never produces a customer_name column — so this contract can never be satisfied by the job as written.

  3. Invoke the same job the quick start used, passing the broken contract as the fourth argument:

    Terminal window
    spark-submit \
    --class com.invaract.runner.DemoJobHarness \
    --master local[*] \
    --jars plugin/target/scala-2.12/invaract-spark-plugin-0.2.0.jar \
    runner/target/scala-2.12/invaract-spark-runner.jar \
    demo/input/sample.csv \
    demo/output/result_broken_example.parquet \
    demo/output/report_broken_example.json \
    demo/contracts/invaract_output_broken_example.yaml
  4. The job exits non-zero, and prints exactly what was wrong:

    Contract violation: 'invaract_demo_output@1.0.0' rejected this transformation. Write aborted.
    What the contract expects:
    input 'orders' at demo/input/sample.csv: id: integer, value: integer
    output 'result' at demo/output/result_broken_example.parquet: id: integer, value: integer, value_squared: integer, value_tier: string, customer_name: string
    What the plan contains:
    Write(file:/.../demo/output/result_broken_example.parquet, format=parquet, saveMode=overwrite)
    └─ Project
    ├─ Project
    │ ├─ Read(file:/.../demo/input/sample.csv)
    │ ├─ id = id
    │ ├─ value = value
    │ └─ value_squared = (value * value)
    ├─ id = id
    ├─ value = value
    ├─ value_squared = value_squared
    └─ value_tier = CASE WHEN value > 50 THEN high ELSE low END
    Why it violates the contract (1 violation):
    1. [MISSING_OUTPUT_FIELD] required field 'customer_name' is absent from the actual OUTPUT schema
    How to correct it:
    1. Add a 'customer_name' column (type 'string') to the output, or mark it optional in the contract if it isn't always produced.
  5. Terminal window
    ls demo/output/result_broken_example.parquet

    The file does not exist. Spark never got to execute the write — Invaract rejected the plan during analysis, before any output was produced.

Now that you’ve seen a contract reject a real write, learn the full format so you can write your own: Write a Contract.