Makoto
Menu
Guide · a sharded corpus kept in Git

Carry set, shard and row licenses.

Makoto has no license field, and a verifier cannot establish that a source was licensed or legally usable (section 8.2). What it can do is carry a license claim, require the claim's shape, and let a reader refuse a step that arrives without one. Whether the claim is true stays with the people who review it.

Proposed extension. The vocabulary on this page, https://corpus.example/makoto/license/v1, is an example and not part of Makoto. A corpus would publish its own under a namespace it controls, or several corpora could agree on one shared profile. The mechanisms that carry it (extensions, profiles and policy rules) are current Makoto.

The rule this page is about. Repeated from Refuse a step without it below.
{
  "authorizedKeyIds": [
    "sha256:21981d07157626519dc4de7c1c09043877f8e66afd3554169bf9496df8419f50"
  ],
  "id": "urn:makoto:example:rule:letters-ingest",
  "minimumSignatures": 1,
  "operationTypes": [
    "urn:makoto:example:operation:corpus-ingest"
  ],
  "predicateTypes": [
    "https://usemakoto.dev/predicate/v0.2/transform"
  ],
  "profileConstraints": [
    {
      "closureDigest": {
        "sha256": "fbac8a68cb1a9ad0347e74c104ee885ed607bb7b837a4e1f3372163afc73c35d"
      },
      "digest": {
        "sha256": "ce30589e17abff371e461b6fd75723b055fdddef6e865bcc5fabcf327f07b9ab"
      },
      "id": "https://corpus.example/makoto/license/v1/predicate.schema.json",
      "target": "predicate"
    }
  ]
}

Three levels, three answers

LevelWhere the corpus keeps itWhat Makoto does with it
Setlicense and license_evidence in letters.yaml.Proposed extension. The default in a claim on the ingest statement, validated by a profile.
Shardlicense, attribution and license_evidence on one shard's entry in letters.yaml.Proposed extension. An overrides entry keyed by the shard manifest's subjectName and the shard's entryName.
Rowlicense, attribution, source and maintainer in every row.Nothing in this record. The shard's digest commits to every row, so a changed row changes the digest. These shards are dataset-manifest entries, and profiles apply to statement subjects, so no profile reads their rows.

The claim

The ingest writes the claim into its statement's extensions (section 11.2). Extension keys are absolute HTTPS URIs or URNs, and the core schemas accept any such key. The claim sits on the ingest rather than the origin because per-shard overrides need shard names, which exist only once the ingest has run. This is the file passed to makoto attest transform --extensions:

{
  "https://corpus.example/makoto/license/v1": {
    "default": {
      "evidence": "https://archive.example/harbour-letters/rights",
      "license": "CC0-1.0"
    },
    "overrides": [
      {
        "attribution": "Transcribed by the Harbour Letters Society",
        "entryName": "shards/letters-00001.ndjson",
        "evidence": "https://archive.example/harbour-letters/society-terms",
        "license": "CC-BY-4.0",
        "subjectName": "letters.shards.json"
      }
    ]
  }
}

Shard letters-00001 is the only override; letters-00000 falls under the default. The manifest's own license fields and this claim are covered by the same signature, because the manifest's bytes are a subject of the same statement. Nothing checks that the two agree.

Require its shape with a profile

A profile is a JSON Schema pinned by digest (section 12.1). This one targets the predicate, so it validates the claim inside the signed statement:

{
  "$schema": "https://usemakoto.dev/schema/v0.2/profile-dialect.schema.json",
  "$id": "https://corpus.example/makoto/license/v1/predicate.schema.json",
  "type": "object",
  "required": [
    "extensions"
  ],
  "properties": {
    "extensions": {
      "type": "object",
      "required": [
        "https://corpus.example/makoto/license/v1"
      ],
      "properties": {
        "https://corpus.example/makoto/license/v1": {
          "type": "object",
          "required": [
            "default",
            "overrides"
          ],
          "properties": {
            "default": {
              "type": "object",
              "required": [
                "license",
                "evidence"
              ],
              "properties": {
                "license": {
                  "type": "string",
                  "minLength": 1
                },
                "attribution": {
                  "type": "string",
                  "minLength": 1
                },
                "evidence": {
                  "type": "string",
                  "minLength": 1
                }
              }
            },
            "overrides": {
              "type": "array",
              "items": {
                "type": "object",
                "required": [
                  "subjectName",
                  "entryName",
                  "license",
                  "evidence"
                ],
                "properties": {
                  "subjectName": {
                    "type": "string",
                    "minLength": 1
                  },
                  "entryName": {
                    "type": "string",
                    "minLength": 1
                  },
                  "license": {
                    "type": "string",
                    "minLength": 1
                  },
                  "attribution": {
                    "type": "string",
                    "minLength": 1
                  },
                  "evidence": {
                    "type": "string",
                    "minLength": 1
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

makoto profile create turns the schema into a reference carrying its digest and closure digest. The ingest lists that reference in profiles, and the reference producer refuses to write the statement when the claim does not validate.

{"closureDigest":{"sha256":"fbac8a68cb1a9ad0347e74c104ee885ed607bb7b837a4e1f3372163afc73c35d"},"critical":true,"digest":{"sha256":"ce30589e17abff371e461b6fd75723b055fdddef6e865bcc5fabcf327f07b9ab"},"id":"https://corpus.example/makoto/license/v1/predicate.schema.json","resources":[],"target":"predicate"}

Refuse a step without it

The repository's policy authorizes corpus-ingest transforms only when the statement carries this exact profile, through profileConstraints (section 14):

{
  "authorizedKeyIds": [
    "sha256:21981d07157626519dc4de7c1c09043877f8e66afd3554169bf9496df8419f50"
  ],
  "id": "urn:makoto:example:rule:letters-ingest",
  "minimumSignatures": 1,
  "operationTypes": [
    "urn:makoto:example:operation:corpus-ingest"
  ],
  "predicateTypes": [
    "https://usemakoto.dev/predicate/v0.2/transform"
  ],
  "profileConstraints": [
    {
      "closureDigest": {
        "sha256": "fbac8a68cb1a9ad0347e74c104ee885ed607bb7b837a4e1f3372163afc73c35d"
      },
      "digest": {
        "sha256": "ce30589e17abff371e461b6fd75723b055fdddef6e865bcc5fabcf327f07b9ab"
      },
      "id": "https://corpus.example/makoto/license/v1/predicate.schema.json",
      "target": "predicate"
    }
  ]
}

Here is the same ingest, signed by the same key, without the claim. The signature is valid, but no rule authorizes the statement, so the graph behind the handoff cannot be completed:

$ makoto verify bundle text/letters/makoto \
  --policy lineage/policy.json \
  --schema-catalog lineage/catalog.json \
  --expected-artifact ../expected-letters.json
PASS        load-safely
PASS        parse-strictly
PASS        index-payloads
PASS        core-schemas
PASS        signatures
FAIL        authorization-thresholds
PASS        metadata-profiles
FAIL        authorization
PASS        graph-dependency-artifacts
SKIPPED     graph
SKIPPED     roots-and-heads
FAIL        completeness-anchor
PASS        freshness-anchors
SKIPPED     artifact-bytes
SKIPPED     artifact-profiles
PASS        decision
DENY        decision
manifest sha256:d9deee4b8ca966a6b1207111f0d40fde78b8b2a7bb7c1fd2aeb8e2758572f8f5
E_SIGNER_UNAUTHORIZED: no matching policy rule met its signature threshold
E_MANIFEST_SET: final artifact is not an exact terminal subject of its declared head
E_MANIFEST_SET: historical artifact mapping is not an exact manifest-listed signed subject
E_MANIFEST_SET: signed manifest sets do not equal the verified graph and bundle index

E_SIGNER_UNAUTHORIZED is the cause. The E_MANIFEST_SET lines follow from it: the unauthorized statement drops out of the graph, which then no longer matches what the handoff names.

What this establishes

It establishes that each ingest statement in a record that verifies was signed by a key the reader trusts for ingests, carries a license claim of the shape the profile requires, and cannot change without its signature failing.

It does not establish that the claim is legally correct. The evidence URL points at the terms a reviewer should read, and the reader's own review decides. The profile checks shape only: it wants non-empty strings, not valid SPDX expressions, and nothing resolves the claim's subjectName and entryName against the record the way the verifier resolves inputs[].provenance.entryName. A stricter profile, or a reviewer, would have to.

Rows need a different shape. The verifier can validate every line of an NDJSON subject against an artifact profile (section 12.4). A corpus that named each JSON or NDJSON shard as its own subject, instead of listing it in a dataset manifest, could therefore require a license on every row. Parquet shards cannot be checked this way; the verifier hashes and sizes them and reads nothing inside.

Next: maintain the corpus through Git.

Sign-off, pull requests, and a check that refuses a manifest edited without its lineage record.