Model register and gap report
Text a model writes can carry two layers of marking: metadata in the format it is delivered in, and a watermark in the text itself. The package writes the first wherever the format can hold it. Only the model's provider can embed the second. The register records what you know about each model, and the gap report holds every output against both layers.
The Code of Practice
On 10 June 2026 the European Commission published its voluntary Code of Practice on the transparency of content that AI generates. Measure 1.1 of its first section, which addresses providers of generative AI systems, asks for:
- digitally signed metadata wherever content is generated or exported in a format that supports it, such as a mail, a PDF or an HTML file (Sub-measure 1.1.1);
- an imperceptible watermark, except in very short text, which the Code's glossary puts at fewer than 200 tokens and expects to fall as the techniques improve (Sub-measure 1.1.2).
Free-form text, the text of a chat message for instance, cannot carry metadata, and for it the Code considers the watermark alone sufficient. A provider may rely on marking that an upstream model provider applies.
The model register
List every model your application uses in config/ai-content-marking.php:
'models' => [
'example-writer' => [
'watermarks_text' => 'unknown',
'source' => 'https://example.com/model-card',
'checked_on' => '2026-10-15',
],
],
watermarks_text is yes, no or unknown. source says where the statement comes from, and
checked_on is the day it was last checked, as YYYY-MM-DD. Both may be null. An entry of any other
shape is refused with an InvalidArgumentException that names the model.
The package lists no model. Whether a model watermarks its text is a statement for its provider to
make, it can change from one version to the next, and a register is only worth as much as its
sources. A model the register does not list reads as unknown.
use Pushery\AiContentMarking\Models\ModelRegister;
app(ModelRegister::class)->watermarking('example-writer'); // Watermarking::Unknown
app(ModelRegister::class)->entry('example-writer'); // the ModelEntry, or null when not listed
The gap report
Describe each output you delivered and let GapReporter assess them:
use Pushery\AiContentMarking\Gaps\GapReporter;
use Pushery\AiContentMarking\Gaps\Metadata;
use Pushery\AiContentMarking\Gaps\Output;
use Pushery\AiContentMarking\Gaps\OutputKind;
$reporter = app(GapReporter::class);
$report = $reporter->assess([
new Output('message-1182', 'example-writer', tokens: 412, metadata: Metadata::Signed),
new Output('message-1183', 'example-writer', tokens: 96),
new Output('message-1184', 'example-writer', tokens: 650, kind: OutputKind::Translated),
]);
$report->gaps(); // the assessments of the outputs that lack a layer
$report->exceptions(); // the assessments of the outputs listed as exceptions
$reporter->markdown($report, 'de'); // the report as a document, in any of the package's languages
$report->toArray(); // the same report as plain values
An Output names what identifies it, the model under the name the register uses, and its length in
that model's tokens as the provider counts them. Its kind is Generated unless you say Edited,
Translated or Corrected. Its metadata is NotCarried, a format without metadata, unless you
say Signed, Unsigned or Missing. For a mail, MailMarkingReader::signingDomains() tells you
whether a signature lists the field (see Marking mail).
Each output that is not an exception is checked for these gaps:
| Gap | Found when |
|---|---|
watermark-missing | the output is at least watermark_from_tokens long and the register says its model does not watermark text |
watermark-unknown | the output is at least that long and the register says unknown, or does not list the model |
metadata-missing | the format carries metadata and the marking is not in it |
metadata-unsigned | the marking stands in metadata that no signature covers |
A translation and a correction are listed as exceptions and not assessed. Article 50(2) of the AI Act does not apply where a system performs an assistive function for standard editing or does not substantially alter the input or its meaning. Which outputs fall there is your call, and the report shows each one so that whoever reads it sees the call as well.
The length from which a watermark is expected is gaps.watermark_from_tokens, 200 by default, or
AI_CONTENT_MARKING_WATERMARK_FROM_TOKENS in the environment.
What it decides
Nothing beyond the gaps. Whether an output really is an exception, whether the register is right about a model and whether a gap is acceptable stay with you, and the report is the record those judgments are made on. A gap analysis that measures your application against the Code, with the day it was last reviewed, is a document of your own; the register and the report are what it can cite.