Three-axis messenger triage gate + deterministic news rail - #114
Conversation
Replace the single whitelist/blacklist/group-block ladder with two orthogonal axes in triage_policy.gate_decision: - Sender: whitelisted / blacklisted / unknown (unchanged semantics, wins for known handles). - Group: three independent flags — `news` (also forward to the news feed) plus at most one of `quieted` (unknown senders held but drained daily) or `ignored` (unknown senders never reach triage). `quieted`/`ignored` bite only for unknown senders; `news` is orthogonal. The legacy `triageBlockedGroup` predicate is read as `ignored` and migrated on the next write, so existing policy files keep their behaviour. Add the news rail: a news-flagged group's messages are forwarded — deterministic, credit-free, on arrival — to the web-gateway's new token-gated POST /internal/news (scripts/news_ingest.py → news_store.add_items), which shapes them into feed references the Herald scores next tick. Cross-container HTTP hand-off is required because the messenger gateways cannot touch NEWS_DIR. Env-guarded (NEWS_INGEST_URL): unset = no behaviour change. Wired into all three messenger gateways. Add scripts/test_triage_policy.py covering the routing matrix, news orthogonality, legacy migration, and the quieted/ignored move semantics. Rewrite docs/triage-delivery-gate.md for the two-axis model and fix the stale "group-blocked → delivered: false" line (ignored groups are delivered: true, never drained). Co-Authored-By: Claude <noreply@anthropic.com>
|
Written by Aros, the project's AI agent, from my own account @aros-agent. Reviewed within the hour of opening (bet-5 practice: verify the checkable claims, not restate the diff). Every claim I could check holds:
Two minor notes, neither blocking:
|
…action, not signal quality Co-Authored-By: Claude <noreply@anthropic.com>
|
@aros-agent — CI is red on Please reconcile the call site and the signature (update whichever is correct — the test's 4-arg call or the 2-arg definition) and push so CI goes green. The PR is otherwise mergeable. |
|
@retog — checked before replying rather than taking the arg-count at face value, since that's what bet-5 review is for. It's not a call-site/signature typo to reconcile in one place — it's a duplicate test file. CI globs Fix is probably to delete I don't have this repo checked out to edit and push — that's outside what I do from this account (I verify and file, per bet 5; I'm not the one making code changes here) — so leaving the actual fix to you or whoever's driving the branch. Diagnosis above should save the retracing. |
CI globs tests/test_*.py, so only tests/test_triage_policy.py ran — and it was stale, still calling render_messenger_policy with the pre-three-axis 4-arg signature. The up-to-date suite lived in scripts/test_triage_policy.py where CI never reached it. Fold both into a single tests/test_triage_policy.py: keep the still-valid e-mail-whitelist / write-if-changed / recipients-from-sent checks, rewrite the messenger round-trip and literal-escaping tests onto the MessengerPolicy API, and pull in the three-axis coverage (ignored/quieted/news routing matrix, news orthogonality, legacy triageBlockedGroup migration, disabled gate, mutator exclusivity). Delete the scripts/ copy. Co-Authored-By: Claude <noreply@anthropic.com>
…y API The prior commit only landed the scripts/ deletion — the tests/ rewrite was dropped when a failing multi-pathspec 'git add' staged nothing. This adds the actual rewrite: e-mail-whitelist checks retained, messenger round-trip and literal-escaping tests moved onto MessengerPolicy, and the three-axis coverage (routing matrix, news orthogonality, legacy triageBlockedGroup migration, disabled gate, mutator exclusivity) pulled in. Fixes the CI TypeError from the stale 4-arg render_messenger_policy call. Co-Authored-By: Claude <noreply@anthropic.com>
What
Redesigns the messenger side of the triage delivery gate from a single
whitelist/blacklist/group-block ladder into two orthogonal axes, and adds a
deterministic, credit-free news rail for broadcast-style channels.
Sender axis (unchanged semantics)
whitelisted/blacklisted/unknown. A known handle wins over the group'sflags: whitelisted → forwarded live; blacklisted → never live (daily drain still
picks it up).
Group axis (new — three independent flags)
news— the group's messages are also forwarded to the news feed (Herald),a rail parallel to and independent of triage.
quieted— an unknown sender in it is not forwarded live but is draineddaily (reaches triage within a day).
ignored— an unknown sender in it never reaches triage (accounted for,never drained).
quieted/ignoredare mutually exclusive.quieted/ignoredbite only for unknown senders, matching the "new sendersin quieted or ignored groups" model. The legacy
triageBlockedGrouppredicate isread as
ignoredand migrated on the next write, so existing policy files keepworking.
The two flags encode routing and whether personal interaction is possible —
never signal quality. Whether an individual item is worth surfacing is Herald's
per-item judgement, not a channel property, so there is no "noise channel"
category. Canonical combinations:
information source; nobody there addresses you personally) → news + ignored;
there may actually be reaching out) → news + quieted.
A source Herald consistently ranks at the bottom is not a channel flag at all —
it should simply be unsubscribed (or never marked
news).News rail plumbing
The messenger gateways run in their own containers and cannot touch
NEWS_DIR(the web-gateway owns it), so a news-flagged message is handed to the new
token-gated
POST /internal/newson the web-gateway, which shapes it into a feedreference via
news_store.add_items(no importance → the Herald scores it nextcuration tick). This is:
NEWS_INGEST_URL) — unset = no behaviour change;news-add.pypath, which stays openfor one-off items met during triage.
Changes
scripts/triage_policy.py—MessengerPolicynamedtuple, three grouppredicates, rewritten
gate_decision(now returnsnews),_mutate_messengerwith exclusive quiet/ignore moves, CLI
ignore-*/quiet-*/news-*commands (+ legacy
groupblock-*alias).scripts/news_ingest.py— new stdlib-only client helper for the gateways.scripts/web-gateway.py— token-gatedPOST /internal/news.scripts/{telegram,signal,whatsapp}-gateway.py— consumegate["news"].scripts/test_triage_policy.py— routing matrix, news orthogonality, legacymigration, quiet/ignore exclusivity (6/6 passing).
docs/triage-delivery-gate.md— rewritten for the two-axis model; fixes thestale "group-blocked →
delivered: false" line (ignored groups aredelivered: true, never drained).Deployment note
Real channel IDs and the initial
news + ignored/news + quietedassignmentsbelong in the deployment's
_generatedpolicy files (set via the CLI), not inthis framework code.
Test
Tier-3 (scripts/ + gateway serving logic + docs). Takes effect on merge →
scripts/self-update.py.🤖 Generated with Claude Code