Unified News Syndication Security & Safety
This document outlines the safety mechanisms and architectural constraints designed to prevent accidental or malformed automated posts to social media (Twitter/X, GitHub, Open Collective) and RSS by the CI/CD pipeline and Vox Orchestrator agents.
Related: searchable incident patterns and external references — news_syndication_incident_patterns.md.
1. The Accidental Post Problem
Automated systems, especially agentic orchestration loops, can rapidly generate content. Without strict constraints, a misconfigured agent or a rogue loop could spam production feeds.
Common causes:
- Unbounded retries — Failing to record completion, causing duplicate posts.
- Live credentials in “test” paths — No dry-run or mock HTTP separation.
- Weak typing — Invalid frontmatter slipping through.
2. Safety Mechanisms
A. dry_run (global and per-item)
The Publisher honors config.dry_run || item.syndication.dry_run. When true:
- No HTTP writes to X, GitHub, or Open Collective.
- RSS file is not mutated (only “would update” logs).
- MCP
vox_news_test_syndicateforces dry-run and omits tokens.
B. Single source of truth (types + validation)
- GitHub:
GitHubPostType(Release|Discussion) with serde-friendly YAML.Discussionrequiresdiscussion_category.Releaseusesrelease_tag(defaults to news id) and supportsdraft. - Defaults:
vox_publisher::contractcentralizes site URL, feed path, and API bases. - Templates: canonical Markdown lives under
crates/vox-publisher/news-templates/(embedded at compile time). Human-facing copies may exist underdocs/news/templates/but the crate directory is authoritative when they differ.
C. Maker–checker (two approvers) + “armed” gate
For live syndication (!orchestrator.news.dry_run and !item.syndication.dry_run):
- VoxDb must be attached.
publication_approvalsmust contain two distinctapprovervalues for the publicationid+ current content digest (content_sha3_256) (MCP:vox_news_approveand scientia publication tools).publish_armedmust be true in[orchestrator.news]or environmentVOX_NEWS_PUBLISH_ARMED=1(see env-vars.md).
If any check fails, NewsService skips the item (no publish, no published_news row).
D. Idempotency (published_news)
Before work, NewsService skips items whose published_news row matches the current content_sha3_256 (legacy NULL-digest rows still block until backfilled; digest-aware republish when body changes). Each publish attempt is recorded in publication_attempts (news_publish_attempts is legacy). After a successful live publish with no enabled-channel failures, mark_news_published stores the content digest plus GitHub, Twitter, and Open Collective ids, and the canonical publication state transitions to published.
E. Discovery
NewsService walks news_dir recursively by default (scan_recursive), so docs/news/drafts/*.md is picked up once drafts are under the configured tree.
3. MCP tools
| Tool | Role |
|---|---|
vox_news_test_syndicate | Parse + dry-run publish_all (no tokens). |
vox_news_draft_research | Write docs/news/drafts/{id}.md from the embedded research template. |
vox_news_approve | Append approval row (requires VoxDb). |
vox_news_approval_status | Distinct approver count / dual flag. |
vox_news_simulate_publish_gate | Explain blockers for live publish without posting. |
Strict JSON input schemas are registered in vox-mcp input_schemas.rs.
4. Tests (no production posts)
vox-publisher:dry_run_tests, local HTTP mock tests for X + Open Collective.vox-db:news_approval_testsfor dual approval andpublished_newscolumn mapping.