4.0 KiB
4.0 KiB
Audit: Feedback Loop UX
Scope
Security, data integrity, and operational audit of the feedback loop UX feature (p1-feedback-loop-ux).
Security
Input Validation
- user_id and entity_id are opaque integers. No string parsing, no injection vectors.
- creator_id in MuteCreator/UnmuteCreator is a u64. No trust boundary crossed -- the caller provides the creator_id, and the method trusts it. This matches existing patterns (e.g.,
signal_with_contextaccepts user_id without validation). - No new network endpoints. This feature adds only in-process Rust API methods. No HTTP routes, no deserialization of external input.
require_writeableguard prevents writes on read-only follower nodes, maintaining the leader-follower invariant.
Authorization
- No authorization model in scope. The feedback API trusts the caller to pass the correct user_id. This is consistent with all other
TidalDbmethods (signal, retrieve, search). Authorization is an application-layer concern. - No privilege escalation. A user can only affect their own feedback state. The
user_idparameter scopes all operations.
Data Leakage
feedback_stateonly returns boolean flags. No signal values, no embeddings, no metadata leaked through this API.- Skip counter is ephemeral. Resets on restart. No durable record of how many times a user skipped an item (only the final hard-neg promotion is visible).
Data Integrity
Atomicity
- Each
submit_feedbackcall performs all side effects synchronously before returningOk(()). There is no partial-apply failure mode where a signal is written but the bitmap is not updated. - Exception: WAL write failure. If
self.signal()returns an error (e.g., backpressure), the method returnsErrimmediately. No bitmap updates have occurred yet because the signal write happens first in all action branches. This is correct: the method is fail-fast, and the caller can retry.
Durability
- Signal-writing actions are WAL-durable. Like, Hide, Dislike, MuteCreator, NotInterested all call
self.signal()which writes to the WAL. - Unhide writes a negative-weight signal for durability (weight=-1.0 on "hide" signal type). This ensures the unhide survives restart via WAL replay.
- Undo-only actions (Unlike, UndoDislike, UnmuteCreator, Unsave) are ephemeral. This is by design and documented in the spec. After restart, the undo state is lost unless the application replays it.
Consistency
- DashMap provides sequential consistency within a single process. A write on thread A followed by a read on thread B sees the write.
- No stale reads possible within the same process, because there is no caching layer between the bitmap writes and the query pipeline reads.
- Cross-node replication is eventually consistent. Signal writes propagate via WAL shipping. Bitmap state is rebuilt from WAL on followers. This is documented in the spec as a non-goal for immediate cross-node reflection.
Operational Impact
Memory
- skip_counter: DashMap<(u64, u32), u8>. Each entry is ~17 bytes (key: 12 bytes, value: 1 byte, overhead: ~4 bytes). At 1M unique user-item skip pairs, this is ~17MB. Acceptable for a single-node database.
- No new background threads. No new timers, no new channels, no new periodic tasks.
Performance
- No regression on hot paths.
submit_feedbackdoes not add any overhead tosignal(),retrieve(), orsearch(). It is a new entry point, not a wrapper around existing hot paths. - feedback_state is O(1). 4 DashMap lookups, no iteration, no I/O.
Monitoring
- No new metrics. The existing signal write metrics (WAL append latency, signal count) already cover the signal-writing actions. Feedback-specific metrics (e.g., hide rate, mute rate) can be added as a future feature.
Verdict
APPROVED. No security, data integrity, or operational concerns. The feature is safe for production use.