You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/native/R15-SECURE-STORAGE-CONTRACT.md
+42-16Lines changed: 42 additions & 16 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -317,6 +317,29 @@ anchor for later control-record commits while ordinary record generations contin
317
317
per-record markers. This is a storage mechanism boundary, not a renderer or Storage-Core policy
318
318
escape.
319
319
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
+
320
343
## 6. Protected-record envelope
321
344
322
345
### 6.1 Version 1 wire contract
@@ -585,13 +608,13 @@ The authority manifest separately selects the active storage epoch and is switch
585
608
migration/rotation commit protocol. These control records are protected and included in the
586
609
inventory; they are not mutable plaintext sidecars.
587
610
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.
595
618
596
619
Outside a migration, Core requires the authenticated record generation and epoch to match both the
597
620
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:
658
681
old active generation.
659
682
8. Flush/sync the containing directory or platform-equivalent metadata required to persist the
660
683
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.
668
693
11. Reconcile stale staging deterministically on startup using authenticated generation/operation
669
694
metadata; never delete an unrecognized file merely because it has a temp suffix.
670
695
@@ -701,13 +726,14 @@ Core never turns an uncertain state into default project data.
701
726
| After file sync, before promotion | Complete staging may be verified/adopted by the journal or discarded; old authority remains until commit. |
702
727
| After promotion, before directory sync | Old marker remains active; the new generation is preserved and not yet authoritative. |
703
728
| 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. |
705
730
| After journal/manifest advancement | New authority is durable; cleanup remains separately retryable. |
706
731
| During cleanup | Authority is unchanged; cleanup can resume without deleting the committed generation. |
707
732
708
733
The post-directory-sync/pre-marker rule is normative: startup first authenticates the durable
709
734
`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.
711
737
If the candidate is missing, malformed, or fails authentication, it restores `ACTIVE(old)` and
712
738
preserves the candidate/staging bytes for recovery. An unrecognized candidate is never adopted
713
739
solely because it is newer or has a plausible filename.
0 commit comments