pub struct Record {
pub body: RecordBody,
pub prev_hash: Digest,
pub hash: Digest,
pub signature: Option<KeySignature>,
/* private fields */
}Expand description
A sealed journal entry: body, chain links, and the bytes that were hashed.
Fields§
§body: RecordBody§prev_hash: Digest§hash: Digest§signature: Option<KeySignature>Who wrote it, if the plane was configured to say.
Beside the hash rather than inside the body, and that placement is forced: the signature covers the chain hash, so putting it in the body would make the hash cover the signature that covers the hash.
None is an ordinary state, not a defect — a plane that has not been
given a Signer writes unsigned records, and
history written before signing was configured stays unsigned forever.
What must never happen is a verifier silently accepting that; see
Record::verify_signed.
Implementations§
Source§impl Record
impl Record
Sourcepub const MAX_RECORD_BYTES: usize
pub const MAX_RECORD_BYTES: usize
The largest a single journal record may be.
A megabyte is generous for a record describing an effect and far too small for an inlined image, which is the intent: media belongs outside a chain that can never forget it. The number is in the same range the field settled on — Temporal caps payloads at 2 MB and claim-checks above 256 KiB — and is deliberately a hard refusal rather than a truncation, because a silently shortened record is a journal that lies.
Enforced in the step Record::seal_signed and Record::seal_at
both end in, so no store can be added that quietly skips it.
Sourcepub fn seal(body: RecordBody, prev_hash: Digest) -> Result<Self, StoreError>
pub fn seal(body: RecordBody, prev_hash: Digest) -> Result<Self, StoreError>
Serialize canonically and link into the chain.
Sourcepub fn seal_signed(
body: RecordBody,
prev_hash: Digest,
signer: Option<&dyn Signer>,
) -> Result<Self, StoreError>
pub fn seal_signed( body: RecordBody, prev_hash: Digest, signer: Option<&dyn Signer>, ) -> Result<Self, StoreError>
Seal, and attest it as the given signer.
The signature is taken over the chain hash, which already covers
prev_hash ‖ canonical(body). Because the hash chains, this signature
transitively commits to every record before this one — so rewriting any
part of the prefix invalidates every later signature, not just its own.
Sourcepub fn seal_at(
body: RecordBody,
written: Option<Vec<u8>>,
prev_hash: Digest,
signer: Option<&dyn Signer>,
) -> Result<Self, StoreError>
pub fn seal_at( body: RecordBody, written: Option<Vec<u8>>, prev_hash: Digest, signer: Option<&dyn Signer>, ) -> Result<Self, StoreError>
Seal at the position a store assigned, keeping the written bytes when the append carries them.
With no written bytes this is Record::seal_signed. With them — a
restore — the bytes are hashed against prev_hash as they stand and the
body is the store’s index view of them, so they must name the position
the store assigned: bytes claiming another seq, epoch or run would
land a record under a chain position its own body contradicts.
§Errors
StoreError::Corrupt for written bytes that name another position,
StoreError::RecordTooLarge above the limit, and whatever
Record::seal_signed refuses.
Sourcepub fn from_stored_signed(
raw: Vec<u8>,
prev_hash: Digest,
hash: Digest,
signature: Option<KeySignature>,
) -> Result<Self, StoreError>
pub fn from_stored_signed( raw: Vec<u8>, prev_hash: Digest, hash: Digest, signature: Option<KeySignature>, ) -> Result<Self, StoreError>
Reconstruct from storage, verifying the link before trusting the content, and carrying whatever signature the store kept.
The body is decoded from raw; the hash is recomputed from raw. A
record whose stored hash disagrees is rejected rather than returned with
a warning — a journal you cannot trust is worse than no journal, because
it produces an audit trail that is quietly a lie.
Reads under the Identity upcaster — this build’s
shapes and no others. A store that carries a real upcaster calls
from_stored_with instead.
Sourcepub fn from_stored_with(
upcaster: &dyn Upcaster,
raw: Vec<u8>,
prev_hash: Digest,
hash: Digest,
signature: Option<KeySignature>,
) -> Result<Self, StoreError>
pub fn from_stored_with( upcaster: &dyn Upcaster, raw: Vec<u8>, prev_hash: Digest, hash: Digest, signature: Option<KeySignature>, ) -> Result<Self, StoreError>
Reconstruct, lifting the record forward if it was written at an older shape.
The version is checked on every read, not only when something looks
wrong. A record carries v, and a reader that writes it and never
reads it back has a version field for decoration: a journal written by a
build one shape ahead parses cleanly here, with the fields this build
has never heard of dropped on the floor, and every decision downstream
is then made over a record nobody fully read. So the version is the
first thing asked about the parsed body, and the answer comes from the
Upcaster rather than from a constant — which is
what makes the seam a live path rather than a declaration waiting for
its first migration to also be its first exercise.
The hash stays over the bytes that were written. An upcast produces
a body this build understands and leaves raw and hash alone, so
tamper evidence is unaffected by the reader’s age — the rule
Upcaster states, enforced here by construction
because the lift happens after the link is verified and never touches
the bytes.
§Errors
StoreError::Corrupt if the stored hash does not cover the bytes,
StoreError::UnknownRecordVersion if no upcaster can reach this
build’s shape from the one on the record,
StoreError::UnreadableRecordShape if the record is at the version
this build writes and still does not parse — the skew a hard cut
produces — and StoreError::Encoding if the bytes are not a record
at all.
Sourcepub fn raw(&self) -> &[u8] ⓘ
pub fn raw(&self) -> &[u8] ⓘ
The exact bytes covered by Self::hash.
pub fn seq(&self) -> Seq
pub fn kind(&self) -> &RecordKind
pub fn effect_key(&self) -> Option<EffectKey>
Sourcepub fn admission_source(&self) -> Option<&str>
pub fn admission_source(&self) -> Option<&str>
The producer whose origin_key admitted
this run — the key’s source half — when this is the admission record
and its key was built that way.
What a served surface asks before it reads or acts on a run it is addressed by id: the source is the authenticated sender the surface keyed the admission with, so it answers “whose run is this” from a field the run already carries.
Sourcepub fn verify_chain(
records: &[Self],
from: Digest,
) -> Result<Digest, StoreError>
pub fn verify_chain( records: &[Self], from: Digest, ) -> Result<Digest, StoreError>
Verify a contiguous run of records links correctly.
Checks both the chain and the sequence: a gap means records were deleted, which the per-record hash alone would not catch.
Sourcepub fn verify_signed(
records: &[Self],
from: Digest,
verifier: &dyn Verifier,
require_signature: bool,
) -> Result<Digest, StoreError>
pub fn verify_signed( records: &[Self], from: Digest, verifier: &dyn Verifier, require_signature: bool, ) -> Result<Digest, StoreError>
Verify the chain and that a known key signed every record.
Two separate questions, deliberately answered by two separate calls. The
chain says the records are consistent with each other; the signatures say
who wrote them. A caller that only runs Self::verify_chain is asking
the weaker question, and this crate’s own store does exactly that on
every read — because a plane without a configured verifier has no basis
to reject anything, and failing closed there would make signing
impossible to adopt incrementally.
require_signature is what stops that leniency becoming a hole. With it
set, an unsigned record is a failure rather than a shrug — which is the
posture an auditor wants, and the opposite of the one a plane resuming
its own history wants.
§Errors
StoreError::Corrupt if the chain is broken, or SignatureError as a
corrupt-record detail if a signature is missing or wrong.
Trait Implementations§
impl StructuralPartialEq for Record
Auto Trait Implementations§
impl Freeze for Record
impl RefUnwindSafe for Record
impl Send for Record
impl Sync for Record
impl Unpin for Record
impl UnsafeUnpin for Record
impl UnwindSafe for Record
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more