fm = set_gate(fm, "editorial_review", "true") sat 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. The gate it would have been 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 in the corpus, on multi-session artifact safety, shipped with a visible scrub artifact in a code block: an un-substituted template path, complete with a live session id. Its own frontmatter said anonymized: true. That flag meant only “no forbidden term matched,” a weaker claim than “clean,” and nothing downstream knew the difference.

The flag we almost added

Our first instinct was to gate publish() on editorial_review being true and leave the write path alone. We would add a --force-signoff flag so publish could still set the key when someone wanted to push a post through in one command. We got as far as sketching the flag before we stopped. It is the same forgery with an extra step. Anyone scripting the publish call would 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 break the pipeline in a worse direction than the bug did:

  1. publish() no longer touches editorial_review at all. It still sets status: published, and nothing else in that call changed.
  2. holds() now requires editorial_review: true to already be present in frontmatter. When it’s absent, holds() refuses with a named reason, in the same style as its existing four (parked, dedup, merge_target, unscreened).
  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. Before it writes anything it prints the post’s title, its full gate block, and an excerpt. It dry-runs by default and needs an explicit --yes to commit. It sets exactly one key, and nothing else in frontmatter moves.

There was a second latent bypass 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. We didn’t touch that repo, but we flagged it as a live route around the fix instead of closing the ticket as if it weren’t there.

Checking that it held

The test suite that should have caught this in the first place didn’t exist yet, so writing it was part of the task. 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.

Then we ran ops-cli blog-publish in survey mode against the real corpus, with the new hold wired in. It showed the size of the hole: all 251 posts in the review queue had been sitting one flag away from shipping unread.

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. The second clause is what makes the first one stick. A refusal with no legal way to satisfy it just gets bypassed by whoever’s blocked next, and the sign-off command is what keeps that person from reaching for the flag we almost added.

If your own pipeline has an automated step, check whether it can mark its own work as human-reviewed. The review flag should be settable only by a separate, deliberate human action. And look for older code paths that skip the new gate, because one of those can quietly undo the fix.