No article was removed from the live site. That distinction matters.

The change existed only on an isolated branch before release. The isolated branch and human review kept the commit out of main. The current release gate also requires a request to point at the current main, so the test branch could not advance directly to production.

Codex executed this round-trip test within publishing boundaries defined by the site owner. The repair was then based on reviewable commits and build results. During the test, an existing public article was saved without changing its body content. The resulting commit added one field to the frontmatter:

draft: true

If that save had been accepted as a harmless round-trip and promoted, the entry would have disappeared from its route, the home page, the knowledge archive, RSS, the sitemap, and article JSON-LD. The isolated workflow caught the change before release, and human review kept it from advancing. A recovery commit restored the file, and the unsafe state never entered the production history.

That failure changed the acceptance rule: a successful save and unchanged Markdown AST and visible-text fingerprints are not enough on their own. A CMS round-trip must preserve frontmatter semantics and the resulting public output.

How a no-edit save changed publication state and how the isolated workflow kept it out of release

How a no-edit save changed publication state

The Pages CMS schema defines draft as a Boolean with a default value of true. That is the correct default for a newly created entry.

The older published files were different. The four files present when Pages CMS was integrated predated the complete draft policy and did not contain an explicit draft field. The earlier site logic treated a missing field as published. In this test, saving the older file caused Pages CMS to write the configured default into the field that had previously been absent.

The same commit also normalized the title quotes, removed the first blank line before the body, and changed the final newline. Those changes were visually noisy in the diff, but the single draft: true line was the part that changed public behavior.

Why unchanged body semantics were still a failure

A body-only comparison would have reported that the article content survived. In a static site, however, frontmatter is executable input to the build.

In this repository:

  • getPublishedKnowledge() only returns entries where draft === false;
  • static detail routes use the same publication rule;
  • the home page, archive, and RSS feed reuse the published collection;
  • the sitemap excludes draft URLs during configuration;
  • a draft detail page does not emit public BlogPosting JSON-LD;
  • the post-build validator rejects any generated draft route or slug.

An unchanged body therefore proves only that the body is unchanged. It says nothing about routes, metadata, or distribution surfaces.

This is the same distinction described in From Real Projects to Reusable Knowledge: public knowledge includes status, dates, type, and distribution decisions—not only prose.

Revert the failed save first, then make publication intent explicit

After the failure, a recovery commit reverted the Pages CMS save on the isolated branch. The purpose was not cosmetic diff cleanup. It preserved a clear evidence boundary:

  • the failed-save commit records the real risk;
  • the recovery commit records the return to a safe state;
  • neither commit was used to advance a release branch or merged into a release candidate;
  • the eventual release candidate was rebuilt from a clean state.

Merging both the failure and its revert could produce the same final file, but it would also place a known unsafe publication-state change in the release history. Keeping the round-trip evidence on its own branch made the purpose of each commit easier to audit.

The final implementation did not change the default for new content to false. That would make newly created entries public by default and simply move the risk elsewhere.

Instead, the migration process gave the four public entries present at integration time an explicit YAML Boolean:

draft: false

The repository then enforced the rule in three places:

  1. the Astro content schema requires a Boolean draft field;
  2. repository validation rejects a missing value and the string "false";
  3. production queries publish only entries strictly equal to false.

Older and newer content no longer depend on an implicit fallback. The intended publication state has to exist in the file.

Why merge: true did not solve this automatically

The repository retains this Pages CMS setting:

settings:
  content:
    merge: true

It is an important safety control. When structured content is saved, Pages CMS merges the submitted fields into the existing file so keys outside the editor schema are preserved.

But this test involved a field that the editor did manage. draft was declared in the schema and had a default. Merge behavior protects unmanaged keys; it does not reject a configured field from being written. Safe round-trips still require explicit migration, type validation, and a real save test.

What the second round-trip covered

The next non-production test did more than repeat the original Chinese save:

  • an existing Chinese entry retained Boolean draft: false after saving;
  • an existing English entry from the other collection also retained Boolean draft: false;
  • Pages CMS could normalize formatting, but frontmatter meaning, Markdown AST, rendered HTML, and visible text stayed equivalent;
  • temporary Chinese and English entries defaulted to draft: true and shared one translationKey;
  • the test image and both temporary drafts were removed by a cleanup commit after GitHub Actions preview-build checks;
  • the production build contained no temporary route, media reference, or draft slug.

“Idempotent” here does not mean that every quote and blank line must match the original formatting forever. It means that a second save must not keep changing content meaning or publication state.

A reusable CMS round-trip checklist

For the next file-based CMS integration, run at least these checks:

  1. Create a semantic fingerprint before saving. Record frontmatter types, the Markdown AST, rendered HTML, and visible text—not just the body hash.
  2. Test an existing file from every distinct collection. One language or schema is not enough.
  3. Perform a no-edit save. Inspect defaults, dates, arrays, quoting, and unmanaged fields.
  4. Save the normalized result again. The second save should be semantically idempotent.
  5. Create a real draft. Verify its default state, translation pairing, media path, and preview.
  6. Inspect public artifacts. Drafts must not enter any public surface.
  7. Remove all fixtures. The final candidate diff must contain no temporary entry or image.
  8. Keep failure evidence isolated. Preserve the test commits, but rebuild the release candidate from a clean state.

The most important check is conceptual: an editor’s success message proves that a save request completed. It does not prove that file semantics or site output stayed unchanged.

The boundary of this risk

This is not evidence that Pages CMS turns every old article into a draft. The trigger was specific: an old file omitted a field, the editor schema supplied a default for that field, and a save wrote the default into the repository.

The same pattern can appear in another Git-backed CMS, a form editor, or a migration script. The field may be named published, featured, locale, or something else entirely. The durable lesson is not to avoid one tool. It is to treat a no-edit save as a data migration and test its semantic output.

That is also why new content still defaults to draft: true. The goal is not to make everything public by default. The goal is to make every file declare its state and to let production accept only the state that has been explicitly reviewed.

References