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
impl DecisionLog
Sourcepub fn open_with_rotation(
path: impl Into<PathBuf>,
root_key: Option<&[u8]>,
rotation: RotationConfig,
) -> Result<Self>
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.
pub fn default_path() -> PathBuf
Sourcepub fn rotate(&self) -> Result<()>
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.
Sourcepub fn path(&self) -> &Path
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.
pub fn chain_id(&self) -> Option<ChainId>
Sourcepub fn is_healthy(&self) -> bool
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.
Sourcepub fn append(&self, rec: &DecisionRecord) -> Result<()>
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:
- Compute the chained payload (prev + new hash).
write_all+flush+sync_allthe 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.- 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.
Sourcepub fn append_decision(&self, d: &Decision) -> Result<()>
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.
Sourcepub fn read_all(path: impl AsRef<Path>) -> Result<Vec<DecisionRecord>>
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.
Sourcepub fn read_all_chained(path: impl AsRef<Path>) -> Result<Vec<ChainedRecord>>
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.
Sourcepub fn verify_chain(path: impl AsRef<Path>, root_key: &[u8]) -> Result<ChainId>
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§
impl !Freeze for DecisionLog
impl !RefUnwindSafe for DecisionLog
impl Send for DecisionLog
impl Sync for DecisionLog
impl Unpin for DecisionLog
impl !UnwindSafe for DecisionLog
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<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§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