tidaldb/.sdlc/features/m10-agent-capability-boundaries/audit.md

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_rejected counter is incremented for read violations (consistent with write violation accounting).
  • overrides_rejected counter is independent (tracked separately from signals_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.