144 lines
4.2 KiB
Markdown
144 lines
4.2 KiB
Markdown
# Design: Signal Revocation Controls
|
|
|
|
## Overview
|
|
|
|
Concrete module layout, data structures, storage encoding, read-path
|
|
integration, and preference vector rebuild strategy.
|
|
|
|
---
|
|
|
|
## Tag: `Tag::Revocation = 0x0F`
|
|
|
|
Added to `storage/keys.rs` (0x0E = CommunityMembership, 0x0F = Revocation,
|
|
0x10 = PurgeManifest, 0x11 = CommunityLeave).
|
|
|
|
Key format (stored in `users_engine`):
|
|
```
|
|
[user_id: 8 bytes BE][0x00][0x0F][revocation_id: 16 bytes UUID raw]
|
|
Value: serde_json bytes of SignalRevocation
|
|
```
|
|
|
|
---
|
|
|
|
## New Files
|
|
|
|
```
|
|
tidal/src/
|
|
entities/
|
|
revocation.rs -- RevocationId, RevocationScope, SignalRevocation, RevocationIndex
|
|
db/
|
|
revocation.rs -- TidalDb methods: revoke_signal, cancel_revocation, list_revocations
|
|
```
|
|
|
|
---
|
|
|
|
## Data Structures
|
|
|
|
### `RevocationId`
|
|
|
|
Newtype over `uuid::Uuid` with `new()`, `as_bytes()`, `from_bytes()`.
|
|
Derives `Serialize`/`Deserialize` via the uuid serde feature.
|
|
|
|
### `RevocationScope`
|
|
|
|
```rust
|
|
pub enum RevocationScope {
|
|
SignalType { signal_type: String, since_ns: Option<u64>, until_ns: Option<u64> },
|
|
TimeRange { since_ns: u64, until_ns: u64 },
|
|
AgentSession { session_id_raw: u64 }, // u64 because SessionId has no serde
|
|
}
|
|
```
|
|
|
|
Note: `AgentSession` stores `session_id_raw: u64` (from `SessionId::as_u64()`)
|
|
because `SessionId` does not derive `Serialize`/`Deserialize`.
|
|
|
|
### `RevocationIndex`
|
|
|
|
```rust
|
|
pub struct RevocationIndex {
|
|
inner: DashMap<u64, Vec<SignalRevocation>>,
|
|
}
|
|
```
|
|
|
|
Fast path: `is_suppressed` returns `false` immediately if user has no entry
|
|
in the DashMap (no lock acquisition, just a shard lookup that misses).
|
|
|
|
---
|
|
|
|
## `TidalDb` Field
|
|
|
|
```rust
|
|
revocation_index: Arc<crate::entities::RevocationIndex>,
|
|
```
|
|
|
|
Initialized in both `from_config` and `from_parts` as
|
|
`Arc::new(RevocationIndex::new())`.
|
|
|
|
---
|
|
|
|
## `db/revocation.rs` — Public API
|
|
|
|
Three methods on `TidalDb`:
|
|
- `revoke_signal(user_id, scope) -> Result<RevocationId>` — validate, persist, upsert index
|
|
- `cancel_revocation(user_id, id) -> Result<bool>` — cancel in index, re-persist
|
|
- `list_revocations(user_id) -> Result<Vec<SignalRevocation>>` — read from index
|
|
- `revocation_index() -> &Arc<RevocationIndex>` — accessor for executor
|
|
|
|
Internal helper `persist_revocation` writes to `users_engine` using
|
|
`serde_json::to_vec`.
|
|
|
|
Public function `rebuild_revocations(index, storage)` — startup restore
|
|
(called from `db/mod.rs` open path).
|
|
|
|
---
|
|
|
|
## Read-Path Integration
|
|
|
|
In `ranking/executor/mod.rs`, during the signal scoring loop:
|
|
|
|
```
|
|
score = ledger.read_decay_score(entity_id, signal_type, idx)
|
|
last_ts = ledger.entry_last_update_ns(entity_id, signal_type_id)
|
|
if for_user.is_some() and revocation_index.is_suppressed(uid, signal_type, last_ts, session_id):
|
|
score = 0.0
|
|
```
|
|
|
|
`entry_last_update_ns` is already implemented on `SignalLedger` (T3).
|
|
|
|
---
|
|
|
|
## Startup Restore
|
|
|
|
`rebuild_revocations(index, storage)` in `db/revocation.rs`:
|
|
- Full-keyspace scan of `users_engine`
|
|
- Filters for `Tag::Revocation` via `parse_key`
|
|
- Groups by `user_id`, calls `load_for_user` per group
|
|
|
|
Called from `db/mod.rs` open path alongside `rebuild_collections` and
|
|
`rebuild_suggestion_index`.
|
|
|
|
---
|
|
|
|
## uuid Dependency
|
|
|
|
`uuid` is available as a transitive dependency (confirmed: build passes without
|
|
explicit declaration). Adding it explicitly to `Cargo.toml` for clarity.
|
|
|
|
---
|
|
|
|
## Files Changed / Created
|
|
|
|
| File | Change |
|
|
|------|--------|
|
|
| `tidal/Cargo.toml` | Add `uuid = { version = "1", features = ["v4", "serde"] }` |
|
|
| `tidal/src/storage/keys.rs` | Add `Tag::Revocation = 0x0F`; update `from_byte`, test arrays |
|
|
| `tidal/src/entities/revocation.rs` | New — core types and RevocationIndex |
|
|
| `tidal/src/entities/mod.rs` | Add `pub mod revocation;` and re-exports |
|
|
| `tidal/src/signals/ledger/core.rs` | Add `entry_last_update_ns()` helper |
|
|
| `tidal/src/db/mod.rs` | Add `revocation_index` field; add `mod revocation;` |
|
|
| `tidal/src/db/revocation.rs` | New — public API + rebuild function |
|
|
| `tidal/src/db/state_rebuild.rs` | Call `rebuild_revocations` in open path |
|
|
| `tidal/src/ranking/executor/mod.rs` | Apply suppression check during scoring |
|
|
| `tidal/src/lib.rs` | Export `RevocationId`, `RevocationScope`, `SignalRevocation` |
|
|
| `tidal/tests/m10_revocation.rs` | New — 10 integration tests |
|