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, orno_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_relatedpossibly_relatedno_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_hashreceipt.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_changedsummary.comparison.correlation
The important part is not just that correlation exists.
It is that Ota prefers the sharpest declared explanation it can recover:
- exact declared owner truth
- declared owner subtree drift
- named references
- 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:
env: vars: DATABASE_URL: required: trueThe receipt compare came back with:
correlation: "likely_related"contract_snapshot_changed: truecontract_changes[]: env.vars.DATABASE_URL.requiredlikely_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:
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.nodelikely_related_changes[]: execution.contexts.host.requirements.runtimes.node- introduced blocker:
Version mismatch for runtime: node
{
"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
ota receipt --json --archive --promote-baselineota receipt --snapshot latestota diff ./.ota/receipts/repo-receipt-....json ./ota.yamlota receipt --json --baseline promotedOn repos with more than one declared workflow, scope the receipt lane explicitly:
ota receipt --workflow frontend --json --archiveota receipt --workflow frontend --json --baseline latestThat 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:
- read
summary.comparison.correlation - read
contract_changes[] - read
likely_related_changes[]
Take action