DEV Community

Avery Lin
Avery Lin

Posted on

Freeze Schema Snapshots for Reference Tables; Hand-Write Production Failure Semantics

Generated reference tables stay honest when they compile from a hashed schema snapshot, not from chat memory of last week's routes. Chat-written parameter lists drift after a rename, and they often invent status codes the service never returns. The durable split is mechanical tables on one side and operational semantics on the other, with CI enforcing both. This article gives a snapshot file, a table compiler, an ownership manifest, and a check that fails dirty diffs.

Separate projection from meaning before any model runs

A parameter table is a projection of types, required flags, and enumerated values that already live in a reviewed schema. Failure semantics are not in that schema: they describe retries, partial commits, and which 409 is a quota event rather than a uniqueness clash. Mixing those layers in one prompt produces fluent pages that look complete while smuggling unverified operational claims into generated markdown. Teams that already pin examples to fixture hashes still leak meaning when a model rewrites what an error means in production.

The ownership boundary has to sit at the section, not only at the example block. Reference tables may be regenerated on every schema freeze. Production failure notes must remain human-owned files that the generator cannot open for write.

Inputs you freeze before the first draft

Do not start from a live OpenAPI URL during generation, because a moving document makes every table unverifiable later. Capture a snapshot, hash it, and treat that digest as the only legal input to the table compiler. Label the following as a proposed layout until your repository actually stores these paths.

  1. Write docs/_facts/schema.snapshot.json from the reviewed spec, not from a model summary of the spec.
  2. Record docs/_facts/schema.snapshot.sha256 in the same commit as the snapshot bytes themselves.
  3. Keep docs/reference/ writable by the compiler and keep docs/ops/ unwritable by that same process.
  4. Store docs/_facts/ownership.yaml so continuous integration can reject a model diff that crosses ops paths.

The snapshot is a facts file for structure only. It must not contain retry advice, SLA sentences, or incident language that a human has not already signed elsewhere.

What the model may draft, and what it must not touch

Use the table below as a decision artifact during review, not as a vibe check after the pull request is already green. If a row would change customer behavior during an outage, it belongs on the human side even when the wording looks like a status table.

Section Source of truth Author Regenerable
Path list Frozen snapshot Compiler Yes
Query and body parameter tables Frozen snapshot Compiler Yes
Enum value tables Frozen snapshot Compiler Yes
Documented HTTP status codes Frozen snapshot responses map Compiler Yes
Which 409 is quota versus conflict Incident reviews, runbooks Human No
Retry, backoff, and idempotency rules Platform contract Human No
Partial-failure and rollback notes Storage and queue owners Human No
Support window and deprecation policy Product and legal Human No

A free drafting model can fill the regenerable rows if the compiler, not the chat transcript, is the publisher of record. It cannot be the publisher of the last four rows, because those claims are not mechanically present in the snapshot.

Artifact: snapshot, compiler, ownership file, and a failing check

The compiler below is labeled as a worked example. It reads a tiny frozen snapshot, emits markdown tables, and refuses to write under docs/ops/. Operators should extend field coverage rather than asking a model to invent columns the snapshot does not contain.

{
  "snapshot_id": "payments-v3-2026-09-14",
  "paths": {
    "/v3/charges": {
      "post": {
        "operationId": "createCharge",
        "parameters": [
          {"name": "Idempotency-Key", "in": "header", "required": true, "schema": {"type": "string"}},
          {"name": "dry_run", "in": "query", "required": false, "schema": {"type": "boolean"}}
        ],
        "requestBody": {
          "required": true,
          "properties": {
            "amount_cents": {"type": "integer", "minimum": 1},
            "currency": {"type": "string", "enum": ["USD", "EUR"]}
          }
        },
        "responses": {"201": {"description": "created"}, "409": {"description": "conflict"}}
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
# docs/_facts/ownership.yaml
compiler_write:
  - docs/reference/parameters.md
  - docs/reference/enums.md
  - docs/reference/status-codes.md
human_only:
  - docs/ops/failure-semantics.md
  - docs/ops/retry-and-idempotency.md
  - docs/ops/support-windows.md
snapshot: docs/_facts/schema.snapshot.json
snapshot_hash_file: docs/_facts/schema.snapshot.sha256
Enter fullscreen mode Exit fullscreen mode
#!/usr/bin/env python3
"""Compile reference tables from a frozen schema snapshot. Do not write ops paths."""
from __future__ import annotations

import hashlib
import json
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
SNAP = ROOT / "docs/_facts/schema.snapshot.json"
HASH = ROOT / "docs/_facts/schema.snapshot.sha256"
REF = ROOT / "docs/reference"
FORBIDDEN_PREFIX = ROOT / "docs/ops"


def assert_hash() -> None:
    digest = hashlib.sha256(SNAP.read_bytes()).hexdigest()
    recorded = HASH.read_text(encoding="utf-8").strip()
    if digest != recorded:
        raise SystemExit(f"snapshot hash mismatch: {digest} != {recorded}")


def emit_parameters(spec: dict) -> str:
    rows = ["| Operation | Name | In | Required | Type |", "| --- | --- | --- | --- | --- |"]
    for path, item in spec["paths"].items():
        for method, op in item.items():
            op_id = op.get("operationId", f"{method.upper()} {path}")
            for param in op.get("parameters", []):
                schema = param.get("schema", {})
                rows.append(
                    f"| {op_id} | `{param['name']}` | {param['in']} | "
                    f"{param.get('required', False)} | {schema.get('type', '')} |"
                )
            body = op.get("requestBody", {}).get("properties", {})
            for name, schema in body.items():
                required = name in op.get("requestBody", {}).get("required", []) or op.get("requestBody", {}).get("required") is True
                rows.append(
                    f"| {op_id} | `{name}` | body | {bool(required)} | {schema.get('type', '')} |"
                )
    return "<!-- compiled from schema.snapshot.json; do not hand-edit -->\n" + "\n".join(rows) + "\n"


def emit_enums(spec: dict) -> str:
    rows = ["| Field | Values |", "| --- | --- |"]
    for item in spec["paths"].values():
        for op in item.values():
            for name, schema in op.get("requestBody", {}).get("properties", {}).items():
                if "enum" in schema:
                    values = ", ".join(f"`{v}`" for v in schema["enum"])
                    rows.append(f"| `{name}` | {values} |")
    return "<!-- compiled from schema.snapshot.json; do not hand-edit -->\n" + "\n".join(rows) + "\n"


def emit_statuses(spec: dict) -> str:
    rows = ["| Operation | Status | Snapshot note |", "| --- | --- | --- |"]
    for path, item in spec["paths"].items():
        for method, op in item.items():
            op_id = op.get("operationId", f"{method.upper()} {path}")
            for code, body in op.get("responses", {}).items():
                note = body.get("description", "")
                rows.append(f"| {op_id} | `{code}` | {note} |")
    return "<!-- compiled from schema.snapshot.json; do not hand-edit -->\n" + "\n".join(rows) + "\n"


def write_ref(name: str, text: str) -> None:
    target = (REF / name).resolve()
    if FORBIDDEN_PREFIX.resolve() in target.parents or target == FORBIDDEN_PREFIX.resolve():
        raise SystemExit(f"refusing to write human-owned path: {target}")
    target.parent.mkdir(parents=True, exist_ok=True)
    target.write_text(text, encoding="utf-8")


def main() -> None:
    assert_hash()
    spec = json.loads(SNAP.read_text(encoding="utf-8"))
    write_ref("parameters.md", emit_parameters(spec))
    write_ref("enums.md", emit_enums(spec))
    write_ref("status-codes.md", emit_statuses(spec))


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

Human-owned failure notes stay in a separate file that the compiler never opens. The sample below is documentation structure, not a claim about any live payments API.

# Failure semantics (human-owned; do not generate)

The snapshot lists `409` as `conflict`. That token is not enough for operators.

- Treat `409` plus `code=idempotency_replay` as a safe replay, not as a unique-key failure.
- Treat `409` plus `code=quota_exhausted` as a shed load event; do not retry from the client SDK.
- A `201` does not mean the downstream ledger has committed; wait for `charge.settled`.

Retry and rollback rules live in retry-and-idempotency.md, not in the status table.
Enter fullscreen mode Exit fullscreen mode
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(git rev-parse --show-toplevel)"
cd "$ROOT"
expected="$(tr -d '[:space:]' < docs/_facts/schema.snapshot.sha256)"
actual="$(sha256sum docs/_facts/schema.snapshot.json | awk '{print $1}')"
if [[ "$expected" != "$actual" ]]; then
  echo "schema snapshot hash mismatch" >&2
  exit 1
fi
python3 tools/compile_reference_tables.py
git diff --name-only -- docs/ops | grep -q . && {
  echo "compiler or model touched human-owned ops docs" >&2
  exit 1
}
# Fail if compiled files lost their machine header after a free-model edit.
for f in docs/reference/parameters.md docs/reference/enums.md docs/reference/status-codes.md; do
  grep -q "compiled from schema.snapshot.json" "$f" || exit 1
done
Enter fullscreen mode Exit fullscreen mode

Numbered workflow for one schema change

Follow the sequence as a release checklist rather than as optional style guidance. Skipping the hash step is how invented columns re-enter the reference set.

  1. Review the schema change with the API owners, then freeze schema.snapshot.json in a dedicated commit.
  2. Recompute schema.snapshot.sha256 in that same commit so later compilers can refuse a swapped file.
  3. Run compile_reference_tables.py and commit only files under docs/reference/ that the compiler emitted.
  4. Update docs/ops/failure-semantics.md only when production meaning changed, and require a human reviewer on that path.
  5. Reject any model patch whose diff lists docs/ops/ or drops the compiled-from header on reference tables.
  6. Publish the docs set only after both the snapshot hash job and the ops-path guard have passed.

A drafting model can propose table layout during step three if it never becomes the process that writes the markdown. The publisher of record remains the compiler against the hashed snapshot.

Where a free drafting server belongs in this split

Local compilation is enough when the snapshot is already reviewed and the table code is deterministic. Some teams still want a remote box to iterate on compiler prompts, header text, or column order without standing up their own GPU host. Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode's free model access and free server option can host that drafting loop while the repository continues to accept only compiler output under docs/reference/.

Do not send docs/ops/ into that loop, and do not treat model prose as a substitute for the snapshot hash. If the server is unavailable, the same compiler still runs on a laptop because the facts file is already in git. Keep promotion out of the ownership manifest; the manifest is a write-policy, not a product page.

Limitations the compiler will not hide

The snapshot cannot encode store-specific partial failure, because those behaviors are not HTTP response maps. Enum tables will omit values that exist only in a feature flag the schema never listed. Status tables will repeat the snapshot's one-line description, which is usually a token such as conflict, not an operator action. Hash pinning also fails closed if someone force-updates the snapshot without an API review, so the hash is a consistency check rather than a substitute for that review.

This workflow does not measure model quality, latency, or cost, and it does not claim a durability window for any hosted drafting server. It also does not generate getting-started walkthroughs, credential lifecycle copy, or signed tutorials, which remain separate human-owned surfaces.

Who should not use this split

Skip the compiler if you do not yet have a reviewed schema, because the tables would only freeze an untrusted draft. Skip it if legal requires every sentence in one signed narrative, because a generated table still needs a human attestation you may not be able to split. Skip it if your product has no mechanical responses map and every failure mode is tribal knowledge; write the runbook first, then decide whether tables are worth emitting. Autonomous doc bots that rewrite the whole docs/ tree in one pass should not adopt this pattern, because they erase the write boundary the checks exist to protect.

If you try the ownership manifest, review failure-semantics.md in the same change as the schema freeze rather than as a later polish pass.

Top comments (0)