The Showboat Receipt

A signed record of what an agent built.

takt.receipt/v1 · Status: Draft · 22 August 2026, amended 25 and 28 August, 5, 8, 9 and 24 September


Status of this document

This is a draft. It documents the receipt format as TAKT writes it today, verified against the source rather than against any design document. It is published so that the format can be read, criticised and implemented by people who do not work on TAKT.

Nothing here describes intended behaviour. Where the implementation is thinner than the design, this document says so, in §6.

The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as described in RFC 2119.


1. What this is for

When an AI agent writes code, someone eventually needs to answer a question about it: what was built, by what, and when. Today that question is usually answered by a person's memory of a chat window.

A Showboat Receipt is a small JSON file written after every build. It records what changed, which model produced it, what the tests said, and which key signed the record. It is signed with Ed25519, so it cannot be edited afterwards without the signature failing.

The receipt is not a security control. It stops nothing. It is a record, and its only claim is that the record has not been altered since it was made. What it cannot claim is that the record was true when it was made. See §6, which opens with that limit because every other one sits underneath it.

That distinction is the whole design. Preventing an agent from doing something it should not is hard and gets harder. Making a tamper-evident note of what it did is easy and stays easy.


2. Where a receipt lives

A receipt MUST be written to:

<project-root>/.takt/receipts/<id>.json

One file per build. Receipts MUST NOT be modified after writing.


3. The document

3.1 Core fields

A receipt minted by a current version contains all twelve of these. Older receipts contain fewer, because fields have been added over time. See §4.3, which is the most important section in this document for anyone writing a consumer.

FieldTypeWhat it holds
schemastringFormat identifier. takt.receipt/v1 for this version.
idstringUnique identifier. receipt_ followed by a UTC timestamp as YYYYMMDDTHHMMSS, then an underscore and a short hash. Example: receipt_20990101T000000_EXAMPLE0.
blueprint_idstringIdentifier of the plan this build came from.
timestampstringWhen the receipt was minted, as RFC 3339 with microsecond precision.
files_changedarray of stringPaths the build wrote to.
test_resultsobjectSee §3.3.
git_commitstringThe commit this build produced, as a full 40-character SHA.
model_providerstringWhich company's model ran the build.
model_idstringWhich model.
execution_metricsobjectSee §3.4.
verification_depthstringHow thoroughly the build was checked. Always the literal "stub" today. See §6.3.
signaturestringEd25519 signature over the rest of the document, base64, 88 characters. See §4.

UPDATE, 24 September 2026. files_changed has changed meaning. Until 15 September 2026 it listed every path the build wrote to. Since then it lists only files whose content differed from what was on disk: each file the build returns is compared byte for byte before anything is written, and an identical file is neither written nor listed.

A receipt does not mark which meaning applies. Its mint date is the only guide, and §4.3 explains why that is not enough. Nor is files_changed a complete list of what the named commit contains: see §6.10.

3.2 Continuity fields

These are deliberately not called provenance fields, and the distinction is worth stating rather than leaving a reader to catch.

In supply-chain security, provenance is a term of art with checkable requirements attached to it. SLSA's build levels require that builds run on a hosted platform which generates and signs the provenance, and that the secret used to sign it is not reachable by the build steps themselves. In practice that means a signing key held by the platform, not by the thing being built.

TAKT meets neither requirement. It signs locally, with a key it minted itself, on the same machine as the thing being attested, using a key that machine can reach. That is not provenance in the sense the field uses the word, and this document will not call it that.

What these fields do establish is continuity: this receipt came from the same signer as the ones before it, and none of them has been altered since signing. Combined with the plan binding in §6.1, that is a narrower claim than provenance, and one of the few in this space that holds without any external trust anchor.

It does not establish who the signer is, or that the signing environment was honest. Those are different claims and this format does not make them.

Added in v1. Each is optional for a specific compatibility reason, not because it is unimportant: receipts written under v0 and still on disk do not contain them, and making them required would render every existing receipt unreadable.

A producer implementing v1 SHOULD write all three. A consumer MUST accept their absence and MUST NOT treat absence as evidence of tampering.

These fields appear as explicit JSON null when unset, not omitted. A v1 receipt on disk carries "blueprint_hash": null where no hash was computed. Both forms occur: absent on v0 receipts, null on v1 receipts where the value was not available. See §4.1. This distinction is signed, and getting it wrong breaks verification.

FieldTypeWhat it holds
signer_fingerprintstring or nullFirst 16 hex characters of the SHA-256 of the raw 32-byte public key. Identifies which key signed this receipt. Confirmed on disk as exactly 16 lowercase hex characters.
blueprint_hashstring or nullSHA-256 of the canonicalised plan, as recorded at mint time. Ties the receipt to a plan by content rather than to a name that could be reused. It does not establish that the recorded hash was taken from the plan actually executed: see §6.1. Observed carrying a real value on builds, and null on synthetic receipts where no plan was hashed.
prev_receipt_hashstring or nullSHA-256 of the previous receipt file as written to disk, not of its canonical form. null on the first receipt in a project. Chains receipts so that a deletion in the middle becomes visible.

Read §6 before relying on the second and third of these. They are recorded and signed. Nothing currently checks them.

3.3 test_results

FieldTypeWhat it holds
statusstringOne of passed, failed, not_run.
passedintegerNumber of passing tests.
failedintegerNumber of failing tests.
outputstringRaw output from the test run.

passed and not_run have both been seen on receipts. failed has not, though it is the documented third value.

not_run means no test runner was configured. It is a third state on purpose: a build with no tests is not a build whose tests failed. A consumer MUST NOT treat not_run as a failure, and MUST NOT treat it as a pass.

This is the same refusal as §5. Where a boolean would force an honest state into one of two lies, the format takes a third value instead. It appears twice in this document because it is a rule, not a coincidence.

UPDATE, 24 September 2026. The vocabulary has four values, and two of them changed meaning.

status is now one of passed, failed, not_run and undetermined. undetermined means TAKT tried to find out and could not. It is not not_run, where nobody asked.

Since 23 September 2026, for plain web pages, TAKT opens the page before and after the build and records the result in these same fields. So passed no longer means a test ran. On such a receipt it means TAKT opened the page after the build and no script error occurred. It proves the page loaded, not that it works. A page check's output begins Page check:, which is how a reader tells the two apart.

failed changed meaning too. Before 23 September a failed result stopped the build, so it never reached a receipt. A page check writes failed on a build that was saved: the page already had a script error before the build, or it had one after and could not be opened before, so the cause is unknown. A build that broke a page which had loaded cleanly is undone and gets no receipt at all.

undetermined is written when the page could not be opened after the build. It has never occurred on a receipt.

The sentence above that failed has not been seen is no longer true: four receipts from 23 September carry it, all page checks on saved builds. And the passed values seen before 23 September are weaker evidence than they looked. See §11.

3.4 execution_metrics

FieldTypeWhat it holds
retry_countintegerWhether a retry context was present.
escalatedbooleanAlways false today. See §6.3.
call_contextstring"interactive" when a person triggered and watched the build, "autonomous" otherwise.

call_context is inside the signature, so the value cannot be altered after minting. It cannot establish that the value was true when written. A compromised environment writes interactive for an unattended build and signs it, and the signature verifies.

A consumer MUST NOT read interactive as evidence that a person supervised the build. See §6.6, and the limit that opens §6.

What the field is for. It exists so that TAKT can tell work a person triggered from work that ran autonomously, for its own billing and routing. That is TAKT reading its own field, on its own machine, for its own purposes, and nothing in §6 affects it.

It is not evidence for a third party, and it is stated here because a specification that leaves the intended consumer unnamed invites the next reader to derive the over-claim from scratch.


3.5 plan_in_commit

Added 9 September 2026. Optional, and absent from every receipt minted before that date.

What it records. At the moment the receipt is minted, whether the plan file was inside the commit the receipt names. It is computed before signing, so it sits inside the signature and cannot be altered without breaking verification.

Five shapes on disk, and they are not uniform. Three states serialise as a bare string and two as an object. A consumer that switches on a string will fail on two of the five.

StateOn disk
The plan is in the commit"in_commit"
The plan is not in the commit"not_in_commit"
There is no repository"no_repo"
The named commit does not exist{"commit_missing":{"commit":"<the id as passed to the mint>"}}
The git operation failed{"git_error":{"reason":"<the underlying error string>"}}

The last three are one family: the question could not be asked. no_repo carries no reason object because the tag is the reason. The other two carry the detail under commit and reason respectively.

not_in_commit is an answer. The three below it are the absence of one. TAKT looked and the plan was not there, versus TAKT could not look. A consumer MUST NOT treat them alike, and MUST NOT read a could-not-tell state as either an answer or a failure.

This is the third place in this document where a boolean was refused for the same reason, after §5's verification outcomes and §3.3's not_run. It is a rule rather than a coincidence.

Why the field exists at all. A verdict computed when someone opens a receipt is TAKT's opinion, recomputed. Whether the plan was in the commit is a fact at build time, and facts at build time belong inside the signature, where a third party can check them without the machine that made them.

Absence is not a value. A receipt without this field predates it. Absence is distinguishable from all five shapes and MUST NOT be read as any of them, nor as a failure.

Nothing displays this field yet. See §6.9.

No receipt has ever carried this field. All 53 receipts on the machine where this was written predate it, and none has been minted since it landed. The five shapes above were established by reading the type and by minting one receipt in a commit_missing state and reading the written file. The test suite asserts the on-disk form of in_commit only.

So every statement in this section is read from source and confirmed by a single probe, and none of it is confirmed by a receipt in ordinary use. See §11.

UPDATE, 24 September 2026. The table above went stale two hours after it was published, and the statement that no receipt carries the field is false.

This section was published at 18:53 on 9 September 2026. At 21:12 the same evening the source changed two of the five shapes. Today's shapes, confirmed by running the serialisation tests:

StateOn disk
The plan is in the commit"in_commit"
The plan is not in the commit"not_in_commit"
There is no repository{"no_repo":{}}
The named commit does not exist{"commit_missing":{"commit":"<the id as passed to the mint>"}}
The git operation failed{"git_error":{"code":"<git's error code>","class":"<git's error class>"}}

The premise has inverted. The text above says three states are strings and two are objects. Now every answer is a bare string and every could-not-tell is an object, so JSON type alone separates the two families, before any tag is read. That is the property a consumer needs: whether TAKT looked and found an answer, or could not look.

no_repo became an empty object so that the could-not-tell family has one shape. git_error carries git's error code and class instead of its message, because the message embeds absolute file paths, and a receipt is signed and durable: a username inside one could not be removed later without breaking verification. The change landed five days before the first receipt carrying the field was minted, so no receipt has either old shape.

Receipts now carry the field. 48 receipts on the machine where this was written carry it: 27 in_commit and 21 not_in_commit. All 21 not_in_commit receipts come from one automated test run on 14 September, not from a build a person made. no_repo, commit_missing and git_error have never occurred on a real receipt. The test suite now asserts the on-disk form of all five shapes, not of in_commit only. See §11.


4. Canonicalisation and signing

This section is normative. An implementation that gets it wrong will fail to verify receipts that are perfectly sound.

4.1 Producing the signed bytes

  1. Serialise the receipt as a JSON object omitting the signature field entirely. Every other field, including any the implementation does not recognise, MUST be present.

    A field whose value is null MUST be retained, with its null. Null is a value here, not an absence. Several JSON libraries strip nulls by default, and doing so produces different bytes and a signature that fails with no indication of why. This is the most likely single cause of a working implementation failing against real receipts.

  2. Sort every object's keys, recursively, in byte order.

  3. Preserve array order. Recurse into array elements.

  4. Serialise compactly: no whitespace, no indentation, no trailing newline.

  5. Sign the resulting bytes with Ed25519.

  6. Place the signature in the signature field of the document that is written to disk.

The document written to disk MAY be pretty-printed. Formatting on disk has no bearing on verification, because the canonical form is reconstructed rather than read.

4.2 Verifying

  1. Read the receipt file as JSON.
  2. Remove the signature field. Retain its value.
  3. Canonicalise the remainder by the same procedure as §4.1.
  4. Resolve the public key. Where signer_fingerprint is present, it identifies which key.
  5. Verify the signature over the canonical bytes, using strict Ed25519 verification.

Both sides MUST use one canonicalisation implementation. Two implementations that must agree is a class of defect that surfaces long after the code that caused it has been forgotten.

4.3 Verifying is not reading, and they give different answers

This is the section most likely to save an implementer a bad afternoon.

Verification reconstructs the canonical form from the receipt's own contents, not from a fixed list of expected fields. So a receipt verifies whatever fields it carries. Receipts written before a field existed verify today, and receipts written now will verify when more fields are added later.

Reading a receipt is a different operation, and it fails where verification succeeds.

A verifier works on raw JSON and checks a signature over whatever is present, so it never notices that something is absent. A reader that expects a fixed set of fields does notice, and stops.

This is not hypothetical. On the machine where this document was written, 18 of 25 receipts on disk verify correctly and cannot be loaded by a reader expecting the current field set. They are the oldest ones.

Therefore:

The version string is not a reliable guide to the field set. This was confirmed by reading receipts off disk, and it is a defect in the format rather than a subtlety:

MintedschemaShape
30 June 2026takt.receipt/v09 fields
17 July 2026takt.receipt/v012 fields. execution_metrics has retry_count and escalated, no call_context
18 August 2026takt.receipt/v115 fields. execution_metrics gains call_context. Continuity fields present, values may be null
9 September 2026takt.receipt/v116 fields. Gains plan_in_commit. Same schema string as the row above

Two different field sets both call themselves v0, and the shape of a nested object changed as well. As of 9 September 2026 the same is true of v1: a sixteen-field receipt and a fifteen-field receipt both carry takt.receipt/v1.

That is this document's own published defect, repeated. It was recorded as open question 5 on 22 August and the format did the same thing again eighteen days later, which is worth stating plainly rather than noting quietly: a defect a specification publishes and does not fix will be repeated by the thing it describes. A consumer therefore cannot derive its expectations from schema alone and MUST tolerate absence field by field.

This should be fixed in the format rather than worked around by consumers. See §9.

UPDATE, 24 September 2026. Two fields have changed meaning under the same label, with no change in shape.

Since 15 September files_changed lists only files whose content changed, where before it listed every file written: see §3.1. Since 23 September test_results can record a page check, so passed and failed mean something different from what they meant before: see §3.3. Receipts on both sides of each date carry takt.receipt/v1, with the same fields of the same types.

This is question 5 again, and worse. A shape change can be detected: a consumer that tolerates absence field by field, as this section requires, copes with it. A meaning change cannot be detected at all. Nothing in the receipt says which meaning applies, and no tolerance recovers it. The only guide is the mint date, and a consumer can know the dates that matter only by reading this document.


5. Verification outcomes

Verification MUST return one of three values. It MUST NOT return a boolean.

OutcomeMeaning
verifiedThe signature was checked against the signer's key and is correct.
failedThe signature was checked and is wrong, or the receipt could not be read.
signer_not_heldThe signing key is not available here, so the signature was never checked.

This is the most important design decision in the format, and it is the one most likely to be discarded by an implementer who thinks two states are enough.

A receipt signed by a key you do not hold is neither pass nor fail. Nothing was checked. Reporting it as verified is false. Reporting it as failed accuses a build that may be perfectly sound, and a verifier that cries wolf gets ignored, which costs more than the accusation.

failed is the only outcome that says something is wrong. A consumer MUST NOT present signer_not_held as a warning, an error, or a degraded state.

UPDATE, 24 September 2026. Since 12 September a receipt whose signer_fingerprint is not exactly 16 lowercase hexadecimal characters is failed outright, before any key is looked up. The fingerprint chooses which key the signature is checked against, and it comes from the receipt itself, so without the rule a forged receipt could point it at a key of its own choosing outside the key store. The outcome is failed, not signer_not_held, because signer_not_held reads as made on another machine, which would be false reassurance about a forged receipt. A receipt with no fingerprint is unaffected.

This is a small application of the monotonic principle from the in-toto attestation framework: a policy should be written so that ignoring information can never turn a refusal into an approval. Absence of evidence is not evidence.


5A. A complete receipt

Every value below is synthetic and deliberately unusable. Dates are in 2099, hashes are runs of a single digit, identifiers say EXAMPLE. Nothing here corresponds to a real receipt, and none of it will verify against anything. Only the shapes are real.

{
  "schema": "takt.receipt/v1",
  "id": "receipt_20990101T000000_EXAMPLE0",
  "blueprint_id": "blueprint_2099-01-01_example-plan-do-not-use",
  "timestamp": "2099-01-01T00:00:00.000000+00:00",
  "files_changed": [
    "src/example.ts"
  ],
  "test_results": {
    "status": "not_run",
    "passed": 0,
    "failed": 0,
    "output": "No test runner configured"
  },
  "git_commit": "0000000000000000000000000000000000000000",
  "model_provider": "example-provider",
  "model_id": "example-model-1",
  "execution_metrics": {
    "retry_count": 0,
    "escalated": false,
    "call_context": "interactive"
  },
  "signer_fingerprint": "0000000000000000",
  "blueprint_hash": "0000000000000000000000000000000000000000000000000000000000000000",
  "prev_receipt_hash": null,
  "verification_depth": "stub",
  "signature": "EXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEE="
}

The canonical bytes signed for this receipt are the same object with signature removed, every key sorted recursively, and no whitespace. It begins:

{"blueprint_hash":"000000...","blueprint_id":"blueprint_2099-01-01_example-...

Two things to notice. blueprint_hash sorts first, ahead of blueprint_id, and the nested objects are sorted too: sorting only the top level produces a different byte sequence and every signature fails. And prev_receipt_hash is null in this example, which is a value that must appear in the canonical form. Stripping it has the same effect.


6. What this does not do

This section is not a disclaimer. It is the part of the document most likely to be useful, and each item below was confirmed by running the code rather than by reading it.

The limit that governs all the others. §1 says the receipt's only claim is that the record has not been altered since it was made. That is true and it is one word short of the whole truth.

Unaltered since minting says nothing about whether it was true at minting. A receipt produced honestly by a compromised environment is a correct signature over a false record, and the format cannot tell the two apart. Every limit below sits underneath that one.

This is the same boundary as §3.2: signing locally with a locally held key can establish that nothing has changed since, and cannot establish that the signer or its environment was honest at the time.

The reason it generalises rather than needing restating per field: every artefact this format binds is produced by the same environment. The plan, the hash, the receipt and the signature all come from the machine whose honesty is in question. No amount of internal consistency escapes that, and a reader should treat every claim below as detecting change after the fact rather than dishonesty at the time.

6.1 The plan fingerprint is now checked, going forward only

Amended 28 August 2026. This section previously recorded that blueprint_hash was written and never read. That is no longer true.

TAKT re-hashes the plan a receipt names and compares it to the recorded value. The result is a third statement, separate from the signature check, and it has three outcomes rather than two:

OutcomeMeaning
plan matchedThe plan on disk in the working folder hashes to the recorded value. Not the plan in any commit. See §6.4.
plan differedIt does not. Something changed after the build.
plan cannot be checkedThe plan is absent, or the receipt carries no hash.

cannot be checked is not a pass, and a consumer MUST NOT present it as one. It is the same refusal as §5: nothing was checked, so neither verdict is honest.

This is not retroactive. On the machine where this was written, 26 of 33 receipts name a plan that no longer exists and 30 carry no hash at all. Every one of those reports cannot be checked. The capability applies to receipts minted from 27 August 2026 onward.

This inherits the limit that opens §6, and it matters most here because this is the strongest claim in the document. plan matched establishes that the plan on disk now hashes to the value recorded then. It does not establish that the recorded value was taken from the plan the build actually executed. Committing the plan alongside the code lets a third party form their own judgement about whether the plan matches the work. It does not defeat the limit, because an environment that was dishonest can produce a plan and code that agree.

CORRECTION, 8 September 2026. The sentence about a third party assumes the check reads the repository. It does not: it reads the working folder. A third party holding only the repository cannot run this check at all today. See §6.4.

One rule changed with it. A plan's status field is excluded from the hash, so that a plan moving from draft to approved does not make TAKT accuse itself of tampering. This invalidated three existing receipts, which now report plan differed. The invalidation was accepted deliberately rather than worked around.

Plan names now carry microseconds, and a collision refuses. Names were previously a date and a title slug, so two plans with the same title on the same day overwrote each other silently. Three names in one receipt set were already used twice, one pair five hours apart in the same project with different commits and different files.

6.2 The receipt chain is recorded and never checked

prev_receipt_hash is written and signed. Nothing reads it, and nothing notices a gap in the chain.

And when something does, what it will detect is alteration after the fact. A chain makes a deletion visible to a later reader. It does nothing against the environment that minted the chain, which can produce an internally consistent sequence containing anything.

The recording half is witnessed. Across 47 receipts on one machine: 39 predate the field entirely, 5 are correctly null because each is the first receipt in its project, and 3 carry a real link. In one case the link was verified rather than assumed present, by hashing the raw bytes of the preceding receipt file and matching, with six other plausible rules computed and ruled out. The value is the SHA-256 of the previous receipt's file as written to disk, not of its canonical form, not of its signature.

An implementer needs that distinction, because the two hashes differ and only one will match.

6.3 Two fields carry no information yet, on purpose

execution_metrics.escalated is written as false on every receipt. Model escalation does not exist in TAKT, so the field cannot yet be anything else.

verification_depth is written as the literal string "stub" on every receipt. The source comment beside it reads: honest about Phase 1 limits.

The reason is worth stating rather than leaving to be inferred: no tests are run today. The test command is a hardcoded empty string, so every build reports not_run whatever the plan asked for. A reader who sees "stub" next to "not_run" would work this out; saying it is better than being worked out.

CORRECTION, 24 September 2026. "No tests are run today" and "every build reports not_run" are no longer true for plain web pages. Since 23 September TAKT opens such a page after the build and records the result in test_results: see §3.3. That is a page check, not a test suite. The test command is still the empty string, and every other kind of project still reports not_run. verification_depth is still "stub" and escalated still false on every receipt minted since, so the rest of this section stands.

Both are present rather than omitted, and both hold a value that openly means "nothing here yet" rather than a plausible-looking value that would imply otherwise.

A consumer MUST NOT infer anything from either field at this version. A future version will give them meaning, and a consumer reading verification_depth today and finding "stub" has learned the truth: the depth of checking is not yet recorded.

This is deliberate and it is the pattern to follow when adding fields. A field that quietly carries nothing is worse than one labelled as carrying nothing, because the first is indistinguishable from a field that is working.

6.4 Plans are now part of the shared record

Amended 28 August 2026. This section previously recorded that plans lived only on the machine that produced them, were excluded from version control, and were deleted by design, which made the binding permanently uncheckable.

The plan is now committed alongside the code it produced, in the commit the receipt names. One commit hash yields both the built files and the plan. A receipt is checkable by anyone holding the repository, not only by whoever still has the folder.

Receipts minted before this date are not covered. See §6.1.

CORRECTION, 8 September 2026. The two claims above are wrong in different ways, and one fact causes both.

Nothing on the verification path reads the repository. The verifier builds a path into the working folder and reads the plan file from disk. It never looks at what is in a commit.

So "the plan is now committed alongside the code it produced" may well be true, and nothing confirms it. No log line, no warning, and none of the receipt's fifteen fields records whether the plan entered the commit.

And "a receipt is checkable by anyone holding the repository" is false, and the failure is silent. A receipt can report plan matched while comparing against a file that is in no commit at all.

The sentences are preserved rather than rewritten, because a specification that quietly changes what it said is the thing this document argues against.

What the repair changes is the question, not the source. Today the check answers: does the plan on disk still match the hash recorded then. When the comparison moves to the plan as committed, it will answer: does the plan that produced this code still match, as recorded at the time. Those are different questions, and both claims above close with that single change.

Update, 9 September 2026. Half of the first claim is no longer unconfirmable. plan_in_commit records, at mint time and inside the signature, whether the plan was in the commit. See §3.5. The second claim is untouched: the verification path still does not read the repository, and nothing displays the new field.

6.5 Keys cannot be rotated, and no installation can be told to trust another

Amended 28 August 2026. This section previously recorded that there was one signing key rather than one per installation. That has not been true since 24 August 2026. An installation that has never run before creates its own key on first launch, and signer_fingerprint identifies which installation produced a receipt. The section was stale for three days.

Two limits remain, and they are the ones that matter for anyone verifying a receipt they did not produce.

No key has been rotated. The rotation path is unexercised, and a format that chains receipts has to answer what a rotation does to the chain before one happens.

Recognising a key and trusting it are different, and only the first exists. TAKT can tell that a receipt was signed by a key it does not hold. It cannot be told to accept that key as legitimate.

Which is why §5's third outcome is not a formality: signer_not_held is currently the only possible answer for any receipt produced on another machine. A verifier that treats it as a failure will reject every honest receipt that reaches it from outside.

6.6 The receipt does not record that anyone approved the build

A receipt records what was built, by which model, and that the record has not been altered. It does not record that a person crossed a gate before the build ran.

Where a system requires human approval before an agent acts, that approval is the most consequential fact about the build and it is absent from the evidence.

execution_metrics.call_context is adjacent to this and is not the same thing. It records whether a person triggered the run, not that a person was shown a specific plan and accepted it. A consumer MUST NOT read interactive as approval.

If a future version records approval, the wording matters more than the field. The honest claim is that the system recorded an approval at a time, never that a person read and understood a plan. No signature can attest comprehension, and a format that implies it is worse than one that stays silent.

A build that runs with no human involved SHOULD say so explicitly rather than omit the field. See §9, question 6, which this shares.

6.7 The plan that is signed may not be the plan that was read

blueprint_hash binds the technical plan. In systems where a person approves a rendered, translated or plain-language version of that plan rather than the plan itself, one technical plan can produce more than one wording, and the version a person can honestly be held to is the one they were shown.

The receipt binds the artifact, not the presentation. Where those differ, either both are bound or the format should say plainly that the receipt does not cover what the approver actually saw.

This is a general problem for any system that puts a human gate in front of an agent and shows that human something other than the exact bytes being attested. It is stated here because it is easier to design for than to discover.

6.8 Nothing reads a receipt

Amended 28 August 2026. This section previously recorded that no code path in TAKT had ever loaded a receipt.

A tolerant reader now exists alongside the strict one, and reads every generation: 27 of 27 on the machine where this was written, up from 8.

State the distinction precisely. The strict type used at mint time still refuses any receipt missing a field it declares. That is correct rather than broken: it is what makes a future gap loud at the moment a receipt is written. The tolerant reader is a second path, not a replacement.

Note also that reading and verifying were always different, and only the first was ever missing. Verification never constructed the strict type, which is why §4.3's compatibility limit had cost nothing.


6.9 plan_in_commit is recorded and nothing displays it

The field is written, signed and correct. No surface shows it to anyone. The intended consumer is the verification badge, and that work has not landed.

This document has a convention for a field that carries no information: §6.3, where escalated and verification_depth openly mean "nothing here yet". It has no convention for a field that carries real information nothing reads, and this is the first. The convention is being made here rather than implied: such a field is documented, and the section documenting it says nothing reads it.

A reader assumes anything in a specification is available to them. It is not, yet.

CORRECTION, 24 September 2026. Two surfaces display the field now, and only one has been seen.

Since 12 September TAKT's chat says after each build whether the plan was saved with it. That line has been seen, in saved conversations from 15 September onward, and every time it said the plan was saved. The warning it gives when the plan was not saved has never been seen.

The verification badge displays the field too, since 9 September. It has never been seen doing so.

6.10 Changes already staged ride into the signed commit

Added 24 September 2026. When TAKT commits a build, it adds the build's files to git's staging area as it finds it. Anything already staged there, with git add before the build, goes into the same commit. The receipt names that commit in git_commit, and the name is inside the signature, but those changes are not in files_changed and the build did not make them. So the commit a receipt points at can contain changes the receipt does not list.

Unlike the items above, this was read from source, not confirmed by running. See §11.

6.11 Rewritten history is not detected

Added 24 September 2026. plan_in_commit records whether the plan was in the commit when the receipt was minted. If that commit is later rebased, amended or force-pushed away, the recorded answer stays as it was. It was true. It may no longer be. Nothing detects this.

The obvious repair, reading the repository whenever a receipt is checked, would weaken the check for the reader who matters most: a third party without the repository would get a could-not-tell answer every time. It belongs as a second check where a repository happens to be present, never as a replacement for the recorded fact.

Like §6.10, this was read from source rather than confirmed by running.


7. Parsing rules

Unknown fields. A consumer MUST preserve fields it does not recognise and MUST include them when reconstructing the canonical form for verification. Dropping an unknown field will cause a correct receipt to fail.

Version handling. The major version in schema indicates compatibility. A consumer encountering an unrecognised major version MUST NOT attempt to verify the receipt and MUST report that it cannot, rather than reporting failure.

Absent optional fields. Absence is not tampering. See §3.2.

Formatting on disk. Ignore it. See §4.1.


8. Relationship to existing work

There is no shortage of records of what AI agents do. There is a shortage of records anyone can check.

in-toto and SLSA define a general attestation framework: a signed statement about a subject, carrying a typed predicate. They are designed for build provenance rather than for agent behaviour, and the framework anticipates new predicate types being defined inside it.

Cursor's Agent Trace, published as an RFC in January 2026 with several vendors supporting it, records which lines of code came from AI and which from a human. It explicitly excludes ownership, training-data provenance and code quality, it points at conversations by URL rather than embedding them, and it carries no cryptographic integrity.

The gap they share. Every one of these records what an agent did. None records what it was allowed to do, and none is signed.

And a limit this format shares with none of them, in the other direction. in-toto and SLSA assume a trusted build platform holding a key the build process cannot reach. TAKT has no such platform: it signs on the same machine, with a key it minted itself. That is why §3.2 claims continuity rather than provenance, and why a reader should not treat a TAKT receipt as equivalent to a SLSA attestation. The signature proves nothing has changed since signing. It does not prove the signer was honest.

The Showboat Receipt as specified here is not yet an in-toto predicate. It is a flat document with its own schema identifier. The intended direction is to publish it as a predicate type inside the in-toto framework, so that it interoperates with existing tooling rather than competing with it, and to remain a compliant producer of any attribution format that gains adoption.

That is a statement of direction. It is not implemented, and nothing in this document depends on it.


9. Open questions

Published rather than resolved, because a specification that hides its uncertainties is harder to improve.

  1. Should the plan be embedded in the receipt? It would make the receipt self-contained and end §6.4, at the cost of size and of putting plan text into a durable record.
  2. What should verification do when blueprint_hash does not match? Answered 28 August 2026. Not failed. It is reported as a separate statement with its own three outcomes, so a plan that differs does not impugn a signature that is sound. See §6.1.
  3. Should a broken chain be an outcome of its own? A missing prev_receipt_hash link is not a signature failure, and the three outcomes in §5 have no room for it.
  4. How should a receipt be verified on a machine that did not produce it? Today it cannot be, beyond recognising that the key is unknown.
  5. How should the version string be repaired? It does not currently determine the field set: two different shapes both call themselves v0, and since 9 September 2026 two shapes call themselves v1 as well. This question is now more urgent than when it was written, because the format repeated the defect after publishing it. Options are a version bump on every field addition, an explicit capability list inside the receipt, or a rule that consumers must always tolerate absence field by field. The third is what §4.3 currently requires, and it puts the cost on every consumer rather than on the producer.
  6. Should unset continuity fields be null or omitted? Both occur today. Either is workable, but only one should be permitted, and the choice is signed so it cannot be changed silently. This shares an answer with §6.6: a build that ran with no human should say so, which argues for explicit nulls over omission.
  7. How should human approval be recorded? See §6.6. The field is the easy part. The wording is not, and a format that implies comprehension where it can only attest a timestamp is worse than one that records nothing.
  8. Should the presentation be bound as well as the artifact? See §6.7. Binding both doubles what a receipt carries. Binding neither means the receipt does not cover what the approver saw.

10. Version history

VersionDateChange
takt.receipt/v0June 2026Twelve required fields. Still readable and still verifies.
takt.receipt/v1August 2026Adds signer_fingerprint, blueprint_hash, prev_receipt_hash, all optional for compatibility with v0 receipts on disk.

Amendments to this document

DateChange
24 September 2026§3.1 updated: files_changed lists only files whose content changed since 15 September, and a receipt does not mark which meaning applies. §3.3 updated: the vocabulary has four values, a page check can write passed, failed or undetermined, and passed and failed changed meaning; undetermined has never occurred. §3.5 updated: its table went stale two hours after publishing, every answer is now a string and every could-not-tell an object, and 48 receipts carry the field. §4.3 gains meaning changes under the same label, which cannot be detected. §5 gains the fingerprint rule. §6.3 corrected: page checks run for plain web pages. §6.9 corrected: the chat displays the field and has been seen doing so; the badge displays it and has not. §6.10 and §6.11 added: staged changes ride into the signed commit, and rewritten history is not detected. §11 gains five rows. The 9 September row below says §3.5 has four states; the table beside it has five, and it had five when it was published. The row is left as published.
9 September 2026§3.5 added for plan_in_commit, a field recorded inside the signature with four states of which two must not be collapsed. §4.3's table gains the sixteen-field v1 shape and states that this document's own published defect has been repeated. §6.9 added: the field is recorded and nothing displays it. §6.4's correction block gains an update. §9 question 5 escalated.
8 September 2026§6.4 corrected: nothing on the verification path reads the repository, so one claim in it is unconfirmable and the other is false with a silent failure. §6.1's third-party sentence and its plan matched row corrected for the same reason. Both original sentences preserved with correction blocks rather than rewritten. §3.4 gains what call_context is for: TAKT's own billing and routing, on its own machine, and not evidence for a third party.
5 September 2026§3.2 renamed from Provenance fields to Continuity fields, with the reason stated: provenance is a term of art requiring a trusted build platform, which TAKT does not have. §8 gains the matching limit. §3.4 corrected: it said a build that ran unattended cannot later be presented as supervised, which contradicted §6.6 and was false in the same way §1 was. §3.2's blueprint_hash line and §6.2's chain line narrowed for the same reason. §6.1 gains the ceiling it inherits. §3.2's SLSA reference corrected: it attributed a position to SLSA that SLSA does not state. Re-attributed to the build-level requirements, which are checkable. §6 gains the limit that governs the rest: unaltered since minting says nothing about whether it was true at minting. §1 points at it.
28 August 2026§6.5 corrected: it recorded one signing key per installation as a limit, which stopped being true on 24 August and stayed published for three days. §6.1 and §6.4 reversed: the plan fingerprint is now checked, and the plan is committed alongside the code. §6.8 amended: a tolerant reader now exists. §9 question 2 answered. The format itself does not change; what changed is what TAKT does with it.
25 August 2026§6.6, §6.7 and §6.8 added: approval is not recorded, the signed plan may differ from the plan a person read, and nothing reads a receipt. Questions 7 and 8 added to §9. No change to the format itself.

11. How the facts in this document were established

Stated because a specification that claims a method should say which one, and because the distinction matters more than the conclusion.

ClaimHow it was established
Field names, types and presenceRead from source, 22 August 2026
Older receipts carry fewer fieldsConfirmed by running. A nine-field receipt from 30 June verifies clean and fails to load. 18 of 25 receipts on one machine behave this way.
Two field sets share the version v0Confirmed on disk, by reading receipts from 30 June and 17 July.
Continuity fields appear as nullConfirmed on disk, on a v1 receipt from 18 August.
Canonicalisation and signing procedureRead from source
The three verification outcomesRead from source
verification_depth is always "stub"Confirmed on a receipt on disk, 22 August 2026.
test_results.status vocabularynot_run and passed confirmed on receipts on disk. failed unconfirmed. The constraint itself sits at a component boundary whose running version could not be established on 22 August.
blueprint_hash is now checkedConfirmed by running, 27 August 2026. A real build reported the signature verified and the plan unchanged as two separate statements.
prev_receipt_hash is recorded and never checkedSame test. Separately, the recording half was confirmed across 47 receipts, and one link was verified by hashing the predecessor's raw bytes and matching.
plan_in_commit's five on-disk shapesRead from source, plus one probe that minted a commit_missing receipt and read the file. Not confirmed on any receipt in ordinary use: no receipt carries the field.
plan_in_commit on real receipts, as of 24 September 2026Confirmed on real receipts for in_commit (27) and not_in_commit (21). The 21 not_in_commit receipts come from one automated test run on 14 September, not from a build a person made. no_repo, commit_missing and git_error have never occurred on a real receipt. All five shapes confirmed by running the serialisation tests, 24 September 2026.
test_results.status vocabulary, as of 24 September 2026Four values, read from source. failed confirmed on disk: four receipts from 23 September, all page checks on saved builds. passed from a page check confirmed on disk: five receipts from 23 September. undetermined has never occurred. The only passed receipts from before 23 September on the machine where this was written come from test folders on 15 and 18 August, with outputs reading "all good" and "ok". They look synthetic and are not treated as evidence that a test ran.
A malformed signer_fingerprint is failedConfirmed by running, 24 September 2026: eleven tests, uppercase hexadecimal included.
Staged changes ride into the signed commit (§6.10)Read from source, 15 September and again 24 September 2026. Not confirmed by running.
Rewritten history is not detected (§6.11)Read from source: nothing on the verification path reads the repository.

Where this document says "read from source", it means exactly that. Reading is weaker evidence than running, and this project has been wrong three times in a week by treating the two as equivalent.

The items marked as not yet confirmed will be settled by reading a receipt file written by the running application, which is a different thing from reading the code that writes it.


The name Showboat Receipt is TAKT's. Nothing in the format requires it.