DEV Community

Avery Lin
Avery Lin

Posted on

Compile Quickstart Steps From a Fixture Manifest; Hand-Write Secrets, Failure, and Teardown

Quickstart tutorials fail in a predictable way when a model invents commands that never ran in continuous integration. The durable fix is to freeze every command block from a reviewed fixture manifest and let the model draft only surrounding prose. Humans must still sign secret handling, abort behavior, host assumptions, and teardown, because those claims are operational rather than syntactic. Teams that invert this split publish samples that look complete and then break on the first missing environment variable.

This article treats getting-started copy as a compile problem with a human-owned annex. It does not classify API reference paragraphs, error catalogs, or release notes. The workflow below is a proposal with labeled example files, not a report of production traffic or measured reader conversion.

What the split actually protects

Tutorial readers copy blocks in order and stop at the first unexpected prompt. A model that drafts those blocks from chat memory will smooth over flags, image tags, and working directories that only exist in one author's laptop session. Continuous integration already knows which commands exited zero against a pinned fixture pack, so that pack is the only honest source for the command column.

Prose around those commands is a different artifact. Sentence rhythm, heading order, and short warnings can be drafted after the command hashes lock. Secret injection, what happens when step three fails, which ports collide on a shared laptop, and how to delete volumes are not properties of a successful CI job. Those statements need a named reviewer because they describe harm, not syntax.

Decision table: draft versus own

Tutorial fragment Source of truth Model may draft? Human must sign?
Command text, flags, working directory Reviewed quickstart.manifest.yaml plus CI log No; compile verbatim Reviewer signs the manifest, not each paste
Expected stdout snippets from fixtures Captured, truncated logs with redaction rules No; compile with a max-line cap Owner signs redaction rules
Section titles and transition sentences Frozen commands plus outline Yes, after hash lock Spot-check for invented flags
Required env var names Manifest env_from list Compile names only Owner writes how values are minted
Secret values, tokens, connection strings Never in git or prompts No Owner forbids literals in the compiler
Failure meaning when a command exits non-zero Runbook, not CI green path No Owner writes abort and retry
Teardown, volume deletion, billed-cloud cleanup Ops policy No Owner signs destructive commands
"Safe for production" or uptime claims Support and legal No Do not generate

Keep the table in the repository beside the compiler. When a pull request adds a command, the table tells the reviewer whether prose generation is even allowed yet.

Artifact: a fixture manifest the compiler can refuse

Label the following files as a worked example. They are not captured from a public product and they do not assert benchmarks.

# docs/_fixtures/quickstart.manifest.yaml
# status: reviewed
schema_version: 1
id: qs-local-stack-2026-09
ci_job: docs-quickstart-smoke
runtime:
  os: linux
  compose_file: fixtures/quickstart/compose.yaml
  workdir: /workspace/demo
steps:
  - id: clone_sample
    argv: ["git", "clone", "--depth", "1", "https://example.invalid/demo.git", "."]
    timeout_s: 60
  - id: up_deps
    argv: ["docker", "compose", "up", "-d", "--wait", "db"]
    timeout_s: 120
  - id: apply_schema
    argv: ["./scripts/migrate", "up"]
    env_from: ["DATABASE_URL"]
    timeout_s: 30
  - id: run_probe
    argv: ["./scripts/probe", "--json"]
    timeout_s: 15
teardown:
  argv: ["docker", "compose", "down", "-v"]
  human_owned: true
forbid:
  - secret_literals
  - production_safety_claims
  - host_network_assumptions
redact_stdout:
  max_lines: 8
  deny_patterns: ["postgres://", "Bearer ", "AKIA"]
Enter fullscreen mode Exit fullscreen mode

The manifest pins argv arrays, not shell one-liners with unquoted interpolation. teardown.human_owned is a compiler hard stop: the model never writes the cleanup section, even if CI ran compose down. forbid is checked against both compiled blocks and drafted prose before merge.

Numbered workflow

1. Record commands from the smoke job, not from chat

Run the same argv arrays the tutorial will show. Capture exit codes and a redacted stdout slice. Do not paste a model's guessed docker run into the manifest because it "looks standard."

# proposal: record from the docs smoke job on 2026-09-15
set -euo pipefail
cd fixtures/quickstart
docker compose up -d --wait db
./scripts/migrate up
./scripts/probe --json | head -n 8
docker compose down -v
Enter fullscreen mode Exit fullscreen mode

Store the job name in ci_job so a later compiler can fail when the job is renamed or skipped on a branch. If the smoke job is allowed to fail, the tutorial has no freeze point and generation should refuse.

2. Hash every frozen command block

# compile_quickstart.py — example compiler, not a packaged product
from __future__ import annotations

import hashlib, json, re, sys
from pathlib import Path

import yaml

FORBID = (
    re.compile(r"postgres://\S+", re.I),
    re.compile(r"Bearer\s+\S+", re.I),
    re.compile(r"AKIA[0-9A-Z]{16}"),
    re.compile(r"\bsafe for production\b", re.I),
)


def argv_block(argv: list[str]) -> str:
    return " ".join(argv)


def digest(text: str) -> str:
    return hashlib.sha256(text.encode("utf-8")).hexdigest()[:16]


def main(manifest_path: str, prose_path: str | None) -> int:
    man = yaml.safe_load(Path(manifest_path).read_text())
    if man.get("status") != "reviewed":
        print("refuse: manifest is not reviewed", file=sys.stderr)
        return 2
    frozen = []
    for step in man["steps"]:
        block = argv_block(step["argv"])
        frozen.append({"id": step["id"], "block": block, "sha256_16": digest(block)})
    out = {
        "manifest_id": man["id"],
        "ci_job": man["ci_job"],
        "commands": frozen,
        "teardown_human_owned": bool(man.get("teardown", {}).get("human_owned")),
    }
    Path("docs/_generated/quickstart.commands.json").write_text(json.dumps(out, indent=2))
    if prose_path:
        prose = Path(prose_path).read_text()
        for pat in FORBID:
            if pat.search(prose):
                print(f"refuse: forbidden pattern {pat.pattern}", file=sys.stderr)
                return 3
        for item in frozen:
            if item["block"] not in prose:
                print(f"refuse: missing frozen block {item['id']}", file=sys.stderr)
                return 4
    print(json.dumps({"ok": True, "steps": len(frozen)}))
    return 0


if __name__ == "__main__":
    sys.exit(main(*sys.argv[1:4], *([None] if len(sys.argv) < 3 else [])))
Enter fullscreen mode Exit fullscreen mode

The sixteen-character digest is a review aid, not a cryptographic security boundary. Its job is to make silent flag edits visible in the pull request diff of quickstart.commands.json.

3. Draft prose only after the JSON exists

Feed the compiler output plus a short outline into a drafting step. The prompt may request headings, a one-sentence goal, and a note that readers should stop on non-zero exit. The prompt must not ask the model to invent extra commands, default passwords, or cloud regions.

Disclosure: This article was prepared as part of MonkeyCode's product outreach. A team that already reviews fixtures can run that drafting step with MonkeyCode's free model access on the free server option, then keep the signed annex in the same pull request. Those two availability claims are the only product facts used here; this article does not name models, quotas, hardware, or runtimes.

4. Attach a human-owned operational annex

Place the annex in a file the compiler never overwrites, for example docs/quickstart.ops.md. Required sections:

  1. Secrets. How DATABASE_URL is created for local use, where it must not be pasted, and that sample values in docs are non-functional placeholders.
  2. Failure. What a migrate timeout means, whether re-running ./scripts/migrate up is idempotent, and when to stop and file an issue instead of retrying.
  3. Host assumptions. Linux is what the smoke job ran; macOS file sharing, rootless Docker, and CI-in-Docker nesting are out of scope unless separately signed.
  4. Teardown. Exact destructive command, what volumes it deletes, and that billed resources are not covered by this tutorial.

If any of those four headings is missing, the publish gate fails even when every frozen argv is present.

5. Close with three publish gates

Gate A — Manifest reviewed and ci_job still exists on the default branch.
Gate B — Every argv in the tutorial hashes to docs/_generated/quickstart.commands.json.
Gate C — Operational annex signed; forbid patterns absent from drafted prose.
Enter fullscreen mode Exit fullscreen mode

A documentation preview that skips Gate C will still look finished. That is the failure mode this workflow is built to catch.

Reproducible test plan

Label this as an unexecuted example suite. Wire it to the same job named in the manifest.

# test_quickstart_frozen.py — example assertions
import json, pathlib, yaml

ROOT = pathlib.Path(__file__).parents[1]


def test_manifest_status_reviewed():
    man = yaml.safe_load((ROOT / "docs/_fixtures/quickstart.manifest.yaml").read_text())
    assert man["status"] == "reviewed"
    assert man["teardown"]["human_owned"] is True


def test_generated_hashes_match_argv():
    man = yaml.safe_load((ROOT / "docs/_fixtures/quickstart.manifest.yaml").read_text())
    gen = json.loads((ROOT / "docs/_generated/quickstart.commands.json").read_text())
    assert gen["ci_job"] == man["ci_job"]
    assert len(gen["commands"]) == len(man["steps"])


def test_tutorial_contains_frozen_blocks_only_in_order():
    gen = json.loads((ROOT / "docs/_generated/quickstart.commands.json").read_text())
    text = (ROOT / "docs/quickstart.md").read_text()
    positions = [text.index(item["block"]) for item in gen["commands"]]
    assert positions == sorted(positions)


def test_ops_annex_has_required_headings():
    ops = (ROOT / "docs/quickstart.ops.md").read_text().lower()
    for heading in ("secrets", "failure", "host assumptions", "teardown"):
        assert heading in ops
Enter fullscreen mode Exit fullscreen mode

Add a CI rule that docs-quickstart-smoke must run before compile_quickstart.py. If the smoke job is skipped with paths-ignore on markdown-only branches, the freeze is stale by construction.

Limitations

The compiler cannot see commands the smoke job never executed. Windows shells, podman, and rootless setups need their own manifests or an explicit unsupported label. Redaction patterns miss novel secret shapes, so stdout capture remains a leak risk if logs are attached raw. Drafted prose can still smuggle host-network claims that are not secret-shaped; Gate C needs a human reader, not only regex.

This approach is a poor fit for incident runbooks, contractual SLA pages, and any tutorial that must mint real credentials in the reader's cloud account. It is also the wrong tool when the product has no deterministic smoke job, because there is then nothing honest to freeze. Maintainers who want a single chat transcript to become a getting-started guide should not use this split.

The compile-and-sign sequence is slow on purpose. That cost is the point: quickstart pages are copied more often than they are reread, so an invented flag is cheaper to type than to support. If a docs change is only typography, skip generation and edit the prose file against the already frozen JSON.

A reasonable next check is whether your smoke job and tutorial still share one argv list; if they do not, the fixture manifest is the smaller patch. MonkeyCode is optional for the prose draft once that list is reviewed, and it is unnecessary when the page is already a signed annex with no surrounding copy to generate.

Top comments (0)