A generated troubleshooting guide should leave draft status only after every sentence carries an explicit owner class. Observable facts that a parser extracted from source may be restated by a model into reader prose. Recovery steps, data-loss warnings, and security promises must stay unsigned until a named human writes them. The split keeps mechanical restatement on the automation path and keeps operational risk on a reviewer.
Inline comments often resemble finished documentation simply because they sit beside the branch that failed in review. They remain author notes, however, and they are not verified instructions for a reader in production. A single comment can name a symptom, guess a cause, and promise a safe retry in one line. Publishing that mixture as a guide moves untested judgment into the page operators trust during an incident.
What the model may draft, and what a human must own
The ledger defines four owner classes, and each class names exactly one kind of allowed writer. FACT covers public symbols, error codes, and defaults that a parser has extracted from the source tree. EXAMPLE covers fenced snippets whose test command, source commit, and content checksum are already recorded in the ledger. RECOVERY covers restarts, rollbacks, deletions, and credential rotation, while PROMISE covers support and safety claims.
A model may draft reader prose only for FACT rows, and only by restating fields present in the ledger. A model may add connective sentences around an EXAMPLE block, but it may not edit the fenced sample. A human must write every RECOVERY row and every PROMISE row, then set the signer name and review date. An empty owner field is a publish blocker, not a stylistic suggestion left for a later editor.
Workflow
Step 1 — Harvest candidate sentences from marked comments
Restrict the harvest to module docstrings and to source lines that begin with the marker # guide:. Leave ordinary comments outside the pipeline, because those lines often store doubts, jokes, or unfinished hypotheses. Split harvested text on sentence boundaries, and store the path, the line number, and the raw sentence. Do not paraphrase during extraction, because a paraphrase would hide the exact claim you must classify.
Step 2 — Assign an owner class with an explicit lexicon
Run a deterministic classifier across every harvested sentence before any model call is permitted to start. Mark the row as RECOVERY or PROMISE when the recovery-verb list or the promise-token list matches. Mark the row FACT only when it names a symbol, code, or default that also exists in the extracted fact table. Leave every remaining sentence as UNRESOLVED until a human selects a class or deletes the line.
Step 3 — Freeze examples, then allow narration around them
Copy each passing snippet into an examples directory and record the test command beside its checksum. Record the source commit as well, so a later reviewer can see which tree produced the sample. The drafting prompt may quote that checksum and describe the inputs shown in the frozen fence. A later diff check fails the build when the published fence hash no longer matches the ledger hash.
Step 4 — Draft only the rows the ledger marks as eligible
Send the model a JSON list that contains only FACT rows together with the frozen example metadata. Require the model to echo each fact identifier beside the sentence it wrote for that row. Reject any output that introduces a number, flag, URL, or product claim absent from the ledger. Leave RECOVERY and PROMISE sections as headings plus an unsigned placeholder so the gap stays visible.
Step 5 — Block publish until human-owned rows are signed
A reviewer writes the recovery note in the placeholder and sets signed_by together with signed_on. The checker rejects a signature when the new recovery text is character-identical to the harvested comment. Copying a comment into the signature field is not a review, so the build must fail closed. The checker also rejects a missing date or a blank signer before the page may enter the docs tree.
Artifact: a local classifier and a copy check
The Python below is an unexecuted example of the ledger builder, and it has not been timed or deployed. It does not call a hosted model, and it does not claim a measured accuracy for the lexicon. The lists are intentionally short so you can see the fail-closed behavior on a three-line fixture. Adapt the verb lists and run the file on Python 3.11 or newer before you trust a red build.
#!/usr/bin/env python3
"""Unexecuted example: classify # guide: lines into an ownership ledger."""
import hashlib
import json
import re
import sys
from pathlib import Path
RECOVERY = re.compile(
r"\b(restart|rollback|delete|rotate|wipe|purge|restore)\b",
re.I,
)
PROMISE = re.compile(
r"\b(always|never|guarantee[ds]?|compliant|production-safe)\b",
re.I,
)
FACTISH = re.compile(r"\b([A-Z][A-Z0-9_]{2,})\b")
def classify(sentence: str, facts: set[str]) -> str:
if RECOVERY.search(sentence):
return "RECOVERY"
if PROMISE.search(sentence):
return "PROMISE"
tokens = set(FACTISH.findall(sentence))
if tokens and tokens <= facts:
return "FACT"
return "UNRESOLVED"
def harvest(root: Path, facts: set[str]) -> list[dict]:
rows = []
for path in sorted(root.rglob("*.py")):
text = path.read_text(encoding="utf-8")
for number, line in enumerate(text.splitlines(), 1):
if "# guide:" not in line:
continue
sentence = line.split("# guide:", 1)[1].strip()
owner = classify(sentence, facts)
rows.append({
"id": f"{path}:{number}",
"sentence": sentence,
"owner_class": owner,
"signed_by": None,
"signed_on": None,
"draftable": owner == "FACT",
})
return rows
def main() -> int:
root = Path(sys.argv[1])
fact_path = Path(sys.argv[2])
facts = set(json.loads(fact_path.read_text(encoding="utf-8"))["codes"])
rows = harvest(root, facts)
payload = {
"rows": rows,
"sha256": hashlib.sha256(
json.dumps(rows, sort_keys=True).encode("utf-8")
).hexdigest(),
}
json.dump(payload, sys.stdout, indent=2)
sys.stdout.write("\n")
blocked = [row for row in rows if not row["draftable"]]
return 1 if blocked else 0
if __name__ == "__main__":
raise SystemExit(main())
The three fixture lines below are sample inputs for the classifier, not production troubleshooting advice. The first line names an error code and can become FACT if that code also exists in the fact table. The second line matches a recovery verb, so the ledger must block drafting and demand a human signature. The third line matches a promise token, so it stays unsigned even if the surrounding tone sounds confident.
{
"codes": ["ERROR_429"]
}
# guide: RetryableError uses code ERROR_429 when the client should back off.
# guide: Restart the worker and wipe the local queue after a partial write.
# guide: This path is production-safe and never drops acknowledged messages.
Run the classifier from the repository root and redirect the JSON ledger to a reviewed path. Reading the classifier rules predicts one FACT row, one RECOVERY row, and one PROMISE row for the fixture. The process should exit non-zero because two of the three fixture rows are not draftable at all. Store the draftable count beside the ledger hash so the next reviewer can see whether comments drifted.
python ownership_ledger.py ./src ./facts.json > ownership_ledger.json
echo "exit=$?"
python -c 'import json; rows=json.load(open("ownership_ledger.json"))["rows"]; print(sum(r["draftable"] for r in rows), "draftable of", len(rows))'
The helper below is an unexecuted example, and it encodes the publish block for human-owned rows. It fails the row when the signer or the review date is missing from the ledger record. It also fails the row when the signed recovery text is identical to the harvested source comment. Wire it into the same continuous-integration job that already fails on a non-zero classifier exit.
def human_row_blocked(row: dict) -> bool:
"""Unexecuted example: True means the page must not publish."""
if row["owner_class"] not in {"RECOVERY", "PROMISE"}:
return False
signed = (row.get("recovery_text") or "").strip()
if not row.get("signed_by") or not row.get("signed_on"):
return True
return signed == row["sentence"].strip()
Decision table for the review queue
The table below is the review contract for one troubleshooting page, not a general style guide. A style guide can improve tone, but it does not stop a model from inventing a rollback order. The publish rule is binary so a continuous-integration job can enforce it without interpreting tone. Treat UNRESOLVED as fail closed, because an unlabeled sentence is not safer than a risky one.
| Ledger class | Model may draft | Human must own | Publish rule |
|---|---|---|---|
| FACT | Restate a code or default present in the fact table | Confirm the extractor kept negations and qualifiers | Allow only when the draft echoes the fact id |
| EXAMPLE | Narrate around the frozen fence | Keep the snippet, test command, commit, and checksum | Fail if the published fence hash drifts |
| RECOVERY | Nothing | Write restart, rollback, deletion, and data-loss notes | Fail when signed_by or signed_on is empty |
| PROMISE | Nothing | Write support, compliance, and safety claims | Fail when the text copies the harvested comment |
| UNRESOLVED | Nothing | Choose a class or delete the sentence | Fail closed until a human classifies the row |
Where free model access and a free server option fit
Disclosure: This article was prepared as part of MonkeyCode's product outreach. After the ledger exists, free model access is sufficient for the narrow job of restating FACT rows. The prompt should include fact identifiers and example checksums, not unsigned recovery notes from the same page. That input boundary keeps the drafting call on restatement rather than on incident advice the model must not own.
The free server option is relevant as a place to host that drafting step outside production documentation infrastructure. This article does not establish quotas, hardware size, retention, duration, or permanence for either availability claim. Confirm the current plan terms in primary product documentation before you bind a pipeline to them. If the free option cannot retain artifacts, keep the JSON ledger in git and use the server only for preview.
Limitations of the lexicon gate
The lexicon is only a heuristic, and it misses operational risk written as implication rather than as a banned verb. A sentence such as "clear the spool and continue" can destroy queued data without matching wipe or delete. Synonyms, negation, and local jargon will be mislabeled until reviewers extend the lists from real misses. A checksum proves the fence was not edited, but it does not prove the sample still matches later runtime behavior.
This workflow does not replace threat modeling, legal review, or the judgment of an incident commander. It also does not prove that an extracted error code is still raised by the current build. Pair the ledger with a test that imports the catalog and asserts each FACT code still exists. If you skip that test, accept that the ledger can freeze a constant the code no longer emits.
Who should not use this approach
Skip this gate when nobody is available to sign RECOVERY and PROMISE rows before publication. An empty signature field is the control, and a scripted fake signer would remove that control entirely. Skip the harvest for security advisories, breach notices, and regulated disclosures that need a different source of truth. Skip it when the page is mostly narrative tutorial prose, because nearly every row will land in UNRESOLVED.
Teams that already compile a page from a reviewed fact file should not add a second drafter on the same headings. Two drafting passes will diverge, and the ownership table cannot decide which paragraph remains current. Keep one drafting pass, one ledger file, and one human signature set for each published page. If those three artifacts disagree, stop publication and repair the ledger before you call the model again.
Do not adopt the workflow merely because model access is free, since price does not change ownership. A free drafting call can still invent a rollback order when the prompt includes unsigned recovery notes. Keep those notes out of the prompt even when the server option makes another drafting call easy to repeat.
What to measure before widening the rollout
Count harvested sentences, draftable rows, and rows rejected for a missing or copied signature on one page. Those three counts show whether the lexicon is too tight or too loose for that repository. They are local review observations rather than product benchmarks, and the ratios will differ across codebases. Record the counts beside the ledger hash so the next reviewer can see whether the source comments drifted.
Start with a single troubleshooting page that already has stable error codes and a human reviewer on the hook. Compare the blocked rows with the comments a reviewer would have challenged during an ordinary docs pass. If the ledger blocks the same risky lines and also catches a copied promise, the gate is matching human review on that page. If you keep review artifacts in git, run this unexecuted classifier on that page before you widen the rollout.
Top comments (0)