Skip to content

Concept: Receipts and proofs

Status: shape-only for cryptographic verification; fixture-rehearsed for shape and outcomes. Receipts are present on every membrane crossing. Today they are not signatures over canonical bytes — they are SHA256 hashes of (ref|subject|outcome) (see 022 gap report item 17). Proof bundles are fixture IDs.

A receipt is the artifact every membrane crossing leaves behind. It exists so that any party with a legitimate interest in what happened — the actor, the subject company, an advisor, an auditor, a counterparty, a regulator — can confirm that the act crossed the membrane in the shape claimed.

The shape:

interface MembraneReceipt {
ref: GestaltRef; // a stable id for this receipt
outcome: MembraneOutcome; // admitted | refused | pending | ...
reasons: string[]; // structured reason codes / human notes
signature?: string; // signature over canonical bytes (future)
fixture?: boolean; // true today on every receipt
}

Every operation in the membrane contract returns a receipt in its response, in the receipt field of MembraneResponse.

In conventional software, a successful response is its own proof: the call returned 200 OK, the row exists. This breaks for everything Gestalt is trying to do:

  • An auditor years later cannot verify “the row existed” — the row may have changed.
  • A counterparty cannot independently verify what was admitted on the other Geist.
  • A refusal disappears into a log line; nobody can prove that an attempt was made and refused for a specific reason.
  • A Steuerberater cannot rely on “the system accepted my opinion” — she needs proof of what she signed under, what version of the capability, what authority epoch.

Receipts are the discipline that makes “what happened” survive every participant. Even refusals get receipts. Even Zeitgestalt queries get receipts (receipt-backed inquiry).

The outcome enum:

admitted serious atom committed
refused structured refusal
pending awaiting signing / evidence / advisor
projected admitted into a projected reality only
verified a non-mutating call (probe, query, verify) succeeded
queued accepted into a durable outbox (effect.intent)
executed dispatched (effect.dispatch)
failed dispatched but failed (idempotent record)

See reference/outcomes.md.

A short list of structured reason codes plus human-readable notes. Examples:

[ "fixture precheck requires invoice_payload" ]
[ "shop commit fixture admitted" ]
[ "tenant self resolved through fixture SDK fetch" ]
[ "proof disclosure requires explicit scoped entitlement" ]

For refusals, the body of the response carries the structured refusal fields (failedGate, requiredEvidence, etc.). The receipt’s reasons is a complementary view, not a substitute.

Today the SDK exposes a shape-check verifier:

import { verifyFixtureReceipt } from "@gestalt/sdk";
const result = verifyFixtureReceipt(receipt);
// { verified: true, fixture: true, reasons: ["receipt shape accepted"] }

This checks:

  • receipt.ref is present (or receipt.fixture is true),
  • receipt.outcome is one of the known enum values.

It does not check a cryptographic signature against canonical bytes. The membrane endpoint POST /v1/receipts/verify exists but returns { verified: true, fixture: true } regardless.

When the runtime signer lands (see 022 item 17), receipts will:

  • be signed over canonical bytes including the operation, the request envelope hash, the response body hash, the timestamp, and the runtime signer’s identity,
  • be verifiable against the runtime signer’s published key without contacting the cloud,
  • support key rotation: old receipts verify against historical keys, new ones verify against current.

The SDK shape will not change. Today’s verifyFixtureReceipt(receipt) will become a real verification call.

Every receipt today carries fixture: true. This is intentional:

  • It means “no real consequence was bound by this crossing.”
  • It means “do not rely on this for any external claim.”
  • It means “do not present this to a third party as a proof of fact.”

A doc, an SDK, or a Koerper that strips the fixture: true marker is violating the discipline. Don’t do that.

A proof bundle is a scoped, signed collection of receipts, packages, evidence, and signer provenance, suitable for disclosure to a counterparty, advisor, or regulator.

Proof bundle requests:

const proof = await client.requestProof({
tenant: "tenant_node:rheinwerk_calibration",
subjectCompany: "company_geist:rheinwerk_calibration",
scope: "advisor_review", // or "self_audit" | "external_counterparty"
fields: ["invoice", "payment_observation", "advisor_opinion"],
purpose: "Q1 2026 Steuerberater review of receivable settlement",
});

If no entitlement is cited, the request is refused with missing_entitlement. Disclosure is always entitlement-scoped. There is no global “give me a proof” path.

The fuller M6 proof bundle:

const bundle = await client.requestProofBundle({
tenant: "tenant_node:rheinwerk_calibration",
entitlement: "entitlement:fixture_proof_bundle",
});

Today this returns a fixture bundle ID. Real proof issuance is in closed_runtime_boundary (see 022 item 5).

When proof issuance becomes operational, a bundle will carry:

  • the receipts for the relevant atoms,
  • the capability atoms they cited (with their authority sources),
  • the authority package versions in force at admission time,
  • the evidence bundles referenced,
  • the signer provenance chain (which runtime signer produced the receipts, and which key version),
  • the lens or entitlement under which the bundle was disclosed,
  • the disclosure scope (what fields the relying party may display, retain, forward),
  • the validity window (until when the disclosure is current),
  • the revocation path (how the disclosing tenant can revoke reliance).

A relying party will be able to verify a bundle entirely offline against the published runtime signer key and the published authority package hashes. No need to call back to Gestalt.

Proof bundles are not “exports.” They are not bulk dumps of company truth. They are entitlement-scoped, signed assertions about specific facts.

Constraints:

  • A Steuerberater receives a bundle scoped to their lens — visible facts, retention, fee period.
  • A counterparty company receives a bundle scoped to the bilateral edge between them.
  • A regulator receives a bundle scoped to a specific filing or inquiry, not universal telemetry.
  • An auditor receives a bundle scoped to the audit’s bounds.

The forbidden surface explicitly includes unscoped_proof_bundle_disclosure. There is no path through any SDK or any tool to a “give me everything” bundle. By design.

What it isWho producesWho relies
ReceiptArtifact of a single membrane crossingCloud Geist (runtime signer)Anyone who saw the crossing
Proof bundleScoped, signed collection of receipts + supporting dataCloud Geist on entitled requestThe party named in the entitlement
Evidence bundleRaw signed material backing a claim before admissionConnectors, witnesses, the actorGravity, capabilities, downstream atoms

Evidence flows into atoms. Receipts come out of membrane crossings. Proofs are assembled from receipts and supporting evidence on demand.

const response = await client.precheckIntent({...});
// every response carries a receipt (when the operation produces one)
console.log(response.receipt?.ref);
console.log(response.receipt?.outcome);
console.log(response.receipt?.reasons);
// shape-verify
const verified = verifyFixtureReceipt(response.receipt!);

A Koerper should treat receipts as opaque blobs to store and forward — not to interpret. The Geist owns interpretation.

  • Not logs. Logs are observability. Receipts are claims.
  • Not transaction IDs. A transaction ID identifies a row. A receipt asserts what crossed the membrane and why.
  • Not API keys. Receipts are public artifacts of public crossings. They are safe to share with the parties named in the operation.
  • Not invoices. Receipts are about the membrane crossing, not about money. An invoice is its own atom; the act of admitting that invoice atom produces a receipt.