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 diffshows 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 = falselose 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.ndjsonis 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 differgonomics, ed25519 is reserved for the portable identity bundle (ADR-014) where the threat model actually warrants it.