pub struct ApprovalLedger { /* private fields */ }Expand description
An append-only ledger of human-in-the-loop decisions, keyed by fingerprint (last decision wins). Optionally persisted as JSONL so the approval state survives restarts — HITL decisions are durable harness state, not transient prompts.
Concurrency: a single writer per journal file is assumed. The
stateless FFI opens a fresh ledger per call, so a product that drives
approvals from multiple processes against one journal must serialize
those writes itself (e.g. route them through the daemon). Reads
tolerate the writer appending concurrently; a torn final line is
skipped on load and counted in ApprovalLedger::skipped_on_load.
Implementations§
Source§impl ApprovalLedger
impl ApprovalLedger
pub fn new() -> ApprovalLedger
Sourcepub fn with_journal(path: impl Into<PathBuf>) -> Result<ApprovalLedger, Error>
pub fn with_journal(path: impl Into<PathBuf>) -> Result<ApprovalLedger, Error>
Create a ledger backed by a JSONL journal at path, loading any
existing decisions. Each line is one ApprovalRecord; the last
line for a fingerprint wins, so a later rejection overrides an
earlier approval.
Sourcepub fn with_private_journal(
path: impl Into<PathBuf>,
) -> Result<ApprovalLedger, Error>
pub fn with_private_journal( path: impl Into<PathBuf>, ) -> Result<ApprovalLedger, Error>
with_journal for a journal CAR keeps in its own
state root, such as the daemon’s ~/.car/approvals.jsonl. The load and
every record go through the owner-private opens: the
file is created 0600, an existing looser file is tightened in place,
and a symlink at the path or in a parent directory, or a parent the
user does not own, is an error rather than followed (#3403). A missing
journal loads empty. Refusing at load matters as much as at write: a
symlink to a file holding an Approved row would otherwise authorize
an operation nobody approved. Records are written exactly as with_journal
writes them, so fingerprints keep matching.
Caller-chosen journal paths (the FFI ledger_path) stay on
with_journal: the private open also makes the
parent directory 0700 and refuses one the user does not own, which a
journal placed directly in /tmp would fail.
Sourcepub fn skipped_on_load(&self) -> usize
pub fn skipped_on_load(&self) -> usize
Number of unparseable lines skipped during the load. Nonzero means the journal is corrupt or was torn by a concurrent writer.
Sourcepub fn record(
&mut self,
record: ApprovalRecord,
) -> Result<&ApprovalRecord, Error>
pub fn record( &mut self, record: ApprovalRecord, ) -> Result<&ApprovalRecord, Error>
Record a decision, persisting it to the journal when configured. Returns the stored record.
A journal write failure is an error, not best-effort (review A7):
callers emit ApprovalRecorded audit events on the strength of this
call, so a decision that only landed in memory must not be reported
as durable. On Err the decision is NOT stored (memory and journal
stay consistent — both lack it) and the caller must surface the
failure. An in-memory ledger (no journal) cannot fail.
Sourcepub fn lookup(&self, fingerprint: &str) -> Option<&ApprovalRecord>
pub fn lookup(&self, fingerprint: &str) -> Option<&ApprovalRecord>
The current decision for a fingerprint, if any.
pub fn all(&self) -> impl Iterator<Item = &ApprovalRecord>
Sourcepub fn record_decision(
&mut self,
fingerprint: &str,
required_tier: PermissionTier,
decision: ApprovalDecision,
reviewer: &str,
reason: &str,
evidence: Option<String>,
) -> Result<ApprovalRecord, Error>
pub fn record_decision( &mut self, fingerprint: &str, required_tier: PermissionTier, decision: ApprovalDecision, reviewer: &str, reason: &str, evidence: Option<String>, ) -> Result<ApprovalRecord, Error>
Build and record a decision against an explicit
fingerprint — the ledger-level twin of
PermissionGate::record_for_fingerprint, for callers that share
ONE ledger across many gates/sessions (e.g. the daemon’s shared
approval substrate) and so must not route the write through any
single session’s gate. Errs when the journal write fails (the
decision is then NOT recorded — see Self::record).
Trait Implementations§
Source§impl Debug for ApprovalLedger
impl Debug for ApprovalLedger
Source§impl Default for ApprovalLedger
impl Default for ApprovalLedger
Source§fn default() -> ApprovalLedger
fn default() -> ApprovalLedger
Auto Trait Implementations§
impl Freeze for ApprovalLedger
impl RefUnwindSafe for ApprovalLedger
impl Send for ApprovalLedger
impl Sync for ApprovalLedger
impl Unpin for ApprovalLedger
impl UnsafeUnpin for ApprovalLedger
impl UnwindSafe for ApprovalLedger
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
impl<T> ErasedDestructor for Twhere
T: 'static,
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