Skip to content

Commit e34c32f

Browse files
committed
docs(core): define R-15 commit authority
1 parent b2a61da commit e34c32f

1 file changed

Lines changed: 42 additions & 16 deletions

File tree

docs/native/R15-SECURE-STORAGE-CONTRACT.md

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,29 @@ anchor for later control-record commits while ordinary record generations contin
317317
per-record markers. This is a storage mechanism boundary, not a renderer or Storage-Core policy
318318
escape.
319319

320+
### 5.4 Canonical digest contract
321+
322+
All R-15 digests use SHA-256 with a 32-byte output and an explicit ASCII domain-separation prefix.
323+
The digest input is never locale-dependent or JSON-dependent. Unless a rule below says otherwise,
324+
strings use the §6.2 encoding (`u32be(byte_length)` followed by UTF-8 bytes), integers use unsigned
325+
big-endian fixed-width encoding, and records are sorted by the encoded byte tuple stated below.
326+
Implementations must reject invalid UTF-8, over-limit fields, and checked-length overflow before
327+
hashing; a digest algorithm or input change requires a new envelope/journal version.
328+
329+
| Digest | Exact input, in order | Use |
330+
|---|---|---|
331+
| `content_digest` | `"worldscript-r15/content/v1"` bytes, then the complete canonical protected envelope bytes (`WSR1` header plus ciphertext) | Lets the commit marker verify that the generation-addressable envelope is the one it committed without exposing plaintext. |
332+
| `marker_set_digest` | `"worldscript-r15/marker-set/v1"` bytes, then `u32be(entry_count)`, then each entry sorted by `(record_class bytes, logical_record_id bytes, project_id bytes or empty)` and encoded as record class, logical ID, project ID presence/value, `u64be(record_generation)`, `u64be(key_epoch)`, `u32be(state_code)`, and the 32-byte `content_digest` | Checkpoints the complete authenticated record-marker set in the authority root. |
333+
| `root_digest` | `"worldscript-r15/root/v1"` bytes, then `u64be(root_generation)`, `u64be(active_key_epoch)`, `u64be(root_checkpoint_revision)`, the 32-byte `marker_set_digest`, and canonical root commit evidence (`operation_id`, fencing generation, journal revision, and committed state) | Authenticates the root body named by the pointer. The digest field itself is excluded from its input. |
334+
| `pointer_digest` | `"worldscript-r15/pointer/v1"` bytes, then the canonical slot name, `u64be(root_generation)`, and the 32-byte `root_digest` | Binds the active-slot pointer to one committed root slot. |
335+
| `inventory_digest` | `"worldscript-r15/inventory/v1"` bytes, then `u32be(inventory_version)`, `u32be(entry_count)`, and sorted record descriptors containing class, logical ID, project ID scope, source-authority kind, and source generation | Makes a migration inventory reproducible without hashing plaintext payloads. |
336+
337+
The root's `marker_set_digest` is committed together with every record-marker authority change,
338+
including an ordinary write, under the same `with_fence` boundary. Record generations remain
339+
independent; the root does not serve as a per-record lookup table. A marker/root digest mismatch is
340+
therefore a pending or recovery state, not a readable new generation: startup preserves the prior
341+
marker and candidate, then completes or rolls back the fenced transition using journal evidence.
342+
320343
## 6. Protected-record envelope
321344

322345
### 6.1 Version 1 wire contract
@@ -585,13 +608,13 @@ The authority manifest separately selects the active storage epoch and is switch
585608
migration/rotation commit protocol. These control records are protected and included in the
586609
inventory; they are not mutable plaintext sidecars.
587610

588-
The authority-root manifest does not duplicate every record's generation. It anchors the active
589-
storage epoch and carries a digest of the sorted authenticated record-marker set (logical identity,
590-
marker generation, epoch, and state). Each `record-commit` marker remains authoritative for exactly
591-
one logical identity. A read requires the envelope generation and epoch to match that marker, and
592-
the marker-set digest to match the committed root (or the matching migration journal view).
593-
Independent records may therefore advance generations independently without weakening root-level
594-
rollback detection.
611+
The authority-root manifest does not duplicate every record's generation or act as their lookup
612+
table. It anchors the active storage epoch and carries the `marker_set_digest` defined in §5.4.
613+
Each `record-commit` marker remains authoritative for exactly one logical identity. Record
614+
generations may advance independently, but each marker change and the corresponding root digest
615+
checkpoint are committed under the same fence. A read requires the envelope generation and epoch to
616+
match its marker and the marker-set digest to match the committed root (or the matching migration
617+
journal view); a marker written without that root checkpoint remains pending and is not served.
595618

596619
Outside a migration, Core requires the authenticated record generation and epoch to match both the
597620
committed record marker and the authority-root manifest. A valid older ciphertext for the same
@@ -658,13 +681,15 @@ For a new or replacement protected record, Core semantics are:
658681
old active generation.
659682
8. Flush/sync the containing directory or platform-equivalent metadata required to persist the
660683
promoted generation.
661-
9. Atomically replace/sync the protected commit marker from `PENDING` to `ACTIVE(new)`; only this
662-
marker change transfers ordinary record authority. For a migration target, the marker change and
663-
its `VERIFIED_TARGET` journal/read-authority checkpoint are one `with_fence` transition: retain
664-
`READ_AUTHORITY_PENDING` and block target reads until both are durable, or return recovery if the
665-
adapter cannot provide that fenced semantic.
666-
10. Only after the marker, its containing directory, and any required migration checkpoint are
667-
durable return `DURABLE_COMMIT_SUCCESS` and advance the authority manifest when applicable.
684+
9. Atomically replace/sync the protected commit marker from `PENDING` to `ACTIVE(new)` together
685+
with the authority-root `marker_set_digest` checkpoint under one `with_fence` transition. Only
686+
this paired commit transfers ordinary record authority. For a migration target, include the
687+
matching `VERIFIED_TARGET` journal/read-authority checkpoint in the same fenced semantic: retain
688+
`READ_AUTHORITY_PENDING` and block target reads until all required state is durable, or return
689+
recovery if the adapter cannot provide that semantic.
690+
10. Only after the marker, root checkpoint, their containing directory metadata, and any required
691+
migration checkpoint are durable return `DURABLE_COMMIT_SUCCESS`; never report success after a
692+
marker-only commit.
668693
11. Reconcile stale staging deterministically on startup using authenticated generation/operation
669694
metadata; never delete an unrecognized file merely because it has a temp suffix.
670695

@@ -701,13 +726,14 @@ Core never turns an uncertain state into default project data.
701726
| After file sync, before promotion | Complete staging may be verified/adopted by the journal or discarded; old authority remains until commit. |
702727
| After promotion, before directory sync | Old marker remains active; the new generation is preserved and not yet authoritative. |
703728
| After directory sync, before marker advancement | New bytes are durable but marker metadata is stale. Startup validates the authenticated pending intent and completes the marker, or restores `ACTIVE(old)` while preserving the candidate for recovery. It never guesses from timestamps. |
704-
| After marker advancement, before migration checkpoint/manifest advancement | The migration read authority is `READ_AUTHORITY_PENDING` under the fence; no reader selects the new target. Startup completes the matching checkpoint or restores `ACTIVE(old)` while preserving the candidate, then releases the fence. Ordinary non-migration writes may treat their already-durable marker as authoritative. |
729+
| After marker advancement, before root/checkpoint advancement | The record and migration read authority is `READ_AUTHORITY_PENDING` under the fence; no reader selects the new target. Startup completes the matching root/checkpoint commit or restores `ACTIVE(old)` while preserving the candidate, then releases the fence. A marker-only ordinary write is never reported as durable success. |
705730
| After journal/manifest advancement | New authority is durable; cleanup remains separately retryable. |
706731
| During cleanup | Authority is unchanged; cleanup can resume without deleting the committed generation. |
707732

708733
The post-directory-sync/pre-marker rule is normative: startup first authenticates the durable
709734
`PENDING` intent and candidate generation. If both match, it idempotently completes the commit
710-
marker and, for migration, the matching journal/manifest checkpoint while holding the same fence.
735+
marker, the root marker-set checkpoint, and, for migration, the matching journal/manifest checkpoint
736+
while holding the same fence.
711737
If the candidate is missing, malformed, or fails authentication, it restores `ACTIVE(old)` and
712738
preserves the candidate/staging bytes for recovery. An unrecognized candidate is never adopted
713739
solely because it is newer or has a plausible filename.

0 commit comments

Comments
 (0)