DEV Community

Avery Lin
Avery Lin

Posted on

Classify Doc Paragraphs Into Compile Lanes and Signature Lanes

Generated API documentation fails most often when compile work and signature work share one prompt. A model can restate schemas, examples, and error catalogs when those artifacts already exist in the repository. A human must still own deprecation windows, support scope, and any sentence that binds the product to a future. This article defines a draft-lane workflow, a decision table, and a small classifier you can run before merge.

Separate restatement from obligation

Reviewers treat a generated page as a single artifact, which hides mixed authority inside ordinary paragraphs. Compile statements can be checked against OpenAPI files, fixture payloads, and status enumerations already stored in git. Signature statements cannot be checked that way, because they create obligations the repository does not yet prove. Mixing both classes in one draft forces humans to reread tables they could have trusted and still miss a buried promise.

The practical fix is not a better prompt. It is a lane tag on every documentation block, plus a merge gate that refuses untagged or mis-tagged text. Compile lanes may be drafted by a model that only restates checked-in files. Signature lanes stay empty until a named human writes them, and the classifier treats an unsigned promise as a failed check.

Decision table: what may be drafted

Use this table as the contract for the generator, not as optional review folklore. If a block type is absent from the table, the gate must fail closed rather than guess a lane.

Block type Lane Allowed inputs Merge rule
Path and operation summaries copied from OpenAPI summary compile openapi.yaml pointer Fail if text diverges from the pointer
Parameter and field tables compile schema + required Fail if extra columns appear
Request and response examples compile fixtures under fixtures/ Fail if JSON is not a fixture clone
Error code catalogs compile components.responses or error enum Fail if a code lacks a schema source
Enum value lists compile schema enum Fail if order or values drift
Auth scheme names and header identifiers compile securitySchemes Fail if a name is not in the spec
Deprecation or sunset wording signature none from the model Fail if present without owner
Support hours, channels, or severity targets signature none from the model Fail if present without owner
Migration deadlines and “clients must” sentences signature none from the model Fail if present without owner
Security guarantees and data-retention claims signature none from the model Fail if present without owner
Pricing, quota, and rate-limit promises signature none from the model Fail if present without owner

The table is the artifact reviewers should argue about before any model runs. Changing a row is a docs-policy change, not a prompt tweak hidden in chat history.

Workflow

Follow the steps in order. Skipping the lane file and jumping to generation recreates the mixed-authority page this gate exists to stop.

  1. Inventory every heading in the reference set and map it to one row in the decision table above. Headings that do not match a row stay unpublished until the table is extended by a reviewer.
  2. Write a lane file that lists compile sources and signature owners, then commit that file before any draft job is allowed to start. The lane file is policy, so it needs the same review as an OpenAPI change.
  3. Insert a lane marker immediately under each heading, including a source pointer for compile blocks or an owner identity for signature blocks. Untagged headings are merge failures, not style nits.
  4. Run the compile job only against tagged compile blocks, with the model forbidden from creating new headings. New headings would bypass the inventory and smuggle signature text into a compile page.
  5. Run the classifier in continuous integration and reject the change when compile text matches a promise pattern or when a signature block lacks an owner. Do not rely on a later editorial pass to catch lane leaks.
  6. Require the signature owner to write remaining blocks in a separate commit after the compile page is already green. Mixing both edits in one diff hides which sentences were machine restated.

Lane markers in Markdown

The marker format is deliberately boring so a small script can parse it. HTML comments survive most static-site pipelines and remain invisible to readers.

## List widgets

<!-- lane:compile source:openapi.yaml#/paths/~1widgets/get -->

| Name | In | Type | Required |
| --- | --- | --- | --- |
| limit | query | integer | false |

## Support window

<!-- lane:signature owner:api-steward -->

Replacement text for this heading is written by the owner after compile checks pass.
Enter fullscreen mode Exit fullscreen mode

Compile markers without source are invalid. Signature markers without owner are invalid. A heading that contains both markers is invalid, because mixed authority inside one section is the failure mode under review.

Classifier script

The following script is a proposal you can run locally. It is a gate, not a semantic proof that examples are correct, and it should fail closed on unknown headings.

#!/usr/bin/env python3
"""Fail CI when doc headings lack lanes or compile text looks like a promise."""
from __future__ import annotations

import argparse
import re
import sys
from pathlib import Path

HEADING = re.compile(r"^(#{2,6})\s+(.+)$")
LANE = re.compile(
    r"<!--\s*lane:(compile|signature)"
    r"(?:\s+source:(?P<source>\S+))?"
    r"(?:\s+owner:(?P<owner>\S+))?\s*-->"
)
PROMISE = re.compile(
    r"(?ix)\b("
    r"we\s+will|guaranteed|sla|slo|"
    r"deprecated\s+on|sunset|until\s+\d{4}|"
    r"clients\s+must|support\s+hours|p[1-3]\s+response|"
    r"retention\s+period|rate\s+limit\s+will"
    r")\b"
)


def iter_blocks(text: str):
    lines = text.splitlines()
    i = 0
    while i < len(lines):
        hm = HEADING.match(lines[i])
        if not hm:
            i += 1
            continue
        title = hm.group(2).strip()
        body = []
        i += 1
        while i < len(lines) and not HEADING.match(lines[i]):
            body.append(lines[i])
            i += 1
        yield title, "\n".join(body)


def check_file(path: Path) -> list[str]:
    errors = []
    for title, body in iter_blocks(path.read_text(encoding="utf-8")):
        loc = f"{path} :: {title}"
        m = LANE.search(body)
        if not m:
            errors.append(f"{loc}: missing lane marker")
            continue
        lane = m.group(1)
        if lane == "compile":
            if not m.group("source"):
                errors.append(f"{loc}: compile lane requires source=")
            if PROMISE.search(body):
                errors.append(f"{loc}: compile lane contains a signature sentence")
            if m.group("owner"):
                errors.append(f"{loc}: compile lane must not set owner=")
        elif lane == "signature":
            if not m.group("owner"):
                errors.append(f"{loc}: signature lane requires owner=")
            if m.group("source"):
                errors.append(f"{loc}: signature lane must not set source=")
    return errors


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--docs", type=Path, required=True)
    args = parser.parse_args()
    files = sorted(args.docs.rglob("*.md"))
    if not files:
        print("no markdown files under", args.docs, file=sys.stderr)
        return 2
    errors = []
    for path in files:
        errors.extend(check_file(path))
    for item in errors:
        print(item)
    return 1 if errors else 0


if __name__ == "__main__":
    raise SystemExit(main())
Enter fullscreen mode Exit fullscreen mode

Run it against the reference tree before the compile job and again after the model writes Markdown. The second run is the one that catches leaked promises.

python3 lane_gate.py --docs ./docs/reference
echo "exit $?"
Enter fullscreen mode Exit fullscreen mode

A zero exit code means every heading has exactly one valid lane. It does not mean the compile tables match the schema; that check still belongs to a schema diff or a fixture comparison in a later job.

Where a free compile job fits

A compile lane does not require a paid coding agent or a privileged production network to restate schema tables. MonkeyCode's free model access and free server option can run the restatement job against checked-in OpenAPI and fixture files. Disclosure: This article was prepared as part of MonkeyCode's product outreach. The same lane file still blocks merge when the model emits a signature sentence, which keeps product tooling limited to the compile path.

Keep the server job dumb. Pass only the tagged compile sections and the source files named in those tags, then write output back over those sections alone. If the job is allowed to rewrite the whole page, it can delete signature markers or invent headings the inventory never approved.

Test plan for the gate

Label these cases as unexecuted until you add them to CI. They are the minimum set that proves the classifier is doing lane work rather than spelling work.

  1. A heading with no comment must fail, even when the body is a perfect parameter table copied from OpenAPI. Missing authority is a merge blocker.
  2. A compile heading whose body includes we will or a four-digit sunset year must fail, even when a valid source pointer is present. Promise vocabulary is not a table.
  3. A signature heading with owner:api-steward and no compile source must pass, even when the body is still a placeholder sentence. Empty promises are allowed only as owned stubs.
  4. A heading that contains both source and owner must fail, because the block would again mix compile authority with signature authority.
  5. After a model draft, rerun the gate and confirm newly added headings do not appear. Heading creation is a policy change, not a compile side effect.

Limitations

Regular expressions do not understand obligation. A compile block can still understate a required field, clone a stale fixture, or drop an error code that the schema still defines. The gate only stops untagged blocks and a small vocabulary of promise sentences; it will miss a politely worded commitment that avoids those phrases.

Signature owners can rubber-stamp a stub and leave customers without a real support window. The owner field records accountability, not completeness, and legal review remains outside this workflow. Some documentation pipelines strip HTML comments, which would remove markers and fail every heading, or worse, fail open if the strip happens after CI.

The workflow also assumes a reviewed OpenAPI file and fixture directory already exist. Without those inputs, compile lanes have nothing honest to restate, and the model will fill tables from training residue. That output is not a compile lane, even if you paste a marker above it.

Who should not use this approach

Do not apply lane tags to narrative incident reports, partner announcements, or pricing pages that are signature text from the first heading to the last. The classifier will either fail everything or teach authors to hide promises in wording the regex does not catch. Do not use it on SDKs where generated examples must compile against live clients; fixture cloning is weaker than a type-checked sample project.

Teams without a named docs steward should not enable the signature lane at all. An owner value that points at a rotating chat room creates the appearance of a signature while leaving nobody to write the block. In that case, omit the heading from the public reference set until ownership exists.

Compile-only teams that already freeze field tables from OpenAPI can add this classifier without changing release cadence. The useful first patch is the lane file and the failing test for untagged headings, not a larger generator rewrite.

Top comments (1)

Collapse
 
raknaos profile image
Raknaos

The decision-table idea works because reviews of generated docs fail the same way everywhere: people read one artifact and can't see which sentences carry a promise the company has to keep. Separating restatement from obligation makes that visible before merge instead of in production.

How do you handle the leaky middle, though? Error catalogs and support-scope tables are compile-lane-looking content that quietly bind the product to the future. That boundary is where every classifier rule I've written has failed first.