Coordination receipts
Every successful coordination action emits a cryptographic receipt. The receipt log is Limlock's primary accountability surface — third parties can replay a coordination history without trusting Limlock's API, and reputation-graph rankers can sort over signed completion data.
The schema lives in the reference implementation at @golf/shared/receipts; the TypeScript types are the runtime contract, the JSON Schema files are the cross-language contract.
Six receipt types
intent.created— emitted on intent creation. Contains intent ID, creator ID, group size, payload hash.constraint.submitted— emitted per participant constraint submission. Contains intent ID, player name, payload hash.intent.resolved— emitted when the resolver produces an option set. Contains options count and an options hash so a verifier can confirm the option set wasn't silently mutated post-resolution.vote.cast— emitted per vote. Contains intent ID, player name, option ID, timestamp.booking.handoff— emitted when the deep-link is issued. Contains intent ID, course ID, date, time, price, hash of the booking URL (the URL itself may carry transient session tokens).booking.confirmed— optional, only emitted when a course cooperates with a post-booking attestation hook. Most courses won't in v0.1.
Common envelope
Every receipt carries the same five envelope fields:
interface ReceiptBase {
type: ReceiptType; // Discriminator
schemaVersion: '0.1';
timestamp: number; // ms since epoch
previousReceiptHash: string; // sha-256 hex of prior receipt, "" at chain origin
signature: string; // base64 Ed25519 signature, "" until signed
} The signed payload is the entire receipt object with signature set to the empty string. That canonicalization rule lets verifiers
recompute the signing input without negotiating a separate envelope. Verifiers must treat signature: "" as unsigned and untrusted.
The previousReceiptHash field forms a chain per intent: the first receipt
(intent.created) carries an empty string; every subsequent receipt's previousReceiptHash equals the SHA-256 of the prior receipt in the chain.
A third party can replay a sub-chain without trusting the API server's ordering.
Signing
Receipts are signed with an Ed25519 keypair. The public key is published at https://limlock.com/.well-known/limlock-receipt-key as base64-encoded raw
public key bytes (32 bytes pre-encoding). External verifiers fetch the key from that
URL and verify with any Ed25519-capable library.
Algorithm: Ed25519 (RFC 8032). Same primitive A2A v1.0's signed agent cards use, same as most modern protocol-layer signing standards.
intent.created receipt type is signed in production today. The
other five types are scaffolded with the schema in place but emit unsigned (signature: "") until the next signing rollout phase. Backfilling unsigned receipts retroactively is
impossible — capture lands now even at partial coverage, because the data has the
longest half-life of any artifact in the protocol.Public verification endpoint
The reference implementation exposes:
GET https://api.limlock.com/receipts/:intentId— all receipts for that intent in chain order. Public, no auth.GET https://api.limlock.com/receipts/:intentId/:receiptId— a single receipt. Public, no auth.
A receipt fetched from the API can be verified offline with the public key plus any Ed25519 library. The API server is never in the trust path for verification.
Why this matters
By 2027, agent-discovery commoditizes — every serious travel API will have an agent card, a Skill, a published spec. What rankers will sort over is completion data: did this agent successfully deliver the last 1,000 times it was called, at what latency, at what cost. Cryptographic coordination receipts are how Limlock starts accruing that data today, while the cost of capturing it is low and competitors haven't yet started.
Read more on the architectural reasoning at docs/strategy/agent-distribution.md §11.