Skip to content

spec: bind synthetic-content marking to the execution that produced it - #144

Merged
imran-siddique merged 1 commit into
mainfrom
spec/synthetic-content-marking
Aug 9, 2026
Merged

spec: bind synthetic-content marking to the execution that produced it#144
imran-siddique merged 1 commit into
mainfrom
spec/synthetic-content-marking

Conversation

@imran-siddique

Copy link
Copy Markdown
Member

The last item where a live 2026 deadline meets our primitives. Article 50(2) and 50(4) have applied since 2 August 2026; everything else in the AI Act mapping now sits behind December 2027.

One C2PA assertion, com.agentrust-io.trace, carrying a hashed reference to the Trust Record for the execution that produced the asset.

The spec opens with what it does not do

The temptation to overclaim here is unusually strong, so §0 comes before anything else:

  • It does not stop anyone stripping the mark. Removing a C2PA manifest from a file is trivial and nothing here changes that.
  • It is not watermarking. Where an obligation calls for a hard binding, a watermark stays a separate control.
  • It does not make a deployment compliant. One checkable artifact for one part of one obligation.
  • It says nothing about whether the content is true or safe. Only which attested execution produced it.

What binding actually buys, stated exactly: a mark that is present becomes checkable against a hardware-rooted claim rather than being an unverifiable label, and in a channel that requires marks, an absent one is detectable.

Why bind at all

A C2PA manifest already says who signed this asset. It does not say what produced it — a signed claim of "generated by Acme Studio" is an assertion about a company, not about a model, a prompt, a policy or a runtime. Same gap this specification exists to close, arriving somewhere else.

Three signatures, merged into none

Check What it establishes
C2PA signature the assertion was in the manifest when the asset was signed
TRACE signature the execution happened as described
The hash the record being pointed at is the one the signer meant

A verifier that checks one of the three has checked a third. verify_assertion() performs only the middle one and says so in its own docstring.

Two API decisions

build_assertion() takes bytes, not a record object. A hash over a re-serialized dict is a hash of bytes nobody will fetch — key order, separators and escaping all change the bytes without changing the record, and the verifier hashes what the server sends. A test pins that indent=2 alone breaks the binding.

verify_assertion() has no signature-only path. record_bytes is a required positional parameter with no default, because an assertion whose hash was never checked is a URL in a file. A test asserts the parameter has no default, so a future convenience overload cannot quietly add one.

17 tests, 263 in the suite, ruff and mypy clean. No media-format dependency: embedding the assertion into an asset is c2pa tooling's job.

🤖 Generated with Claude Code

Article 50(2) and 50(4) have applied since 2 August 2026 and are the only AI Act
obligations that bite this year. One C2PA assertion, com.agentrust-io.trace,
carries a hashed reference to the Trust Record for the execution that produced
the asset.

The document opens with what this does not do, because the temptation to
overclaim here is unusually strong. It does not stop anyone stripping the mark:
removing a C2PA manifest from a file is trivial and nothing here changes that.
It is not watermarking, and where an obligation calls for a hard binding a
watermark remains a separate control. It does not make a deployment compliant.
It says nothing about whether the content is true or safe. What it buys is
narrower and real: a mark that is present becomes checkable against a
hardware-rooted claim instead of being an unverifiable label, and in a channel
that requires marks an absent one is detectable.

A C2PA manifest already says who signed an asset. It does not say what produced
it, which is the same gap this specification exists to close arriving somewhere
else. The assertion references the record rather than restating it, so there are
not two copies to disagree.

Three things must be checked and the assertion merges none of them. The C2PA
signature says the assertion was in the manifest when the asset was signed. The
TRACE signature says the execution happened as described. The hash says the
record being pointed at is the record the signer meant. A verifier that checks
one of the three has checked a third.

build_assertion takes the exact bytes that will be served rather than a record
object, because a hash over a re-serialized dict is a hash of bytes nobody will
fetch; a test pins that indent=2 alone breaks the binding. verify_assertion has
no signature-only path: record_bytes is required, since an assertion whose hash
was never checked is a URL in a file.

17 tests, 263 in the suite, ruff and mypy clean.
@imran-siddique
imran-siddique merged commit 7be497c into main Aug 9, 2026
6 checks passed
@imran-siddique
imran-siddique deleted the spec/synthetic-content-marking branch August 9, 2026 20:21
imran-siddique added a commit that referenced this pull request Aug 9, 2026
Ships enforcement_mode 'declared' (#143) and the content-marking binding (#144).

Cut now because the framework adapters cannot default enforcement_mode to
'declared' against an unreleased enum: they would have to keep refusing the
field, which is the overstatement the value exists to remove.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant