Skip to content

feat(comment): AI spam checking + real nested replies - #201

Merged
WebTigers merged 1 commit into
mainfrom
feat/comment-spam-nesting
Aug 29, 2026
Merged

feat(comment): AI spam checking + real nested replies#201
WebTigers merged 1 commit into
mainfrom
feat/comment-spam-nesting

Conversation

@WebTigers

Copy link
Copy Markdown
Owner

AI spam checking

Tiger_Comment_Spam is the registry COMMENTS.md §6 designed, with the in-platform agent as its first checker. An Akismet-style module registers into the same seam.

  • Shown only when an agent is connected — and the settings service refuses to store an enabled flag without one. A stored 1 with no agent looks like a working filter and silently isn't.
  • No agent → silent no-op + one Tiger_Log line, comment passes through unchecked.
  • isConnected(), not isAvailable() — the latter also asks whether the current user may chat, meaningless for a background check running for an anonymous commenter.
  • A verdict may only TIGHTEN. spam bins it; ham / unknown / timeout / no-agent / broken-checker all leave the moderation posture alone. Nothing a checker says can publish something that wasn't going to be published — that's what makes it safe to hand attacker-controlled text to a model.
  • Prompt injection is the live risk: the body is delimited and framed as data, only the two literal answers are accepted, anything else is unknown (fail-open). Worst case an injection wins the treatment the comment would have had with no checker at all.
  • The poster is never told they were classified — a binned comment gets the same "awaiting moderation" reply, or a spammer iterates until it passes.
  • One-shot complete(), not the agent Loop — a classification needs no tools, no ReAct steps, no transcript, and must not be able to do anything.

Cost/latency is real and acknowledged in the doc: an LLM round-trip per comment with a body, on the org's BYO key. Opt-in, skipped for star-only ratings, fails open on timeout.

Nested replies

parent_id was accepted by the API but unreachable — no reply affordance, and the thread rendered flat. Now:

  • Default depth 3 (tiger.comment.threading, per-subject override).
  • The list payload publishes the limit, so the Reply button disappears at the limit rather than failing on submit.
  • The client orders the flat list into a tree, appending orphans rather than dropping somebody's words when a parent is removed mid-thread.
  • Indent still caps at 3 so a deep thread doesn't walk off a phone.

Tests

36 new. One harness note worth flagging: several deliberately exercise fail-soft branches that log why they degraded, which trips beStrictAboutOutputDuringTests and reports a passing test as risky. They set the null log writer, so a covered branch doesn't have to choose between being tested and being quiet.

Full suite: OK (2183 tests, 21627 assertions).

Two additions to the comment module.

SPAM — Tiger_Comment_Spam is the registry COMMENTS.md §6 designed, with the in-platform agent as
its first checker. An Akismet-style module registers into the same seam.

- Offered in the module's admin ONLY when an agent is actually connected, and the settings service
  REFUSES to store an enabled flag without one: a stored `1` with no agent looks like a working
  filter and silently isn't. With no agent it's a no-op plus one Tiger_Log line and the comment
  passes through unchecked, exactly as asked.
- Gated on Tiger_Agent::isConnected(), not isAvailable() — the latter also asks whether the CURRENT
  USER may chat, which is meaningless for a background check running for an anonymous commenter.
- A verdict may only TIGHTEN. `spam` bins the comment; ham/unknown/timeout/no-agent/broken-checker
  all leave the install's moderation posture alone. Nothing a checker says can publish something
  that wasn't going to be published — which is what makes it safe to hand attacker-controlled text
  to a language model.
- Prompt injection is the live risk, so the body is delimited and framed as DATA, only the two
  literal answers are accepted, and anything else is `unknown` (fail-open). The worst an injection
  wins is the treatment the comment would have had with no checker at all.
- The poster is never told they were classified — a binned comment gets the same "awaiting
  moderation" reply a held one does, or a spammer just iterates until it passes.
- One-shot provider complete(), not Tiger_Agent_Loop: a classification needs no tools, no ReAct
  steps, no transcript, and must not be able to DO anything.

NESTING — parent_id was accepted by the API but unreachable: there was no reply affordance and the
thread rendered flat. Now the default depth is 3 (tiger.comment.threading, per-subject override),
the list payload publishes the limit so the Reply button disappears AT the limit rather than failing
on submit, and the client orders the flat list into a tree (appending orphans rather than dropping
somebody's words when a parent is removed mid-thread). Indent still caps at 3 so a deep thread
doesn't walk off a phone.

25 integration + 11 unit tests for these. One harness note: several deliberately exercise fail-soft
branches that LOG why they degraded, which trips beStrictAboutOutputDuringTests — they set the null
log writer so a covered branch doesn't have to choose between being tested and being quiet.

Full suite: OK (2183 tests, 21627 assertions).
@WebTigers
WebTigers merged commit 44f381f into main Aug 29, 2026
13 checks passed
@WebTigers
WebTigers deleted the feat/comment-spam-nesting branch August 29, 2026 13:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant