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.
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.
- 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
- 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.
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.
- SENSITIVE_TERMS scan
Rejects company/client names and internal project codenames across all LLM-generated fields — blog, expUpdate, every block label.
- SKILL_VERBS check
expUpdate.highlights must use skill verbs (implemented, optimized, integrated, etc.). Describes demonstrated capability, not job tasks.
- Metric pattern detection
≥2 specific measurements (ms, %, rps, GB) in one sentence = suspected internal before/after data → build fail.
- Slug uniqueness
Duplicate slug across daily files or essays → build fail. No hidden page collision or SEO conflict.
- 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 so there is no extra request until the user actually expands the archive. These are observable properties of the running site, not claims.
- 1Grok researchweb_search + web_fetch: 3-5 real sources, thesis, counter-intuitive points (typically under 2 minutes)
- 2ValidateAJV + SENSITIVE_TERMS + SKILL_VERBS + slug uniqueness — exit 1 on any violation (near instant)
- 3gen-news-index6.7KB slim metadata from all daily files + essays, sorted by date (sub-second)
- 4Astro buildBlocks → SVG/HTML, mechanism GIF from architecture block, template archive (under a minute)
- 5Deploy + LinkedInCDN push + LinkedIn API post from linkedinPost field — no manual copy-paste
Mechanism GIFs are also data-driven
When a post contains a rich block (architecture, flow, sequence), a server-side generator using the same block data produces the 8-frame 1200×630 card used on LinkedIn. The animation is derived from the structure the model emitted. No separate design pass, no manual frame export, no WebGL on the client.
The same data contract that protects the site also powers the social surface.
This is what a daily, self-updating, LLM-assisted portfolio actually looks like when you treat the pipeline as a real system instead of a clever prompt.