Blog posts need live URL contracts
A repo-backed blog should not count a post as published when the file is written. Publication starts when the live URL renders.
A repo-backed blog has a trap built into it: writing the post feels like the finish line.
It is not.
For djasha.me, the content lives in the repository as .mdoc files, the public site is built by Astro, and the actual reader sees whatever the deployed site serves. There are several places where a post can be "done" locally and still not exist publicly: frontmatter can fail, a build can break, a PR can sit unmerged, deployment can lag, or the live route can return a perfectly confident 404.
That is why the blog factory rule is blunt: a post counts only when the production blog URL returns HTTP 200 and renders the expected title.
Sources: Astro content collections documentation, Astro Content Loader API documentation, GitHub pull request documentation, GitHub Actions workflow syntax, and Cloudflare Cache-Control documentation.
Files are drafts until the route answers
Static sites make publishing feel deceptively simple. Add a file. Commit it. Build the site. Done.
That mental model is fine for a human tinkering locally. It is too loose for an agent that reports work to a user.
An agent can create a beautiful post in the right folder and still be wrong about publication. The content collection may reject a field. The route may filter out drafts. The production build may read a generated content directory. The CDN may still serve the previous version. A redirect rule may shadow the route. None of those failures are visible if the agent stops at git status.
The contract should be external:
Input: intended slug and title Check: production URL returns 200 Check: response contains the expected title or body phrase Only then: report published
That sounds obvious. It is also the step most automation skips when it wants to sound finished.
The PR is a checkpoint, not a finish line
GitHub pull requests are useful because they turn publishing into a reviewable artifact. You can inspect the diff, run checks, leave comments, and merge only when the change is safe enough.
But a PR is not publication. A green build is not publication. Even a merged PR is not publication until the deploy path has caught up and the public URL answers.
For a personal portfolio, that distinction matters because the blog is not just content. It is proof of judgment. A post that claims a system works while its own URL 404s is the worst possible demo.
So the workflow needs three states, not two:
prepared: file exists and local checks pass accepted: PR merged after review published: live URL renders expected content
Most content tools collapse those states because they are optimized for speed. Agent-run publishing should keep them separate because agents are very good at confusing a completed subtask with the completed job.
Build checks catch structure, not reality
Astro's content tooling is exactly the kind of guardrail a repo-backed blog should have. Frontmatter schemas, content collections, and build-time errors catch a lot of bad changes before they reach readers.
Still, a build is an internal check.
It tells you the site can be produced from the current source. It does not tell you which version is live. It does not tell you whether the CDN has refreshed. It does not tell you whether the final hostname routes to the new artifact.
That is why the live check belongs after merge and deploy, not before. Local build first. PR checks next. Production URL last.
This is not ceremony. It is the only check that uses the same interface the reader uses.
Agents need boring receipts
The right final report from a publishing agent should be almost annoyingly concrete:
Title: Blog posts need live URL contracts PR: merged Build: passed URL: /blog/blog-posts-need-live-url-contracts HTTP: 200 Verified phrase: "Files are drafts until the route answers"
That receipt is better than a paragraph saying the post was successfully published. It gives the user something they can test. It also gives the next agent a clean handoff if something breaks later.
The same pattern applies outside blogging. A Postiz job is not a social post until the provider permalink exists. A package release is not done until the registry shows the version. A deployment is not done until the public health check answers from outside the machine that built it.
Automation should stop reporting intent as outcome.
The useful rule
The rule I want for djasha.me is simple:
No live URL, no publication claim.
That rule slows the agent down in the right place. It does not make drafting harder. It does not make editing harder. It only blocks the final sentence: "published."
Good autonomous publishing is not the agent writing faster. It is the agent knowing when the work has crossed the boundary from repository state into public reality.