pub struct ChainContent<'a> {
pub seq: u64,
pub key_id: &'a str,
pub transaction_id: &'a str,
pub request_id: &'a str,
pub request_hash: &'a str,
pub action_name: &'a str,
pub risk_level: RiskLevel,
pub summary: &'a str,
pub approval_id: Option<&'a str>,
pub warnings_json: &'a str,
pub created_at: &'a str,
}Expand description
Immutable fields hashed into the chain. Status is intentionally absent — see module docs.
§Security contract — chain-content immutability
Every field in this struct is captured once at INSERT time and baked
into chain_hash = ed25519_sign(canonical(self) || prev_chain_hash, key).
After the row is written the hash is a one-time commitment: no field
in this struct may ever be mutated in place.
summary is the field most likely to attract a future “let me just fix
that typo” API. Do not add an update_summary (or similar) function.
If a correction is genuinely needed, choose one of the two safe options:
- Insert a corrective row — a new transaction row that references the
original
transaction_idin its ownsummary, leaving the original row and its chain hash untouched. - Extend the chain protocol — introduce a dedicated amendment record type with its own chain link, so that both the original commitment and the correction are auditable.
Any other approach silently breaks chain integrity: verify_chain will
flag the modified row as Broken because the stored signature will no
longer verify against the row’s content.
The canonical serialisation is stable across SQLite/Postgres backends. Each field is emitted as
<tag-name> 0x1E <tag-value> 0x1Fwhere 0x1E is the tag/value separator within a single field and 0x1F
is the field separator that terminates the field and introduces the
next one. We use the ASCII C0 byte values RS (0x1E) and US (0x1F)
because they are guaranteed not to appear in any normal text field, but
our role assignment is the inverse of the ASCII C0 convention
(where RS = “record separator” and US = “unit separator”). The names
are kept in the source for byte-level traceability against the canonical
buffer, not as a claim about ASCII semantics.
Inside a value, the four
bytes \\, NUL, RS, US are escaped to \\\\, \\0, \\1E, \\1F
respectively. The escape table is prefix-free (every escape starts
with \\), so any value can be injected without ambiguity. See
push_field for the implementation and tests for the round-trip.
Fields§
§seq: u64§key_id: &'a str§transaction_id: &'a str§request_id: &'a str§request_hash: &'a str§action_name: &'a str§risk_level: RiskLevel§summary: &'a strHuman-readable description of the planned action.
Immutable after insert. This field is included in the chain hash
(see ChainContent struct-level doc). It MUST NOT be updated after
the row is written — doing so silently invalidates chain_hash and
will be detected as VerifyOutcome::Broken by sysknife audit verify.
See the struct-level security contract for the only safe correction
strategies.
approval_id: Option<&'a str>Signed receipt commitment for daemon-created previews. Legacy/imported
rows may be None; serialised as empty for wire compatibility.
warnings_json: &'a strJSON-canonical (sorted keys) array of warning strings.
created_at: &'a strImplementations§
Source§impl<'a> ChainContent<'a>
impl<'a> ChainContent<'a>
Sourcepub fn canonical_bytes(&self) -> Vec<u8> ⓘ
pub fn canonical_bytes(&self) -> Vec<u8> ⓘ
Stable canonical encoding of every field. Within each field the
0x1E byte separates tag from value, and 0x1F terminates the field;
raw NUL becomes the two-byte escape \0.
Trait Implementations§
Source§impl<'a> Clone for ChainContent<'a>
impl<'a> Clone for ChainContent<'a>
Source§fn clone(&self) -> ChainContent<'a>
fn clone(&self) -> ChainContent<'a>
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl<'a> Debug for ChainContent<'a>
impl<'a> Debug for ChainContent<'a>
impl<'a> Eq for ChainContent<'a>
Source§impl<'a> PartialEq for ChainContent<'a>
impl<'a> PartialEq for ChainContent<'a>
impl<'a> StructuralPartialEq for ChainContent<'a>
Auto Trait Implementations§
impl<'a> Freeze for ChainContent<'a>
impl<'a> RefUnwindSafe for ChainContent<'a>
impl<'a> Send for ChainContent<'a>
impl<'a> Sync for ChainContent<'a>
impl<'a> Unpin for ChainContent<'a>
impl<'a> UnsafeUnpin for ChainContent<'a>
impl<'a> UnwindSafe for ChainContent<'a>
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
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
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