DEV Community

Avery Lin
Avery Lin

Posted on

The Docs Ownership Manifest: Turning 'Human Must Own It' Into a CI Check

Documentation ownership policies fail when they live in a README paragraph instead of a machine-checkable file. A manifest that maps every documentation section to a model-drafted or human-owned bucket turns a vague promise into a failing CI build. This article provides a runnable checker, a classification table, and the limits of the approach.

Previous posts in this series described the draft/own split as a workflow; this one makes it executable. Model-generated documentation has a cost problem that is easy to miss. Drafting is nearly free, but verification is expensive, and teams rarely budget for the second half. When every section looks polished, reviewers stop asking which claims were actually checked.

What the model may draft

The first step is deciding which sections belong in the model bucket. The safe categories are mechanical descriptions that mirror the source code directly and require no external judgment. Parameter tables, installation steps, error message catalogs, and code samples copied from tested examples all qualify.

The model bucket has one hard rule: every drafted section must carry a visible marker and a reviewer signature. The marker makes provenance obvious, and the signature records that a human actually read the section. Without both markers present, the section remains unverified by definition.

What a human must own

The human bucket is smaller but carries all the risk. Compatibility guarantees, deprecation timelines, security claims, performance numbers, and design rationale should never start as model output. These statements bind the project to external expectations, and a plausible-sounding draft is worse than no draft.

A model can summarize what the code does today, but it cannot know what the team promised last quarter. It cannot know which behavior is accidental and which is contractual. Those decisions require product context that no prompt can supply.

Category Model may draft Human must own Review depth
Parameter tables Yes Sign-off Verify against source
Installation steps Yes Sign-off Run the steps once
Error message catalog Yes Sign-off Spot-check codes
Code samples Yes Sign-off Execute the sample
Compatibility guarantees No Write from scratch Design review
Deprecation timelines No Write from scratch Product decision
Security claims No Write from scratch Security review
Performance numbers No Write from scratch Benchmark audit

The ownership manifest

The contract itself is a small YAML file that lives next to the documentation. Each entry names a file, a heading, and an owner. The checker treats the manifest as the single source of truth, so changing ownership becomes a code review instead of a hallway conversation.

# docs/ownership.yaml
files:
  - path: api-reference.md
    sections:
      - heading: "Parameters"
        owner: model
      - heading: "Error codes"
        owner: model
      - heading: "Compatibility guarantees"
        owner: human
      - heading: "Deprecation policy"
        owner: human
Enter fullscreen mode Exit fullscreen mode

The matching Markdown section for a model-drafted block carries two markers. The first declares provenance, and the second records the human review. Both are plain HTML comments, so they stay invisible in the rendered page.

## Parameters

<!-- AI-DRAFTED -->

| name | type | description |
|------|------|-------------|
| retries | int | Maximum attempts before failure |

<!-- REVIEWED_BY: Avery Lin, 2026-08-27 -->
Enter fullscreen mode Exit fullscreen mode

Why HTML comments instead of a sidecar metadata file? Because the markers travel with the section when someone copies it into an issue or a changelog. A sidecar file loses that association the moment the content moves.

The enforcement script

The checker is a single Python file with no logic beyond reading the manifest and scanning headings. It fails on four conditions: a missing file, a missing heading, a model section without markers, or a human section that carries an AI marker. The script depends on PyYAML, so the CI step installs it first.

#!/usr/bin/env python3
"""Enforce a documentation ownership manifest."""

import sys
from pathlib import Path

import yaml

MANIFEST = Path("docs/ownership.yaml")
DOCS_DIR = Path("docs")


def extract_sections(md_path: Path):
    sections = []
    current_heading = None
    current_lines = []
    for line in md_path.read_text().splitlines():
        if line.startswith("## "):
            if current_heading is not None:
                sections.append((current_heading, current_lines))
            current_heading = line[3:].strip()
            current_lines = []
        elif current_heading is not None:
            current_lines.append(line)
    if current_heading is not None:
        sections.append((current_heading, current_lines))
    return dict(sections)


def check_file(entry: dict) -> list[str]:
    md_path = DOCS_DIR / entry["path"]
    if not md_path.exists():
        return [f"missing file: {entry['path']}"]
    errors = []
    sections = extract_sections(md_path)
    for rule in entry.get("sections", []):
        heading = rule["heading"]
        owner = rule.get("owner", "human")
        if heading not in sections:
            errors.append(f"missing section: {entry['path']} ## {heading}")
            continue
        text = "\n".join(sections[heading])
        if owner == "model":
            if "AI-DRAFTED" not in text:
                errors.append(f"model section lacks AI-DRAFTED: {entry['path']} ## {heading}")
            if "REVIEWED_BY" not in text:
                errors.append(f"model section lacks REVIEWED_BY: {entry['path']} ## {heading}")
        elif "AI-DRAFTED" in text:
            errors.append(f"human-owned section has AI-DRAFTED: {entry['path']} ## {heading}")
    return errors


def main() -> int:
    manifest = yaml.safe_load(MANIFEST.read_text())
    errors = []
    for entry in manifest["files"]:
        errors.extend(check_file(entry))
    for error in errors:
        print(f"  - {error}")
    return 1 if errors else 0


if __name__ == "__main__":
    sys.exit(main())
Enter fullscreen mode Exit fullscreen mode

A merge that adds a new section without updating the manifest fails, which is exactly the point. Ownership becomes part of the definition of done, not an afterthought in the review thread.

pip install pyyaml
python scripts/check_doc_ownership.py
Enter fullscreen mode Exit fullscreen mode

Rolling it out in five steps

  1. Classify every existing section with the decision table above.
  2. Encode the result in docs/ownership.yaml next to the documentation.
  3. Add the AI-DRAFTED and REVIEWED_BY markers to every model-owned section.
  4. Run the checker locally and fix violations before opening the pull request.
  5. Add the CI job below and make it a required check for merges.

Wiring it into CI

The gate belongs in the same pipeline that runs your tests. A documentation change that violates the manifest should block the merge, because a docs-only PR is the one nobody reviews carefully.

# .github/workflows/docs-ownership.yml
name: docs-ownership
on: [pull_request]
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install pyyaml
      - run: python scripts/check_doc_ownership.py
Enter fullscreen mode Exit fullscreen mode

The workflow file runs on every pull request, including draft PRs, so ownership violations surface early. If your repository already uses a path filter for docs changes, apply the same filter here to avoid noise on unrelated commits.

For the drafting half of this pipeline, MonkeyCode offers free model access and a free server option, which covers the initial generation of parameter tables and code samples. Disclosure: This article was prepared as part of MonkeyCode's product outreach. The enforcement script is tool-agnostic and remains useful even if you switch drafting tools.

Limitations

The script verifies markers, not truth. A reviewer can sign a section without reading it, and a model can be listed as the owner of a section that a human actually wrote. The check is a process guard, not a quality guarantee.

The approach also assumes stable headings and a disciplined team. If sections are renamed casually, the manifest drifts and the build starts failing for the wrong reasons. Duplicate headings collapse to the last occurrence, so keep heading names unique.

Teams that should not use this workflow include single-person projects with no external readers and documentation generated entirely from code comments. In those cases, the manifest adds ceremony without adding safety. The contract earns its keep only when multiple people touch the docs and the consequences of a wrong claim are real.

Top comments (0)