← Back to blog
Feature note2026-06-27 12:00 UTC

How Ota Explains Contract Drift Against New Failures

Ota can now archive semantic contract truth, diff normalized assumptions, and explain whether new blockers are likely related to contract drift without pretending every failure has one certain cause.

Overview

When a repo starts failing after a contract change, most systems can show you two separate facts:

  • a new failure appeared
  • the contract changed

What they usually do not tell you is whether those facts belong together.

That is the gap this feature closes.

Ota can now:

  • archive the exact semantic contract truth that produced a receipt
  • diff normalized contract assumptions instead of raw YAML formatting
  • compare a current receipt against an archived baseline
  • say whether newly introduced blockers are likely_related, possibly_related, or no_clear_correlation

That distinction matters.

Ota does not diff formatting noise. It diffs normalized semantic contract truth.

And it does not pretend every new failure has one certain cause. It publishes an honest advisory verdict:

  • likely_related
  • possibly_related
  • no_clear_correlation

That is the right product shape for execution governance: stronger evidence, without causal theater.

What shipped

This shipped as three connected surfaces.

1. Semantic snapshot identity

Every successful receipt now carries:

  • receipt.contract_snapshot_hash
  • receipt.assumption_set_hash

When a receipt is archived, Ota also writes the normalized semantic contract JSON under .ota/contracts/....

That means the receipt can point at the exact semantic contract truth that produced it.

2. Semantic diff

ota diff now compares normalized assumption truth, not YAML noise.

That matters because the real operator questions are semantic:

  • did this env requirement become mandatory?
  • did this runtime requirement tighten?
  • did this surface ownership move?

Those should not be hidden behind comments, ordering, or formatting drift.

3. Honest receipt correlation

ota receipt --json --baseline ... can now compare the current receipt against archived baseline truth and publish:

  • contract_changes[]
  • likely_related_changes[]
  • summary.comparison.contract_snapshot_changed
  • summary.comparison.correlation

The important part is not just that correlation exists.

It is that Ota prefers the sharpest declared explanation it can recover:

  1. exact declared owner truth
  2. declared owner subtree drift
  3. named references
  4. broader same-family overlap

So reusable surfaces.<name> and readiness.probes.<name> outrank weaker adjacent workflow references when both are plausible.

Pressure proof

This did not stop at unit coverage.

We pressure-tested it against controlled drift cases using the branch binary.

Env drift proof

We archived and promoted a clean baseline, then introduced:

CONTRACTyaml
env:  vars:    DATABASE_URL:      required: true

The receipt compare came back with:

  • correlation: "likely_related"
  • contract_snapshot_changed: true
  • contract_changes[]: env.vars.DATABASE_URL.required
  • likely_related_changes[]: env.vars.DATABASE_URL.required
  • introduced blocker: Missing environment variable: DATABASE_URL

That is exactly the right answer: the introduced blocker lines up with the newly declared required environment truth.

Tool drift proof

We did the same for a missing tool lane by introducing a fake declared requirement.

The compare output stayed honest:

  • introduced blocker: Missing tool: fake-tool
  • top correlated changes pointed at the declared tool truth

Runtime drift proof

This is the case that mattered most, because it exposed a real product gap.

When runtime drift came through:

CONTRACTyaml
execution:  contexts:    host:      requirements:        runtimes:          node: ">=99"

the introduced blocker was:

  • Version mismatch for runtime: node

but correlation originally came back as:

  • no_clear_correlation

That was wrong.

The drift was direct and declared. Ota should have connected it.

So we fixed the core matcher to treat execution.contexts.<name>.requirements.runtimes.<runtime> as first-class declared runtime requirement truth.

After that fix, the same proof returned:

  • correlation: "likely_related"
  • contract_changes[]: execution.contexts.host.requirements.runtimes.node
  • likely_related_changes[]: execution.contexts.host.requirements.runtimes.node
  • introduced blocker: Version mismatch for runtime: node
JSONjson
{
  "correlation": "likely_related",
  "contract_changes": [
    {
      "path": "execution.contexts.host.requirements.runtimes.node",
      "status": "add",
      "category": "execution",
      "risk": "high",
      "target": ">=99"
    }
  ],
  "likely_related_changes": [
    {
      "path": "execution.contexts.host.requirements.runtimes.node",
      "status": "add",
      "category": "execution",
      "risk": "high",
      "target": ">=99"
    }
  ],
  "introduced": [
    "Version mismatch for runtime: node"
  ]
}

That is the bar.

Pressure should expose the missing match, and Ota should get stronger instead of teaching users to work around it.

Why this matters

This is more than a nicer diff.

It changes what a receipt can mean.

A receipt can now answer:

  • what semantic contract truth was active
  • whether that truth changed
  • which assumptions changed
  • whether those changes plausibly explain the new blocker

That is the kind of evidence humans can review, CI can gate on, and agents can use without guessing.

Docs

Quick path

VERIFYbash
ota receipt --json --archive --promote-baselineota receipt --snapshot latestota diff ./.ota/receipts/repo-receipt-....json ./ota.yamlota receipt --json --baseline promoted

On repos with more than one declared workflow, scope the receipt lane explicitly:

VERIFYbash
ota receipt --workflow frontend --json --archiveota receipt --workflow frontend --json --baseline latest

That keeps one workflow's archived receipt history from silently replacing another workflow's baseline lane.

If a new blocker appears, the intended operator path is:

  1. read summary.comparison.correlation
  2. read contract_changes[]
  3. read likely_related_changes[]