| rfc | 0068 | |||
|---|---|---|---|---|
| title | Graph commit record | |||
| track | maintainer | |||
| status | draft | |||
| implementation | not-started | |||
| authors |
|
|||
| created | 2026-09-15 | |||
| updated | 2026-10-08 | |||
| discussion | ||||
| supersedes | ||||
| superseded_by | ||||
| blocked_on |
|
Depends on: RFC 0067 for private
table effects and the three-field pin, as amended by
RFC: Detached-only tables, which
removed promotion at storage stamp v11. This RFC changes only where the
graph commit is stored and how current state is read.
Surveyed: OmniGraph 0.11.0 on main at d1dd8b97; Lance 11.0.0 and
12.0.0-rc.1; RFCs 0013, 0022, 0024, 0028, 0030, 0038, 0042, 0058, 0062,
0063; Lance discussions #7176, #7222, #7260, #7264; object_store 0.14.1;
probe 14 in crates/omnigraph/tests/wal_probe_investigation.rs.
A graph commit becomes one immutable object, the commit record, written with the storage crate's conditional create at a name that encodes the next sequence number on its branch. The conditional create is the publication door and the cross-process fence. The record carries the parent commit, actor, schema identity, the pins of every table the commit touched in RFC 0067's shape, catalog deltas and merge lineage.
Current state is read from the newest checkpoint, an object holding every table pin at one sequence number, plus the records after it. The read cost is bounded by the checkpoint interval, not by history. A best-effort hint object names the latest sequence; readers probe upward from it.
In the throughput path recorded in RFC 0067 this is the fourth and last step, the only one that moves the per-branch commit ceiling itself; live maintenance, staging outside the gates and group commit come before it and do not depend on it.
The __manifest Lance dataset, its per-branch native refs, its journal fold,
the registration clock of RFC 0062, and the clone adapter that repairs
inherited index bases for it are retired. Graph branches become
streams of records with a metadata object each. Nothing follows a
publication: detached-only tables removed table-pin promotion, and a record
names the schema contract it accepts, as a __manifest publication carries
it since stamp v13. A content write is staging, one detached commit per
touched table in parallel, and one conditional create.
What does not change: per-type Lance datasets, RFC 0067's detached staging as detached-only tables leave it, the query and merge semantics, policy enforcement, the HTTP and CLI contracts, and every public error type.
Every graph commit today is a merge-insert into a Lance dataset, and every
writer and reader that needs current state folds that dataset's rows.
Measured on this branch with the cost harness, one write at shallow depth
performs 23 object reads, 18 of them on __manifest. The fold grows with
uncompacted history: RFC 0013 traced production writes of tens of seconds to
it, and a production server measured on 2026-09-04 spent most of a
multi-second one-row write reloading __manifest across hundreds of object
reads. RFC 0024 tried to make current state flat with head rows inside the
same dataset and rejected its own candidate because a filtered scan over a
history-sized Lance table is still history-sized physical work.
Upstream reached the same conclusion for its directory catalog: per-commit writes through a Lance table measured 0.34 to 1.1 operations per second at scale and flat under concurrency (#7176), and the mechanism was removed (#7222). The replacement shape upstream converged on, and the one this RFC's author proposed there (#7260), is a small immutable record published with put-if-not-exists. The maintainer objection to that proposal was that a log cannot fence external fast-path writers who never consult it. Inside the graph that objection does not apply: the record is the only publication door for graph state, and under RFC: Detached-only tables nothing writes a graph table's linear history after the table is created.
RFC 0067 removes the recovery liability but leaves this cost in place. This RFC removes the cost and, with it, the last piece of engine-owned Lance machinery that exists only to coordinate other Lance datasets.
- Every public API, error type and CLI command keeps its contract. Commit ids, branch names, snapshot addressing by commit id, and change-feed cursors keep their meaning.
- A write acknowledges after its record is durable. A lost response is resolved by reading the record at the attempted sequence and comparing its commit id; the engine reports success or a typed conflict, never unknown, unless the read itself fails.
- Reading a branch costs at most one hint read, a few existence probes, one checkpoint read and up to one checkpoint interval of record reads, at any history depth. Time travel to a commit costs the same from the checkpoint at or before it.
branch createwrites one stream metadata object and one checkpoint.branch deletemarks the stream retired with a conditional update. Both are visible to every process at once.cleanupprunes records and checkpoints past the retention horizon that no retained checkpoint depends on, and protects table versions pinned by any retained record or checkpoint: the tracing collector of RFC: Detached-only tables takes its roots from retained records and checkpoints instead of retained__manifestversions.--keep Nkeeps its meaning over graph commits.repairreports a hint behind the true latest, a missing checkpoint, and a record whose parent is not the previous record, and can rewrite the hint and checkpoint. It never rewrites a record.- Records and checkpoints are JSON and readable with any object-store client, which is the operator's view of graph history.
- Graphs written by this format refuse older binaries by storage stamp. Existing graphs reach the format through the offline conversion route or by export and rebuild.
{graph root}/_graph/
streams/{stream}.json stream metadata (branch authority)
{stream}/{u64::MAX - seq:020}.commit one record per graph commit
{stream}/checkpoints/{seq:020}.json every table pin at seq
{stream}/latest.json hint: {"seq": N}
A stream is one branch life, named {logical}.{ULID} as RFC 0042
names native refs today. The logical name stays the only public identity.
Reverse-sorted record names make the newest record sort first on stores
that list lexicographically (S3, Azure, GCS, memory); the local filesystem
and S3 Express do not, which is why the hint exists.
{
"format": 1,
"seq": 4127,
"commit_id": "01K5...",
"parent_commit_id": "01K4...",
"stream": "main",
"actor": "act-ragnor",
"created_at": 1757900000000,
"schema": {"identity_domain": "...", "ir_hash": "sha256:...", "identity_version": 9,
"content_hash": "sha256:..."},
"pins": [
{"stable_table_id": 12, "incarnation": 3, "table_key": "node:Person",
"target_version": 1201, "staged_version": 9223372036854775901,
"transaction_uuid": "...", "entity_count": 48012}
],
"registrations": [], "tombstones": [], "renames": [],
"lineage": {"kind": "merge", "source_stream": "review.01K3...", "source_seq": 88,
"merged_parent_commit_id": "01K2..."},
"idempotency_key": null
}A record is O(touched tables); probe 14 measured 765 bytes for three
tables. Unknown fields are ignored on read and preserved on rewrite; a
format above the reader's is refused before interpretation, as the
recovery sidecar schema is today. idempotency_key is the surface issue
#513 asks for and is out of scope here except for the field.
schema.content_hash names the accepted contract's source and IR, archived
under __history/schemas/<sha256>.schema with a conditional create before
the record, the object a stamp-14 commit names today. Content-addressed and
immutable, the archive is unreachable until a record names it, so a record
publishes its schema contract with its pins, and schema acceptance needs no
step after publication.
A checkpoint is the complete fold at one sequence: every live table's pin,
its registration and its version metadata, plus the schema identity, its
content hash and the head commit id. Probe 14 measured 38 KB for 217 tables. The publisher
writes a checkpoint after its own record whenever seq is a multiple of the
interval, best effort; a checkpoint is a pure function of the records up to
its sequence, so a missing or torn one is rebuilt from the previous
checkpoint and the records between. Checkpoints are derived state under
invariant 7 and never publication authority.
- Capture authority exactly as RFC 0022 §2 describes, from one reconstruction of the stream: hint, probe upward, checkpoint, records. The captured token is the latest sequence and its commit id.
- Prepare, validate and stage detached effects as RFC 0067 describes, and archive a changed schema contract's content (see Record).
- Write the record at
seq + 1withwrite_text_if_absent. Success is the graph commit.Ok(false)means another writer publishedseq + 1: discard the attempt and re-prepare from the new state, exactly as a read-set change does today. A strict API returns the typed conflict. - On an error whose outcome is unknown, read
seq + 1. An equal commit id is success; a different one is the conflict; an absent object is a failed attempt with no effect. - Write the hint and, on an interval boundary, the checkpoint, best effort. Nothing else follows the publication.
The read set is arbitrated by the record name: two writers who captured the
same sequence contend on the same object, and the object store admits one.
This is the same coarse per-branch token RFC 0022 uses through the
graph_head row, with the same consequence that same-branch writers
serialize and cross-branch writers do not. Probe 14 shows eight concurrent
writers landing at sequences 1 through 8 with one winner per number.
- Read the hint; treat absence or a parse failure as sequence 0.
- Probe
seq + 1,seq + 2, ... until absent. A hint stale bykcostsk + 1probes (probe 14: a hint three behind cost four). - Read the checkpoint at or before the latest; if absent, the previous one.
- Read the records after it, concurrently, and fold.
The fold is the existing manifest projection with the journal replaced by records: registrations keyed by stable identity and incarnation, the newest record wins, tombstones retire, renames preserve identity. RFC 0062's registration clock is the record sequence.
Stream metadata carries the logical name, the parent stream and fork
sequence, the creator, and a retirement marker. branch create writes the
metadata with a conditional create, then writes the branch's first
checkpoint, which is the parent's fold at the fork sequence, so a branch
read never touches its parent. branch delete sets the retirement marker
with write_text_if_match. Listing branches lists the streams/ prefix and
filters retired entries. The path-prefix rules and the native-ref
retirement metadata of RFC 0042 have no counterpart, because streams are
flat objects.
A merge prepares against a captured source (stream, seq) and publishes on
the target stream with lineage naming that source, preserving RFC 0058's
retained ancestry and RFC 0063's self-contained lineage. Pointer adoption
copies the source pin. The source is an effect precondition, not a member
of the target's arbitration, exactly as RFC 0022 §6.2 states.
A snapshot is (stream, seq). The fold to a sequence uses the checkpoint at
or before it. The change feed's first-parent order is record order, and its
per-commit diff uses the pins of consecutive records as it uses consecutive
manifest versions today; the RFC 0030 cursor encodes the sequence.
Recorded as an idea and its reasoning, not as part of this RFC's scope: the record is the natural place to keep track of what each commit changed, so that the change feed and merge stop recomputing it.
What recomputes today. The change feed finds the tables a commit touched by
diffing registration rows across manifest versions, reads each pin's
transaction, walks fragment metadata and deletion vectors, and prunes to
changed rows through the _row_last_updated_at_version stamps before
hydrating images. Merge gathers source and target candidates since the base
from deletion vectors and new fragments under the keyed-write byte cap, with
a proven-pure-insert shortcut that walks up to 1,024 versions of history and
a projection cache that refreshes incrementally. Both derive the delta from
Lance physical state on every call; the cost is flat in history only because
of those caps.
What the RFCs already give. RFC 0067 makes every commit's effect on a
table one recorded transaction with a uuid, an exact unit a change set can
name. RFC: Detached-only tables already derives each commit's change set
from that transaction, its fragments for inserts and updates and
omnigraph.deleted_ids for deletes, with the exact path as fallback, so
the feed no longer prunes through row stamps. This RFC's record already
carries the pins of every table a commit touched, which alone removes the feed's table-discovery scan, turns
the first-parent walk into reading records by sequence, and makes the
caught-up poll one hint read.
The idea. Extend the record with a per-table change set: the ids the commit inserted, updated and deleted, inline up to a cap and otherwise in a delta object the record references, the same shape as the inline-transaction rule. Every writer knows its id set at publication: a mutation knows the keys it inserted and updated, the delete planner materializes the exact id list for its filter, a load knows its batch keys, and a merge has its classified rows. The set is derived from what was actually committed and lands in the same conditional create as the pins, so it cannot drift from the commit it describes. Inserted and updated ids are also recoverable from the new fragments, so the record strictly needs only the deleted ids, which the transaction already carries today; on the record they would be read without opening each table's transaction.
What the consumers would become. The feed serves a page by reading records from the cursor forward and hydrating only the listed ids: no candidate scans and no dependence on stamps, with cost proportional to the change volume on the page. Merge takes the union of change sets over the records on each side since the base, which is bounded by divergence and is exactly the set it must classify anyway, then hydrates current values for those ids; the projection cache and the pure-insert history walk collapse into record reads. Retention follows records and checkpoints, so a feed gap maps to record retention rather than to Lance version pruning. Group commit publishes a batch as one graph commit (RFC: Group commit), so a batch is one record and one feed block, and change sets need no per-member split.
Why it is not in scope here. A change set duplicates something derivable from Lance, and invariant 12 applies: the exact path must remain as the fallback, and a DST instrument should recompute a sample of records from physical state and compare before the feed or merge trusts the recorded set alone. Record size, the spill threshold, and what a change set means for a merge whose classification reads a Blob column also need their own evidence. It belongs in an extension of this RFC or a small RFC after it.
Records and checkpoints older than the retention horizon are pruned when no
retained checkpoint needs them as its fold base. Table versions pinned by any
retained record or checkpoint are protected from Lance cleanup, which is the
protection cleanup computes today with a simpler input. The retired
__manifest dataset is deleted by the conversion route once the first
checkpoint is durable.
A conditional-create conflict is StorageFailure::Precondition. A lost
response is resolved by read-back and never surfaced as unknown when the
read succeeds. Durability on return is the object store's; the local
backend stages a complete temporary file and hard-links it, which the
storage crate pins, and does not fsync, which matches Lance's own manifests
on the local filesystem.
- 2 (one publication door): the record's conditional create. Strengthened: the door is one object-store primitive with no fold behind it.
- 3 (one coherent view): a snapshot is
(stream, seq); a write captures one sequence and re-prepares on any movement. - 4 (publish once): one record per graph commit.
- 5 (recovery is part of the commit protocol): publication has no pre-publication durable effect of its own; a lost acknowledgement is resolved by read-back; nothing follows the publication: no table-pin promotion (RFC: Detached-only tables), and the schema contract is named by the record, its archived content unreachable until a record names it.
- 7 (physical acceleration is derived): checkpoints and hints are derived, rebuildable, and never authority.
- 11 (bounded, observable): a current-state read is bounded by the checkpoint interval; contention retries are bounded and typed.
- 12 (one source of truth): the record stream replaces
__manifestas the graph authority. Lance datasets and the accepted schema remain what they are. The invariants text names__manifest; accepting this RFC updates that sentence. - 13 (evidence matches the boundary): the gates above.
Deny-list items: "a custom WAL or transaction manager" names machinery that duplicates Lance's per-dataset transactions. The graph's own commit journal is already OmniGraph's, implemented today as a Lance dataset; this RFC changes its representation to the primitive Lance itself commits with and removes a fold, it does not add a second log. "Cold full-history reconstruction per request" is what the checkpoint bounds. No queue, no shadow copy, no process-local lock presented as fencing.
- Wire: none.
- Storage: the next storage stamp after v14. New graphs write records only.
Conversion of a v14 graph writes one record per graph commit on each
branch, read from the branch's
__manifestbuffer and its__historyfiles with the legacy area included, a checkpoint at each branch head, stream metadata for each live native ref, and then deletes__manifestand the commit files under__history, keeping the schema archive the records name; it runs offline under the same quiescence RFC 0064 requires of explicit upgrades. Export and rebuild remains the alternative. - Downgrade: refused by stamp. Reversal to v14 is a fold of the record
stream into each branch's
__manifesthead and buffer and the__historyfiles, feasible because records are complete, and is provided by the conversion tool in both directions until this RFC's implementation reaches complete. - Support boundaries: unchanged from RFC 0067.
- Keep
__manifestand compact it aggressively. Compaction bounds fragment count, not the semantic row volume, and the merge-insert still reads the dataset on every publish. RFC 0013 and RFC 0024 measured this. - Durable heads inside
__manifest(RFC 0024). Rejected by its own Gate A: logically flat, physically not. - An external manifest store (DynamoDB or SQL). Adds a service dependency for what put-if-not-exists on the object store already gives, which is why Lance keeps its external store optional.
- Lance namespace directory catalog. Its
__manifestis the same shape with the same measured cost, which is why upstream removed per-commit writes from it. - Wait for upstream multi-table transactions (#7264). Stalled since July; would stage on branches and flip through the catalog, which is the cost being removed here.
- One dataset per graph. Recorded in RFC 0067 as the stronger end state; it removes this record too, at the cost of a full rewrite. This RFC is the optimum that keeps per-type datasets.
| Claim | Result |
|---|---|
| Eight concurrent writers each publishing one record by conditional create | sequences 1 through 8, one winner per number, no gaps; losers retried up to eight times |
| Reconstruct latest after 1,000 records with a checkpoint every 50 | one hint read, one existence probe, one checkpoint read |
| Time travel to sequence 437 | 38 objects |
| Hint stale by three | four existence probes, one checkpoint read |
| Record for three tables; checkpoint for 217 tables | 765 bytes; 38 KB |
| Local listing order; bounded listing | unordered; bounds cap, they do not order |
The S3 conditional put is object_store 0.14's default for S3 and the handler Lance selects for s3, gs, az and memory; the storage crate's conditional-create tests pass on the memory and local backends.
write_cost.rs,warm_read_cost.rs,branch_control_cost.rs,changes_cost.rs,merge_cost.rs: replace the__manifestread curves with record-stream curves and the new ceilings.branching.rs,point_in_time.rs,merge_truth_table.rs,merge_fast_forward.rs,lineage_projection.rs,changes.rs: same logical assertions over the new authority.maintenance.rs: retention of records and checkpoints, protection of pinned table versions, conversion round trip.failpoints.rs: crash after the record and before the hint, before the checkpoint; lost acknowledgement resolved by read-back.- DST: concurrent publishers on one stream, stale hints, missing and torn checkpoints, conversion under injected faults.
- Server
s3.rsand clusters3_cluster.rs, and the Azure suites: the race and lost-acknowledgement cases. forbidden_apis.rs: the record writer joins the durable-call registry; the__manifestpublisher leaves it.
- Publication: one conditional create, plus one hint write and one amortized checkpoint write, at every depth.
- Current-state read: at most 3 + (interval - 1) object reads at depths 10, 100, 1,000 and 10,000, on file, S3 and Azure.
- Branch create: at most three objects. Branch delete: one conditional update.
- DST: zero lost or duplicated commits across the concurrency scenarios.
- RFC 0067 and RFC: Detached-only tables landed, with the pin shape this record carries (done).
- Record, checkpoint, hint and stream readers and writers behind the next storage stamp, exercised on new graphs; the cost instrument checked in.
- Conversion tool in both directions with its round-trip evidence.
- Retire the
__manifestpublisher, state fold, namespace and layout modules, the__manifest-era upgrade routes for new graphs, native__manifestbranches, and the clone adapter; updatewrites.md,architecture.md,invariants.md§12,versioning.md,control-plane.mdwhere it names manifest objects, and the release notes.
Each stop leaves main shippable; the stamp gates activation.
- Checkpoint interval: fixed at 50, or size-based so wide catalogs checkpoint less often.
- Whether stores that list lexicographically should skip the hint and use a bounded listing as the primary latest lookup.
- Whether stream metadata needs a graph-level index object, or listing the
streams/prefix suffices for the branch counts the server expects. - Group commit at the branch gate: RFC: Group commit (draft, PR #785) publishes a batch as one graph commit, so it would be one record with one commit id. Decided there.
- Whether the record carries per-table change sets (see Change sets on the record), and if so whether only deleted ids or the full inserted, updated and deleted sets, and at what size they spill to a delta object.
This section is implemented and stands apart from the draft above. Storage
stamp 14 kept __manifest and bounded its history another way: a branch's
__manifest holds the head and a byte-bounded buffer of commits, and older
commits are released into immutable Lance files under __history
(versioning.md). The offline
upgrade from stamp 13 (RFC 0064) has to
keep every pre-upgrade commit addressable by its id. Those ids are ULIDs, not
the hb1.<block>.<slot>.<nonce> ids whose name is their address, so the
upgrade writes a legacy area, __history/legacy/, once, and nothing
writes there again.
Every object is immutable, created with put_if_absent, and never deleted.
| Object | Path under __history/ |
Format | Content | Bound |
|---|---|---|---|---|
| Data file | legacy/data/<n:08>.lance |
Lance V2_2 file with the columns of every __history extent |
the complete HistoryRecords of one writer's consecutive own commits, in chain order |
commit fields ≤ HISTORY_RELEASE_BYTES (256 KiB); record bytes ≤ LEGACY_FILE_RECORD_BYTES (2 MiB); rows ≤ HISTORY_BLOCK_SLOTS; a lone larger record takes its own file |
| Directory | legacy/locator/directory.oglx |
OGLD0001, layout version, source stamp, attempt ULID, counts, then one entry per data file (version range, rows, SHA-256 of its records), per id shard and per writer shard (key range, entries, SHA-256), the sorted absent merged parents, and a trailing SHA-256 |
the whole locator | ≤ TAIL_BYTES (512 KiB) |
| Id shard | legacy/locator/ids/<n:08>.oglx |
OGLI0001, count, 24-byte entries sorted by binary ULID: id, file, row |
commit id to (file, row) | ≤ TAIL_BYTES: 21,844 entries |
| Writer shard | legacy/locator/writers/<n:08>.oglx |
OGLW0001, entries sorted by SHA-256 of the native name: kind (Main, Live, Retired, Orphaned), name, parent name and version, head version and id, first file and file count |
one entry per writer with own commits | ≤ TAIL_BYTES |
| Schema content | schemas/<sha256>.schema |
the existing OGSC0001 archive |
each distinct contract a legacy record names | existing |
At about 430 bytes of commit fields and 6 KiB of record per commit the 2 MiB cap binds first, near 340 commits per file:
| Legacy commits | Data files | Id shards | Directory | Lookup by id, first / later / warm | Full lineage walk |
|---|---|---|---|---|---|
| 10,000 | 30 | 1 (234 KiB) | about 2 KiB | 4 / 1 / 0 GETs | 1 LIST + 30 suffix GETs |
| 1,000,000 | about 2,950 | 46 | about 153 KiB | 4 / 1 or 2 / 0 GETs | 3 LIST pages + 2,950 suffix GETs |
The second row sizes the locator only: the stamp-13 census refuses above
about 75,000 commits on one lineage. The directory reaches TAIL_BYTES near
10,000 files, about 3.4 million commits. Two levels stay because a
single-level map of a million commits is 24 MiB, above what the extent cache
holds.
The deny-list rejects a storage primitive Lance already owns, maintained parallel truth and cold full-history reconstruction per request. The legacy area is argued against each.
- The records are Lance files. A data file is an extent like a native
block: the same columns, the same writer (
write_extent), the same reader, one suffix GET ofTAIL_BYTESfor its whole lineage. Only its key and its validation (one writer, a first-parent chain, nohb1id) are new. - The locator is a map, written once. The Lance-owned alternative is a dataset with a scalar index on the commit id. That brings a manifest, a version chain, a transaction log and index files for content that is written by one offline, fenced process and then never changes; a lookup would read a manifest and index pages where the flat form reads one shard of bounded size. No object here is updated, so there is no transaction to manage, no log and no concurrent writer. The shape is the one the merged schema archive already has: a magic, a byte-exact re-encode check on decode and SHA-256 digests.
- It is not parallel truth. After a ref is converted its
__manifestholds the head alone; the legacy data files are the only copy of the earlier commits. The locator is derived from them, and cannot drift because neither is ever rewritten: the directory carries the digest of every shard and of every file's records, the upgrade intent carries the digest of the directory before the fence, and the upgrade reads every object again against those digests before it activates the graph. A converted head that is later released as a singleton is an equal copy, proven before the fence, and full reads require copies to be equal. - No read reconstructs history. A lookup by id on a handle that has seen the directory is one shard GET and one data-file read; a full lineage walk is one LIST and one suffix GET per file, as for native blocks. A root born at stamp 14 pays one directory miss per handle on a commit id that is genuinely absent, and nothing otherwise.
Two details follow from the substrate. All locator objects share the prefix
legacy/locator/ because a local put_if_absent can leave a
<path>.tmp.<uuid> file behind; the extent listing skips that one prefix and
still refuses any other unknown key. And the absence of the directory is the
one absence a handle keeps: the directory is written before activation and
never again, no handle can exist before activation because the pending key
refuses every open, and a root restore clears the handle's cache.
A merged parent that exists nowhere in the source (a merge followed by a branch delete before stamp 8 reclaimed the tree) is kept verbatim on its commit and listed in the directory; lineage reads treat a listed id as known absent. A first-parent gap refuses the upgrade.
Reversal is restoring the pre-upgrade backup. A stamp-14 binary built before
this amendment reads no legacy/ and reports pre-upgrade ids as not found.
-
2026-09-15: drafted as the sequel to RFC 0067 after the write-path investigation; probe 14 recorded as initial evidence.
-
2026-10-04: amendment added for the legacy area of the stamp-13 to stamp-14 upgrade: Lance data files plus flat locator objects under
__history/legacy/, in place of a Lance dataset with an id index. The draft's record stream is not changed by it. -
2026-10-04: the legacy area of the amendment also holds the pre-upgrade commits of a stamp-8 or stamp-9 source (release v0.11.0), which the same upgrade now converts (RFC 0064). The layout, the bounds and the argument of the amendment are unchanged; the directory's source stamp field is 8, 9 or 13. Two statements of the amendment hold for a stamp-13 source only. The schema content row lists each distinct contract a legacy record names: for a stamp-8 or stamp-9 source that is one contract, the one at the graph root at the upgrade, named by every record. And the census bound of about 75,000 commits on one lineage is derived from the stamp-13 overwrite publish; a stamp-8 or stamp-9
__manifestkeeps every row and every version, and its bound in commits has not been derived. The amendment's heading keeps its name so that existing links resolve. -
2026-10-08: consistency pass after RFC: Detached-only tables (storage stamp v11), which removed promotion.
- Promotion is gone from the dependency, the summary, the publication steps, the invariants, the test windows and the rollout.
- The stamp becomes the next one after v14, and conversion starts from a
v14 graph: each branch's
__manifesthead and buffer and its__historyfiles, the legacy area of the amendment included. - The change-set section now starts from the transaction-carried change sets detached-only tables ship.
- The group-commit question points to RFC: Group commit, and the change-set section follows its one commit per batch instead of a per-member feed.
- "Nothing follows a publication" now rests on two facts: no table-pin promotion, and a record that names its accepted schema contract by content hash, archived before the record as stamp 14 archives it.
- The stale "Number provisional" banner and a self-referential blocker ("RFC 0068 accepted") are removed.
The record's design is unchanged.