This post is an artifact, not a story

You are reading output that was produced by the pipeline described in this post. Grok did the research pass against current sources. The blocks and linkedinPost fields were validated by the exact guardrails in validate-content.mjs that run on every piece of content. This JSON file was emitted, passed the checks, was committed, and is now being rendered — without anyone writing HTML by hand or manually copying text to LinkedIn.

The standard portfolio failure mode is narrative rot: a person writes a story about their skills at one point in time, then the story and the actual work diverge. The only way to keep the record current at daily frequency is to stop treating publishing as writing and start treating it as a system with contracts, gates, and separation between what the model is allowed to decide and what the renderer is allowed to emit.

Once an LLM sits inside that system, the usual vague problems become hard constraints. A fluent model can emit text that would break the design system, introduce XSS vectors through raw markup, claim metrics that do not exist in the repo, or use language that would fail the skill-verb requirement on expUpdate. Without mechanical gates before astro build, you are simply hoping the prompt worked this time.

topic + date contextraw JSON outputvalidated contentindex.json 6.7KBdist/ static fileslinkedinPost textTrigger / npm runGrok CLIvalidate-content.mjsgen-news-index.mjsAstro BuildStatic Deploy (CDN)LinkedIn API
Grok research → Claude validate → Astro render → deploy + LinkedIn

Routing follows failure modes, not model marketing

Assigning every task to whichever model is currently fashionable produces two classes of defect: grounding errors on work that needed fresh external sources, and consistency/guardrail errors on work that needed strict mechanical editing.

Grok has web_search + web_fetch plus a bias toward non-obvious, falsifiable claims. Claude has tool use, code execution, and a demonstrated ability to make the smallest change that satisfies a contract across many files. The rule used in this repository (and read by both Grok Build and Claude Code from the single CLAUDE.md at the root) is therefore uncertainty × realtime-need × context-volume × taste.

In real production voice systems (self-hosted Whisper/NeMo for audio grounding + Gemini for reasoning/extraction), similar routing is applied: heavy-lifting models for audio (cost/control) + reasoning models for structured output (with schemas). Batching and contracts allow scaling without exploding cost/tokens. The portfolio pipeline applies the same principle: research (Grok) vs execution/guardrails (Claude), with the schema as the safety boundary.

This post is a live instance. The research direction and the initial structure of the argument were produced under the first set of conditions. The enforcement that the resulting text does not violate the schema, does not contain forbidden patterns, correctly populates the blocks array, and would survive the same guardrails as every other post, was produced under the second set. The evidence is the file you are looking at and the fact that the build that included it succeeded.

The history of the rule lives in docs/decisions.md (ADR-003 and subsequent updates). The rule is not a preference. It is the current, observable procedure of the repository.

Grok — Research
  • web_search + web_fetch: real-time sources, current API docs, today's news
  • Counter-intuitive insight extraction from multiple independent sources
  • Thesis proposal: falsifiable claim, not generic description
  • First-principles analysis and anti-sycophancy pushback on weak ideas
Claude — Execution
  • AJV schema validation + security guardrail enforcement before any file write
  • Code execution: gen-news-index, validate-content, Astro build trigger
  • Smallest-correct-diff: edit only what needs to change across many files
  • Mechanical consistency: apply changes uniformly across the codebase

The boundary is the schema, not the prompt

The model is only permitted to emit data that matches the schema. Everything that touches the browser (SVG, HTML structure, ARIA, animation timing, responsive behavior) is produced by components that read that data. Changing the visual language requires changing the component, not every past content file. This is the actual security boundary. Prompt engineering is only the thing you do before you hit the real gate.

LLM emit data, site renders HTML — never the reverse

The schema contract is the security boundary. Grok writes JSON blocks; the Astro component renders safe SVG/HTML following the design system. When the animation style changes, you update the component — not every content file. XSS prevention, accessible ARIA, consistent animation: all enforced at render time, not prompt time.

The guardrails that actually ran on the text you are reading

All of the following checks were applied to the content that became this post:

  • Sensitive terms (company names, client names, internal codenames) — rejected across every field.
  • Skill verbs on any highlights — must describe demonstrated capability.
  • Metric pattern detection — multiple specific internal measurements in one sentence triggers failure.
  • Slug uniqueness.
  • Full AJV validation against the schema (lengths, enums, URI formats).

Any violation would have caused the build to exit 1 before this file could be committed or deployed. The fact that you can read this sentence means the checks passed on the data that produced it.

  1. SENSITIVE_TERMS scan

    Rejects company/client names and internal project codenames across all LLM-generated fields — blog, expUpdate, every block label.

  2. SKILL_VERBS check

    expUpdate.highlights must use skill verbs (implemented, optimized, integrated, etc.). Describes demonstrated capability, not job tasks.

  3. Metric pattern detection

    ≥2 specific measurements (ms, %, rps, GB) in one sentence = suspected internal before/after data → build fail.

  4. Slug uniqueness

    Duplicate slug across daily files or essays → build fail. No hidden page collision or SEO conflict.

  5. AJV schema validation

    Every field validated: maxLength, minLength, enum, pattern, format:uri for sources. Type mismatch = no deploy.

Slim index and lazy loading are not optimizations — they are part of the contract

The news index is kept deliberately small (~6.7KB). It contains only what is needed to list and link. Full bodies and block data are loaded on demand. Older posts use a hidden