Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.cast.digitalfinancehq.com/llms.txt

Use this file to discover all available pages before exploring further.

The principles in this guide are not aspirational. This page shows where each one is enforced in a working build — a demo that walks one bilateral payment confirmation from a held invoice to a signed, independently verifiable record. The snippets below are taken verbatim from the build’s schema.sql and canonicalize.ts. Where the guide makes a structural claim, the code that backs it is shown next to it — so the claim and the constraint are the same characters on the page.
This is Session 1 scope: the bilateral event loop. It deliberately proves the foundation — that a verified, co-authored record can be created and independently checked — before any downstream ledger automation is built. See Session 1 scope below for exactly what is and isn’t included.

What the demo does

One payment, three screens, each written for a different audience. The asymmetry is the point: the people doing the work never see cryptography; the people who underwrite the result see all of it.
ScreenAudienceShows JSON / crypto?
Buyer AP queueControllerNo — operational queue only
Seller Trade MessageVendor AP clerk, on a phoneNo — amount, invoice, destination, three actions
Verifier viewLender / auditor / insurerYes — payload JSON, hashes, signature, recompute
The verifier screen ends with a Recompute & verify button that re-derives the hash and checks the signature entirely in the browser — the clearest demonstration of the thesis, because the viewer proves it themselves rather than taking our word.

See the demo

The Layer 0 build is a working application, available on request. Reach out to book a walkthrough of the full buyer → seller → verifier flow, including the in-browser recompute.

Verification is a precondition to insert

The central claim of the architecture is that a record cannot exist unless it was verified first. This is not a status column that an event can sit in as “unverified” — it is the structure of the only code path that reaches the database. The single transaction recomputes the hash, checks the signature, and only then writes both the event and its signature together. If either check fails, the transaction rolls back and nothing is written:
The gated transaction (shape, from schema.sql)
BEGIN;
  -- assert sha256(payload_version || canonical_json) == payload_hash
  -- assert ed25519.verify(payload_hash, signature, public_key)
  -- if either assertion fails -> throw -> ROLLBACK (nothing is written)
  SELECT insert_signed_event(...);   -- writes events + event_signatures
COMMIT;
The two assertions run in application code inside the same database transaction as the insert. The guarantee is that the only path to insertion is gated by those checks, and the event row and signature row live or die together — not that the database engine itself performs the cryptography. (Stock Postgres can’t verify Ed25519 without an untrusted procedural language, and the build keeps to stock Postgres so a verifier in 2031 needs no exotic extensions.) There is no “pending” state and no “unverified” flag anywhere in the schema.

A posting cannot exist without a bilateral event

The strongest structural guarantee, and the easiest to verify: it is a non-nullable foreign key. A ledger entry with no co-authored event behind it cannot be inserted — the database rejects it.
postings — from schema.sql
CREATE TABLE postings (
  posting_id text        PRIMARY KEY,
  event_id   text        NOT NULL REFERENCES events(event_id),   -- NON-NEGOTIABLE #2
  account    text        NOT NULL,
  amount     text        NOT NULL,
  created_at timestamptz NOT NULL DEFAULT now()
);
This constraint lives in the schema, not in application code — so it holds against a bug, a migration script, a direct database write, or an engineer in 2031 who never read the application.

Signatures are over canonical hashes, not rows

A signature made today must still verify years from now, even after the events table gains new columns. That only works if the exact bytes that were signed are reproducible. The build signs a hash of the canonicalized payload, never the database row:
canonicalize.ts — the single definition of the signed bytes
export function payloadHash(payload: unknown, version: CanonVersion = 'v1'): Buffer {
  const canon = canonicalize(payload, version);
  const signedBytes = Buffer.concat([Buffer.from(version, 'utf8'), canon]);
  return createHash('sha256').update(signedBytes).digest();
}
The canonicalizer is committed code with a sha256 registered in the database. The server refuses to start if the file on disk drifts from the registered hash — so the function that produced a signature can always be reproduced exactly. The browser verifier mirrors this function byte-for-byte, pinned by a parity test.

Lineage — why this is the asset

The property that makes a transaction insurable, financeable, and survivable.

One active policy, enforced in SQL

policy_versions — from schema.sql
CREATE UNIQUE INDEX policy_versions_one_active
  ON policy_versions (active) WHERE active = true;
A partial unique index makes “more than one active policy” unrepresentable. The rule is in the schema, not a convention the application is trusted to follow.

How the record stays append-only

The system never updates and never deletes. Every state transition appends a new event; every correction is a new event, not an edit. The buyer queue even distinguishes held from confirmed by whether a work order’s bilateral_event_id is null yet — a value that can only ever be set, and only through the verified insert path.
This is a property of how the system operates: there is no code path that updates or deletes an event. It is described here as behavior rather than as a database-level prohibition, which is the precise and honest framing.

Five of the seven invariants, enforced today

The full architecture defines seven structural invariants. This build, operating on the bilateral loop without settlement, enforces five of them — and adds two implementation-level guarantees of its own.
Architecture invariantIn this build?
Posting requires a Bilateral EventEnforced — non-nullable FK shown above
Lineage hash chain integrityEnforced — recomputed at verify time
Single active Policy VersionEnforced — partial unique index
Hard-coded triggers cannot be suppressedEnforced — first-payment and bank-change
Events are append-onlyEnforced — no update/delete path
Bilateral Event requires confirmed Work Order stateExercised when the full state machine lands
Settlement export requires approved Control trackOut of Session 1 scope — settlement is gated
The two not yet exercised both concern settlement, which Session 1 deliberately gates out. They are not unenforced by oversight — the demo never reaches the stage where they apply. On top of the five, this build adds two implementation guarantees the schema-level list doesn’t enumerate: signature verification as a precondition to insert, and WebAuthn attestation stored on every signature.

Session 1 scope

The demo proves the foundation deliberately before building on it. What that means precisely:

Implemented

The bilateral loop: Event, Policy Version, and Work Order as live objects, plus the Posting constraint (the table exists with its non-nullable FK, enforcing the guarantee even though it is never written this session).

Scoped for later

Case — the exception primitive — becomes a table when exception handling is built. Postings, GL coding, budget checks, ERP export, and settlement are gated until the bilateral loop is proven at volume.
The gate is explicit: downstream ledger automation is out of scope until the bilateral loop runs at 1,000 events, above a 70% confirmation rate, with a median confirmation under five minutes. The settlement_state column is even CHECK-locked to a single value, so the schema itself rejects a settlement write this session.
Why prove the loop first: the bilateral event is the entire architectural claim. If a co-authored, independently verifiable record can be created and checked, everything downstream is a configuration problem. If it can’t, no amount of downstream automation rescues it.

What an external reader should take away

Most of these guarantees are not promises in prose — they are constraints in a schema and a hash in a registry. A skeptical reader does not have to trust the operator; they can read the same constraints shown here, and on the verifier screen, recompute the proof themselves.

See the five primitives these enforce

Event, Policy Version, Work Order, Posting, Case — the objects this build instantiates.