Skip to content

Open the news ingest endpoint by default, wire NEWS_INGEST_URL in compose - #137

Merged
retog merged 1 commit into
mainfrom
feat/news-ingest-open-by-default
Aug 20, 2026
Merged

Open the news ingest endpoint by default, wire NEWS_INGEST_URL in compose#137
retog merged 1 commit into
mainfrom
feat/news-ingest-open-by-default

Conversation

@retog

@retog retog commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Follow-up to #114. The deterministic news rail it merged is inert in every deployment: POST /internal/news is fail-closed on CONVERSATION_BACKEND_TOKEN, and no compose file sets NEWS_INGEST_URL. Both had to be wired by hand, for a URL that only ever has one sensible value.

Open by default

The rail carries broadcast content the deployment does not control — a post in an open channel is written by whoever cares to write it, and reaches the Herald through the legitimate path regardless. Authenticating the transport therefore buys no integrity for what lands in the feed, while a fail-closed default fails silently: forward_news() is best-effort and swallows a 403, so a token mismatch produces a rail that looks wired and quietly drops every item.

POST /internal/news now accepts an untokened call. A deployment that wants it locked down sets NEWS_INGEST_TOKEN, which is enforced when present.

/internal/conversations and /internal/email stay fail-closed. They push to the user's devices and send mail; filing a feed reference reaches nobody.

Why a separate variable

Reusing CONVERSATION_BACKEND_TOKEN would not work: scripts/entrypoint.sh generates it whenever it is missing, so the web-gateway always holds one while the gateway containers never see it. "No token configured" would be unreachable and every forward would 403 — the exact silent failure this PR removes. tests/test_news_ingest_auth.py pins that case.

news_ingest.py still falls back to CONVERSATION_BACKEND_TOKEN when NEWS_INGEST_TOKEN is unset, so a deployment wired before this PR keeps working.

Zero-config URL

NEWS_INGEST_URL=http://retinue:8080/internal/news is now set on all three gateway services in the base compose file, next to the RETINUE_GATEWAY_URL and STT_SERVICE_URL entries that already follow that pattern. In-network addresses belong in the base compose; only deployment-specific values belong in an override. Emptying it disables the rail.

Changes

  • scripts/web-gateway.py_news_ingest_authorized(), with the reasoning in its docstring; _handle_internal_news uses it.
  • scripts/news_ingest.py — prefers NEWS_INGEST_TOKEN, falls back to the old variable.
  • docker-compose.ymlNEWS_INGEST_URL default plus optional NEWS_INGEST_TOKEN passthrough on the three gateway services.
  • docs/triage-delivery-gate.md — documents the asymmetry and drops the stale "token-gated" wording.
  • tests/test_news_ingest_auth.py — new, 4 cases.

Verification

tests/test_news_ingest_auth.py 4/4, test_news_gateway.py, test_news_store.py, test_triage_policy.py 13/13 and test_web_gateway_send_page.py 5/5 all pass; both touched scripts compile; the compose file parses and yields the new entries on all three services.

Tier 3 — takes effect on merge plus a rebuild of the gateway images.

… compose

POST /internal/news was fail-closed on CONVERSATION_BACKEND_TOKEN and no
deployment set NEWS_INGEST_URL, so the deterministic news rail merged in #114
was inert everywhere and needed two manual variables to come alive.

Two problems, both fixed here:

- Authenticating this endpoint buys no integrity. The rail exists to carry
  broadcast content the deployment does not control — a post in an open channel
  is written by whoever cares to write it and reaches the Herald through the
  legitimate path anyway. Meanwhile fail-closed fails *silently*, because
  forward_news() swallows a 403 by design. The endpoint is now open unless
  NEWS_INGEST_TOKEN is set, which is enforced when it is.
- It gets its own variable rather than reusing CONVERSATION_BACKEND_TOKEN: the
  entrypoint generates that one whenever it is missing, so "no token
  configured" would never be observable and every gateway call would 403.

/internal/conversations and /internal/email stay fail-closed — they push to the
user's devices and send mail; filing a feed reference reaches nobody.

NEWS_INGEST_URL now defaults to the in-network web-gateway address on all three
gateway services, alongside RETINUE_GATEWAY_URL and STT_SERVICE_URL, so the rail
needs no deployment configuration.

Tests: tests/test_news_ingest_auth.py (4 cases, incl. the generated-token trap).

Co-Authored-By: Claude <noreply@anthropic.com>
@retog
retog merged commit 42705b4 into main Aug 20, 2026
1 check passed
@retog
retog deleted the feat/news-ingest-open-by-default branch August 20, 2026 16:55
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