Struct DecisionLog

Source
pub struct DecisionLog { /* private fields */ }
Expand description

Thread-safe append-only JSONL log.

When constructed via DecisionLog::open, the log is plain JSONL. When constructed via DecisionLog::open_with_chain, each record is chained to the previous via HMAC-SHA256.

Implementations§

Source§

impl DecisionLog

Source

pub fn open(path: impl Into<PathBuf>) -> Result<Self>

Open a plain (un-chained) JSONL log at path.

§Errors

Returns Error::Io if the path cannot be opened for append or if the parent directory cannot be created.

§Examples
use agentguard_core::decision::DecisionLog;
let log = DecisionLog::open("/tmp/audit.jsonl").unwrap();
Source

pub fn open_with_chain( path: impl Into<PathBuf>, root_key: &[u8], ) -> Result<Self>

Open a hash-chained JSONL log at path with the given root key.

§Errors

Returns Error::Io if the path cannot be opened or the parent directory cannot be created.

§Examples
use agentguard_core::decision::DecisionLog;
let log = DecisionLog::open_with_chain("/tmp/audit.jsonl", b"root-key").unwrap();
Source

pub fn open_with_rotation( path: impl Into<PathBuf>, root_key: Option<&[u8]>, rotation: RotationConfig, ) -> Result<Self>

Open with an explicit rotation policy. When the active file exceeds rotation.max_bytes, the next append renames it to a timestamped sibling and opens a fresh active file.

Source

pub fn default_path() -> PathBuf

Source

pub fn rotate(&self) -> Result<()>

Force a rotation: rename the active file to a timestamped sibling in the same directory and reopen the active file. The chain id sidecar is moved alongside so the rotated file’s chain id is preserved.

Source

pub fn path(&self) -> &Path

Path the log was opened at. Useful for agentguard doctor and for tests that need to assert where the audit was written.

Source

pub fn chain_id(&self) -> Option<ChainId>

Source

pub fn is_healthy(&self) -> bool

Whether the writer still has a usable active file handle. This turns permanent append failures into an observable readiness signal instead of leaving the service healthy while every decision fails to audit.

Source

pub fn append(&self, rec: &DecisionRecord) -> Result<()>

Append a record. When the log is chained, the record is signed.

§Crash safety (chained mode)

The file lock is held for the entire critical section:

  1. Compute the chained payload (prev + new hash).
  2. write_all + flush + sync_all the JSON line to the log file. The next startup validates the complete tail before accepting new writes, so a torn or malformed final record fails closed instead of silently starting a disconnected chain.
  3. The chain head advances only after step 2 succeeds. On failure, the chain stays at the previous head and the next caller retries from the same state.

On restart, load_head_from_file reads the on-disk head and adopts it — even if we crashed between the sync_all and the head advance, the record is fully on disk and the chain matches.

§Errors

Returns Error::Json if rec cannot be serialized, or Error::Io if the write/fsync fails.

Source

pub fn append_decision(&self, d: &Decision) -> Result<()>

Convenience wrapper around Self::append that constructs the DecisionRecord from a Decision with empty session/chain metadata. Use Self::append directly to attach session ID or delegation chain.

§Errors

Same as Self::append.

Source

pub fn read_all(path: impl AsRef<Path>) -> Result<Vec<DecisionRecord>>

Read every record from the audit log and its timestamped rotation siblings in chronological order, accepting either plain or chained records (mixed log files are supported; older records may pre-date chain metadata).

§Errors

Returns Error::Io if the file cannot be read, Error::Json if a record cannot be parsed.

Source

pub fn read_all_chained(path: impl AsRef<Path>) -> Result<Vec<ChainedRecord>>

Read every record from the audit log, requiring all records to carry chain metadata. A plain (non-chained) record in the log surfaces as Error::Json with the row number.

§Errors

Returns Error::Io if the file cannot be read, Error::Json if a record cannot be parsed or is missing chain metadata.

Source

pub fn verify_chain(path: impl AsRef<Path>, root_key: &[u8]) -> Result<ChainId>

Verify the entire audit log against the root key.

Reads every record from path and its timestamped rotation siblings, checks the continuous HMAC chain, and returns the chain id. Returns an error if a record is malformed, tampered, or belongs to another chain.

§Errors

Returns Error::Other (formatted string) on parse failure, hash mismatch, or chain head mismatch.

§Examples
use agentguard_core::decision::DecisionLog;
let path = std::env::temp_dir().join("agentguard-verify-chain.jsonl");
let _ = std::fs::remove_file(&path);
let log = DecisionLog::open_with_chain(&path, b"secret").unwrap();
// ... write some decisions ...
drop(log);
let chain_id = DecisionLog::verify_chain(&path, b"secret").unwrap();
println!("verified chain: {}", chain_id);
let _ = std::fs::remove_file(&path);

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,