From 36b875e29edf1a4d8a25d8bb7ae0e6580d8f1c0e Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Wed, 14 Oct 2026 19:22:53 +0300 Subject: [PATCH] docs(audit): document Audit vs Audit2 surface and pin legacy Audit as unserved Part of tinyhumansai/openhuman#7328 Co-authored-by: Medulla --- crates/tinysecurity-bus/src/audit_tests.rs | 23 +++++++++++++++++ docs/specs/security-module.md | 30 ++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/crates/tinysecurity-bus/src/audit_tests.rs b/crates/tinysecurity-bus/src/audit_tests.rs index 86b7f1b..4495845 100644 --- a/crates/tinysecurity-bus/src/audit_tests.rs +++ b/crates/tinysecurity-bus/src/audit_tests.rs @@ -128,3 +128,26 @@ fn even_an_exact_echo_cannot_confirm_an_unscoped_batch() { "matching receipt must never bless missing trusted scope" ); } +#[test] +fn legacy_audit_member_is_not_served_and_audit2_covers_every_legacy_kind() +-> Result<(), serde_json::Error> { + use crate::AuditKind; + assert!(!crate::names::METHODS.contains(&crate::names::methods::AUDIT)); + assert!(crate::names::METHODS.contains(&crate::names::methods::AUDIT2)); + let pairs = [ + (AuditKind::PolicyChanged, AuditKind2::PolicyChanged), + (AuditKind::Approval, AuditKind2::Approval), + (AuditKind::Judge, AuditKind2::Judge), + (AuditKind::Execution, AuditKind2::Execution), + (AuditKind::Sandbox, AuditKind2::Sandbox), + ]; + for (legacy, current) in pairs { + assert_eq!( + serde_json::to_string(&legacy)?, + serde_json::to_string(¤t)? + ); + } + // Shell is the one category version two adds beyond the legacy vocabulary. + assert_eq!(serde_json::to_string(&AuditKind2::Shell)?, "\"shell\""); + Ok(()) +} diff --git a/docs/specs/security-module.md b/docs/specs/security-module.md index 56f868d..3ab6cd7 100644 --- a/docs/specs/security-module.md +++ b/docs/specs/security-module.md @@ -202,6 +202,36 @@ The stable journal supplies receipt authority; rotated files are reconstructable views. Process termination/recovery tests do not imply physical power-loss or Windows directory-creation durability guarantees. + +### Audit versus Audit2 + +One ordered, contiguous audit stream per trusted scope and stream identity carries +policy-change, native-routine, approval, judge, shell, execution and sandbox +decisions as `AuditKind2` events. `Audit2` (callback `AuditSink2`) is the only +served audit member. The legacy `Audit` name and `AuditKind`/bool-receipt +vocabulary are reserved and never served: a bool cannot prove which event +committed, in what order, or against which tail. Every legacy kind keeps its +serialized name in `AuditKind2`; `shell` is the one added category. + +`Audit2` takes an exact sanitized batch with `expected_tail` and returns an exact +receipt (`AuditEventReceipt2` per event, `new_tail`) only after data and metadata +sync. `EvaluateAudited2`/`CheckAudited2` (and the scoped variants) derive the +pre-execution event from the original request and computed verdict, so the host +never supplies the verdict. Terminal `RecordExecution2` events are a separate +phase. + +Local files: Unix creates journal, lock and JSONL projections fd-relative with +mode 0600 and verifies owner and mode on every open. Windows requires a +preprovisioned protected current-user DACL and creates new views with restricted +inherited access; it never widens an existing ACL. Rotation runs before the +journal append, so a rotation failure (including a Windows sharing/lock violation +on rename) denies the candidate before any journal byte is written, and the same +retry commits exactly once after the blocker releases. + +Failure semantics: required audit denies pre-execution on any callback or storage +failure, timeout, malformed receipt or stale generation. Optional audit reports +degraded health, never fabricates a receipt and never sets `committed`; scope and +sanitation failures deny even when audit is optional. ## Sandbox and crypto Contract 1.9 implements `Plan2`, `EvaluateSandboxApproval2` and