proto becomes bxcore, the one shared crate (M5b task 00)
Done the way the owner asked and the spike mapped: the crate and the manifests renamed, then what the build reported fixed. Layers: 138 "proto unresolved" errors across 169 files; cargo fmt reordering imports; six fixture paths built as strings in brokerd's and bxctl's tests, which the compiler cannot see, and comments naming crates/proto/; the crate-dependency check and its self-test. One more the spike did not reach: a `\nproto =` key inside a printf format in the self-test, which a word-boundary replacement skips because `n` and `p` are both word characters. make gate: gate: ok, 944 tests; no new entry in /tmp. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "bxcore"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
publish.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
humantime.workspace = true
|
||||
emsha.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
toml.workspace = true
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
//! Audit log record types. One JSON object per line, hash-chained by the exact bytes of each line.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{CallId, DataClass, DenyReason, Hash32, SessionId, Timestamp};
|
||||
|
||||
pub use crate::chain::{ChainFailure, ChainReport, ChainVerifier, Location, TornTail};
|
||||
|
||||
// JSON: {"outcome":"denied","reason":"no_grant"} ; the tag sits beside the fields
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "outcome", rename_all = "snake_case", deny_unknown_fields)]
|
||||
pub enum DecisionRecord {
|
||||
Allowed {},
|
||||
Ask {},
|
||||
Denied { reason: DenyReason },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ApprovalAnswer {
|
||||
Approved,
|
||||
Refused,
|
||||
Expired,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ResultStatus {
|
||||
Result,
|
||||
Failed,
|
||||
}
|
||||
|
||||
// JSON: {"type":"decision","session":"…",…} ; the tag sits beside the fields
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
|
||||
pub enum AuditEvent {
|
||||
Decision {
|
||||
session: SessionId,
|
||||
call: CallId,
|
||||
tool: String,
|
||||
arguments: String,
|
||||
outcome: DecisionRecord,
|
||||
grant: Option<String>,
|
||||
grant_sha256: Option<Hash32>,
|
||||
taint: DataClass,
|
||||
untrusted: bool,
|
||||
},
|
||||
Approval {
|
||||
session: SessionId,
|
||||
call: CallId,
|
||||
decision: u64,
|
||||
answer: ApprovalAnswer,
|
||||
by: Option<String>,
|
||||
post: Option<String>,
|
||||
reason: Option<String>,
|
||||
outcome: DecisionRecord,
|
||||
grant: Option<String>,
|
||||
grant_sha256: Option<Hash32>,
|
||||
taint: DataClass,
|
||||
untrusted: bool,
|
||||
},
|
||||
Result {
|
||||
session: SessionId,
|
||||
call: CallId,
|
||||
decision: u64,
|
||||
status: ResultStatus,
|
||||
class: DataClass,
|
||||
untrusted: bool,
|
||||
truncated: bool,
|
||||
bytes: u64,
|
||||
sha256: Hash32,
|
||||
taint_after: DataClass,
|
||||
},
|
||||
Recovery {
|
||||
torn_bytes: u64,
|
||||
torn_sha256: Hash32,
|
||||
},
|
||||
AcceptedBreak {
|
||||
file: String,
|
||||
line: u64,
|
||||
last_good: Hash32,
|
||||
},
|
||||
}
|
||||
|
||||
// JSON: {"seq":0,"time":"…","prev":"…","event":{"type":"decision",…}} ; `event` is a nested object.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct AuditRecord {
|
||||
pub seq: u64,
|
||||
pub time: Timestamp,
|
||||
pub prev: Hash32,
|
||||
pub event: AuditEvent,
|
||||
}
|
||||
|
||||
/// True if `name` is an audit log file: `YYYY-MM-DD.jsonl`, ASCII digits with `-` at positions 4
|
||||
/// and 7, a month from 01 to 12 and a day from 01 to 31. `brokerd` and `bxctl audit verify` both
|
||||
/// decide with this, so they read the same files.
|
||||
pub fn is_audit_log_name(name: &str) -> bool {
|
||||
let Some(date) = name.strip_suffix(".jsonl") else {
|
||||
return false;
|
||||
};
|
||||
let shaped = date.len() == 10
|
||||
&& date.bytes().enumerate().all(|(i, b)| match i {
|
||||
4 | 7 => b == b'-',
|
||||
_ => b.is_ascii_digit(),
|
||||
});
|
||||
if !shaped {
|
||||
return false;
|
||||
}
|
||||
let number = |range: std::ops::Range<usize>| date.get(range).and_then(|s| s.parse::<u8>().ok());
|
||||
matches!(number(5..7), Some(1..=12)) && matches!(number(8..10), Some(1..=31))
|
||||
}
|
||||
@@ -0,0 +1,411 @@
|
||||
//! Verifies the audit log's hash chain. A pure state machine: the caller reads the files and feeds
|
||||
//! it lines. Shared by `brokerd`'s startup check and `bxctl audit verify`.
|
||||
//!
|
||||
//! Each line is held back until the next one has been seen, because a `Recovery` record says
|
||||
//! that the line before it is not a record at all.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use crate::{AuditEvent, AuditRecord, DecisionRecord, Hash32, Timestamp, sha256};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Location {
|
||||
pub file: String,
|
||||
pub line: u64,
|
||||
}
|
||||
|
||||
/// The first failure since the last accepted break, with what an `AcceptedBreak` record appended
|
||||
/// now would have to carry.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ChainFailure {
|
||||
pub file: String,
|
||||
pub line: u64,
|
||||
pub what: String,
|
||||
pub last_good: Hash32,
|
||||
pub break_prev: Hash32,
|
||||
pub break_seq: u64,
|
||||
/// The last line fed has no newline; it must be ended before a break record is appended.
|
||||
pub tail_torn: bool,
|
||||
}
|
||||
|
||||
/// The last line of the last file when it needs recovery, with what its `Recovery` record
|
||||
/// would have to carry.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TornTail {
|
||||
pub at: Location,
|
||||
pub has_newline: bool,
|
||||
pub bytes: u64,
|
||||
pub sha256: Hash32,
|
||||
pub recovery_prev: Hash32,
|
||||
pub recovery_seq: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ChainReport {
|
||||
pub records: u64,
|
||||
pub head: Option<Hash32>,
|
||||
pub next_seq: u64,
|
||||
pub failure: Option<ChainFailure>,
|
||||
pub recoveries: Vec<Location>,
|
||||
pub accepted_breaks: Vec<Location>,
|
||||
/// `seq` of `ask` decisions with no `Approval` after them.
|
||||
pub abandoned: Vec<u64>,
|
||||
/// `seq` of decisions that were allowed, at once or by approval, with no `Result` after them.
|
||||
pub unfinished: Vec<u64>,
|
||||
pub clock_warnings: Vec<Location>,
|
||||
pub torn_tail: Option<TornTail>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Held {
|
||||
at: Location,
|
||||
hash: Hash32,
|
||||
bytes: u64,
|
||||
has_newline: bool,
|
||||
record: Option<AuditRecord>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Broken {
|
||||
file: String,
|
||||
line: u64,
|
||||
what: String,
|
||||
last_good: Hash32,
|
||||
expected_seq: u64,
|
||||
/// Lines from the failing line up to the last one judged, the failing line included.
|
||||
lines: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ChainVerifier {
|
||||
resumed: bool,
|
||||
first_file: Option<String>,
|
||||
file: String,
|
||||
line_no: u64,
|
||||
next_seq: u64,
|
||||
prev: Hash32,
|
||||
head: Option<Hash32>,
|
||||
/// Hash of the line before the held one, whether or not it verified.
|
||||
before_held: Hash32,
|
||||
held: Option<Held>,
|
||||
/// The held line is a `Recovery` record that described the line before it.
|
||||
held_recovers: bool,
|
||||
broken: Option<Broken>,
|
||||
last_time: Option<Timestamp>,
|
||||
records: u64,
|
||||
open_asks: BTreeSet<u64>,
|
||||
open_runs: BTreeSet<u64>,
|
||||
recoveries: Vec<Location>,
|
||||
accepted_breaks: Vec<Location>,
|
||||
clock_warnings: Vec<Location>,
|
||||
}
|
||||
|
||||
impl Default for ChainVerifier {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainVerifier {
|
||||
/// Expects `seq` 0 and an all-zero `prev`.
|
||||
pub fn new() -> Self {
|
||||
Self::start(false, 0, Hash32::ZERO)
|
||||
}
|
||||
|
||||
/// Continues from a known point: the `seq` after, and the hash of, the last line of the file
|
||||
/// before the first one fed.
|
||||
pub fn resume(next_seq: u64, prev: Hash32) -> Self {
|
||||
Self::start(true, next_seq, prev)
|
||||
}
|
||||
|
||||
fn start(resumed: bool, next_seq: u64, prev: Hash32) -> Self {
|
||||
ChainVerifier {
|
||||
resumed,
|
||||
first_file: None,
|
||||
file: String::new(),
|
||||
line_no: 0,
|
||||
next_seq,
|
||||
prev,
|
||||
head: None,
|
||||
before_held: prev,
|
||||
held: None,
|
||||
held_recovers: false,
|
||||
broken: None,
|
||||
last_time: None,
|
||||
records: 0,
|
||||
open_asks: BTreeSet::new(),
|
||||
open_runs: BTreeSet::new(),
|
||||
recoveries: Vec::new(),
|
||||
accepted_breaks: Vec::new(),
|
||||
clock_warnings: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts the next file. Files are fed in name order.
|
||||
pub fn file(&mut self, name: &str) {
|
||||
if self.first_file.is_none() {
|
||||
self.first_file = Some(name.to_string());
|
||||
}
|
||||
self.file = name.to_string();
|
||||
self.line_no = 0;
|
||||
}
|
||||
|
||||
/// Feeds a whole file: `file(name)`, then every line of `content`.
|
||||
pub fn feed(&mut self, name: &str, content: &[u8]) {
|
||||
self.file(name);
|
||||
let mut rest = content;
|
||||
while !rest.is_empty() {
|
||||
match rest.iter().position(|b| *b == b'\n') {
|
||||
Some(end) => {
|
||||
let (line, tail) = rest.split_at(end);
|
||||
self.line(line, true);
|
||||
rest = tail.get(1..).unwrap_or_default();
|
||||
}
|
||||
None => {
|
||||
self.line(rest, false);
|
||||
rest = &[];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One line without its newline. `has_newline` is false only for a file's last line.
|
||||
pub fn line(&mut self, bytes: &[u8], has_newline: bool) {
|
||||
self.line_no = self.line_no.saturating_add(1);
|
||||
let at = Location {
|
||||
file: self.file.clone(),
|
||||
line: self.line_no,
|
||||
};
|
||||
let Ok(hash) = sha256(bytes) else {
|
||||
// The hasher fails only on an input too long to count; such a line is no record.
|
||||
self.judge_held(None);
|
||||
self.fail(&at, "the line is too long to hash".to_string());
|
||||
return;
|
||||
};
|
||||
let current = Held {
|
||||
at,
|
||||
hash,
|
||||
bytes: u64::try_from(bytes.len()).unwrap_or(u64::MAX),
|
||||
has_newline,
|
||||
record: serde_json::from_slice(bytes).ok(),
|
||||
};
|
||||
self.judge_held(Some(¤t));
|
||||
self.held = Some(current);
|
||||
}
|
||||
|
||||
/// Judges the held line now that the line after it (if any) is known.
|
||||
fn judge_held(&mut self, next: Option<&Held>) {
|
||||
let Some(held) = self.held.take() else {
|
||||
return;
|
||||
};
|
||||
let recovers = std::mem::replace(&mut self.held_recovers, false);
|
||||
if self.broken.is_some() {
|
||||
self.judge_in_region(&held);
|
||||
} else if self.is_recovered_by(&held, next) {
|
||||
self.recoveries.push(held.at.clone());
|
||||
self.held_recovers = true;
|
||||
} else {
|
||||
self.judge_record(&held, recovers);
|
||||
}
|
||||
self.before_held = held.hash;
|
||||
}
|
||||
|
||||
/// True if `next` is a `Recovery` record that describes `held` and chains from the line
|
||||
/// before it. Whether `held` parses makes no difference.
|
||||
fn is_recovered_by(&self, held: &Held, next: Option<&Held>) -> bool {
|
||||
let Some(AuditRecord {
|
||||
seq,
|
||||
prev,
|
||||
event:
|
||||
AuditEvent::Recovery {
|
||||
torn_bytes,
|
||||
torn_sha256,
|
||||
},
|
||||
..
|
||||
}) = next.and_then(|n| n.record.as_ref())
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
*torn_bytes == held.bytes
|
||||
&& *torn_sha256 == held.hash
|
||||
&& *prev == self.prev
|
||||
&& *seq == self.next_seq
|
||||
}
|
||||
|
||||
fn judge_record(&mut self, held: &Held, recovers: bool) {
|
||||
let Some(record) = &held.record else {
|
||||
self.fail(&held.at, "does not parse as an audit record".to_string());
|
||||
return;
|
||||
};
|
||||
if let AuditEvent::AcceptedBreak { file, .. } = &record.event {
|
||||
// Only a resumed verifier may meet a break with no failure before it: the failure
|
||||
// is in a file it was not given. The full verification judges the rest.
|
||||
if self.names_earlier_file(file) && record.prev == self.prev {
|
||||
self.accept_break(held, record);
|
||||
} else {
|
||||
self.fail(
|
||||
&held.at,
|
||||
"an accepted break with no failure before it".to_string(),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if record.seq != self.next_seq {
|
||||
let what = format!("seq is {}, expected {}", record.seq, self.next_seq);
|
||||
self.fail(&held.at, what);
|
||||
return;
|
||||
}
|
||||
if record.prev != self.prev {
|
||||
let what = if held.at.line == 1 && (self.resumed || self.head.is_some()) {
|
||||
"does not chain from the last line of the file before"
|
||||
} else {
|
||||
"prev is not the hash of the line before"
|
||||
};
|
||||
self.fail(&held.at, what.to_string());
|
||||
return;
|
||||
}
|
||||
if matches!(record.event, AuditEvent::Recovery { .. }) && !recovers {
|
||||
self.fail(
|
||||
&held.at,
|
||||
"a recovery record that does not describe the line before it".to_string(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
self.advance(held, record);
|
||||
}
|
||||
|
||||
/// A line after a failure. It is read only to see whether it is the break record.
|
||||
fn judge_in_region(&mut self, held: &Held) {
|
||||
let Some(broken) = &mut self.broken else {
|
||||
return;
|
||||
};
|
||||
if let Some(record) = &held.record
|
||||
&& let AuditEvent::AcceptedBreak {
|
||||
file,
|
||||
line,
|
||||
last_good,
|
||||
} = &record.event
|
||||
&& record.prev == self.before_held
|
||||
{
|
||||
let names_this = *file == broken.file
|
||||
&& *line == broken.line
|
||||
&& *last_good == broken.last_good
|
||||
&& broken.expected_seq.checked_add(broken.lines) == Some(record.seq);
|
||||
if names_this
|
||||
|| (self.resumed
|
||||
&& self
|
||||
.first_file
|
||||
.as_deref()
|
||||
.is_some_and(|f| file.as_str() < f))
|
||||
{
|
||||
self.broken = None;
|
||||
self.accept_break(held, record);
|
||||
return;
|
||||
}
|
||||
}
|
||||
broken.lines = broken.lines.saturating_add(1);
|
||||
}
|
||||
|
||||
fn names_earlier_file(&self, file: &str) -> bool {
|
||||
self.resumed && self.first_file.as_deref().is_some_and(|first| file < first)
|
||||
}
|
||||
|
||||
fn accept_break(&mut self, held: &Held, record: &AuditRecord) {
|
||||
self.accepted_breaks.push(held.at.clone());
|
||||
self.advance(held, record);
|
||||
}
|
||||
|
||||
/// `held` is a verified record: it becomes the head.
|
||||
fn advance(&mut self, held: &Held, record: &AuditRecord) {
|
||||
self.records = self.records.saturating_add(1);
|
||||
self.head = Some(held.hash);
|
||||
self.prev = held.hash;
|
||||
self.next_seq = record.seq.saturating_add(1);
|
||||
if self.last_time.is_some_and(|last| record.time < last) {
|
||||
self.clock_warnings.push(held.at.clone());
|
||||
}
|
||||
self.last_time = Some(record.time);
|
||||
match &record.event {
|
||||
AuditEvent::Decision { outcome, .. } => match outcome {
|
||||
DecisionRecord::Allowed {} => {
|
||||
self.open_runs.insert(record.seq);
|
||||
}
|
||||
DecisionRecord::Ask {} => {
|
||||
self.open_asks.insert(record.seq);
|
||||
}
|
||||
DecisionRecord::Denied { .. } => {}
|
||||
},
|
||||
AuditEvent::Approval {
|
||||
decision, outcome, ..
|
||||
} => {
|
||||
self.open_asks.remove(decision);
|
||||
if matches!(outcome, DecisionRecord::Allowed {}) {
|
||||
self.open_runs.insert(*decision);
|
||||
}
|
||||
}
|
||||
AuditEvent::Result { decision, .. } => {
|
||||
self.open_runs.remove(decision);
|
||||
}
|
||||
AuditEvent::Recovery { .. } | AuditEvent::AcceptedBreak { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn fail(&mut self, at: &Location, what: String) {
|
||||
if self.broken.is_some() {
|
||||
return;
|
||||
}
|
||||
self.broken = Some(Broken {
|
||||
file: at.file.clone(),
|
||||
line: at.line,
|
||||
what,
|
||||
last_good: self.prev,
|
||||
expected_seq: self.next_seq,
|
||||
lines: 1,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn finish(mut self) -> ChainReport {
|
||||
let mut torn_tail = None;
|
||||
let mut tail_torn = false;
|
||||
let mut last_line = self.before_held;
|
||||
if let Some(held) = self.held.take() {
|
||||
last_line = held.hash;
|
||||
tail_torn = !held.has_newline;
|
||||
let needs_recovery = !held.has_newline || held.record.is_none();
|
||||
if self.broken.is_none() && needs_recovery {
|
||||
torn_tail = Some(TornTail {
|
||||
at: held.at.clone(),
|
||||
has_newline: held.has_newline,
|
||||
bytes: held.bytes,
|
||||
sha256: held.hash,
|
||||
recovery_prev: self.prev,
|
||||
recovery_seq: self.next_seq,
|
||||
});
|
||||
} else {
|
||||
self.held = Some(held);
|
||||
self.judge_held(None);
|
||||
}
|
||||
}
|
||||
let failure = self.broken.map(|b| ChainFailure {
|
||||
file: b.file,
|
||||
line: b.line,
|
||||
what: b.what,
|
||||
last_good: b.last_good,
|
||||
break_prev: last_line,
|
||||
break_seq: b.expected_seq.saturating_add(b.lines),
|
||||
tail_torn,
|
||||
});
|
||||
ChainReport {
|
||||
records: self.records,
|
||||
head: self.head,
|
||||
next_seq: self.next_seq,
|
||||
failure,
|
||||
recoveries: self.recoveries,
|
||||
accepted_breaks: self.accepted_breaks,
|
||||
abandoned: self.open_asks.into_iter().collect(),
|
||||
unfinished: self.open_runs.into_iter().collect(),
|
||||
clock_warnings: self.clock_warnings,
|
||||
torn_tail,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum DataClass {
|
||||
Public,
|
||||
Private,
|
||||
Secret,
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
//! Text from outside (model output, tool arguments, server errors) made safe to show: control
|
||||
//! characters, zero-width and bidirectional marks, line separators and the BOM become `\uXXXX`.
|
||||
//! Used by `bxctl` for the terminal and by `gatewayd` for Mattermost, so both show the same.
|
||||
|
||||
// The control bytes, the zero-width and Unicode line/paragraph separators, and the BOM. A code
|
||||
// point in any of these ranges must never reach the terminal raw.
|
||||
const ESCAPE_RANGES: &[(u32, u32)] = &[
|
||||
(0x0000, 0x001f),
|
||||
(0x007f, 0x009f),
|
||||
(0x200b, 0x200f),
|
||||
(0x2028, 0x202e),
|
||||
(0x2060, 0x2069),
|
||||
(0xfeff, 0xfeff),
|
||||
];
|
||||
|
||||
pub fn escape_json_text(s: &str) -> String {
|
||||
escape(s, false)
|
||||
}
|
||||
|
||||
pub fn escape_model_text(s: &str) -> String {
|
||||
escape(s, true)
|
||||
}
|
||||
|
||||
fn escape(s: &str, model_text: bool) -> String {
|
||||
let mut out = String::with_capacity(s.len());
|
||||
for c in s.chars() {
|
||||
let cp = u32::from(c);
|
||||
if should_escape(c, model_text) {
|
||||
out.push_str(&format!("\\u{:04x}", cp));
|
||||
} else {
|
||||
out.push(c);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
// A control point is escaped, except that model text keeps \n and \t.
|
||||
fn should_escape(c: char, model_text: bool) -> bool {
|
||||
let cp = u32::from(c);
|
||||
if ESCAPE_RANGES.iter().any(|(lo, hi)| cp >= *lo && cp <= *hi) {
|
||||
!(model_text && (cp == 0x09 || cp == 0x0a))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
//! Length-prefixed frame codec: one envelope per big-endian 4-byte length.
|
||||
|
||||
use std::io::Read;
|
||||
|
||||
use crate::{Envelope, PROTOCOL_VERSION};
|
||||
|
||||
pub const MAX_FRAME: usize = 1_048_576;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FrameError {
|
||||
Closed,
|
||||
Empty,
|
||||
TooLarge(usize),
|
||||
BadVersion(u32),
|
||||
Json(serde_json::Error),
|
||||
Io(std::io::Error),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FrameError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
FrameError::Closed => write!(f, "stream closed before a frame"),
|
||||
FrameError::Empty => write!(f, "frame length was zero"),
|
||||
FrameError::TooLarge(n) => write!(f, "frame of {n} bytes exceeds the maximum"),
|
||||
FrameError::BadVersion(v) => write!(f, "unsupported protocol version {v}"),
|
||||
FrameError::Json(e) => write!(f, "invalid JSON envelope: {e}"),
|
||||
FrameError::Io(e) => write!(f, "frame I/O error: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for FrameError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
FrameError::Json(e) => Some(e),
|
||||
FrameError::Io(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_frame<W: std::io::Write>(w: &mut W, env: &Envelope) -> Result<(), FrameError> {
|
||||
let body = serde_json::to_vec(env).map_err(FrameError::Json)?;
|
||||
let len = body.len();
|
||||
if len > MAX_FRAME {
|
||||
return Err(FrameError::TooLarge(len));
|
||||
}
|
||||
w.write_all(&(len as u32).to_be_bytes())
|
||||
.map_err(FrameError::Io)?;
|
||||
w.write_all(&body).map_err(FrameError::Io)?;
|
||||
w.flush().map_err(FrameError::Io)
|
||||
}
|
||||
|
||||
pub fn read_frame<R: std::io::Read>(r: &mut R) -> Result<Envelope, FrameError> {
|
||||
let mut buf = [0u8; 4];
|
||||
let mut filled = 0;
|
||||
while filled < 4 {
|
||||
let n = read_bytes(r, &mut buf[filled..]).map_err(FrameError::Io)?;
|
||||
if n == 0 {
|
||||
return Err(if filled == 0 {
|
||||
FrameError::Closed
|
||||
} else {
|
||||
FrameError::Io(std::io::Error::from(std::io::ErrorKind::UnexpectedEof))
|
||||
});
|
||||
}
|
||||
filled += n;
|
||||
}
|
||||
|
||||
let len = u32::from_be_bytes(buf);
|
||||
if len == 0 {
|
||||
return Err(FrameError::Empty);
|
||||
}
|
||||
if len > MAX_FRAME as u32 {
|
||||
return Err(FrameError::TooLarge(len as usize));
|
||||
}
|
||||
|
||||
let len = len as usize;
|
||||
let mut body = vec![0u8; len];
|
||||
r.read_exact(&mut body).map_err(FrameError::Io)?;
|
||||
|
||||
let env = serde_json::from_slice::<Envelope>(&body).map_err(FrameError::Json)?;
|
||||
if env.v != PROTOCOL_VERSION {
|
||||
return Err(FrameError::BadVersion(env.v));
|
||||
}
|
||||
Ok(env)
|
||||
}
|
||||
|
||||
/// Reads into `buf`, retrying on `Interrupted`. Returns `Ok(0)` on a clean end of stream.
|
||||
fn read_bytes<R: Read>(r: &mut R, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
loop {
|
||||
match r.read(buf) {
|
||||
Ok(0) => return Ok(0),
|
||||
Ok(n) => return Ok(n),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
//! The messages on `gateway.sock`, between `brokerd` and `gatewayd`: one connection carries one
|
||||
//! pending approval to Mattermost and its answer back (M4b spec, section 4).
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::wire::DenyReason;
|
||||
use crate::wire::PendingApproval;
|
||||
|
||||
/// The most characters a refusal's reason may have.
|
||||
pub const MAX_REASON: usize = 500;
|
||||
|
||||
/// `brokerd` → `gatewayd`, first: the approval to post.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ApprovalRequest {
|
||||
pub approval: PendingApproval,
|
||||
}
|
||||
|
||||
/// `gatewayd` → `brokerd`: it is posted, in these posts, first part first.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ApprovalPosted {
|
||||
pub posts: Vec<String>,
|
||||
}
|
||||
|
||||
/// `gatewayd` → `brokerd`: an approver answered, with this post.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ApprovalReply {
|
||||
pub approval: u64,
|
||||
pub approved: bool,
|
||||
pub by: String,
|
||||
pub post: String,
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
/// `brokerd` → `gatewayd`, last: what came of the answer.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ApprovalOutcome {
|
||||
pub outcome: GatewayOutcome,
|
||||
}
|
||||
|
||||
// JSON: {"outcome":"denied","reason":"no_grant"} ; the tag sits beside the fields
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "outcome", rename_all = "snake_case", deny_unknown_fields)]
|
||||
pub enum GatewayOutcome {
|
||||
/// Approved, and the grants as they are now still allow it.
|
||||
Runs {},
|
||||
/// Approved, but denied on the re-check, or the answer could not be recorded.
|
||||
Denied { reason: DenyReason },
|
||||
/// The refusal is recorded.
|
||||
Refused {},
|
||||
/// It was no longer pending when the answer came.
|
||||
Gone {},
|
||||
}
|
||||
|
||||
/// A Mattermost id: 26 characters of `a-z0-9`.
|
||||
pub fn is_mattermost_id(id: &str) -> bool {
|
||||
// `id.len() == 26` and every byte `b.is_ascii_lowercase() || b.is_ascii_digit()`.
|
||||
id.len() == 26
|
||||
&& id
|
||||
.bytes()
|
||||
.all(|b| b.is_ascii_lowercase() || b.is_ascii_digit())
|
||||
}
|
||||
|
||||
impl ApprovalReply {
|
||||
/// `by` and `post` are Mattermost ids and the reason is at most `MAX_REASON` characters; the
|
||||
/// first problem otherwise.
|
||||
pub fn check(&self) -> Result<(), String> {
|
||||
// In this order, each an Err with exactly this text:
|
||||
// 1. `!is_mattermost_id(&self.by)`: "the answer's `by` is not a Mattermost id".
|
||||
// 2. `!is_mattermost_id(&self.post)`: "the answer's `post` is not a Mattermost id".
|
||||
// 3. a reason with more than `MAX_REASON` characters (`r.chars().count()`, not bytes):
|
||||
// `format!("the reason is over {MAX_REASON} characters")`.
|
||||
// 4. Ok(()).
|
||||
if !is_mattermost_id(&self.by) {
|
||||
return Err("the answer's `by` is not a Mattermost id".to_string());
|
||||
}
|
||||
if !is_mattermost_id(&self.post) {
|
||||
return Err("the answer's `post` is not a Mattermost id".to_string());
|
||||
}
|
||||
if let Some(reason) = &self.reason
|
||||
&& reason.chars().count() > MAX_REASON
|
||||
{
|
||||
return Err(format!("the reason is over {MAX_REASON} characters"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
//! Grant file schema. A grant is TOML written by the owner to allow one kind of tool call.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{DataClass, Timestamp};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Mode {
|
||||
Auto,
|
||||
Ask,
|
||||
Deny,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase", deny_unknown_fields)]
|
||||
pub struct Constraints {
|
||||
#[serde(default)]
|
||||
pub paths: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub hosts: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub patterns: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Grant {
|
||||
pub tool: String,
|
||||
pub mode: Mode,
|
||||
pub max_taint: DataClass,
|
||||
#[serde(default = "default_result_class")]
|
||||
pub result_class: DataClass,
|
||||
#[serde(default = "default_untrusted")]
|
||||
pub untrusted: bool,
|
||||
#[serde(default)]
|
||||
pub expires: Option<Timestamp>,
|
||||
#[serde(default)]
|
||||
pub secret: Option<String>,
|
||||
#[serde(default)]
|
||||
pub constraints: Constraints,
|
||||
}
|
||||
|
||||
fn default_result_class() -> DataClass {
|
||||
DataClass::Private
|
||||
}
|
||||
|
||||
fn default_untrusted() -> bool {
|
||||
true
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
use crate::Hash32;
|
||||
use emsha::Hash as _;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct HashError;
|
||||
|
||||
impl std::fmt::Display for HashError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "sha256 error")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for HashError {}
|
||||
|
||||
pub struct Sha256(emsha::sha256::SHA256);
|
||||
|
||||
impl Sha256 {
|
||||
pub fn new() -> Self {
|
||||
Sha256(emsha::sha256::SHA256::new())
|
||||
}
|
||||
|
||||
pub fn update(&mut self, data: &[u8]) -> Result<(), HashError> {
|
||||
self.0.update(data).map_err(|_| HashError)
|
||||
}
|
||||
|
||||
pub fn finish(mut self) -> Result<Hash32, HashError> {
|
||||
let mut out = [0u8; emsha::sha256::SIZE];
|
||||
self.0.finalize(&mut out).map_err(|_| HashError)?;
|
||||
Ok(Hash32::from_bytes(out))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Sha256 {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sha256(data: &[u8]) -> Result<Hash32, HashError> {
|
||||
let mut h = Sha256::new();
|
||||
h.update(data)?;
|
||||
h.finish()
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
//! Host names and patterns, shared by `brokerd` and the egress proxy so both check hosts with the
|
||||
//! same rules.
|
||||
|
||||
/// A host label is 1 to 63 bytes of `a-z`, `0-9` or `-`, and neither starts nor ends with `-`.
|
||||
fn valid_label(label: &str) -> bool {
|
||||
if !(1..=63).contains(&label.len()) {
|
||||
return false;
|
||||
}
|
||||
let bytes = label.as_bytes();
|
||||
if bytes.iter().next() == Some(&b'-') || bytes.iter().last() == Some(&b'-') {
|
||||
return false;
|
||||
}
|
||||
bytes
|
||||
.iter()
|
||||
.all(|&byte| matches!(byte, b'a'..=b'z' | b'0'..=b'9' | b'-'))
|
||||
}
|
||||
|
||||
/// A host is 1 to 253 bytes of dot-separated labels, each a whole non-dot name, and the last label
|
||||
/// starts with a letter. That last rule keeps out every spelling of an IPv4 address.
|
||||
pub fn valid_host(host: &str) -> bool {
|
||||
if !(1..=253).contains(&host.len()) {
|
||||
return false;
|
||||
}
|
||||
let labels: Vec<&str> = host.split('.').collect();
|
||||
if labels.len() < 2 {
|
||||
return false;
|
||||
}
|
||||
for label in &labels {
|
||||
if !valid_label(label) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
match labels.iter().last() {
|
||||
Some(last) => matches!(last.bytes().next(), Some(byte) if byte.is_ascii_lowercase()),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// A host, or `*.` followed by a host. Nothing else.
|
||||
pub fn valid_host_pattern(pattern: &str) -> bool {
|
||||
match pattern.strip_prefix("*.") {
|
||||
Some(base) => valid_host(base),
|
||||
None => valid_host(pattern),
|
||||
}
|
||||
}
|
||||
|
||||
/// Does `pattern` match `host`? Without `*.` the strings must be equal; with `*.base` the host must
|
||||
/// end in `.base` with something before the dot.
|
||||
pub fn host_matches(pattern: &str, host: &str) -> bool {
|
||||
let base = match pattern.strip_prefix("*.") {
|
||||
Some(base) => base,
|
||||
None => return pattern == host,
|
||||
};
|
||||
match host.strip_suffix(base) {
|
||||
Some(before) => match before.strip_suffix('.') {
|
||||
Some(prefix) => !prefix.is_empty(),
|
||||
None => false,
|
||||
},
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ValueError {
|
||||
SessionId,
|
||||
Hash32,
|
||||
Timestamp,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ValueError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ValueError::SessionId => write!(f, "invalid session id"),
|
||||
ValueError::Hash32 => write!(f, "invalid hash32"),
|
||||
ValueError::Timestamp => write!(f, "invalid timestamp"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ValueError {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(try_from = "String", into = "String")]
|
||||
pub struct SessionId(String);
|
||||
|
||||
impl SessionId {
|
||||
pub fn new(s: &str) -> Result<Self, ValueError> {
|
||||
if s.is_empty() || s.len() > 64 {
|
||||
return Err(ValueError::SessionId);
|
||||
}
|
||||
if !s
|
||||
.bytes()
|
||||
.all(|b| matches!(b, b'a'..=b'z' | b'0'..=b'9' | b'-'))
|
||||
{
|
||||
return Err(ValueError::SessionId);
|
||||
}
|
||||
Ok(SessionId(s.to_string()))
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SessionId {
|
||||
fn default() -> Self {
|
||||
// Not the empty string, which `new` rejects: a fallback must still be a valid id.
|
||||
SessionId("chat-0-0".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for SessionId {
|
||||
type Error = ValueError;
|
||||
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||
SessionId::new(&s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SessionId> for String {
|
||||
fn from(id: SessionId) -> String {
|
||||
id.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
pub struct Epoch(pub u32);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
pub struct CallId(pub u64);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(try_from = "String", into = "String")]
|
||||
pub struct Hash32([u8; 32]);
|
||||
|
||||
impl Hash32 {
|
||||
pub const ZERO: Hash32 = Hash32([0; 32]);
|
||||
|
||||
pub fn from_bytes(bytes: [u8; 32]) -> Self {
|
||||
Hash32(bytes)
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> &[u8; 32] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn to_hex(&self) -> String {
|
||||
const HEX: &[u8; 16] = b"0123456789abcdef";
|
||||
let mut out = String::with_capacity(64);
|
||||
for &byte in &self.0 {
|
||||
out.push(char::from(HEX[usize::from(byte >> 4)]));
|
||||
out.push(char::from(HEX[usize::from(byte & 0x0f)]));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn from_hex(s: &str) -> Result<Self, ValueError> {
|
||||
if s.len() != 64 {
|
||||
return Err(ValueError::Hash32);
|
||||
}
|
||||
let mut out = [0u8; 32];
|
||||
for (i, pair) in s.as_bytes().chunks(2).enumerate() {
|
||||
let hi = hex_val(pair[0])?;
|
||||
let lo = hex_val(pair[1])?;
|
||||
out[i] = (hi << 4) | lo;
|
||||
}
|
||||
Ok(Hash32(out))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for Hash32 {
|
||||
type Error = ValueError;
|
||||
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||
Hash32::from_hex(&s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Hash32> for String {
|
||||
fn from(h: Hash32) -> String {
|
||||
h.to_hex()
|
||||
}
|
||||
}
|
||||
|
||||
fn hex_val(c: u8) -> Result<u8, ValueError> {
|
||||
match c {
|
||||
b'0'..=b'9' => Ok(c - b'0'),
|
||||
b'a'..=b'f' => Ok(c - b'a' + 10),
|
||||
_ => Err(ValueError::Hash32),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
#[serde(try_from = "String", into = "String")]
|
||||
pub struct Timestamp(u64);
|
||||
|
||||
impl Timestamp {
|
||||
/// 9999-12-31T23:59:59.999Z, the last instant RFC 3339 can spell with a four-digit year.
|
||||
pub const MAX: Timestamp = Timestamp(253_402_300_799_999);
|
||||
|
||||
pub fn from_unix_millis(ms: u64) -> Result<Self, ValueError> {
|
||||
if ms > Self::MAX.0 {
|
||||
return Err(ValueError::Timestamp);
|
||||
}
|
||||
Ok(Timestamp(ms))
|
||||
}
|
||||
|
||||
pub fn unix_millis(&self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn now() -> Self {
|
||||
let dur = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default();
|
||||
let ms = u64::try_from(dur.as_millis()).unwrap_or(u64::MAX);
|
||||
Timestamp::from_unix_millis(ms).unwrap_or(Timestamp::MAX)
|
||||
}
|
||||
|
||||
pub fn to_rfc3339(&self) -> String {
|
||||
let t = std::time::UNIX_EPOCH
|
||||
.checked_add(std::time::Duration::from_millis(self.0))
|
||||
.unwrap_or(std::time::UNIX_EPOCH);
|
||||
humantime::format_rfc3339_millis(t).to_string()
|
||||
}
|
||||
|
||||
pub fn parse(s: &str) -> Result<Self, ValueError> {
|
||||
let t = humantime::parse_rfc3339(s).map_err(|_| ValueError::Timestamp)?;
|
||||
let dur = t
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map_err(|_| ValueError::Timestamp)?;
|
||||
let ms = u64::try_from(dur.as_millis()).map_err(|_| ValueError::Timestamp)?;
|
||||
let parsed = Timestamp::from_unix_millis(ms)?;
|
||||
if parsed.to_rfc3339() != s {
|
||||
return Err(ValueError::Timestamp);
|
||||
}
|
||||
Ok(parsed)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for Timestamp {
|
||||
type Error = ValueError;
|
||||
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||
Timestamp::parse(&s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Timestamp> for String {
|
||||
fn from(t: Timestamp) -> String {
|
||||
t.to_rfc3339()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//! Shared data types and the frame codec for Boxmaker. No policy and no I/O beyond frames.
|
||||
|
||||
pub mod audit;
|
||||
pub mod chain;
|
||||
pub mod class;
|
||||
pub mod escape;
|
||||
pub mod frame;
|
||||
pub mod gateway;
|
||||
pub mod grant;
|
||||
pub mod hash;
|
||||
pub mod hosts;
|
||||
pub mod ids;
|
||||
pub mod log;
|
||||
pub mod memory;
|
||||
pub mod sha1;
|
||||
pub mod tools;
|
||||
pub mod wire;
|
||||
|
||||
pub use audit::{
|
||||
ApprovalAnswer, AuditEvent, AuditRecord, DecisionRecord, ResultStatus, is_audit_log_name,
|
||||
};
|
||||
pub use chain::{ChainFailure, ChainReport, ChainVerifier, Location, TornTail};
|
||||
pub use class::DataClass;
|
||||
pub use frame::{FrameError, MAX_FRAME, read_frame, write_frame};
|
||||
pub use gateway::{
|
||||
ApprovalOutcome, ApprovalPosted, ApprovalReply, ApprovalRequest, GatewayOutcome, MAX_REASON,
|
||||
is_mattermost_id,
|
||||
};
|
||||
pub use grant::{Constraints, Grant, Mode};
|
||||
pub use hash::{HashError, Sha256, sha256};
|
||||
pub use ids::{CallId, Epoch, Hash32, SessionId, Timestamp, ValueError};
|
||||
pub use log::{LogRecord, ToolCall, Usage};
|
||||
pub use memory::{
|
||||
ConsolidateEntry, EntryAction, MemoryAdmin, MemoryAdminReply, MemoryArgs, MemoryBlockReply,
|
||||
MemoryCall, MemoryCommand, MemoryFile, RecallArgs, Reindexed, SessionUpdated,
|
||||
};
|
||||
pub use wire::{
|
||||
ApprovalList, Approve, ApproveResult, DenyReason, Empty, Envelope, ErrorCode, GrantProblem,
|
||||
GrantsReport, Message, PROTOCOL_VERSION, PendingApproval, Refuse, ToolRequest, ToolResponse,
|
||||
Turn, TurnDone, TurnEvent, WireError,
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
//! Session log record types. One JSON object per line; assistant messages are stored verbatim.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{CallId, DataClass, Epoch, Hash32, SessionId, Timestamp};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ToolCall {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub arguments: String,
|
||||
}
|
||||
|
||||
/// What one completion cost. The same five numbers as `LogRecord::Usage`, without the time.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Usage {
|
||||
pub cache_n: u64,
|
||||
pub prompt_n: u64,
|
||||
pub predicted_n: u64,
|
||||
pub reasoning_tokens: u64,
|
||||
pub thinking_capped: bool,
|
||||
}
|
||||
|
||||
// JSON: {"type":"user","time":"…","content":"…"} ; the tag sits beside the fields; types are snake_case
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
|
||||
pub enum LogRecord {
|
||||
SessionStart {
|
||||
time: Timestamp,
|
||||
session: SessionId,
|
||||
epoch: Epoch,
|
||||
slot: u32,
|
||||
baseline: Hash32,
|
||||
},
|
||||
User {
|
||||
time: Timestamp,
|
||||
content: String,
|
||||
},
|
||||
Assistant {
|
||||
time: Timestamp,
|
||||
content: Option<String>,
|
||||
reasoning_content: Option<String>,
|
||||
tool_calls: Vec<ToolCall>,
|
||||
},
|
||||
ToolResult {
|
||||
time: Timestamp,
|
||||
call: CallId,
|
||||
tool_call_id: String,
|
||||
content: String,
|
||||
class: DataClass,
|
||||
untrusted: bool,
|
||||
truncated: bool,
|
||||
},
|
||||
Usage {
|
||||
time: Timestamp,
|
||||
cache_n: u64,
|
||||
prompt_n: u64,
|
||||
predicted_n: u64,
|
||||
reasoning_tokens: u64,
|
||||
thinking_capped: bool,
|
||||
},
|
||||
CacheLoss {
|
||||
time: Timestamp,
|
||||
expected: u64,
|
||||
got: u64,
|
||||
},
|
||||
EpochEnd {
|
||||
time: Timestamp,
|
||||
next: Epoch,
|
||||
summary: String,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
//! Memory: the arguments of `recall` and `memory`, their shape checks, and the messages `memoryd`
|
||||
//! exchanges with `loopd`, `brokerd` and `bxctl` (M5a spec, sections 5 and 7). Content rules (an
|
||||
//! entry matching the file, a file's limit, the check on characters) are `memoryd`'s.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::class::DataClass;
|
||||
use crate::ids::SessionId;
|
||||
use crate::wire::ToolRequest;
|
||||
|
||||
/// The tool names.
|
||||
pub const RECALL: &str = "recall";
|
||||
pub const MEMORY: &str = "memory";
|
||||
|
||||
/// The most characters a `recall` query may have.
|
||||
pub const MAX_QUERY: usize = 500;
|
||||
/// The most hits a `recall` may ask for, and how many it gets when it does not say.
|
||||
pub const MAX_K: u64 = 10;
|
||||
pub const DEFAULT_K: u64 = 5;
|
||||
/// The most characters a `note` may have.
|
||||
pub const MAX_NOTE: usize = 1_000;
|
||||
/// The most characters of any other entry: the larger file's limit.
|
||||
pub const MAX_ENTRY: usize = 2_200;
|
||||
/// The line that separates entries in `memory.md`, `user.md` and `notes.md`.
|
||||
pub const SEPARATOR: &str = "§";
|
||||
|
||||
/// The arguments for `recall`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct RecallArgs {
|
||||
pub query: String,
|
||||
#[serde(default = "default_k")]
|
||||
pub k: u64,
|
||||
}
|
||||
|
||||
fn default_k() -> u64 {
|
||||
DEFAULT_K
|
||||
}
|
||||
|
||||
/// Which identity file an edit is for.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum MemoryFile {
|
||||
Memory,
|
||||
User,
|
||||
}
|
||||
|
||||
impl MemoryFile {
|
||||
/// The file's name under `memory/`.
|
||||
pub fn name(self) -> &'static str {
|
||||
match self {
|
||||
MemoryFile::Memory => "memory.md",
|
||||
MemoryFile::User => "user.md",
|
||||
}
|
||||
}
|
||||
|
||||
/// The file's limit in characters.
|
||||
pub fn limit(self) -> usize {
|
||||
match self {
|
||||
MemoryFile::Memory => 2_200,
|
||||
MemoryFile::User => 1_375,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The arguments for `memory`: one action. `note` records experience; the others edit identity.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "action", rename_all = "snake_case", deny_unknown_fields)]
|
||||
pub enum MemoryArgs {
|
||||
Add {
|
||||
file: MemoryFile,
|
||||
text: String,
|
||||
},
|
||||
Replace {
|
||||
file: MemoryFile,
|
||||
old: String,
|
||||
text: String,
|
||||
},
|
||||
Remove {
|
||||
file: MemoryFile,
|
||||
old: String,
|
||||
},
|
||||
Consolidate {
|
||||
file: MemoryFile,
|
||||
entries: Vec<ConsolidateEntry>,
|
||||
},
|
||||
Note {
|
||||
text: String,
|
||||
},
|
||||
}
|
||||
|
||||
/// One line of a `consolidate`: an existing entry (`old`) with exactly one of `keep`, `text`,
|
||||
/// `move`, `drop`; or a new entry (`text` alone).
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConsolidateEntry {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub old: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub keep: bool,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub text: Option<String>,
|
||||
#[serde(default, rename = "move", skip_serializing_if = "std::ops::Not::not")]
|
||||
pub moved: bool,
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub drop: bool,
|
||||
}
|
||||
|
||||
/// What a `consolidate` line does.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EntryAction {
|
||||
Keep(String),
|
||||
Rewrite { old: String, text: String },
|
||||
Move(String),
|
||||
Drop(String),
|
||||
New(String),
|
||||
}
|
||||
|
||||
impl ConsolidateEntry {
|
||||
/// The line's action, or why it has none.
|
||||
pub fn action(&self) -> Result<EntryAction, &'static str> {
|
||||
let flags = usize::from(self.keep) + usize::from(self.moved) + usize::from(self.drop);
|
||||
match (&self.old, &self.text, flags) {
|
||||
(None, Some(text), 0) => Ok(EntryAction::New(text.clone())),
|
||||
(None, _, _) => Err("an entry without old must have text and nothing else"),
|
||||
(Some(old), Some(text), 0) => Ok(EntryAction::Rewrite {
|
||||
old: old.clone(),
|
||||
text: text.clone(),
|
||||
}),
|
||||
(Some(old), None, 1) => {
|
||||
if self.keep {
|
||||
Ok(EntryAction::Keep(old.clone()))
|
||||
} else if self.moved {
|
||||
Ok(EntryAction::Move(old.clone()))
|
||||
} else {
|
||||
Ok(EntryAction::Drop(old.clone()))
|
||||
}
|
||||
}
|
||||
(Some(_), _, _) => {
|
||||
Err("an entry with old must have exactly one of keep, text, move or drop")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Why an entry's text is not a well-formed entry: empty once trimmed, too long, or holding a line
|
||||
/// that starts with the separator, spaces aside. In `notes.md` such a line would read as a header,
|
||||
/// and a header says where an entry came from. `None` when it is fine.
|
||||
pub fn entry_problem(text: &str, max: usize) -> Option<String> {
|
||||
if text.trim().is_empty() {
|
||||
return Some("an entry cannot be empty".to_string());
|
||||
}
|
||||
let chars = text.chars().count();
|
||||
if chars > max {
|
||||
return Some(format!(
|
||||
"an entry has at most {max} characters; this has {chars}"
|
||||
));
|
||||
}
|
||||
for line in text.lines() {
|
||||
if line.trim_start().starts_with(SEPARATOR) {
|
||||
return Some(format!(
|
||||
"an entry cannot hold a line that starts with {SEPARATOR}"
|
||||
));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// `entry_problem` as a `Result`, so a check can use `?`.
|
||||
fn entry(text: &str, max: usize) -> Result<(), String> {
|
||||
match entry_problem(text, max) {
|
||||
Some(problem) => Err(problem),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
impl RecallArgs {
|
||||
/// The shape rules of `recall` (M5a spec, section 5).
|
||||
pub fn check(&self) -> Result<(), String> {
|
||||
if self.query.trim().is_empty() || self.query.chars().count() > MAX_QUERY {
|
||||
return Err(format!("query must have 1 to {MAX_QUERY} characters"));
|
||||
}
|
||||
if self.k == 0 || self.k > MAX_K {
|
||||
return Err(format!("k must be 1 to {MAX_K}"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl MemoryArgs {
|
||||
/// The shape rules of `memory` (M5a spec, section 5): every text is a well-formed entry, and
|
||||
/// every `consolidate` line has one action and the list is not empty.
|
||||
pub fn check(&self) -> Result<(), String> {
|
||||
match self {
|
||||
MemoryArgs::Add { text, .. } => entry(text, MAX_ENTRY),
|
||||
MemoryArgs::Replace { old, text, .. } => {
|
||||
entry(old, MAX_ENTRY)?;
|
||||
entry(text, MAX_ENTRY)
|
||||
}
|
||||
MemoryArgs::Remove { old, .. } => entry(old, MAX_ENTRY),
|
||||
MemoryArgs::Note { text } => entry(text, MAX_NOTE),
|
||||
MemoryArgs::Consolidate { entries, .. } => {
|
||||
if entries.is_empty() {
|
||||
return Err("consolidate needs every entry of the file".to_string());
|
||||
}
|
||||
for line in entries {
|
||||
match line.action().map_err(str::to_string)? {
|
||||
EntryAction::Keep(old)
|
||||
| EntryAction::Move(old)
|
||||
| EntryAction::Drop(old) => entry(&old, MAX_ENTRY)?,
|
||||
EntryAction::Rewrite { old, text } => {
|
||||
entry(&old, MAX_ENTRY)?;
|
||||
entry(&text, MAX_ENTRY)?;
|
||||
}
|
||||
EntryAction::New(text) => entry(&text, MAX_ENTRY)?,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `memoryd` → `loopd`: the memory block for a new session's baseline.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct MemoryBlockReply {
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
/// `loopd` → `memoryd`: this session's log has new records.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct SessionUpdated {
|
||||
pub session: SessionId,
|
||||
}
|
||||
|
||||
/// `brokerd` → `memoryd`: a decided `recall` or `memory` call, with the session's state when it
|
||||
/// was decided.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct MemoryCall {
|
||||
pub request: ToolRequest,
|
||||
pub taint: DataClass,
|
||||
pub untrusted: bool,
|
||||
}
|
||||
|
||||
/// `memoryd` → `bxctl`: what a reindex read.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Reindexed {
|
||||
pub logs: u64,
|
||||
pub records: u64,
|
||||
pub notes: u64,
|
||||
}
|
||||
|
||||
/// The owner's memory commands (M5a spec, section 10).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum MemoryCommand {
|
||||
Log,
|
||||
Undo,
|
||||
Pending,
|
||||
Accept,
|
||||
Reject,
|
||||
}
|
||||
|
||||
/// `bxctl` → `memoryd`: one command; `arg` is the count for `log`, the edit or pending id for
|
||||
/// `undo`, `accept` and `reject`, and empty otherwise.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct MemoryAdmin {
|
||||
pub command: MemoryCommand,
|
||||
pub arg: String,
|
||||
}
|
||||
|
||||
/// `memoryd` → `bxctl`: the text to print.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct MemoryAdminReply {
|
||||
pub text: String,
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
//! SHA-1 (FIPS 180-4), used only to check the `Sec-WebSocket-Accept` header of a WebSocket
|
||||
//! handshake (RFC 6455, section 4.2.2). Never use it for anything that needs to resist attack.
|
||||
|
||||
/// The digest of `data`.
|
||||
pub fn sha1(data: &[u8]) -> [u8; 20] {
|
||||
let mut h = Sha1::new();
|
||||
h.update(data);
|
||||
h.finish()
|
||||
}
|
||||
|
||||
/// SHA-1 fed in pieces.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Sha1 {
|
||||
state: [u32; 5],
|
||||
block: [u8; 64],
|
||||
filled: usize,
|
||||
length: u64,
|
||||
}
|
||||
|
||||
impl Default for Sha1 {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Sha1 {
|
||||
pub fn new() -> Sha1 {
|
||||
Sha1 {
|
||||
state: [
|
||||
0x6745_2301,
|
||||
0xEFCD_AB89,
|
||||
0x98BA_DCFE,
|
||||
0x1032_5476,
|
||||
0xC3D2_E1F0,
|
||||
],
|
||||
block: [0; 64],
|
||||
filled: 0,
|
||||
length: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, mut data: &[u8]) {
|
||||
// Add 8 * data.len() to `length` (wrapping; `u64::try_from`, never `as`). Copy bytes into
|
||||
// `block` from `filled` on; each time it is full (64), `compress` it and set `filled` to 0.
|
||||
// Use `split_at` and `get_mut(..)`, no indexing that can go out of bounds.
|
||||
self.length = self
|
||||
.length
|
||||
.wrapping_add(8u64.wrapping_mul(u64::try_from(data.len()).unwrap_or(u64::MAX)));
|
||||
while !data.is_empty() {
|
||||
let space = 64 - self.filled;
|
||||
let take = data.len().min(space);
|
||||
let (left, right) = data.split_at(take);
|
||||
if let Some(dst) = self.block.get_mut(self.filled..self.filled + take) {
|
||||
dst.copy_from_slice(left);
|
||||
}
|
||||
self.filled += take;
|
||||
if self.filled == 64 {
|
||||
let block = self.block;
|
||||
self.compress(&block);
|
||||
self.filled = 0;
|
||||
}
|
||||
data = right;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finish(mut self) -> [u8; 20] {
|
||||
// Save `length`. Feed 0x80 then zeros so that 56 bytes of the block are filled (56 -
|
||||
// filled, or 120 - filled when filled >= 56), then the saved length as 8 big-endian bytes,
|
||||
// through `update`. `update` adds to `length`: put the saved value back after. Then the
|
||||
// five state words, big-endian.
|
||||
let saved = self.length;
|
||||
let zeros = if self.filled < 56 {
|
||||
56 - self.filled
|
||||
} else {
|
||||
120 - self.filled
|
||||
};
|
||||
let total = zeros + 8;
|
||||
let mut pad = [0u8; 128];
|
||||
pad[0] = 0x80;
|
||||
pad[zeros..total].copy_from_slice(&saved.to_be_bytes());
|
||||
self.update(&pad[..total]);
|
||||
self.length = saved;
|
||||
|
||||
let mut out = [0u8; 20];
|
||||
for (i, word) in self.state.iter().enumerate() {
|
||||
out[i * 4..i * 4 + 4].copy_from_slice(&word.to_be_bytes());
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn compress(&mut self, block: &[u8; 64]) {
|
||||
// FIPS 180-4, section 6.1.2: w[0..16] are the block as big-endian u32s; w[i] = (w[i-3] ^
|
||||
// w[i-8] ^ w[i-14] ^ w[i-16]).rotate_left(1) for 16..80. Eighty rounds with f and k by
|
||||
// range: 0..=19 (b & c) | (!b & d), 0x5A827999; 20..=39 b ^ c ^ d, 0x6ED9EBA1; 40..=59 (b &
|
||||
// c) | (b & d) | (c & d), 0x8F1BBCDC; 60..=79 b ^ c ^ d, 0xCA62C1D6. All additions
|
||||
// wrapping. Add a..e into state.
|
||||
let mut w: [u32; 80] = [0; 80];
|
||||
let (chunks, _) = block.as_chunks::<4>();
|
||||
for (i, chunk) in chunks.iter().enumerate() {
|
||||
w[i] = u32::from_be_bytes(*chunk);
|
||||
}
|
||||
for i in 16..80 {
|
||||
w[i] = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]).rotate_left(1);
|
||||
}
|
||||
|
||||
let (mut a, mut b, mut c, mut d, mut e) = (
|
||||
self.state[0],
|
||||
self.state[1],
|
||||
self.state[2],
|
||||
self.state[3],
|
||||
self.state[4],
|
||||
);
|
||||
for (round, &ww) in w.iter().enumerate() {
|
||||
let (f, k) = if round <= 19 {
|
||||
((b & c) | (!b & d), 0x5A82_7999)
|
||||
} else if round <= 39 {
|
||||
(b ^ c ^ d, 0x6ED9_EBA1)
|
||||
} else if round <= 59 {
|
||||
((b & c) | (b & d) | (c & d), 0x8F1B_BCDC)
|
||||
} else {
|
||||
(b ^ c ^ d, 0xCA62_C1D6)
|
||||
};
|
||||
let temp = a
|
||||
.rotate_left(5)
|
||||
.wrapping_add(f)
|
||||
.wrapping_add(e)
|
||||
.wrapping_add(k);
|
||||
e = d;
|
||||
d = c;
|
||||
c = b.rotate_left(30);
|
||||
b = a;
|
||||
a = temp.wrapping_add(ww);
|
||||
}
|
||||
|
||||
self.state[0] = self.state[0].wrapping_add(a);
|
||||
self.state[1] = self.state[1].wrapping_add(b);
|
||||
self.state[2] = self.state[2].wrapping_add(c);
|
||||
self.state[3] = self.state[3].wrapping_add(d);
|
||||
self.state[4] = self.state[4].wrapping_add(e);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
//! The four tools' arguments, decoded from the model's JSON then re-serialised. Shared by
|
||||
//! `brokerd` (which validates them) and `toolkit` (inside the container, which runs them), so the
|
||||
//! two must agree on one definition.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// The arguments for `read_file`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ReadFileArgs {
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
/// The arguments for `write_file`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct WriteFileArgs {
|
||||
pub path: String,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
/// The arguments for `shell`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ShellArgs {
|
||||
pub command: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub cwd: Option<String>,
|
||||
}
|
||||
|
||||
/// The arguments for `http_fetch`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct HttpFetchArgs {
|
||||
pub url: String,
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::gateway::{ApprovalOutcome, ApprovalPosted, ApprovalReply, ApprovalRequest};
|
||||
use crate::memory::{
|
||||
MemoryAdmin, MemoryAdminReply, MemoryBlockReply, MemoryCall, Reindexed, SessionUpdated,
|
||||
};
|
||||
use crate::{CallId, DataClass, DecisionRecord, SessionId, Timestamp, Usage};
|
||||
|
||||
pub const PROTOCOL_VERSION: u32 = 1;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Envelope {
|
||||
pub v: u32,
|
||||
pub id: u64,
|
||||
pub r#final: bool,
|
||||
pub msg: Message,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(
|
||||
tag = "kind",
|
||||
content = "body",
|
||||
deny_unknown_fields,
|
||||
rename_all = "snake_case"
|
||||
)]
|
||||
pub enum Message {
|
||||
ToolRequest(ToolRequest),
|
||||
ToolResponse(ToolResponse),
|
||||
Error(WireError),
|
||||
Turn(Turn),
|
||||
TurnEvent(TurnEvent),
|
||||
TurnDone(TurnDone),
|
||||
Approvals(Empty),
|
||||
ApprovalList(ApprovalList),
|
||||
Approve(Approve),
|
||||
ApproveResult(ApproveResult),
|
||||
Refuse(Refuse),
|
||||
Ok(Empty),
|
||||
CheckGrants(Empty),
|
||||
GrantsReport(GrantsReport),
|
||||
ApprovalRequest(ApprovalRequest),
|
||||
ApprovalPosted(ApprovalPosted),
|
||||
ApprovalReply(ApprovalReply),
|
||||
ApprovalOutcome(ApprovalOutcome),
|
||||
MemoryBlock(Empty),
|
||||
MemoryBlockReply(MemoryBlockReply),
|
||||
SessionUpdated(SessionUpdated),
|
||||
MemoryCall(MemoryCall),
|
||||
Reindex(Empty),
|
||||
Reindexed(Reindexed),
|
||||
MemoryAdmin(MemoryAdmin),
|
||||
MemoryAdminReply(MemoryAdminReply),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct WireError {
|
||||
pub code: ErrorCode,
|
||||
pub detail: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ErrorCode {
|
||||
BadFrame,
|
||||
BadVersion,
|
||||
BadMessage,
|
||||
Internal,
|
||||
SessionFull,
|
||||
TurnLimit,
|
||||
SessionBusy,
|
||||
NoSuchSession,
|
||||
SessionExists,
|
||||
Inference,
|
||||
Forbidden,
|
||||
NoSuchApproval,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ToolRequest {
|
||||
pub session: SessionId,
|
||||
pub call: CallId,
|
||||
pub tool: String,
|
||||
pub arguments: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "status", deny_unknown_fields, rename_all = "snake_case")]
|
||||
pub enum ToolResponse {
|
||||
PendingApproval {
|
||||
approval: u64,
|
||||
expires: Timestamp,
|
||||
},
|
||||
Result {
|
||||
content: String,
|
||||
class: DataClass,
|
||||
untrusted: bool,
|
||||
truncated: bool,
|
||||
},
|
||||
Failed {
|
||||
message: String,
|
||||
},
|
||||
Denied {
|
||||
reason: DenyReason,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DenyReason {
|
||||
NoGrant,
|
||||
GrantExpired,
|
||||
TaintTooHigh,
|
||||
DeniedByGrant,
|
||||
ApprovalRefused,
|
||||
ApprovalExpired,
|
||||
GrantsInvalid,
|
||||
AuditUnavailable,
|
||||
InvalidArguments,
|
||||
StateUnreadable,
|
||||
}
|
||||
|
||||
// JSON: {"kind":"turn","body":{"session":"…","content":"…","resume":false}}
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Turn {
|
||||
pub session: SessionId,
|
||||
pub content: String,
|
||||
pub resume: bool,
|
||||
}
|
||||
|
||||
// JSON: {"event":"content","text":"…"} — the tag sits beside the fields, snake_case
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "event", deny_unknown_fields, rename_all = "snake_case")]
|
||||
pub enum TurnEvent {
|
||||
Queued {
|
||||
ahead: u64,
|
||||
},
|
||||
Waiting {
|
||||
slot_busy: bool,
|
||||
},
|
||||
Progress {
|
||||
total: u64,
|
||||
cache: u64,
|
||||
processed: u64,
|
||||
},
|
||||
Reasoning {
|
||||
text: String,
|
||||
},
|
||||
Content {
|
||||
text: String,
|
||||
},
|
||||
ToolCallStarted {
|
||||
name: String,
|
||||
},
|
||||
ToolResult {
|
||||
name: String,
|
||||
class: DataClass,
|
||||
truncated: bool,
|
||||
},
|
||||
ThinkingCapped {
|
||||
tokens: u64,
|
||||
},
|
||||
Retrying {
|
||||
attempt: u32,
|
||||
after_ms: u64,
|
||||
error: String,
|
||||
},
|
||||
CacheLoss {
|
||||
expected: u64,
|
||||
got: u64,
|
||||
},
|
||||
ApprovalPending {
|
||||
approval: u64,
|
||||
tool: String,
|
||||
expires: Timestamp,
|
||||
},
|
||||
ToolDenied {
|
||||
name: String,
|
||||
reason: DenyReason,
|
||||
},
|
||||
}
|
||||
|
||||
// JSON: {"kind":"turn_done","body":{"content":"…","usage":{…}}}
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct TurnDone {
|
||||
pub content: String,
|
||||
pub usage: Usage,
|
||||
}
|
||||
|
||||
// The admin messages on admin.sock. JSON: {"kind":"approvals","body":{}}
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Empty {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct PendingApproval {
|
||||
pub approval: u64,
|
||||
pub session: SessionId,
|
||||
pub call: CallId,
|
||||
pub tool: String,
|
||||
pub arguments: String,
|
||||
pub grant: String,
|
||||
pub taint: DataClass,
|
||||
pub created: Timestamp,
|
||||
pub expires: Timestamp,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ApprovalList {
|
||||
pub items: Vec<PendingApproval>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Approve {
|
||||
pub approval: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ApproveResult {
|
||||
pub outcome: DecisionRecord,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Refuse {
|
||||
pub approval: u64,
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct GrantProblem {
|
||||
pub file: String,
|
||||
pub line: Option<u64>,
|
||||
pub problem: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct GrantsReport {
|
||||
pub problems: Vec<GrantProblem>,
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
//! Tests for the admin messages of `admin.sock` and the two new error codes, against byte-exact
|
||||
//! fixtures. Do not edit these or the fixtures.
|
||||
|
||||
use bxcore::{
|
||||
ApprovalList, Approve, ApproveResult, CallId, DataClass, DecisionRecord, DenyReason, Empty,
|
||||
Envelope, ErrorCode, GrantProblem, GrantsReport, Message, PendingApproval, Refuse, SessionId,
|
||||
Timestamp, WireError,
|
||||
};
|
||||
|
||||
fn fixture(name: &str) -> String {
|
||||
let path = format!("{}/tests/fixtures/wire/{name}", env!("CARGO_MANIFEST_DIR"));
|
||||
let text = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("{path}: {e}"));
|
||||
text.trim_end_matches('\n').to_string()
|
||||
}
|
||||
|
||||
/// The fixture must decode to `want`, and `want` must encode to exactly the fixture's bytes.
|
||||
fn check(name: &str, id: u64, msg: Message) {
|
||||
let want = Envelope {
|
||||
v: 1,
|
||||
id,
|
||||
r#final: true,
|
||||
msg,
|
||||
};
|
||||
let text = fixture(name);
|
||||
let got: Envelope = serde_json::from_str(&text).unwrap_or_else(|e| panic!("{name}: {e}"));
|
||||
assert_eq!(got, want, "{name}: decoded value");
|
||||
assert_eq!(
|
||||
serde_json::to_string(&want).unwrap(),
|
||||
text,
|
||||
"{name}: encoded bytes"
|
||||
);
|
||||
}
|
||||
|
||||
fn pending() -> PendingApproval {
|
||||
PendingApproval {
|
||||
approval: 41,
|
||||
session: SessionId::new("chat-1789700000-42").unwrap(),
|
||||
call: CallId(3),
|
||||
tool: "shell".to_string(),
|
||||
arguments: r#"{"command":"rm -rf /home/kyle/scratch/build","cwd":"/home/kyle/scratch"}"#
|
||||
.to_string(),
|
||||
grant: "shell-scratch".to_string(),
|
||||
taint: DataClass::Private,
|
||||
created: Timestamp::parse("2026-09-18T08:05:00.000Z").unwrap(),
|
||||
expires: Timestamp::parse("2026-09-18T08:20:00.000Z").unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_three_requests_with_an_empty_body() {
|
||||
check("approvals.json", 5, Message::Approvals(Empty {}));
|
||||
check("check_grants.json", 8, Message::CheckGrants(Empty {}));
|
||||
check("ok.json", 7, Message::Ok(Empty {}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approval_list() {
|
||||
let list = ApprovalList {
|
||||
items: vec![pending()],
|
||||
};
|
||||
check("approval_list.json", 5, Message::ApprovalList(list));
|
||||
let empty = ApprovalList { items: Vec::new() };
|
||||
check("approval_list_empty.json", 5, Message::ApprovalList(empty));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approve_and_its_result() {
|
||||
check(
|
||||
"approve.json",
|
||||
6,
|
||||
Message::Approve(Approve { approval: 41 }),
|
||||
);
|
||||
check(
|
||||
"approve_result_allowed.json",
|
||||
6,
|
||||
Message::ApproveResult(ApproveResult {
|
||||
outcome: DecisionRecord::Allowed {},
|
||||
}),
|
||||
);
|
||||
check(
|
||||
"approve_result_denied.json",
|
||||
6,
|
||||
Message::ApproveResult(ApproveResult {
|
||||
outcome: DecisionRecord::Denied {
|
||||
reason: DenyReason::NoGrant,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refuse_with_and_without_a_reason() {
|
||||
check(
|
||||
"refuse.json",
|
||||
7,
|
||||
Message::Refuse(Refuse {
|
||||
approval: 41,
|
||||
reason: Some("not while I am away".to_string()),
|
||||
}),
|
||||
);
|
||||
check(
|
||||
"refuse_no_reason.json",
|
||||
7,
|
||||
Message::Refuse(Refuse {
|
||||
approval: 41,
|
||||
reason: None,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grants_report() {
|
||||
let report = GrantsReport {
|
||||
problems: vec![
|
||||
GrantProblem {
|
||||
file: "notes-read.toml".to_string(),
|
||||
line: Some(3),
|
||||
problem: "unknown field `mdoe`".to_string(),
|
||||
},
|
||||
GrantProblem {
|
||||
file: "Bad_Name.toml".to_string(),
|
||||
line: None,
|
||||
problem: "the file name is not a valid grant id".to_string(),
|
||||
},
|
||||
],
|
||||
};
|
||||
check("grants_report.json", 8, Message::GrantsReport(report));
|
||||
let ok = GrantsReport {
|
||||
problems: Vec::new(),
|
||||
};
|
||||
check("grants_report_ok.json", 8, Message::GrantsReport(ok));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_two_new_error_codes() {
|
||||
check(
|
||||
"error_forbidden.json",
|
||||
9,
|
||||
Message::Error(WireError {
|
||||
code: ErrorCode::Forbidden,
|
||||
detail: "approve is not accepted on broker.sock".to_string(),
|
||||
}),
|
||||
);
|
||||
check(
|
||||
"error_no_such_approval.json",
|
||||
6,
|
||||
Message::Error(WireError {
|
||||
code: ErrorCode::NoSuchApproval,
|
||||
detail: "41".to_string(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/// An empty body is an object with no keys: not `null`, not a missing body, not an object with a
|
||||
/// key in it.
|
||||
#[test]
|
||||
fn an_empty_body_must_be_an_empty_object() {
|
||||
let good = fixture("approvals.json");
|
||||
assert!(serde_json::from_str::<Envelope>(&good).is_ok());
|
||||
let bad = [
|
||||
good.replacen("\"body\":{}", "\"body\":null", 1),
|
||||
good.replacen(",\"body\":{}", "", 1),
|
||||
good.replacen("\"body\":{}", "\"body\":{\"all\":true}", 1),
|
||||
];
|
||||
for text in bad {
|
||||
assert_ne!(text, good);
|
||||
assert!(
|
||||
serde_json::from_str::<Envelope>(&text).is_err(),
|
||||
"accepted {text}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// `reason` and `line` may be null but may not be left out: every field is always written, so a
|
||||
/// reader never has to guess what a missing one means.
|
||||
#[test]
|
||||
fn optional_fields_are_null_not_absent() {
|
||||
let refuse = fixture("refuse_no_reason.json");
|
||||
let cut = refuse.replacen(",\"reason\":null", "", 1);
|
||||
assert_ne!(cut, refuse);
|
||||
assert!(
|
||||
serde_json::from_str::<Envelope>(&cut).is_ok(),
|
||||
"serde reads a missing Option as None; this documents it"
|
||||
);
|
||||
assert!(
|
||||
serde_json::to_string(&Refuse {
|
||||
approval: 1,
|
||||
reason: None
|
||||
})
|
||||
.unwrap()
|
||||
.contains("\"reason\":null")
|
||||
);
|
||||
assert!(
|
||||
serde_json::to_string(&GrantProblem {
|
||||
file: "a.toml".to_string(),
|
||||
line: None,
|
||||
problem: "x".to_string()
|
||||
})
|
||||
.unwrap()
|
||||
.contains("\"line\":null")
|
||||
);
|
||||
}
|
||||
|
||||
/// An outcome is strict too. serde does not apply `deny_unknown_fields` to unit variants such as
|
||||
/// `allowed`, so `DecisionRecord` must not rely on the derive for it.
|
||||
#[test]
|
||||
fn an_outcome_rejects_unknown_and_misplaced_fields() {
|
||||
for good in [
|
||||
r#"{"outcome":"allowed"}"#,
|
||||
r#"{"outcome":"ask"}"#,
|
||||
r#"{"outcome":"denied","reason":"no_grant"}"#,
|
||||
] {
|
||||
let value: DecisionRecord = serde_json::from_str(good).unwrap();
|
||||
assert_eq!(serde_json::to_string(&value).unwrap(), good);
|
||||
}
|
||||
for bad in [
|
||||
r#"{"outcome":"allowed","zz":1}"#,
|
||||
r#"{"outcome":"ask","zz":1}"#,
|
||||
r#"{"outcome":"denied","reason":"no_grant","zz":1}"#,
|
||||
r#"{"outcome":"allowed","reason":"no_grant"}"#,
|
||||
r#"{"outcome":"ask","reason":null,"zz":1}"#,
|
||||
r#"{"outcome":"allowed","reason":null}"#,
|
||||
r#"{"outcome":"denied"}"#,
|
||||
r#"{"outcome":"approved"}"#,
|
||||
r#"{"reason":"no_grant"}"#,
|
||||
] {
|
||||
assert!(
|
||||
serde_json::from_str::<DecisionRecord>(bad).is_err(),
|
||||
"accepted {bad}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pending_approvals_reject_bad_values() {
|
||||
let good = fixture("approval_list.json");
|
||||
assert!(serde_json::from_str::<Envelope>(&good).is_ok());
|
||||
let bad = [
|
||||
// an approval id is a number
|
||||
good.replacen("\"approval\":41", "\"approval\":\"41\"", 1),
|
||||
// the session id is validated
|
||||
good.replacen("chat-1789700000-42", "../etc", 1),
|
||||
// the taint is one of the three classes
|
||||
good.replacen("\"taint\":\"private\"", "\"taint\":\"internal\"", 1),
|
||||
// a field is missing
|
||||
good.replacen("\"grant\":\"shell-scratch\",", "", 1),
|
||||
// a field nobody defined
|
||||
good.replacen("\"grant\":", "\"note\":1,\"grant\":", 1),
|
||||
];
|
||||
for text in bad {
|
||||
assert_ne!(text, good);
|
||||
assert!(
|
||||
serde_json::from_str::<Envelope>(&text).is_err(),
|
||||
"accepted {text}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,389 @@
|
||||
//! The audit chain verifier against the fixture logs in `tests/fixtures/audit/`. Do not edit
|
||||
//! this file or the fixtures: their hashes are real, and one changed byte changes the verdict.
|
||||
//!
|
||||
//! Every fixture is a small audit directory. `good` is an undamaged two-day log; the others are
|
||||
//! `good` with one thing done to it, named by the directory.
|
||||
|
||||
use bxcore::{AuditRecord, ChainReport, ChainVerifier, Hash32, Location, sha256};
|
||||
|
||||
const D1: &str = "2026-09-17.jsonl";
|
||||
const D2: &str = "2026-09-18.jsonl";
|
||||
|
||||
fn dir(case: &str) -> String {
|
||||
format!("{}/tests/fixtures/audit/{case}", env!("CARGO_MANIFEST_DIR"))
|
||||
}
|
||||
|
||||
/// The `.jsonl` files of a case, in name order, with their bytes.
|
||||
fn files(case: &str) -> Vec<(String, Vec<u8>)> {
|
||||
let dir = dir(case);
|
||||
let mut names: Vec<String> = std::fs::read_dir(&dir)
|
||||
.unwrap_or_else(|e| panic!("{dir}: {e}"))
|
||||
.map(|entry| entry.unwrap().file_name().into_string().unwrap())
|
||||
.filter(|name| name.ends_with(".jsonl"))
|
||||
.collect();
|
||||
names.sort();
|
||||
assert!(!names.is_empty(), "{dir}: no files");
|
||||
names
|
||||
.into_iter()
|
||||
.map(|name| {
|
||||
let bytes = std::fs::read(format!("{dir}/{name}")).unwrap();
|
||||
(name, bytes)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn verify(case: &str) -> ChainReport {
|
||||
let mut verifier = ChainVerifier::new();
|
||||
for (name, bytes) in files(case) {
|
||||
verifier.feed(&name, &bytes);
|
||||
}
|
||||
verifier.finish()
|
||||
}
|
||||
|
||||
/// Line `line` (1-based) of a file of a case, without its newline.
|
||||
fn line_of(case: &str, file: &str, line: usize) -> Vec<u8> {
|
||||
let (_, bytes) = files(case)
|
||||
.into_iter()
|
||||
.find(|(name, _)| name == file)
|
||||
.unwrap();
|
||||
bytes.split(|b| *b == b'\n').nth(line - 1).unwrap().to_vec()
|
||||
}
|
||||
|
||||
fn hash_of(case: &str, file: &str, line: usize) -> Hash32 {
|
||||
sha256(&line_of(case, file, line)).unwrap()
|
||||
}
|
||||
|
||||
fn at(file: &str, line: u64) -> Location {
|
||||
Location {
|
||||
file: file.to_string(),
|
||||
line,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn good_log_verifies() {
|
||||
let report = verify("good");
|
||||
assert_eq!(report.failure, None);
|
||||
assert_eq!(report.records, 10);
|
||||
assert_eq!(report.next_seq, 10);
|
||||
assert_eq!(report.head, Some(hash_of("good", D2, 5)));
|
||||
assert_eq!(
|
||||
report.abandoned,
|
||||
vec![6],
|
||||
"the ask at seq 6 has no approval"
|
||||
);
|
||||
assert_eq!(
|
||||
report.unfinished,
|
||||
vec![7],
|
||||
"the allowed call at seq 7 has no result"
|
||||
);
|
||||
assert!(report.recoveries.is_empty());
|
||||
assert!(report.accepted_breaks.is_empty());
|
||||
assert!(report.clock_warnings.is_empty());
|
||||
assert_eq!(report.torn_tail, None);
|
||||
}
|
||||
|
||||
/// The tampering suite: each case fails, at this file and line, with this text.
|
||||
#[test]
|
||||
fn tampering_is_found_at_the_right_line() {
|
||||
let parse = "does not parse as an audit record";
|
||||
let cases = [
|
||||
// The changed line still parses and chains; the line after it no longer chains from it.
|
||||
(
|
||||
"changed-byte",
|
||||
D1,
|
||||
4,
|
||||
"prev is not the hash of the line before",
|
||||
),
|
||||
("deleted-line", D1, 3, "seq is 3, expected 2"),
|
||||
("swapped-lines", D1, 2, "seq is 2, expected 1"),
|
||||
("seq-gap", D1, 3, "seq is 3, expected 2"),
|
||||
(
|
||||
"file-not-chained",
|
||||
D2,
|
||||
1,
|
||||
"does not chain from the last line of the file before",
|
||||
),
|
||||
("cut-short", D1, 3, parse),
|
||||
(
|
||||
"break-wrong-line",
|
||||
D1,
|
||||
4,
|
||||
"prev is not the hash of the line before",
|
||||
),
|
||||
(
|
||||
"break-wrong-last-good",
|
||||
D1,
|
||||
4,
|
||||
"prev is not the hash of the line before",
|
||||
),
|
||||
(
|
||||
"break-wrong-prev",
|
||||
D1,
|
||||
4,
|
||||
"prev is not the hash of the line before",
|
||||
),
|
||||
(
|
||||
"break-wrong-seq",
|
||||
D1,
|
||||
4,
|
||||
"prev is not the hash of the line before",
|
||||
),
|
||||
(
|
||||
"break-without-failure",
|
||||
D2,
|
||||
6,
|
||||
"an accepted break with no failure before it",
|
||||
),
|
||||
("recovery-wrong-hash", D2, 6, parse),
|
||||
("recovery-wrong-length", D2, 6, parse),
|
||||
(
|
||||
"recovery-describes-nothing",
|
||||
D2,
|
||||
6,
|
||||
"a recovery record that does not describe the line before it",
|
||||
),
|
||||
("torn-recovery", D2, 6, parse),
|
||||
];
|
||||
for (case, file, line, what) in cases {
|
||||
let failure = verify(case)
|
||||
.failure
|
||||
.unwrap_or_else(|| panic!("{case}: verified, but it is damaged"));
|
||||
assert_eq!(
|
||||
(failure.file.as_str(), failure.line, failure.what.as_str()),
|
||||
(file, line, what),
|
||||
"{case}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_failure_says_what_a_break_record_must_carry() {
|
||||
let failure = verify("changed-byte").failure.unwrap();
|
||||
assert_eq!(failure.last_good, hash_of("changed-byte", D1, 3));
|
||||
assert_eq!(failure.break_prev, hash_of("changed-byte", D2, 5));
|
||||
// The failing line should have had seq 3; seven lines run from it to the end of the log.
|
||||
assert_eq!(failure.break_seq, 10);
|
||||
assert!(!failure.tail_torn);
|
||||
|
||||
// A failure at the very first line: nothing verified, so last_good is all zeros.
|
||||
let mut verifier = ChainVerifier::new();
|
||||
verifier.file(D1);
|
||||
verifier.line(b"not json", true);
|
||||
verifier.line(b"nor this", true);
|
||||
let failure = verifier.finish().failure.unwrap();
|
||||
assert_eq!(
|
||||
(failure.line, failure.last_good, failure.break_seq),
|
||||
(1, Hash32::ZERO, 2)
|
||||
);
|
||||
assert_eq!(failure.break_prev, sha256(b"nor this").unwrap());
|
||||
|
||||
let failure = verify("torn-recovery").failure.unwrap();
|
||||
assert!(failure.tail_torn, "the last line has no newline");
|
||||
assert_eq!(
|
||||
failure.break_seq, 12,
|
||||
"seq 10 for line 6, and two lines to the end"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verification_stops_counting_at_a_failure() {
|
||||
let report = verify("changed-byte");
|
||||
assert_eq!(report.records, 3);
|
||||
assert_eq!(report.head, Some(hash_of("changed-byte", D1, 3)));
|
||||
assert_eq!(report.next_seq, 3);
|
||||
assert_eq!(report.torn_tail, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_torn_tail_is_not_a_failure() {
|
||||
// (case, file, line, has_newline, records, recovery_seq, file and line of the record before)
|
||||
let cases = [
|
||||
("torn-tail", D2, 6, false, 10, 10, (D2, 5)),
|
||||
// Complete JSON that lacks only its newline is torn all the same.
|
||||
("torn-tail-complete-json", D2, 6, false, 10, 10, (D2, 5)),
|
||||
// A crash between ending a torn line and writing its Recovery.
|
||||
("torn-unparseable-newline", D2, 6, true, 10, 10, (D2, 5)),
|
||||
("torn-first-line", D2, 1, false, 5, 5, (D1, 5)),
|
||||
];
|
||||
for (case, file, line, has_newline, records, seq, before) in cases {
|
||||
let report = verify(case);
|
||||
assert_eq!(report.failure, None, "{case}");
|
||||
assert_eq!(report.records, records, "{case}");
|
||||
let torn = report
|
||||
.torn_tail
|
||||
.unwrap_or_else(|| panic!("{case}: no torn tail"));
|
||||
let bytes = line_of(case, file, line as usize);
|
||||
assert_eq!(torn.at, at(file, line), "{case}");
|
||||
assert_eq!(torn.has_newline, has_newline, "{case}");
|
||||
assert_eq!(torn.bytes, bytes.len() as u64, "{case}");
|
||||
assert_eq!(torn.sha256, sha256(&bytes).unwrap(), "{case}");
|
||||
assert_eq!(torn.recovery_seq, seq, "{case}");
|
||||
assert_eq!(
|
||||
torn.recovery_prev,
|
||||
hash_of(case, before.0, before.1),
|
||||
"{case}"
|
||||
);
|
||||
assert_eq!(
|
||||
report.next_seq, seq,
|
||||
"{case}: the torn line is not a record"
|
||||
);
|
||||
}
|
||||
let whole = line_of("torn-tail-complete-json", D2, 6);
|
||||
assert!(
|
||||
serde_json::from_slice::<AuditRecord>(&whole).is_ok(),
|
||||
"this case must be a line that parses"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_empty_latest_file_is_fine() {
|
||||
let report = verify("empty-latest");
|
||||
assert_eq!((report.failure, report.torn_tail), (None, None));
|
||||
assert_eq!((report.records, report.next_seq), (5, 5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_recovered_line_is_not_a_record_and_not_a_failure() {
|
||||
// (case, where the recovered line is, records, abandoned, unfinished)
|
||||
let cases = [
|
||||
("recovered", at(D2, 6), 12, vec![6], vec![]),
|
||||
// The recovered line is complete JSON with seq 10; the Recovery takes seq 10 again.
|
||||
("recovered-complete-json", at(D2, 6), 12, vec![6], vec![]),
|
||||
// Torn on one day, recovered on the next: the Recovery is in the torn line's file.
|
||||
("recovered-next-day", at(D1, 6), 11, vec![7], vec![8]),
|
||||
("recovered-first-line", at(D2, 1), 6, vec![], vec![]),
|
||||
];
|
||||
for (case, recovered, records, abandoned, unfinished) in cases {
|
||||
let report = verify(case);
|
||||
assert_eq!(report.failure, None, "{case}");
|
||||
assert_eq!(report.torn_tail, None, "{case}");
|
||||
assert_eq!(report.recoveries, vec![recovered], "{case}");
|
||||
assert_eq!(report.records, records, "{case}");
|
||||
assert_eq!(report.abandoned, abandoned, "{case}");
|
||||
assert_eq!(report.unfinished, unfinished, "{case}");
|
||||
assert!(report.clock_warnings.is_empty(), "{case}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_clock_stepped_back_is_a_warning() {
|
||||
let report = verify("clock-back");
|
||||
assert_eq!(report.failure, None);
|
||||
assert_eq!(report.records, 11);
|
||||
assert_eq!(report.clock_warnings, vec![at(D2, 6)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_accepted_break_clears_the_failure_before_it() {
|
||||
// (case, where the break record is, records, next_seq)
|
||||
let cases = [
|
||||
("accepted-break", at(D1, 6), 5, 7),
|
||||
("accepted-break-older-file", at(D2, 6), 5, 12),
|
||||
// A deleted line in day 2 as well: one break covers every failure before it.
|
||||
("accepted-break-two-failures", at(D2, 5), 4, 10),
|
||||
// A line in the region claims seq 18446744073709551615. The break's seq is counted
|
||||
// from lines, so it is 10 all the same.
|
||||
("accepted-break-max-seq", at(D2, 6), 4, 11),
|
||||
];
|
||||
for (case, break_at, records, next_seq) in cases {
|
||||
let report = verify(case);
|
||||
assert_eq!(report.failure, None, "{case}");
|
||||
assert_eq!(report.accepted_breaks, vec![break_at], "{case}");
|
||||
assert_eq!(report.records, records, "{case}");
|
||||
assert_eq!(report.next_seq, next_seq, "{case}");
|
||||
}
|
||||
// Records inside the region are not vouched for: the approval of seq 2 is in it.
|
||||
let report = verify("accepted-break");
|
||||
assert_eq!(report.abandoned, vec![2]);
|
||||
assert_eq!(report.unfinished, vec![6]);
|
||||
}
|
||||
|
||||
/// A verifier that starts at the latest file cannot judge a break that names an older one. It
|
||||
/// checks the break's `prev` and goes on; the full verification judges the rest.
|
||||
#[test]
|
||||
fn a_resumed_verifier_accepts_a_break_naming_an_earlier_file() {
|
||||
let case = "accepted-break-older-file";
|
||||
let last: AuditRecord = serde_json::from_slice(&line_of(case, D1, 5)).unwrap();
|
||||
let mut verifier = ChainVerifier::resume(last.seq + 1, hash_of(case, D1, 5));
|
||||
let (_, day2) = files(case).into_iter().nth(1).unwrap();
|
||||
verifier.feed(D2, &day2);
|
||||
let report = verifier.finish();
|
||||
assert_eq!(report.failure, None);
|
||||
assert_eq!(report.accepted_breaks, vec![at(D2, 6)]);
|
||||
assert_eq!((report.records, report.next_seq), (7, 12));
|
||||
|
||||
// The same break with a wrong prev is not accepted, resumed or not.
|
||||
let mut verifier = ChainVerifier::resume(last.seq + 1, hash_of(case, D1, 5));
|
||||
let (_, day2) = files("break-wrong-prev").into_iter().nth(1).unwrap();
|
||||
verifier.feed(D2, &day2);
|
||||
assert!(verifier.finish().failure.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resume_continues_from_the_file_before() {
|
||||
let last: AuditRecord = serde_json::from_slice(&line_of("good", D1, 5)).unwrap();
|
||||
let (_, day2) = files("good").into_iter().nth(1).unwrap();
|
||||
|
||||
let mut verifier = ChainVerifier::resume(last.seq + 1, hash_of("good", D1, 5));
|
||||
verifier.feed(D2, &day2);
|
||||
let report = verifier.finish();
|
||||
assert_eq!(report.failure, None);
|
||||
assert_eq!((report.records, report.next_seq), (5, 10));
|
||||
|
||||
// Resumed from the wrong hash, the first line of the file does not chain.
|
||||
let mut verifier = ChainVerifier::resume(last.seq + 1, Hash32::ZERO);
|
||||
verifier.feed(D2, &day2);
|
||||
let failure = verifier.finish().failure.unwrap();
|
||||
assert_eq!((failure.file.as_str(), failure.line), (D2, 1));
|
||||
assert_eq!(
|
||||
failure.what,
|
||||
"does not chain from the last line of the file before"
|
||||
);
|
||||
assert_eq!(
|
||||
failure.last_good,
|
||||
Hash32::ZERO,
|
||||
"the hash it was resumed with"
|
||||
);
|
||||
}
|
||||
|
||||
/// `feed` is `file` and then `line` for each line; both ways must give the same report.
|
||||
#[test]
|
||||
fn feed_is_file_then_lines() {
|
||||
for case in [
|
||||
"good",
|
||||
"torn-tail",
|
||||
"recovered",
|
||||
"changed-byte",
|
||||
"empty-latest",
|
||||
] {
|
||||
let mut verifier = ChainVerifier::new();
|
||||
for (name, bytes) in files(case) {
|
||||
verifier.file(&name);
|
||||
let mut rest: &[u8] = &bytes;
|
||||
while !rest.is_empty() {
|
||||
match rest.iter().position(|b| *b == b'\n') {
|
||||
Some(end) => {
|
||||
verifier.line(&rest[..end], true);
|
||||
rest = &rest[end + 1..];
|
||||
}
|
||||
None => {
|
||||
verifier.line(rest, false);
|
||||
rest = &[];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(verifier.finish(), verify(case), "{case}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_empty_log_is_fine() {
|
||||
let report = ChainVerifier::new().finish();
|
||||
assert_eq!(
|
||||
(report.failure, report.torn_tail, report.head),
|
||||
(None, None, None)
|
||||
);
|
||||
assert_eq!((report.records, report.next_seq), (0, 0));
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
//! Tests for `bxcore::escape`: text the model wrote is printed as data. Do not edit.
|
||||
//!
|
||||
//! The expected escapes are built by `esc`, never spelled out, so that nothing that handles this
|
||||
//! file can turn one into the character it stands for.
|
||||
|
||||
use bxcore::escape::{escape_json_text, escape_model_text};
|
||||
|
||||
const BACKSLASH: char = '\\';
|
||||
|
||||
/// The escape for one code point: a backslash, `u`, and four lowercase hex digits.
|
||||
fn esc(code: u32) -> String {
|
||||
format!("{BACKSLASH}u{code:04x}")
|
||||
}
|
||||
|
||||
fn ch(code: u32) -> char {
|
||||
char::from_u32(code).unwrap()
|
||||
}
|
||||
|
||||
/// Every code point that must be escaped, as inclusive ranges.
|
||||
const HIDDEN: [(u32, u32); 6] = [
|
||||
(0x0000, 0x001f),
|
||||
(0x007f, 0x009f),
|
||||
(0x200b, 0x200f),
|
||||
(0x2028, 0x202e),
|
||||
(0x2060, 0x2069),
|
||||
(0xfeff, 0xfeff),
|
||||
];
|
||||
|
||||
fn hidden(code: u32) -> bool {
|
||||
HIDDEN.iter().any(|(lo, hi)| (*lo..=*hi).contains(&code))
|
||||
}
|
||||
|
||||
/// Walks every code point below U+11000, not a sample: each is either escaped exactly or left
|
||||
/// exactly as it is.
|
||||
#[test]
|
||||
fn every_listed_code_point_is_escaped_and_no_other() {
|
||||
let mut escaped = 0;
|
||||
for code in 0..0x11000u32 {
|
||||
let Some(c) = char::from_u32(code) else {
|
||||
continue; // the surrogates are not characters
|
||||
};
|
||||
let text = format!("a{c}b");
|
||||
let got = escape_json_text(&text);
|
||||
if hidden(code) {
|
||||
escaped += 1;
|
||||
assert_eq!(got, format!("a{}b", esc(code)), "U+{code:04X}");
|
||||
} else {
|
||||
assert_eq!(got, text, "U+{code:04X} must pass through");
|
||||
}
|
||||
}
|
||||
assert_eq!(escaped, 32 + 33 + 5 + 7 + 10 + 1, "the six ranges, in full");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_edges_of_each_range() {
|
||||
for (lo, hi) in HIDDEN {
|
||||
assert_eq!(escape_json_text(&ch(lo).to_string()), esc(lo));
|
||||
assert_eq!(escape_json_text(&ch(hi).to_string()), esc(hi));
|
||||
if lo > 0 {
|
||||
let before = ch(lo - 1).to_string();
|
||||
assert_eq!(escape_json_text(&before), before, "U+{:04X}", lo - 1);
|
||||
}
|
||||
let after = ch(hi + 1).to_string();
|
||||
assert_eq!(escape_json_text(&after), after, "U+{:04X}", hi + 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_digits_are_lowercase_and_there_are_always_four() {
|
||||
assert_eq!(escape_json_text("\x1b"), esc(0x1b));
|
||||
assert!(escape_json_text("\x1b").ends_with("001b"));
|
||||
assert!(escape_json_text("\0").ends_with("0000"));
|
||||
assert!(escape_json_text(&ch(0xfeff).to_string()).ends_with("feff"));
|
||||
assert!(escape_json_text(&ch(0x202e).to_string()).ends_with("202e"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_escape_sequence_cannot_reach_the_terminal() {
|
||||
let text = "before\x1b[8mhidden\x1b[0m\x07after";
|
||||
let got = escape_json_text(text);
|
||||
assert!(!got.contains('\x1b') && !got.contains('\x07'), "{got:?}");
|
||||
assert_eq!(
|
||||
got,
|
||||
format!(
|
||||
"before{}[8mhidden{}[0m{}after",
|
||||
esc(0x1b),
|
||||
esc(0x1b),
|
||||
esc(0x07)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_path_cannot_be_shown_backwards() {
|
||||
// U+202E makes a terminal draw what follows from right to left.
|
||||
let text = format!("/home/kyle/notes/{}dm.terces", ch(0x202e));
|
||||
assert_eq!(
|
||||
escape_json_text(&text),
|
||||
format!("/home/kyle/notes/{}dm.terces", esc(0x202e))
|
||||
);
|
||||
let text = format!("a{}b{}c", ch(0x200b), ch(0x2066));
|
||||
assert_eq!(
|
||||
escape_json_text(&text),
|
||||
format!("a{}b{}c", esc(0x200b), esc(0x2066))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ordinary_text_is_unchanged() {
|
||||
for text in [
|
||||
"",
|
||||
"plain",
|
||||
r#"{"command":"ls -l","cwd":"/home/kyle"}"#,
|
||||
"naïve café 日本語 🙂",
|
||||
"a backslash \\ and a quote \" stay as they are",
|
||||
] {
|
||||
assert_eq!(escape_json_text(text), text);
|
||||
assert_eq!(escape_model_text(text), text);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_text_escapes_newline_and_tab_but_model_text_keeps_them() {
|
||||
let text = "one\n\ttwo\r\n";
|
||||
assert_eq!(
|
||||
escape_json_text(text),
|
||||
format!("one{}{}two{}{}", esc(0x0a), esc(0x09), esc(0x0d), esc(0x0a))
|
||||
);
|
||||
assert_eq!(
|
||||
escape_model_text(text),
|
||||
format!("one\n\ttwo{}\n", esc(0x0d)),
|
||||
"only newline and tab pass; a carriage return could overwrite the line"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn model_text_escapes_everything_else_the_same_way() {
|
||||
for (lo, hi) in HIDDEN {
|
||||
for code in lo..=hi {
|
||||
if code == 0x0a || code == 0x09 {
|
||||
continue;
|
||||
}
|
||||
assert_eq!(
|
||||
escape_model_text(&ch(code).to_string()),
|
||||
esc(code),
|
||||
"U+{code:04X}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -H\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,6 @@
|
||||
{"seq":5,"time":"2026-09-18T09:00:00.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"decision","session":"chat-1","call":3,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":18446744073709551615,"time":"2026-09-18T09:00:01.000Z","prev":"451896c692a37d00ea44b95b379c71850f2b6577bbff878591de47df832c9f44","event":{"type":"decision","session":"chat-1","call":4,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-18T09:00:02.000Z","prev":"4d04826fa2d19247131f45cb87d242099fa735f77c281e6e07656581abcda2ed","event":{"type":"decision","session":"chat-1","call":5,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T09:00:03.000Z","prev":"17438f107a2e7bc3dafba3beae1f610155b76ccf87db534aad0841ba5c02c238","event":{"type":"decision","session":"chat-1","call":6,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:00:04.000Z","prev":"62eb4f6a64789e9401df1009485910d0791ed6ea1f57c172b95c6d58eb2f7c15","event":{"type":"approval","session":"chat-1","call":6,"decision":8,"answer":"refused","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":10,"time":"2026-09-18T09:30:00.000Z","prev":"526e63bfcbab56505a872d14ca9a600936ca00a0156160fc46c428986883cb68","event":{"type":"accepted_break","file":"2026-09-17.jsonl","line":4,"last_good":"6707895855712d119b54e67d86f46331122e0f82450a5481c2d770fa7f16f021"}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -H\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,7 @@
|
||||
{"seq":5,"time":"2026-09-18T09:00:00.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"decision","session":"chat-1","call":3,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":6,"time":"2026-09-18T09:00:01.000Z","prev":"451896c692a37d00ea44b95b379c71850f2b6577bbff878591de47df832c9f44","event":{"type":"decision","session":"chat-1","call":4,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-18T09:00:02.000Z","prev":"4d04826fa2d19247131f45cb87d242099fa735f77c281e6e07656581abcda2ed","event":{"type":"decision","session":"chat-1","call":5,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T09:00:03.000Z","prev":"17438f107a2e7bc3dafba3beae1f610155b76ccf87db534aad0841ba5c02c238","event":{"type":"decision","session":"chat-1","call":6,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:00:04.000Z","prev":"62eb4f6a64789e9401df1009485910d0791ed6ea1f57c172b95c6d58eb2f7c15","event":{"type":"approval","session":"chat-1","call":6,"decision":8,"answer":"refused","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":10,"time":"2026-09-18T09:30:00.000Z","prev":"526e63bfcbab56505a872d14ca9a600936ca00a0156160fc46c428986883cb68","event":{"type":"accepted_break","file":"2026-09-17.jsonl","line":4,"last_good":"6707895855712d119b54e67d86f46331122e0f82450a5481c2d770fa7f16f021"}}
|
||||
{"seq":11,"time":"2026-09-18T09:30:01.000Z","prev":"17662d4b56809a03a432c30d037904fcab478ed8b3d9fa8a1f515b7dbe0a7837","event":{"type":"decision","session":"chat-1","call":9,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -H\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
{"seq":5,"time":"2026-09-18T09:00:00.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"decision","session":"chat-1","call":3,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-18T09:00:02.000Z","prev":"4d04826fa2d19247131f45cb87d242099fa735f77c281e6e07656581abcda2ed","event":{"type":"decision","session":"chat-1","call":5,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T09:00:03.000Z","prev":"17438f107a2e7bc3dafba3beae1f610155b76ccf87db534aad0841ba5c02c238","event":{"type":"decision","session":"chat-1","call":6,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:00:04.000Z","prev":"62eb4f6a64789e9401df1009485910d0791ed6ea1f57c172b95c6d58eb2f7c15","event":{"type":"approval","session":"chat-1","call":6,"decision":8,"answer":"refused","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:30:00.000Z","prev":"526e63bfcbab56505a872d14ca9a600936ca00a0156160fc46c428986883cb68","event":{"type":"accepted_break","file":"2026-09-17.jsonl","line":4,"last_good":"6707895855712d119b54e67d86f46331122e0f82450a5481c2d770fa7f16f021"}}
|
||||
@@ -0,0 +1,7 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -H\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":5,"time":"2026-09-17T08:30:00.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"accepted_break","file":"2026-09-17.jsonl","line":4,"last_good":"6707895855712d119b54e67d86f46331122e0f82450a5481c2d770fa7f16f021"}}
|
||||
{"seq":6,"time":"2026-09-17T08:30:01.000Z","prev":"1d166141aa286ccb2b76e4c5b640a397a8af3c7fef0623a145182c812bacc114","event":{"type":"decision","session":"chat-1","call":9,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,6 @@
|
||||
{"seq":5,"time":"2026-09-18T09:00:00.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"decision","session":"chat-1","call":3,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":6,"time":"2026-09-18T09:00:01.000Z","prev":"451896c692a37d00ea44b95b379c71850f2b6577bbff878591de47df832c9f44","event":{"type":"decision","session":"chat-1","call":4,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-18T09:00:02.000Z","prev":"4d04826fa2d19247131f45cb87d242099fa735f77c281e6e07656581abcda2ed","event":{"type":"decision","session":"chat-1","call":5,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T09:00:03.000Z","prev":"17438f107a2e7bc3dafba3beae1f610155b76ccf87db534aad0841ba5c02c238","event":{"type":"decision","session":"chat-1","call":6,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:00:04.000Z","prev":"62eb4f6a64789e9401df1009485910d0791ed6ea1f57c172b95c6d58eb2f7c15","event":{"type":"approval","session":"chat-1","call":6,"decision":8,"answer":"refused","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":10,"time":"2026-09-18T09:30:00.000Z","prev":"526e63bfcbab56505a872d14ca9a600936ca00a0156160fc46c428986883cb68","event":{"type":"accepted_break","file":"2026-09-18.jsonl","line":5,"last_good":"0000000000000000000000000000000000000000000000000000000000000000"}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -H\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,6 @@
|
||||
{"seq":5,"time":"2026-09-18T09:00:00.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"decision","session":"chat-1","call":3,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":6,"time":"2026-09-18T09:00:01.000Z","prev":"451896c692a37d00ea44b95b379c71850f2b6577bbff878591de47df832c9f44","event":{"type":"decision","session":"chat-1","call":4,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-18T09:00:02.000Z","prev":"4d04826fa2d19247131f45cb87d242099fa735f77c281e6e07656581abcda2ed","event":{"type":"decision","session":"chat-1","call":5,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T09:00:03.000Z","prev":"17438f107a2e7bc3dafba3beae1f610155b76ccf87db534aad0841ba5c02c238","event":{"type":"decision","session":"chat-1","call":6,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:00:04.000Z","prev":"62eb4f6a64789e9401df1009485910d0791ed6ea1f57c172b95c6d58eb2f7c15","event":{"type":"approval","session":"chat-1","call":6,"decision":8,"answer":"refused","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":10,"time":"2026-09-18T09:30:00.000Z","prev":"526e63bfcbab56505a872d14ca9a600936ca00a0156160fc46c428986883cb68","event":{"type":"accepted_break","file":"2026-09-17.jsonl","line":4,"last_good":"0000000000000000000000000000000000000000000000000000000000000000"}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -H\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,6 @@
|
||||
{"seq":5,"time":"2026-09-18T09:00:00.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"decision","session":"chat-1","call":3,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":6,"time":"2026-09-18T09:00:01.000Z","prev":"451896c692a37d00ea44b95b379c71850f2b6577bbff878591de47df832c9f44","event":{"type":"decision","session":"chat-1","call":4,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-18T09:00:02.000Z","prev":"4d04826fa2d19247131f45cb87d242099fa735f77c281e6e07656581abcda2ed","event":{"type":"decision","session":"chat-1","call":5,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T09:00:03.000Z","prev":"17438f107a2e7bc3dafba3beae1f610155b76ccf87db534aad0841ba5c02c238","event":{"type":"decision","session":"chat-1","call":6,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:00:04.000Z","prev":"62eb4f6a64789e9401df1009485910d0791ed6ea1f57c172b95c6d58eb2f7c15","event":{"type":"approval","session":"chat-1","call":6,"decision":8,"answer":"refused","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":10,"time":"2026-09-18T09:30:00.000Z","prev":"526e63bfcbab56505a872d14ca9a600936ca00a0156160fc46c428986883cb68","event":{"type":"accepted_break","file":"2026-09-17.jsonl","line":3,"last_good":"6707895855712d119b54e67d86f46331122e0f82450a5481c2d770fa7f16f021"}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -H\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,7 @@
|
||||
{"seq":5,"time":"2026-09-18T09:00:00.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"decision","session":"chat-1","call":3,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":6,"time":"2026-09-18T09:00:01.000Z","prev":"451896c692a37d00ea44b95b379c71850f2b6577bbff878591de47df832c9f44","event":{"type":"decision","session":"chat-1","call":4,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-18T09:00:02.000Z","prev":"4d04826fa2d19247131f45cb87d242099fa735f77c281e6e07656581abcda2ed","event":{"type":"decision","session":"chat-1","call":5,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T09:00:03.000Z","prev":"17438f107a2e7bc3dafba3beae1f610155b76ccf87db534aad0841ba5c02c238","event":{"type":"decision","session":"chat-1","call":6,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:00:04.000Z","prev":"62eb4f6a64789e9401df1009485910d0791ed6ea1f57c172b95c6d58eb2f7c15","event":{"type":"approval","session":"chat-1","call":6,"decision":8,"answer":"refused","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{}
|
||||
{"seq":11,"time":"2026-09-18T09:30:00.000Z","prev":"526e63bfcbab56505a872d14ca9a600936ca00a0156160fc46c428986883cb68","event":{"type":"accepted_break","file":"2026-09-17.jsonl","line":4,"last_good":"6707895855712d119b54e67d86f46331122e0f82450a5481c2d770fa7f16f021"}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -H\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,6 @@
|
||||
{"seq":5,"time":"2026-09-18T09:00:00.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"decision","session":"chat-1","call":3,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":6,"time":"2026-09-18T09:00:01.000Z","prev":"451896c692a37d00ea44b95b379c71850f2b6577bbff878591de47df832c9f44","event":{"type":"decision","session":"chat-1","call":4,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-18T09:00:02.000Z","prev":"4d04826fa2d19247131f45cb87d242099fa735f77c281e6e07656581abcda2ed","event":{"type":"decision","session":"chat-1","call":5,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T09:00:03.000Z","prev":"17438f107a2e7bc3dafba3beae1f610155b76ccf87db534aad0841ba5c02c238","event":{"type":"decision","session":"chat-1","call":6,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:00:04.000Z","prev":"62eb4f6a64789e9401df1009485910d0791ed6ea1f57c172b95c6d58eb2f7c15","event":{"type":"approval","session":"chat-1","call":6,"decision":8,"answer":"refused","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":11,"time":"2026-09-18T09:30:00.000Z","prev":"526e63bfcbab56505a872d14ca9a600936ca00a0156160fc46c428986883cb68","event":{"type":"accepted_break","file":"2026-09-17.jsonl","line":4,"last_good":"6707895855712d119b54e67d86f46331122e0f82450a5481c2d770fa7f16f021"}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -H\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":5,"time":"2026-09-18T09:00:00.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"decision","session":"chat-1","call":3,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":6,"time":"2026-09-18T09:00:01.000Z","prev":"451896c692a37d00ea44b95b379c71850f2b6577bbff878591de47df832c9f44","event":{"type":"decision","session":"chat-1","call":4,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-18T09:00:02.000Z","prev":"4d04826fa2d19247131f45cb87d242099fa735f77c281e6e07656581abcda2ed","event":{"type":"decision","session":"chat-1","call":5,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T09:00:03.000Z","prev":"17438f107a2e7bc3dafba3beae1f610155b76ccf87db534aad0841ba5c02c238","event":{"type":"decision","session":"chat-1","call":6,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:00:04.000Z","prev":"62eb4f6a64789e9401df1009485910d0791ed6ea1f57c172b95c6d58eb2f7c15","event":{"type":"approval","session":"chat-1","call":6,"decision":8,"answer":"refused","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,6 @@
|
||||
{"seq":5,"time":"2026-09-18T09:00:00.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"decision","session":"chat-1","call":3,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":6,"time":"2026-09-18T09:00:01.000Z","prev":"451896c692a37d00ea44b95b379c71850f2b6577bbff878591de47df832c9f44","event":{"type":"decision","session":"chat-1","call":4,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-18T09:00:02.000Z","prev":"4d04826fa2d19247131f45cb87d242099fa735f77c281e6e07656581abcda2ed","event":{"type":"decision","session":"chat-1","call":5,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T09:00:03.000Z","prev":"17438f107a2e7bc3dafba3beae1f610155b76ccf87db534aad0841ba5c02c238","event":{"type":"decision","session":"chat-1","call":6,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:00:04.000Z","prev":"62eb4f6a64789e9401df1009485910d0791ed6ea1f57c172b95c6d58eb2f7c15","event":{"type":"approval","session":"chat-1","call":6,"decision":8,"answer":"refused","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":10,"time":"2026-09-17T23:59:58.000Z","prev":"526e63bfcbab56505a872d14ca9a600936ca00a0156160fc46c428986883cb68","event":{"type":"result","session":"chat-1","call":5,"decision":7,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":5,"time":"2026-09-18T09:00:00.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"decision","session":"chat-1","call":3,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":6,"time":"2026-09-18T09:00:01.000Z","prev":"451896c692a37d00ea44b95b379c71850f2b6577bbff878591de47df832c9f44","event":{"type":"decision","session":"chat-1","call":4,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-18T09:00:02.000Z","prev":"4d04826fa2d19247131f45cb87d242099fa735f77c281e6e07656581abcda2ed","event":{"type":"decision","session":"chat-1","call":5,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T09:00:03.000Z","prev":"17438f107a2e7bc3dafba3beae1f610155b76ccf87db534aad0841ba5c02c238","event":{"type":"decision","session":"chat-1","call":6,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:00:04.000Z","prev":"62eb4f6a64789e9401df1009485910d0791ed6ea1f57c172b95c6d58eb2f7c15","event":{"type":"approval","session":"chat-1","call":6,"decision":8,"answer":"refused","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
@@ -0,0 +1,4 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":5,"time":"2026-09-18T09:00:00.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"decision","session":"chat-1","call":3,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":6,"time":"2026-09-18T09:00:01.000Z","prev":"451896c692a37d00ea44b95b379c71850f2b6577bbff878591de47df832c9f44","event":{"type":"decision","session":"chat-1","call":4,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-18T09:00:02.000Z","prev":"4d04826fa2d19247131f45cb87d242099fa735f77c281e6e07656581abcda2ed","event":{"type":"decision","session":"chat-1","call":5,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T09:00:03.000Z","prev":"17438f107a2e7bc3dafba3beae1f610155b76ccf87db534aad0841ba5c02c238","event":{"type":"decision","session":"chat-1","call":6,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:00:04.000Z","prev":"62eb4f6a64789e9401df1009485910d0791ed6ea1f57c172b95c6d58eb2f7c15","event":{"type":"approval","session":"chat-1","call":6,"decision":8,"answer":"refused","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":5,"time":"2026-09-18T09:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":3,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":6,"time":"2026-09-18T09:00:01.000Z","prev":"d5bb0822b4a83babe54392edcdc0b895e9919e118ca0d9c42da179be8a7719a8","event":{"type":"decision","session":"chat-1","call":4,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-18T09:00:02.000Z","prev":"ee18e126c0bb8dfa719c7c2b0a94d26019017a4c7e983b61668ed81cc135cb94","event":{"type":"decision","session":"chat-1","call":5,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T09:00:03.000Z","prev":"cfccb0f03849b11c17a2a71a0281583052cbfb96cd5b9eb184145142e35db056","event":{"type":"decision","session":"chat-1","call":6,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:00:04.000Z","prev":"56ca7170d34f57b158991a3f4798c795fa5d147f8ca44a858197cd94c9c6f14c","event":{"type":"approval","session":"chat-1","call":6,"decision":8,"answer":"refused","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":5,"time":"2026-09-18T09:00:00.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"decision","session":"chat-1","call":3,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":6,"time":"2026-09-18T09:00:01.000Z","prev":"451896c692a37d00ea44b95b379c71850f2b6577bbff878591de47df832c9f44","event":{"type":"decision","session":"chat-1","call":4,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-18T09:00:02.000Z","prev":"4d04826fa2d19247131f45cb87d242099fa735f77c281e6e07656581abcda2ed","event":{"type":"decision","session":"chat-1","call":5,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T09:00:03.000Z","prev":"17438f107a2e7bc3dafba3beae1f610155b76ccf87db534aad0841ba5c02c238","event":{"type":"decision","session":"chat-1","call":6,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:00:04.000Z","prev":"62eb4f6a64789e9401df1009485910d0791ed6ea1f57c172b95c6d58eb2f7c15","event":{"type":"approval","session":"chat-1","call":6,"decision":8,"answer":"refused","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,8 @@
|
||||
{"seq":5,"time":"2026-09-18T09:00:00.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"decision","session":"chat-1","call":3,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":6,"time":"2026-09-18T09:00:01.000Z","prev":"451896c692a37d00ea44b95b379c71850f2b6577bbff878591de47df832c9f44","event":{"type":"decision","session":"chat-1","call":4,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-18T09:00:02.000Z","prev":"4d04826fa2d19247131f45cb87d242099fa735f77c281e6e07656581abcda2ed","event":{"type":"decision","session":"chat-1","call":5,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T09:00:03.000Z","prev":"17438f107a2e7bc3dafba3beae1f610155b76ccf87db534aad0841ba5c02c238","event":{"type":"decision","session":"chat-1","call":6,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:00:04.000Z","prev":"62eb4f6a64789e9401df1009485910d0791ed6ea1f57c172b95c6d58eb2f7c15","event":{"type":"approval","session":"chat-1","call":6,"decision":8,"answer":"refused","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":10,"time":"2026-09-18T09:00:05.000Z","prev":"526e63bfcbab56505a872d14ca9a600936ca00a0156160fc46c428986883cb68","event":{"type":"result","session":"chat-1","call":5,"decision":7,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":10,"time":"2026-09-18T09:10:00.000Z","prev":"526e63bfcbab56505a872d14ca9a600936ca00a0156160fc46c428986883cb68","event":{"type":"recovery","torn_bytes":368,"torn_sha256":"6627168a838f2beffce7ca0be64c0be17ecaec1184d9915c3c9ae046eeb9fe8c"}}
|
||||
{"seq":11,"time":"2026-09-18T09:10:01.000Z","prev":"20a555911cfdfa0c514ec07c4162733e64b5ebc26e8bd660804fd1c9a9638c6c","event":{"type":"result","session":"chat-1","call":5,"decision":7,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,2 @@
|
||||
{"seq":10,"time":"2026-09-18T09:00:05.000Z","prev":"526e63bfcbab56505a
|
||||
{"seq":5,"time":"2026-09-18T09:10:00.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"recovery","torn_bytes":70,"torn_sha256":"bec17a53cfdde80790a9101d04a5423848b61250cccb4bb02efdbc2f30197d26"}}
|
||||
@@ -0,0 +1,7 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":5,"time":"2026-09-17T23:59:59.000Z","prev":"29ef9d1d28442c8615bf8db10598d
|
||||
{"seq":5,"time":"2026-09-18T00:00:30.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"recovery","torn_bytes":80,"torn_sha256":"d6aa54d6db80b2944686ee4317dea6b2519a98c994ea383ac4fc415fc5472aa8"}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":6,"time":"2026-09-18T09:00:00.000Z","prev":"be4b24b8b8b219dc38c3b73ce43e9a431d5c5e51d740a8937ec155d71cc8c212","event":{"type":"decision","session":"chat-1","call":3,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-18T09:00:01.000Z","prev":"3da9d8ea7c71c912ec7d0b2faa6709df1148d49290268264d1da72d06ae56b2f","event":{"type":"decision","session":"chat-1","call":4,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T09:00:02.000Z","prev":"d9b8896a97cac08380f305c24e5ee7ad25268c7a338d05711dcba98a2fd41343","event":{"type":"decision","session":"chat-1","call":5,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:00:03.000Z","prev":"bf6a9a326fcde5060568de8497806fe22239715877ff432e8d87a80f55d30a54","event":{"type":"decision","session":"chat-1","call":6,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":10,"time":"2026-09-18T09:00:04.000Z","prev":"e7bb179edf34ebff28d7f9efb6d11ae481975119273c6664ea71f82197812590","event":{"type":"approval","session":"chat-1","call":6,"decision":9,"answer":"refused","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,8 @@
|
||||
{"seq":5,"time":"2026-09-18T09:00:00.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"decision","session":"chat-1","call":3,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":6,"time":"2026-09-18T09:00:01.000Z","prev":"451896c692a37d00ea44b95b379c71850f2b6577bbff878591de47df832c9f44","event":{"type":"decision","session":"chat-1","call":4,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-18T09:00:02.000Z","prev":"4d04826fa2d19247131f45cb87d242099fa735f77c281e6e07656581abcda2ed","event":{"type":"decision","session":"chat-1","call":5,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T09:00:03.000Z","prev":"17438f107a2e7bc3dafba3beae1f610155b76ccf87db534aad0841ba5c02c238","event":{"type":"decision","session":"chat-1","call":6,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:00:04.000Z","prev":"62eb4f6a64789e9401df1009485910d0791ed6ea1f57c172b95c6d58eb2f7c15","event":{"type":"approval","session":"chat-1","call":6,"decision":8,"answer":"refused","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":10,"time":"2026-09-18T09:00:05.000Z","prev":"526e63bfcbab56505a
|
||||
{"seq":10,"time":"2026-09-18T09:10:00.000Z","prev":"526e63bfcbab56505a872d14ca9a600936ca00a0156160fc46c428986883cb68","event":{"type":"recovery","torn_bytes":70,"torn_sha256":"bec17a53cfdde80790a9101d04a5423848b61250cccb4bb02efdbc2f30197d26"}}
|
||||
{"seq":11,"time":"2026-09-18T09:10:01.000Z","prev":"68aa294e6a5a2f3925e4c2f0d4094ef047591295d17185ea2c8ec6a709530414","event":{"type":"result","session":"chat-1","call":5,"decision":7,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{"seq":5,"time":"2026-09-18T09:00:00.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"decision","session":"chat-1","call":3,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":6,"time":"2026-09-18T09:00:01.000Z","prev":"451896c692a37d00ea44b95b379c71850f2b6577bbff878591de47df832c9f44","event":{"type":"decision","session":"chat-1","call":4,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-18T09:00:02.000Z","prev":"4d04826fa2d19247131f45cb87d242099fa735f77c281e6e07656581abcda2ed","event":{"type":"decision","session":"chat-1","call":5,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T09:00:03.000Z","prev":"17438f107a2e7bc3dafba3beae1f610155b76ccf87db534aad0841ba5c02c238","event":{"type":"decision","session":"chat-1","call":6,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:00:04.000Z","prev":"62eb4f6a64789e9401df1009485910d0791ed6ea1f57c172b95c6d58eb2f7c15","event":{"type":"approval","session":"chat-1","call":6,"decision":8,"answer":"refused","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":10,"time":"2026-09-18T09:10:00.000Z","prev":"526e63bfcbab56505a872d14ca9a600936ca00a0156160fc46c428986883cb68","event":{"type":"recovery","torn_bytes":70,"torn_sha256":"bec17a53cfdde80790a9101d04a5423848b61250cccb4bb02efdbc2f30197d26"}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,7 @@
|
||||
{"seq":5,"time":"2026-09-18T09:00:00.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"decision","session":"chat-1","call":3,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":6,"time":"2026-09-18T09:00:01.000Z","prev":"451896c692a37d00ea44b95b379c71850f2b6577bbff878591de47df832c9f44","event":{"type":"decision","session":"chat-1","call":4,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-18T09:00:02.000Z","prev":"4d04826fa2d19247131f45cb87d242099fa735f77c281e6e07656581abcda2ed","event":{"type":"decision","session":"chat-1","call":5,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T09:00:03.000Z","prev":"17438f107a2e7bc3dafba3beae1f610155b76ccf87db534aad0841ba5c02c238","event":{"type":"decision","session":"chat-1","call":6,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:00:04.000Z","prev":"62eb4f6a64789e9401df1009485910d0791ed6ea1f57c172b95c6d58eb2f7c15","event":{"type":"approval","session":"chat-1","call":6,"decision":8,"answer":"refused","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":10,"time":"2026-09-18T09:00:05.000Z","prev":"526e63bfcbab56505a
|
||||
{"seq":10,"time":"2026-09-18T09:10:00.000Z","prev":"526e63bfcbab56505a872d14ca9a600936ca00a0156160fc46c428986883cb68","event":{"type":"recovery","torn_bytes":14,"torn_sha256":"f41f3fa625ff120ddca7ef456bf66371ecea23c129f4e4c32367101edb516cf8"}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,7 @@
|
||||
{"seq":5,"time":"2026-09-18T09:00:00.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"decision","session":"chat-1","call":3,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":6,"time":"2026-09-18T09:00:01.000Z","prev":"451896c692a37d00ea44b95b379c71850f2b6577bbff878591de47df832c9f44","event":{"type":"decision","session":"chat-1","call":4,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-18T09:00:02.000Z","prev":"4d04826fa2d19247131f45cb87d242099fa735f77c281e6e07656581abcda2ed","event":{"type":"decision","session":"chat-1","call":5,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T09:00:03.000Z","prev":"17438f107a2e7bc3dafba3beae1f610155b76ccf87db534aad0841ba5c02c238","event":{"type":"decision","session":"chat-1","call":6,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:00:04.000Z","prev":"62eb4f6a64789e9401df1009485910d0791ed6ea1f57c172b95c6d58eb2f7c15","event":{"type":"approval","session":"chat-1","call":6,"decision":8,"answer":"refused","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":10,"time":"2026-09-18T09:00:05.000Z","prev":"526e63bfcbab56505a
|
||||
{"seq":10,"time":"2026-09-18T09:10:00.000Z","prev":"526e63bfcbab56505a872d14ca9a600936ca00a0156160fc46c428986883cb68","event":{"type":"recovery","torn_bytes":71,"torn_sha256":"bec17a53cfdde80790a9101d04a5423848b61250cccb4bb02efdbc2f30197d26"}}
|
||||
@@ -0,0 +1,3 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":5,"time":"2026-09-18T09:00:00.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"decision","session":"chat-1","call":3,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":6,"time":"2026-09-18T09:00:01.000Z","prev":"451896c692a37d00ea44b95b379c71850f2b6577bbff878591de47df832c9f44","event":{"type":"decision","session":"chat-1","call":4,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-18T09:00:02.000Z","prev":"4d04826fa2d19247131f45cb87d242099fa735f77c281e6e07656581abcda2ed","event":{"type":"decision","session":"chat-1","call":5,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T09:00:03.000Z","prev":"17438f107a2e7bc3dafba3beae1f610155b76ccf87db534aad0841ba5c02c238","event":{"type":"decision","session":"chat-1","call":6,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:00:04.000Z","prev":"62eb4f6a64789e9401df1009485910d0791ed6ea1f57c172b95c6d58eb2f7c15","event":{"type":"approval","session":"chat-1","call":6,"decision":8,"answer":"refused","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"seq":10,"time":"2026-09-18T09:00:05.000Z","prev":"526e63bfcbab56505a
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,7 @@
|
||||
{"seq":5,"time":"2026-09-18T09:00:00.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"decision","session":"chat-1","call":3,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":6,"time":"2026-09-18T09:00:01.000Z","prev":"451896c692a37d00ea44b95b379c71850f2b6577bbff878591de47df832c9f44","event":{"type":"decision","session":"chat-1","call":4,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-18T09:00:02.000Z","prev":"4d04826fa2d19247131f45cb87d242099fa735f77c281e6e07656581abcda2ed","event":{"type":"decision","session":"chat-1","call":5,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T09:00:03.000Z","prev":"17438f107a2e7bc3dafba3beae1f610155b76ccf87db534aad0841ba5c02c238","event":{"type":"decision","session":"chat-1","call":6,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:00:04.000Z","prev":"62eb4f6a64789e9401df1009485910d0791ed6ea1f57c172b95c6d58eb2f7c15","event":{"type":"approval","session":"chat-1","call":6,"decision":8,"answer":"refused","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":10,"time":"2026-09-18T09:00:05.000Z","prev":"526e63bfcbab56505a
|
||||
{"seq":10,"time":"2026-09-18T09:10:00.000Z","prev"
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,6 @@
|
||||
{"seq":5,"time":"2026-09-18T09:00:00.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"decision","session":"chat-1","call":3,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":6,"time":"2026-09-18T09:00:01.000Z","prev":"451896c692a37d00ea44b95b379c71850f2b6577bbff878591de47df832c9f44","event":{"type":"decision","session":"chat-1","call":4,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-18T09:00:02.000Z","prev":"4d04826fa2d19247131f45cb87d242099fa735f77c281e6e07656581abcda2ed","event":{"type":"decision","session":"chat-1","call":5,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T09:00:03.000Z","prev":"17438f107a2e7bc3dafba3beae1f610155b76ccf87db534aad0841ba5c02c238","event":{"type":"decision","session":"chat-1","call":6,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:00:04.000Z","prev":"62eb4f6a64789e9401df1009485910d0791ed6ea1f57c172b95c6d58eb2f7c15","event":{"type":"approval","session":"chat-1","call":6,"decision":8,"answer":"refused","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":10,"time":"2026-09-18T09:00:05.000Z","prev":"526e63bfcbab56505a872d14ca9a600936ca00a0156160fc46c428986883cb68","event":{"type":"result","session":"chat-1","call":5,"decision":7,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,6 @@
|
||||
{"seq":5,"time":"2026-09-18T09:00:00.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"decision","session":"chat-1","call":3,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":6,"time":"2026-09-18T09:00:01.000Z","prev":"451896c692a37d00ea44b95b379c71850f2b6577bbff878591de47df832c9f44","event":{"type":"decision","session":"chat-1","call":4,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-18T09:00:02.000Z","prev":"4d04826fa2d19247131f45cb87d242099fa735f77c281e6e07656581abcda2ed","event":{"type":"decision","session":"chat-1","call":5,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T09:00:03.000Z","prev":"17438f107a2e7bc3dafba3beae1f610155b76ccf87db534aad0841ba5c02c238","event":{"type":"decision","session":"chat-1","call":6,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:00:04.000Z","prev":"62eb4f6a64789e9401df1009485910d0791ed6ea1f57c172b95c6d58eb2f7c15","event":{"type":"approval","session":"chat-1","call":6,"decision":8,"answer":"refused","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":10,"time":"2026-09-18T09:00:05.000Z","prev":"526e63bfcbab56505a
|
||||
@@ -0,0 +1,5 @@
|
||||
{"seq":0,"time":"2026-09-17T08:00:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"chat-1","call":1,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:00:01.000Z","prev":"d7a63875b77829169f2c9f1bfb54cf00cc11a7efdcc34fa3a3ea1e889ea38eaa","event":{"type":"result","session":"chat-1","call":1,"decision":0,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
{"seq":2,"time":"2026-09-17T08:00:02.000Z","prev":"dc8b744a6123f275332ab7b538100b9acdd1e4a9e1cc03a47f126700c21aa4ea","event":{"type":"decision","session":"chat-1","call":2,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":3,"time":"2026-09-17T08:00:03.000Z","prev":"5125cad73872977d597bb5bae5eda8c7f41fa34eb7f35bb3a3c6d2e34ffe9483","event":{"type":"approval","session":"chat-1","call":2,"decision":2,"answer":"approved","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"allowed"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":4,"time":"2026-09-17T08:00:04.000Z","prev":"e37ad0c18430235141ca1b8242653066983995e239d78cfdea4678111a2d0987","event":{"type":"result","session":"chat-1","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":25,"sha256":"ed52694265ada1850bdf1a90f9c6b6e1ed9e45fbca44fbe9114639ada3dc8051","taint_after":"private"}}
|
||||
@@ -0,0 +1,6 @@
|
||||
{"seq":5,"time":"2026-09-18T09:00:00.000Z","prev":"29ef9d1d28442c8615bf8db10598d38eecc589e469ee14a54085038e2aad9563","event":{"type":"decision","session":"chat-1","call":3,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":6,"time":"2026-09-18T09:00:01.000Z","prev":"451896c692a37d00ea44b95b379c71850f2b6577bbff878591de47df832c9f44","event":{"type":"decision","session":"chat-1","call":4,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-18T09:00:02.000Z","prev":"4d04826fa2d19247131f45cb87d242099fa735f77c281e6e07656581abcda2ed","event":{"type":"decision","session":"chat-1","call":5,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"allowed"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T09:00:03.000Z","prev":"17438f107a2e7bc3dafba3beae1f610155b76ccf87db534aad0841ba5c02c238","event":{"type":"decision","session":"chat-1","call":6,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-scratch","grant_sha256":"3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020","taint":"private","untrusted":false}}
|
||||
{"seq":9,"time":"2026-09-18T09:00:04.000Z","prev":"62eb4f6a64789e9401df1009485910d0791ed6ea1f57c172b95c6d58eb2f7c15","event":{"type":"approval","session":"chat-1","call":6,"decision":8,"answer":"refused","by":"bxctl","post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":10,"time":"2026-09-18T09:00:05.000Z","prev":"526e63bfcbab56505a
|
||||
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
tool = "read_file"
|
||||
mode = "always"
|
||||
max_taint = "private"
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
tool = "http_fetch"
|
||||
mode = "ask"
|
||||
max_taint = "secret"
|
||||
result_class = "public"
|
||||
untrusted = false
|
||||
expires = "2026-12-31T00:00:00.000Z"
|
||||
secret = "example-api-token"
|
||||
|
||||
[constraints]
|
||||
paths = ["/home/kyle/notes/**"]
|
||||
hosts = ["example.com", "api.example.com"]
|
||||
patterns = ["^GET "]
|
||||
@@ -0,0 +1,3 @@
|
||||
tool = "read_file"
|
||||
mode = "auto"
|
||||
max_taint = "private"
|
||||
@@ -0,0 +1,2 @@
|
||||
tool = "read_file"
|
||||
mode = "auto"
|
||||
@@ -0,0 +1,6 @@
|
||||
tool = "read_file"
|
||||
mode = "auto"
|
||||
max_taint = "private"
|
||||
|
||||
[constraints]
|
||||
path = ["/etc/**"]
|
||||
@@ -0,0 +1,4 @@
|
||||
tool = "read_file"
|
||||
mode = "auto"
|
||||
max_taint = "private"
|
||||
max_tiant = "secret"
|
||||
@@ -0,0 +1,10 @@
|
||||
{"seq":0,"time":"2026-09-17T08:05:00.000Z","prev":"0000000000000000000000000000000000000000000000000000000000000000","event":{"type":"decision","session":"mm-thread-42","call":1,"tool":"read_file","arguments":"{\"path\":\"/etc/hosts\"}","outcome":{"outcome":"allowed"},"grant":"read-etc","grant_sha256":"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","taint":"private","untrusted":false}}
|
||||
{"seq":1,"time":"2026-09-17T08:05:00.500Z","prev":"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","event":{"type":"result","session":"mm-thread-42","call":1,"decision":0,"status":"result","class":"secret","untrusted":true,"truncated":true,"bytes":65536,"sha256":"ffeeddccbbaa99887766554433221100ffeeddccbbaa99887766554433221100","taint_after":"secret"}}
|
||||
{"seq":2,"time":"2026-09-17T08:05:01.250Z","prev":"ffeeddccbbaa99887766554433221100ffeeddccbbaa99887766554433221100","event":{"type":"decision","session":"mm-thread-42","call":2,"tool":"shell","arguments":"{\"command\":\"df -h\"}","outcome":{"outcome":"ask"},"grant":"shell-ask","grant_sha256":"ffeeddccbbaa99887766554433221100ffeeddccbbaa99887766554433221100","taint":"secret","untrusted":true}}
|
||||
{"seq":3,"time":"2026-09-17T08:06:00.000Z","prev":"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","event":{"type":"approval","session":"mm-thread-42","call":2,"decision":2,"answer":"approved","by":"u8f3k2","post":"p9x7","reason":null,"outcome":{"outcome":"allowed"},"grant":"shell-auto","grant_sha256":"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","taint":"secret","untrusted":true}}
|
||||
{"seq":4,"time":"2026-09-17T08:06:00.100Z","prev":"ffeeddccbbaa99887766554433221100ffeeddccbbaa99887766554433221100","event":{"type":"result","session":"mm-thread-42","call":2,"decision":2,"status":"failed","class":"private","untrusted":false,"truncated":false,"bytes":27,"sha256":"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","taint_after":"secret"}}
|
||||
{"seq":5,"time":"2026-09-17T08:07:00.000Z","prev":"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","event":{"type":"decision","session":"cron-morning","call":1,"tool":"rm_rf","arguments":"{}","outcome":{"outcome":"denied","reason":"no_grant"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":6,"time":"2026-09-17T08:08:00.000Z","prev":"ffeeddccbbaa99887766554433221100ffeeddccbbaa99887766554433221100","event":{"type":"approval","session":"cron-morning","call":3,"decision":4,"answer":"refused","by":"bxctl","post":null,"reason":"not \"now\"","outcome":{"outcome":"denied","reason":"approval_refused"},"grant":null,"grant_sha256":null,"taint":"private","untrusted":false}}
|
||||
{"seq":7,"time":"2026-09-17T08:23:00.000Z","prev":"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","event":{"type":"approval","session":"cron-morning","call":4,"decision":5,"answer":"expired","by":null,"post":null,"reason":null,"outcome":{"outcome":"denied","reason":"approval_expired"},"grant":null,"grant_sha256":null,"taint":"public","untrusted":false}}
|
||||
{"seq":8,"time":"2026-09-18T00:00:00.000Z","prev":"ffeeddccbbaa99887766554433221100ffeeddccbbaa99887766554433221100","event":{"type":"recovery","torn_bytes":117,"torn_sha256":"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}}
|
||||
{"seq":9,"time":"2026-09-18T00:00:01.000Z","prev":"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","event":{"type":"accepted_break","file":"2026-09-17.jsonl","line":7,"last_good":"ffeeddccbbaa99887766554433221100ffeeddccbbaa99887766554433221100"}}
|
||||
@@ -0,0 +1,7 @@
|
||||
{"type":"session_start","time":"2026-09-17T08:05:00.000Z","session":"mm-thread-42","epoch":0,"slot":0,"baseline":"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}
|
||||
{"type":"user","time":"2026-09-17T08:05:01.000Z","content":"What is in /etc/hosts?"}
|
||||
{"type":"assistant","time":"2026-09-17T08:05:03.000Z","content":null,"reasoning_content":"The user wants a file.\nI will read it.","tool_calls":[{"id":"call_a1","name":"read_file","arguments":"{\"path\":\"/etc/hosts\"}"}]}
|
||||
{"type":"tool_result","time":"2026-09-17T08:05:04.000Z","call":1,"tool_call_id":"call_a1","content":"127.0.0.1 localhost\n","class":"private","untrusted":true,"truncated":false}
|
||||
{"type":"assistant","time":"2026-09-17T08:05:06.000Z","content":"It maps localhost to 127.0.0.1.","reasoning_content":null,"tool_calls":[]}
|
||||
{"type":"cache_loss","time":"2026-09-17T09:00:00.000Z","expected":30695,"got":0}
|
||||
{"type":"epoch_end","time":"2026-09-17T12:00:00.000Z","next":1,"summary":"Looked at /etc/hosts."}
|
||||
@@ -0,0 +1,7 @@
|
||||
{"type":"session_start","time":"2026-09-18T08:05:00.000Z","session":"chat-1789700000-42","epoch":0,"slot":0,"baseline":"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}
|
||||
{"type":"user","time":"2026-09-18T08:05:01.000Z","content":"What time is it?"}
|
||||
{"type":"assistant","time":"2026-09-18T08:05:03.000Z","content":null,"reasoning_content":null,"tool_calls":[{"id":"call_a1","name":"clock","arguments":"{}"}]}
|
||||
{"type":"usage","time":"2026-09-18T08:05:03.000Z","cache_n":539,"prompt_n":27,"predicted_n":24,"reasoning_tokens":8,"thinking_capped":false}
|
||||
{"type":"tool_result","time":"2026-09-18T08:05:03.100Z","call":1,"tool_call_id":"call_a1","content":"2026-09-18T08:05:03.000Z","class":"public","untrusted":false,"truncated":false}
|
||||
{"type":"assistant","time":"2026-09-18T08:05:05.000Z","content":"It is five past eight.","reasoning_content":null,"tool_calls":[]}
|
||||
{"type":"usage","time":"2026-09-18T08:05:05.000Z","cache_n":589,"prompt_n":40,"predicted_n":64,"reasoning_tokens":27,"thinking_capped":true}
|
||||
@@ -0,0 +1 @@
|
||||
{"v":1,"id":5,"final":true,"msg":{"kind":"approval_list","body":{"items":[{"approval":41,"session":"chat-1789700000-42","call":3,"tool":"shell","arguments":"{\"command\":\"rm -rf /home/kyle/scratch/build\",\"cwd\":\"/home/kyle/scratch\"}","grant":"shell-scratch","taint":"private","created":"2026-09-18T08:05:00.000Z","expires":"2026-09-18T08:20:00.000Z"}]}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"v":1,"id":5,"final":true,"msg":{"kind":"approval_list","body":{"items":[]}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"v":1,"id":5,"final":true,"msg":{"kind":"approvals","body":{}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"v":1,"id":6,"final":true,"msg":{"kind":"approve","body":{"approval":41}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"v":1,"id":6,"final":true,"msg":{"kind":"approve_result","body":{"outcome":{"outcome":"allowed"}}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"v":1,"id":6,"final":true,"msg":{"kind":"approve_result","body":{"outcome":{"outcome":"denied","reason":"no_grant"}}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"v":1,"id":8,"final":true,"msg":{"kind":"check_grants","body":{}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"v":1,"id":0,"final":true,"msg":{"kind":"error","body":{"code":"bad_version","detail":"expected 1"}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"v":1,"id":9,"final":true,"msg":{"kind":"error","body":{"code":"forbidden","detail":"approve is not accepted on broker.sock"}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"v":1,"id":6,"final":true,"msg":{"kind":"error","body":{"code":"no_such_approval","detail":"41"}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"v":1,"id":3,"final":true,"msg":{"kind":"error","body":{"code":"session_full","detail":"this conversation is full; start a new one"}}}
|
||||
@@ -0,0 +1 @@
|
||||
{"v":1,"id":2,"final":true,"msg":{"kind":"approval_outcome","body":{"outcome":{"outcome":"denied","reason":"no_grant"}}}}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user