ADR 0055: Research Versions are commits
ADR 0055: Research Versions are commits
Section titled “ADR 0055: Research Versions are commits”Status: Proposed
Implementation: #1771 is the close gate and stays Proposed until it completes. Slice 1 (#1773, storage) records parents, author and committer on every new Version, keeps the permanent ancestry ledger, and moves the research format to revision 7. Round trip, merge and erase are later slices of #1771. Credit display, the derived people list and removal of access metadata (#2125) publish what history records.
Related: #1771, #1772, ADR 0039 (research Version publication), ADR 0041 (Branch publication), ADR 0043 (Proposal acceptance), ADR 0044 (research interchange), ADR 0051–0053 (discovery and Hub publish).
Context
Section titled “Context”A research Version identified frozen content and nothing else. It recorded no parent, so a Branch head could be replaced by an unrelated Version without anyone noticing, intermediate history could not travel, and a second committer had nothing to fast-forward. Credit for the work lived nowhere in the Version; creator and actor UUIDs on Branch and Proposal records are opaque identifiers.
Decision
Section titled “Decision”A Version is a commit. Each new ResearchVersionRecord records:
parents: its parent Versions, first parent the prior head of its context;author: who wrote the change;committer: who recorded it;provenance: where restored or brought content came from, when that source is not a parent.
Signatures are free-form ResearchSignature { name, email?, orcid? }. They are
credit, not authentication: nothing reads them to grant or prove permission.
The Version identity digest is unchanged in form: SHA-256 of the record’s
struct-order JSON with physical placement (generation_uuid,
manifest_sha256) zeroed. The new fields are part of that record, so the digest
commits to parents, author, committer and provenance. Removing credit later is a
history rewrite. Each new field is omitted when empty, so a record without them
encodes, and therefore digests, exactly as a revision 6 record did. A golden
vector (a real research/6 record and the digest its producer committed) pins
this.
Parents per operation
Section titled “Parents per operation”Storage enforces these parents at publication; a head never moves to a Version that does not name the prior head as its first parent.
| Operation | Parents | Also recorded |
|---|---|---|
Project capture (Register), graph projection |
prior head, or none for a context’s first Version | |
| Branch creation | origin Version | |
| Execute, ontology, claim, suppress, reference | prior head | |
| Restore (Branch, context or Project) | prior head | provenance: restored with the source Version |
| Bring | prior head | provenance: brought with the Slice source Version; field baselines already record it per field |
| Upstream incorporation | prior head, upstream Version | |
| Whole-Proposal acceptance | prior head (if any), Proposal source Version | |
| Partial Proposal acceptance | prior head (if any) | the review’s accepted items and mappings |
Proposal payloads and acceptance proofs are frozen selections, not commits; they carry no parents. An origin captured for Branch creation or Project upstream does not move a head and carries no parents.
Bounds
Section titled “Bounds”- At most
MAX_PARENTS(8) parents, each non-nil, distinct, not the Version itself, and already in the permanentidentitiesledger. - Signature name: 1–256 UTF-8 bytes, no surrounding whitespace, no control
characters. Email: at most 254 bytes,
local@domainwith a 1–64 byte local part, a 1–253 byte domain with no empty labels, and no whitespace, control characters or angle brackets; syntax only, never delivered to. ORCID: the bare0000-0000-0000-000Xform with its ISO 7064 MOD 11-2 check digit verified. - Requests reject invalid signatures with a validation error before preparing content; storage refuses a registry holding one as corrupt.
Ancestry ledger
Section titled “Ancestry ledger”ResearchRegistry.ancestry maps every Version recorded with parents to its
parent list. Like identities it is permanent: releasing a Version’s payload
keeps its entry, every publisher must carry every entry unchanged, and it is
bounded by MAX_RECEIPTS (4,096) with a resource-limit refusal. A retained
Version’s parents must equal its ledger entry; the ledger must be acyclic and
name only known identities. Descent of a released Version stays walkable.
A research interchange manifest carries the ancestry of its closure: every entry reachable from its Versions, with the identities of every ancestor cited. An imported archive’s ancestry must agree with the importing registry’s ledger.
Credit and access
Section titled “Credit and access”Credit lives in the project; access lives on the host. A public repository is public: there is no anonymity and no credit redaction. Authors and committers recorded in research history are always published, and published credit is public and permanent. Because the Version identity commits to author and committer, removing credit would be a history rewrite, and #2124’s rule that a rewrite never changes a published Version identity forbids it. Nothing in the publish path masks or substitutes credit.
Published credit appears in three derived places, none of them entered by hand:
- each lineage Version carries its
authorandcommitter, and a Fork’s origin citation carries the cited origin Version’s (origin_author,origin_committer) (ADR 0052); - the summary’s
peoplelist is derived from the lineage’s credited Versions: every distinct signature with the roles (author,committer) it holds, in canonical order.ProjectSummary::bind_lineagerequires equality, so a reader recomputes and checks it (ADR 0051); metadata.authorsstays user-authored display text and is published as written. It is neither derived from history nor redacted.
Permission to see or change anything belongs to the Hub or other host and never
appears in project, package, lineage or summary formats. The former
access.collaborators, access.visibility and access.access_policy Project
metadata fields are removed in place from the Project metadata record and from
every format. No format publishes or accepts them, and there is no migration
(pre-v1): Project metadata contract version 2 omits access, and a stored version
1 record, including one in a revision 6 Project, is refused on read as
GF_UNSUPPORTED_PROJECT_FORMAT. The portable export planner reads the metadata
participant before copying it, so a raw legacy participant can never reach a
package. The revision 6 research layout itself remains readable (see Format and
compatibility); a Project whose metadata is version 1 must have that participant
rewritten with the current contract before its metadata is read or exported.
Format and compatibility
Section titled “Format and compatibility”Research capability and registry revision 7 (graphforge-research-registry/7,
producer research/7). Revision 6 research layouts remain readable; revisions
before 6 are refused with GF_UNSUPPORTED_CAPABILITY_VERSION, as before.
This layout compatibility does not override the separate interchange producer
check: an archive’s outer graphforge-storage/<version>;research-interchange/1
producer must match the reader’s exact package version. Pre-v1 package-version
changes can therefore reject an older package, or reject research-history access
to a Project containing such an archive, even when its revision 6 layout is
readable. Opening the Project alone does not establish that its archived
research history is admissible.
Layout compatibility needs no translation because every field revision 7 adds is omitted when empty: revision 6 registry bytes are valid revision 7 bytes and decode, validate and digest identically.
- Reading. A revision 6 registry (
registry/6) is read as-is. Its Versions are parentless legacy roots with no author, committer or provenance; its ancestry ledger is empty; their identity digests are unchanged. A registry labelled revision 6 that holds any revision 7 commit data is refused. - Upgrade on the first research write. Publishers that do not write research (graph writes, checkpoints) carry revision 6 unchanged. The first research write (any registry operation, or research decisions) relabels the research capability and its participants to revision 7 in that same publication, so the upgrade is atomic and crash-safe: a crash leaves CURRENT at either the revision 6 or the revision 7 generation. No record’s bytes are rewritten. The new Version’s first parent is the prior head even when that head is a revision 6 Version. Once a Project is revision 7, no publication may label it revision 6 again.
- Packages. Revision 6 research packages that satisfy the current producer
contract are admitted and import at revision 7 by the same relabelling; their
imported Versions and archive stay legacy roots and keep
research_capability_version6 in the archive. Every export is written at revision 7, including a whole-Project export of an admissible Project imported at revision 6. This does not promise import or re-export of archives from a different pre-v1 package version. A Version freezes the Project metadata of its time, so a Version captured with the retired metadata contract (version 1, which carriedaccess) cannot be exported at all (GF_UNSUPPORTED_PROJECT_FORMAT); this includes the revision 6 fixtures. - Readers of research participants. Canonical decisions written at revision 6 read unchanged. Checkpoint summary diffs span the upgrade; record-level checkpoint diff still has no research registry adapter at any revision.
Author and committer are optional on requests in this slice, so existing callers
keep working and record none. Branch creator_uuid and Proposal and review
actor_uuid stay on their records until #1772 supplies default signatures;
new Version credit uses author and committer.
Alternatives
Section titled “Alternatives”- Parents only in a side ledger. Rejected: the identity would not commit to them, so a publisher could rewrite history without changing any identity.
- Authenticated identities. Rejected: authentication is the host’s job, and committing account identifiers would put access control into history.
- Migrating revision 6 records. Rejected: giving old Versions parents or signatures would change their identities. They stay parentless roots.
- A separate upgrade command. Rejected: the first research write already publishes the whole registry atomically, so the upgrade needs no extra step.
Consequences
Section titled “Consequences”- A head that does not descend from the prior head is refused at publication, which is what lets publish and clone fast-forward (#1771, slice 2).
- Every new Version is larger by its parent list and signatures; the ledger adds at most one entry per Version identity.
- Every reader of the research capability accepts two revisions until revision 6 support is withdrawn by a later decision.