# The record document

Edition 2026-08-14. Describes GAVR record schema 1.0.

Structure is [`gavr-record-1.0.json`](../src/schemas/gavr-record-1.0.json).
Cross-field rules are [companion-rules.md](companion-rules.md). This document is
the reasoning: what each part exists to fix, and which parts are easy to get
subtly wrong.

The record document carries **no signatures**. A signature is _about_ this
document, so embedding one would make the thing being signed depend on the
signature over it. Signatures travel beside the document, in the transport
envelope and on the registry's row.

## Containers: the unit of attribution

A record holds a single list, `containers`. Each entry carries its own `author`
and `role`, and is separately signed.

**There is no dedicated field for the owner.** CVE Record 5.x has
`containers.cna`, naming a body type GAVR does not have: the owner here is a
GNEA, GNR or GNTLR, and a GNIA is a hosting node rather than an assigning body.
So the owner's container is the entry carrying `role: "owner"`.

What the dedicated field gave for free was cardinality, and that is now two
rules instead. A record in any state but `RESERVED` must carry at least one
owner container (GAVR-R-001) and must name its owner (GAVR-R-037); every
container claiming `role: "owner"` must be authored by that owner (GAVR-R-038).
Several are permitted, and all of them are the owner's own words.

This is why multi-party claims are nearly free here. The vendor's position and
the researcher's position are two containers, each attributable, rather than one
narrative somebody had to win. It fixes two documented problems at once: the
inverted burden of proof, where a researcher must persuade a vendor to publish
their view, and DISPUTED-forever, where a disagreement has nowhere to live but a
flag.

`role` says what the author is **to this record**:

| `role`        | Who                                                                    | `author.kind` |
| ------------- | ---------------------------------------------------------------------- | ------------- |
| `owner`       | The record's own authority, named by the record's `owner` field.       | `AUTHORITY`   |
| `peer`        | An accredited authority that is neither the owner nor a root.          | `AUTHORITY`   |
| `contributor` | A party with no accreditation: a lone researcher, attributable anyway. | `USER`        |
| `publisher`   | A GNDP.                                                                | `PUBLISHER`   |
| `registry`    | The registry itself.                                                   | `REGISTRY`    |
| `root`        | An accrediting body acting on a record in its subtree.                 | `AUTHORITY`   |
| `translator`  | A party restating another container in another language.               | `AUTHORITY`, `PUBLISHER`, `USER`, `TOOL` |
| `reviewer`    | A party endorsing the record, saying what it actually checked.         | `AUTHORITY`, `PUBLISHER`, `USER`, `TOOL` |
| `regulator`   | A statutory body acting under law, not a position in the graph.        | `REGULATOR`   |

Three of these are relational and describe a position only meaningful against
one record; `publisher` and `registry` restate what the author already is. Read
`author.kind` for what a party IS, and `role` for what it is HERE.

**Some container fields are role-scoped.** Everything a container can say about
the vulnerability itself — products, severity, weaknesses, remediations,
detection, references, credits — is available to every role. A role-scoped field
is different in kind: it is a claim about the author's standing to be acting on
this record at all, so it may appear only on the roles it belongs to
(GAVR-R-028), and where it defines the role it is required (GAVR-R-029).

The two governance roles are exempt from that second half. A `root` may
intervene and a `registry` may reverse, but neither is required to, because a
governance body's container is not always an act: a root that reviews a record
and finds nothing wrong, or disagrees with one claim, is not sanctioning
anybody, and the registry accepts, countersigns, excises under an order and
dismisses appeals. Requiring the act's block made both roles able to say exactly
one thing. What keeps the exemption safe is that neither can assess on a plain
container: a `registry` is withheld every claim key outright (GAVR-R-034), and a
`root` may carry them only inside an intervention (GAVR-R-045). So a governance
container either performs a governance act or says something that is not an
assessment of the flaw.

| Field          | Role             | What it carries                                            |
| -------------- | ---------------- | ---------------------------------------------------------- |
| `intervention` | `root`           | The grounds, the accreditation chain, and the appeal route. |
| `peerStanding` | `peer`           | What this body is to the record, and what it contests.      |
| `coordination` | `owner`, `peer`  | How coordinated the disclosure was, as that party saw it.   |
| `contests`     | `owner`, `peer`, `root` | Specific claims elsewhere on the record this party disagrees with. |
| `enrichment`   | `publisher`      | How a GNDP produced what it added, and what it rests on.    |
| `vouching`     | `contributor`    | What the vouching party checked, and who the author claims to be. |
| `appealDecision` | `registry`     | How an appeal against an intervention was decided.          |
| `translation`  | `translator`     | Which container is restated, in which language, and how.    |
| `review`       | `reviewer`, `root` | The verdict, and what was actually checked to reach it.   |
| `regulatoryAction` | `regulator`  | The jurisdiction, the instrument, and what falls due.       |

`contests` is the missing middle between agreeing and disputing. The
record-level `disputed` flag is about the whole record, so a party that accepts
everything except one CVSS vector previously had to dispute the entire record or
say nothing structural at all. It points at a container `id` rather than an array
index, because indices move whenever anything before them is added.

An accrediting body may contest too, and may only `dispute` (GAVR-R-035). The
veto stays with the party accountable for the record; a root that wants a claim
gone has `intervention`, which is attributed, appealable and carries the
accreditation chain a mirror needs to check that the acting body really does sit
above the owner. A plain contest carries no such chain, so a rejection through
one could not be checked offline at all. The registry is absent from the row for
a different reason: a secretariat contesting a claim does so as a `root`, which
keeps `registry` meaning acceptance and countersignature and nothing else.

**An owner's `rejected` contest IS the veto**, and it lives in the owner's own
container rather than as a status on the refused party's. That is forced, not
stylistic: `status` is inside the container signing pre-image —
`containerSigningPayload` omits exactly one key, and it is `signature` — so a
status written by anybody other than the author invalidates the author's own
signature over their own words. A badly-signed container is worse than an
unsigned one, because it looks attested to anything rendering a badge without
re-verifying. `compareContainers` refuses the write independently, requiring
every foreign container to survive byte-identical.

So the rejection is the owner's signed claim, the refused container and its
signature are untouched, and a reader sees both what was said and that the owner
refuses it. Only the owner may `reject` (GAVR-R-035); anybody else `disputes`.
Revoking a party's access changes none of this: grants are held outside the
record, so revocation stops future revisions and cannot reach what is already
published and countersigned.

`enrichment.method` is the single biggest thing a data publisher's container used
to withhold. A GNDP is this network's answer to CVE's ADP — it adds claims to
records it does not own, and CISA-ADP attaching an SSVC decision is the shape —
but a human decision and a model output rendered identically, so a consumer
weighing an enrichment against the owner's own assessment could not tell which
it held. `derivedFrom` separates the grounds for the claim from further reading,
which `references` alone cannot.

`vouching.verificationBasis` and VOUCHED custody answer different questions.
Custody proves that somebody vouched; the basis says on what grounds, and
without it "a maintainer I have worked with for years" and "an email that
arrived last Tuesday" are identical endorsements. There is deliberately no
`other` member: `unverified` IS the honest residual, so a voucher with nothing
to point at has a truthful value to pick rather than an escape hatch.

**`affiliation` and `contact` are the contributor's own claims about
themselves**, unverified by construction. A surface that renders either as
established fact is misreporting the document. That obligation cannot be
expressed in JSON Schema, so it is stated here rather than filed as a rule
nothing could check. `contact` also carries a cost worth stating rather than
discovering: a record is published, mirrored by parties nobody supervises, and
never deleted, so an address written there is public permanently.

`appealDecision` records how an appeal against an intervention was decided.
Acceptance and the countersignature live BESIDE the document, on the revision, so
they are not what a registry container carries.

It was called `reversal` and could express only one outcome, which meant a
**dismissed appeal had no expression anywhere in the document**: an intervention
that was challenged and survived read exactly like one nobody had ever
questioned, and a reader could not tell "nobody objected" from "somebody
objected and was refused". Those are very different things to know about a live
sanction. Adding a disposition to a block called `reversal` would have left the
name lying for precisely the case being added, so the block was renamed while
renaming was still free. `appealedContainerId` follows: the pointer names what
the appeal was ABOUT, which is true under both dispositions, where
`reversesContainerId` was true under only one.

A consumer deciding whether a sanction is still in force must therefore read the
disposition and not merely the pointer. Only `upheld` lifts the intervention.
Anything else leaves it standing, which is also the safe reading of a
disposition a future edition may add.

**Five roles are narrowed.** A `root`, `registry`, `translator`, `reviewer` or
`regulator` container may not carry `affected`, `severity`, `weaknesses`,
`remediations` or `detection` (GAVR-R-034). None of them assesses the
vulnerability: a root governs the record, the registry accepts and countersigns
it, a translator restates somebody else's words, a reviewer endorses, and a
regulator acts under law. A CVSS score in any of those is a claim the party has
no standing for, on the surface a consumer is most likely to read as
authoritative — and in the translator's case the reader least able to catch it is
the one who needed the translation.

**A root is barred CONDITIONALLY, and is therefore not one of the five.** It may
carry the claim keys inside an `intervention` and nowhere else (GAVR-R-045).

That condition carries both halves of the design. Because a plain root container
cannot assess, the requirement that every root container be an intervention
could be dropped, so a root may speak without sanctioning and still not compete
with the party accountable for the record. And because an intervening one can,
a root that upholds a formal dispute can state what is RIGHT rather than only
that something is wrong. An unconditional bar left it able to sanction an owner
for a factually wrong record while unable to supply the correct severity, which
any accredited peer could do, being unconstrained; `contests` names the claim it
disagrees with and carries prose, and prose is not something a consumer can
merge.

The correction is then attributed, appealable, bounded by `diffIntervention`,
and checkable offline through `accreditationPath` like any other intervention.
`intervention.corrects` names the containers whose claims it replaces, so a
consumer gets machine-readable precedence instead of having to guess whether a
root's severity outranks the owner's. It is deliberately separate from
`supersedes`, which names an earlier container from that same root: one is an
author's own bookkeeping and the other is cross-party precedence, and a single
pointer meaning both would leave a reader unable to tell a revised sanction from
a corrected claim.

There is no way for a root to author its correction as some other role. The
roles that may carry claims and the roles a root could honestly hold are
disjoint: `publisher` and `contributor` are closed by author kind, and `owner`
and `peer` by meaning, `peer` being defined as neither the owner nor a root. A
root assesses as a root or not at all.

Every other role may say anything about the vulnerability itself — including a
lone contributor with a working proof of concept, which is the case that decides
it.
Narrowing is expensive to undo, since widening back is itself a breaking change,
which is why the list is short rather than tidy.

`coordination` is the one role-scoped block two roles share, and the sharing is
deliberate. A peer recording `owner-unresponsive` while the owner records
`coordinated` is not a modelling failure to be resolved away — it is exactly the
disagreement the container model exists to hold, and two separate enums would
have made the two statements incomparable and hidden it.

`intervention.accreditationPath` is the part worth reading twice. `diffIntervention`
already lets a mirror apply exactly the bound the registry applied with no
network call, but whether the intervening party sits **above** the owner was
answerable only by querying the accreditation graph — so a mirror could verify
that a root stayed inside its powers while being unable to verify it was a root
at all. The path is ordered from the root down to the owner and includes both
ends, so it can be checked without trusting `author` to say where the chain
begins.

**The two columns are not independent** — GAVR-R-023. The role and the author
kind used to be free of each other, so every combination was legal: a `USER`
container claiming `root`, a `PUBLISHER` claiming `owner`. Each renders on a
public surface as an act by a party of a kind that cannot perform it, and
rendering the AUTHOR accurately does not help, because the author is accurate
and the role is the lie. The mapping falls out of what each role means:
`owner`, `peer` and `root` are positions in the accreditation graph; `publisher`
and `registry` admit exactly the kind they restate; `contributor` is _defined_
as unaccredited, so an authority claiming it would be disclaiming its own
accreditation.

That bounds the forgery rather than ending it — a real authority can still claim
`root` over a record it has no standing on, because both sides of that claim are
`AUTHORITY`. What closes the rest is **who may attach a container on whose
behalf**:

- `contributor` and `publisher` may be **transmitted**. A lone researcher holds
  no registered key and has no op of their own, so the owner carrying their
  words is the only way `contributor` reaches a record at all. The record
  attributes the claim without pretending the researcher signed it.
- `owner`, `peer`, `root` and `registry` may not. Each asserts a _standing_ over
  the record, and a standing is not something a third party can assert for you —
  a `root` container is an intervention, which every surface renders as an
  accrediting body acting against the owner. Those arrive through their holder's
  own signed op or not at all.

This is transitional, and per-container signing is what ends it: once a
container carries its author's own signature, transmission becomes provable
rather than merely attributed, and the question stops being _which roles may be
transmitted_ and becomes _is this container signed by the party it names_.

**Nobody edits anybody else's container.** This applies to an ordinary revision
as much as to an accrediting body's intervention. Without it on the ordinary
path, a root attaches a correction and the owner's very next submission deletes
it, leaving the record reading as though nothing happened.

The single exception is narrow and is not an exception to attribution: a party
granted `AMEND` may revise the owner's container, and a revision that does so
**must carry the owner's own signature** beside the submitter's. The owner has
still said every word attributed to them; what the grant removes is the need for
the owner to be the one who transmits them.

**A root intervenes with its own container, never by rewriting the owner's.** A root
able to edit the owner's container could put statements into the owner's mouth
under the owner's own attribution — not a stronger version of the same power,
but the one thing the container model exists to make impossible, and no audit
log recovers it once a forged claim reaches a mirror inside a document that
verifies.

Container **order** is inside the signed document. A party who could reorder
could push a correction it dislikes below the fold on every surface rendering in
document order, without altering a byte of anybody's words.

## What this format fixes

Each of these answers a complaint that recurs across independent sources, not a
preference.

### Software with no package registry can still be named

The top-ranked complaint across every system studied, with three separate
causes. CPE cannot distinguish `log4j-core` from `log4j-api`. OSV requires an
ecosystem and GHSA rejects unsupported ones, which excludes anything not shipped
through a package registry. A vendor naming its own closed-source product has no
registry at all.

So **purl, CPE, repository URL and free-form vendor/product are co-equal and
none is required**. At least one must be present (rule GAVR-R-014), because a
product nobody can name is not a record — but demanding a _particular_ one is
how the incumbents exclude whole classes of software. `ecosystem` is free text
rather than an enum, because an enum is precisely the mechanism by which GHSA
rejects ecosystems it has not got round to supporting.

**Function and symbol-level locations** are first-class and absent from CVE, OSV
and GCVE alike. They let a consumer ask "do I call this function" rather than
only "do I have this package", which is the question behind the measured 92%
false-positive rate in SCA output.

### A version model that survives real release engineering

Each bound carries its own inclusivity, so all four combinations are
expressible. CVE 5.0 and OSV both lack an exclusive lower bound, and CVE cannot
express an inclusive upper bound at all — `<=` is unrepresentable, so converters
routinely **invent a version that was never released** in order to write `<`.

A **backported fix** needs no special construction: it is two ranges, and
nothing forbids the second range's bound being lower than the first's. GHSA
forbids a fixed version below the range, so a fix backported to an older
maintenance branch is unrepresentable there.

`affected-versions-unknown` says the product **is** affected but which versions
is not established. It has been an open CVE schema issue since February 2023 and
is written today as prose or not at all. It is distinct from `unknown`, which
says nothing is known, and it cannot carry bounds (rule GAVR-R-016) because
stating them would contradict the claim.

### "Not affected" is first-class, and never encodes support policy

VEX exists for this and has three incompatible encodings, so a consumer must
support all three or lose the answer. `unaffected` is a status here.

An unaffected range **must say why** (rule GAVR-R-015). The reason is a separate
field from the status, and that separation is the fix. A real case: OpenSSL
3.1.8 looked vulnerable and was excluded from the affected list because the
branch was end of life. The affected list therefore silently encoded support
policy, and a consumer reading it concluded the version was safe. "Not affected"
and "out of support, unassessed" are different claims and must never share a
field.

### Severity has a precedence rule, in the data

NVD and CNAs disagree on a third or more of dual-scored CVEs, and the same rater
disagrees with themselves over 40% of the time at nine months. Today a data
publisher can overwrite a CNA's score and the CNA cannot refuse, while
precedence exists only as policy prose.

**Every score is retained with its author**, and `author` is mandatory: an
unattributed score cannot be argued with, which is how the overwrite problem
persists. `severityPrecedence` states which wins, so a parser can execute it —
`preferredAuthor` names a party rather than an index, so it survives scores being
added or reordered.

`EPSS` and `SSVC` rank last by default, because "how likely is exploitation" and
"what should I do" are different questions from "how bad is it" and are not
substitutes for it.

### Relations are machine-readable

CNA Rule C3 literally prescribes encoding a duplicate relationship as an English
note inside the description, so the single most machine-actionable fact about a
record is published as prose no tool can act on.

Relations here are a **closed, directional set**. Direction is the fix for OSV's
asymmetric-alias problem, where two records alias each other and no rule says
which side wins: `duplicate-of` points from the duplicate to the original, and
the inverse is a different relation with a different meaning, so there is never a
symmetric pair to arbitrate. Every relation carries how it was established, so
an authority's assertion and a community guess are distinguishable.

### Nothing is destroyed

Rejecting a CVE deletes the record's content. Verified: CVE-2025-2825 and
CVE-2023-5129 now return no description at all, and roughly 85 records in 2025
were rejected _after_ publication, so tools had already ingested them.

In GAVR every state is a transition over retained content. This is why
`wasWithdrawnOnce` exists and latches permanently: a record restored from
withdrawal is still a record that was withdrawn, and a consumer reading only
`state` must not be misled.

A container is the same: it is never spliced out of the array, because the array
is signed and ordered and the index is what `Remediation.appliesTo` points at and
what a reader cites. A party taking its own container back sets `status` and
leaves the entry where it is — so a consumer sees both that something was said
and what became of it, which is the whole disagreement.

Note that `status` is set by the container's AUTHOR and by nobody else. It is
covered by the author's own signature, so a second party changing it would break
that signature, and a badly signed container is worse than an unsigned one: an
unsigned container is honestly unattested, where a broken one still looks
attested to anything that renders a badge without re-verifying. An owner who
rejects a container therefore says so in its own container, with `contests`,
which leaves both statements standing and attributable on both sides.

**`redactions` is the single exception, and the tombstone is what keeps it
honest.** The reasons redaction exists for are the ones where retention is the
harm: a leaked credential inside a description, personal data under an erasure
order, a name the embargo covered. A format that cannot remove those forces its
operators to remove them by hand, off the record, which destroys the history and
the audit trail together. So the text goes and the FACT of its going stays,
permanently and attributably — a reader can always tell a record that was
redacted from one that never said anything. The tombstone list is append-only,
checked rather than assumed, because a party who could remove the record of a
removal could make redaction indistinguishable from tampering.

### Redacting another party's container

A redaction that reaches inside somebody else's container carries a
`containerId` naming it, and it is a different act from redacting your own words
in three ways an implementation has to carry.

**The reasons are narrower.** Only `leaked-credential`, `personal-data` and
`legal-order` justify it, and this is the one half a JSON Schema validator
catches on its own (GAVR-R-036). The excluded three — `embargo-violation`,
`operational-risk`, `published-in-error` — are judgements about what should have
been said, and a party who disagrees with another party's statement has
`contests`, which leaves both statements standing. Silently deleting somebody's
words because you think they were unwise is the power this format withholds.

**Only the owner or a root above it may do it.** A peer contributing alongside
the owner is strictly additive. The removal takes effect immediately and is
appealable, mirroring the rule a root already operates under for its other
restrictive powers: waiting on approval keeps a live secret published, which is
the one cost that compounds while a process runs.

**It may only REMOVE.** The redacted container must be obtainable from its
predecessor by deleting values and nothing else — no value added, none changed,
and the relative order of what survives preserved. Without that bound the
tombstone would be a licence to replace another party's container wholesale
under their own name, which is the forgery the whole container model exists to
prevent, arriving through the door opened to stop a leak. It may not remove the
container's `id`, `author`, `role` or `signature` either, since those are what
make the removal explicable at all.

### Ownership transfer, and the container the outgoing owner leaves behind

`role: "owner"` means the record's CURRENT owner, and every owner container must
be authored by them. A transfer therefore breaks the record the instant it
happens: the outgoing owner's container claims a role that is no longer theirs.

Nothing on that container can be changed to fix it. A container's signature
covers every key except the signature itself, so `status`, `role` and `author`
are all inside it — editing any of them destroys the outgoing owner's
attestation over words that have not changed. Deleting it destroys the history
this format exists to keep. And asking them to stand down first fails in exactly
the case transfer exists for: `reassigned-by-root` and `reassigned-by-registry`
happen precisely when the outgoing owner is unresponsive.

**So the transfer names what it retired, and the container is never touched.**
The ownership event carries `retires`, a list of container `id`s, and those
containers are exempt from the owner-authorship rule. Three consequences follow,
and each is the reason the mechanism is shaped this way:

- The outgoing owner's bytes are byte-identical afterwards, so **their signature
  keeps verifying forever**. A party's statement about a vulnerability does not
  become less true because the record changed hands, and a broken signature there
  would make every transfer read as tampering.
- It needs **no cooperation from the outgoing owner**, so a hostile or abandoned
  transfer works exactly like a consensual one.
- `role: "owner"` keeps meaning the current owner. A reader never has to work out
  which of two owner containers is in force, because only one is live.

A container must carry an `id` to be retired — the same constraint a redaction
tombstone lives under, and for the same reason: an index moves, and a pointer
that drifts onto a different party's words is worse than no pointer.

Two rules bound it. A transfer may only retire a container the record actually
carries, because an id listed before any container holds it would be a standing
permission to attach somebody else's words as an owner statement later. And a
transfer may not retire *every* owner container: the incoming owner attaches its
own in the same revision, so the record never passes through a state where
nobody speaks for it.

Retired containers belong on the record's history rather than beside the live
claims. They are not detectable from the container alone — that is the whole
point — so a consumer reads `ownershipHistory[].retires` to find them.

### Reaching back through the history

A redaction that touches only the current revision is cosmetic. Every accepted
revision is retained verbatim, so removing something from revision 7 leaves it
readable in revisions 1 to 6, and a tombstone then becomes a signpost pointing
at where the content still is.

Reaching back is not free: a stored revision's bytes are covered by its digest,
the authority's signature and the registry's countersignature, so excising from
one breaks all three permanently. The reasons therefore split, because they are
not the same act.

**`legal-order` and `personal-data` excise.** These are obligations to stop
holding something, and access control does not satisfy either — the data is
still there. Every prior revision that held the content is rewritten, each
carrying its own tombstone, and their signatures no longer verify.

**Every other reason redacts at the serving layer.** `leaked-credential` is the
instructive one: by the time a redaction is filed the secret is burned and must
be rotated regardless, so the record of what was exposed and for how long is
worth more than its removal, and every signature in the chain stays verifiable.

The two give different guarantees and no surface may present them as one. A
serving-layer redaction is undone by anyone with database access; an excision
cannot be undone at all except from the escrow.

**Excision matches by VALUE, never by the tombstone's path.** A `path` addresses
the current document, and prior revisions do not share its shape — a container
added in revision 4 shifts every index after it, so a path naming one party's
words today names somebody else's in an older revision. The removed values are
derived by diffing the pre- and post-redaction documents, which carries no
positional assumption, and are then removed wherever they appear. That is
deliberately broader than the original removal: the obligation is that the
instance stops holding the content, not one particular copy of it.

**It matches whole values, though, never substrings, and that boundary is part
of the guarantee rather than a gap in it.** What is matched is the shape that
actually went: remove a description entry and the value is the description
object, so the same string sitting elsewhere in the document as a bare field is
a different value and survives. Matching substrings instead would be worse — a
substring sweep over a document nobody reviewed deletes coincidental matches out
of other parties' signed containers, breaking their signatures over content no
order ever named, and removing more than was adjudicated is its own harm. A
second copy in another shape needs its own tombstone.

### The escrow, and destroying it

Excising serves the obligation and takes something from the parties who are
accountable for the record: the owner, a root above it, and the registry's
secretariat lose the ability to review what was removed at the same instant
everybody else does. A registry therefore keeps the pre-excision copy of each
rewritten revision, taken before the bytes go.

The copy still verifies against that revision's stored digest and both its
signatures, because it *is* the bytes they cover. That is what makes it useful
rather than merely informative: a reviewer confirms the copy is authentic
instead of taking the registry's word for it.

**Which reasons leave a recoverable copy, stated plainly.** `legal-order` and
`personal-data` excise and escrow. Every other reason redacts at the serving
layer, so nothing is escrowed and nothing needs to be: the bytes are still in
the stored revision.

**A copy defeats an erasure order by design, so destroying it is a separate,
explicit act.** Review stays possible right up to the moment an order is
complied with; complying is then a signed action, attributed in the registry's
audit chain, rather than a side effect of the redaction. Afterwards the escrow
entry remains as a tombstone recording that a copy existed and was destroyed —
"never escrowed" and "escrowed and destroyed" are different facts, and a party
asking what happened to a revision needs the second one. The tombstone holds
neither personal data nor secrets, so keeping it complies with the order that
emptied it.

Who may read an escrow, and who may destroy it, is one question with one answer:
the record's owner, a root above it, or the secretariat. Whoever is accountable
enough to review what was removed is accountable enough to comply with an order
to stop holding it.

**The guarantee ends at this instance's own storage.** Mirrors serve records
they have already fetched, and operator git mirrors keep history that is
append-only by design. Neither can be reached by a redaction filed here; only
the party holding that copy can act on it. A redaction feature that quietly
implied more than that would be worse than one with a stated boundary, so it is
stated: excision is a promise about one registry's database, not about the
network.

**The container's signature will not verify afterwards, and that is expected.**
Bytes it covered are gone. A verifier that is given the record's `redactions`
reports this as `redacted` rather than as a bad signature — the container is
still not attested, because some of what its author signed has been removed, but
an identified party is on record as having removed it and said why. A verifier
given no tombstones reports a bad signature, which is true and useless: without
the distinction, every redacted record reads as a forgery, which is a worse
outcome than the leak the redaction was performed to stop.

## Extensions

A container may carry properties this specification does not define, provided
they are named `x_something`. The pattern is CVE's own, so an implementer who
has written that check already has written this one.

**Containers only.** The rest of the document is the part every consumer must
agree about — the identifier set, the lifecycle, who owns it — and an extension
point there would let a record carry a lifecycle claim the specification cannot
see. A container is one party's own statement, so an unknown key inside one is
that party saying something extra, which is the only place where "ignore what
you do not understand" is a safe instruction.

**A consumer MUST preserve unknown `x_` keys verbatim when re-serialising.**
This is normative rather than a courtesy: the document is canonicalised and
signed, so a parser that drops a key it does not understand emits bytes that
fail every signature over them — and it fails for a record it was handed rather
than one it wrote.

An unknown key WITHOUT the prefix is refused (GAVR-R-027). Without that, a typo
in a defined field name is silently accepted as data rather than reported, which
is the failure mode an open object has and a closed one does not.

## The identifier set

A record holds an identifier **set**, not an identifier. One advisory
demonstrably holds a CVE and a GCVE and several GHSAs and a test-registry CVE at
once.

Several identifiers in one namespace is the **normal case**: two authorities each
assign before anyone notices it is the same bug, an advisory mirrors several
existing GHSAs, a registry rejects one and issues a replacement. Every one of
those is a fact worth keeping, and discarding them destroys exactly the
duplicate-resolution information Rule C3 forces people to write as prose.

`role` is therefore what says which identifier to **cite**:

| Role         | Meaning                                                            |
| ------------ | ------------------------------------------------------------------ |
| `PRIMARY`    | The record's own identifier. At most one, and it equals `id`.      |
| `CANONICAL`  | The one to cite in this namespace and environment. At most one.    |
| `DUPLICATE`  | Names the same vulnerability, assigned elsewhere. Kept, not cited. |
| `SUPERSEDED` | Was canonical here, replaced.                                      |
| `REJECTED`   | The registry rejected it. Retained so the history stays legible.   |

Four companion rules govern the set: GAVR-R-004 through GAVR-R-007. The last one
is the least obvious: **a namespace must retain at least one citable
identifier**, because a namespace holding nothing but history names no
identifier at all, which is a record that has lost track of what it is called.

`environment` is `prod` or `test`. A test-registry identifier is real and must
not collide with a live one, and it occupies its own canonical slot.

## Prose and markdown

Long-form fields carry a `format` from a closed set, defaulting to `text/plain`,
so every value written before the field existed keeps meaning what it meant.

**The dialect is pinned.** "Markdown" is not one language: CommonMark and GFM
disagree about tables and autolinks. A bare `format: "markdown"` would recreate
exactly the undefined multi-language behaviour this schema criticises CVE for
having left open since 2022, so the format string names its variant.

**The profile is restricted, and enforced at the schema rather than in a
renderer.** Records are served by mirrors, node agents and third-party consumers
that nobody here supervises, so "our renderer sanitises" is a promise about one
renderer. Refused: raw HTML, images, and any link destination that is not
`https:`, `http:` or `mailto:`. Two of those are less obvious than they look.

- **A relative link is refused** because a record has no single base URL. The
  same signed bytes would resolve somewhere different on the registry, on each
  mirror and inside a node agent.
- **An image is refused** because a remote image loads when the record is
  **read**, so it tells whoever hosts it who is reading the advisory and when —
  including during an embargo, and including on mirrors that cannot strip it.
  That is a beacon rather than an illustration.

**HTML inside a fenced code block or a code span is fine, and that exemption is
load-bearing.** An advisory about XSS, template injection or an unsafe
deserialiser has to be able to _show_ the offending markup, so a scanner that
cannot tell a fence from a live tag refuses precisely the records that most need
rich text. Indented code blocks are deliberately **not** recognised: four-space
indentation is ambiguous with list continuation without a full parser, and the
failure direction there is refusal, which is the safe one — fence your code.

The profile enforces only what can act on a reader. Verifying a positive
allowlist of blocks needs a parser; claiming to gate emphasis or tables would be
claiming a guarantee this format cannot make.

**It applies to long-form fields only.** Short scalars — titles, vendor, product,
package names — stay plain, or a package genuinely named `foo_bar_baz` renders as
italics in every listing, feed row and email subject on the network.

**Plain text is derived, never authored twice.** The rendering is deterministic
and total, and it is what plain-text consumers and the CVE export read. A second
authored field would be a second copy of the same content, and two copies of one
thing drift. Determinism is not tidiness: the document is canonicalised and
hashed, so a rendering that varied between calls would move the digest of
everything derived from it.

Multi-language descriptions have **defined** behaviour: BCP 47 tags, lowercase,
unique within a list, with exactly one entry marked primary. CVE leaves all of
this undefined — whether `en-US` twice is legal, whether `EN` differs from `en` —
so consumers guess.

## CVE Record 5.x export

The export is lossless for markdown: the plain rendering goes in
`descriptions[].value`, which is what existing consumers read, and the original
rides alongside in `supportingMedia` as `text/markdown`, which the carrier
format's own schema names as an example for exactly this.

One rule an exporter must not get wrong. **An open-ended CVE version range is
`lessThan: "*"`, never a bare version.** `{version, status, versionType}` with no
bound means **one** version in CVE, because the specification's own resolution
algorithm returns the entry's status only when the queried version equals
`version`. Emitting it for "affected from 1.2.0, no fix known" claims a single
version is affected.

And **anything that digests the export must supply the update timestamp**.
Omitted, the exporter is expected to stamp the real time, which is the correct
semantic for filing a new version and makes the output unhashable. A digest an
approval is bound to would then move on every render.
