tidaldb/.sdlc/features/m10-signal-revocation-controls/qa-plan.md

3.4 KiB

QA Plan: Signal Revocation Controls

Scope

Unit tests in entities/revocation.rs, integration tests in tidal/tests/m10_revocation.rs, and property tests via proptest.


1. Unit Tests (--lib)

1.1 entities/revocation.rs

ID Scenario Pass Condition
U1 SignalType scope, no bounds Matches correct type; rejects other types
U2 SignalType scope, since bound Rejects signals before since_ns
U3 SignalType scope, until bound Rejects signals after until_ns
U4 SignalType scope, both bounds Window boundary inclusive on both sides
U5 TimeRange scope Inclusive on both ends; any signal type
U6 AgentSession scope Matches by session_id_raw; rejects None or wrong id
U7 is_suppressed: no revocations Returns false (fast path)
U8 is_suppressed: matching active Returns true
U9 is_suppressed: inactive revocation Returns false
U10 cancel: unknown id Returns false
U11 cancel: deactivates is_suppressed returns false after cancel
U12 list: includes cancelled Vec contains both active and cancelled
U13 overlapping revocations Union semantics — either matching returns true
U14 upsert replaces existing by id Vec length stays 1 after replacing
U15 RevocationId bytes roundtrip from_bytes(as_bytes()) == original

1.2 signals/ledger/core.rs

ID Scenario Pass Condition
U16 entry_last_update_ns: no entry Returns 0
U17 entry_last_update_ns: after signal Returns correct timestamp

2. Integration Tests (tidal/tests/m10_revocation.rs)

ID Test name Scenario
I1 basic_signal_type_revocation Revoke "view"; decay score suppressed; "like" unaffected
I2 time_range_revocation Signals in range suppressed; outside unaffected
I3 agent_session_revocation Session signals suppressed; non-session unaffected
I4 cancel_revocation_lifts_suppression Revoke; verify suppressed; cancel; verify restored
I5 revocation_survives_restart Revoke; close; reopen; verify still suppressed
I6 overlapping_revocations Two overlapping revocations both apply
I7 revoke_unknown_signal_type Returns TidalError::Schema
I8 revoke_invalid_time_range Returns TidalError::InvalidInput
I9 cancel_nonexistent_revocation Returns false (not an error)
I10 preference_vector_rebuild_after_revocation Pref vector excludes revoked items

3. Property Tests (proptest)

ID Property
P1 No revocations → is_suppressed always false
P2 scope.matches deterministic (same inputs = same output)
P3 Union semantics: if any revocation matches, is_suppressed is true
P4 RevocationId bytes roundtrip

4. Static Analysis

  • cargo clippy --manifest-path tidal/Cargo.toml -- -D warnings — zero warnings
  • cargo fmt --manifest-path tidal/Cargo.toml --check — clean

5. Performance Sanity

  • is_suppressed with 0 revocations: single DashMap shard miss, ~10ns
  • is_suppressed with 5 active revocations: linear scan of 5 items, ~50ns
  • No measurable overhead on ranking hot path for users with 0 revocations

Pass Criteria

  • All lib tests pass (1299+ tests, zero failures)
  • All 10 m10_revocation integration tests pass
  • clippy -D warnings: zero warnings
  • Revocations survive restart (I5 passes)