Blog delivery contract skill

The contract every blog must pass before being presented to the user.

by AgriciDaniel·MIT license·★ 2,219 Stars on the repo·GitHub ↗

Use now

Files of Blog delivery contract

AgriciDaniel/main1 file
blog-delivery-contract.md
Show the full text162 lines

Blog Delivery Contract

The contract every blog must pass before being presented to the user. Five gates that fire automatically between content generation and delivery, plus an iteration loop that retries failures up to three times before escalating.

This contract is the v1.9.0 answer to a failure pattern from the v1.8.x cycle: skills had reviewers, but the reviewer ran as advisory and the writer presented sloppy drafts anyway. The fix is infrastructure, not effort. Same shape as scripts/lint_prose.py (v1.8.4) and tests/test_installer_sync.py (v1.8.6).

Gate summary

Gate What it enforces Failure mode Implementation
1. Capability Discovery Required tools + agents are available before write begins Block if no valid local hero and no permitted image path; block if reviewer agent missing scripts/blog_preflight.py --gate 1
2. Format Completeness .md + .html + .pdf + hero.png all present Block on any missing artifact scripts/blog_render.py produces all three
3. Visual Verification Rendered HTML has no SVG overflow, no console errors, valid JSON-LD Block on any defect; preserve screenshots scripts/blog_preflight.py --gate 3 via patchright
4. Content Review blog-reviewer scores ≥ 90/100 AND zero P0 issues Block + iterate agents/blog-reviewer.md (now blocking)
5. Asset + Link Integrity Every <img> resolves, every <a> returns 200, schema validates Block on any 404 or count mismatch scripts/blog_preflight.py --gate 5

All gates run sequentially. First failure halts the chain and triggers the iteration loop. Successful drafts ship with preflight-report.json + review.md + preview/*.png in the draft folder.

Gate 1: Capability Discovery

Runs once at the start of /blog write or /blog rewrite. Enumerates the project's available capabilities and writes <draft-folder>/capabilities.json. Every later gate consumes this artifact rather than re-detecting.

What gets enumerated
  • MCP servers loaded: nanobanana-mcp, dataforseo-mcp, others. Detected via tool availability, not by reading .mcp.json (the file may declare servers that failed to start).
  • Env vars present: GOOGLE_AI_API_KEY, UNSPLASH_ACCESS_KEY, PEXELS_API_KEY, PIXABAY_API_KEY. Key names only; values never read or logged.
  • Optional Python deps: patchright, weasyprint, google-genai, requests. Probed via importlib.util.find_spec().
  • Project-root context files: BRAND.md, VOICE.md, DISCOURSE.md. Loaded via scripts/load_untrusted_root.py (the existing v1.8.3 helper).
  • Agents available: blog-reviewer is mandatory; blog-researcher, blog-writer, blog-seo, blog-translator are optional.
  • Helper scripts present: scripts/lint_prose.py, scripts/analyze_blog.py, the new scripts/blog_preflight.py itself.
Failure modes
  • No hero path at all (no valid local hero.png or .jpg, no Banana MCP, no Gemini key, no stock API key, and Openverse unreachable): BLOCK with explicit setup instructions.
  • Reviewer agent missing: BLOCK. Cannot enforce Gate 4 without it.
  • Capability declared but unused: WARN (informational, not blocking). Example: dataforseo-mcp is loaded but the post topic doesn't need keyword research.

Gate 2: Format Completeness

Every delivered blog ships with four artifacts in <draft-folder>/:

  • <slug>.md: canonical source of truth. Frontmatter + prose + figure references. The .html and .pdf are rendered from this; they cannot diverge by construction.
  • <slug>.html: self-contained, valid HTML5, JSON-LD BlogPosting schema, Open Graph + Twitter Card meta, dark-mode-aware CSS via prefers-color-scheme, real <img> tag for hero (never a chart-as-hero).
  • <slug>.pdf: generated from rendered HTML via patchright's page.pdf(), or weasyprint as fallback when Playwright is unavailable.
  • hero.png (or .jpg): 1200×630 raster image. Either generated by an image-gen path or downloaded from a CC-licensed stock source. Never hot-linked; always lives in the draft folder.

Implementation lives in scripts/blog_render.py. Failure to produce any of the four artifacts blocks delivery.

Optional hygiene pass (non-blocking)

After rendering and before Gate 3, you may run python3 scripts/blog_hygiene.py --md <slug>.md --html <slug>.html --apply to auto-apply judgment-free fixes: add loading="lazy" to images that lack it and insert a Table of Contents on posts over 2000 words. It is optional, never blocks delivery, and reports any images missing alt text (which a human must fill in). It does not replace any gate.

Gate 3: Visual Verification

Renders the .html in headless patchright at three viewport widths and checks for visual defects. This is the "review before present" step.

Viewport widths
  • 375×812 (mobile, iPhone SE class)
  • 768×1024 (tablet, iPad portrait)
  • 1280×800 (desktop)
Checks per viewport
  1. Full-page screenshot: saved to <draft-folder>/preview/<width>.png for inspection.
  2. SVG bounding box check: for every <svg> and <figure> element, query getBoundingClientRect() on the element and on every descendant text, path, rect, and image child. Assert no descendant overflows its parent SVG viewBox. This catches the exact class of defect from the rankenstein.pro draft: labels positioned outside the chart area, dashed lines passing through annotation text.
  3. Dark-mode pass: re-render with prefers-color-scheme: dark emulation. Assert the body background-color differs from light mode. This catches the var() in attribute regression where dark mode silently fails to swap colors because CSS custom properties were used in SVG XML attributes (which don't reliably resolve).
  4. Console errors: capture browser console output during render. Assert zero errors.
  5. JSON-LD validation: parse the <script type="application/ld+json"> block. Assert valid JSON. Assert @type: BlogPosting with required fields (headline, image, datePublished, author).
Renderer requirement

Strict delivery requires patchright or an equivalent renderer. If no renderer is available, Gate 3 blocks and marks the draft non-shippable. Operators may use --no-strict for an intermediate local preview, but the output must not be presented as passed.

Gate 4: Content Review (BLOCKING)

The existing blog-reviewer agent (agents/blog-reviewer.md) runs against the rendered .html (not the raw .md). Reviewer output is now blocking, not advisory.

Blocking decision rules
  • Overall score < 90/100 → BLOCK
  • Any P0 issue from editorial-heuristics.md → BLOCK (a draft can score 95 and still have one load-bearing fabricated stat; P0 is an absolute filter independent of the numeric score)
  • AI-detection burstiness flag OR more than 3 known AI phrases OR vocabulary diversity (TTR) below 0.4 → BLOCK
  • All clear → proceed to Gate 5

The blocking decision is emitted as the last line of the reviewer scorecard, in the format:

BLOCKING: true (Overall 87/100 below threshold; P0 on heuristic 5)
BLOCKING: false (cleared all gates)

Machine-readable by scripts/blog_preflight.py so the orchestrator does not have to parse the human-readable scorecard.

Reviewer report saved to <draft-folder>/review.md. Shown to the user on success ("here is why this passed") and on final failure ("here is why this is still blocked after 3 iterations").

  • Every <img src="..."> resolves. Local paths must stay under the draft root after resolve(), refuse symlinks, and use slug-sanitized filenames. Absolute URLs must use http or https only; reject javascript:, data:, file:, protocol-relative URLs, credentials in URLs, and invalid hosts.
  • External URL checks must resolve DNS before connecting and reject loopback, private, link-local, multicast, reserved, unspecified, and cloud-metadata IP ranges. Do not follow redirects automatically; if redirects are allowed, validate every redirect target with the same checks. Use 5s timeouts, response-size caps, and no request bodies.
  • Validate links with HEAD first, then fall back to GET with a small range request when servers block HEAD. Accept valid 2xx or 3xx responses, and allow documented 403 or 405 cases only through the per-project external-links.allowed config.
  • og:image URL resolves with the same SSRF, redirect, size, and timeout rules; this is the load-bearing social-preview asset.
  • Every <a href="https://..."> resolves under the same policy, or is in the per-project external-links.allowed config.
  • Every <code>filename.ext</code> mention either references a real file in the project (verified via Path.exists()) or is wrapped in a "hypothetical example" marker.
  • <link rel="canonical"> is set and well-formed.
  • JSON-LD wordCount matches actual <article> word count within ±5%. Catches the "I claimed 1,715 words but the body is 1,400" honesty defect.

Hero Image Generation Ladder

Tried in order. First success wins. Skip steps for capabilities not available per Gate 1's capabilities.json.

  1. Banana MCP (nanobanana-mcp loaded as a tool, not just declared in .mcp.json): call its generate_image tool with an optimized six-component prompt (Subject + Action + Context + Composition + Lighting + Style) targeting 1200×630.
  2. Direct Gemini API (GOOGLE_AI_API_KEY present, MCP not loaded): call the google-genai SDK with gemini-3.1-flash-image by default. Fallback, in order, to gemini-3.1-flash-lite-image and gemini-3-pro-image. See https://ai.google.dev/gemini-api/docs/image-generation.
  3. Premium stock APIs (UNSPLASH_ACCESS_KEY, PEXELS_API_KEY, or PIXABAY_API_KEY present): search via the official API using post title + top tags as query. Do not scrape or construct raw CDN URLs. Capture each source's license metadata and attribution requirements, download the asset locally, and write hero-credit.txt. Unsplash, Pexels, and Pixabay use their own licenses; do not treat them as CC sources.
  4. Openverse public API (no key required): GET https://api.openverse.org/v1/images/?q=<query>&aspect_ratio=wide&license=cc0,by. Pick the top relevance match with complete attribution. Always download to hero.<ext> plus hero-credit.txt for CC attribution.
  5. Block with clear error: "Hero image required but no generation path available. Configure Banana MCP, set GOOGLE_AI_API_KEY, set UNSPLASH/PEXELS/PIXABAY key, or place a 1200×630 hero.png in the draft folder manually."

Implementation: scripts/generate_hero.py. Always writes hero-credit.txt next to hero.<ext> for attribution compliance, even when generation paths 1-2 (AI-generated, no attribution needed) are used. The file then contains "AI-generated; no attribution required."

Iteration Loop

When any gate fails, the orchestrator (skills/blog/SKILL.md) drives a retry loop:

  1. Capture diagnostic: which gate failed, which specific check, screenshot if visual, scorecard if content.
  2. Construct iteration prompt keyed to the failing gate:
    • Gate 2 missing artifact → re-run scripts/blog_render.py after fixing the source .md
    • Gate 3 visual fail → re-run scripts/blog_render.py with adjusted layout (e.g. shrink SVG inner area, wrap long labels)
    • Gate 4 score < 90 → re-dispatch blog-writer agent with the reviewer report as input and an instruction to fix the lowest-scoring category first
    • Gate 4 P0 issue → re-dispatch with a targeted instruction for that specific P0
    • Gate 5 404 → re-dispatch with instruction to remove or replace the broken URL
  3. Re-run all five gates from Gate 1. (Re-running Gate 1 catches the case where an iteration changed capabilities, e.g. installed a missing dep.)
  4. On pass: present draft to user with a one-line summary ("Delivered after N iteration(s)").
  5. On fail after 3 iterations: STOP. Show the user the failure diagnostic for each remaining defect, the partial draft, the latest reviewer report, and an explicit "Manual fix required" message. Do not iterate further automatically.

The orchestrator holds the loop counter. Sub-skills never loop themselves; that would risk infinite loops if two sub-skills disagree.

Bypass Mechanism

Strict mode is the default. Users can override only via an explicit operator-controlled --no-strict flag on scripts/blog_preflight.py or signed/trusted project config. Draft frontmatter is untrusted content and must never disable delivery gates. Any bypass logs loudly:

WARNING: Delivery contract bypassed. Failed gates: [Gate 3, Gate 5].
The draft is being presented anyway per --no-strict. Do not publish without manual review.

Bypass is intended for two cases: (1) the contract has a false positive the user has verified, (2) the user is iterating on a draft and wants to see intermediate output before the gates pass. It is not intended for shipping. A future CI workflow will reject merges where preflight-report.json shows "blocked": true && "strict": false (the bypassed-and-published case); that enforcement is not yet implemented.

References

  • skills/blog/references/quality-scoring.md: the 100-point numeric scoring rubric used by Gate 4
  • skills/blog/references/editorial-heuristics.md: the P0-P3 ordinal scoring used for the P0 filter in Gate 4
  • skills/blog/references/visual-media.md: image and asset standards consumed by Gate 5
  • skills/blog/references/schema-stack.md: JSON-LD structure validated by Gate 3 step 5
  • agents/blog-reviewer.md: the reviewer agent that produces the Gate 4 scorecard
  • scripts/load_untrusted_root.py: the v1.8.3 helper used for project-root file loading in Gate 1
  • scripts/lint_prose.py: the v1.8.4 prose linter run as part of Gate 4's editorial-heuristics scoring
  • tests/test_blog_delivery_contract.py: coherence test that asserts this contract and its implementation stay in sync

How this contract maps to the v1.8.x lesson

The rankenstein.pro draft failure was a Category-3 defect: a contradiction between what the project's tooling could do (Banana MCP, blog-reviewer, /blog image, Playwright) and what the writer actually invoked (none of them). The v1.8.x lesson says: convert Category-3 into a gate or a test, not into more discipline. This contract is exactly that. Five gates fire automatically. The writer cannot forget to use the tools because the tools are wired into the gates themselves.

1# Blog Delivery Contract
2 
3The contract every blog must pass before being presented to the user. Five gates that fire automatically between content generation and delivery, plus an iteration loop that retries failures up to three times before escalating.
4 
5This contract is the v1.9.0 answer to a failure pattern from the v1.8.x cycle: skills had reviewers, but the reviewer ran as advisory and the writer presented sloppy drafts anyway. The fix is infrastructure, not effort. Same shape as `scripts/lint_prose.py` (v1.8.4) and `tests/test_installer_sync.py` (v1.8.6).
6 
7## Gate summary
8 
9| Gate | What it enforces | Failure mode | Implementation |
10|---|---|---|---|
11| 1. Capability Discovery | Required tools + agents are available before write begins | Block if no valid local hero and no permitted image path; block if reviewer agent missing | `scripts/blog_preflight.py --gate 1` |
12| 2. Format Completeness | `.md` + `.html` + `.pdf` + `hero.png` all present | Block on any missing artifact | `scripts/blog_render.py` produces all three |
13| 3. Visual Verification | Rendered HTML has no SVG overflow, no console errors, valid JSON-LD | Block on any defect; preserve screenshots | `scripts/blog_preflight.py --gate 3` via `patchright` |
14| 4. Content Review | `blog-reviewer` scores ≥ 90/100 AND zero P0 issues | Block + iterate | `agents/blog-reviewer.md` (now blocking) |
15| 5. Asset + Link Integrity | Every `<img>` resolves, every `<a>` returns 200, schema validates | Block on any 404 or count mismatch | `scripts/blog_preflight.py --gate 5` |
16 
17All gates run sequentially. First failure halts the chain and triggers the iteration loop. Successful drafts ship with `preflight-report.json` + `review.md` + `preview/*.png` in the draft folder.
18 
19## Gate 1: Capability Discovery
20 
21Runs once at the start of `/blog write` or `/blog rewrite`. Enumerates the project's available capabilities and writes `<draft-folder>/capabilities.json`. Every later gate consumes this artifact rather than re-detecting.
22 
23### What gets enumerated
24 
25- **MCP servers loaded**: `nanobanana-mcp`, `dataforseo-mcp`, others. Detected via tool availability, not by reading `.mcp.json` (the file may declare servers that failed to start).
26- **Env vars present**: `GOOGLE_AI_API_KEY`, `UNSPLASH_ACCESS_KEY`, `PEXELS_API_KEY`, `PIXABAY_API_KEY`. Key names only; values never read or logged.
27- **Optional Python deps**: `patchright`, `weasyprint`, `google-genai`, `requests`. Probed via `importlib.util.find_spec()`.
28- **Project-root context files**: `BRAND.md`, `VOICE.md`, `DISCOURSE.md`. Loaded via `scripts/load_untrusted_root.py` (the existing v1.8.3 helper).
29- **Agents available**: `blog-reviewer` is mandatory; `blog-researcher`, `blog-writer`, `blog-seo`, `blog-translator` are optional.
30- **Helper scripts present**: `scripts/lint_prose.py`, `scripts/analyze_blog.py`, the new `scripts/blog_preflight.py` itself.
31 
32### Failure modes
33 
34- **No hero path at all** (no valid local `hero.png` or `.jpg`, no Banana MCP, no Gemini key, no stock API key, and Openverse unreachable): BLOCK with explicit setup instructions.
35- **Reviewer agent missing**: BLOCK. Cannot enforce Gate 4 without it.
36- **Capability declared but unused**: WARN (informational, not blocking). Example: `dataforseo-mcp` is loaded but the post topic doesn't need keyword research.
37 
38## Gate 2: Format Completeness
39 
40Every delivered blog ships with four artifacts in `<draft-folder>/`:
41 
42- `<slug>.md`: canonical source of truth. Frontmatter + prose + figure references. The `.html` and `.pdf` are rendered from this; they cannot diverge by construction.
43- `<slug>.html`: self-contained, valid HTML5, JSON-LD `BlogPosting` schema, Open Graph + Twitter Card meta, dark-mode-aware CSS via `prefers-color-scheme`, real `<img>` tag for hero (never a chart-as-hero).
44- `<slug>.pdf`: generated from rendered HTML via `patchright`'s `page.pdf()`, or `weasyprint` as fallback when Playwright is unavailable.
45- `hero.png` (or `.jpg`): 1200×630 raster image. Either generated by an image-gen path or downloaded from a CC-licensed stock source. Never hot-linked; always lives in the draft folder.
46 
47Implementation lives in `scripts/blog_render.py`. Failure to produce any of the four artifacts blocks delivery.
48 
49### Optional hygiene pass (non-blocking)
50 
51After rendering and before Gate 3, you may run `python3 scripts/blog_hygiene.py --md <slug>.md --html <slug>.html --apply` to auto-apply judgment-free fixes: add `loading="lazy"` to images that lack it and insert a Table of Contents on posts over 2000 words. It is optional, never blocks delivery, and reports any images missing alt text (which a human must fill in). It does not replace any gate.
52 
53## Gate 3: Visual Verification
54 
55Renders the `.html` in headless `patchright` at three viewport widths and checks for visual defects. This is the "review before present" step.
56 
57### Viewport widths
58 
59- 375×812 (mobile, iPhone SE class)
60- 768×1024 (tablet, iPad portrait)
61- 1280×800 (desktop)
62 
63### Checks per viewport
64 
651. **Full-page screenshot**: saved to `<draft-folder>/preview/<width>.png` for inspection.
662. **SVG bounding box check**: for every `<svg>` and `<figure>` element, query `getBoundingClientRect()` on the element and on every descendant `text`, `path`, `rect`, and `image` child. Assert no descendant overflows its parent SVG `viewBox`. This catches the exact class of defect from the rankenstein.pro draft: labels positioned outside the chart area, dashed lines passing through annotation text.
673. **Dark-mode pass**: re-render with `prefers-color-scheme: dark` emulation. Assert the body `background-color` differs from light mode. This catches the `var()` in attribute regression where dark mode silently fails to swap colors because CSS custom properties were used in SVG XML attributes (which don't reliably resolve).
684. **Console errors**: capture browser console output during render. Assert zero errors.
695. **JSON-LD validation**: parse the `<script type="application/ld+json">` block. Assert valid JSON. Assert `@type: BlogPosting` with required fields (`headline`, `image`, `datePublished`, `author`).
70 
71### Renderer requirement
72 
73Strict delivery requires `patchright` or an equivalent renderer. If no renderer is available, Gate 3 blocks and marks the draft non-shippable. Operators may use `--no-strict` for an intermediate local preview, but the output must not be presented as passed.
74 
75## Gate 4: Content Review (BLOCKING)
76 
77The existing `blog-reviewer` agent (`agents/blog-reviewer.md`) runs against the rendered `.html` (not the raw `.md`). Reviewer output is now **blocking**, not advisory.
78 
79### Blocking decision rules
80 
81- Overall score **< 90/100** → BLOCK
82- **Any P0 issue** from `editorial-heuristics.md` → BLOCK (a draft can score 95 and still have one load-bearing fabricated stat; P0 is an absolute filter independent of the numeric score)
83- AI-detection burstiness flag OR more than 3 known AI phrases OR vocabulary diversity (TTR) below 0.4 → BLOCK
84- All clear → proceed to Gate 5
85 
86The blocking decision is emitted as the last line of the reviewer scorecard, in the format:
87 
88```
89BLOCKING: true (Overall 87/100 below threshold; P0 on heuristic 5)
90BLOCKING: false (cleared all gates)
91```
92 
93Machine-readable by `scripts/blog_preflight.py` so the orchestrator does not have to parse the human-readable scorecard.
94 
95Reviewer report saved to `<draft-folder>/review.md`. Shown to the user on success ("here is why this passed") and on final failure ("here is why this is still blocked after 3 iterations").
96 
97## Gate 5: Asset Existence + Link Integrity
98 
99- Every `<img src="...">` resolves. Local paths must stay under the draft root after `resolve()`, refuse symlinks, and use slug-sanitized filenames. Absolute URLs must use `http` or `https` only; reject `javascript:`, `data:`, `file:`, protocol-relative URLs, credentials in URLs, and invalid hosts.
100- External URL checks must resolve DNS before connecting and reject loopback, private, link-local, multicast, reserved, unspecified, and cloud-metadata IP ranges. Do not follow redirects automatically; if redirects are allowed, validate every redirect target with the same checks. Use 5s timeouts, response-size caps, and no request bodies.
101- Validate links with `HEAD` first, then fall back to `GET` with a small range request when servers block `HEAD`. Accept valid 2xx or 3xx responses, and allow documented 403 or 405 cases only through the per-project `external-links.allowed` config.
102- `og:image` URL resolves with the same SSRF, redirect, size, and timeout rules; this is the load-bearing social-preview asset.
103- Every `<a href="https://...">` resolves under the same policy, or is in the per-project `external-links.allowed` config.
104- Every `<code>filename.ext</code>` mention either references a real file in the project (verified via `Path.exists()`) or is wrapped in a "hypothetical example" marker.
105- `<link rel="canonical">` is set and well-formed.
106- JSON-LD `wordCount` matches actual `<article>` word count within ±5%. Catches the "I claimed 1,715 words but the body is 1,400" honesty defect.
107 
108## Hero Image Generation Ladder
109 
110Tried in order. First success wins. Skip steps for capabilities not available per Gate 1's `capabilities.json`.
111 
1121. **Banana MCP** (`nanobanana-mcp` loaded as a tool, not just declared in `.mcp.json`): call its `generate_image` tool with an optimized six-component prompt (Subject + Action + Context + Composition + Lighting + Style) targeting 1200×630.
1132. **Direct Gemini API** (`GOOGLE_AI_API_KEY` present, MCP not loaded): call the `google-genai` SDK with `gemini-3.1-flash-image` by default. Fallback, in order, to `gemini-3.1-flash-lite-image` and `gemini-3-pro-image`. See `https://ai.google.dev/gemini-api/docs/image-generation`.
1143. **Premium stock APIs** (`UNSPLASH_ACCESS_KEY`, `PEXELS_API_KEY`, or `PIXABAY_API_KEY` present): search via the official API using post title + top tags as query. Do not scrape or construct raw CDN URLs. Capture each source's license metadata and attribution requirements, download the asset locally, and write `hero-credit.txt`. Unsplash, Pexels, and Pixabay use their own licenses; do not treat them as CC sources.
1154. **Openverse public API** (no key required): `GET https://api.openverse.org/v1/images/?q=<query>&aspect_ratio=wide&license=cc0,by`. Pick the top relevance match with complete attribution. Always download to `hero.<ext>` plus `hero-credit.txt` for CC attribution.
1165. **Block with clear error**: "Hero image required but no generation path available. Configure Banana MCP, set GOOGLE_AI_API_KEY, set UNSPLASH/PEXELS/PIXABAY key, or place a 1200×630 hero.png in the draft folder manually."
117 
118Implementation: `scripts/generate_hero.py`. Always writes `hero-credit.txt` next to `hero.<ext>` for attribution compliance, even when generation paths 1-2 (AI-generated, no attribution needed) are used. The file then contains "AI-generated; no attribution required."
119 
120## Iteration Loop
121 
122When any gate fails, the orchestrator (`skills/blog/SKILL.md`) drives a retry loop:
123 
1241. **Capture diagnostic**: which gate failed, which specific check, screenshot if visual, scorecard if content.
1252. **Construct iteration prompt** keyed to the failing gate:
126 - Gate 2 missing artifact → re-run `scripts/blog_render.py` after fixing the source `.md`
127 - Gate 3 visual fail → re-run `scripts/blog_render.py` with adjusted layout (e.g. shrink SVG inner area, wrap long labels)
128 - Gate 4 score < 90 → re-dispatch `blog-writer` agent with the reviewer report as input and an instruction to fix the lowest-scoring category first
129 - Gate 4 P0 issue → re-dispatch with a targeted instruction for that specific P0
130 - Gate 5 404 → re-dispatch with instruction to remove or replace the broken URL
1313. **Re-run all five gates** from Gate 1. (Re-running Gate 1 catches the case where an iteration changed capabilities, e.g. installed a missing dep.)
1324. **On pass**: present draft to user with a one-line summary ("Delivered after N iteration(s)").
1335. **On fail after 3 iterations**: STOP. Show the user the failure diagnostic for each remaining defect, the partial draft, the latest reviewer report, and an explicit "Manual fix required" message. Do not iterate further automatically.
134 
135The orchestrator holds the loop counter. Sub-skills never loop themselves; that would risk infinite loops if two sub-skills disagree.
136 
137## Bypass Mechanism
138 
139Strict mode is the default. Users can override only via an explicit operator-controlled `--no-strict` flag on `scripts/blog_preflight.py` or signed/trusted project config. Draft frontmatter is untrusted content and must never disable delivery gates. Any bypass logs loudly:
140 
141```
142WARNING: Delivery contract bypassed. Failed gates: [Gate 3, Gate 5].
143The draft is being presented anyway per --no-strict. Do not publish without manual review.
144```
145 
146Bypass is intended for two cases: (1) the contract has a false positive the user has verified, (2) the user is iterating on a draft and wants to see intermediate output before the gates pass. It is not intended for shipping. A future CI workflow will reject merges where `preflight-report.json` shows `"blocked": true && "strict": false` (the bypassed-and-published case); that enforcement is not yet implemented.
147 
148## References
149 
150- `skills/blog/references/quality-scoring.md`: the 100-point numeric scoring rubric used by Gate 4
151- `skills/blog/references/editorial-heuristics.md`: the P0-P3 ordinal scoring used for the P0 filter in Gate 4
152- `skills/blog/references/visual-media.md`: image and asset standards consumed by Gate 5
153- `skills/blog/references/schema-stack.md`: JSON-LD structure validated by Gate 3 step 5
154- `agents/blog-reviewer.md`: the reviewer agent that produces the Gate 4 scorecard
155- `scripts/load_untrusted_root.py`: the v1.8.3 helper used for project-root file loading in Gate 1
156- `scripts/lint_prose.py`: the v1.8.4 prose linter run as part of Gate 4's editorial-heuristics scoring
157- `tests/test_blog_delivery_contract.py`: coherence test that asserts this contract and its implementation stay in sync
158 
159## How this contract maps to the v1.8.x lesson
160 
161The rankenstein.pro draft failure was a Category-3 defect: a contradiction between what the project's tooling could do (Banana MCP, blog-reviewer, /blog image, Playwright) and what the writer actually invoked (none of them). The v1.8.x lesson says: convert Category-3 into a gate or a test, not into more discipline. This contract is exactly that. Five gates fire automatically. The writer cannot forget to use the tools because the tools are wired into the gates themselves.
162 

Discussion

Alternatives

Employment contract templatesCreate employment contracts, offer letters, and HR policy documents following legal best practices. Use when drafting employment agreements, creating HR policies, or standardizing employment documentation.Business & ops · MITNDA (Non-Disclosure Agreement) DraftingDraft a detailed Non-Disclosure Agreement between two parties covering information types, jurisdiction, and clauses needing legal review. Use when creating confidentiality agreements or preparing an NDA for a partnership. · MITContract & Proposal WriterGenerate professional, jurisdiction-aware business documents: freelance contracts, project proposals, SOWs, NDAs, and MSAs. Structured Markdown output with docx conversion instructions. Covers US (Delaware), EU (GDPR), UK, and DACH (German law) jurisdictions. Not a substitute for legal counsel — use as strong starting points. Use when drafting a freelance contract, preparing a client proposal, writing an SOW for a new engagement, or producing an NDA before sharing sensitive material.Business & ops · MITGeneral counsel advisorGeneral Counsel advisory for startups: contract review (MSA, SaaS, NDA, DPA, employment), IP strategy, term sheet decoding, and regulatory landscape mapping. Use when reviewing any contract or term sheet, deciding when to engage outside counsel, defining IP strategy, evaluating regulatory exposure (HIPAA, GDPR, FDA, fintech), or when user mentions general counsel, GC, legal review, contract risk, term sheet, IP assignment, or regulatory exposure. NOT a substitute for licensed counsel — surfaces questions to bring to qualified attorneys. · MIT