Provenance action extensibility
Status
Accepted — conditionally — 23 August 2026 (owner review).
The model below is adopted. Five conditions attach, all of them still 1.1-blocking, and they are listed in the Decision as conditions C1–C5 rather than as aspirations: §2.3 of the 1.1 draft may not ratify until every one is satisfied. Two of them (C4, C5) required changes to the 1.1 draft schema and those changes are in it; the other three are deliverables and are blocking rows on standards/ver/1.1-draft/RELEASE.md.
Nothing in VER 1.0.x depends on the outcome.
Context
§9 defines the acquisition chain as an append-only list of transform events, and the schema closes action to six values:
The provenance object is additionalProperties: false, so there is no sanctioned place to put an event the enum cannot name (Appendix D #2).
The reference implementation demonstrates the cost rather than arguing it. Its atproto publish step has no legal home in the Record: the module documents that the PRD asks for {"action": "publish", …}, the schema rejects it, and the at:// URI ends up in a Qdrant payload — outside the custody log that is supposed to be the Record's own account of its life (REC-11, WIRE-30). The implementation then mirrors the enum in code and hard-refuses anything outside it, so the omission propagates into a runtime error rather than a degraded record.
Empirically, action: "publish" is rejected by the published schema. So is export, sign, compose, crop, resize, upscale, denoise, and generate — the last of which is not a hypothetical: a Record describing a generated image cannot say so in its chain.
The pressure is structural, not incidental. Any closed enum in a standard that describes what pipelines do will be wrong within a year, because pipelines acquire operations faster than standards ship. The question is what happens when it is wrong.
Options
(a) Bump the enum every minor release. Every new action requires a schema revision and a ver_version bump. Value spaces stay closed and machine-checkable; unknown actions stay illegal, so a producer performing an unnamed operation must either lie (transcode for everything) or omit the event. Omitting it silently breaks the append-only claim: a chain missing events is worse than a chain with an unfamiliar one. Cadence-bound — a new operation waits for the next minor.
(b) Open the value space, backed by a registry. action becomes an open string. A closed core set keeps its exact current meaning and remains mandatory-to-understand; additional values are registered in a VER Registry with a definition and a stable name. Consumers meeting an unregistered value treat it as opaque and keep the Record. Costs: the registry must actually exist and be maintained, and consumers must be written to tolerate unknown values — a behaviour that has to be specified, because the natural implementation rejects.
(c) Namespaced x- extension actions. x-vendor.action alongside the closed enum, for every extension. No registry to run, and vendor extensions never collide with future core values. But applied to every extension it guarantees three vendors invent three names for cropping, and it puts a permanent syntactic mark on anything not blessed at 1.0 — including operations that later become universal, which then carry a vendor's name forever or force a migration.
Decision
Option (b), with a closed core set, and (c)'s namespacing kept for unregistered values only — accepted, subject to C1–C5.
- The core set is closed and stable.
decode,normalize,embed,transcode,redact,importkeep their exact 1.0 meanings. Every conformant consumer understands them. - Specification-registered extensions ship with 1.1:
publish,export,compose,upscale,crop,resize,color_adjust,denoise,generate,sign— ten, for sixteen tokens defined by the specification itself at 1.1. These are the actions the audit found pipelines actually performing, plussign, which the Record has a home for (provenance.signature, annex E17) and no way to name. - **The value space is syntactically open; unregistered values MUST be authority-namespaced.**
actionis a string, not an enum, and the schema admits two forms: a bare token, which MUST be one of the sixteen above or anactiveentry of a named registry snapshot, and<reverse-dns-authority>/<token>, which anyone may mint under a domain they control. An unregistered bare token is non-conformant; the same operation under an authority the Producer controls is conformant immediately, with no standards action and no waiting. This is the (b)+(c) hybrid: (c)'s syntactic mark is applied only to values that are not registered, so it is a temporary state a value leaves by being registered, not a permanent second tier. Registry membership is a rule of the standard enforced by the profile, not by the schema — no schema can hold a registry that changes between releases. - 1.0.x is unaffected. The enum stays closed there. This is 1.1 material and requires
ver_version: "1.1".
The five conditions
C1 — the governed registry must exist first. Deliverable; blocking. "Registry" means, at minimum: a stable publicly available URL; immutable, individually addressable snapshots; a digest per snapshot; append-only history; a written deprecation policy; named maintainers; and an appeals path for a refused registration. Until all seven exist, opening the value space ships the cost of an open string and none of the benefit of a governed one. Drafted at 1.1 §2.2.
C2 — conformance binds to a registry snapshot, not to the live registry. Encoded in the 1.1 draft schema. provenance.registry carries the snapshot's version and sha256 (optionally its uri), and the schema requires it whenever the chain carries a bare token that is not one of the sixteen the specification defines. Three sub-rules, all normative in 1.1 §2.2: a token is never reused for a different meaning; a deprecation never invalidates history — a Record minted against snapshot N stays conformant when the token is deprecated in N+1; and a Consumer resolves a token against the snapshot the Record names rather than against whatever the registry says today.
C3 — core versus registered, and old-consumer behaviour, are stated. Drafted; 1.1 §2.3 carries the three-tier table. Core (the six 1.0 values) and specification-registered (the ten added at 1.1) are must-understand: resolvable without consulting anything, because this document defines them. Everything else — later registry entries, and every authority-namespaced value — may be treated as opaque. A consumer meeting a bare token outside the sixteen MUST treat the event as opaque custody, MUST preserve it, MUST NOT reject the Record on that basis, and MUST NOT map it onto a known action or infer semantics from its spelling. It MAY resolve the token against the named snapshot; failing to fetch that snapshot leaves the event opaque and the Record conformant. This obligation is the load-bearing half of the decision — without it, opening the enum only moves the failure from the producer to the consumer.
C4 — asset events and Record events are distinguished. Encoded. 1.1 chain entries carry an optional target, asset | record, defaulting to asset so that no 1.0 event changes meaning. sign acts on the Record, and where a sign event states target the schema requires it to be record. Without this, a chain mixes "what happened to the pixels" with "what happened to the document about the pixels" and a consumer cannot tell which it is reading.
C5 — sign and publish carry their artifacts. Encoded (the first half). A Record whose chain carries a sign event MUST carry provenance.signature; the 1.1 schema enforces it. publish SHOULD carry or reference the publication URI, for which 1.1 defines a normative optional uri member on chain entries — deliberately not digest-coupled, because it names a location rather than a content-addressed artifact (1.1 §15.3). This is the gap the reference implementation demonstrated: its at:// publication URI had no legal home in the Record and ended up in a search-index payload.
Registry policy is drafted at 1.1 §2.2: names are lowercase, stable once assigned, never redefined, and never reused.
Consequences
Good.
- The append-only claim becomes honest. A producer performing an unnamed operation records it accurately instead of choosing between lying and omitting.
- The registry decouples vocabulary growth from schema releases. A new action needs a registry entry, not a
ver_versionbump. - The core set preserves what closed enums are actually for: a consumer can still reason exhaustively about the six operations that matter for reproducibility.
- The same mechanism is designed to generalise, and 1.1 §2.4 says so — without exercising it.
identity.perceptual[].alg,embeddings[].source.classandmetadata.raw[].segmentare registered in the draft and their enums stay closed, because an open value space with no published registry is an unconstrained string. Opening them is gated on C1 (the registry document), andsource.classadditionally on a registry-backed grade table (annex E19). SoSCH-22's observation — that VER 1.0 closes seventeen value spaces as enums while using registry language — is answered for one of them and open for the other sixteen. Nothing here closesSCH-22. targetmakes the chain answer a question it previously could not: whether an event happened to the Asset or to the Record.signis the case that forced it, and the default keeps every 1.0 event's meaning intact.sign⇒signatureis checkable by a schema, so the one claim in this ADR that could be made falsely — "this Record was signed" — cannot be made without the artifact.
Bad.
- A registry that does not exist is worse than a closed enum. Names get minted ad hoc, and the value space fragments with no arbiter. This is C1, and it gates the section: ratifying §2.3 without a live registry ships the cost and none of the benefit.
- C2 costs a field.
provenance.registryexists only to name a snapshot, and a producer using nothing but the sixteen specification tokens never needs it. That is deliberate — the field appears exactly when a Record depends on something outside the specification — but it is one more member, and a producer that forgets it while using an eleventh token fails validation rather than degrading. - Static analysis weakens. Tooling that exhaustively switches on
actionneeds a default branch, and some will get it wrong by rejecting. - Consumers will violate the tolerate-unknown obligation. It is a MUST that cannot be enforced by any validator with access only to the Record.
- The sixteen-token list now lives in three places — the §2.3 table, the schema's
actiondescription, and the schema's registry conditional. They must be changed together, and a registry addition after 1.1 changes none of them, which is the whole point.
Neutral.
- The wire profile is unaffected today because no provenance federates at all (
WIRE-30). When it does, the same open-value-space rule applies, drafted at 1.1 §14. - If ratification reverses to option (a), the 1.1 draft's registered extensions become an enum bump,
provenance.registryleaves the schema, and the consumer obligation is dropped. No published artifact changes; only the draft does.
