Part III — System Architecture · Chapter 27
The Four Attestation Kinds
The Four Attestation Kinds
Same shape, four roles. Walk any attestation backward through its supports graph to the originating observations and re-hash the source bytes. Every link in that chain is independently falsifiable. That is the promise. This chapter is how it keeps.
ℹPrerequisites▼
Before reading this chapter, you should be comfortable with: Chapters 13–16 (Pipeline, Postgres, Ingestion, Primitives). Attestation kinds are the output types of different pipeline configurations.
A Canon attestation has four blocks: Witness, Findings, Refutation, Seal. Every attestation kind shares this shape. What changes between kinds is what the blocks contain and what role the attestation plays in the chain from raw source to courtroom argument.
There are four kinds. This chapter describes each one, explains why four are necessary and sufficient, and shows how a recipient walks the chain from any attestation back to its originating source bytes. The fifth kind — AuditAttestation — is introduced here and treated in full in Chapter 26.
At a glance
- The four attestation kinds — ObservationAttestation, SearchAttestation, BriefAttestation, and AuditAttestation — form a provenance chain from raw source bytes to courtroom argument, with each kind's Findings block citing IDs from the kind below it.
- Each kind has a distinct Witness/Findings/Refutation structure: an ObservationAttestation's Witness carries a content hash and custody chain; a SearchAttestation's carries a query embedding and result set; a BriefAttestation's carries synthesized claims each traceable to a SearchAttestation.
- The chain hash in each kind's Seal commits to everything that kind attests: swapping a source file, altering a search result set, or modifying a synthesized claim changes the chain hash, invalidates the Ed25519 signature, and fails the verifier at step 3.
Learning objectives
By the end of this chapter you should be able to:
- Map a given evidence scenario to the correct attestation kind — decide whether an acquisition, a retrieval, a synthesis, or a system audit is the subject — and explain why the other three kinds would be incorrect.
- Describe the Witness block structure for each of the four kinds: what fields are required, what each field commits to, and how the
content_hashor query embedding anchors verification. 3. Walk the chain from a BriefAttestation back to an ObservationAttestation by followingsupportsreferences across attestation IDs, identifying which seven-step verifier call applies at each level. 4. Explain why a SearchAttestation cannot replace an ObservationAttestation: what provenance question a SearchAttestation answers and what question only an ObservationAttestation can answer. ## The chain this chapter is about The most useful metaphor for the attestation chain is a supply chain with cryptographic receipts. Every step that transforms data issues a receipt. The receipts reference each other by ID. A recipient who receives a finished product — a paragraph in a legal brief, a claim about parental conduct — can follow the receipts backward: who processed this, from what input, at what time, and has the input changed since? In Canon's terms: - A BriefAttestation contains claims about a legal argument. Each claim cites a SearchAttestation. - A SearchAttestation contains the results of a retrieval query. Each result cites an ObservationAttestation. - An ObservationAttestation contains the hash of an acquired source file. That hash verifies the source bytes. Walk backward from brief to search to observation to bytes. Every step is independently verifiable. The chain is acyclic by construction (Canon R3: a claim'ssupportslist can only reference observations or earlier claims, never later claims, and never in a cycle). The verifier enforces this at step 5 of the seven-step protocol. > ▼ Why It Matters. > > In 2026 TPR proceedings, the parent's attorney may receive an opposing > party's evidence packet — a summary of contact failures, missed visits, > and communication lapses — assembled by a software system. Without the > attestation chain, the attorney cannot determine whether the summary > accurately reflects its sources or whether a model hallucinated, omitted, > or distorted evidence in the assembly process. > > With the attestation chain, the attorney can ask: show me the > BriefAttestation for this paragraph. Show me the SearchAttestation it > cites. Show me the ObservationAttestations for the search results. Run > the seven-step protocol on each. If any step fails, the chain has been > broken. If all steps pass, the chain is intact, and the attorney's challenge > must focus on the substantive quality of the claims, not on their provenance. > > The chain does not make the claims true. It makes their provenance falsifiable. ## ObservationAttestation: the foundation An ObservationAttestation attests to a specific acquisition: that a source was received at a specific time and that the hash of its bytes is what the attestation records. This is the foundational kind. Every SearchAttestation, BriefAttestation, and AuditAttestation ultimately traces back to ObservationAttestations. The chain starts here. The Witness block of an ObservationAttestation carries one or moreWitnessEntryrecords. Each entry has: -observation_id: a stable identifier for this specific observation. -source: the URI of the upstream source (an email inbox, a court docket, an iMessage export, a PDF at a specific S3 path). -received_at: the RFC 3339 microsecond UTC timestamp of acquisition. -custody_chain: an ordered list of custodian transitions between the source and the current system. -content_hash: the SHA-256 hash of the source bytes, prefixedsha256:. -content_reforcontent_inline: the bytes themselves, either as a retrievable URI or as base64. The model, fromschema.py:
# meridian/canon/schema.py (excerpt)
class WitnessEntry(BaseModel):
observation_id: str = Field(..., pattern=r"^obs-[A-Za-z0-9_-]+$")
source: str
received_at: str
custody_chain: list[CustodyEvent] = Field(default_factory=list)
content_hash: str = Field(..., pattern=r"^sha256:[0-9a-f]{64}$")
content_ref: Optional[str] = None
content_inline: Optional[str] = None
@model_validator(mode="after")
def _content_retrievable(self) -> "WitnessEntry":
if self.content_ref is None and self.content_inline is None:
raise ValueError(
"WitnessEntry MUST have either content_ref or content_inline (R2)"
)
return self
The validator at the bottom enforces Canon R2: if neither content_ref nor content_inline is present, construction fails. A WitnessEntry without retrievable content is not verifiable; it cannot satisfy the verifier's step 4 (witness content re-hash). The Pydantic model refuses to build it. The Findings block of an ObservationAttestation is minimal. Its claims are of inference_type: observation — they describe what was observed, not what was concluded. The gaps list can be empty for observation-type claims (the R5 validator in schema.py enforces that only non-observational inference
types must enumerate gaps; observation claims are exempt because an observation
is, by definition, not an inference). The refutation block must still run at
least one challenge, but the applicable challenge scope is narrower:
fabrication (does the hash match?) and temporal error (is the received_at
timestamp plausible?) are typically the only two that apply.
A minimal ObservationAttestation
{
"canon_version": "0.1.1",
"attestation_id": "01JABCDEF0123456789ABCDEF01",
"kind": "observation",
"issued_at": "2026-03-15T14:07:32.441Z",
"issuer": "meridian-cannon/ingest-v1",
"matter_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"subject": "iMessage export, Isabel iPhone, acquired 2026-03-15",
"witness": [
{
"observation_id": "obs-imsg-export-001",
"source": "file://localhost/mnt/data/exports/isabel_imessage_20260315.zip",
"received_at": "2026-03-15T14:07:31.000Z",
"custody_chain": [
{
"custodian": "technician:mw-0042",
"received_at": "2026-03-15T14:07:31.000Z"
}
],
"content_hash": "sha256:a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4",
"content_ref": "s3://meridian-evidence/matter-001/imsg-export-001.zip"
}
],
"findings": {
"method": "Direct hash verification of acquired zip archive against technician receipt.",
"claims": [
{
"claim_id": "claim-obs-001",
"statement": "The iMessage export file was received from technician mw-0042 at 2026-03-15T14:07:31.000Z with SHA-256 hash a3b4c5d6...",
"supports": ["obs-imsg-export-001"],
"inference_type": "observation",
"gaps": []
}
]
},
"refutation": {
"challenges": [
{
"challenge_id": "chal-obs-001",
"type": "fabrication",
"targets": ["claim-obs-001"],
"input": "Claim: the file was received with stated hash. Verify: re-hash content_ref bytes and compare to content_hash.",
"outcome": "survived"
}
],
"coverage": {
"applied": ["fabrication"],
"declined": [
{ "type": "omission", "reason": "Observation claim; no evaluative content that could be omitted." },
{ "type": "distortion", "reason": "Observation claim; no characterization to distort." },
{ "type": "temporal_error", "reason": "Timestamp verified by technician receipt; temporal check subsumed by fabrication challenge." },
{ "type": "attribution_error", "reason": "Single named custodian; no multi-actor ambiguity." }
]
}
}
}
The seal field is absent in this example. It is populated by emit.py after construction: emit.py canonicalizes the attestation, computes the chain hash, and signs it with the issuer's Ed25519 private key. Until emit.py runs, the attestation is a draft; the Attestation model in schema.py marks the seal field as Optional. ## SearchAttestation: attesting to retrieval A SearchAttestation attests to a specific retrieval operation. Its Witness block contains the query — the query text, the embedding vector, the retrieval method (BM25, dense, hybrid), and the parameters. Its Findings block contains the result set: the top-k documents retrieved, their relevance scores, and their observation IDs (linking each result to the ObservationAttestation that proved its provenance). Its claims assert that the result set is correct for the stated query and method. SearchAttestations are Phase E in the Meridian-Cannon implementation roadmap and are not yet implemented. Their absence from current AI legal research practice is a concrete gap worth naming. The SearchAttestation answers the question a recipient cannot ask today: What did the retrieval system actually look for, and what did it actually find? A legal researcher using an AI-assisted search tool cannot currently produce, in discovery, any artifact proving what query the tool ran or what results it returned. The retrieval step is opaque. Canon's SearchAttestation closes that gap: the retrieval step is attested with the same structure as every other step — Witness, Findings, Refutation, Seal. The recipient who receives a BriefAttestation and wants to challenge the retrieval step — who wants to know whether relevant documents were omitted from the search results — has a path: follow the BriefAttestation's claim supports to the SearchAttestation, read its Findings block to see the actual result set and scores, check the query in its Witness block, and run a parallel retrieval with the same query and parameters to verify the result set is reproducible. > ◆ Going Deeper — The SearchAttestation as the missing link in AI legal research. > > Current commercial legal AI tools (Westlaw AI, Lexis+ AI, CoCounsel) > retrieve cases and generate summaries without attesting to the retrieval > query. A lawyer who uses one of these tools cannot produce, in response to > an opposing motion, a document proving what the tool searched for, what > parameters it used, or what it did not find. > > This creates a specific discovery liability. If opposing counsel asks "what > did your AI research tool retrieve when it found no precedent for X?", the > current answer is "we don't know; the tool doesn't record that." A > SearchAttestation makes the answer "here is the sealed record of the query > and result set, independently verifiable by any recipient with the same > embedding model." > > The SearchAttestation also enables a form of retrieval audit that is > currently impossible: comparing two SearchAttestations for the same query > at different times, and verifying that the result set did not change between > them (or documenting that it did, and why). For a matter where the evidence > corpus evolves over time — new documents are ingested, old documents are > produced to opposing counsel — the ability to attest to "what the search > found on March 15" versus "what the search finds on April 30" is directly > relevant to preservation obligations and spoliation risk. ## BriefAttestation: attesting to synthesis A BriefAttestation attests to a synthesized document: a legal brief section, an expert report paragraph, a case summary, a motion's factual background. Its Findings block contains the synthesized text, broken into individual claims. Each claim's supports list references either ObservationAttestations (for direct factual claims) or SearchAttestations (for claims derived from retrieval). The chain from raw source to courtroom argument is complete. BriefAttestations are Phase F in the roadmap and are not yet implemented. An AI-drafted motion section carries no attestation chain today: a judge cannot verify that its citations are accurate, that its factual summaries reflect their sources, or that the retrieval behind its research was sound. A BriefAttestation changes that. The Findings block of a BriefAttestation is the most complex of any kind: individual claims must be specific enough to be challenged and traced, which means the synthesis step must produce granular, claim-per-sentence or claim-per-assertion output rather than paragraph-level summaries. The challenge harness (Chapter 19) runs against each claim in the BriefAttestation's Findings. The refutation block documents the outcome. A recipient who challenges a BriefAttestation's claim that "the parent attempted contact on March 15" follows this path: 1. Find the claim in the BriefAttestation's Findings block. 2. Read its supports list. It points to a SearchAttestation. 3. Read that SearchAttestation's Findings block. It shows the query ("parent contact march 15") and the top-3 results, each with an observation_id. 4. Read each ObservationAttestation. Verify the content hashes match the stored bytes. 5. Read the bytes. Confirm that the text in the iMessage export does or does not show a contact attempt on March 15. This is the chain the recipient can walk. It is documented, sealed, and independently falsifiable at every link. ## AuditAttestation: attesting to the system An AuditAttestation attests to a complete audit trail — every transform, every query, every production — over a specific time range. It is used to prove that the system itself was not tampered with during a given period. AuditAttestations are described in full in Chapter 26. The summary here: the audit_log table in the Meridian-Cannon schema is hash-chained. Every row's chain_hash is computed over the previous row's chain_hash plus the current row's content. A row that was inserted, deleted, or modified after the fact will break the chain at that point. An AuditAttestation seals a snapshot of the chain at a specific moment: it records the chain's starting and ending hashes, the time range, and the count of events in the period. A recipient who receives the audit log and an AuditAttestation can re-walk the chain and verify that the log matches the attestation. The AuditAttestation matters for a specific legal question: when a party claims spoliation — that evidence was destroyed, altered, or suppressed after a litigation hold attached — the AuditAttestation for the relevant time period provides a direct answer. Either the audit chain is intact (and the claim of spoliation must be directed at something outside the system's custody) or it is broken (and the break is located, dated, and visible). ## Why four and not more The four kinds cover the complete chain from acquisition to argument. Each step — acquiring a source, enriching it, retrieving it, synthesizing it into a legal argument — is covered by exactly one kind. A fifth kind either duplicates an existing kind or sits outside the chain. Some alternatives worth considering and rejecting: An EnrichmentAttestation. The stub for Chapter 20 listed Enrichment as a distinct kind between Observation and Search. An enrichment step — entity extraction, embedding, classification — transforms an observed document into structured metadata. In Canon v0.1.1's current schema, enrichment outputs are recorded in the ObservationAttestation's Findings block as additional claims. A separate EnrichmentAttestation kind would add one level to the chain without adding coverage; the enrichment claims are already attested and challengeable within the ObservationAttestation's refutation block. The current spec folds enrichment into Observation; this chapter reflects that decision. A ProductionAttestation. When a party produces evidence to opposing counsel, one might attest to the production event itself — what was produced, in what format, to whom, at what time. The Meridian-Cannon schema handles this in the productions table (schema file 20_provenance.sql), which is linked to the audit log and covered by AuditAttestations. A separate ProductionAttestation kind would be redundant. A TranscriptAttestation. Audio transcription produces a text document from an audio source. This is an enrichment step: the audio is the observed source (ObservationAttestation), the transcript is a derived claim in the Findings block. The chain is already covered. Four is enough because the chain has four logical levels: source, search, synthesis, audit. One kind per level. The chain is complete. ## MediaAttestation (v0.2.0) Media evidence — photographs, audio recordings, video — requires a provenance mechanism that travels with the file, not just with the database row that describes it. A JPEG or MP4 produced in discovery can be separated from its acquisition record; the content hash in an ObservationAttestation verifies the file's integrity but does not embed provenance into the file itself. In v0.2.0, MediaAttestation uses C2PA (Coalition for Content Provenance and Authenticity) manifests to embed provenance directly into media files:
pip install meridian-canon[c2pa]
C2PA is an open standard (ISO/IEC 21122) that attaches a cryptographically signed manifest to JPEG, MP4, WAV, and other formats. The manifest includes the acquisition source, the acquisition timestamp, a SHA-256 hash of the content, and the signing certificate. Because the manifest is embedded in the file, it persists through format-preserving operations such as email attachment, cloud storage upload, and courtroom presentation.
Key fields in MediaAttestation: | Field | Description | |---|---| | media_type | MIME type of the source file (image/jpeg, video/mp4, audio/wav, etc.) | | source_url | URI of the acquisition source | | acquisition_timestamp | RFC 3339 timestamp of acquisition | | manifest_hash | SHA-256 of the embedded C2PA manifest | | certificate_pem | PEM-encoded signing certificate | When the c2pa-python package is not installed, MediaAttestation falls back to SHA-256 hash verification alone — the same path as a standard ObservationAttestation. The C2PA manifest is additive; its absence does not break the chain. MediaAttestation is a specialization of ObservationAttestation, not a fifth attestation kind. It uses the same Witness/Findings/Refutation/Seal structure; what differs is the Witness block's content_ref, which points to a C2PA-embedded file rather than an unmodified binary blob, and the addition of the manifest_hash and certificate_pem fields in the attestation metadata. > § For the Record — FRE 901(b)(9). > > "Evidence describing a process or system and showing that it produces an > accurate result." > > A sealed ObservationAttestation is direct evidence satisfying FRE 901(b)(9) > for the acquisition process. The attestation describes the process (ingest > pipeline, technician receipt, hash verification), identifies the system > (Meridian-Cannon v1, Ed25519 key fingerprint), and shows that it produced an > accurate result (fabrication challenge survived, hash verified). A court > admitting digital evidence under FRE 901(b)(9) has a readily applicable hook > for the ObservationAttestation. ## The running case: which kind covers what The 2026 TPR proceeding produces evidence across six personal-data systems: Gmail, iMessage, Apple Health, Facebook Messenger, court dockets (CCAP), and phone records. The parent's attorney is building a response to the caseworker's contact-failure claim. (a) The iMessage export from Isabel's iPhone → ObservationAttestation. The export is a zip file containing iMessage records from the parent's device. A technician acquires the file, hashes it on receipt, logs the acquisition event with a custody entry, and emits an ObservationAttestation. The Witness block carries the hash and the source URI. The Findings block claims: "This file was acquired from X at Y time and its bytes hash to Z." The refutation block runs FABRICATION (verify the hash) and declines the rest. The ObservationAttestation's observation_id becomes the anchor for every downstream attestation that references these iMessage records. (b) The keyword search for "pickup time" in the attorney's evidence review → SearchAttestation. The attorney's review system runs a hybrid BM25 + dense retrieval query for "pickup time" over the full evidence corpus. The SearchAttestation records the query text, the query embedding vector, the retrieval method parameters (top-k = 20, BM25 weight = 0.4, dense weight = 0.6), and the 20 results with their scores. Each result carries an observation_id pointing to an ObservationAttestation. The attorney can verify that the retrieval was run correctly by re-running the same query with the same parameters and comparing results. The SearchAttestation's seal commits to the result set as of the query time; future re-runs that return different results do not invalidate the sealed attestation but do document that the corpus changed. (c) The paragraph in the attorney's motion asserting "the parent attempted contact on March 15" → BriefAttestation. The motion paragraph is a synthesized claim. The BriefAttestation's Findings block breaks the paragraph into individual claims. The claim "the parent attempted contact on March 15" carries a supports entry pointing to the SearchAttestation from step (b), which in turn points to an ObservationAttestation for the iMessage export that contains the March 15 message thread. The chain is: motion paragraph claim → SearchAttestation → ObservationAttestation → iMessage export bytes → SHA-256 hash. Every link is sealed and independently verifiable. (d) The three-month period of evidence collection → AuditAttestation. The evidence collection ran from January through March 2026. At the end of March, the system emits an AuditAttestation covering the three-month period. It seals the starting and ending chain_hash values of the audit_log table, the count of events in the period (ingestions, queries, productions, key rotations), and the time range. A recipient who receives this AuditAttestation can re-walk the audit chain and verify that no events were deleted or inserted after the fact. The AuditAttestation for this period is the artifact that answers a spoliation challenge. If opposing counsel argues that evidence was destroyed after the litigation hold attached on February 1, the AuditAttestation for February through March either shows an intact chain (the claim is wrong) or shows a break (the break is located and dated). > ☉ In the Wild — Digital forensics in federal prosecution. > > In federal child exploitation prosecutions, the forensic requirements go > beyond file production. Forensic examiners must produce not just the files > but the chain showing where they were found (which drive, which directory > path, which partition offset), when they were acquired, using what imaging > tool, and verified by what hash. The Federal Rules require that the > evidence be authenticated — that the proponent show the files are what they > claim to be (FRE 901(b)(9)) — and the forensic chain is what satisfies that. > > A Canon ObservationAttestation over the forensic acquisition would be the > exact artifact the prosecution needs: it seals the acquisition source, the > timestamp, the hash, and the custody chain in a single signed document that > any recipient can verify without the examiner's cooperation. A defense expert > who runs the seven-step protocol on the ObservationAttestation and finds it > valid has confirmed the acquisition's integrity; a defense expert who finds > step 4 failing (hash mismatch) has found evidence of post-acquisition > modification. > > An AuditAttestation over the forensic pipeline would answer the defense's > most common challenge: that the evidence was mishandled between acquisition > and trial. The AuditAttestation commits to the audit log's chain integrity > for the relevant period. A gap in that chain — a period where the log cannot > be verified — is visible, located, and dated. It is not an argument; it is > a measurement. ## Walking the chain The meridian/canon/walk.py verifier implements the seven-step protocol for any attestation kind. Walking a BriefAttestation chain back to its ObservationAttestations requires running the verifier multiple times: once on the BriefAttestation, then on each SearchAttestation in its supports graph, then on each ObservationAttestation in the search results. The verifier's step 5 (supports closure) checks that every supports entry in the Findings block resolves to an observation_id in this attestation's Witness block or to a claim_id earlier in this Findings block. It does not automatically fetch and verify the referenced attestations; cross-attestation walking requires the caller to resolve attestation_id references and invoke the verifier on each resolved attestation in turn. The walk function in walk.py for a single attestation:
# meridian/canon/walk.py (excerpt)
def walk(attestation: dict) -> dict:
"""Run the seven-step verification protocol on a single attestation."""
s1_msg, pem = _step1_public_key_fetch(attestation["seal"])
s2 = _step2_signature_verify(pem, attestation["seal"])
s3 = _step3_chain_hash_recompute(attestation)
s4 = _step4_witness_content_hashes(attestation)
s5 = _step5_supports_resolution(attestation)
s6 = _step6_refutation_targets(attestation)
s7 = _step7_coverage_assessment(attestation)
binary_steps = [s1_msg, s2, s3, s5, s6]
valid = all(s == "pass" for s in binary_steps) and s4["failed"] == 0
return {"verdict": "valid" if valid else "invalid", "steps": {...}}
A complete chain walk for the running case's BriefAttestation requires:
walk(brief_attestation)— verify the brief's seal and supports closure. 2. For each SearchAttestation referenced in the brief's supports:walk(search_attestation)— verify the search's seal and result-set integrity. 3. For each ObservationAttestation referenced in the search results:walk(observation_attestation)— verify the source hash. The count in the running case: one BriefAttestation, one SearchAttestation (the "pickup time" query), between five and twenty ObservationAttestations depending on how many sources were retrieved. A motion section with three paragraphs, each backed by two or three search queries, each pulling from ten to twenty source documents, might walk back through sixty to one hundred and twenty ObservationAttestations. This is not a theoretical concern; it is why the attestation store's ID indexing and the verifier's batching support (not yet implemented) matter. > ✻ Try This. > > Draw the four-level attestation stack for the running case on paper. Use > boxes for attestations and arrows forsupportsreferences. > > Start from the top: the motion paragraph about March 15 contact is the > BriefAttestation. Draw an arrow down to the SearchAttestation it references. > Draw arrows from the SearchAttestation to each ObservationAttestation in its > top-k results. For each ObservationAttestation, draw an arrow to the source > (label it with the source type: iMessage, Gmail, CCAP, phone records). > > How many ObservationAttestations are in the chain for this one paragraph? > How many for the motion as a whole, if the motion has twelve paragraphs > each backed by one search query retrieving ten results? > > Now: what happens to the chain if one ObservationAttestation fails step 4 > (hash mismatch)? The source bytes changed after the attestation was issued. > Does the motion paragraph survive? Does the SearchAttestation survive? > Does the BriefAttestation survive? Walk the failure forward through the > chain. The answer is: the ObservationAttestation is invalid; the > SearchAttestation that references it has a broken link; the > BriefAttestation's claim that cites that search result is unverifiable. > The motion paragraph is not necessarily wrong, but its chain of provenance > has a known gap, and opposing counsel is entitled to know where the gap is. ## Seal Types (v0.2.0) In v0.1.1, every attestation had one seal type: a custodian-signed Ed25519 seal. In v0.2.0, there are four seal types. An attestation can carry one or more: | Seal type | Mechanism | Installation | |---|---|---| |custodian_signed| Ed25519 signature over JCS-canonicalized payload; stored insealstable | Default; always available | |dsse| DSSE envelope (PAE + Ed25519);emit_dsse()path (see Chapter 16) | Default; always available | |media_c2pa| C2PA manifest embedded in the media file |pip install meridian-canon[c2pa]| |transparency| Rekor transparency log entry; append-only, publicly auditable |pip install meridian-canon[transparency]| The transparency seal extends custodian signing with a third-party timestamp and inclusion proof. WhenMERIDIAN_REKOR_ENABLED=1, theseal_attestationDagster asset (or theemit.pyfunction when called directly) submits the sealed envelope to a Rekor instance. The returnedentry_uuidandlog_indexare stored inrekor_entries(schemaE0_rekor.sql). The Rekor log is append-only and publicly auditable; any party with theentry_uuidcan verify the submission independently of the original custodian. The four seal types are not mutually exclusive. An attestation sealed withcustodian_signed+transparencyhas both a custodian signature verifiable offline and a public log entry verifiable against Rekor. For evidence that will be distributed to opposing counsel or submitted to a court, the combination ofdsse+transparencyprovides the strongest externally verifiable provenance.
The AttestationKind enum
# meridian/canon/schema.py (excerpt)
class AttestationKind(str, Enum):
OBSERVATION = "observation"
ENRICHMENT = "enrichment"
SEARCH = "search"
BRIEF = "brief"
AUDIT = "audit"
The ENRICHMENT value in the enum reflects the roadmap's earlier four-kind plan (Observation, Enrichment, Search, Brief). As of Canon v0.1.1, enrichment outputs are carried in ObservationAttestation Findings blocks rather than in a separate kind. The enum value is preserved for schema compatibility but is not emitted by the current emit.py implementation. A future revision may reintroduce EnrichmentAttestation as a distinct kind if the volume of enrichment metadata outgrows what fits cleanly in the ObservationAttestation's Findings block.
MediaAttestation extends ObservationAttestation by embedding a C2PA manifest directly into the media file — the manifest travels with the JPEG or MP4 through email, cloud storage, and courtroom presentation, unlike a database row that can be separated from the file. - The four seal types — custodian_signed, dsse, media_c2pa, and transparency — are not mutually exclusive; a single attestation can carry any combination, and for court-facing attestations the combination of dsse + transparency provides the strongest externally verifiable provenance. - A court-facing attestation should carry both dsse and transparency seals: dsse confirms issuer identity via PAE-structured Ed25519, while transparency anchors the sealed artifact in a public append-only Merkle log that anyone can audit without contacting the issuer. - c2pa-python adds embedded provenance that persists through format-preserving operations; without it, MediaAttestation falls back to SHA-256 hash verification alone — functionally equivalent to a standard ObservationAttestation. AttestationKind enum and the Attestation model in schema.py. For each kind, identify which fields in the Witness block would typically differ. (Hint: a SearchAttestation's Witness block attests to a query embedding; an ObservationAttestation's attests to source bytes. What does that mean for source and content_hash?) 2. Run python -c "from meridian.canon.emit import build_observation; help(build_observation)". Read the function signature. Identify what arguments it requires and which attestation fields it populates automatically. ### Core 3. Emit a valid ObservationAttestation for a local file of your choice (a text file, a PDF, any file in your test corpus). Use meridian.canon.emit and verify the output with meridian.canon.walk. Confirm step 4 passes by verifying the file's SHA-256 hash against the content_hash in the Witness block. 4. Design the Witness block for a SearchAttestation. What fields would you include? Write a JSON sketch of the Witness block for the "pickup time" query in the running case. Include: query text, embedding dimension, retrieval method, top-k parameter, and retrieval timestamp. 5. A paralegal runs a keyword search for 'custody exchange' across the case's text message corpus and uses the top-3 results to write a paragraph in the attorney's brief. Draw the attestation chain this creates: identify the kind of attestation for the search, for the brief paragraph, and for each source text message. Which attestation's chain_hash appears in the brief attestation's witness.custody_chain? ### Stretch 6. The chain walk for a BriefAttestation with twelve paragraphs and ten search results per paragraph requires walking up to 120 ObservationAttestations. Design a batched walk function signature that accepts a BriefAttestation and recursively resolves its entire supports graph. What should it return? What should it log? 7. An AuditAttestation covers a time range of evidence collection. Design the Findings block claims for an AuditAttestation covering January through March 2026 in the running case. What claims would you make? What would their inference_type be? What gaps would they disclose? 8. FRE 901(b)(9) requires "evidence describing a process or system and showing that it produces an accurate result." Write a one-paragraph expert witness declaration (suitable for attaching to a motion) that uses an ObservationAttestation to satisfy FRE 901(b)(9) for an iMessage export. Cite the specific fields of the attestation that correspond to the rule's requirements. ## Build-your-own prompt For your capstone matter: which attestation kinds will your pipeline emit? Every evidence pipeline must emit ObservationAttestation for every source acquisition. If your pipeline supports search (keyword or semantic), it must emit SearchAttestation for every retrieval used to inform a brief or report. If you produce any synthesized document — a timeline, a brief, a summary — it must be backed by BriefAttestation. Map your evidence workflow to the four kinds before writing a single line of emission code. The mapping is architecture; the code is implementation. ## Further reading - Meridian-Canon-Revised §6.10 (attestation kinds), §8.3 (seven-step verification protocol), §4.1.2 (four-stage chain). - meridian/canon/schema.py — AttestationKind, WitnessEntry, Attestation. - meridian/canon/walk.py — the reference seven-step verifier. - meridian/witness/wrapper.py — attest_acquisition and backfill_observations. - NIST SP 800-86, "Guide to Integrating Forensic Techniques into Incident Response" — chain of custody requirements for digital evidence. - Federal Rule of Evidence 901(b)(9); Advisory Committee Notes thereto (authentication by process or system). - Garrie & Hill, "Electronic Discovery and Digital Evidence in a Nutshell" (2d ed.) — the forensic chain-of-custody framework that Canon's ObservationAttestation formalizes. - Russ Cox, "Transparent Logs for Skeptical Clients" — the append-only log model that informs AuditAttestation design. - C2PA specification: https://c2pa.org/specifications/ — the Coalition for Content Provenance and Authenticity standard for media provenance. - c2pa-python library: https://github.com/contentauth/c2pa-python — the Python binding for embedding and reading C2PA manifests. - Rekor (Sigstore): https://github.com/sigstore/rekor — the transparency log used by the [transparency] seal type.
Next: Chapter 21 — Schema Design & Migrations.