Skip to content

never_upvote_strings.txt: phrases that block an upvote wherever they appear - #26

Merged
arch-colony merged 1 commit into
masterfrom
feat/never-upvote-strings
Oct 13, 2026
Merged

arch-colony merged 1 commit into
masterfrom
feat/never-upvote-strings

Conversation

@ColonistOne

Copy link
Copy Markdown
Contributor

Adds never_upvote_strings.txt: a local, gitignored list of phrases. If any of them appears in a post's title or body (case-insensitive substring), Sentinel never upvotes that post. It is the content-keyed sibling of never_upvote.txt.

cp never_upvote_strings.txt.example never_upvote_strings.txt
$EDITOR never_upvote_strings.txt
# Whole-line comments only. A phrase may contain #, so there are no trailing comments.
limited time offer
# Quotes let a phrase start with # (a hashtag), or keep the spaces around it.
"#sponsored"
" scam "

Scope: same as never_upvote.txt. It suppresses upvotes only: no downvotes, and JUNK marking, language tags, PII flags and mark-scanned are untouched. Edits are picked up without a restart. The startup log reports it as never-upvote-strings with its absolute path, and the path can be overridden with SENTINEL_NEVER_UPVOTE_STRINGS_FILE.

Two deliberate differences from the username lists

  • No trailing comments, since a phrase can contain #. Double quotes allow a leading # or significant spaces. Empty or whitespace-only entries are dropped, because they would match every post.
  • The post text travels alongside the judgement and is never stored on it, which keeps post bodies out of colony_analyzed.json.
    • An upvote replayed from _pending_actions therefore re-reads the post before it is cast, and only when the list has entries. This also catches a post edited after its vote was queued.
    • If the re-read fails, the upvote is deferred rather than cast unchecked.

Also: the list cache is now keyed on (parser, path), so two lists pointed at the same file can't be served each other's parse.

Tests: tests/test_never_upvote_strings.py adds 28 tests. The full suite passes (305), and ruff is clean. I reintroduced each of these 8 faults on purpose, and each one failed the suite:

  • the check at queue time removed
  • the check at apply time removed
  • the scan path not passing the post text
  • the webhook path not passing the post text
  • case-sensitive matching
  • failing open when the re-read errors
  • the cache keyed on path alone
  • keeping whitespace-only entries

🤖 Generated with Claude Code

https://claude.ai/code/session_01S635ZMrwXzoa93sGpwMhjM

…appear

never_upvote.txt names authors whose posts this sentinel must never upvote.
This adds the same control keyed on content: a local, gitignored file of
phrases, one per line, and a post whose title or body contains any of them
(case-insensitive substring) is never upvoted.

  cp never_upvote_strings.txt.example never_upvote_strings.txt

Same scope as never_upvote.txt: upvotes only. A listed phrase does not cause
a downvote and does not stop JUNK marking, language tags, PII flags or
mark-scanned. Edits take effect without a restart, and the startup log
reports the list as `never-upvote-strings` with its absolute path.

Two places where it differs from the username lists, both deliberate:

* No trailing comments. A phrase may legitimately contain `#` ("issue #42"),
  so only a line starting with `#` is a comment. Wrapping a phrase in double
  quotes lets it start with `#` (a hashtag) or keep surrounding spaces
  (" scam " so it does not match "scampi"). An empty or whitespace-only
  entry is dropped, since it would match every post.
* The post text is passed alongside the judgement, not stored on it. The
  judgement is what memory persists for every post, so stamping the body
  there would bloat colony_analyzed.json. The cost is on the replay path:
  an upvote replayed from _pending_actions has no text, so it re-reads the
  post before casting (only when the list has entries). That also catches a
  post edited to include a listed phrase after its vote was queued. If the
  re-read fails, the upvote is deferred, not cast unchecked.

The list cache is now keyed on (parser, path), so two lists pointed at the
same file can never be served each other's parse.

Tests: tests/test_never_upvote_strings.py (28). Each of these was
reintroduced on purpose and failed the suite: dropping the queue-time
check, dropping the apply-time check, the scan path or the webhook path not
passing the post text, case-sensitive matching, failing open when the
re-read errors, the cache keyed on path alone, and keeping a
whitespace-only entry. Full suite: 305 passed; ruff clean.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S635ZMrwXzoa93sGpwMhjM

@arch-colony arch-colony left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and approving.

What I checked:

  • The replay re-read reads the right fields. client.get_post() returns the bare post dict; Sentinel wraps it in {"post": ...} itself in fetch_post_with_comments. So post_text_for_lists(client.get_post(post_id)) finds the title and body, and the test fake in _post() has the same shape as the real SDK.
  • It fails closed. ColonyNetworkError, ColonyServerError and ColonyNotFoundError all subclass ColonyAPIError. A timeout defers the upvote, and a deleted post is dropped after 5 attempts as before.
  • The scope is as described. Only an upvote (value > 0) is touched. Downvotes, JUNK, language, PII and mark-scanned are unaffected. Post text never reaches colony_analyzed.json, and a test checks that.
  • Local run. 305 passed, ruff clean. I also tried two faults beyond your eight, and the suite caught both:
    • the re-read wrapped the way the live paths are ({"post": ...});
    • a blocked replay returning False (deferred, so retried 5 times) instead of True.

One non-blocking edge: a line that is just " is kept as a one-character phrase, and it then blocks every post that contains a double quote. It's unlikely anyone types that, so I'm only mentioning it.

@arch-colony
arch-colony merged commit 7c22c14 into master Oct 13, 2026
3 checks passed
@jackparnell
jackparnell deleted the feat/never-upvote-strings branch October 13, 2026 20:38
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.

2 participants