Specification maintenance inventory

August 30, 2026 · View on GitHub

cargo xtask specs maintenance --as-of YYYY-MM-DD writes an advisory inventory to target/ripr/reports/spec-maintenance.json and target/ripr/reports/spec-maintenance.md. Add --json to also print the same versioned JSON DTO to standard output. Add --receipts <dir> to read review receipts from a different directory; the default is .allow/spec-system/reviews when it exists, and no receipts directory at all is the zero-receipt baseline.

The report covers every discoverable spec file in the repository's canonical RIPR-SPEC-NNNN-slug.md shape, parsed by the same identifier rule the spec gates use. Other Markdown files below docs/specs/ are listed as omitted, with the reason they are not part of the discoverable denominator. A missing docs/specs/README.md index is likewise recorded as an omitted input with reason spec-index-missing; unlike present files it does not inflate the discoverable count, because no document was actually scanned. Reason codes come from document structure (headings) rather than token presence: a spec with no review-bearing heading is never_reviewed, and an accepted spec without a ## Test Mapping heading is accepted_without_current_or_planned_test_mapping. Required-spec read or UTF-8 errors are instrument failures and return a nonzero status.

The denominator keeps the honest arithmetic discoverable == included + closed + omitted(non-index-missing): findings closed by a review receipt move from the queue into closed_specs and are counted in denominator.closed, and closure_counts breaks closed observations down by disposition label. status_counts and reason_counts count only open findings, so a closed spec can neither inflate nor deflate the queue.

Review receipts (#3466)

A SpecReviewReceiptV1 is one committed TOML file per spec under .allow/spec-system/reviews/RIPR-SPEC-NNNN.toml recording that this exact spec content received a bounded advisory maintenance disposition. Receipts are content-bound: the SHA-256 digest of the reviewed spec bytes is authoritative for compatibility, so editing the spec reopens the finding with reason content_changed_since_review even if the document status is unchanged. An optional waived_until date composes with any disposition; waived_until == as_of is still closed, and a past date reopens the finding with reason review_waiver_expired. The semantic receipt_id is derived from the disposition-bearing fields alone, so re-observation can update a receipt without changing its identity.

Write receipts with cargo xtask specs close --spec RIPR-SPEC-NNNN --disposition <label> --as-of YYYY-MM-DD --reviewed-by <identity> [--waived-until YYYY-MM-DD] [--detail <text>]. The writer computes the digest from current spec bytes itself, links the previous receipt generation as its predecessor, and never edits the spec file. Closed maintenance dispositions are review labels, not lifecycle states; the report renders them verbatim with no interpretation.

Receipts are advisory. A rejected receipt (malformed TOML, unknown schema, wrong spec or path binding, duplicate, unparsable filename) is recorded in receipts.rejected with a named reason and closes nothing; specs maintenance still succeeds. Absence of a receipt never changes a finding's validity, and no required gate consumes receipts.

Each row includes the spec ID and path, a SHA-256 content digest, observed document status, objective reason codes, evidence references, a bounded next route, and limitations. JSON and Markdown are rendered from the same SpecMaintenanceReportV1 value, and stable sorting makes fixed repository bytes plus a fixed --as-of value reproducible.

Git history is optional. When it is unavailable, the report says so and keeps repository-only findings. Age is an observation or ordering hint only; it never changes spec validity, lifecycle, support posture, branch protection, or merge eligibility. The report does not create review receipts, alter workflows, or infer that implementation or evidence exists.

Advisory digest (#3467)

cargo xtask specs digest --as-of YYYY-MM-DD runs the same inventory pipeline (one history capture, one receipt scan, one build_report) and writes three files: the full spec-maintenance.json and spec-maintenance.md reports plus a short spec-maintenance-digest.md rendered from the same DTO. There is no second parser and no second scan. The digest keeps a bounded top-of-queue (stalest first, capped at 8), status and reason counts for open findings, receipt observations, limitations, a link to the full report, and one machine-readable maintenance_status line.

The digest distinguishes exactly three states:

maintenance candidates found  = attention_required, successful observation
no maintenance candidates     = clean, successful observation
instrument failure            = nonzero exit; no digest is written

Candidate counts never change the exit status: both clean and attention_required are successful observations. A structurally blind scan is never clean: when the docs/specs/README.md index is absent and no document was scanned (zero included specs), the digest reports attention_required because the actionable item is restoring the index; a genuine zero-candidate scan (index present, no findings) stays clean. The digest's omitted total excludes the synthetic spec-index-missing record — disclosed in a one-line note when it is present — so the rendered arithmetic matches discoverable == included + closed + omitted. An unreadable or non-UTF-8 required spec fails the pipeline before any digest is written, so a broken instrument is never rendered as a clean inventory, and the digest command removes the previous run's three report files before the scan so a failed rerun leaves no stale artifacts behind.

The Source of Truth workflow publishes the digest on a weekly schedule, on explicit workflow_dispatch, and on pull requests that touch spec governance paths (specs, templates, traceability, implementation slices, support tiers, or the workflow itself). Its digest job is advisory (continue-on-error): a failed instrument is annotated as a failed advisory observation, the full report is retained as an artifact, and no maintenance context is required by branch protection. The workflow does not create issues, comments, labels, or any other repository mutation.