DEV Community

Avery Lin
Avery Lin

Posted on

Free to Draft, Costly to Verify: An Ownership Boundary for Generated Docs

The real cost of documentation moves when generation becomes free, and it lands on verification. A model can draft a page in seconds, but a human still has to decide whether every claim is safe to publish. The useful question is therefore not how many words you can generate, but which parts of a doc a human must own before it ships. This article defines that ownership boundary, then gives you a merge gate that enforces it.

Drafting is one pass; verification is per claim

Generation is a single pass, while verification is a per-claim judgment, and the two never scale together. A draft that costs nothing still has to be read, checked against the code, and judged for accuracy, exactly like a draft that cost hours. Generated text adds one more task on top: you must verify the verifier, because the model cannot confirm its own statements against your codebase. The only way to keep review sane is to limit what the model is allowed to write in the first place.

The classification below follows one structural rule instead of a style preference. A documentation section is draftable when an automated check can confirm its claims, and it becomes human-owned when correctness depends on judgment, policy, or experience that no script can evaluate. A function signature can be verified by parsing the source, so a model may draft it. A deprecation promise is a policy decision about what you will support, so the model may collect facts about it but may not write the final sentence.

The draftable and the owned

Doc component Model may draft Human must own Cheapest verification
API reference scaffold Yes Signatures match real code Automated AST diff against source
Code examples Yes, first pass Examples run against real state CI execution with fixtures
Tutorial structure Draft outline Narrative matches actual behavior Human walk-through
Error and edge-case lists Candidate list List covers real failure modes Review against issue tracker
Architecture decisions No Rationale, trade-offs, rejected options Human author plus reviewer
Security and threat notes No Threat model, impact, mitigations Human author plus security review
Deprecation and version promises No Which contracts stay stable Human plus linked changelog
Limitations Candidate list What is actually tested and true Curated by a human

Read the table as a negotiation, not as a fixed answer. A team without a dedicated security reviewer should move even more sections into the human-owned column. A team with golden-path examples and no public API might drop the reference scaffold entirely. What should never change is the rule itself: draft what you can verify cheaply, and own what only a human can judge.

The fragment drafting loop

The boundary only helps when it is set before the first prompt, so the workflow starts with a plan and not with a request. Each step below assumes you run the loop with MonkeyCode's free model access for the fragment drafts and MonkeyCode's free server option for the orchestration step.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

Neither the workflow nor the gate depends on those choices; they simply make many small iterations affordable, which is exactly what the next five steps require.

  1. Write the plan first. List every section of the target doc and mark each one model or human, using the table above. Do not open the editor until the plan exists.
  2. Draft one fragment at a time. Send a small prompt per fragment instead of one request for the whole document, which keeps every claim isolated and testable.
  3. Verify the draftable fragments cheaply. Run the examples in CI, diff generated signatures against the source, and capture command output rather than trusting prose.
  4. Convert owned sections into question stubs. Turn each human-owned section into its open questions, so ownership becomes an enumerated list instead of a vague instruction.
  5. Merge only when the gate passes. Run the checker below in CI, and treat any unresolved human-owned stub as a failed build.

The artifact: a plan contract and a merge gate

The script below takes a JSON plan, creates the fragment drafts and the human question stubs, and then blocks any merge that still contains an unresolved human-owned section. It uses only the standard library, and the draft() function is a placeholder for the endpoint you configured, which keeps the code honest about what it cannot do.

{
  "sections": [
    {
      "id": "api_reference",
      "owner": "model",
      "prompt": "List every public function in src/ with its exact signature."
    },
    {
      "id": "examples",
      "owner": "model",
      "prompt": "Write three usage examples for the public API."
    },
    {
      "id": "security_notes",
      "owner": "human",
      "questions": [
        "What credentials does this service hold?",
        "What is the impact if a single token leaks?"
      ]
    },
    {
      "id": "deprecation_policy",
      "owner": "human",
      "questions": [
        "Which client versions stay supported this quarter?",
        "What is the migration path for the older clients?"
      ]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode
#!/usr/bin/env python3
'''Enforce section ownership before a single token is generated.'''
import json
import pathlib
import sys


def load_plan(path: str) -> dict:
    return json.loads(pathlib.Path(path).read_text())


def draft(section: dict) -> str:
    # Replace with a call to the model endpoint you configured.
    return '<!-- draft:' + section['id'] + ' -->\n\n(placeholder draft)\n'


def human_stub(section: dict) -> str:
    questions = '\n'.join('- [ ] ' + q for q in section['questions'])
    return '<!-- owner:' + section['id'] + ' -->\n\n' + questions + '\n'


def assemble(plan: dict) -> str:
    parts = []
    for section in plan['sections']:
        if section['owner'] == 'model':
            parts.append(draft(section))
        elif section['owner'] == 'human':
            parts.append(human_stub(section))
        else:
            raise SystemExit('unknown owner: ' + section['owner'])
    return '\n\n'.join(parts)


def unresolved_owners(plan: dict, doc: str) -> list:
    return [
        s['id']
        for s in plan['sections']
        if s['owner'] == 'human' and 'owner:' + s['id'] in doc
    ]


def main() -> None:
    plan = load_plan(sys.argv[1])
    doc_path = pathlib.Path(sys.argv[2])
    if '--draft' in sys.argv:
        doc_path.write_text(assemble(plan))
        print('drafted ' + str(doc_path) + '; human stubs are waiting')
        return
    doc = doc_path.read_text()
    missing = unresolved_owners(plan, doc)
    if missing:
        raise SystemExit('merge blocked: human must own -> ' + ', '.join(missing))
    print('ownership contract satisfied')


if __name__ == '__main__':
    main()
Enter fullscreen mode Exit fullscreen mode

Use the tool in two phases. The first command generates the fragment drafts plus the question stubs, and the second runs as a CI gate on every pull request that touches the document.

python doc_contract.py docs/plan.json docs/guide.md --draft
python doc_contract.py docs/plan.json docs/guide.md
Enter fullscreen mode Exit fullscreen mode

A failing run is the feature

Assume the examples pass, the signature diff passes, and someone still forgets the security note on the way to the pull request. The gate fails with a message that is also an action list.

$ python doc_contract.py docs/plan.json docs/guide.md
merge blocked: human must own -> security_notes
Enter fullscreen mode Exit fullscreen mode

The developer opens the generated stub, answers the two questions, removes the ownership marker, and the build turns green. The mechanism turns a review comment into a checkbox the author cannot quietly skip, because the model can never clear the marker for the human sections. That is the point of the gate: the free draft stops at the boundary, and the human step becomes visible in the diff.

What this does not do

The gate proves that a human edited the section, not that the edit is correct, so a thoughtful reviewer still matters. The verify column from the table is not enforced by this minimal script, which means example execution and signature diffs belong in your existing CI alongside it. The draft() placeholder needs a real endpoint, so treat the code as a starting point rather than a finished integration.

Teams with pure prose docs and no examples will find this ceremony heavy, because the fragment plan adds a second source of truth to maintain. Teams with one expert writer who owns the whole document will likely prefer their existing template. And anyone who publishes generated text without human review should not add this, because the gate allocates effort but never creates expertise.

Start with one doc

Pick the next document that touches a security note or a deprecation promise, write its plan before any prompt, and let the gate remind everyone who owns what. One doc, one plan, and one failing build is enough to see the boundary working. The boundary will do more for your documentation than another round of prompting ever will.

Top comments (0)