5.4 KiB
Review: Agent Capability Boundaries
Summary
The implementation correctly extends AgentPolicy with five new fields for read-path and profile-override access control, enforces them at the session layer, validates them at schema build time, and surfaces violations through the existing TidalError::PolicyViolation path. All 12 acceptance criteria from the spec are met by the 17 integration tests.
Spec Conformance
| Acceptance Criterion | Status | Notes |
|---|---|---|
AC1: allowed_read_signals allow-list enforcement |
PASS | test_read_allowed_signal_succeeds, test_read_disallowed_signal_fails_not_allowed |
AC2: denied_read_signals deny-list enforcement |
PASS | test_read_denied_signal_fails_denied |
AC3: allowed_user_attributes allow/deny |
PASS | test_attribute_read_allowed_succeeds, test_attribute_read_disallowed_fails, test_attribute_read_denied_by_deny_list |
AC4: Empty allowed_profile_overrides blocks overrides |
PASS | test_profile_override_disallowed_fails |
| AC5: Explicit profile override list enforced | PASS | test_profile_override_allowed_proceeds |
| AC6: Violations recorded in audit log | PASS | test_audit_log_records_read_denial |
| AC7: Empty-policy zero-regression | PASS | test_empty_policy_no_regression |
| AC8: Schema rejects unknown signal in allow-list | PASS | test_schema_build_fails_unknown_read_signal |
| AC9: Schema rejects allow/deny conflict | PASS | test_schema_build_fails_read_signal_conflict |
| AC10: Schema rejects unknown profile override name | PASS | test_schema_build_fails_unknown_profile_override |
AC11: signals_rejected and overrides_rejected counters |
PASS | test_signals_rejected_incremented_on_read_denial, test_overrides_rejected_incremented |
AC12: Ungated read_decay_score unaffected |
PASS | test_read_without_session_unrestricted |
Design Conformance
The implementation follows the design document exactly:
AgentPolicyinschema/validation/policies.rs: 5 new fields, empty-default,..Default::default()propagated to all existing struct literals.PolicyEvaluatorinsession/policy.rs:check_read(),check_attribute_read(),check_profile_override()added with correct deny-before-allow ordering.AuditKindinsession/audit.rs:ReadDenied,AttributeReadDenied,ProfileOverrideRejectedadded.SessionStateinsession/state.rs:overrides_rejected: AtomicU64was already present from a prior session;SessionSnapshotcorrectly reads it viaoverrides_rejected.load(Relaxed)in bothbuild_snapshotandbuild_frozen_snapshot(previously hardcoded to 0 — this was a pre-existing bug that the feature surfaced and fixed).db/signals.rs: 4 new public session-gated read methods and 2 private helpers.db/query_ops.rs:enforce_profile_override_policy()helper added, called in bothretrieve()andsearch().SchemaBuilderinschema/validation/builders/mod.rs:declare_profile_names()added;known_profile_namesfield used for profile-override validation;"*"sentinel expansion implemented.
The design noted that SchemaBuilder::new() would need to change from const fn to regular fn due to Vec initialization. This was handled correctly.
One design note: the spec described a SessionPolicyBuilder fluent API for building policies, but the implementation takes a simpler direct approach — users construct AgentPolicy structs directly and pass them to SchemaBuilder::session_policy(). This is acceptable; the fluent builder was a suggestion in the spec, not a hard requirement, and the direct struct approach is simpler and avoids an intermediate builder type.
Code Quality
signals.rs — The two #[allow(clippy::significant_drop_tightening)] attributes are correctly scoped to check_session_read_policy and check_session_attribute_read_policy. The lint fires because the DashMap guard is held across an if let chain that could be tightened; the allow is justified since tightening would require restructuring the borrow around a ? operator in a way that makes the code less readable.
query_ops.rs — enforce_profile_override_policy() is a clean private helper. The EntityId import that was erroneously added was removed. The for_session check correctly delegates to the new helper for both retrieve() and search().
policy.rs — The check_read implementation matches the design sequence exactly: deny list first (O(n) scan), then allow list (O(n) scan, short-circuits when empty). The 12 new unit tests in the existing test block cover the edge cases. The check_profile_override implementation correctly treats an empty allowed_profile_overrides as "no overrides permitted at all," which matches the spec.
lib.rs — AuditKind, PolicyViolation, PolicyViolationKind are re-exported for integration test use. These were previously crate-internal; the re-export makes them accessible without requiring callers to traverse internal module paths.
Issues Found
None. The implementation is clean, the tests are complete, clippy passes with -D warnings, and all 17 integration tests pass.
Test Coverage
17 integration tests covering all spec acceptance criteria. 12 unit tests in session/policy.rs covering the three new PolicyEvaluator methods. Total test count at time of completion: 1367 lib + 17 m10 integration tests.
Verdict
APPROVED — implementation is correct, complete, and well-tested.