spec: bind synthetic-content marking to the execution that produced it - #144
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
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
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 thatindent=2alone breaks the binding.verify_assertion()has no signature-only path.record_bytesis 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,
ruffandmypyclean. No media-format dependency: embedding the assertion into an asset is c2pa tooling's job.🤖 Generated with Claude Code