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-012: Provenance frontmatter for AI-authored entities

Date: 2026-05-25 Status: Accepted Author: Rômulo Febasi


Context

EU AI Act Article 50 enforcement starts on 2026-08-02 with penalties up to €15M or 3% of global turnover. Article 50 requires machine-readable provenance on AI-authored content; the C2PA norm has been propagating from images to text, and the practical standard emerging in the wild (Glean audit logs, Notion AI compliance) is to stamp the content itself with a small set of fields recording who asked for the change, what prompt produced it, when it was sanctioned, and who sanctioned it.

Synapse’s pending queue (BR-12.3) already gates AI writes behind a human accept. What is missing is the per-file record on each accepted entity. Without it, an exported Markdown file leaves no intrinsic trace of having been AI-authored: fine for a personal note, a problem for any regulated context.

The audit trail in .synapse/audit.ndjson (ADR-013, SYN-110) covers the workspace-wide story. ADR-012 covers the file-level story.

Decision

Every entity created or mutated through the pending-queue accept path (crate::core::pending::accept → apply_capture_fact or apply_update_status) carries five (or six) x-ai-* frontmatter fields:

x-ai-source: mcp:capture_fact     # how the proposal entered the queue
x-ai-decision-trace-id: pending:7 # stable, queryable trace id
x-ai-model: anthropic/claude-opus-4-7  # best-effort, optional
x-ai-prompt-hash: sha256:f8e2c1…  # sha256 of pending.payload
x-ai-accepted-at: 2026-05-25T15:30:00Z
x-ai-accepted-by: romulo@febasi.com.br

Names live under the x-ai- namespace so existing YAML readers, the doctor lint (BR-9.1), and any third-party tool treat them as forward- compatible extensions (BR-3.4 preserves order; BR-3.1 ignores unknown keys). They are stamped after the caller-supplied per-type fields so they read as a trailing banner in the markdown.

An escape hatch lives in cascade config:

[provenance]
inline = true              # default; emit stamps inline
accepted_by = "user@org"   # override $USER; defaults to env $USER, then "unknown"

When inline = false, the audit trail in .synapse/audit.ndjson remains the only provenance record: useful when the user prefers clean entity files but still needs the regulatory receipt.

The mutators that touch auxiliary data (link_entity, add_workspace_topic, add_workspace_tag) do not stamp anything into entities: they mutate links / manifest fields where per-row stamps do not fit the model. The audit trail still records them.

Consequences

  • + Every AI-authored entity is self-describing as such. git diff shows the stamps without any tool.
  • + The decision-trace-id links the file back to a pending row and to an audit.ndjson entry; reproducibility for syn audit replay (SYN-113) is one query away.
  • + Hash of payload, not the payload itself, in frontmatter, the body stays clean and the audit trail keeps the full payload for replay until it is pruned.
  • − Workspaces in regulated industries with inline = false lose the file-level guarantee. Documented; the audit trail compensates.
  • − A user who manually edits the markdown can strip the stamps. By design: the user owns the workspace. The audit trail in .synapse/audit.ndjson is append-only and lives outside the entity file, so it survives.

Open question (closed in v0.4: SYN-137): when an entity is mutated twice via the pending queue, the second accept overwrites the stamps with its own values. v0.3 lost the prior six fields on every re-mutation; v0.4 keeps them. Before stamping the new values, apply_to_raw snapshots the existing x-ai-* six-tuple into a mapping and prepends it to the x-ai-history array (newest first). The array is capped at [provenance] history_cap entries (default 10) so a hot-edited entity does not balloon its frontmatter indefinitely.

Alternatives considered

  • C2PA manifest sidecar files. Rejected: the markdown is the source of truth (BR-1.4). Sidecars duplicate state and drift.
  • Stamps in a meta: sub-mapping. Rejected: breaks BR-3.4 insertion order discipline and is harder to grep.
  • Audit-trail-only (no inline stamps). Rejected as the default, loses the AI-Act-grade file-level receipt that downstream tools (publishers, exporters) can read without parsing the audit log. Kept as the opt-out via inline = false.
  • PGP-signed entity files. Rejected: disproportionate to the threat model, breaks git diff ergonomics, ed25519 is reserved for the portable identity bundle (ADR-014) where the threat model actually warrants it.