Skip to content

Rotkeeper Schema Reference

Field-by-field schemas for Rotkeeper's YAML and manifest files: bones/asset-manifest.yaml, bones/config/rotkeeper.yaml, the release-manifest.txt bill of materials β€” plus the CLI --json stdout envelopes for scan and dip.

Browse Docs pages

This page documents the exact fields of Rotkeeper's machine-readable files. It is derived from the scripts that read and write them (rc-assets.sh, rc-release.sh, rc-status.sh, rc-env.sh, rc-new.sh, rc-glue.sh); when a script and this page disagree, the script is authoritative.

bones/asset-manifest.yaml

Generated by bash rotkeeper.sh assets (rc-assets.sh). The previous manifest is archived to bones/archive/asset-manifest-<YYYY-MM-DD_HHMM>.yaml before regeneration. Empty asset trees produce a single # assets: [] comment.

- path: "css/theme-dark.css"     # asset path relative to the layout's assets dir (home/assets in crypt)
  sha256: "1dbb3b1b...c0b504d661" # hex digest via sha256sum or shasum -a 256 (shared rk_sha256)
Field Type Rules
path string Relative to the assets dir; only [a-zA-Z0-9/._-] allowed, no .. or ../ segments, .DS_Store never listed
sha256 string Lowercase hex SHA-256 of the source asset file

Lifecycle: written by assets, archived before each rewrite, and consumed by render as a precondition (render refuses to start without it or a bones/meta/asset-manifest.yaml) and by assets itself to prune stale generated copies under output/assets (pruning happens only when the output tree is marked generated).

bones/config/rotkeeper.yaml

The only always-present configuration file; written by init, optional paths may be serialized there. Every other key has a fallback in the reader, so a minimal file (title + description + default_template) is valid.

title: "Rotkeeper Config"
description: "Minimal valid config for rendering tests"
default_template: "theme-spooky-dark.html"
layout_style: "crypt"        # optional: crypt | busy | sterile
paths:                       # optional serialized path cache (init writes it)
  CONTENT_DIR: "home/content"
  TEMPLATE_DIR: "bones/templates"
Field Type Read by Default / fallback
title string status β€”
description string status β€”
project string status [not set]
author string status, new, glue [not set] / empty
version string status [not set] (the authoritative version is bones/config/version)
license string status [not set]
default_template string render, glue, new, bootstrap theme-spooky-dark.html (legacy key; theme_registry.default wins over it)
theme_registry map render, glue, new, status mode names β†’ template files; default selects the site-wide theme (#252)
input_format string env, render, preflight, adapter markdown; valid values markdown | textile (anything else warns and falls back to markdown)
layout_style string env crypt; valid profiles crypt | busy | sterile
paths map env derived per layout style when absent

theme_registry semantics (#252): maps mode names to template files (e.g. daisy: "theme-daisy.html", daisy-vanilla: "theme-daisy-vanilla.html"). The default entry is the per-site theme and wins over the legacy default_template key; per-page template: frontmatter wins over both. rk_resolve_default_template (rc-utils.sh) validates every registered value exists under bones/templates on resolution and falls back to the first available template when the default is missing.

paths cache semantics: a serialized paths block is validated against the active layout and repository root on load (validate_layout_alignment in rc-utils.sh). init deliberately forces an environment reload after writing it; do not hand-edit a paths block without re-running init or the alignment check will report the mismatch.

bones/config/release-manifest.txt (in the release archive)

Generated inside the distribution by bash rotkeeper.sh release <version> (rc-release.sh). This is the bill of materials for a release; the legacy rotkeeper-bom.yaml no longer exists and never ships.

Rotkeeper framework distribution manifest
version: 0.5.2
model: framework distribution (dispatcher, bones system, templates, configuration, project docs)
ruleset: release allowlist v1 (explicit root entries, required spine, forbidden prefixes and artifacts)
listed_entries: <count>
entries:
rotkeeper/AGENTS.md
rotkeeper/bones/config/release-manifest.txt
...
Field Meaning
version Release version from bones/config/version
model Distribution model (framework distribution is the only supported model)
ruleset Allowlist contract enforced against the archive
listed_entries Number of files listed under entries:
entries: One sorted, root-relative path per file inside the archive β€” the allowlist the packager verifies against

Verification failures (unexpected root files, missing required spine, forbidden credentials/artifacts) abort the release before the archive is finalized.

bones/archive/ β€” tomb archives

pack writes versioned .tar.gz tombs (tomb-<YYYY-MM-DD_HHMMSS>-NNNN.tar.gz), tombkit bundles (tombkit-*), content archives (tomb-content-*), JSON exports (tomb-export-*), and releases (releases/rotkeeper-<version>.zip).

Tomb versioning policy: archives are append-only and immutable. The random -NNNN tag (GNU and BSD safe β€” %N is GNU-only) guarantees unique names even for packs within the same second; old tombs are never invalidated, overwritten, or pruned. Each archive embeds a metadata.json (name, sha256 of the uncompressed tar, timestamp, mode, file count) and is recorded in bones/manifest.txt as <relative-path> <sha256>. gzip -t integrity is verified at pack time (validate_gz) and re-asserted by the test harness; any failed or interrupted pack is cleaned up by the partial-archive trap (no half-written .tar or truncated .gz survives).

CLI --json output

Several dispatcher commands emit a single machine-readable JSON object on stdout when passed --json; human-facing output, report files, and exit codes are unchanged when the flag is absent. New outputs are tagged with a stable schema field (rotkeeper.<subject>.v1) so CI consumers can version-check before parsing. As everywhere else on this page, when a script and this page disagree, the script is authoritative.

scan --json

Emitted by bash rotkeeper.sh scan --json. Scan audits the render ledger (bones/manifest.txt): missing are ledger entries absent from disk, orphans are files under the rendered output tree the ledger does not list, and digests verify ledger-listed files. The report files under bones/reports/ keep their existing shape; this object carries the same findings inside an additive envelope.

{
  "schema": "rotkeeper.scan.v2",
  "generated_at": "2026-08-27T00:00:00Z",
  "manifest": "bones/manifest.txt",
  "counts": { "missing": 0, "orphans": 0, "digest_mismatches": 0 },
  "missing": [],
  "orphans": [],
  "digest_count": 2,
  "digests": {
    "home/content/index.md": "1dbb3b1b…c0b504d661"
  },
  "digest_mismatches": [
    { "path": "bones/archive/tomb-2026-08-27_120000-1234.tar.gz", "expected": "1dbb3b1b…c0b504d661", "actual": "deadbeef…00000000" }
  ]
}
Field Type Rules
schema string Always rotkeeper.scan.v2
generated_at string ISO-8601 UTC timestamp emitted at audit time
manifest string Manifest path audited, root-relative
counts.missing, counts.orphans, counts.digest_mismatches number Lengths of the arrays below, mirrored for convenience
missing string[] Ledger entries absent from disk; empty array when clean
orphans string[] Files under OUTPUT_DIR absent from the ledger; output/assets/ is exempt (owned by the assets ritual)
digest_count number Number of entries in digests
digests map Ledger-listed file path β†’ lowercase hex SHA-256 (files present on disk only)
digest_mismatches object[] Ledger entries with a recorded SHA-256 (pack's <path> <sha256> two-space format) whose on-disk digest differs; each entry {path, expected, actual} where actual is hex or null when the file is absent (also in missing[])

v1 β†’ v2: v1's full-tree walk never produced orphans/digests β€” the extension filter could not match under the script's IFS (#292). v2 scopes orphans to the output tree, retargets digests at ledger entries, and adds digest_mismatches[] verifying pack's recorded SHA-256 lines (P2).

dip --json

Emitted by bash rotkeeper.sh dip --json. Rows appear in matrix order (sorted owned docs, then sorted unowned docs) with values equal to the published dip-matrix.md table cells minus markdown link decoration. Works under --dry-run without writing anything.

{
  "schema": "rotkeeper.dip-matrix.v1",
  "generated_at": "2026-08-27T00:00:00Z",
  "matrix_file": "home/content/docs/dip-matrix.md",
  "totals": { "ok": 10, "stub": 0, "missing": 0, "stale": 0, "unowned": 0, "rows": 10 },
  "rows": [
    { "target_file": "bones/scripts/rc-dip.sh", "doc": "bones/scripts/rc-dip.md", "last_code_edit": "2026-08-27", "last_doc_edit": "2026-08-27", "status": "OK" }
  ],
  "ownership_collisions": [],
  "obsolete_moved": [],
  "degraded": { "autopsy_report": false, "fsbook_catalog": false }
}
Field Type Rules
schema string Always rotkeeper.dip-matrix.v1
generated_at string ISO-8601 UTC timestamp
matrix_file string Published matrix path relative to root
totals map Counts by status (ok, stub, missing, stale, unowned) plus total rows
rows[] object target_file, doc, last_code_edit, last_doc_edit, status (all strings); targets without an owning script use "Unknown"
ownership_collisions[] object {doc, claims} where claims lists the two generated sources claiming the same doc page
obsolete_moved[] string[] Docs moved to the obsolete tree this run (always empty under --dry-run)
degraded.autopsy_report, degraded.fsbook_catalog boolean True when that input artifact was missing and discovery degraded

Other producers

status, links, and a11y each support --json today with command-specific shapes (see their reference pages). They predate the envelope convention above and do not carry a schema tag; treat their exact fields as defined by the scripts themselves.

Related