Day 38 — Publish Through Gates
The original workflow deliberately writes the article before asking another model call to package it as metadata, HTML, and JSON-LD. Keep that separation. Packaging is a transformation that can lose content or create invalid markup.
Give every stage a completion contract
Section titled “Give every stage a completion contract”Rankforge’s baseline validator requires recognizable sections:
let required: &[&str] = match stage { Stage::Research => &["## Business summary", "## Search intents"], Stage::Angle => &["## Primary keyword", "## Working title"], Stage::Outline => &["# "], Stage::Article => &["# ", "## FAQ"], Stage::SeoPackage => &["## Meta", "## HTML", "## JSON-LD"],};This is syntactic validation only. It proves that a section marker exists, not that the angle is good, the FAQ answers real questions, the HTML is safe, or the structured data matches the visible article. Name the claim your validator actually establishes.
Turn prompt rules into code
Section titled “Turn prompt rules into code”The TypeScript prompt forbids em dashes, en dashes, and semicolons. A prompt request is not enforcement. Rankforge scans the article and delivery package and rejects forbidden punctuation.
This small rule teaches a larger pattern:
deterministic requirement available? ─▶ enforce in codesemantic judgment required? ─▶ evaluate with evidence and calibrated reviewUse code for one H1, required metadata fields, valid JSON parsing, safe HTML elements, URL syntax, and length bounds. Use evaluators or humans for usefulness, originality, tone, and whether an answer satisfies intent.
Prevent the packaging stage from thinning the article
Section titled “Prevent the packaging stage from thinning the article”The baseline saves both blog-4-article.md and blog-5-seo-package.md. Add a coverage evaluator that
extracts article headings and checks whether every accepted section appears in the HTML. Parse the
JSON-LD fenced block into serde_json::Value and verify:
@typematches the visible content;- the headline matches the article H1;
- FAQ questions and answers exist visibly in the HTML;
- no unsupported rating, price, address, or date appears;
- the canonical URL belongs to the configured site.
Do not publish JSON-LD simply because it parses.
Break it deliberately
Section titled “Break it deliberately”Copy the fixture and make one mutation at a time:
- remove
## FAQfrom the article; - place a semicolon in its body;
- remove
## JSON-LDfrom the package; - put valid JSON-LD under the wrong heading;
- add a supported section to Markdown but omit it from HTML.
Classify each failure as syntax, structure, semantic consistency, factual support, or editorial quality. Different failure layers need different repair behavior.
What you built
Section titled “What you built”An article and its SEO package are now separate, inspectable artifacts with explicit acceptance rules. The model can draft and transform. Rust decides whether the delivery shape is complete enough to move forward.