Makoto
Menu
The provenance casebook

Data provenance fails in familiar, expensive ways.

These are cases in which data left its source and someone later needed to prove what happened. Each case names the evidence Makoto needs to carry and the decision a receiver can make with it.

Featured case · one dataset, attested three times

What happens to the evidence when the data changes?

A dataset is attested, then normalized, then reduced to a public-safe derivative. Each edit produces different bytes, so each edit needs its own statement — and each new statement carries the identity of the one before it at the foot of the document, which is what turns three assertions into a chain.

The walkthrough shows every document in full, the digests that changed at each step, and what a receiver can and cannot conclude from them.

Follow one dataset through attest, change, and re-attest →

  1. 01
    AttestBind an origin statement to the exact source bytes.
  2. 02
    ChangeNew bytes, new digest, and a gap no statement covers.
  3. 03
    Re-attestA transformation statement naming its predecessor.
  4. 04
    TransformDrop identifiers, generalise, and re-attest again.
Featured case · one table, edited in place

What if the data is never copied, only updated?

Most tables are rewritten where they sit, so storage holds only the latest rows. Real SQL runs against one SQLite table twice; after each command a deterministic export of the table is hashed and attested, and each statement names the one before it.

Same table, new state each step. Where the lifecycle walkthrough writes new files, this one shows what survives when the old bytes do not.

Follow one table through load, update, and update again →

  1. 01
    LoadInsert the rows and attest the export of the table.
  2. 02
    UpdateAn UPDATE rewrites every row in place.
  3. 03
    Re-attestA statement naming the state and statement it replaced.
  4. 04
    Update againOne more command, one more link in the chain.
Featured case · independent handoff

Can the receiver verify the story without trusting the producer’s system?

A producer creates source data, signs its origin, normalizes it, produces a public-safe derivative, and signs the handoff. The receiver starts with its own schemas and signer policy, reconstructs the graph, and hashes the bytes it actually received.

Run the full producer-to-receiver proof →

  1. 01
    SourceBind collection claims to exact source bytes.
  2. 02
    TransformPoint each step to its exact predecessor.
  3. 03
    HandoffSign roots, profiles, and the final artifact.
  4. 04
    VerifyApply receiver-owned policy and re-hash the data.

Six ways the history gets lost

  1. 01

    Poisoned pipeline

    A corrupted sensor file carries impossible readings, duplicate rows, and a malicious payload into downstream tables. Predecessor links expose the blast radius and the signed claims that admitted it.

    Open case →
  2. 02

    Scientific reproducibility

    A paper cites experiment.csv, but the outlier removal, normalization, and original source have disappeared with the people who ran them. The lineage graph preserves a path back to the result.

    Open case →
  3. 03

    Dataset release

    A release includes the data but not the evidence. Attach signed origin, transformation, and digest records so consumers can inspect exactly what shipped.

    Open case →
  4. 04

    Configuration postmortem

    Throughput falls after a weekend change made by a service account. A transformation claim records the configuration, code identity, executor, and affected artifact at the time of the run.

    Open case →
  5. 05

    AI dataset gate

    A plausible label has been poisoned. The model builder refuses data unless its source, processing steps, authorized attesters, required profiles, and final bytes verify.

    Open case →
  6. 06

    Invisible Unicode

    Signatures and hashes prove the hidden characters were not altered; they do not make the characters safe. A profile requires an explicit analysis and a normalization or rejection decision.

    Open case →

The evidence patterns inside those cases

PatternWhat it recordsWhy it matters
Origin statementSource identity, collection method, acquisition time, governing claims, and the source artifact digest.Every lineage graph has a defensible beginning.
Transformation statementInput statement and artifact digests, output digest, transformation identity, code reference, parameters, and executor claims.Each step can be checked without editing the step before it.
Stream windowA bounded time or offset range, partition identity, record count, and a digest or Merkle root for that window.Streaming systems can attest to stable windows instead of pretending the stream is one static file.
Bundle and handoffThe statement graph, required profiles, roots, final artifact, and completeness claims selected for transfer.The receiver knows what evidence was intentionally delivered and what it must resolve.
Signed envelopeThe exact statement payload plus one or more DSSE signatures.Content tampering becomes detectable, while authorization remains a receiver policy decision.

The documents themselves

Each pattern above is a real file. These are complete, unabridged, and valid against the published schemas; every digest in them was computed from bytes checked into this repository.

Origin statement

predicate https://usemakoto.dev/predicate/v0.2/origin

Where a lineage graph is allowed to begin. An origin is the only statement type with no predecessor, so it carries the burden of saying what the bytes were drawn from and under what authority.

{
  "_type": "https://in-toto.io/Statement/v1",
  "subject": [
    {
      "name": "customers.raw.json",
      "digest": {
        "sha256": "4e90181e7f75ccdf0e4d81c83d281f53806bed241787cdc751e22b2d4cd19919"
      }
    }
  ],
  "predicateType": "https://usemakoto.dev/predicate/v0.2/origin",
  "predicate": {
    "schemaVersion": "0.2",
    "event": {
      "id": "urn:uuid:9f1c6a02-5d74-4f0b-9a3e-1c6b4d8e77a1",
      "occurredAt": "2026-09-16T16:00:00Z"
    },
    "source": {
      "kind": "urn:makoto:example:source:partner-api",
      "uri": "https://api.partner-bank.example/v2/customers",
      "name": "Partner Bank customer extract",
      "mediaType": "application/json",
      "retrievedAt": "2026-09-16T15:58:12Z",
      "version": "2026-09-16"
    },
    "profiles": [],
    "extensions": {
      "urn:makoto:example:collection": {
        "method": "scheduled-pull",
        "collector": "urn:makoto:example:collector:prod-west-01",
        "collectorVersion": "1.4.2",
        "environment": "production",
        "recordsCollected": 3,
        "recordsDropped": 0
      },
      "urn:makoto:example:governance": {
        "originGeography": "US-WEST-2",
        "lawfulBasis": "contractual",
        "agreementReference": "https://legal.example.com/dpa/partner-bank-2026",
        "agreementEffective": "2026-01-15T00:00:00Z",
        "confidentiality": "confidential",
        "intendedUse": "fraud-detection model training"
      }
    }
  }
}
subject[].digest.sha256
The artifact this statement is about, identified by content rather than by name. Rename the file and the statement still applies; change one byte and it no longer does.
predicate.source.kind
What class of thing the data came from. A receiver policy matches on this, so it is a controlled value rather than free prose.
predicate.source.uri
The specific endpoint or location. Informative; a receiver cannot verify it from the statement.
predicate.source.retrievedAt
When the bytes were pulled, as distinct from when the statement was written.
predicate.event.id
A stable identifier for the collection run, so two statements about the same bytes can be told apart.
predicate.extensions
Open by design. Collector identity, origin geography, and lawful basis live here rather than as first-class fields. A receiver may read them; core verification does not check them.

014b4252977a17c8f2423210fc4c123a464efcc36eefa70e4203c3adee1a3b11 is this statement's own digest, taken over its canonical form. Download the document.

Transformation statement

predicate https://usemakoto.dev/predicate/v0.2/transform

Every step after the first. What makes it a link rather than a standalone claim is the inputs block, placed here at the foot of the predicate, where it names both the bytes consumed and the statement that described them.

{
  "_type": "https://in-toto.io/Statement/v1",
  "subject": [
    {
      "name": "customers.normalized.json",
      "digest": {
        "sha256": "421e19cf002aa25cf6f2f9ed788e04c9badfd9cbc6b00030b0b089810b2ae4f8"
      }
    }
  ],
  "predicateType": "https://usemakoto.dev/predicate/v0.2/transform",
  "predicate": {
    "schemaVersion": "0.2",
    "event": {
      "id": "urn:uuid:4b0d2e11-8a37-4c62-b5d9-2f7e0a9c3b44",
      "occurredAt": "2026-09-16T16:00:00Z"
    },
    "operation": {
      "type": "urn:makoto:example:operation:normalize",
      "name": "Normalize customer records",
      "tool": {
        "name": "makoto-example-normalizer",
        "version": "1.2.0",
        "uri": "git+https://github.com/makoto-project/makoto.git"
      },
      "parametersDigest": {
        "sha256": "c463deba9e6da45408de8cc72cb352026f0f527d9fbd1f6d33f45266862002b2"
      }
    },
    "profiles": [],
    "extensions": {
      "urn:makoto:example:execution": {
        "invocationId": "transform-20260916-160000-4b0d2e",
        "startedAt": "2026-09-16T16:00:00Z",
        "finishedAt": "2026-09-16T16:00:01Z",
        "recordsIn": 3,
        "recordsOut": 3,
        "recordsDropped": 0,
        "deterministic": true
      }
    },
    "inputs": [
      {
        "name": "raw",
        "digest": {
          "sha256": "4e90181e7f75ccdf0e4d81c83d281f53806bed241787cdc751e22b2d4cd19919"
        },
        "provenance": {
          "statementDigest": {
            "sha256": "014b4252977a17c8f2423210fc4c123a464efcc36eefa70e4203c3adee1a3b11"
          },
          "subjectName": "customers.raw.json"
        }
      }
    ]
  }
}
predicate.operation.type
What kind of step this was. Receiver policies authorize signers per operation type, so a key trusted to normalize is not automatically trusted to anonymize.
predicate.operation.tool
Name, version, and repository of the code that ran, for reproducibility.
predicate.operation.parametersDigest
A hash of the configuration, not the configuration itself. The parameters can be published, withheld, or held internally without changing what this statement commits to.
predicate.inputs[].digest
The bytes this step consumed. A receiver compares this against the predecessor's subject digest; a mismatch means something was substituted between steps.
predicate.inputs[].provenance.statementDigest
The predecessor statement's identity, and therefore the edge in the graph. Because it is the hash of the whole previous statement, the edge cannot be rewired without re-signing everything downstream.
predicate.inputs[].provenance.subjectName
Which subject of that predecessor is meant, for statements carrying more than one.

9ef9051c4f0229f9238cfc8541e76d9f2517ddc69a6e3ccf8e69604899527a55 is this statement's digest; it names 014b4252977a17c8f2423210fc4c123a464efcc36eefa70e4203c3adee1a3b11 as its predecessor. A second transformation, public-transform-example.json, names this one in turn. The walkthrough follows all three in order.

Stream window

predicate https://usemakoto.dev/predicate/v0.2/origin

Streaming systems cannot attest to a stream, because a stream has no final bytes. They attest to bounded windows of it. Here one tumbling minute of sensor readings is serialized to newline-delimited JSON and treated as an artifact like any other.

Read this before reading the document. Makoto defines exactly two predicate types, origin and transformation. There is no normative stream-window predicate. The window boundaries, the partition list, and the Merkle root below are producer claims carried in extensions. A receiver cannot verify any of them from this statement.

What a receiver does verify is the subject digest: c4c2f3be19a1f816d5fe22a69b47a221ee84a29e6b3066d2b71241b8ddd14c64, the SHA-256 of the serialized window file. The Merkle root is reproducible from that same file, because the document records the exact leaf, internal-node, and odd-node rules used to build it — but reproducing a value is not the same as a signature covering it.

{
  "_type": "https://in-toto.io/Statement/v1",
  "subject": [
    {
      "name": "iot-sensors.window-2026-09-16T10-00.jsonl",
      "digest": {
        "sha256": "c4c2f3be19a1f816d5fe22a69b47a221ee84a29e6b3066d2b71241b8ddd14c64"
      }
    }
  ],
  "predicateType": "https://usemakoto.dev/predicate/v0.2/origin",
  "predicate": {
    "schemaVersion": "0.2",
    "event": {
      "id": "urn:uuid:2e8f4a15-7c03-4d9e-9b16-5a0d3e6f8c27",
      "occurredAt": "2026-09-16T10:01:00Z"
    },
    "source": {
      "kind": "urn:makoto:example:source:mqtt-stream",
      "uri": "mqtt://sensors.factory.example.com:1883",
      "name": "Factory floor section A sensor stream",
      "mediaType": "application/x-ndjson",
      "retrievedAt": "2026-09-16T10:01:00Z",
      "version": "window-2026-09-16T10:00"
    },
    "profiles": [],
    "extensions": {
      "urn:makoto:example:window": {
        "note": "Not verifiable from this statement. Makoto v0.2 defines no stream-window predicate; these are producer claims about how the serialized window above was assembled. The subject sha256 is the only binding a v0.2 receiver checks.",
        "streamId": "iot_sensors",
        "partitions": [
          "sensors/temp/readings",
          "sensors/pressure/readings",
          "sensors/vibration/readings"
        ],
        "type": "tumbling",
        "duration": "PT1M",
        "windowStart": "2026-09-16T10:00:00Z",
        "windowEnd": "2026-09-16T10:01:00Z",
        "watermark": "2026-09-16T09:59:55Z",
        "allowedLateness": "PT5S",
        "recordCount": 12
      },
      "urn:makoto:example:merkle": {
        "note": "Reproducible from the window file, but not covered by any v0.2 verification rule.",
        "algorithm": "sha256",
        "leafInput": "one NDJSON line, newline excluded",
        "internalNode": "sha256(leftDigest || rightDigest) over raw 32-byte digests",
        "oddNodeRule": "promoted unchanged to the next level",
        "leafCount": 12,
        "treeHeight": 4,
        "root": "75a86974b9b00f412452a096bc6f77afcc32d13760f2e85ebb107aeb1c49cde4"
      },
      "urn:makoto:example:collection": {
        "collector": "urn:makoto:example:collector:factory-edge-01",
        "collectorVersion": "1.2.0",
        "location": "factory-floor-section-a",
        "lateRecords": 0,
        "droppedRecords": 0
      }
    }
  }
}
subject[].digest.sha256
The serialized window, hashed whole. This is the binding a receiver checks, and the reason the window can be attested at all today.
predicate.source.kind
Marks the source as a stream rather than a file, so a policy can treat window statements differently.
extensions.…window
Boundaries, watermark, allowed lateness, and record count. Unverified producer claims.
extensions.…merkle
Algorithm, leaf rule, internal-node rule, odd-node rule, and root. Recomputable from the window file; not covered by core verification.

The window itself: stream-window-example.jsonl, 12 records. Its Merkle root is 75a86974b9b00f412452a096bc6f77afcc32d13760f2e85ebb107aeb1c49cde4, and the statement digest is c4a818eeacbaa87e4a8d1c209487d6dfb930003c90e86d0aa7bdc44fccffc046. Download the document.

Bundle index and dataset manifest

schemas bundle.schema.json · dataset-manifest.schema.json

The earlier format collected a dataset's whole history into one aggregate document, a data bill of materials. The current specification splits that job. The bundle index maps statement digests to transport paths and claims no authority of its own; a separately signed handoff is what commits to the graph.

{
  "version": "0.2",
  "manifest": "manifest.dsse.json",
  "attestations": [
    {
      "statementDigest": {
        "sha256": "014b4252977a17c8f2423210fc4c123a464efcc36eefa70e4203c3adee1a3b11"
      },
      "path": "attestations/014b4252977a17c8f2423210fc4c123a464efcc36eefa70e4203c3adee1a3b11.dsse.json"
    },
    {
      "statementDigest": {
        "sha256": "9ef9051c4f0229f9238cfc8541e76d9f2517ddc69a6e3ccf8e69604899527a55"
      },
      "path": "attestations/9ef9051c4f0229f9238cfc8541e76d9f2517ddc69a6e3ccf8e69604899527a55.dsse.json"
    },
    {
      "statementDigest": {
        "sha256": "6c8f8061f0173e9ee0011fdb5ec604dde4f93597d29d0b8f5098ea05683ea33d"
      },
      "path": "attestations/6c8f8061f0173e9ee0011fdb5ec604dde4f93597d29d0b8f5098ea05683ea33d.dsse.json"
    }
  ],
  "artifacts": [
    {
      "statementDigest": {
        "sha256": "6c8f8061f0173e9ee0011fdb5ec604dde4f93597d29d0b8f5098ea05683ea33d"
      },
      "subjectName": "customers.public.json",
      "digest": {
        "sha256": "ecd821dbe6f4204c4aad64878643d7954cb8c94abbf8d0b28f9c2bfc272f781e"
      },
      "path": "artifacts/final/ecd821dbe6f4204c4aad64878643d7954cb8c94abbf8d0b28f9c2bfc272f781e.bin"
    }
  ],
  "schemaCatalog": "schemas/catalog.json"
}

An index deliberately holds no signature and no trust claim. Deleting an entry from it does not hide a step, because the statements themselves name their predecessors — the missing one shows up as a predecessor that cannot be resolved.

The set of files travelling alongside can be listed with digests and sizes:

{
  "version": "0.2",
  "entries": [
    {
      "name": "data/customers.raw.json",
      "digest": {
        "sha256": "4e90181e7f75ccdf0e4d81c83d281f53806bed241787cdc751e22b2d4cd19919"
      },
      "size": 311,
      "mediaType": "application/json"
    },
    {
      "name": "data/customers.normalized.json",
      "digest": {
        "sha256": "421e19cf002aa25cf6f2f9ed788e04c9badfd9cbc6b00030b0b089810b2ae4f8"
      },
      "size": 304,
      "mediaType": "application/json"
    },
    {
      "name": "data/customers.public.json",
      "digest": {
        "sha256": "ecd821dbe6f4204c4aad64878643d7954cb8c94abbf8d0b28f9c2bfc272f781e"
      },
      "size": 291,
      "mediaType": "application/json"
    }
  ]
}

dbom-example.json · dataset-manifest-example.json · a signed handoff manifest

A corpus kept in Git as directory indexes, leaf manifests and shards uses these same documents. The sharded-corpus guide maps one onto them, carries per-shard licenses, and gates pull requests on the lineage record.

Signed envelope

schema envelope.schema.json · DSSE

The statements above are unsigned, which is why this page can only claim they are internally consistent. Signing wraps the exact payload bytes in a DSSE envelope. This one is real, taken from the runnable proof:

{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJwcmVkaWNhdGUiOnsiZXZlbnQiOnsiaWQiOiJ1cm46dXVpZDoxMTExMTExMS0xMTExLTQxMTEtODExMS0xMTExMTExMTExMTEiLCJvY2N1cnJlZEF0IjoiMjAyNi0wOS0xNlQxNjowMDowMFoifSwicHJvZmlsZXMiOltdLCJzY2hlbWFWZXJzaW9uIjoiMC4yIiwic291cmNlIjp7ImtpbmQiOiJ1cm46bWFrb3RvOmRlbW86c291cmNlOnN5bnRoZXRpYy1maWxlIiwidXJpIjoidXJuOm1ha290bzpkZW1vOnYwLjI6c291cmNlOmN1c3RvbWVycy1yYXcifX0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3VzZW1ha290by5kZXYvcHJlZGljYXRlL3YwLjIvb3JpZ2luIiwic3ViamVjdCI6W3siZGlnZXN0Ijp7InNoYTI1NiI6IjRlOTAxODFlN2Y3NWNjZGYwZTRkODFjODNkMjgxZjUzODA2YmVkMjQxNzg3Y2RjNzUxZTIyYjJkNGNkMTk5MTkifSwibmFtZSI6ImN1c3RvbWVycy5yYXcuanNvbiJ9XX0=","payloadType":"application/vnd.in-toto+json","signatures":[{"keyid":"sha256:fd110d301d2f077de1414b8f99f441b1403fab207b2052fbd2c065e4ee8e7dc2","sig":"t5z5qJzbSf8wAaW4voQA76rD6wcnO8XF+62NGJBzTY4ObO8btyJyFYSbzuJlvhIO0IBl/m8/NYXYbwopvfMZCQ=="}]}
payload
The statement, base64-encoded. Signers sign these exact bytes, so a verifier checks the signature without having to parse or re-serialize the JSON.
payloadType
Declares how to interpret the payload once decoded.
signatures[].keyid
Which key signed. Whether that key is allowed to sign this statement is a receiver policy decision, not a property of the envelope.
signatures[].sig
The signature itself. It establishes authenticity, and nothing else — not authorization, not freshness, not that the claims inside are true.

The statement digest hashes the decoded payload, not the envelope, so adding a second valid signature does not change the statement's identity in the graph. That file's name, 56b7be4394fe09c62ec7a3d5763cecc251e9696f267f35b2acc717b0d170a27a, is that digest.

Dataset study · SQuAD

Unknown is better than invented.

The retained SQuAD record is useful because it shows what a provenance tool must not do. The public dataset has a familiar source and a final artifact, but intermediate hashes were not published and no third party attested to bias, quality, or regulatory claims.

Those absences belong in the record. Makoto can distinguish signed evidence from reconstructed narrative; it cannot retroactively create proof that the source pipeline never emitted.

SQuAD is a good subject precisely because its provenance is unusually well documented for a public dataset. The source corpus is an English Wikipedia snapshot, the annotation was crowdsourced through Mechanical Turk, and the processing is described in the paper. Three distinct transformations, each with inputs and outputs that could have been hashed.

They were not. The record says so, in the places where a digest should be:

{
  "step": 1,
  "description": "English Wikipedia articles (October 2015 snapshot) were filtered to 536 articles covering a range of topics, then converted from raw MediaWiki markup to plain text.",
  "tool": "Wikipedia Extractor",
  "tool_version": null,
  "repository": null,
  "inputs": [
    {
      "name": "English Wikipedia dump",
      "uri": "https://dumps.wikimedia.org/enwiki/",
      "snapshot": "2015-10",
      "digest": "sha256:NOT_PUBLISHED",
      "provenance_gap": true,
      "note": "The exact dump date and file used are not documented in the SQuAD paper or dataset card."
    }
  ],
  "operations": [
    "Selected 536 Wikipedia articles across diverse topics",
    "Converted MediaWiki markup to plain text, stripping citations, tables, and infoboxes",
    "Segmented articles into paragraphs for use as reading passages"
  ],
  "output_digest": "sha256:NOT_PUBLISHED",
  "note": "Intermediate passage corpus was never published as a standalone artifact."
}

Two fields carry the whole point. digest reads sha256:NOT_PUBLISHED rather than a plausible-looking hex string, and provenance_gap is true. A generator that invented a digest here would produce a record that looks stronger and is worth less, because a consumer could no longer tell a verified step from a reconstructed one.

The same discipline applies at the top level and at the end:

{
  "signature": {
    "algorithm": "sha256",
    "digest": "sha256:COMPUTE_FROM_plain_text_train_validation_parquet_files",
    "signed_at": "2026-03-07T00:00:00Z",
    "signed_by": "https://github.com/rajpurkar.keys",
    "provenance_gap": true,
    "note": "No canonical artifact hash has been published by the dataset authors. The HuggingFace-hosted Parquet files would need to be hashed at a specific commit to produce a stable digest."
  },
  "attestations": [],
  "attestation_gaps": [
    {
      "type": "bias_audit",
      "note": "No third-party bias audit has been conducted or signed against a specific dataset version. Known bias: Wikipedia over-represents certain topics, demographics, and writing styles."
    },
    {
      "type": "regulatory_compliance",
      "note": "No attestation for EU AI Act, CRA, or equivalent regulatory compliance exists for this dataset."
    },
    {
      "type": "quality_certification",
      "note": "No independent quality certification exists. The paper reports human-level F1 of 86.8 on validation, but this is a model benchmark, not a dataset quality attestation."
    }
  ]
}

The dataset authors never published a canonical artifact hash, so the signature block records a gap instead of a number. The attestations array is empty: no third party has certified this dataset for bias, quality, or regulatory compliance. attestation_gaps then names the three specific certifications that are missing and why each one would matter — an absence stated precisely is actionable, where an absence left implicit is invisible.

In a world where those fields are populated — where an audit firm signs a bias finding against a specific digest, or a regulator attests to EU AI Act compliance — that signature travels with the data permanently, and a consumer can check it without contacting either party.

This record is in the earlier aggregate DBOM shape, not the current statement shape, and is kept as a dataset study rather than as a format example. The current equivalents are the documents above.

Inspect the SQuAD record · Use the dataset analysis prompt

Try it on a dataset you care about

The fastest way to find out what your own data cannot prove is to attempt the record and see where it goes blank.

Generate a draft record for a public dataset

Point any capable model at a dataset card and its paper using the dataset analysis prompt. It produces a draft record and, more usefully, a list of the provenance gaps it could not fill. Those gaps are the signal; the filled fields are the easy part.

curl -sO https://usemakoto.dev/prompts/dbom-generation-prompt.md
# then paste it, plus a dataset card URL, into the model of your choice

Compare what comes back against the SQuAD record. If your draft has fewer gaps than SQuAD's, be suspicious of it rather than pleased with it.

Reproduce the digests on this page

Artifact digests are plain file hashes. Statement digests are taken over the canonical serialization of the statement, so hash that rather than the pretty-printed file:

curl -sO https://usemakoto.dev/attestations/origin-example.json
shasum -a 256 origin-example.json   # the file as served, not the statement digest
import hashlib, json, urllib.request

BASE = "https://usemakoto.dev/attestations/"
for name in ("origin-example", "transform-example", "public-transform-example"):
    raw = urllib.request.urlopen(BASE + name + ".json").read()
    canonical = json.dumps(json.loads(raw), sort_keys=True, separators=(",", ":"))
    print(hashlib.sha256(canonical.encode()).hexdigest(), name)

Each printed digest should match the one the next statement in the chain names as its predecessor. The walkthrough shows that chain end to end.

Run the whole thing locally

The reference implementation builds the graph, signs it, hands it off, and verifies it as an independent receiver, then runs seven tampering cases that each produce a named denial. No credentials, no service, no Docker.

git clone https://github.com/makoto-project/makoto.git
cd makoto
uv sync --locked --dev
./scripts/demo.sh --acceptance

Read the annotated walkthrough of that run, or start from the tooling and SDK overview.

Add the case your team actually has

A useful example names the producer, recipient, bytes, transformation graph, trust policy, and failure that must close the gate.

Bring the handoff your data team struggles with. A good contribution can be a narrative, a schema profile, a failing fixture, a receiver policy, or a tested implementation.