A signature is one check, not the verdict.
A receiver should be able to verify the evidence without access to the producer’s platform: parse the resources, authenticate each signed claim, decide whether each signer was authorized, reconstruct the source-to-handoff graph, resolve exact schemas, and hash the bytes that actually arrived.
uv run makoto verify bundle demos/end-to-end/generated/positive-bundle \
--policy demos/end-to-end/generated/receiver/policy.json \
--schema-catalog demos/end-to-end/generated/receiver/catalog.json \
--expected-manifest sha256:e24aae77ba1374162edff49aa0ab1d8dd7e38c333e134a2128ad1598fec99fb7 \
--expected-head sha256:99aee76db46a720405da6d0015d427c791b0d4e02367b9e2bc36ce82ae812bce \
--expected-artifact demos/end-to-end/generated/receiver/expected-artifact.json \
--evaluation-time 2026-09-16T16:00:00Z \
--jsonVerify from your language
All language interfaces →Each page below is a complete working path: install, validate structure against the hosted schemas, produce an attestation, verify a bundle, branch on a failed verdict, and read the report the verifier returns.
Shell and CI
Verify in a pipeline step with the reference CLI, jq, and a non-zero exit that stops the job.
Ingestion gates
Validate with jsonschema, drive the CLI through subprocess, and raise on any decision other than allow.
Node services
Compile hosted schemas with AJV 2020, spawn the verifier, and surface the primary error to the caller.
TypeScriptTyped report contract
Declared types for the decision, check statuses, diagnostic codes, and a narrowing guard over the parsed report.
GoServices and sidecars
Struct-decode the report, run the verifier with os/exec, and fail closed on a malformed document.
Structure only
jq, check-jsonschema, pydantic, AJV, and Go against the hosted Draft 2020-12 schemas.
What a real verdict checks
| Check | Question answered | What failure means |
|---|---|---|
| Structure | Do the statements, envelopes, profiles, policy, and handoff match their JSON Schemas? | The evidence is malformed or violates a required profile. |
| Cryptography | Do the DSSE signatures authenticate the exact payload bytes? | The metadata changed, the signature is invalid, or the key does not match. |
| Authorization | Does receiver-owned policy trust each key for this source, operation, profile, and handoff? | A mathematically valid signature was made by the wrong party. |
| Graph | Does every transformation point to the exact predecessor statement and input artifact? | A step is missing, rewired, duplicated, or disconnected from an origin. |
| Handoff | Does the signed manifest commit to the exact roots, heads, profiles, statements, and final artifacts? | The presented graph may be continuous but incomplete. |
| Freshness | Does an independent expected manifest, head, nonce, artifact, or age bound match? | An older valid handoff may have been replayed. |
| Data bytes | Do the bytes received by the consumer match the signed artifact digest? | The data changed after the claim was made. |
Run the reference verifier
Start with the deterministic handoff fixture. It produces one allowed bundle and seven denial cases, so the first experience includes the failure boundary as well as the happy path.
git clone https://github.com/makoto-project/makoto.git
cd makoto
uv sync --locked --dev
./scripts/demo.sh --acceptanceTo inspect the receiver command directly:
uv run makoto verify bundle demos/end-to-end/generated/positive-bundle \
--policy demos/end-to-end/generated/receiver/policy.json \
--schema-catalog demos/end-to-end/generated/receiver/catalog.json \
--expected-manifest sha256:e24aae77ba1374162edff49aa0ab1d8dd7e38c333e134a2128ad1598fec99fb7 \
--expected-head sha256:99aee76db46a720405da6d0015d427c791b0d4e02367b9e2bc36ce82ae812bce \
--expected-artifact demos/end-to-end/generated/receiver/expected-artifact.json \
--evaluation-time 2026-09-16T16:00:00Z \
--jsonExit status 0 is an allow and 1 is a deny, each with a full JSON report on stdout; 2 and 3 mean the tool could not run and no verdict exists. The expected manifest, head, and artifact are the receiver’s independent anchors, and this policy requires all three: drop them and the same bundle is denied at step 11. The report keeps schema, signature, authorization, profile, graph, completeness, freshness, and artifact checks separate. A single boolean would hide which trust boundary failed.
The fourteen ordered steps
Verification is a fixed sequence of fourteen steps carrying sixteen check identifiers, and the order is part of the contract. The codes below are the complete published diagnostic map for each step; W_ codes are warnings that never deny on their own. A later check reports skipped when an earlier one did not establish its prerequisite, so a report never implies more was proven than actually was. Every entry in checks[] carries an id, a status of pass, fail, indeterminate, not_checked, or skipped, and the prerequisiteChecks that caused a skip.
| Step | Check id | What it establishes | Diagnostic codes |
|---|---|---|---|
| 0 | policy load | Before any bundle is read, the policy itself is checked for rules that shadow one another. | W_POLICY_RULE_OVERLAP |
| 1 | load-safely | The bundle unpacks inside its own root, within resource limits. | E_BUNDLE_UNSAFE_PATH, E_HANDOFF_REQUIRED, E_RESOURCE_LIMIT |
| 2 | parse-strictly, core-schemas | Every resource is well-formed JSON with no duplicate keys, and each envelope carries the expected payload type. | E_BUNDLE_UNSAFE_PATH, E_CORE_SCHEMA, E_ENVELOPE_MALFORMED, E_HANDOFF_REQUIRED, E_JSON_DUPLICATE_KEY, E_JSON_INVALID, E_PAYLOAD_TYPE, E_RESOURCE_LIMIT |
| 3 | index-payloads | Each payload is addressed by its own digest, so later steps reference exact bytes. | E_JSON_DUPLICATE_KEY, E_JSON_INVALID, E_RESOURCE_LIMIT, E_STATEMENT_DIGEST |
| 4 | core-schemas | Statements, predicates, handoff, policy, and catalog match the pinned core schemas. | E_BUNDLE_UNSAFE_PATH, E_CATALOG_INVALID, E_CORE_SCHEMA, E_JSON_DUPLICATE_KEY, E_JSON_INVALID, E_PREDICATE_SEMANTICS_UNSUPPORTED, E_PROFILE_TARGET_MISSING, E_RESOURCE_LIMIT |
| 5 | signatures | Each DSSE signature authenticates the exact payload bytes that were signed. | E_RESOURCE_LIMIT, E_SIGNATURE_INVALID, W_SIGNATURE_UNKNOWN |
| 6 | authorization-thresholds | Signer counts and thresholds satisfy receiver policy. | E_RESOURCE_LIMIT, E_SIGNER_UNAUTHORIZED |
| 7 | metadata-profiles, authorization | Declared profiles resolve to pinned digests, and each key is authorized for this source, operation, and profile. | E_PROFILE_INVALID, E_PROFILE_UNRESOLVED, E_RESOURCE_LIMIT, W_PROFILE_INDETERMINATE, W_PROFILE_RESOURCE_LIMIT |
| 8 | graph-dependency-artifacts | The artifacts a transformation claims as inputs match their declared digests and manifests. | E_ARTIFACT_DIGEST, E_BUNDLE_UNSAFE_PATH, E_DATASET_MANIFEST_INVALID, E_DATASET_MANIFEST_REQUIRED, E_PROFILE_INVALID, E_PROFILE_UNRESOLVED, E_RESOURCE_LIMIT, W_ARTIFACT_VALIDATION_LIMIT, W_PROFILE_INDETERMINATE |
| 9 | graph | Every step binds a real predecessor and its exact subject, with no cycles or duplicated event identifiers. | E_EVENT_ID_DUPLICATE, E_GRAPH_CYCLE, E_INPUT_DIGEST, E_PREDECESSOR_MISSING, E_PREDECESSOR_SUBJECT, E_RESOURCE_LIMIT |
| 10 | roots-and-heads | Each chain terminates at a valid origin. | E_RESOURCE_LIMIT, E_ROOT_INVALID |
| 11 | completeness-anchor, freshness-anchors | The signed manifest set is complete, and the receiver’s independent expectations for manifest, heads, artifacts, recipient, nonce, and age hold. | E_EXPECTED_ARTIFACT, E_EXPECTED_HEAD, E_EXPECTED_MANIFEST, E_FRESHNESS_REQUIRED, E_HANDOFF_NONCE, E_HANDOFF_RECIPIENT, E_HANDOFF_STALE, E_MANIFEST_SET, E_REQUIRED_PROFILE_MISSING, E_RESOURCE_LIMIT, W_FRESHNESS_NOT_CHECKED |
| 12 | artifact-bytes | The bytes on disk hash to the signed artifact digest at the declared size. | E_ARTIFACT_DIGEST, E_ARTIFACT_MISSING, E_ARTIFACT_SIZE, E_BUNDLE_UNSAFE_PATH, E_DATASET_MANIFEST_INVALID, E_RESOURCE_LIMIT, W_HISTORICAL_ARTIFACT_NOT_CHECKED |
| 13 | artifact-profiles | The structured artifact satisfies the content rules its profile declares. | E_ARTIFACT_FORMAT, E_PROFILE_INVALID, E_PROFILE_UNRESOLVED, E_REQUIRED_PROFILE_MISSING, E_RESOURCE_LIMIT, W_ARTIFACT_UNPROFILED, W_ARTIFACT_VALIDATION_LIMIT, W_PROFILE_INDETERMINATE |
| 14 | decision | The single allow, deny, or indeterminate outcome derived from everything above. | E_RESOURCE_LIMIT |
Why the order is published. A receiver that reruns these checks in a different order can reach a different answer from the same bundle. The sequence, the skip semantics, and the diagnostic codes are the interoperability surface, not an implementation detail.
Reading the report
Every run emits one document against the verification-report schema. Read decision first, then primaryError to learn which boundary failed, then checks[] to see what was actually established.
{
"reportVersion": "0.2",
"decision": "deny",
"reportTruncated": false,
"primaryError": "E_ARTIFACT_DIGEST",
"bundleId": "urn:uuid:55555555-5555-4555-8555-555555555555",
"evaluationTime": "2026-09-16T16:00:00Z",
"policyDigest": {
"sha256": "614eb137303bcea78f25ded2e332a10649358484f9582984c1730ddc106ebfcd"
},
"policyDigestEncoding": "exact-input-bytes",
"coreCatalogDigest": {
"sha256": "b68cb2cae64cfcd2a5021a725505ccbaa80dfaa053322ce276af9c6650860304"
},
"manifestDigest": {
"sha256": "e24aae77ba1374162edff49aa0ab1d8dd7e38c333e134a2128ad1598fec99fb7"
},
"expectedManifestDigest": {
"sha256": "e24aae77ba1374162edff49aa0ab1d8dd7e38c333e134a2128ad1598fec99fb7"
},
"summary": {
"artifactsChecked": 0,
"artifactsDeclared": 1,
"heads": 1,
"historicalMaterialsChecked": 0,
"historicalMaterialsDeclared": 0,
"manifestSignaturesAuthorized": 1,
"manifestSignaturesRequired": 1,
"manifestSignaturesValid": 1,
"profilesDeclared": 1,
"profilesValidated": 1,
"quarantinedStatementsTotal": 0,
"roots": 1,
"signaturesChecked": 4,
"signaturesTotal": 4,
"signaturesValid": 4,
"statementsAuthorized": 3,
"statementsReachable": 3,
"statementsTotal": 3,
"statementsValid": 3
},
"checks": [
{ "id": "load-safely", "status": "pass", "prerequisiteChecks": [] },
{ "id": "parse-strictly", "status": "pass", "prerequisiteChecks": [] },
{ "id": "index-payloads", "status": "pass", "prerequisiteChecks": [] },
{ "id": "core-schemas", "status": "pass", "prerequisiteChecks": [] },
{ "id": "signatures", "status": "pass", "prerequisiteChecks": [] },
{ "id": "authorization-thresholds", "status": "pass", "prerequisiteChecks": [] },
{ "id": "metadata-profiles", "status": "pass", "prerequisiteChecks": [] },
{ "id": "authorization", "status": "pass", "prerequisiteChecks": [] },
{ "id": "graph-dependency-artifacts", "status": "pass", "prerequisiteChecks": [] },
{ "id": "graph", "status": "pass", "prerequisiteChecks": [] },
{ "id": "roots-and-heads", "status": "pass", "prerequisiteChecks": [] },
{ "id": "completeness-anchor", "status": "pass", "prerequisiteChecks": [] },
{ "id": "freshness-anchors", "status": "pass", "prerequisiteChecks": [] },
{ "id": "artifact-bytes", "status": "fail", "prerequisiteChecks": [] },
{ "id": "artifact-profiles", "status": "pass", "prerequisiteChecks": [] },
{ "id": "decision", "status": "pass", "prerequisiteChecks": [] }
],
"errors": [
{
"causedByCheck": "artifact-bytes",
"code": "E_ARTIFACT_DIGEST",
"context": {
"artifactDigest": {
"sha256": "ecd821dbe6f4204c4aad64878643d7954cb8c94abbf8d0b28f9c2bfc272f781e"
},
"head": {
"sha256": "99aee76db46a720405da6d0015d427c791b0d4e02367b9e2bc36ce82ae812bce"
},
"subjectName": "customers.public.json"
},
"message": "final artifact bytes differ from the signed digest",
"step": 12
}
],
"warnings": [],
"tool": {
"name": "makoto",
"version": "0.3.0a0"
}
}This is a real report. It is the reference verifier’s output for the fixture bundle whose delivered data was changed after signing, trimmed only by omitting the per-statement, per-profile, and per-artifact arrays. Signatures, authorization, and the graph all pass; artifact-bytes alone fails, at step 12. The complete report is published with the walkthrough.
- decisionallow, deny, or indeterminateTreat anything other than
allowas a refusal.indeterminatemeans the verifier could not establish the answer, which is not the same as proving the evidence good. - primaryErrorThe one code that explains the outcomeRoute on this before reading the array. It is
nullon an allow. - checksWhat was actually establishedA
skippedstatus with a non-emptyprerequisiteChecksis the verifier saying it did not test this, rather than that it passed. - errorsOrdered diagnosticsEach carries
code,step,causedByCheck, a humanmessage, and a typedcontext. - warningsNon-fatal gapsCodes such as
W_FRESHNESS_NOT_CHECKEDorW_ARTIFACT_UNPROFILEDmark evidence the run did not cover. An allow with warnings is a narrower claim than an allow without them. - reportTruncatedCompleteness of the document itselfWhen true, the arrays were cut at a resource limit. Do not infer absence of a finding from a truncated report.
Use hosted schemas for structural validation
The hosted schemas are ordinary Draft 2020-12 JSON Schemas. Any conforming validator can check an individual resource. This is useful at editors, CI gates, and ingestion boundaries, but it does not validate signatures, signer authorization, graph completeness, freshness, or data bytes.
uvx --from check-jsonschema check-jsonschema \
--schemafile https://usemakoto.dev/schema/v0.2/trust-policy.schema.json \
policy.jsonThe hosted catalog publishes every core schema and its SHA-256 digest. Private organizational profiles can use their own URI and remain unpublished; the signed profile reference pins the exact root and closure digests so a receiver can resolve them from an authenticated local catalog.
See every external validator, command by command →
Content rules remain separate from integrity
A digest can prove that invisible Unicode or another dangerous string has not changed. It cannot make that string safe. Put normalization, control-character, field, or bounded-pattern rules in an organizational profile and require the receiver to validate the actual structured artifact against that profile.
What verification still cannot prove
Passing every check proves that exact bytes match authorized signed claims and the receiver’s independent expectations. It does not prove that a source told the truth, that a claimed transformation actually ran, that the data is safe or high quality, or that signed metadata is confidential. Those are separate controls and evidence.
Match the report contract, not just the JSON shape.
A useful implementation must preserve the distinctions between authenticity and authorization, continuity and completeness, and completeness and freshness. Start from the public schemas, conformance fixtures, denial reports, and reference implementation.