Skip to content

Protocol & codecs

@johnhenry/oat-protocol and @johnhenry/oat-qr-fountain are the two layers neither custom element exposes directly, but both build on.

Every payload — a message, a file, a UI proposal, a signed decision, a bootstrap manifest — travels as an artifact: canonical CBOR, a content digest, and an optional Ed25519 signature.

import { buildArtifact, generateSigningKey, computeDigest, verifyArtifact } from '@johnhenry/oat-protocol';
const key = await generateSigningKey();
const artifact = await buildArtifact(payload, { signingKey: key });
// on the other end:
const ok = await verifyArtifact(artifact); // checks digest AND signature

The signature carries the signer’s public key inline. There’s no separate key-exchange step for cross-device use — a valid signature alone tells you some key signed it. Whether that key is one you should trust is a separate question, handled by trust-on-first-use (see Security model).

A sender can request capabilities (e.g. calendar.event.create); the effective grant is always the intersection of three sets:

effective = sender requested ∩ receiver policy ∩ user-approved grants

Rendering a UI is never itself authority. Declarative actions carry typed, receiver-mediated requests — never remote code, never a DOM handle back to the sender.

ui.decision — signed acknowledgments travel back too

Section titled “ui.decision — signed acknowledgments travel back too”

The receiver can build a signed acknowledgment of what happened (accepted/downgraded/rejected, which capabilities were actually granted/denied, a correlation token) and send it back over any channel — including a second <optical-send>/<optical-receive> pair, so a full round trip can happen entirely optically with no other transport.

extractUiDecision() refuses to extract anything without a verified signature, since a decision artifact is itself a claim that capabilities were granted — an unsigned one is worthless as an audit record.

The wire format: @johnhenry/oat-qr-fountain

Section titled “The wire format: @johnhenry/oat-qr-fountain”

LT (Luby Transform) fountain coding turns an artifact into a stream of QR frames that survives loss, duplication, and reordering — the receiver doesn’t need every frame, or frames in order, just enough of them.

import { encode, decode } from '@johnhenry/oat-qr-fountain';

This is the layer that makes <optical-receive> tolerant of a shaky camera angle or a frame the reader missed. It has no size cap of its own — see the throughput note on the overview page before assuming a transfer will be fast.