Reference documentation stays trustworthy when interface facts are extracted and locked before any model writes orientation prose around them. A block-role manifest records that split before drafting starts, so review does not depend on catching invented flags by eye. Deterministic extraction owns signatures, flags, environment variables, and error codes, while the model may fill only marked orientation slots. The checker later in this article fails the page when drafted prose names an interface token that the locked inventory does not contain.
Models that draft reference pages often smooth over a missing row by inventing a plausible flag, status, or default value. That failure is different from a weak explanation, because the sentence looks fluent while the contract itself is false. Full-page review does not scale once several endpoints share one pull request and each page mixes tables with narrative. A role tag on every block makes the allowed draft surface small enough for a script to check without rereading tables.
Heading-level ownership is not enough when one heading contains both a generated flag table and a paragraph of advice. The unit of control here is the block, not the file and not the heading, because those larger units hide mixed authority. A style promise about tone also does not stop a model from adding a flag that no release ever shipped. The manifest therefore names concrete tokens, and the checker compares drafted sentences against that closed token set.
What the model may draft
The model may draft orientation slots that explain sequencing, prerequisites in plain language, and common misreadings of a locked row. Those slots may compare two documented flags only when both flags already appear in the inventory for that page. The model may also propose a short note about when not to call the operation, if every concrete token is already listed. Generic words such as request, client, and retry stay allowed, while product-specific tokens must be listed before any draft.
Orientation text should use a surface from the locked row rather than restating the default, the type, or the status code. Restating those fields creates a second source of truth that will drift on the next release even if the first draft was careful. If a sentence needs a default, the page should render that default from the inventory, not from the drafted paragraph. The drafting prompt should receive slot ids and allowed surfaces, not the unfinished page as a free-form writing canvas.
What a human must own
A human must own the inventory source, whether that source is an OpenAPI document, a captured help file, or an error catalog. A human must own status mappings, default values, deprecation markers, and the decision that a given token is public. A human must approve each orientation slot before it exists, because an extra slot is an extra place to invent scope. A human must also merge extracted tables into the page, so a model cannot rewrite a locked block while polishing nearby prose.
Ownership is not the same thing as typing every sentence that a reader eventually sees on the published page. A human can delegate extraction to a script, then review the inventory diff before any prose job is allowed to run. What cannot be delegated is the judgment that a token is safe to document, stable enough to name, and correctly scoped. If that judgment is missing, a drafting pass will not create it, and the checker will only confirm that the invention was consistent.
Step-by-step workflow
Follow the six steps in order, and treat each artifact as an input to the next rather than as optional notes. Skipping the lock file and asking for a full page is how invented flags re-enter an otherwise careful repository. The commands and files below are a proposed workflow, not a record of a run against a production documentation set. Replace the sample command name with the binary or spec that the team already treats as the interface source.
1. Capture the interface inventory
Store one inventory file per reference page, and treat that file as the only list of public tokens the page may name. Capture help text or spec excerpts with a deterministic command, then convert the capture into a token file a human reviews. Do not ask a model to transcribe a specification into the inventory, even when the specification is long or awkwardly formatted. A transcription pass can drop a required field and still look complete to a reviewer who did not open the source.
mkdir -p docs/inventory docs/pages docs/slots docs/fixtures tools
# Proposed capture. Replace mycli with the binary the team actually ships.
mycli --help > docs/inventory/mycli-help.txt
{
"page_id": "export-job",
"source_capture": "docs/inventory/mycli-help.txt",
"note": "Proposed inventory. Defaults are null because this file is not a captured product.",
"tokens": [
{"id": "flag.out", "kind": "flag", "public": true, "default": null},
{"id": "flag.since", "kind": "flag", "public": true, "default": null},
{"id": "err.partial", "kind": "error", "public": true, "default": null}
]
}
If the source is OpenAPI, extract operation ids, parameters, and response codes with a parser the team already trusts. Keep the raw capture next to the token file so a reviewer can see what the parser dropped or renamed. Commit the raw capture and the token file in the same change, and reject token files that have no matching capture. This step is finished only when a human has marked each token public, internal, or omitted from the page.
2. Write the block-role manifest
The manifest is the contract between extraction and drafting, and it should be reviewed like a schema change rather than like prose. Each block has a role of extract or orient, plus the token ids that the role is allowed to mention. Extract blocks are rendered from the token file and are not sent to the model as writable text. Orient blocks are the only regions a drafting job may replace, and each one carries a word cap.
# docs/inventory/export-job.manifest.yaml
# Proposed example. Not a captured production file.
page_id: export-job
inventory_sha256: "<fill-after-step-3>"
blocks:
- id: flags
role: extract
source: docs/inventory/export-job.tokens.json
- id: sequencing
role: orient
max_words: 120
allowed_token_ids: [flag.out, flag.since, err.partial]
- id: skip-when
role: orient
max_words: 80
allowed_token_ids: [flag.since, err.partial]
tokens:
- id: flag.out
surface: ["--out"]
- id: flag.since
surface: ["--since"]
- id: err.partial
surface: ["EXPORT_PARTIAL", "partial export"]
The surface list is the set of spellings a sentence may use for a token, and the sample keeps those spellings distinctive. A multi-word surface, such as a named error phrase, belongs in that list so the checker can allow the phrase without allowing nearby inventions. Keep allowed_token_ids narrow, because a slot that may name every token on the page is almost as loose as a full-page prompt. If two slots need the same flag, list that flag in both, rather than creating one large slot that covers the whole page.
3. Lock the inventory hash
Compute a hash after a human accepts the token file, and store that hash in the manifest before any slot is drafted. The hash binds the prose job to a specific inventory, so a quiet edit cannot change the contract under an old draft. If the token file changes, the hash check fails until a human updates the manifest in the same commit on purpose. That failure is useful, because orientation jobs should stop when the interface moves instead of rewriting themselves around the change.
python3 - <<'PY'
import hashlib
import pathlib
p = pathlib.Path("docs/inventory/export-job.tokens.json")
print(hashlib.sha256(p.read_bytes()).hexdigest())
PY
Paste the printed digest into inventory_sha256, and do not let a drafting script update that field automatically. An automatic hash update would hide the very edit the lock is meant to surface during review. Reviewers should look at the token diff first, then accept or reject the new digest as a separate decision. Only after that decision should anyone schedule a new orientation draft for the slots whose allowed tokens changed.
4. Draft only the orientation slots
Send the model the slot id, the word cap, and the allowed surfaces, and ask for that slot alone. Do not send a request that says to write the reference page, the introduction, and the examples in one pass. A narrow prompt reduces the chance that the model adds a sibling flag that merely sounds consistent with the table. Save the result under a slot file named for the block id, and leave every extract block untouched by that job.
Draft slot sequencing only.
Allowed surfaces: --out, --since, EXPORT_PARTIAL, partial export.
Do not name any other flag, environment variable, status, or error code.
Do not restate defaults, types, or status codes.
Maximum 120 words.
When a drafting tool is useful for this step, keep it on slot text only, after the inventory hash is already committed. MonkeyCode's free model access can draft an approved orientation slot, and the free server option can run the hash step and the checker.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
This article does not claim particular model names, quotas, hardware sizes, time limits, or benchmark results for that setup. The page remains useful without those options, because the manifest, the hash, and the checker are ordinary repository files. A local Python install is enough to rehearse the gate before any hosted runner is involved. The hosted option is a placement choice for steps that are already specified, not a source of interface facts.
5. Render the page from roles
A renderer should concatenate extracted tables and orientation files in manifest order, without asking a model to assemble the page. It should refuse to read a slot file for an extract block, because that file is evidence that a draft overwrote a locked region. It should also refuse to render an orient block whose slot file is missing, rather than filling the gap with generated prose. Missing prose is a visible gap a human can fill, while silent generation hides the fact that no approved slot text existed.
6. Run the token checker
The checker loads allowed surfaces, rejects unknown interface-like tokens in orientation files, and verifies the inventory hash before it reads prose. It is a proposed, unexecuted example, so treat the results as a specification for the gate rather than as measured outcomes. The script depends on PyYAML, uses only the standard library otherwise, and never contacts a model or a network service. Run it in the same job that builds the page, and block publication when the exit code is nonzero.
#!/usr/bin/env python3
"""Proposed, unexecuted checker for orientation slots.
Compares slot files with a locked manifest. It does not call a model.
Requires PyYAML.
"""
from __future__ import annotations
import hashlib
import json
import re
import sys
from pathlib import Path
import yaml
FLAG_HINT = re.compile(r"--[a-z0-9][-a-z0-9]*")
CONST_HINT = re.compile(r"\b[A-Z][A-Z0-9_]{2,}\b")
HTTP_HINT = re.compile(r"\bHTTP\s+\d{3}\b")
def load_manifest(path: Path) -> dict:
data = yaml.safe_load(path.read_text(encoding="utf-8"))
if not isinstance(data, dict):
raise SystemExit("manifest must be a mapping")
return data
def assert_hash(manifest: dict, token_path: Path) -> None:
expected = str(manifest.get("inventory_sha256", ""))
actual = hashlib.sha256(token_path.read_bytes()).hexdigest()
if expected != actual:
raise SystemExit(
f"inventory hash mismatch: manifest={expected} file={actual}"
)
def assert_ids(manifest: dict, token_path: Path) -> None:
payload = json.loads(token_path.read_text(encoding="utf-8"))
ids = {row["id"] for row in payload["tokens"]}
for row in manifest["tokens"]:
if row["id"] not in ids:
raise SystemExit(
f"manifest token missing from inventory: {row['id']}"
)
def surfaces(manifest: dict, allowed_ids: list[str]) -> list[str]:
by_id = {row["id"]: row["surface"] for row in manifest["tokens"]}
found: list[str] = []
for token_id in allowed_ids:
if token_id not in by_id:
raise SystemExit(f"unknown token id in slot: {token_id}")
found.extend(by_id[token_id])
return found
def strip_allowed(text: str, allowed: list[str]) -> str:
cleaned = text
for phrase in sorted(set(allowed), key=len, reverse=True):
if phrase.startswith("--"):
pattern = re.escape(phrase) + r"(?![A-Za-z0-9-])"
elif " " in phrase:
pattern = re.escape(phrase)
else:
pattern = r"\b" + re.escape(phrase) + r"\b"
cleaned = re.sub(pattern, " ", cleaned, flags=re.IGNORECASE)
return cleaned
def unknown_hints(text: str, allowed: list[str]) -> list[str]:
leftovers = strip_allowed(text, allowed)
found: list[str] = []
found.extend(FLAG_HINT.findall(leftovers))
found.extend(CONST_HINT.findall(leftovers))
found.extend(HTTP_HINT.findall(leftovers))
return found
def main() -> None:
if len(sys.argv) != 4:
raise SystemExit(
"usage: check_orientation_slots.py MANIFEST TOKENS SLOT_DIR"
)
manifest = load_manifest(Path(sys.argv[1]))
token_path = Path(sys.argv[2])
slot_dir = Path(sys.argv[3])
assert_hash(manifest, token_path)
assert_ids(manifest, token_path)
failures: list[str] = []
for block in manifest["blocks"]:
slot_file = slot_dir / f"{block['id']}.md"
if block["role"] != "orient":
if slot_file.exists():
failures.append(f"extract block has a draft file: {slot_file}")
continue
if not slot_file.exists():
failures.append(f"missing orientation slot: {slot_file}")
continue
text = slot_file.read_text(encoding="utf-8")
words = text.split()
if len(words) > int(block["max_words"]):
failures.append(
f"{block['id']} has {len(words)} words; cap is {block['max_words']}"
)
allowed = surfaces(manifest, block["allowed_token_ids"])
for hint in unknown_hints(text, allowed):
failures.append(
f"{block['id']} names unlisted interface-like token: {hint}"
)
if failures:
print("\n".join(failures))
raise SystemExit(1)
print("orientation slots match the locked inventory")
if __name__ == "__main__":
main()
A passing slot uses only allowed surfaces and stays under the word cap recorded for that block in the manifest. A failing slot names a flag or an error constant that never appears in the surface list for that slot. The sample failure below uses a gzip flag and an export timeout constant, neither of which is present in the sample inventory. Against that failing fixture, the checker is specified to exit nonzero and to print each unlisted flag or constant.
Write the archive with --out, then narrow the window with --since.
If the job reports EXPORT_PARTIAL, rerun only after the source is complete.
Do not treat a partial export as a finished archive.
<!-- Proposed failing fixture. Keep it outside docs/slots until you want a red build. -->
Pass --gzip when the archive is large, and set EXPORT_TIMEOUT if the network is slow.
python3 tools/check_orientation_slots.py \
docs/inventory/export-job.manifest.yaml \
docs/inventory/export-job.tokens.json \
docs/slots
How to read a checker failure
Read a failure as a routing decision, not as a suggestion to regenerate the whole page and hope the next draft is cleaner. The table below maps common signals to the role that must change and to the person or tool that should act. Use it during review so a model is not asked to repair a row that a human still has to own. If the table and the checker disagree, trust the inventory and fix the checker, rather than widening the prompt to silence the gate.
| Signal | Role that owns the fix | Required action |
|---|---|---|
| Unlisted flag or error constant in a slot | orient | Reject the slot and redraft with the same allowed surfaces |
| Default, type, or status restated in prose | orient | Delete the restatement and render the field from the inventory |
| Sequencing advice that uses only allowed surfaces | orient | Human edit for clarity, then accept |
| Missing deprecation marker or wrong default | extract | Human updates the token file, then rehashes |
| New public error code | extract | Human adds the token, rehashes, then refreshes affected slots |
| Extract block has a slot file | extract | Delete the slot file and investigate how it was written |
| Hash mismatch | inventory | Stop drafts until a human accepts the new digest |
Limitations of the gate
Word-boundary and hint matching miss inventions that avoid tokens, such as a sentence that adds an optional path argument without naming it. A human still needs to read every orientation slot, because a real flag can be described incorrectly without introducing a new token. Phrase search can false-positive when a surface is a common word, so prefer a distinctive spelling whenever the interface gives you one. The checker does not review security claims, legal wording, support promises, or whether a public token should have been public.
Hash locking detects inventory drift, but it does not tell you whether the accepted inventory matched the binary shipped last week. You still need a release process that ties the capture command to a version, a commit, or another source you can reopen. The hint pattern is intentionally narrow, and a team with different token shapes should extend the tests before extending the regular expression. Widening the pattern without fixtures will create noise, and reviewers will start ignoring the gate that this workflow depends on.
Who should not use this approach
Teams with no specification, help capture, or other inventory source should not adopt this workflow yet. The checker would then bless a hand-typed list that nobody tied to a shipping interface, which recreates the original risk under a new filename. Marketing pages, incident narratives, and conceptual essays without stable tokens are a poor fit for extract and orient roles. Anyone who wants a model to discover the interface should stop, because discovery is an ownership task rather than a drafting task.
Large generated references that are already rendered entirely from a specification may not need orientation slots at all. Adding slots there only creates prose that can drift from the generator, so prefer improving the generator over adding a second writer. Solo maintainers who cannot review slot text should also skip the drafting step and keep the page fully extracted. A gate that nobody reads is worse than a short page, because it suggests a control that the repository does not actually practice.
Put the gate in the build
The practical split is small and strict: tools and humans own interface rows, and a model may write only inside preapproved orientation slots. Lock the inventory hash, render extract blocks without a model, and reject any slot that names an unlisted surface. If MonkeyCode's free server is already available to the team, run the hash step and the checker there before merging any free-model slot draft. That order keeps drafting convenience from becoming the source of the interface the page claims to document.
Top comments (0)