feat(blog): Title Contract + Technical Sanity Check + Self-Heal + angle-aware LinkedIn generator

Pipeline now has 21 steps:
- STEP0: Title Contract binds LLM to headline promise
- STEP19: Technical Sanity Check (optical engineering accuracy)
- STEP20: Self-Heal (auto-fix technical errors preserving tone)
- STEP21: Title Contract Verification (final gate check)
- LinkedIn generator is now angle-aware (no more default Physical Layer hook)
This commit is contained in:
Rene Fichtmueller 2026-04-05 23:11:16 +02:00
parent 7c8f545c18
commit 6c818c79b5
2 changed files with 267 additions and 25 deletions

View File

@ -299,6 +299,31 @@ CONTENT MODULES (use 2-3 per article):
- Vendor bullshit vs reality - Vendor bullshit vs reality
- When NOT to use this technology`; - When NOT to use this technology`;
// ═══════════════════════════════════════════════════════
// STEP 0: TITLE CONTRACT — binds the LLM to the headline promise
// ═══════════════════════════════════════════════════════
export const STEP0_TITLE_CONTRACT = `You are a senior technical editor. Your job is to analyze a blog title and create a binding contract that the article MUST fulfill.
Title: {{TITLE}}
Analyze the title and return EXACTLY this structure:
TOPIC DOMAIN: (what subject area e.g., "pricing comparison", "standards maturity", "migration process")
PROMISE TYPE: (what the title promises e.g., "COST ANALYSIS", "STATUS REPORT", "DECISION FRAMEWORK", "STEP-BY-STEP GUIDE", "MARKET TIMING")
REQUIRED CONTENT: (what MUST appear in the article to fulfill the title be specific, 3-5 bullet points)
FORBIDDEN CONTENT: (what would VIOLATE the title promise be specific)
READER TAKEAWAY: (what the reader should know/do after reading one sentence)
ANGLE TYPE: (A=Economic, B=Decision, C=Market, D=Operational, E=Political, F=Migration)
CRITICAL RULES:
- If the title says "Cost Difference" the article MUST be about costs, pricing, TCO. NOT about physical layer failures.
- If the title says "Production-Ready" the article MUST be a status report on maturity. NOT about what breaks.
- If the title says "Migration Guide" the article MUST be step-by-step. NOT about failure scenarios.
- If the title says "Comparison" the article MUST compare specific options. NOT tell a generic story.
The contract you produce will be injected into every subsequent pipeline step to prevent drift.`;
// ═══════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════
// STEP 1: TOPIC EXPANSION // STEP 1: TOPIC EXPANSION
// ═══════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════
@ -1045,6 +1070,135 @@ Return ONLY a JSON object:
Article: Article:
{{ARTICLE}}`; {{ARTICLE}}`;
// ═══════════════════════════════════════════════════════
// STEP 11: TECHNICAL SANITY CHECK — expert-level accuracy verification
// ═══════════════════════════════════════════════════════
export const STEP_TECHNICAL_SANITY = `You are a senior optical network engineer reviewing a blog post for technical correctness.
Your job is NOT to rewrite the text. Your job is to find technical inaccuracies, imprecise wording, or misleading statements.
Be strict. Assume the reader is an expert.
CHECK THESE AREAS:
1. MEASUREMENT vs OBSERVATION
- Does the text confuse inspection (visual, scope) with measurement (OPM, OLTS, DOM)?
- "inspection scope measures loss" WRONG. A scope shows contamination/defects. An OPM/OLTS measures loss.
- "inspection scope reveals insertion loss" WRONG. It reveals contamination that CAUSES insertion loss.
2. FIBER & OPTICS ACCURACY
- MMF vs SMF behavior correct distinctions?
- SR4 vs DR4 vs FR4 correct fiber counts, connectors, wavelengths?
- DR4 = MPO-12 (8 fibers, parallel, 1310nm). FR4 = LC duplex (CWDM4, 2km). NEVER mix these.
- Connector types correct? (MPO vs LC vs SC)
- Reach claims accurate?
3. OPTICAL BUDGET LOGIC
- TX_min / RX_sensitivity usage correct?
- Link budget calculations make sense?
- Margin discussion realistic? (DR4 4.8 dB budget, not more)
- DR4 fiber attenuation = 0.35 dB/km at 1310nm (NOT 1550nm which is 0.22 dB/km)
4. FAILURE BEHAVIOR REALISM
- CRC errors vs hard link down correct description?
- Intermittent vs permanent failure accurate?
- FEC behavior correct?
- "creeping errors" pattern realistic?
5. TERMINOLOGY PRECISION
- "contacts" for fiber WRONG. Use: connector end-faces, ferrules, mating points
- "signal loss" too generic. Use: insertion loss, return loss, or attenuation
- "worn contacts" WRONG for fiber. Use: contamination, alignment degradation, connector wear
- "patch panels worn contacts" WRONG. Use: aging connectors and accumulated contamination
6. CAUSE vs SYMPTOM
- Does the text correctly separate root cause from symptom?
- Does it avoid blaming optics when the issue is physical layer?
7. POWER SPECS
- 400G 10-15W per PORT (not per chassis)
- 800G 15-25W per PORT
- "1kW per port" HARD FAIL
Return ONLY this JSON:
{
"technical_score": <1-10>,
"critical_issues": [{"issue": "...", "why_wrong": "...", "fix": "..."}],
"precision_fixes": [{"original": "...", "problem": "...", "better": "..."}],
"safe_to_publish": true/false
}
Article:
{{ARTICLE}}`;
// ═══════════════════════════════════════════════════════
// STEP 12: SELF-HEAL — fix technical errors preserving tone
// ═══════════════════════════════════════════════════════
export const STEP_SELF_HEAL = `You are a senior optical network engineer and technical editor.
You have the original article and a technical sanity check report. Your task is to REPAIR the text.
RULES:
- Fix all critical technical issues identified in the report
- Sharpen imprecise wording (scope vs OPM, contacts vs end-faces, etc.)
- Preserve the original TONE and FLOW do not make it sound like a whitepaper
- Do NOT add unnecessary content or over-explain
- Do NOT rewrite sentences that are already correct
- Only change what needs fixing minimal, surgical edits
- Keep it readable for engineers, not academics
SPECIFIC FIXES TO APPLY:
- "inspection scope reveals/measures insertion loss" "inspection scope reveals contamination that impacts insertion loss"
- "contacts" (when discussing fiber) "connector end-faces" or "mating points"
- "MMF is stricter/less tolerant" be specific: "DR4 links have tighter loss budgets than SR4 deployments"
- "worn contacts on patch panels" "accumulated contamination on connector interfaces"
- Verify all dBm values against reference: DR4 budget 4.8 dB, attenuation at 1310nm = 0.35 dB/km
Return ONLY the repaired article text no commentary, no changelog.
TECHNICAL REPORT:
{{SANITY_REPORT}}
ARTICLE:
{{ARTICLE}}`;
// ═══════════════════════════════════════════════════════
// STEP 13: TITLE CONTRACT VERIFICATION — final check
// ═══════════════════════════════════════════════════════
export const STEP_TITLE_CONTRACT_CHECK = `You are a senior editor performing a FINAL quality gate check.
Compare this article against its Title Contract (the promise the headline makes).
TITLE CONTRACT:
{{TITLE_CONTRACT}}
ARTICLE:
{{ARTICLE}}
CHECK:
1. Does the article FULFILL the title's promise? (yes/no with explanation)
2. Does the article DRIFT into a different topic? (identify specific paragraphs)
3. Would a reader who clicked this title get what they expected? (yes/no)
4. Is the ending on-topic or does it drift into generic "validate your setup" advice?
If the article VIOLATES the title contract, return:
{
"contract_fulfilled": false,
"violations": ["specific violation 1", "specific violation 2"],
"drift_paragraphs": ["paragraph text that drifts"],
"verdict": "REJECT — article does not match title promise"
}
If the article FULFILLS the contract:
{
"contract_fulfilled": true,
"violations": [],
"verdict": "PASS — article delivers on title promise"
}`;
// ═══════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════
// NEW BLOG TYPES (v0.2.0) // NEW BLOG TYPES (v0.2.0)
// ═══════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════
@ -2155,34 +2309,43 @@ ANTI-AI FILTER — make it human:
Symmetric structure make it slightly uneven Symmetric structure make it slightly uneven
GOLD REFERENCE (engagement-boosted this is the target): ANGLE-AWARE HOOK SELECTION CRITICAL
Everything looks fine. Until it doesn't. The LinkedIn post MUST match the article's angle. Do NOT default to the Physical Layer story.
You bring up a new batch of 400G links. Lab tests were clean. Traffic is stable. TITLE CONTRACT FOR THIS ARTICLE:
{{TITLE_CONTRACT}}
Then error counters start creeping up. Match the hook and behavior beats to the article's actual angle:
Not enough to fail. Just enough to waste your time. IF ECONOMIC/TCO article:
HOOK: "A $350 optic just turned into an $18,000 problem." or "Your 400G budget looks fine. Until you add the numbers nobody showed you."
BEATS: cost surprises, TCO reality, budget impact
CLOSE: cost-based insight, not "clean your connectors"
So optics get blamed. IF DECISION/COMPARISON article:
HOOK: "Three quotes on your desk. The cheapest one isn't always the most expensive." or "OEM vs compatible. Everyone has an opinion. Here's what the data says."
BEATS: decision criteria, what specs don't tell you
CLOSE: decision framework, not failure story
Swapped. Replaced. Escalated. IF MARKET/TIMING article:
HOOK: "800G is shipping. Sort of." or "Half the industry says buy now. The other half says wait."
BEATS: what's real vs hype, timing signals
CLOSE: timing advice, not generic
Nothing changes. IF OPERATIONAL/PROCESS article:
HOOK: "We deployed 200 links in 3 weeks. Zero issues. Here's the boring reason why."
BEATS: process steps, what was done differently
CLOSE: process insight, not failure
Config looks correct. Hardware looks fine. IF MIGRATION article:
HOOK: "Month 1: everything on schedule. Month 6: nothing was."
BEATS: planning vs reality, specific surprises
CLOSE: what to plan for, based on real experience
That's usually the point where people start looking in the wrong place. DO NOT USE THE PHYSICAL LAYER HOOK ("Everything looks fine. Until it doesn't.") it has been used already. Create a FRESH hook that matches THIS article's specific angle.
Same optics. Same setup. Different result.
At 100G, you get away with it.
At 400G, you don't.
Full breakdown in the blog link in first comment.
#OpticalNetworking #DataCenter #NetworkEngineering #Flexoptix
Return ONLY the post text. No commentary. No "Here is the post:". Start directly with the hook. Return ONLY the post text. No commentary. No "Here is the post:". Start directly with the hook.

View File

@ -1016,11 +1016,15 @@ async function runLlmPipeline(
buildFeedbackContext, buildFeedbackContext,
buildSLLContext, buildSLLContext,
withCalibration, withCalibration,
STEP0_TITLE_CONTRACT,
STEP_TECHNICAL_SANITY,
STEP_SELF_HEAL,
STEP_TITLE_CONTRACT_CHECK,
} = await import("../llm/fo-blog-pipeline"); } = await import("../llm/fo-blog-pipeline");
const LLM_OPTS = { temperature: 0.7, maxTokens: 8192, timeoutMs: 480000 }; const LLM_OPTS = { temperature: 0.7, maxTokens: 8192, timeoutMs: 480000 };
const LLM_REFINE = { temperature: 0.4, maxTokens: 6144, timeoutMs: 480000 }; const LLM_REFINE = { temperature: 0.4, maxTokens: 6144, timeoutMs: 480000 };
const TOTAL_STEPS = 18; // 16-step pipeline + APM + Viral Signal + LinkedIn const TOTAL_STEPS = 21; // 17-step pipeline + title contract + technical sanity + self-heal + contract check
let stepsCompleted = 0; let stepsCompleted = 0;
try { try {
@ -1120,13 +1124,23 @@ async function runLlmPipeline(
} }
} catch { /* fine if no articles yet */ } } catch { /* fine if no articles yet */ }
// ═══ STEP 0: Title Contract — bind LLM to headline promise ═══
console.log(" Step 0: Title Contract (binding headline to content)...");
setProgress(draftId, 1, "Step 0: Title Contract");
const step0 = await generate(systemPrompt,
STEP0_TITLE_CONTRACT.replace("{{TITLE}}", title),
{ ...LLM_REFINE, maxTokens: 2048 }
);
const titleContract = step0.text;
console.log(` Title Contract: ${titleContract.split("\n").slice(0, 3).join(" | ").slice(0, 120)}...`);
// ═══ STEP 1: Topic Expansion ═══ // ═══ STEP 1: Topic Expansion ═══
console.log(" Step 1/10: Topic Expansion..."); console.log(" Step 1: Topic Expansion...");
setProgress(draftId, 1, "Step 1/10: Topic Expansion"); setProgress(draftId, 2, "Step 1: Topic Expansion");
const step1 = await generate(systemPrompt, const step1 = await generate(systemPrompt,
STEP1_TOPIC_EXPANSION STEP1_TOPIC_EXPANSION
.replace("{{TOPIC}}", title) .replace("{{TOPIC}}", title)
.replace("{{EXISTING_ANGLES}}", existingAnglesContext), .replace("{{EXISTING_ANGLES}}", existingAnglesContext + "\n\nTITLE CONTRACT (the article MUST fulfill this):\n" + titleContract),
LLM_OPTS LLM_OPTS
); );
stepsCompleted = 1; stepsCompleted = 1;
@ -1136,7 +1150,7 @@ async function runLlmPipeline(
setProgress(draftId, 2, "Step 2/10: Angle Selection"); setProgress(draftId, 2, "Step 2/10: Angle Selection");
const step2 = await generate(systemPrompt, const step2 = await generate(systemPrompt,
STEP2_ANGLE_SELECTION STEP2_ANGLE_SELECTION
.replace("{{FORBIDDEN_ANGLES}}", forbiddenAnglesContext) .replace("{{FORBIDDEN_ANGLES}}", forbiddenAnglesContext + "\nTITLE CONTRACT:\n" + titleContract)
.replace("{{SCENARIOS}}", step1.text), .replace("{{SCENARIOS}}", step1.text),
LLM_REFINE LLM_REFINE
); );
@ -1160,7 +1174,7 @@ async function runLlmPipeline(
const step4 = await generate(systemPrompt, const step4 = await generate(systemPrompt,
STEP4_MASTER_DRAFT STEP4_MASTER_DRAFT
.replace("{{OUTLINE}}", step3.text) .replace("{{OUTLINE}}", step3.text)
.replace("{{CONTEXT_DATA}}", contextData), .replace("{{CONTEXT_DATA}}", contextData + "\n\nTITLE CONTRACT (you MUST fulfill this — the article must match the headline promise):\n" + titleContract),
{ ...LLM_OPTS, maxTokens: 8192 } { ...LLM_OPTS, maxTokens: 8192 }
); );
stepsCompleted = 4; stepsCompleted = 4;
@ -1342,7 +1356,9 @@ async function runLlmPipeline(
// Fallback: dedicated LinkedIn post generator // Fallback: dedicated LinkedIn post generator
try { try {
const stepLinkedIn = await generate(systemPrompt, const stepLinkedIn = await generate(systemPrompt,
STEP_LINKEDIN_POST.replace("{{ARTICLE}}", viralArticle), STEP_LINKEDIN_POST
.replace("{{TITLE_CONTRACT}}", titleContract)
.replace("{{ARTICLE}}", viralArticle),
{ temperature: 0.6, maxTokens: 1024, timeoutMs: 120000 } { temperature: 0.6, maxTokens: 1024, timeoutMs: 120000 }
); );
linkedinPost = stepLinkedIn.text.trim(); linkedinPost = stepLinkedIn.text.trim();
@ -1361,6 +1377,69 @@ async function runLlmPipeline(
} }
stepsCompleted = 18; stepsCompleted = 18;
// ═══ STEP 19: Technical Sanity Check ═══
console.log(" Step 19/21: Technical Sanity Check...");
setProgress(draftId, 19, "Step 19/21: Technical Sanity Check");
let sanityReport = "";
try {
const stepSanity = await generate(systemPrompt,
STEP_TECHNICAL_SANITY.replace("{{ARTICLE}}", viralArticle),
{ temperature: 0.2, maxTokens: 4096, timeoutMs: 240000 }
);
sanityReport = stepSanity.text.trim();
console.log(` Sanity check: ${sanityReport.includes('"safe_to_publish": false') ? "⚠ ISSUES FOUND" : "✓ safe"}`);
} catch {
console.log(" Technical sanity check skipped (error)");
}
stepsCompleted = 19;
// ═══ STEP 20: Self-Heal (fix technical errors) ═══
if (sanityReport && (sanityReport.includes('"safe_to_publish": false') || sanityReport.includes('"critical_issues"'))) {
console.log(" Step 20/21: Self-Heal (fixing technical errors)...");
setProgress(draftId, 20, "Step 20/21: Self-Heal (technical fixes)");
try {
const stepHeal = await generate(systemPrompt,
STEP_SELF_HEAL
.replace("{{SANITY_REPORT}}", sanityReport)
.replace("{{ARTICLE}}", viralArticle),
LLM_REFINE
);
const healedWords = stepHeal.text.split(/\s+/).length;
if (healedWords > 400) {
viralArticle = stepHeal.text;
console.log(` Self-healed: ${healedWords} words`);
} else {
console.log(" Self-heal output too short — keeping original");
}
} catch {
console.log(" Self-heal skipped (error)");
}
} else {
console.log(" Step 20/21: Self-Heal skipped (no critical issues)");
}
stepsCompleted = 20;
// ═══ STEP 21: Title Contract Verification ═══
console.log(" Step 21/21: Title Contract Verification...");
setProgress(draftId, 21, "Step 21/21: Title Contract Check");
try {
const stepContract = await generate(systemPrompt,
STEP_TITLE_CONTRACT_CHECK
.replace("{{TITLE_CONTRACT}}", titleContract)
.replace("{{ARTICLE}}", viralArticle),
{ temperature: 0.2, maxTokens: 2048, timeoutMs: 120000 }
);
const contractResult = stepContract.text.trim();
if (contractResult.includes('"contract_fulfilled": false') || contractResult.includes('"REJECT')) {
console.log(" ⚠ TITLE CONTRACT VIOLATION — article may not match headline");
} else {
console.log(" ✓ Title contract fulfilled");
}
} catch {
console.log(" Title contract check skipped (error)");
}
stepsCompleted = 21;
// Extract article from Viral Signal output (or APM fallback) // Extract article from Viral Signal output (or APM fallback)
// Fall back to step9.text if output looks too short or empty // Fall back to step9.text if output looks too short or empty
let finalArticleText = viralArticle.trim().length > 200 ? viralArticle : step9.text; let finalArticleText = viralArticle.trim().length > 200 ? viralArticle : step9.text;