A checkout service documented Idempotency-Key on POST /payments. Overnight, a generator rewrote the page from the OpenAPI description fields. The new paragraph said a repeat call "always returns the original response body."
A merchant retried a timeout. The API answered 409 with code: replay_conflict and a short error body, not the first 201. Support quoted the page. Engineering quoted the handler.
The page lost. The retry script lost with it. This sequence is a composite, built from a familiar docs failure, not a log held by this account.
Two jobs, one URL
An API page orients the caller, and it also makes promises. Orientation is the header name, the placement, a fake sample key, and a link onward. A promise is the replay outcome: status code, body identity, key scope, retention, and whether a second call can create a second charge.
Models draft orientation well. They do not own promises. Regen makes that split sharp. Guide sentences may change every release. Contract sentences stay until a named human edits them.
The composite regen touched 11 sentences. Six were orientation. Five asserted replay behavior. Those five are the defect.
What the model may draft
Put orientation in guide.md. The model may create and replace that file.
- Header placement, and a fake sample such as
pay_8f3c - A request skeleton: method, path, content type
- A pointer sentence: replay behavior lives in
freeze.md - Name restatements that add no outcome (
Idempotency-Key,409,replay_conflict) - A "where to look" list for logs and request ids
A changelog line that says the guide was regenerated is also a draft. A changelog line that says behavior changed is not.
What a human must own
Put promises in freeze.md. A human API owner edits that file. The regen job must not have write access to it, and the prompt must not ask the model to refresh it.
The owner signs six rows. Each row names a source, or it says unspecified.
- Key scope: account, merchant, or global.
- Retention, and what an expired key does on the next call.
- Match rule: which request parts are compared on replay.
- Success replay: status code, and whether the body is the stored body.
- Conflict replay: status, error code, and the charge or ledger effect.
- In-flight rule: what the second caller receives while the first call is open.
unspecified may be published. A guessed "always" may not.
HTTP defines 409 as Conflict in RFC 9110. It does not require that code for idempotent replay. Choosing 409 is an API decision, so cite the handler or the spec revision in the owner mark when you choose it. See RFC 9110, status 409.
Decision table
Apply this on every idempotency docs change. The right-hand count is the composite page only. It is not a multi-repo study. Retention and status examples are illustrative claims, not measured defaults.
| Sentence job | Example line | Writer | Survives regen? | Composite count |
|---|---|---|---|---|
| Name the header | Send Idempotency-Key on POST /payments. | Model | No | 2 |
| Show a sample | curl skeleton, fake key | Model | No | 2 |
| Point at the contract | See freeze.md for outcomes. | Model | No | 2 |
| Scope the key | Unique per account, not global. | Human | Yes | 1 |
| Retention | Keys are stored for 24 hours. | Human | Yes | 1 |
| Success replay | Same status and stored body. | Human | Yes | 1 |
| Conflict replay | 409 replay_conflict, no second charge. | Human | Yes | 1 |
| In-flight | 409 in_flight until the first call ends. | Human | Yes | 1 |
Five promise rows. None of them belong in a generated description.
Worked files
Copy these as a starting proposal. They are not a dump from a production repository.
guide.md:
## Calling the endpoint
Send `Idempotency-Key` on `POST /payments`. Use a new key for a new payment intent. Reuse the key only when retrying that same intent.
Replay status codes, body identity, retention, and charging rules are not described here. Read freeze.md. If freeze.md says unspecified, do not infer a retry policy.
curl -X POST https://api.example.test/payments \
-H "Idempotency-Key: pay_8f3c" \
-H "Content-Type: application/json" \
-d '{"amount": 1000, "currency": "USD"}'
freeze.md:
<!-- owner: payments-api; freeze: replay-rule; source: unspecified -->
- scope: unspecified
- retention: unspecified
- match: unspecified
- success replay: unspecified
- conflict: unspecified
- in-flight: unspecified
- charge: unspecified
A freeze file full of gaps is a valid publish. A fluent guide that closes those gaps is not.
Boundary checker
This script is a proposal. It is not a benchmark, and it has no corpus score. It fails closed on contract verbs in guide.md, and on a freeze.md with no owner mark.
#!/usr/bin/env python3
"""Proposal: flag contract verbs in generated idempotency guides."""
import re
import sys
from pathlib import Path
CONTRACT = re.compile(
r"\b(always|guaranteed|exactly[- ]once|byte-for-byte|"
r"identical response|safe to retry|never charges|"
r"same body|no second charge)\b",
re.I,
)
OWNER = re.compile(
r"<!--\s*owner:\s*[^;>]+;\s*freeze:\s*replay-rule;\s*source:\s*[^>]+-->"
)
def main(root: Path) -> int:
guide = (root / "guide.md").read_text(encoding="utf-8")
freeze = (root / "freeze.md").read_text(encoding="utf-8")
errors = []
for i, line in enumerate(guide.splitlines(), 1):
if CONTRACT.search(line):
errors.append(f"guide.md:{i}: contract verb in draftable file")
if not OWNER.search(freeze):
errors.append("freeze.md: missing owner/freeze/source mark")
if re.search(r"\bunspecified\b", freeze, re.I) and CONTRACT.search(guide):
errors.append("guide asserts an outcome while freeze says unspecified")
print("\n".join(errors) if errors else "ok")
return 1 if errors else 0
if __name__ == "__main__":
sys.exit(main(Path(sys.argv[1])))
python3 check_replay_boundary.py docs/idempotency
git diff -- docs/idempotency/freeze.md
The first command checks sentence class. Exit 0 with ok is expected for the honest sample. Exit 1 is expected if guide.md says "always returns the original response". The git diff is the human step: if freeze.md moved, the named owner reviews that hunk before anything else.
What the checker does not prove
A green run does not show that the service matches freeze.md. Pair the docs check with a handler test owned by the service repo. Keep that test out of the docs generator.
If production returns 200 on replay while freeze.md says 409, file a service bug. Do not rewrite the page to match production in an unattended regen.
Review order
- Read the
freeze.mddiff. No owner, no merge. - Reject a single unattended commit that edits
freeze.mdandguide.mdtogether. - Run the checker and store the exit code on the PR.
- Confirm sample keys are fake.
pay_8f3cis acceptable. A live key is not. - When a row stops saying
unspecified, update thesource:mark in that same commit.
A free drafter, used narrowly
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
MonkeyCode enters only as the drafter for guide.md. The operator describes it as an open-source project and supplied two availability notes: free model access, and a free server option. This article does not treat a token total, a model list, a machine size, or a server lifetime as verified. Those figures change.
Read the license and the current limits in the project on the day you start. Do not copy an allowance from an older post into a sprint plan.
Prompt boundary
- Provide the OpenAPI operation, the header name, and a write target of
guide.mdonly. - Require the pointer sentence to
freeze.md. - Forbid outcome verbs: always, guaranteed, identical response, safe to retry.
- Do not pass
freeze.mdas a file the model may edit.
Server boundary
On the free server, run the checker and bring the exit code back to review. The server runs a draft and a script. It does not approve a promise.
If the model emits a contract verb, delete the sentence. That draft failed. Do not soften the verb and ship it.
Limitations
- The regex misses paraphrase. "The second call gives you what the first call gave you" has none of the listed verbs and is still a promise. A human still reads
guide.md. - The script does not parse OpenAPI. A promise inside a
descriptionships if you generate straight into the spec. Keep those fields empty, or set them to the pointer sentence. -
unspecifiedcan become a stall. Put a review date on any row that stays unspecified past one release. - Do not reuse this verb list for auth scopes, cursor stability, or webhook signatures. Each of those needs its own freeze file.
- No cost, latency, or quality figure is claimed. This article did not run a model comparison.
Who should skip it
Skip the split when no API owner will sign freeze.md. An unsigned freeze file is a second guide. Skip it when the handler is still a spike and you cannot publish unspecified; withhold the page.
Skip it when the site generator can emit only one source file and cannot include a second. You need two sources even if the built URL is one page. Skip it for incident notes and status-page commitments. A verb list tuned for payment replay will look strict and still miss the promise that matters there.
The next docs PR
Regenerate the guide. Leave the freeze file untouched unless the owner is on the review. A checker result of ok means the page can ship as orientation. It cannot ship as a retry contract while promise rows remain unspecified.
If a free drafting setup is already on your bench, point it at guide.md only and keep the freeze file out of the write set. A green checker is a boundary result, not evidence that production replays match the page.
Top comments (0)