Skip to content
EN

← Return to Guides

Developer receiptsproofsaudit-chain

FarmDash Receipt Proofs, Shareable Verification, and the Operator Audit Chain

How FarmDash signs execution receipts, exposes public share proofs at /r/:token, and maintains a per-session hash-chained operator audit log — and what each proof does and does not establish.

By FarmDash Pioneers · Published 2026-11-10 · Updated 2026-11-10

TLDR: FarmDash produces three distinct artifacts — a signed execution receipt, a public share proof at /r/:token, and an append-only operator audit chain. Each answers a different question: what happened, can a third party verify it, and was the operator's behaviour internally consistent. None of them is a settlement claim.

The Three Artifacts

Artifact Answers Audience
Execution receipt What did FarmDash execute, and with what outcome? The session owner.
Share proof (/r/:token) Can an external party verify a bounded public projection? Anyone with the link.
Operator audit chain Was each operator action internally consistent with the one before it? FarmDash and auditors.

Confusing these is the most common error when reasoning about FarmDash proofs. A share link proves the public projection matches the receipt — not that the swap was economically correct. An audit chain proves the recorded sequence is intact — not that the recorded actions were permitted. A receipt proves what FarmDash observed — not that the observation matches economic reality.

Understanding which question each artifact answers, and which it does not, is the difference between a verification claim that holds up and one that collapses under scrutiny.

Execution Receipts

A FarmDash receipt is canonicalized with a deterministic JSON encoding: sorted keys, stable numbers, UTF-8, arrays in order. The receipt digest is SHA-256 over that canonical form, making it order-independent and reproducible.

The canonicalization rules matter because digests are only comparable when both sides encode identically. Object keys are sorted lexicographically; undefined values are dropped rather than serialized; arrays preserve their order because order is semantically meaningful for operations. A verifier that re-serializes with different key ordering produces a different digest and reports a false mismatch.

The share envelope is version 2 and carries an exact field set:

v, domain, receipt_id, receipt_digest, public_digest, url, issued_at

domain is pinned to farmdash.one. The envelope is signed, and the signing key is rotatable via the RECEIPT_SIGNING_KEYRING_JSON environment variable, which holds a verification-only ring of { "<kid>": "<base64 PKCS8>" } entries. Key rotation is therefore a deployment action, not a code change, and a rotated key does not invalidate receipts signed under its predecessor — both remain verifiable for as long as they are retained in the ring.

The public key is served dynamically at /.well-known/farmdash-receipt-key.json — it is not a checked-in static file. The path is a Vercel rewrite to the receipts API, which publishes the current key ring by key id. Verification fetches the key ring by key id, so a rotated key remains verifiable while the current key is published.

Why the key is dynamic

A static key file would couple key rotation to a deployment: rotating would require committing a new file, and any verifier holding the old file would reject receipts signed under the new key. Serving the ring dynamically decouples rotation from release, so an operator can rotate in response to a suspected compromise without shipping code.

Receipt fields

A receipt records the intent that was executed and the outcome that was observed. The fields that matter for verification are the receipt identifier, the chain and transaction identity, the action and its parameters, the resulting state, and the outcome classification. Each is included in the canonical digest, so none can be altered after signing without invalidating the receipt.

The outcome is classified rather than inferred. A receipt is not "successful" because a client reported success; it carries an explicit outcome that reflects what FarmDash verified. Reading an outcome field without checking the verification flags that accompany it is the most common way to misreport a receipt.

Verification flow

Verifying a receipt end to end proceeds in a fixed order, and each step depends on the previous one passing:

  1. Canonicalize the receipt using the documented encoding rules.
  2. Compute the digest and compare it against the signed receipt_digest.
  3. Check the envelope signature against the published key ring for the declared key id.
  4. Compare the public projection digest against the projected fields the verifier can see.
  5. Read the outcome only after all four steps pass.

A failure at any step invalidates the remaining steps. There is no partial verification that still supports a claim.

Share Proofs

A share is created from a receipt and mints an opaque token. The resulting URL is:

https://www.farmdash.one/r/<token>

Shares expose a projection of the receipt, not the full record. Two digests are computed:

  • receipt_digest — SHA-256 over the full canonical receipt.
  • public_digest — SHA-256 over the shared projection.

A verifier checks that the projection's digest matches the signed envelope, and that the envelope's receipt_digest matches the receipt the session produced. A mismatch in either fails verification. The two-digest design is deliberate: the public digest lets a third party verify what they can see, while the receipt digest binds that visible projection to the full record without exposing it.

Shares require a terminal outcome. A receipt that is still in flight cannot be shared; the API returns receipt_not_terminal until the outcome is CONFIRMED, FAILED, or REJECTED. This prevents a share from being circulated against a transaction that has not yet settled, where the outcome could still change.

Shares are access-controlled by session ownership. A share token is only honoured for the session that created it; a different agent or session cannot resolve another session's receipt. The token is an bearer capability, so treat a share URL as a disclosure — anyone holding it can read the public projection.

Verification is signature-bound. A share payload includes the signature validity flag, and a verifier must treat an invalid signature as a failed verification rather than a warning. A projection without a valid signature is unauthenticated data, not a proof.

The Operator Audit Chain

Operator actions — safety events, lifecycle transitions, policy changes — are recorded as immutable receipts linked into a per-session hash chain. The chain is per session, not global: each session has its own genesis and its own sequence, so a break in one session's chain does not contaminate another's verification.

Properties of the chain:

  • Genesis link. The first entry in a session anchors to an all-zero hash: 64 zeros. That is the chain head marker.
  • 1-based sequence. Each subsequent entry's sequence is the predecessor's sequence plus one. The first real entry after genesis is sequence 1.
  • Hash binding. Each receipt's previousHash is the predecessor's receiptHash, so any alteration of an earlier entry breaks every link after it.
  • Deterministic intra-session order. When two entries share a millisecond timestamp, sequence is the true ordering key. Timestamps alone are not sufficient because writes within the same millisecond are possible.

verifyChain walks the session's entries in order and fails closed on the first inconsistency:

Failure Meaning
receipt_hash_mismatch A receipt's own hash does not match its content.
reanchored_mid_chain A null previousHash appears anywhere but the oldest entry — an attempt to start a second chain.
chain_starts_mid_link The verification window (200 entries) truncated a longer session, so the chain could not be walked back to genesis.

That last case is deliberate honesty: a session longer than the verification window fails rather than returning a partial pass. Page or archive before verifying very long histories.

Legacy rows predate the chain columns and carry a null previousHash. A null link is valid only at the oldest entry, where it honestly terminates the walk; a null link anywhere else is treated as re-anchoring and fails closed. This means old sessions verify up to their legacy head and no further, rather than appearing fully intact.

What the chain is for

The chain answers an audit question, not a safety question. It establishes that the recorded operator history has not been altered or reordered since it was written. It does not establish that the operator acted within policy — a faithfully recorded policy violation verifies perfectly. Treating a clean chain as evidence of compliant behaviour is a category error.

Recorded actions

The chain records operator actions across sessions, policy, grants, intents, and team membership. Each action is an explicit type, not a free-form string, so an audit can be queried by category:

Category Actions
Session lifecycle SESSION_CREATE, SESSION_PAUSE, SESSION_RESUME, SESSION_KILL, EMERGENCY_FREEZE
Policy POLICY_CREATE, POLICY_UPDATE, POLICY_REVOKE, POLICY_RESET
Grants GRANT_CREATE, GRANT_REVOKE
Intents INTENT_APPROVED, INTENT_REJECTED, INTENT_CO_APPROVED
Team TEAM_MEMBER_ADDED, TEAM_MEMBER_UPDATED, TEAM_MEMBER_REMOVED

Each entry records the operator address, the agent address, the previous state, the new state, and an optional reason. The state transition is captured as data, so an audit can reconstruct exactly what changed and when — not just that something changed.

Verification window

verifyChain reads through a window of up to 200 entries. That limit is a practical bound, not a claim about correctness: a session with more entries than the window cannot be walked back to genesis in a single pass, and the verifier reports chain_starts_mid_link rather than returning a partial success. For long sessions, page the history or archive it before verifying.

Receipt identity

Each audit receipt has a unique identifier and a computed hash. The hash covers the receipt's content, and the receipt_hash is stored in the row's metadata so it can be recomputed and compared on demand. A receipt whose stored hash does not match its recomputed hash has been altered, and the chain fails at that entry.

Because the hash is stored alongside the content it describes, the chain does not depend on the database's own integrity guarantees. An attacker with write access who alters a row without recomputing the hash produces a detectable break; one who recomputes the hash breaks the link to the next entry instead. Both are caught.

Session scoping

Chains are scoped per session, which has two consequences worth understanding. First, a session's chain is independently verifiable: compromising or truncating one session's history does not affect another's verification result. Second, sequence numbers are only meaningful within a session — a sequence 5 in one session and a sequence 5 in another are unrelated entries, and comparing them is meaningless.

Immutability and correction

Audit entries are append-only. There is no update or delete path for an existing receipt; a corrected record is written as a new entry that references the old one through the chain. This means an error in an audit entry is visible forever, and the correction is itself part of the verifiable history rather than an invisible overwrite.

For operators, the practical implication is that an entry should be written correctly the first time. A mistaken entry cannot be erased; it can only be superseded by a later entry that records the correction.

Share lifecycle

A share is a point-in-time artifact. It binds a specific receipt, a specific projection, and a specific token. If the underlying receipt is later corrected through the audit chain, an existing share does not silently update: its digests still describe the projection it was created from. A verifier comparing a share against a newer record must expect a mismatch and treat it as a finding rather than a verification failure of the original share.

Tokens are opaque and unguessable. A share URL is a bearer capability, so distribution should be treated as disclosure of the public projection.

What a verifier should check

A third party verifying a FarmDash share should perform the following checks, in order, and treat any failure as a failed verification:

  1. Fetch the current key ring from the published well-known URL and confirm the declared key id is present.
  2. Recompute the public digest from the projection shown on the page and compare it against the envelope's public_digest.
  3. Verify the envelope signature over the canonical envelope using the key identified by kid.
  4. Confirm the outcome is terminal before drawing any conclusion from the projection.
  5. Confirm the share belongs to the expected session if the verifier has an independent expectation about who shared it.

If the key id is absent from the ring, verification fails rather than falling back to another key. Falling back would allow a signer to claim any key id and have it honoured, which defeats the purpose of publishing a ring.

Common verification errors

Symptom Likely cause
Digest mismatch on an unmodified page Verifier canonicalized differently — key order or number formatting differs.
Signature invalid for a valid receipt Key ring is stale, or the receipt was signed under a rotated key no longer published.
Projection digest matches but receipt digest does not The projection was altered relative to the full receipt.
Share resolves but shows an unexpected outcome Token was reused or the share was created against a different receipt than assumed.

Each of these is a genuine finding, not a technicality. A digest mismatch means the verifier and the signer disagree about what the data is; a signature failure means the claim is unauthenticated; a projection mismatch means the visible data does not reflect the underlying record.

Operational use

For operators, these artifacts support three distinct workflows:

  1. User dispute — reconstruct the execution record and compare the stated expectation against the reconciliation outcome.
  2. External assurance — publish a share link so a third party can verify a bounded projection without access to internal systems.
  3. Internal audit — verify a session's operator chain and investigate any break, including truncation failures.

Each workflow uses a different artifact and a different claim. Conflating them produces assurance that does not hold up outside its intended scope.

Interpreting a clean verification

A clean verification is easy to over-read, so it is worth stating precisely what it establishes and what remains open.

A verified receipt establishes that the recorded execution and outcome are internally consistent and unaltered since signing. It does not establish that the operator acted within policy, that the market outcome was favorable, or that any third party would agree with FarmDash's classification.

A valid share establishes that the public projection matches a signed envelope and that the envelope binds a real receipt. It does not establish that the projection is complete — fields were deliberately removed — or that the underlying swap was economically correct.

An intact audit chain establishes that the recorded operator history has not been altered or reordered. It does not establish that every recorded action was appropriate, nor that no unrecorded action occurred.

Each of these is a real but bounded assurance. The discipline is to state the bound explicitly rather than letting a clean result imply more than it tested.

What Each Proof Does Not Establish

  • A receipt does not prove on-chain settlement. It records what FarmDash executed and observed; economic correctness is a separate reconciliation step.
  • A share proof does not prove the underlying swap was correct. It proves the public projection matches a signed envelope.
  • An audit chain does not prove the operator acted within policy. It proves the recorded sequence is internally consistent and unaltered.

Each artifact has a specific failure mode it defends against, and none defends against the others' failures.

Reading These Artifacts in Practice

  1. Start from the receipt. If the outcome is not terminal, stop — nothing downstream is meaningful yet.
  2. For external verification, use the /r/:token link and check both digests and the signature.
  3. For operator forensics, call verifyChain and treat any non-ok result as a finding, including chain_starts_mid_link.
  4. Never upgrade a verified artifact into a settlement or authorization claim.
  5. State which artifact supports which claim. A receipt supports an execution claim; a share supports a disclosure claim; a chain supports an integrity claim.

Consult the live capability contract before making any claim about what an agent can execute. Documentation describes the mechanism; it is not execution qualification.

Continue researching