# M1 task 06: `proto` audit and session log records **Branch:** `m1` (run `git switch m1`; `git status --short` must be empty, otherwise stop) **Commit subject:** `Add audit and session log record types to proto` ## Goal Add the record types for the two log files. Each log is JSONL: one JSON object per line. This task adds only the types. Writing files, hashing and locking come in later milestones. ## Context From the design brief: "Audit log: append-only JSONL, hash-chained, one record per decision including denials." and "`sessions//.jsonl` — append-only session log". The assistant's messages are stored "exactly as the server returned" them, because the next request must repeat them byte for byte or the inference server's cache is lost. The hash chain hashes the exact bytes of each stored line, so a record must always encode to the same bytes. That is why the fixtures are compared byte for byte in both directions. One audit line: ```json {"seq":1,"time":"2026-09-17T08:05:01.250Z","prev":"000102…1e1f","session":"mm-thread-42","call":2,"tool":"shell","arguments":"{\"command\":\"df -h\"}","session_taint":"private","decision":{"outcome":"approved","grant":"shell-ask","approver":"u8f3k2","post":"p9x7"}} ``` ## Files - Copy: `crates/proto/tests/records.rs`, `crates/proto/tests/fixtures/records/` (2 files) - Create: `crates/proto/src/audit.rs`, `crates/proto/src/log.rs` - Modify: `crates/proto/src/lib.rs`, `docs/implementer-log.md` ## Interfaces Consumes from tasks 02 and 03: `CallId`, `DataClass`, `DenyReason`, `Epoch`, `Hash32`, `SessionId`, `Timestamp`. Produces, re-exported from the crate root. **Keep fields and variants in exactly this order.** ```rust // crates/proto/src/audit.rs // JSON: {"outcome":"allowed","grant":"…"} ; the tag sits beside the fields; outcomes are snake_case pub enum DecisionRecord { Allowed { grant: String }, Approved { grant: String, approver: String, post: Option }, Denied { reason: DenyReason, grant: Option }, } pub struct AuditRecord { pub seq: u64, pub time: Timestamp, pub prev: Hash32, pub session: SessionId, pub call: CallId, pub tool: String, pub arguments: String, pub session_taint: DataClass, pub decision: DecisionRecord, } // crates/proto/src/log.rs pub struct ToolCall { pub id: String, pub name: String, pub arguments: String } // JSON: {"type":"user","time":"…","content":"…"} ; the tag sits beside the fields; types are snake_case pub enum LogRecord { SessionStart { time: Timestamp, session: SessionId, epoch: Epoch, slot: u32, baseline: Hash32 }, User { time: Timestamp, content: String }, Assistant { time: Timestamp, content: Option, reasoning_content: Option, tool_calls: Vec }, ToolResult { time: Timestamp, call: CallId, tool_call_id: String, content: String, class: DataClass, untrusted: bool, truncated: bool }, CacheLoss { time: Timestamp, expected: u64, got: u64 }, EpochEnd { time: Timestamp, next: Epoch, summary: String }, } ``` Derives: every type gets `Debug, Clone, PartialEq, Eq, Serialize, Deserialize`. Rules the tests check: a `None` is written as `null`, never left out (so do not use `skip_serializing_if`). Unknown fields and unknown `type` or `outcome` values are errors. ## API notes Internally tagged enums and `deny_unknown_fields` work as in task 03: `#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]`. ## Steps - [ ] **1. Copy the test and fixtures.** ```sh git switch m1 cp docs/plans/M1/files/crates/proto/tests/records.rs crates/proto/tests/ cp -r docs/plans/M1/files/crates/proto/tests/fixtures/records crates/proto/tests/fixtures/ ``` - [ ] **2. See the test fail.** `cargo test -p proto --test records`. Expected: it does not compile. - [ ] **3. Write `audit.rs` and `log.rs`,** and add to `lib.rs` (alphabetical order): ```rust pub mod audit; pub mod log; pub use audit::{AuditRecord, DecisionRecord}; pub use log::{LogRecord, ToolCall}; ``` - [ ] **4. See the test pass.** `cargo test -p proto --test records`. Expected: `3 passed; 0 failed`. - [ ] **5. Run the gate.** `make gate`. Expected last line: `gate: ok`. - [ ] **6. Log and commit.** ```sh git add crates/proto docs/implementer-log.md git commit ``` ## Done when - `cargo test -p proto --test records` reports 3 passed. - `cargo test -p proto` reports 40 passed in total across the five test files. - `make gate` prints `gate: ok`. - `diff -r crates/proto/tests docs/plans/M1/files/crates/proto/tests` prints nothing. ## Stop and report if - A fixture line cannot be matched byte for byte with the field order given above.