DEV Community

Avery Lin
Avery Lin

Posted on

Extract Task Registries Into a Job Ledger; Sign Idempotency and Poison Policy by Hand

Job documentation decays when writers copy retry numbers by hand while the registry already knows those integers. The durable pattern is to compile a mechanical ledger from task registration, then refuse to publish unsigned human cells. Models may draft purpose prose from docstrings, but they must not invent idempotency, poison handling, or paging ownership. This article walks through an extractor, a draft lane, an owned overlay, and a docs-build gate you can reproduce.

The approach treats generated rows as compile output and treats operational promises as a signed overlay. Teams that already keep workers, queues, and retry constants in source can stop transcribing those facts into Markdown. What remains for people is the policy that code cannot prove: whether a retry is safe, where poison messages go, and who is paged.

What the extractor may emit

A job ledger should contain only values a parser can defend from the current tree. Names, module paths, queue labels, max-retry integers, and soft-time limits belong in that set. Decorator defaults that are literal constants also belong, because a reviewer can re-run the parser and obtain the same cells.

The extractor must not emit customer impact, data-loss risk, or on-call ownership from comments that happen to sit nearby. Comments drift, and they often describe an older retry loop that the decorator no longer uses. If a value is missing in source, the ledger should store null rather than a guessed integer that later looks official in the portal.

What a model may draft

Purpose paragraphs are the only cells that benefit from a language model in this workflow. Given a function name, a verified docstring, and the extracted queue label, a model can propose a short description of what the worker does. That proposal is a draft artifact, not a contract, and the docs renderer must label it as unverified until a human accepts or replaces it.

Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode's free model access and free server option can host that draft lane when a team wants the prompt off a laptop. The model still receives only extracted text, and it still must not fill idempotency, poison policy, or paging cells. If those product options are unavailable, the same overlay and build gate work with an empty draft column.

What a human must own

Four overlay fields stay outside generation because they encode promises that break independently of the registry. Idempotency is a semantic claim about side effects, not a decorator flag. Poison-message policy decides whether a failing payload is parked, dropped, or replayed after a schema change. Paging ownership names a rotation, not a module path. Runtime class describes how long a job may run before it is treated as stuck.

Unknown is a valid overlay value during an incident review, but unknown must not ship in public runbooks. The build gate below treats missing or unknown cells as a failed docs job, which keeps unsigned work in a branch instead of on the support site.

Artifact: a registry parser and an overlay schema

The following Python is a labeled example for a small @job decorator that stores queue and retry metadata on the function object. Adapt the walker to Celery, RQ, or a homegrown registry by changing only the attribute names. Do not treat the sample jobs as a production workload.

# example_jobs.py — labeled example, not a measured production registry
from functools import wraps

def job(queue="default", max_retries=3, soft_time_limit_s=None):
    def deco(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            return fn(*args, **kwargs)
        wrapper.job_queue = queue
        wrapper.job_max_retries = max_retries
        wrapper.job_soft_time_limit_s = soft_time_limit_s
        wrapper.job_name = fn.__name__
        wrapper.job_doc = fn.__doc__ or ""
        return wrapper
    return deco


@job(queue="billing", max_retries=5, soft_time_limit_s=120)
def capture_invoice(invoice_id: str) -> None:
    """Charge a captured authorization and write a ledger row."""
    raise NotImplementedError


@job(queue="mail", max_retries=8, soft_time_limit_s=30)
def send_receipt(invoice_id: str) -> None:
    """Enqueue a receipt email after a successful capture."""
    raise NotImplementedError
Enter fullscreen mode Exit fullscreen mode
# compile_job_ledger.py — labeled example extractor
import importlib.util
import inspect
import json
from pathlib import Path


def load_module(path: Path):
    spec = importlib.util.spec_from_file_location(path.stem, path)
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod


def extract_jobs(path: Path) -> list[dict]:
    mod = load_module(path)
    rows = []
    for name, obj in inspect.getmembers(mod, inspect.isfunction):
        if not hasattr(obj, "job_queue"):
            continue
        rows.append({
            "id": f"{path.stem}.{name}",
            "function": name,
            "module": path.stem,
            "queue": obj.job_queue,
            "max_retries": obj.job_max_retries,
            "soft_time_limit_s": obj.job_soft_time_limit_s,
            "docstring": inspect.cleandoc(obj.job_doc),
            "purpose_draft": None,
        })
    return sorted(rows, key=lambda r: r["id"])


def main() -> None:
    src = Path("example_jobs.py")
    ledger = {"jobs": extract_jobs(src)}
    Path("generated/job_ledger.json").parent.mkdir(parents=True, exist_ok=True)
    Path("generated/job_ledger.json").write_text(
        json.dumps(ledger, indent=2, sort_keys=True),
        encoding="utf-8",
    )


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

The overlay is a separate file that humans edit and that code review treats as policy. Generated identifiers are the only join keys. No retry integer is copied into this file, because the ledger already owns those cells.

# overlays/job_policy.yaml — human-owned; do not generate these cells
jobs:
  example_jobs.capture_invoice:
    idempotent: "no"
    poison_policy: "park payload; page billing-oncall after 3 parks"
    paging_owner: "billing-oncall"
    runtime_class: "synchronous-short"
    purpose_accepted: |
      Charges a captured authorization and writes a ledger row.
      Callers must not enqueue duplicates without a unique invoice_id.
  example_jobs.send_receipt:
    idempotent: "yes"
    poison_policy: "drop after max retries; log template id only"
    paging_owner: "messaging-oncall"
    runtime_class: "asynchronous-short"
    purpose_accepted: |
      Sends a receipt email after capture. Safe to retry because
      the mailer dedupes on invoice_id plus template version.
Enter fullscreen mode Exit fullscreen mode

Numbered workflow

  1. Register each worker with literal queue, retry, and time-limit constants the parser can read without executing the job body.
  2. Run compile_job_ledger.py in continuous integration so generated/job_ledger.json is a build artifact, not a hand-edited wiki page.
  3. Optionally send id, function, queue, and docstring to a drafting model, then store the reply under purpose_draft with an explicit draft label.
  4. Require a human to fill overlays/job_policy.yaml for every ledger id, including idempotent, poison_policy, paging_owner, and runtime_class.
  5. Join ledger rows to overlay rows in the docs job, and fail the build when any id is missing, extra, or still marked unknown.
  6. Render a runbook page that prints generated integers from the ledger and prints policy sentences only from the overlay.
# check_job_overlay.py — labeled example gate
import json
import sys
from pathlib import Path

import yaml

REQUIRED = ("idempotent", "poison_policy", "paging_owner", "runtime_class", "purpose_accepted")
BLOCKED = {"", "unknown", None}


def main() -> int:
    ledger = json.loads(Path("generated/job_ledger.json").read_text(encoding="utf-8"))
    overlay = yaml.safe_load(Path("overlays/job_policy.yaml").read_text(encoding="utf-8"))
    generated_ids = {row["id"] for row in ledger["jobs"]}
    owned_ids = set((overlay.get("jobs") or {}).keys())
    errors = []
    if generated_ids - owned_ids:
        errors.append(f"unsigned jobs: {sorted(generated_ids - owned_ids)}")
    if owned_ids - generated_ids:
        errors.append(f"overlay orphans: {sorted(owned_ids - generated_ids)}")
    for job_id in sorted(generated_ids & owned_ids):
        cell = overlay["jobs"][job_id]
        for key in REQUIRED:
            value = cell.get(key)
            if value in BLOCKED:
                errors.append(f"{job_id}.{key} is unsigned")
        if str(cell.get("idempotent")) not in {"yes", "no"}:
            errors.append(f"{job_id}.idempotent must be yes or no")
    if errors:
        print("\n".join(errors), file=sys.stderr)
        return 1
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
Enter fullscreen mode Exit fullscreen mode
# .github/workflows/job-docs.yml — labeled example, not a hosted workflow claim
name: job-docs
on: [push, pull_request]
jobs:
  compile-and-sign:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install pyyaml
      - run: python compile_job_ledger.py
      - run: python check_job_overlay.py
Enter fullscreen mode Exit fullscreen mode

A renderer can then print a table without mixing lanes. Queue, retries, and time limits come from JSON. Idempotency, poison handling, and paging come from YAML. Purpose text comes from purpose_accepted, never from purpose_draft, so a stale model paragraph cannot reach operators.

Decision table for each cell

Cell Source of truth Allowed generator Human required
Job id, function, module Registry parse Yes Review parser only
Queue name Decorator literal Yes No
max_retries, time limit Decorator literal Yes No
Purpose paragraph Docstring plus optional draft Draft only Must accept or rewrite
Idempotent yes/no Side-effect review No Yes
Poison-message policy Operations review No Yes
Paging owner Rotation roster No Yes
Runtime class SLO conversation No Yes

The table is the contract for code review. A pull request that changes retry integers should update only the registry and the generated ledger. A pull request that changes who is paged should update only the overlay. Mixing those diffs is how unsigned policy sneaks into a mechanical file and later looks like compiler output.

Failure analysis the gate is meant to catch

Three failure modes show up when job docs are written as a single Markdown page. First, a retry constant is raised in code and the runbook still tells operators to wait for three attempts. Second, a new worker ships without an owner, so the first poison message pages a platform rotation that cannot inspect the payload. Third, a model-written summary infers idempotency from the word "send" and operators replay a capture job.

The join gate fails closed on the first two modes because extra or missing ids are cheap to detect. The third mode is blocked by refusing to copy purpose_draft into the published page. If a team needs model assistance, it can store drafts beside the overlay, then require the same reviewer who signs idempotent to accept the paragraph.

Limitations

The extractor only sees literals attached to the decorator in this example. Retry math computed at runtime, queues selected from configuration, and workers registered through plugin discovery will not appear until the parser is extended. Dynamic registration is a reason to fail the docs build with an explicit unsupported-pattern error, not a reason to let a model invent the missing rows.

Idempotency in the overlay is still a human judgment and can be wrong. The gate proves that someone typed yes or no, not that the worker is safe under duplicate delivery. Poison policy text can also rot when a dead-letter queue is renamed. Pair this overlay with a periodic review of paging owners, because rotations change faster than task names.

This workflow does not measure model quality, does not claim a quota, and does not assume a particular GPU or uptime window. Free model access and a free server option are useful only as a drafting host for docstring-backed purpose sketches. They are not a substitute for the overlay or the join gate.

Who should not use this approach

Do not adopt the ledger if the team has no task registry and every worker is an ad hoc shell script with undocumented flags. Parsing will produce an empty catalog and a false sense that unsigned jobs cannot exist. Do not adopt it if legal or customer contracts require a single narrative document with no generated tables, because the join output will not match that format.

Skip the model draft lane when docstrings contain secrets, customer names, or payload samples. The overlay can still be signed from a private runbook. Skip the whole method when jobs are ephemeral research tasks that should not create paging promises in the first place.

Keep the generated ledger in version control next to the overlay so reviewers can see both lanes in one diff. If you already draft on MonkeyCode's free server, limit that host to purpose sketches from extracted docstrings, and leave idempotency, poison policy, and paging in the signed YAML file.

Top comments (0)