hachure 0.15.0 · MIT · pre-1.0npm i hachure

Hachure

the receipt format we don't own

An agent says it verified the change. Kontour hands you a receipt saying the same thing. Why should the receipt be worth more than the agent's word? Only one answer survives contact with a skeptic: because checking it doesn't require us. Our receipts are written in Hachure — a format with its own spec repo, its own MIT license, and two implementations that share no code. If Kontour disappeared tomorrow, every receipt we ever produced would still be readable, and the schemas and test vectors needed to check them would still be sitting in a public repo under a license we can't revoke.

two programs, one file

# a real receipt from /receipts/, checked by the format's own CLI

$ npx -y -p [email protected] -p [email protected] hachure validate \

    governance-readiness-ready.trust.bundle

valid TrustBundle (schemaVersion 5)


# the same file, read by a separate implementation

$ npx -y @kontourai/[email protected] report --format summary \

    --input governance-readiness-ready.trust.bundle

Kontour Surface report surface-1785016929678

Source: veritas-governance-kit/readiness-adapter

Claims: 1 (verified: 1)

Facets: veritas.readiness: 1

High-impact unsupported: none

Stale: none

Recompute needed: none

Disputed: none

Claim groups: 0

Transparency gaps: 0

Why we gave it away

A receipt you can only check with our software isn't a receipt.

Every vendor selling trust has the same conflict of interest: it defines the record, it writes the checker, and it grades its own homework. You can read its dashboard. You cannot independently reproduce it. When the dashboard and reality disagree, the dashboard wins, because the dashboard is the only thing that can read the file.

So we don't own the record. Hachure lives in its own org under its own MIT license, with normative schemas, a versioned status algorithm, and a conformance suite anyone can run against their own implementation. The spec names Kontour AI as the originator — and that's the extent of our control. We can't quietly redefine what a receipt means, because doing it means changing a public spec that other implementations are pinned to.

That constraint is the product. It's the only thing that makes "check this without trusting us" a statement rather than a slogan.

Check it without us

The schemas, the status algorithm, and the conformance vectors are published. Anyone can write a checker and prove it agrees — the vectors exist so an independent implementation can demonstrate it derives the same answers we do.

Keep it after us

A receipt is a JSON file against an MIT-licensed public schema. If you stop using Kontour — or Kontour stops existing — every receipt you already hold stays readable, and the schema it is checked against isn't ours to withdraw.

Emit it beside us

Your pipeline can produce bundles our tools read on exactly the same terms as our own. There is no privileged producer: a bundle from your CI merges with a bundle from ours, and collisions surface instead of one side quietly winning.

Two implementations, one file

The second opinion has to be a second implementation.

hachure is the reference CLI, shipped in the spec's own npm package: a dependency-free implementation of validation, status derivation, and merge. @kontourai/surface is Kontour's implementation — a separate codebase, also dependency-free, with its own validator and its own status deriver. Neither package imports the other or shares a line with it. Both declare the same algorithm revision, statusFunctionVersion 2, which is what the spec actually pins conformance to.

Worth saying out loud: both of those trace back to us. We wrote Surface and we originated the reference CLI. Two codebases that can't share a bug is a real check, and it is a weaker one than a stranger's. The vectors are published precisely so somebody else can build the third — and until that exists, this is a second opinion, not an outside audit.

This page previously described ajv as the second, independent opinion in the validate command. That was wrong, and it's worth showing why rather than quietly deleting it. ajv is the schema engine hachure validate imports. The command pins it because hachure needs it — not because it is a separate check:

ajv is hachure's engine, not its auditor

$ npx -y -p [email protected] hachure validate \

    governance-readiness-not-ready.trust.bundle

hachure: validate requires ajv — install it with: npm i ajv

# exit 1

A real second opinion is a program that read the spec and not our code. Here are both, refusing the same damaged file — one required field removed from a local copy of a receipt. Different codebases, different wording, same refusal, both exit 1.

both implementations refuse the same malformed bundle

# claims[0].claimType deleted from a local copy

$ npx -y -p [email protected] -p [email protected] hachure validate malformed.trust.bundle

invalid TrustBundle:

  /claims/0 must have required property 'claimType'

# exit 1


$ npx -y @kontourai/[email protected] report --input malformed.trust.bundle --format summary

Missing required string field: claimType

# exit 1

Recompute it yourself

The verdict isn't a field. It's a function.

A bundle does carry a status on each claim, but that value is a convenience, not the authority. In Hachure, status is a pure function of the evidence in the bundle and a timestamp you supply. Run the function and you get the verdict yourself — same evidence in, same verdict out, on any conforming implementation, at any moment you name.

hachure derive — a blocked verdict, recomputed

$ npx -y -p [email protected] -p [email protected] hachure derive \

    governance-readiness-not-ready.trust.bundle --now 2026-07-25T12:00:00Z

{

  "statusFunctionVersion": "2",

  "evaluatedAt": "2026-07-25T12:00:00.000Z",

  "statusByClaimId": {

    "working-tree-798d536c54a9d1c0d4c238509527732827088f61d7e74a65c9f830ccb8d77eca.veritas-readiness.mergereadiness": "disputed"

  }

}

Which is why editing the stored verdict buys nothing. Take a local copy of that same receipt and hand-edit its claim status from disputed to verified. The file is still perfectly schema-valid — nothing about it is corrupt. And both implementations still derive disputed, because the evidence didn't change:

the stored status is not the answer

# claims[0].status hand-edited: disputed → verified

$ npx -y -p [email protected] -p [email protected] hachure validate tampered.trust.bundle

valid TrustBundle (schemaVersion 5)


$ npx -y -p [email protected] -p [email protected] hachure derive \

    tampered.trust.bundle --now 2026-07-25T12:00:00Z

{

  "statusFunctionVersion": "2",

  "evaluatedAt": "2026-07-25T12:00:00.000Z",

  "statusByClaimId": {

    "working-tree-798d536c54a9d1c0d4c238509527732827088f61d7e74a65c9f830ccb8d77eca.veritas-readiness.mergereadiness": "disputed"

  }

}


$ npx -y @kontourai/[email protected] report --input tampered.trust.bundle --format summary

Kontour Surface report surface-1785016942237

Source: veritas-governance-kit/readiness-adapter

Claims: 1 (disputed: 1)

Facets: veritas.readiness: 1

High-impact unsupported: none

Stale: none

Recompute needed: none

Disputed: working-tree-798d536c54a9d1c0d4c238509527732827088f61d7e74a65c9f830ccb8d77eca.veritas-readiness.mergereadiness

Claim groups: 0

Transparency gaps: 1

That is what tamper-evident means in practice: editing the file doesn't change what the file derives to. It is not tamper-proof. Someone who can rewrite the evidence records — not just the verdict — will get a different answer out, and nothing in an unsigned bundle stops them. Signing is where that bar goes up, and it's the next section, including where it stops.

Proving an implementation

Nine vectors decide whether your deriver agrees.

The spec ships the test cases, not just the prose. Each vector is an input bundle, a timestamp, and the statuses a conforming implementation has to derive from them — expired freshness windows, revoked events, blocking disputes, resolved authorities. Passing all of them for a given algorithm revision is the spec's stated bar for conformance, and it's how a team writing their own checker proves it agrees with ours without asking us.

These nine cover status derivation; four more, covering how bundles from different producers merge, ship alongside them. All of it is inside the npm package, so running the suite is one command with nothing cloned.

hachure vectors

$ npx -y -p [email protected] -p [email protected] hachure vectors


  PASS sf-authority-resolved

  PASS sf-disputed-blocking

  PASS sf-expired-window

  PASS sf-no-freshness-fields

  PASS sf-reference-bundle-snapshot

  PASS sf-revoked-event

  PASS sf-runtime-observation-required

  PASS sf-stale-duration

  PASS sf-verified-commit

all 9 vectors pass (statusFunctionVersion "2")

How strongly it's signed

Start unsigned in five minutes. Turn the dial up as the stakes rise.

Signing is a dial, not a gate. An unsigned bundle is a valid bundle — forever, by design — because a format nobody can afford to adopt protects nobody. When "the producer says so" stops being enough, there are two more notches, and each is a strict superset of the one below it.

L0

The producer says so

Nothing is signed. You are trusting whoever handed you the file and the channel it arrived over. This is the default, and an unsigned bundle is a valid bundle — not a degraded one. It is also why you can start today: writing a receipt costs you a JSON file.

L1

Signed by a CI identity

A short-lived key issued to a specific workflow or account at the moment of signing. It shows which CI run produced the receipt — as far as you trust that CI provider's identity service. The signature can also be written to a public transparency log, though the format doesn't require it.

L2

Signed by a key you hold

A long-lived key in your own key-management system. For environments that can't lean on a public identity provider — air-gapped networks, internal PKI, regulated deployments. You distribute the public key yourself; the format deliberately doesn't specify how.

Worth being exact about what any of that gets you. A Hachure receipt is tamper-evident locally — change the file and the recomputed verdict tells on you. It is tamper-resistant only up to the identity boundary of whatever signed it: at L1 that boundary is your CI provider, and anyone who can forge that workflow identity can forge the signature. And none of it says the work was good. It says what evidence existed and what verdict that evidence derives to.

The format is also pre-1.0, in its own words: hard versioning, no compatibility promises yet. Breaking changes bump the schema version outright rather than shipping compatibility shims — the bundles on this page declare schemaVersion 5 while the spec is already at 7, and the current validator still accepts 5, 6, and 7. Pin your version. That's the trade for a format still moving fast enough to be worth adopting.

Where to go next

The specification →

Normative JSON schemas, the status function, merge rules, the assurance profile, and interop notes for in-toto, SCITT, and OSCAL. MIT.

hachure.org →

The format's own site: the three verbs, the status function walked through, and a worked recompute example.

hachure on npm →

The schemas, the conformance vectors, and the reference CLI in one dependency-free package.

hachure-action →

A GitHub Action that turns a CI command into a trust.bundle — the shortest path from a check you already run to a receipt.

Kontour receipts →

The bundles used on this page, downloadable as raw files, each rendered from its actual contents.

Every bundle used on this page is a real receipt from one of Kontour's own pipelines, committed to this site's repo and served byte-identical for download. Take one and run the commands above — the verdicts you get should be the verdicts you just read.