Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ADR-035: syn verify report schema + canonical logical dump

Date: 2026-06-18 Status: Accepted Author: Rômulo Febasi

Foundation for SYN-262 (the de-risking spike of the v1.0 syn verify headline, SYN-253). Companion to ADR-026 (audit), ADR-027 (bitemporal), ADR-030 (git substrate). Grounded in docs/researchs/2026-06-16-provable-non-corruption-syn-verify.md.


Context

The v1.0 thesis is provable non-corruption: prove the AI memory was not silently corrupted, offline and per-fact. The keystone invariant (INV-1) is that the disposable SQLite index is a reproducible build of the Markdown, digest(reindex(markdown)) == digest(live index). A hosted competitor cannot offer this, because there is no user-side source to reproduce from.

The research brief flagged one real prerequisite: the .db file is not byte-deterministic. Rowids, FTS5-internal pages, WAL framing, and float vector blobs all vary between two indexes that are logically identical. So we cannot hash the file; we must hash a canonical logical manifest. SYN-262 is that spike; it must produce a dump(ws) that is byte-stable across calls and across a reindex --full, or every downstream invariant is built on sand.

The brief also fixed the proof-artifact shape: an in-toto Statement (subject digest + predicate invariant table) signed with the user’s existing ed25519 identity (reuse SYN-247 receipts: no new crypto). The schema must be frozen before SYN-263 populates it.

Decision

1. Canonical logical dump. core::verify::dump(ws) emits a sorted, line-oriented manifest. Each line is "<table>\t<primary-key…>\t<canonical-json-of-columns>"; lines are sorted lexically, so the output groups by table then primary key and any divergence is a localized line diff that names the offending fact (the basis for the SYN-269 rebuild attestation). JSON is canonicalized with recursively sorted object keys, independent of serde_json’s preserve_order feature. The digest is inputs_hash = sha256:<hex> of the dump bytes.

The dump covers exactly the rows that reindex deterministically reproduces from the Markdown (plus git for last_modified_commit): entities, tags, the deterministic frontmatter-projected links, and entity_fields.

It excludes, by construction:

  • entities_fts: FTS5-internal storage, fully derived from entities;
  • pending: the proposal queue; mutable state reindex does not reproduce, with a non-content AUTOINCREMENT rowid;
  • reindex_ledger: a machine-local (mtime, size) skip cache;
  • sqlite_* internals and the feature-vector float embedding blobs, which are model-dependent and not bit-reproducible;
  • enrichment links (mentions:*): see §Consequences.

2. Report schema synapse.verify-report/1. An in-toto Statement:

{
  "schema": "synapse.verify-report/1",
  "subject":   { "workspace": "...", "git_ref": "HEAD", "inputs_hash": "sha256:…" },
  "predicate": {
    "tool_version": "1.0.0",
    "invariants": [ { "id": "INV-1", "title": "…", "status": "pass|fail|skip", "evidence": "…" } ],
    "checkpoint": "…|null",
    "trust_root": "local_only|checkpoint_witnessed|remote_witnessed",
    "verified_at": "RFC-3339"
  },
  "signature": "base64|null",
  "public_key": "base64|null"
}

trust_root is mandatory and always surfaced: the audit log is tamper-evident, not tamper-proof: a lone local clone cannot detect its own owner’s rewrite without a witness. The Rust data model (core::verify::Report et al.) is defined in SYN-262; the invariant evaluation, signing, and the syn verify command itself land in SYN-263.

Consequences

  • Improves: the index becomes a provable reproducible build of the Markdown; the digest is reproducible across machines (anyone with the repo at git_ref recomputes a bit-identical inputs_hash); a divergence is localized to a named fact, not “the .db differs”.
  • Worsens / limits: syn verify proves the integrity of the deterministic projection of the Markdown, not of model-derived enrichment (embeddings, NER edges). Covering enrichment would require pinning model@precision and is deferred.
  • Real drift surfaced (open): building the dump exposed that the create path writes mentions:* enrichment links that reindex does not regenerate, so the live index and a reindex --full index diverge on those edges (and --full silently drops them). We exclude mentions:* from the dump (same class as float vectors: heuristic/model-derived, not a pure function of reindex(markdown)). The underlying disposability gap, reindex not regenerating enrichment: is a genuine finding to track as a follow-up (SYN-271), not fixed here.
  • Open: the mentions: prefix is the current discriminator for enrichment edges; if future enrichment adds non-mentions: rels, the exclusion predicate must grow with it.

Alternatives considered

  • Hash the .db bytes. Rejected: non-deterministic (rowids, FTS pages, WAL, float blobs): would report corruption on every benign rebuild.
  • Include enrichment links / embeddings in the dump. Rejected: they are model-derived and not reproduced by reindex, so the dump would never be a fixpoint. Pin model@precision later if they must be covered.
  • Make reindex re-run enrichment so the dump can include it. Out of scope for the canonicalization spike, and NER output is not bit-stable across model versions regardless. Tracked separately (SYN-271).
  • A bespoke binary manifest / protobuf. Rejected: the line-oriented text manifest is human-inspectable and diffable (SYN-269) with zero new deps.
  • A non-signed report. Rejected: the brief requires a portable, signed attestation; reusing SYN-247 receipt signing costs no new crypto.