ADR-005: AI Label on Every Artifact, Embedded at Storage
- Status
-
Accepted
- Date
-
2026-09-25
- Authors
-
Netresearch DTT GmbH
Context
Every artifact of this extension is synthetic: TTS voices reading an LLM-written script, images rendered from LLM-written copy or drawn by an image model, and texts written by an LLM. The EU AI Act, Art. 50(2), requires the output of such systems to be "marked in a machine-readable format and detectable as artificially generated or manipulated". The artifacts leave the backend as files an editor downloads (MP3, WebVTT, PNG) and as text an editor copies, so the marker has to travel with the file, not only live in the database.
The formats and tools at hand:
- The podcast MP3 is written by ffmpeg's concat demuxer
(
Ffmpeg). Measured with ffmpeg 7.1: the output starts with an ID3v2.4 tag holding only ffmpeg's ownAudio Stitcher TSSEframe, whatever the input segments carried. - The Schaubild
htmlvariant and flat story slides are PNGs written by Chromium (render.);cjs html_and story slides on an AI background are re-encoded by the GD compositor, which drops every ancillary chunk. Thebg ki_variant is the image model's PNG as returned (nr-llm sends noimage output_, and gpt-image models default to PNG).format - The extension has no image- or audio-metadata library as a dependency, and the repository asks to add none without cause. getID3 (MP3) or a PNG/XMP toolkit would cover far more than the three chunk types and four frames needed here.
sys_in TYPO3 core hasfile_ metadata title,descriptionandalternative; the extension does not extend core tables.
Decision
- One provenance value per artifact.
Aicarries the generator (Provenance nr_as TYPO3 reports it), the IPTC digital source type and the models this extension knows. Every generator builds it and stores itsrepurpose <version> toas theArray () aiblock of the artifact metadata:Label ai,Generated: true generator,digitaland, where known,Source Type models. - IPTC digital source type as the vocabulary. The full AI image, the
podcast and the texts are
trained; the HTML renders — LLM-written copy, optionally on an AI background, set into the human-made branded template — areAlgorithmic Media composite. It is the vocabulary the IPTC Photo Metadata Standard and C2PA use for synthetic media.With Trained Algorithmic Media -
The marker is embedded at storage, in PHP.
Jobtakes the provenance and passes the bytes throughFile Storage:: store () Aibefore writing them to FAL — after the last re-encode, so nothing in the pipeline can drop the marker again:Content Marker - PNG:
tEXt SoftwareandCommentplus aniTXt XML:packet withcom. adobe. xmp Iptc4xmp,Ext: Digital Source Type xmp:andCreator Tool dc:, inserted right afterdescription IHDR. The image data is not touched. Thedc:keeps the UTF-8 text; the Latin-1description tEXt Commentgets it transliterated (ébecomese). - PNG with a
cachunk (a C2PA manifest): left byte-identical. Any change would break the C2PA content hash binding, so the manifest would fail validation. Such a manifest from the image model is expected to declare the AI origin itself — see the assumption under Consequences.BX - MP3: an existing ID3v2 tag is replaced by an ID3v2.3 tag with
TXXX:,AI- generated = true TXXX:and aDigital Source Type COMMframe. Replacing rather than merging keeps the writer to one tag version (ID3v2.3, the version every player reads). The encoder'sTSSEof the replaced tag — ffmpeg'sLavf…— is carried over; this extension does not encode audio, so without one it writes noTSSEand names itself inCOMMonly. The old tag is read as ID3v2.3 (plain frame sizes) or ID3v2.4 (syncsafe frame sizes); an extended header is skipped. An unsynchronised tag (header flag0x80) or an ID3v2.2 tag is replaced without carrying itsTSSE— its frame bytes are not read. The audio after the old tag is kept byte for byte in every case. - WebVTT: a
NOTEblock naming the AI origin right after theWEBVTTheader (a comment block players ignore); the cues are unchanged. - A file that does not match its extension's format makes the store fail,
so the artifact fails instead of being stored unlabelled: a
.pngthat is not a complete PNG (IHDRof length 13 first,IENDlast and exactly at the end — a truncated render is refused), a.mp3without an MPEG frame sync after the tag, a.vttwithout theWEBVTTheader. The check runs before the file is created, so nothing is left in FAL. - Other files are not rewritten.
- PNG:
- FAL description, core field only. For every file stored with a
provenance,
sys_states the AI origin in one sentence. No column is added to a core table. Updating the metadata record the indexer created goes throughfile_ metadata. description Meta— markedData Aspect:: save () @internal, but it is what core's own indexer calls and the only update path; the PHPStan exception is scoped to that one call. -
Visible labels, two settings.
- The result view shows an "AI-generated" badge on every finished artifact,
rows stored before this change included — they are just as generated.
Its tooltip mentions the machine-readable marker only when the row has an
aiblock; older rows get "Created with generative AI." The story card is labelled from its first finished slide and carries no badge when every slide failed.Label ai(default on) renders a small corner label into the Schaubild and story templates, in the artifact's language, on its own dark plate so it stays legible over an AI background.Label Images ki_never passes the HTML renderer and gets no visible label; drawing one onto the model's image would also break a C2PA manifest.image ai(default off) appends "This text was created with AI." in the text's language to the copy-ready text. Off by default because these texts are pasted into CMS pages, newsletter tools and social networks that carry their own AI disclosure, where a second one in the body is noise. The social posts reserve the line's length inside their platform limit. OnlyLabel Texts script_changes; the structured content and the FAQ JSON-LD stay as they are, so the JSON-LD remains valid schema.org.text
The settings are resolved once per run by the orchestrator (
Ai) into theLabel Settings Factory Generation, together with the label texts in the artifacts' language.Context - The result view shows an "AI-generated" badge on every finished artifact,
rows stored before this change included — they are just as generated.
Its tooltip mentions the machine-readable marker only when the row has an
Consequences
- Every file downloaded from the result view — MP3, WebVTT, PNG — carries its
AI origin, readable by standard tools (
ffprobe,exiftool, Pillow, media players), and every artifact row states it in the metadata. - A copied text carries nothing machine-readable. Its marker is the
aiblock in the database; the closing line is a human-readable disclosure and off by default. The editor who publishes the text has to disclose it where it is published.Label - The markers are metadata. Tools that strip metadata — most social networks' image upload, a re-export in an image editor — remove them; the visible corner label on the rendered images survives those, the full AI image has none.
- The text formats name no model: nr-llm's completion API returns the answer, not the model that produced it.
- No C2PA manifest is written. Signing needs a key and a trust anchor this extension does not have; an image model's own manifest is preserved.
- Assumption, unverified: that the image model returns a C2PA manifest in
a
cachunk. If it does not,BX ki_gets this extension's markers like every other PNG; if it does, the manifest is kept and theimage aimetadata still states the origin. Either way the image is labelled. The first realLabel ki_with aimage cachunk settles it.BX