On June 30, we changed 40 lines in src/content.config.ts, added 33, removed 7, and stopped requiring voice, tone, and style before a post could publish.
That sounds like a retreat from standards. It was the opposite. Those three fields gave us a clean looking checklist while leaving the failures that matter free to ship: an unsafe claim, an identifying detail, a broken code sample, a story with no pressure, or an ending that just fades out.
Once the first three posts were public, the gap was hard to ignore. A record could say its tone was direct and its style concise. That told us nothing about whether the post had earned a public slot.
The old contract checked labels
The old schema made voice, tone, and style required publish gates. A post counted as structurally complete because three subjective fields existed. Nothing in that said the writing carried a real incident, the technical claim was safe to publish, or the ending landed.
We were not trying to turn those concerns off. Voice, tone, and style still matter. Our mistake was putting them in the same class as facts, anonymization, and code safety.
The difference is operational. A safety failure has an observable consequence. A fact can be wrong. A customer or internal system can be exposed. A command can be dangerous or misleading. A reviewer can identify each condition, fix it, and know why the gate passed.
Story quality can work the same way when it is specified properly. The reader needs to know what hurt. There has to be enough vulnerability to make the account more than a cleaned up success report. The closing needs to make a final claim instead of pointing at another post or dissolving into a generic lesson.
voice, tone, and style did not have that property. They described intentions, not conditions. We made them mandatory because they were easy to name, which is a poor reason to make anything a gate.
The cutover made the schema narrower and the review harder
The new required set is split into three kinds of gate:
safety: facts, anonymized, code_safetystory: bleeding_neck, vulnerability, concrete_closerapproval: author_reviewSafety answers whether the post can go public without a factual, identity, or implementation problem. Story answers whether the account has enough lived pressure to be worth reading. The final approval stays a human responsibility, and no schema should claim a reviewer is unnecessary.
The critical decision was what we left alone. We did not replace three vague fields with a longer list of vague fields, because that only makes the form longer. We did not promote a style prompt into a hard gate and call it automation either. A prompt can guide a draft. It cannot prove that a draft is truthful, anonymous, technically safe, or narratively complete.
We also kept the old fields in the content model. There are 187 existing posts, and their records still carry voice, tone, and style. Deleting them would turn a publish-policy change into a content migration, with every old record becoming a special case. Keeping them in the schema preserves the history and the content shape. Dropping their required status changes only the publish contract.
That kept the change small enough to verify. The final diff was one file, with no site rewrite and no back-catalog rewrite.
A content-model change can look right while breaking the actual publication path, so the local build was our first diagnostic.
The sequence was straightforward. First, make the safety gates required. Second, make the story gates required. Third, retain voice, tone, and style as optional schema members for the 187 existing posts. Finally, run the local build against the three posts already published.
All three validated under the new contract. The local build was green.
That result answered two different questions. It showed that optional legacy metadata was backward compatible. It also showed that the live examples passed the gates we now say matter. If those three posts had failed, the options would have been clear. Either the gate was badly specified, or the post needed editorial work before it could be considered public. A green build did not settle every editorial judgment, but it ruled out a fake migration success.
Style moved to lint
The alternative we discarded was to keep voice, tone, and style required and tighten the instructions around them. That would preserve an orderly form, but the weakness would remain, because a required label is still just a label.
Style hygiene moved to deterministic lint instead. That is the right layer for rules with mechanical evidence. A linter can catch prohibited punctuation, a forbidden phrase, an incorrect heading pattern, or another explicit editorial constraint. It can fail consistently and explain why. The publish contract stays reserved for the conditions that define whether a post is safe and complete enough to be public.
The configuration now says what we mean. It does not ask a post to certify its own personality. It asks for facts, anonymization, code safety, an honest point of pain, a degree of exposure, a concrete ending, and human signoff.
That is a smaller contract than the old one, and it is much harder to satisfy by filling in the right words. If your own publishing or approval process gates on labels anyone can fill in, look at what a reviewer could verify instead: facts, identity exposure, code safety, and a concrete ending.