feat: moderation core — data model + resolver (Slice 1) #53
No reviewers
Labels
No labels
auth
bug
composer
correctness
database
durability
enhancement
feeds
interactions
notifications
performance
reliability
safety
security
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
starsetbyte/peregrine!53
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/moderation-core"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Slice 1 of the moderation foundation: labels and viewer facts flow network → Room → cache, and a pure ModerationResolver turns them into per-context verdicts (filter/blur/alert/inform/noOverride). Zero user-visible behavior change — nothing is wired to UI yet.
Executes docs/superpowers/plans/2026-07-04-moderation-core.md task-by-task via subagent-driven development, with a task-scoped review after every task plus two deeper gate reviews (post-Task-3 DTO/Room/mapper plumbing, post-Task-6 resolver semantics) and a final whole-branch review. All reviews clean: 0 Critical, 0 Important findings across the branch.
The abandoned feat/moderation branch (PR #11, reference only, never merged) had a "default-Show label bug" where labels silently failed open. This slice's resolver was specifically traced end-to-end to confirm that bug is structurally impossible here.
What's in
Shared Label DTO + labels/viewer.blocking fields on the network models
Room v9→v10 additive migration persisting labels + viewer mute/block facts
FeedRepository mapper round-trips those facts through the cache
New pure data/moderation/ package: types, BuiltInLabels, MuteWordMatcher, ModerationResolver
Purity contract test locking data/moderation/ to zero android/room/retrofit/coroutines imports
Known gaps carried forward (documented, non-blocking)
Label.neg/Label.exp not filtered by the resolver (AppView pre-resolves)
Legacy label-alias settings-key normalization deferred to Stage 5
decideNotification has no direct test coverage
Design-spec doc citations point to docs/moderation-foundation, not this branch — dangling until that branch also merges
Test plan
[x] ./gradlew test — 612/612 passing (215 pre-existing + 397 new)
[x] ./gradlew :app:assembleDebug — schema v10 regenerated and verified
[x] ./gradlew lint — clean for data/moderation; 2 pre-existing unrelated errors confirmed to predate this branch
[x] 8 task-scoped reviews + 2 gate reviews + 1 final whole-branch review — all clean
\p{Punct} in Kotlin/Java regex is ASCII-only, so EDGE_PUNCTUATION and PUNCT_OR_SPACE failed to strip em dashes, curly quotes, ellipses, and CJK full-width punctuation before the token-equality check. A muted word like "crypto" would silently fail to match "so much crypto—" or "“crypto”" -- a real under-mute gap since autocorrect/IMEs insert this punctuation routinely. Switch both regexes to \p{IsPunctuation} (Unicode General_Category=Punctuation), which covers Pd (dashes) and all other punctuation subcategories. Flagged by task reviewer on Task 5 (8f4989c).starsetbyte referenced this pull request2026-07-05 13:44:10 +00:00