How we publish.
Two non-negotiable rules. Six chained stages. The brief commits only past its gates.
Section 1 · 2 non-negotiable rules
The two rules.
Rule #1 Specs come from a hand-curated YAML catalog.
No model invents a spec. Every numeric token in review, guide, rig, component and changelog prose must trace back to content/_specs/<slug>.yaml or the build fails. Briefs are exempt as a collection, because their numbers come from the sources they cite: a brief commits only when its headline number is printed in the source it cites.
CI evidence ↗
- validator·
scripts/validate-specs.ts - doctrine·
content/_specs/_README.md - schema·
content/_specs/_SCHEMA.md - CI step·
.github/workflows/content-gate.yml · validate:specs - brief exemption·
content/_specs/_grandfathered.yaml · collections - brief gate·
lib/synthesize/orchestrator.ts · numberIsSourced
- validator·
Rule #2 The LLM is forbidden from voice-bearing sections.
Voice-bearing headings — Verdict, Intro, Recommendation — may only appear in documents whose frontmatter author is on the allowlist, or the build fails. The validator checks headings, not who wrote the prose: the daily brief is written by a model (stage 6 below) and carries none of those sections.
CI evidence ↗
- validator·
scripts/validate-authorship.ts - CI step·
.github/workflows/content-gate.yml · validate:authorship
- validator·
Section 2 · 6 stages
The pipeline, stage by stage.
Every stage runs on a Vercel cron. Each one writes only its declared output and holds a single boundary it never crosses.
Run order · Gate’s PRs are drafts that wait for review · the brief commits to main past its gates
| Stage | Cron | Reads | Writes | Never |
|---|---|---|---|---|
| 1. Catch | hourly, on the hour | Ten sources: HN Algolia, Phoronix, the NVIDIA Developer Blog, r/LocalLLaMA, arXiv cs.AR, TechCrunch AI, The Verge AI, two YouTube channel lists (AI, robotics), and frontier-lab news feeds | mcs_signal_candidate (deduped by source + source_id) | persists Reddit body text — only title, score, permalink, url, created_utc (ToS perimeter). |
| 2. Cluster | hourly, at :20 | unprocessed mcs_signal_candidate rows in a 48-hour window | mcs_signal_cluster + mcs_signal_cluster_member (3-shingle Jaccard ≥ 0.45) | uses an LLM or embeddings — pure JS, lead-only centroid, deterministic. |
| 3. Strip | hourly, at :40 | mature clusters (closed_at NOT NULL OR member_count ≥ 3) | topic_label heuristically + mcs_signal_claim rows (kind: spec | benchmark | event | price) | writes voice — no Verdict, Intro, or Recommendation prose. Subject vocab is allowlisted. |
| 4. Quantify | 11:00 UTC daily (one cron with Gate and Synthesize) | unverified mcs_signal_claim rows + the spec catalog | verdict on each claim: confirmed | conflict | novel | weak | writes copy — verdicts are advisory inputs to Gate, never published prose. |
| 5. Gate | 11:00 UTC daily (after Quantify) | ready clusters meeting threshold (≥1 conflict OR ≥3 novels OR ≥3 cluster members) | a draft GitHub PR per topic — changelog stub, conflict YAML, novel-spec stub | merges. A draft PR waits for review; nothing merges it automatically. |
| 6. Synthesize | 11:00 UTC daily (after Gate) | the last 36 hours of caught rows and the past week of drift detections | content/briefs/<date>.mdx, committed straight to main. The only stage that calls an LLM. | commits a brief with a dead link, or with a headline number its cited source does not print. Three failed drafts skip the day, and brief-watch files an issue. |
Stage 1 Catch
hourly, on the hour
- Reads
- Ten sources: HN Algolia, Phoronix, the NVIDIA Developer Blog, r/LocalLLaMA, arXiv cs.AR, TechCrunch AI, The Verge AI, two YouTube channel lists (AI, robotics), and frontier-lab news feeds
- Writes
- mcs_signal_candidate (deduped by source + source_id)
- Never
- persists Reddit body text — only title, score, permalink, url, created_utc (ToS perimeter).
Stage 2 Cluster
hourly, at :20
- Reads
- unprocessed mcs_signal_candidate rows in a 48-hour window
- Writes
- mcs_signal_cluster + mcs_signal_cluster_member (3-shingle Jaccard ≥ 0.45)
- Never
- uses an LLM or embeddings — pure JS, lead-only centroid, deterministic.
Stage 3 Strip
hourly, at :40
- Reads
- mature clusters (closed_at NOT NULL OR member_count ≥ 3)
- Writes
- topic_label heuristically + mcs_signal_claim rows (kind: spec | benchmark | event | price)
- Never
- writes voice — no Verdict, Intro, or Recommendation prose. Subject vocab is allowlisted.
Stage 4 Quantify
11:00 UTC daily (one cron with Gate and Synthesize)
- Reads
- unverified mcs_signal_claim rows + the spec catalog
- Writes
- verdict on each claim: confirmed | conflict | novel | weak
- Never
- writes copy — verdicts are advisory inputs to Gate, never published prose.
Stage 5 Gate
11:00 UTC daily (after Quantify)
- Reads
- ready clusters meeting threshold (≥1 conflict OR ≥3 novels OR ≥3 cluster members)
- Writes
- a draft GitHub PR per topic — changelog stub, conflict YAML, novel-spec stub
- Never
- merges. A draft PR waits for review; nothing merges it automatically.
Stage 6 Synthesize
11:00 UTC daily (after Gate)
- Reads
- the last 36 hours of caught rows and the past week of drift detections
- Writes
- content/briefs/<date>.mdx, committed straight to main. The only stage that calls an LLM.
- Never
- commits a brief with a dead link, or with a headline number its cited source does not print. Three failed drafts skip the day, and brief-watch files an issue.
Section 3 · Topology
One picture.
HN ─┐
Phoronix ─┤
NVIDIA ─┤
r/LocalL ─┤
arXiv ─┤
TechCrunch ─┼──→ Catch ──→ Cluster ──→ Strip ──→ Quantify ──→ Gate ──→ DRAFT PR
Verge AI ─┤ hourly hourly hourly 11:00 UTC 11:00 UTC │
YouTube AI ─┤ │ waits for review
YT robots ─┤ │ │
Lab news ─┘ │ ▼
│ /changelog
│
│
└──→ Synthesize ──→ link + number gates ──→ main ──→ /signal
11:00 UTC 3 drafts, then skip the dayTen sources fan in. Six stages run. Two ways out: Gate’s draft PRs wait for review, and the daily brief commits to main once its links resolve and its headline number checks out.
Section 4 · The boundary
What the LLM is and isn't allowed to do.
| Allowed | Forbidden | Enforced by |
|---|---|---|
| Draft the daily brief from the day’s caught sources (Synthesize). | Commit a brief whose headline number its cited source does not print, or whose links do not resolve. | lib/synthesize/orchestrator.ts + lib/links/check.ts (in-process, before the commit) |
| Recommend rigs from the catalog (The Specifier). | Invent a number in review, guide, rig, component or changelog prose. | scripts/validate-specs.ts (Rule #1) |
| Summarize an article in a PR description. | Write a verdict, intro, or recommendation paragraph. | scripts/validate-authorship.ts (Rule #2) |
| Render specs from YAML the human committed. | Persist Reddit body text into mcs_signal_candidate.raw_payload. | lib/catch/sources/reddit-localllama.ts perimeter (ToS, operational) |
Allowed
Draft the daily brief from the day’s caught sources (Synthesize).
Forbidden
Commit a brief whose headline number its cited source does not print, or whose links do not resolve.
Enforced by
lib/synthesize/orchestrator.ts + lib/links/check.ts (in-process, before the commit)Allowed
Recommend rigs from the catalog (The Specifier).
Forbidden
Invent a number in review, guide, rig, component or changelog prose.
Enforced by
scripts/validate-specs.ts (Rule #1)Allowed
Summarize an article in a PR description.
Forbidden
Write a verdict, intro, or recommendation paragraph.
Enforced by
scripts/validate-authorship.ts (Rule #2)Allowed
Render specs from YAML the human committed.
Forbidden
Persist Reddit body text into mcs_signal_candidate.raw_payload.
Enforced by
lib/catch/sources/reddit-localllama.ts perimeter (ToS, operational)