What the store holds
Two things, written before the event is anchored:- The content you passed to
anchor, which was hashed in your process and never sent. - The canonical event record, the bytes
payload_hashcommits to. In TypeScript they are also on the result aseventBytes, in Python asevent_bytes.
sanning.logstore/v1. A store root
looks like this:
From 0.16.0, the store also keeps each record’s signed envelope, next
to the record itself under
envelopes/<payload_hash> (proof#57). A store
written before this release has no envelopes/ directory and reads exactly
as before; the layout marker stays sanning.logstore/v1.Configure a store
Pass a store when you build the anchorer.FsLogStore writes to a local
directory and is the development destination.
no-log-store). Both SDKs check this for every production record, before it is
signed. The record’s locator is the payloadRef (TypeScript) or payload_ref
(Python) you pass on the call if you pass one, and otherwise the answer from the
store’s locatorFor (TypeScript) or locator_for (Python). ObjectLogStore
and FsLogStore have that method. CallbackLogStore takes a locator function
as its second argument (locator= in Python).
A production record with no locator from either place throws
ProductionConfigError: the anchorer has no store and the call passes no
payloadRef, the store
has no locator method and the call has no payloadRef, or the method returns
nothing (an empty or blank string, a non-string) or throws. The message names
which one. Nothing is signed or sent, the chain does not move and no gap is
recorded, so the next record you anchor links to the last one that was
accepted. Building the anchorer never throws for this, because the SDK cannot
know whether your calls will carry a payloadRef. A payloadRef that is not a
valid URI, including an empty string, throws InvalidRefError in dev and
production alike. dev is otherwise unaffected: a locator method that
returns nothing (null, undefined or None) anchors with no payload_ref,
and one that throws throws its own error.
A payloadRef you pass is your own assertion: the SDK signs it as given and
cannot vouch that it points at bytes you kept. With no store configured, a
production call that carries one succeeds and logs a warning saying so.
If you drain the bytes yourself instead, keep them keyed by event id. They are
what an auditor’s check runs against.
Why the pack command reads the store
A hand-over pack is assembled by thebundle command, months after the run.
It reads the proofs from the Sanning index and the bytes from your store.
CONSOLE_API_KEY with the key from the console. There is no flag for
it, because a credential in argv lands in shell history and this is a command
people paste into tickets.
The flags mean the same in both languages:
--logs is optional in the flag grammar and required in practice. The
verb needs your record objects to work out which events are this agent’s:
subject.agent_id lives in the record, and a minimum-disclosure envelope
does not carry it. Without the store the command exits 2 and says so.
The verb verifies the whole pack, bundle and disclosed logs, before it writes
anything, so a pack that would fail at the auditor is never produced.
When a pack can be built
Sanning commits your records to the public record on an interval, rather than one transaction per event. Until the interval holding your records is sealed, there is no inclusion proof to put in a pack, and the command tells you so:3 (undetermined): run it again after the interval seals.
When nothing in the window matches at all, it exits 1.
The partial case exits 3 too, and for a different reason:
--allow-unstamped. The summary
then tells you the pack is short, and passing that on is your job, because the
artifact does not carry it:
Store the bytes in a bucket you own
For production, put the store behind object storage you control. The seam is the same in both languages: an object store inside a log store.endpoint with path-style
addressing makes S3-compatible backends behave alike.
On first use the store checks the bucket once, before it writes a byte, and
reports what it could not establish rather than assuming. A bucket that
already exists cannot have Object Lock turned on afterwards, so the check
tells you to create a fresh one instead of sending you to a console page that
cannot help.
A locator points at where the anchored bytes live, and it rides inside the
signed envelope, which is published permanently. Bucket names and object
keys routinely carry tenant, agent and model names, so treat anything that
can reach a locator as public. A
ref or payloadRef you pass yourself is
signed verbatim.Build a pack straight from the bucket
--logs takes a local store root or an s3:// URL:
SANNING_S3_ENDPOINT, SANNING_S3_ACCESS_KEY_ID and
SANNING_S3_SECRET_ACCESS_KEY in the environment. There is no flag for them,
because a credential in argv lands in shell history. A missing one is named
individually rather than reported as “credentials are missing”, so you know
which to set.
The reader credential is enough: s3:GetObject and s3:ListBucket, and
nothing that writes. See Set up the bucket.
A local copy still works, so syncing the prefix down first remains a valid
route when you would rather hand the command a directory.
What to read next
API keys and scopes
What a key carries, and why the pack command needs no extra grant.
Dev and production
What production means, and why a pack carries one environment.