DEV Community

Avery Lin
Avery Lin

Posted on

Receipt-Bound Setup Guides: Narration Can Be Drafted, Promises Cannot

Generated setup guides stay reliable only when narration is tied to a frozen probe receipt and every promise remains human-owned. A model may explain commands that already ran, but it may not invent support windows, compatibility guarantees, or security contacts. This workflow freezes probe output first, classifies each section, and rejects a draft that crosses the ownership fence. The checker shown here is a proposed local script, not a recorded result from a production fleet.

Mixed authorship is the defect

A setup page usually mixes three kinds of text: observed commands, explanatory narration, and contractual promises. Readers treat those kinds as one voice, so a fluent draft can place an untested platform claim beside a real command. The damage is not stylistic; it is an unsupported statement that later becomes a ticket, a legal notice, or a wrong security contact. Separating draft rights before generation is cheaper than auditing a finished page after fluent prose has already landed.

Teams often ask a model to complete a getting-started page from a repository scan and a memory of similar projects. That request hides the ownership decision inside the prompt, where a reviewer cannot see which sentences were allowed. A fence file makes the decision explicit, diffable, and enforceable before prose is merged into the default branch. Without that fence, later edits argue about tone while the unsupported promise remains in the published page.

Build the probe receipt before any sentence exists

  1. Capture install and smoke-test commands in a shell script that writes stdout, stderr, and exit codes into one receipt file.
  2. Record only tool versions that the script actually printed, and ban later prose from naming a version absent from that receipt.
  3. Store the receipt in version control with a content hash so a later edit cannot silently replace the recorded evidence.
  4. Treat any command that was not executed as missing evidence, and leave that section for a human instead of a guess.

The receipt is a fact store, not a tutorial that later narration is free to extend with familiar commands. Each record needs a stable identifier, the exact argv, the exit code, and a short excerpt that narration may quote. If a field is empty, the corresponding section stays unpublished rather than being filled by a plausible sentence. A one-machine receipt can support a walkthrough, but it cannot support a matrix of systems that nobody probed.

#!/usr/bin/env bash
# Proposed probe capture. Run it on a machine you control; do not treat the log as a support matrix.
set -u
out="probe-receipt.jsonl"
: > "$out"
run() {
  local id="$1"
  shift
  local tmp code
  tmp="$(mktemp)"
  "$@" >"$tmp" 2>&1
  code=$?
  python3 - "$id" "$code" "$tmp" "$@" <<'PY'
import json, sys
probe_id, code, path, *argv = sys.argv[1:]
text = open(path, encoding="utf-8", errors="replace").read()
print(json.dumps({
    "id": probe_id,
    "argv": argv,
    "exit_code": int(code),
    "excerpt": text[-400:],
}))
PY
  rm -f "$tmp"
}
run node-version node --version >> "$out"
run npm-ci npm ci >> "$out"
run smoke npm test --silent >> "$out"
# Linux: sha256sum. macOS: shasum -a 256 | awk '{print $1}'
sha256sum "$out" | awk '{print $1}' > probe-receipt.sha256
Enter fullscreen mode Exit fullscreen mode

That script is a starting pattern, and it must be adapted to the package manager the repository actually uses. It does not claim that the test command passed anywhere except the machine where an operator chose to run it. The hash file is the pin, and macOS hosts should write it with shasum so the token format stays identical. Do not paste secrets, tokens, or customer hostnames into the excerpt field, even when the log contains them.

Assign draft rights in a fence file

Write the ownership fence before opening a chat window or handing the receipt to any draft runner. Each section names an owner, a closed list of probe identifiers, and a list of banned claim stems. The model may fill only sections whose owner is narration and whose probe list is already non-empty. Human sections ship as overlays that the generator must not rewrite, even when extra promises would sound smoother.

page: docs/setup.md
receipt: probe-receipt.jsonl
receipt_sha256_file: probe-receipt.sha256
sections:
  - id: install-narration
    owner: narration
    probes: [node-version, npm-ci]
    allow: [reorder, define-flag, quote-excerpt]
    ban_stems: ["supported until", "we guarantee", "production-ready", "all platforms"]
  - id: smoke-narration
    owner: narration
    probes: [smoke]
    allow: [quote-excerpt, describe-exit-code]
    ban_stems: ["always passes", "sla", "uptime"]
  - id: support-boundary
    owner: human
    probes: []
    required_marker: "<!-- owner: human -->"
    ban_in_model_output: true
  - id: disclosure-contact
    owner: human
    probes: []
    required_marker: "<!-- owner: human -->"
    ban_in_model_output: true
Enter fullscreen mode Exit fullscreen mode

Narration may restate a probed command in order, define a flag that appears in that argv, and quote a stored excerpt. Narration may not add a command, rename a package, or widen an exit code into a compatibility promise. Human owners must write platform lists, removal dates, security contacts, license lines, telemetry claims, and support statements. If nobody will sign the human section, the page should ship without that section instead of borrowing a model sentence.

Use a decision table when a sentence is ambiguous

Sentence kind Owner May a model emit it? Evidence required before publish
Restated command narration yes probe id and matching argv
Flag definition narration yes flag text present in argv
Stderr explanation narration yes quoted text present in excerpt
Version mention narration only if printed version string present in excerpt
Supported OS list human no signed overlay
Security contact human no signed overlay
License or trademark human no signed overlay
SLA, uptime, or quota human no signed overlay, or omit the row
Command nobody ran none no leave the section unpublished

The table is the review contract, and a sentence that does not fit a row should be deleted rather than forced into narration. Version mentions are the most common leak, because models remember popular releases that the probe never printed. Untested commands have no owner on purpose, since neither the model nor a hurried editor has evidence to sign. If a row's evidence is missing, emit an HTML comment that names the gap instead of a fluent substitute sentence.

Reject a draft that crosses the fence

The checker below is proposed Python for a local review job, and it has not been timed or scored on a corpus. Treat a green run as a fence check rather than proof that the narrated facts are true outside the receipt. It fails when narration lacks a probe citation, when a banned stem appears, or when the receipt hash has drifted. It also fails when a human section leaks into the model output, which is the crossing this workflow exists to stop.

#!/usr/bin/env python3
"""Proposed fence check. Unexecuted example: adapt paths before relying on it."""
import hashlib
import json
import pathlib
import sys
import yaml  # PyYAML

root = pathlib.Path(sys.argv[1])
fence = yaml.safe_load((root / "ownership-fence.yaml").read_text())
receipt_path = root / fence["receipt"]
digest = hashlib.sha256(receipt_path.read_bytes()).hexdigest()
pinned = (root / fence["receipt_sha256_file"]).read_text().split()[0]
if digest != pinned:
    sys.exit(f"receipt hash drifted: {digest} != {pinned}")

probes = {}
for line in receipt_path.read_text().splitlines():
    if not line.strip():
        continue
    row = json.loads(line)
    probes[row["id"]] = row

model_text = (root / "draft.narration.md").read_text(encoding="utf-8")
page_text = (root / fence["page"]).read_text(encoding="utf-8")
errors = []
for section in fence["sections"]:
    if section["owner"] == "human":
        marker = section["required_marker"]
        if marker not in page_text:
            errors.append(f"{section['id']}: missing human marker")
        if marker in model_text:
            errors.append(f"{section['id']}: human marker leaked into model output")
        continue
    for probe_id in section["probes"]:
        if probe_id not in probes:
            errors.append(f"{section['id']}: unknown probe {probe_id}")
        cite = f"probe:{probe_id}"
        if cite not in model_text:
            errors.append(f"{section['id']}: narration missing {cite}")
    for stem in section.get("ban_stems", []):
        if stem.lower() in model_text.lower():
            errors.append(f"{section['id']}: banned stem {stem!r}")

if errors:
    print("\n".join(errors))
    sys.exit(1)
print("fence ok")
Enter fullscreen mode Exit fullscreen mode

Run the check from the repository root after the model writes only the narration file named in the job. The human overlay should already sit in the setup page with the required marker before any splice step runs. The splice itself should be a deterministic include, not a second model pass that might blend the two owners. If the hash check fails, regenerate narration from the new receipt instead of editing sentences to match a changed log.

python3 -m pip install --user pyyaml
python3 check_ownership_fence.py .
# clean tree prints: fence ok
# leaked promise prints a banned-stem line and exits non-zero
Enter fullscreen mode Exit fullscreen mode

Review sequence after the draft returns

The numbered review below is the publication gate, and it applies even when the draft runner returns clean Markdown. A clean-looking draft means the model obeyed the prompt shape, not that every claim survived the ownership fence. Attach the checker output to the same change as the narration file so a later editor can see what was rejected. Skip none of the four checks when the page is short, because short pages are where a single promise does the most harm.

  1. Confirm the receipt hash still matches the pin before anyone opens the narration file for line editing.
  2. Run the fence checker and keep the non-zero output attached to the review thread until every error is cleared.
  3. Read the human overlay separately, and reject the page if a promise appears only in the narration file.
  4. Splice with a deterministic include, then publish only when a named owner has signed the human sections.

The human page should already contain both markers before a model is asked for narration. The ownership marker stays in the overlay, and the include marker is the only line the splice script replaces. Keeping those markers literal makes the checker and the splice agree on what the model must not touch. A page that lacks either marker is unfinished, even if the surrounding sentences already sound complete.

<!-- owner: human -->
Security contact: write this line from the on-call roster. Do not generate it.

<!-- include: narration -->
Enter fullscreen mode Exit fullscreen mode

The splice script below is another proposed local tool, and it only substitutes a single include marker with the checked narration file. It refuses to run when the narration file contains a human ownership marker, which keeps the overlay from being overwritten. Run it only after the fence check prints its status line, and write the result to a new path. Review that new path as a diff against the previous published page before anyone merges the setup guide.

#!/usr/bin/env python3
"""Proposed splice. Unexecuted example: replace one include marker, never the overlay."""
import pathlib
import sys

page, narration, dest = sys.argv[1:]
include = "<!-- include: narration -->"
human = "<!-- owner: human -->"
body = pathlib.Path(page).read_text(encoding="utf-8")
insert = pathlib.Path(narration).read_text(encoding="utf-8").strip()
if include not in body:
    sys.exit("missing include marker")
if human in insert:
    sys.exit("narration contains a human marker")
pathlib.Path(dest).write_text(body.replace(include, insert, 1), encoding="utf-8")
print("spliced")
Enter fullscreen mode Exit fullscreen mode
python3 splice_narration.py docs/setup.md draft.narration.md docs/setup.preview.md
git diff -- docs/setup.preview.md
Enter fullscreen mode Exit fullscreen mode

Where a draft runner is allowed to help

Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode's free model access and free server option can host the narration pass if the prompt holds only the fence and the receipt. The operator supplied those two availability claims, and this article states neither model names, quotas, hardware sizes, nor benchmark scores. Use the free server as a scratch runner, then pull the draft file back and run the fence check beside the pinned hash.

Keep the prompt narrow so the runner cannot see human-owned sections as text it is invited to improve. Send the narration section ids, the allowed probe excerpts, and the banned stems, and require a probe citation beside each explanation. Do not ask the runner to make the guide complete, because completeness is the pressure that invents promises. After the file returns, the checker is the gate, and a named human still approves the overlay before publication.

If that free option is unavailable, the same files still work with any local model that can read the receipt. Portability is the point of keeping evidence in the repository rather than inside a vendor session. Do not store the only copy of the receipt on a remote scratch server, because a disappeared session would erase the pin. A missing runner is an inconvenience, while a missing receipt is a reason to stop publication.

Limits, and who should skip this workflow

A single probe receipt describes one environment at one moment, so it cannot justify a support matrix or a region claim. The banned-stem list is brittle, and a model can paraphrase a promise into wording the list does not catch. A human still reads narration for that reason, and the checker never replaces that careful read. Those availability options are conveniences rather than a capacity contract, so do not plan them as permanent fleet capacity.

Teams writing security advisories, privacy notices, license terms, or incident notes should not use this draft path yet. Those pages need counsel or the on-call owner to set the exact allowed sentences before any model sees them. The workflow also fails when no person will sign the human sections before the page is published to readers. In that case the honest output is a shorter page that only restates probed commands, with unsupported headings omitted.

Do not fill the gap with a model sentence and a plan to review it later, because later review lets mixed authorship survive. Start with one setup page, one receipt, and the fence file, and treat the first rejected draft as the useful signal. Widen the pattern only after that rejection has been recorded in review notes that name the crossed section. If install logs already exist, mark one setup page with this fence and run the checker before the next generated revision.

Top comments (0)