4.6 KiB
Audit: Agent Capability Boundaries
Scope
This audit covers the complete implementation of m10-agent-capability-boundaries: the extension of AgentPolicy with read-path and profile-override access controls, session-gated read methods, schema build-time validation, and audit integration.
Files Changed
| File | Change Type | Risk |
|---|---|---|
tidal/src/schema/validation/policies.rs |
Additive (5 new fields + Default update) | Low |
tidal/src/schema/validation/builders/mod.rs |
Additive (new field, new method, new validation) | Low |
tidal/src/schema/error.rs |
Additive (3 new error variants) | Low |
tidal/src/session/policy.rs |
Additive (3 new methods, 5 new enum variants, 12 new tests) | Low |
tidal/src/session/audit.rs |
Additive (3 new AuditKind variants) | Low |
tidal/src/session/snapshot.rs |
Bug fix (overrides_rejected was hardcoded to 0) | Low |
tidal/src/db/signals.rs |
Additive (4 new public methods, 2 new private helpers) | Low |
tidal/src/db/query_ops.rs |
Additive (1 private helper, enforcement calls) | Low |
tidal/src/lib.rs |
Additive (3 new re-exports) | Low |
tidal/tests/m10_agent_capability.rs |
New (17 integration tests) | None |
No existing public API methods were changed. No WAL changes. No storage changes. No new modules.
Correctness
Read-path enforcement correctly follows the deny-before-allow evaluation order specified in the spec. An empty allow list is unrestricted (not a block-all). An empty deny list is a no-op. This matches the spec's zero-cost fast-path requirement.
Profile override enforcement correctly treats an empty allowed_profile_overrides as "no overrides permitted." The query-level profile name supplied by the caller is checked against the policy before the query executor runs. Both retrieve() and search() are covered.
Snapshot bug fix (overrides_rejected was hardcoded to 0): the fix correctly reads from state.overrides_rejected.load(Ordering::Relaxed) in both build_snapshot and build_frozen_snapshot. This was a latent bug in prior code that the feature exposed and corrected.
SchemaBuilder::new() change from const fn to regular fn is correct and necessary due to Vec::new() not being const in the current MSRV (1.91). No callers relied on the const fn property.
Sentinel "*" expansion resolves to all names registered via declare_profile_names(). If declare_profile_names() was not called, the builder's known_profile_names is empty, so "*" expands to an empty list — effectively disabling the guard, which is the correct behavior (same as empty allowed_profile_overrides).
Backward Compatibility
All new AgentPolicy fields default to empty Vec. The ..AgentPolicy::default() pattern was added to the one struct literal in session_tests.rs that used positional construction. All other existing tests and callers are unaffected. The Default impl was updated correctly.
Security Properties
- Read-path enforcement is cooperative at the API boundary (non-session reads remain unrestricted, as documented in the spec non-goals).
- Policy evaluation occurs at the call site before any ledger access — violations do not leak data.
- Audit log entries are recorded for all violations, enabling forensic review.
signals_rejectedcounter is incremented for read violations (consistent with write violation accounting).overrides_rejectedcounter is independent (tracked separately fromsignals_rejected).
Performance
All new policy checks short-circuit in O(1) when the relevant lists are empty (the common case for policies that do not restrict reads). Non-empty list checks are O(n) linear scan over Vec<String> — acceptable for typical policy sizes (n ≤ 20). The spec notes that HashSet promotion at n > 50 is available as a future optimization.
No heap allocation occurs on the fast path for empty-list policies.
Test Adequacy
17 integration tests cover all 12 acceptance criteria. 12 unit tests in session/policy.rs validate the PolicyEvaluator methods in isolation. Coverage includes:
- Allow-list gate (signal in list → Ok, signal not in list → ReadNotAllowed)
- Deny-list gate (signal in deny list → ReadDenied regardless of allow list)
- Attribute read allow/deny
- Profile override allowed and blocked paths
- Audit log recording
- Counter increments (signals_rejected, overrides_rejected)
- Empty-policy regression (no behavior change)
- Schema build-time failure modes (3 cases)
- Sentinel
"*"expansion - Ungated reads unaffected by session policies
Verdict
APPROVED — implementation is correct, safe, backward-compatible, and adequately tested. No issues requiring remediation.