Assay is append-only in the app. There is no general edit of a span or a turn. That does not stop someone who can open the store file from changing a row and saving it. From 0.49.0 the store also carries a hash-linked log. A rewrite is detectable. It is not prevented. Assay never calls an unwitnessed chain immutable. This is not a blockchain. There is no public copy of your turns.

Three grades

Every surface that talks about integrity should name one of these:
GradeWhat is trueWhat is not true
unsealedThe log is not on yet. Old rows could have been edited before seal.Nothing about later edits.
chainedThe row is in the hash-linked log. A lazy edit of one hashed field fails assay verify.Someone who rebuilds every hash in the file still passes a local walk.
anchoredA witness outside this file has a receipt for a head that covers this row.Root on the machine can still delete the disk. They cannot make an old receipt match a new history.
The product claim — “somebody cannot quietly rewrite a session and present the file as original” — is the anchored grade. chained is what you get on the way there. It still catches accidents.

What is hashed

Each new span or turn is hashed onto the previous head. The hash covers the fact as stored: who, when, which sitting, tokens, cost, and a hash of the message text. Not hashed:
  • The sealed ciphertext of the conversation (you may rotate keys; the hash of the plaintext still binds the words)
  • Labels you add later (type, objective, product, cost correction)
A label added after capture does not break the chain. That is on purpose. Rows that existed before you sealed the store stay legacy. Seal records a digest of what was there. It does not time-travel.

Seal

A new empty store from 0.49.0 writes genesis on first open. A store that already had rows stays unsealed until you say so:
assay seal --yes
That cannot be undone. New writes then chain. --yes is required. If a binary older than 0.49.0 kept writing after seal, those rows sit outside the chain and assay verify fails. Chain them without a new genesis:
assay seal --catch-up --yes
Rows from before seal stay unchained. Upgrade the binary that writes the store before you rely on the chain. Config and binary must match. assay --version is the check. These verbs are SQLite. Pass --db. They do not walk a Postgres store.

Verify

assay verify
assay verify --strict
assay verify --json
Walks the log, recomputes hashes, checks that chained facts still match.
ResultExit
Chain matches0
A hashed field was edited, or post-seal rows were not chained1
Unsealed, without --strict0 (warning)
Unsealed, with --strict1
Run this on a schedule. If the job ignores a non-zero exit, you will not see a break.

Witness (assay attest)

Local verify is necessary and not enough. The operator who owns the file can rebuild the chain. assay attest builds a checkpoint of the current head (store id, sequence, head hash) and sends it to a witness you configure.
{
  "attest": {
    "witness": "none",
    "every": "30m"
  }
}
witnessWhat happens
none (default)No network. Grade stays chained at most. Never printed as anchored.
httpsPOST { "digest" } to attest.url. Nothing else: no content, no meta, no principal, no session.
otsDigest to public OpenTimestamps calendars. A calendar receipt that has not confirmed on Bitcoin is not anchored. Until then the grade is chained.
assay attest --dry-run
assay attest
--dry-run prints the digest and sends nothing. Sweep submits when witness is not none. An old receipt binds the head at that sequence. History after that seq is chained-only until the next attest.

What this does not do

  • Stop someone with root from destroying the disk.
  • Recover conversation text after shred. Lose the key and the words are gone.
  • Force every install onto the network. Default witness is none.
  • Make pre-seal rows retroactively anchored.
See Honesty for numbers, Privacy for keys and what may leave, CLI for the verbs, Config for attest.