The line was fm = set_gate(fm, "editorial_review", "true"), buried inside publish(). Six lines above it, the same file’s docstring says a corpus that lies about its own review is worse than a smaller one. The publisher was writing that lie into every post it touched.

Here’s what that meant in practice: ops-cli blog-publish --write <slug> would flip status to published and, on the way, stamp editorial_review: true into the frontmatter itself. Nobody had to open the post. The tool asserted, on behalf of a human who was never in the loop, that a human had read it. And holds(), the function that’s supposed to refuse a publish for cause, had no check on that gate at all, because the gate it was checking didn’t yet distinguish “a person looked at this” from “the forbidden-terms scan came back clean.”

The proof it wasn’t hypothetical was already live. One piece already in the corpus, on multi-session artifact safety, shipped with a visible scrub artifact sitting in a code block: an un-substituted template path, complete with a live session id, while its own frontmatter said anonymized: true. That flag had never meant “clean.” It meant “no forbidden term matched,” which is a different and weaker claim, and nothing downstream knew the difference.

The wrong turn

My first instinct was to just gate publish() on editorial_review being true and leave the write path alone, add a --force-signoff flag so publish could still set it when someone wanted to push a post through in one command. I got as far as sketching the flag before I stopped: that’s not a sign-off gate, that’s the same forgery with an extra step. Anyone scripting the publish call would just pass the flag every time, which is exactly the muscle memory that produced the bug. A gate a machine can satisfy by calling a flag on itself isn’t a gate.

So the fix had to split the write path in two, permanently, with no bridge between them.

What shipped

Three changes, landed together, because any two of them alone breaks the pipeline in a worse direction than the bug did:

  1. publish() no longer touches editorial_review at all. It still sets status: published, nothing else in that call changed.
  2. holds() now requires editorial_review: true to already be present in frontmatter, and refuses with a named reason, in the same style as its existing four refusal reasons (parked, dedup, merge_target, unscreened), when it’s absent.
  3. A new command, ops-cli blog-signoff <slug> [<slug>...], is the only path in the codebase allowed to set that key. It refuses anything not at status: review, prints the post’s title, its full gate block, and an excerpt before it writes anything, dry-runs by default, and needs an explicit --yes to commit. It sets exactly one key. Nothing else in frontmatter moves.

There was a second latent bypass I had to check before calling this closed: content.config.ts:138 in the landing repo has a legacy branch that only checks the three safety gates when a different flag, feature/x, is set and editorial_review is absent. That path predates the sign-off command and would happily publish a post the new gate was built to hold. I didn’t touch that repo, but flagged it as a live route around the fix rather than closing the ticket like it wasn’t there.

Verifying it actually held

The test suite that should have caught this in the first place didn’t exist yet, so part of the task was writing it: publish refuses when editorial_review is absent, publish succeeds once it’s present, signoff sets only that key and leaves status, pubDate, and body untouched, signoff refuses a post that isn’t at status: review. Running ops-cli blog-publish in survey mode against the real corpus afterward, with the new hold wired in, showed the real size of the hole: all 251 posts in the review queue had been sitting one flag away from shipping unread, not a hypothetical few.

The commit message for the schema half put it plainly: stop blog-publish from forging editorial_review, add blog-signoff as the only path that may set it. Two clauses, and the second one is the part that makes the first one stick. A refusal with no legal way to satisfy it just gets bypassed by whoever’s blocked next. The sign-off command is what keeps someone from reaching for the same flag I almost added.