5.8 KiB
Tasks: Community Policy Engine
T1 — Define CommunityPolicy, CommunityContext, and schema types
File: tidal/src/schema/validation/community_policy.rs (new)
- Define
CommunityPolicystruct withallowed_write_signals,denied_write_signals,allowed_read_signals,denied_read_signalsfields (allVec<String>). - Define
CommunityContextstruct withcommunity_id: Stringandrole: String. - Define
pub(super) CommunityPolicyEntrywithname: Stringandpolicy: CommunityPolicy. - Add 4 new
SchemaErrorvariants totidal/src/schema/error.rs:InvalidCommunityPolicyName(String),DuplicateCommunityPolicyName(String),CommunityPolicySignalNotInSchema { policy: String, signal: String },CommunityPolicySignalConflict { policy: String, signal: String }. - Re-export
CommunityPolicyandCommunityContextfromschema/validation/mod.rsandschema/mod.rs. - Unit tests: struct construction, field access.
Acceptance: cargo test --lib -p tidaldb passes; clippy -D warnings clean.
T2 — Add CommunityPolicy to Schema and SchemaBuilder
Files: tidal/src/schema/validation/builders.rs, tidal/src/schema/validation/mod.rs
- Add
community_policies: Vec<CommunityPolicyEntry>field toSchemaBuilder. - Implement
SchemaBuilder::community_policy(name: &str, policy: CommunityPolicy) -> &mut Self. - Add full validation in
SchemaBuilder::build():- Name must be valid identifier (reuse
is_valid_signal_name). - No duplicate names.
- All signal references must exist in schema signals.
- No write allow/deny conflict.
- No read allow/deny conflict.
- Name must be valid identifier (reuse
- Add
community_policies: HashMap<String, CommunityPolicy>field toSchema. - Implement
Schema::community_policy(role: &str) -> Option<&CommunityPolicy>. - Implement
Schema::community_policy_count() -> usize. - Unit tests: valid schema with community policies; each rejection case for new
SchemaErrorvariants.
Acceptance: All schema validation tests pass; clippy -D warnings clean.
T3 — Implement CommunityPolicyEvaluator and signal_with_community_policy()
Files: tidal/src/db/community.rs, tidal/src/session/policy.rs
- Implement
CommunityPolicyEvaluator<'a>withpolicy: &'a CommunityPolicyandrole: &'a str. - Implement
check_write(&self, signal_type: &str) -> Result<(), PolicyViolation>using deny-first, allow-list logic. - Add
CommunityWriteDeniedandCommunityWriteNotAllowedvariants toPolicyViolationKindintidal/src/session/policy.rs. - Implement
TidalDb::signal_with_community_policy(signal_type, entity_id, weight, timestamp, ctx: CommunityContext) -> crate::Result<()>:- Resolve policy from schema by
ctx.role; returnTidalError::NotFoundif missing. - Call
CommunityPolicyEvaluator::check_write; mapPolicyViolationtoTidalError::PolicyViolation. - On success, delegate to
self.signal(...).
- Resolve policy from schema by
- Unit tests for
check_write: allow path, deny-list path, allow-list miss path.
Acceptance: cargo test --lib passes; clippy -D warnings clean.
T4 — Thread CommunityContext through retrieval and scoring
Files: tidal/src/query/retrieve/types.rs, tidal/src/ranking/executor/context.rs, scoring loop files
- Add
community: Option<CommunityContext>toRetrievestruct. - Add
community(ctx: CommunityContext) -> SelftoRetrieveBuilder. - Add
community: Option<CommunityContext>toExecutorContext(or equivalent context struct). - In
ProfileExecutor::score_candidates(or equivalent scoring entry point):- If
communitypresent: resolveCommunityPolicyfrom schema; returnQueryError::NotFoundif role missing. - Build
suppressed: HashSet<SignalTypeId>from read deny/allow lists usingschema.resolve_signal_type(). - In the scoring loop: skip signal contributions whose
SignalTypeIdis insuppressed. - Fast path: if
suppressed.is_empty(), skip thecontainscheck.
- If
- Unit tests: suppressed signals excluded from score; non-suppressed signals included; empty suppressed set takes fast path.
Acceptance: cargo test --lib passes; clippy -D warnings clean.
T5 — Integration tests (m10_community_policy.rs)
File: tidal/tests/m10_community_policy.rs (new)
Cover all 9 scenarios from the spec test matrix:
- Write allowed signal under member role → success.
- Write denied signal under member role →
PolicyViolation(CommunityWriteDenied). - Write signal not in allow list under member role →
PolicyViolation(CommunityWriteNotAllowed). - Write any signal under admin role (empty allow list, empty deny list) → success.
- Read suppressed signal excluded from ranking score: two candidates with equal base signals; one has a suppressed signal contribution; suppressed candidate ranks lower.
- Read suppressed signal with no community context on query → score component included normally.
- Schema with duplicate policy name →
SchemaError::DuplicateCommunityPolicyName. - Schema with unknown signal in policy →
SchemaError::CommunityPolicySignalNotInSchema. - Schema with allow/deny conflict →
SchemaError::CommunityPolicySignalConflict. - Query with unknown role name →
TidalError::NotFound(orQueryError::NotFound).
Acceptance: All 10 integration tests pass with cargo test --test m10_community_policy; zero test failures.
T6 — Final polish and verification
- Run
cargo test --manifest-path tidal/Cargo.toml --lib→ all lib tests pass. - Run
cargo test --manifest-path tidal/Cargo.toml --test m10_community_policy→ all integration tests pass. - Run
cargo clippy --manifest-path tidal/Cargo.toml -- -D warnings→ zero warnings. - Run
cargo fmt --manifest-path tidal/Cargo.toml --check→ no format issues. - Confirm existing test suites unaffected:
m5_uat,m6_uat,m7_uat,m8_uat.
Acceptance: All checks green; no regressions in any existing test suite.