Rituals
Rituals
Section titled “Rituals”What a ritual is, the fixed vocabulary, where a project’s schedule is declared, and how the declaration is checked (#2222, #2230). (Back to reference index.)
A ritual is a recurring, attended activity held on a declared cadence — a comprehensive review every quarter, a dependency audit every month, a security review before each stable release. Declaring the schedule makes it version-controlled instead of remembered; the farm can gather a ritual’s evidence ahead of the session, and the session applies the ritual’s kit.
Per the Prime Directive each fact has one owner: the vocabulary and its defaults live in one file, a project’s schedule lives in its manifest, and run history is never declared — it is derived from ritual-run issues (#2231).
Where rituals live
Section titled “Where rituals live”| What | Path |
|---|---|
| Vocabulary (names, drill kinds, cadence grammar) | schemas/rituals.schema.json → definitions |
| Each ritual’s kit, farm-eligibility and default cadences | registry/rituals.json (BOB_SOURCE), synced to ~/.claude/registry/rituals.json by deploy.sh |
| A project’s schedule | provisions/<project>.json → _meta.rituals |
| BoB’s own drills (restore, lost-laptop, farm-compromise) and their runbooks | registry/rituals.json → the drill entry’s drills; registry/runbooks/drill-*.md (Drills, #2240) |
| A ritual’s checklist — the security review’s attack-surface list, the docs review’s list | provisions/<project>.json → the declaration’s checklist; registry/runbooks/bob-attack-surface-review.md, registry/runbooks/docs-review-checklist.md (Checklists, #2241, #2625) |
| Gate | scripts/checks/check-rituals.js, run by make check-rituals (part of make check) |
Why the manifest and not a sibling file. The provision manifest is already
the single per-project declaration (stack, phase, group, active), there is one
per managed project, and every fleet reader already walks provisions/*.json.
A sibling provisions/<project>.rituals.json would be mis-validated as a
manifest by check-schemas and split one project’s declaration across two
files. Recorded as decision #2368.
The vocabulary
Section titled “The vocabulary”The names are fixed — adding a ritual is a schema change.
| Ritual | Kit | Evidence farm-eligible | Evidence collectors | Default (active) | Default (dormant) |
|---|---|---|---|---|---|
comprehensive-review |
review |
yes | the eight general ones (not the docs-review five) | P3M |
— |
vision-planning |
vision-planning |
yes | status-report, aged-issues, usage-gaps, cost-churn |
P6M |
— |
retrospective |
incident |
yes | status-report, cost-churn, aged-issues, memory-audit |
P3M |
— |
dependency-audit |
security |
yes | dependency-audit |
P1M |
P6M |
security-review |
security |
yes | dependency-audit, fleet-readiness |
P6M |
— |
docs-review |
review |
yes | doc-staleness, doc-coverage, doc-changes, docs-tutorial, docs-site-live, doc-audit |
P1M |
P3M |
portfolio-review |
vision-planning |
yes | portfolio |
P3M on bigbrain, for every project — see The portfolio review |
— |
drill |
incident |
no | — | — (a project declares its own; BoB’s three drills are held on bigbrain at P3M — see Drills) |
— |
guide-walkthrough |
review |
no | — | — (owed by a guide’s requirement, never by a clock — see Guide walkthroughs) | — |
“Farm-eligible” means the ritual’s evidence step may be gathered unattended by
bob tick (#2233); the session itself is always attended. The collectors come
from a fixed set — see Evidence packets. A drill carries a
drill_kind — restore, failover, credential-rotation or
incident-response — and no other ritual may. A project’s own drill is not
scheduled by default; the project declares it. BoB’s own recovery is rehearsed
by three drills declared at the BoB level on the drill entry’s drills
list and held on bigbrain quarterly (Drills, #2240).
portfolio-review is never declared by a project: it is held once for the
whole portfolio (#2235). The dependency-audit entry also carries an
advisories block — the severity at or above which its audit files advisories
as security issues on the owning repo (file_at: high; see
Advisories, #2242).
registry/rituals.json also declares the liveness window that separates active
from dormant: liveness.active_within (P3M).
The portfolio review
Section titled “The portfolio review”Per-project reviews don’t answer “which projects deserve attention this
quarter”. portfolio-review does, once, across every managed project (#2235).
It is declared at the BoB level — a portfolio block on its vocabulary entry —
never in a project manifest:
{ "name": "portfolio-review", "kit": "vision-planning", "farm_eligible": true, "evidence": ["portfolio"], "portfolio": { "home": "bigbrain", "cadence": "P3M", "why": "…" }, "defaults": { "active": null, "dormant": null }}| Field | Rule |
|---|---|
portfolio.home |
required; the project (_meta.project) whose repo holds the runs — bigbrain |
portfolio.cadence |
required; a cadence |
portfolio.why |
optional one line |
defaults |
both null — the ritual is not scheduled per project by liveness |
What the block changes:
- Scheduling. The ritual is on the home project’s schedule at
portfolio.cadence, whatever the home declares in_meta.ritualsor its liveness, and on no other project’s.ritual.js duemarks itscope: "portfolio"; every other ritual isscope: "project". - Filing.
ritual.js open,bob ritual runandprepare --duefile the run on the home project, in its repo, with no--project.--projectnaming any other project is a config error. - Declaring. A project that lists it in
_meta.ritualsfailscheck-rituals(portfolio-declared). - Evidence. Its one collector,
portfolio, reads every managed project at once.
The portfolio collector
Section titled “The portfolio collector”One row per project manifest in provisions/ (the _-prefixed templates and
global manifests excepted). An archived project (_meta.active: false) is
listed, never measured. For every other project:
| Field | Read from |
|---|---|
liveness |
ritual.js’s liveness reader — the fleet snapshots, else the checkout here (Defaults by liveness) |
readiness |
the newest fleet snapshot row’s grades, else readiness.js on the checkout here (source says which) |
rituals |
one ritual.js due --all report: overdue, due, unknown counts and overdue_keys |
issues |
two gh issue list reads per repo — open p1-critical and p2-high |
cost_churn |
one series.js --json read: usd_window (spend over the series window), usd_per_merged_req, fix_to_feat, tokens_window |
harness_defects |
the newest snapshot row, else the series’ harness_defect_recurrences |
The repo is the snapshot row’s repo, else the checkout’s github.com origin.
A field that cannot be measured for one project is null and named in that
row’s unmeasured[]. That is not a gap, since most projects are not checked
out on any one machine. A source that fails for the whole portfolio (due --all, series.js) is a named gap, and every other field is still gathered.
No project manifest at all is a gap. The newest snapshot row counts across
machines; a row whose checkout is absent on its machine (exists: false) is
skipped.
The packet gets a portfolio section before the raw data. It holds one table row per project, active first, then unknown, dormant and archived:
| Project | Liveness | Readiness (dev / test / deploy) | Rituals overdue | Open p1 / p2 | Cost & churn | Harness defects |
|---|---|---|---|---|---|---|
| widget | active (2026-09-01) | working / working / partial | 1: dependency-audit | 2 / 1 | $12.50 spent · $2.25/merged req · fix:feat 0.4 | 1 open, 1 recurrence(s) |
Portfolio status
Section titled “Portfolio status”Each project’s status is declared in its manifest as _meta.portfolio:
| Status | Means | Cadence it declares |
|---|---|---|
invest |
give it attention this period | its own _meta.rituals, or the liveness defaults |
maintain |
keep it healthy, nothing more | its own _meta.rituals, or the liveness defaults |
park |
stop scheduling work; keep it patched | exactly the dormant defaults (a dependency-audit every P6M, a docs-review every P3M) |
archive |
no longer managed | the dormant defaults, with _meta.active: false |
An absent _meta.portfolio reads as maintain, or as archive when
_meta.active is false. check-rituals holds the status and the cadence
together, so a status change and its cadence land in the same PR
(portfolio-status):
- a
parkorarchiveproject must declare the dormant defaults. A schedule written before a ritual joined the vocabulary may lack that ritual’s entry and still passes, so a new ritual never fails every parked manifest at once;bob ritual decidewrites the full defaults the next time the project is decided (#2625); - an
archiveproject must beactive: false; - an inactive project cannot declare any other status.
Portfolio decisions
Section titled “Portfolio decisions”A portfolio run’s body ends with a Portfolio decisions section, after the delta. It has one row per project manifest, including archived ones, and each row records the project’s status when the run was opened:
## Portfolio decisions
| Project | Status now | Decision | Journal ||---|---|---|---|| bigbrain | maintain | invest | — || oldsite | maintain | park | #2400 |In the session, set each Decision to invest, maintain, park or
archive. A decision that differs from Status now changes the project’s
status, and it needs two more things before the run closes:
- a
/journaldecision issue (labelleddecision), linked in that row’s Journal cell; - the manifest change, written with
bob ritual decideand merged with the run.
bob ritual close refuses a portfolio run (portfolio-decision) while any of
these hold:
- a managed project has no row, or a row names no project;
- a row has no decision;
- a changed row links no decision issue;
- the manifest does not yet declare the new status, or declares it without the
cadence
check-ritualsrequires.
Decision issues linked in the table are journal entries, so they are checked
like --journal links and listed under the run’s Journal section. The
closed marker’s closed_with gains decisions: {total, changed}.
Deciding a status — bob ritual decide
Section titled “Deciding a status — bob ritual decide”bob ritual decide oldsite park # report what would change; writes nothingbob ritual decide oldsite park --apply # write provisions/oldsite.jsonbob ritual decide oldsite archive --apply # …regenerate projects.json and archive the GitHub repo| Decision | Writes |
|---|---|
park |
_meta.portfolio: "park"; _meta.rituals set to the dormant defaults, replacing any other declared schedule (named in rituals_replaced); drops active: false |
archive |
as park, plus _meta.active: false; the project’s GitHub repo is archived |
invest, maintain |
_meta.portfolio; a _meta.rituals that is exactly the dormant defaults is removed, so the liveness defaults apply again; drops active: false |
An archive is carried out, not only recorded (#3609). With --apply, archive
archives the project’s GitHub repo. The repo is never deleted, and
gh repo unarchive reverses it. Deciding an archived project anything else
un-archives the repo. The repo is --repo, else the manifest’s _meta.remote,
else the github.com origin of the project’s checkout on this machine. Running
archive --apply again on a project whose manifest already says so finishes
an archive whose repo is still live, and changes nothing once it is archived.
A checkout with no github.com origin has nothing to archive. A project with no
checkout here and no remote named is exit 5, never a silent skip.
An archived project keeps active: false in projects.json, so rituals, the
tick, mirrors and the CI rollout skip it. A checkout of it that is still on a
machine is carried in that machine’s fleet snapshot as summary.archived
({checkouts, projects: [{name, path, dirty}]}), and audit.js view lists it
under Archived projects with the machines that hold it.
Only the manifest’s _meta is rewritten. It is written in the style that
reproduces the original _meta exactly: JSON.stringify indentation, one
object per line inside arrays, or scalar arrays inline. Every other byte of
the file is kept, and the result is checked to parse back to the intended
manifest before anything is written. When active changes, projects.json is
regenerated (projects-derive.js --write), since it carries active.
The manifest is the one the operator resolver finds, in the operator repo since
#2270 (#3458). --apply refuses when the repo holding it is not a git checkout,
because the change must be committed there.
| Flag | Meaning |
|---|---|
<project> |
required; by _meta.project or manifest file name |
<decision> |
required; invest, maintain, park or archive |
--apply |
write, and archive or un-archive the GitHub repo; without it, report what would change |
--repo owner/name |
the GitHub repo, for a project with no checkout on this machine |
--keep-repo |
record the decision and leave GitHub alone (a repo someone else owns) |
--json |
emit bob-ritual-decide/1 |
--root DIR |
read and write another checkout’s provisions/ |
Exit codes: 0 applied, unchanged, or a report · 2 usage or config error
(unknown project or decision, the manifest’s repo is not a git checkout) · 4 the manifest was
written but projects.json could not be regenerated (its output is carried) ·
5 the manifest was written but the GitHub repo was not archived (its error is
carried).
bob-ritual-decide/1: verdict (applied | dry-run | unchanged),
project, file, from and to (statuses), meta_before, meta_after,
rituals_replaced (the schedule a park or archive replaced, or null),
rituals_removed, active_changed, style (expanded | rows |
compact), projects_json (regenerated, exit, output; null unless
active changed on --apply), repo_archive (repo, action archive or
unarchive, verdict, error; null when the decision touches no repo) and
error. The repo_archive verdicts are archived, unarchived, already,
no-repo (the checkout has no github.com origin), unknown (no checkout here
and no repo named), failed and dry-run.
The portfolio sweep — bob ritual sweep
Section titled “The portfolio sweep — bob ritual sweep”The portfolio review decides the projects that have a manifest. The directories on disk and the manifests drift apart: a checkout with no manifest, a manifest whose path is on no machine, a backup beside its original. The sweep (#3609) lists every project directory on the fleet and records one decision for each.
bob ritual sweep inventory # scan the fleet and print the list; writes nothingbob ritual sweep inventory --write # save it as portfolio.json in the operator repobob ritual sweep decide Sites/old-thing scrap --with Sites/old-thing-bakbob ritual sweep status # the rows still undecidedbob ritual sweep apply # what carrying the decisions out would dobob ritual sweep apply --apply # write the manifests, archive the reposbob ritual sweep scrap # the deletion list, per machine; deletes nothingbob ritual sweep scrap --approve <digest>bob ritual sweep scrap --apply # delete what this machine removesbob ritual sweep audit --write # the dated audit, in the operator repobob ritual sweep record <run-issue> --journal <N> --applyinventory scans each machine in machines.json: this one directly, the
others over Tailscale SSH. Under ~/projects and ~/Sites it finds:
- every git checkout down to five levels, so a folder of repos such as
Sites/acmegives one row per repo (node_modulesandvendorare not searched); - every plain directory that sits directly in a root, or beside nested repos;
- a directory whose
.gitis not a repository of its own (a vendored copy), reported as that.
A manifest whose _meta.path is outside the two roots is scanned at that
path. A manifest whose path is on no inventoried machine is a row of its own.
A machine that cannot be reached is named as not inventoried, with the
reason, and the command exits 3.
Each row carries:
| Field | Meaning |
|---|---|
id |
the path relative to the home directory, the same on every machine |
kind |
repo, dir (a plain directory) or manifest (on no machine) |
project |
the manifest that declares this path, or null |
machines |
per machine: git, dirty (uncommitted files), unpushed (commits on no remote), stashes, branch, last_commit |
remote, remote_url, remotes, default_branch |
where it clones from. remote_url is origin’s URL whatever its host. remote is the first remote that names a GitHub repo, origin first. remotes lists every remote by name, so an origin on a host such as Pantheon, with a GitHub remote beside it, shows both (#3685). A row reads no remote only when it has none. |
last_commit, subject |
the newest commit across machines |
nested_in |
the checkout this one sits inside |
same_remote_as |
other rows that are checkouts of the same repo |
audit_class |
its class in the newest audits/portfolio-audit-*.md |
portfolio |
the manifest’s _meta.portfolio, when it declares one |
notes |
what needs a look: a manifest path that is not a checkout, or differs in case |
decision |
{outcome, decided_on, note, with}, or null |
--write saves the document to portfolio.json in the operator repo. A
decision already recorded is kept. A decided row that is no longer on disk
stays in the record, marked gone.
decide records one outcome for a row, named by its id or by its
project. Nothing is carried out by this command.
| Outcome | For | Means |
|---|---|---|
invest, maintain, park |
any row | a project we work on; a row with no manifest gets one |
archive |
a row with a manifest | the project is finished; see bob ritual decide |
scrap |
a row with no manifest | its local copies are to be deleted; --discard accepts the loss of what exists only there |
not-a-project |
a row with no manifest | kept on disk and excluded: a third-party clone, a documents folder |
--with names another row decided as part of the same decision, such as a
backup beside its original or the same repo at a second path. --note
records one line of why. The row that is decided is the project’s one path:
when its manifest sits on a row decided --with it, apply moves the
manifest’s _meta.path to the decided row. A managed project is never
scrapped. It ends with archive.
status counts the decided rows per outcome and lists the rows still
undecided. It exits 0 only when every row has a decision.
apply carries the recorded decisions out, one decision at a time. It
reports each step without --apply and changes nothing. For invest,
maintain, park and archive the steps are:
| Step | What it does |
|---|---|
archive |
archive only, and it runs first. If any inventoried machine holds unpushed commits or stashes in the project’s checkouts, the decision is blocked. The step names each machine, branch and count, and nothing is written. An archived repo refuses pushes, so that work would be stranded. There is no flag to accept the loss: push it, re-inventory, and apply again (#3687). |
manifest |
a directory with no manifest gets one through bob ready. The directory must be a git checkout on this machine. Otherwise the step is blocked and names the machines that hold it. |
meta |
_meta.path is corrected to the decided row. _meta.remote is recorded from the checkout. _meta.default_branch comes from GitHub when the repo is on it (a checkout’s origin/HEAD is set at clone and goes stale), else from the local ref. The step names which one it used (#3687). |
decide |
bob ritual decide <project> <outcome> --apply, which writes _meta.portfolio and, for archive, archives the GitHub repo. |
scrap and not-a-project rows change no manifest. Their record in
portfolio.json is their outcome. projects.json is regenerated when a
manifest changed. Running apply again changes nothing. Each step’s verdict
is done, would, unchanged, blocked or failed, and a blocked or failed
step names its fix.
scrap is the deletion list for the rows decided scrap. With no flag
it deletes nothing. It measures every path again, on the machine that holds
it, and prints one line per path, grouped by machine:
| Verdict | Means |
|---|---|
DELETE |
nothing exists only there: no uncommitted file, no unpushed commit, no stash, and a remote; or an empty directory. With an archive drive declared, only once the path is archived |
DELETE … archived as … |
the path’s archive on the drive was checked against it; the line names the archive and what existed only there |
DELETE … LOSES … |
the row was decided with --discard; the line says what is lost |
BLOCKED |
something exists only there. The line names it and says what to do. A blocked path is never touched. |
gone |
already removed |
UNKNOWN |
its machine could not be reached |
Blockers are: uncommitted files, unpushed commits, stashes, a repo with
commits and no remote, a plain directory that is not empty, and a directory
that holds a row not decided scrap. With an archive drive declared, a path
is also blocked when it is not archived yet, when its archive failed the
check, or when it changed since it was archived.
The archive drive
Section titled “The archive drive”--archive copies every path on the list to a drive before anything is
deleted (#3706). The drive is declared once, in the operator repo’s
config/sweep-archive.json:
{ "mount": "/Volumes/rome", "folder": "bob-sweep-archive", "reach": "open smb://fubaz/rome in Finder, then run the command again", "machines": { "farm-01": { "mount": "/mnt/rome" } }}| Key | Meaning |
|---|---|
mount |
the mounted volume. It counts as there only when something is mounted on it (its device differs from its parent’s), so an empty mount point is never written to |
folder |
the folder on the volume that holds the archives; created when missing |
reach |
how to mount it, printed when it is not there |
machines.<id> |
a machine’s own mount, folder or reach |
mountpoint: false |
the location is a plain directory, not a mounted volume |
When the drive is declared but not there, --archive and --apply exit 2,
name it and say how to reach it, and archive or delete nothing.
On the drive, under <mount>/<folder>/:
| File | Holds |
|---|---|
<machine>/<path>.tar.gz |
the directory, .git included, as tar -C $HOME <path> |
SHA256SUMS |
one line per archive; shasum -a 256 -c SHA256SUMS checks them |
README.md |
what the folder holds and how to restore one path: tar -xzf <machine>/<path>.tar.gz -C "$HOME" |
Each archive is checked by its content: it is read back from the drive,
unpacked into a scratch directory and compared, directory by directory, link
by link and file by file, with the source. A difference is named and the path
stays blocked. A path on another machine is packed and measured on that
machine over the fleet’s SSH transport and streamed to the drive here.
SHA256SUMS and README.md are rebuilt from the records in portfolio.json
(archives, keyed <machine>:<path>) and written in one write each.
An archived path is listed as DELETE … archived as …, and the approval
covers it. Just before --apply deletes it, the path is measured again and
compared with what was archived. A path that changed is refused and the
command exits 1. --discard still deletes a row without an archive.
Deleting needs one approval of that exact list. The dry run prints a digest of
the paths it would delete. --approve <digest> records it in portfolio.json
(scrap_approval, with each approved entry). --apply refuses, exit 2,
when no approval is recorded or the list has changed since. Paths the approved
list deleted and that are now gone do not count as a change, so after one
machine has deleted its share, the next machine runs --apply on the same
approval. A new path, or an approved path that now loses more, still refuses
(#3705).
--apply deletes on the machine it runs on, and only there:
| Path | Removed by |
|---|---|
| a plain directory | bob ritual sweep scrap --apply on that machine |
a checkout on a machine that is not a laptop (farm-01) |
the same, run on that machine |
a checkout on a laptop (role dev-laptop) |
Fleet: the prune step of fleet repos sync (paulirv/fleet#77). The list names it and this command leaves it alone. |
After a row’s copy here is gone, its GitHub repo is archived, never deleted,
when the operator owns it (operator.json forge.owner) and no kept row is a
checkout of it. A repo someone else owns is left alone.
audit renders portfolio.json as one markdown page: the machines
inventoried, the count per outcome, and one row per directory with its
decision. --write saves it as audits/portfolio-audit-<date>.md in the
operator repo. The newest dated audit is the current evidence, and the next
inventory reads each row’s audit_class from it.
record <run-issue> writes the sweep into the portfolio-review run issue,
so that bob ritual close finds what it checks:
- the Portfolio decisions table gets one row per manifest, with
Decision set from the manifest’s
_meta.portfolio. A project the sweep made managed is added withnewunder Status now; --journal <N>is linked on every row whose status changed and that has no link yet;- a Swept directories section lists the rows with no manifest, with their outcome.
It reports without --apply. It names the manifests that still declare no
decision, and the changed rows with no decision issue. The repo is --repo,
else the origin of the bigbrain checkout. Exit 4 when the forge could not
be read or refused the write.
The project list other tools read
Section titled “The project list other tools read”projects.json answers “which projects do we work on, and where does each
clone from” in one read (#3609). It is derived from the manifests by
projects-derive.js and deployed to BOB_HOME:
| Field | From |
|---|---|
name, path, group, active |
_meta.project, _meta.path, _meta.group or the path default, _meta.active |
remote, default_branch |
_meta.remote, _meta.default_branch, recorded by sweep apply; absent until recorded |
portfolio |
_meta.portfolio; absent until decided |
excluded[] |
top level: {path, outcome, remote} for every directory the sweep decided scrap or not-a-project |
Fleet checks its declared repos against this file: a project decided invest
or maintain belongs on a laptop, one decided archive and a directory
decided scrap do not.
| Flag | Meaning |
|---|---|
--write |
inventory: save portfolio.json |
--machines id,id |
inventory: scan only these machines; the rest are named as not inventoried |
--self <id> |
inventory: which machine this is, when its hostname is not in machines.json |
--audit FILE |
inventory: the audit to read classes from |
--with <row> |
decide: repeatable |
--note "<why>" |
decide |
--discard |
decide <row> scrap: accept the loss of what exists only there |
--apply |
apply: write; scrap: delete; without it, report |
--only <row> |
apply: carry out this one decision |
--approve <digest> |
scrap: approve the exact list the dry run printed |
--archive |
scrap: archive every path on the list to the declared drive and check each archive; deletes nothing |
--write |
audit: save the dated audit in the operator repo |
--journal <N> |
record: the decision issue linked on each status change |
--repo owner/name |
record: the repo that holds the run issue |
--json |
emit bob-portfolio/1, or the -decide/1, -status/1, -apply/1, -scrap/1, -audit/1, -record/1 document of the subcommand |
--root DIR |
another checkout’s operator data |
Exit codes: 0 done · 1 status: a row is undecided; apply: a
decision is blocked or failed; scrap --apply: a path could not be removed; scrap --archive: an archive failed its check ·
2 usage or config error (an unknown row, an outcome the row cannot take, no
operator repo, a scrap list that is not approved, an archive drive not declared or not there) · 3 inventory: a machine
was not inventoried · 4 record: the forge could not be read or refused the
write.
A manifest carries what apply records in two _meta keys: remote (the
origin URL of the checkout) and default_branch.
Drills
Section titled “Drills”Recovery procedures that are never rehearsed fail when needed. Three drills
are declared once, at the BoB level, on the drill entry’s drills list in
registry/rituals.json (#2240) — like the portfolio review, each is held on
its home (bigbrain) at its own cadence whatever that project’s liveness,
keyed drill:<name>, with its runs filed in the home’s repo, and no other
project may declare it:
| Drill | Kind | Rehearses | Runbook | Default |
|---|---|---|---|---|
restore |
restore |
rebuilding BOB_HOME and one project on a machine from cdr and declared state alone, timed — what is copied back from the backup by hand is undeclared state |
drill-restore |
P3M |
lost-laptop |
credential-rotation |
revoking every credential whose consumers include one machine, from the revocation list secrets-inventory.json generates — the inventory is only complete enough if the list can be acted on (requires_machine: a run names the machine) |
drill-lost-laptop |
P3M |
farm-compromise |
incident-response |
a tabletop: farm-01 is assumed compromised — blast radius from the inventory, containment order, revocation, rebuild, detection; nothing is executed |
drill-farm-compromise |
P3M |
Each drill entry carries name, kind (a drill_kind), purpose, home,
cadence (the default), runbook (a registry/runbooks/ basename the gate
checks exists), requires_machine, tabletop and why. Every runbook has
the same spine: a timed checklist (target minutes per step, ticked in the
run issue with the clock time), the steps, an outcome template — machine,
started, recovered, time to recover, steps that failed, undeclared state
discovered, steps over target — and an escalation rule. The incident kit
(the drill kit) carries the three runbooks, so bob ritual run drill links
them into .claude/runbooks/.
The home may restate a drill’s cadence — and only that — with a declaration naming the drill:
{ "name": "drill", "drill": "restore", "cadence": "P6M", "why": "restores are rehearsed on every new machine anyway" }drill_kind may accompany it only if it matches. Any other project declaring
drill: <name> is refused (drill-declared), as is the home declaring a
plain drill of the kind that is a BoB-level drill’s key. A drill is never
farm-eligible: bob tick prepares no packet for it, and the rehearsal itself
is the evidence. bob ritual due --project bigbrain lists the three as
drill:restore, drill:lost-laptop and drill:farm-compromise with
scope: drill; a run’s marker carries drill (the name) beside drill_kind,
and machine when one was named. Gaps a drill finds are filed as issues from
the run exactly as any ritual’s findings are
(Closing a run).
Guide walkthroughs
Section titled “Guide walkthroughs”A guide that needs a real person to follow it once is not a to-do. It is an attended session, and the agent leads it (#3809). The ask gate refuses the to-do and names this ritual.
The guide-walkthrough entry in registry/rituals.json lists the guides that
are owed a walkthrough:
| Field | Meaning |
|---|---|
name |
the guide’s name; the run key is guide-walkthrough:<name> |
title |
the guide’s title |
doc |
its file, relative to the home project’s checkout |
home |
the project whose repo holds the guide, its requirement and the runs |
requirement |
the issue whose acceptance criterion asks for the walkthrough |
criterion |
which checkbox of that issue asks for it, counted from 1 |
A walkthrough has no cadence. bob ritual due lists one for each guide whose
requirement is open with that criterion unticked, as due. Ticking the
criterion, once the findings are folded back into the guide, takes it off the
list. A requirement that cannot be read grades its walkthrough unknown. No
project declares the ritual (check-rituals: walkthrough-declared).
bob ritual open guide-walkthrough --guide first-product --dry-run # a listed guide, by namebob ritual open guide-walkthrough --guide docs/tutorials/first-product.mdbob ritual open guide-walkthrough --guide runbooks/restore.md --project <project> # any other .md filebob ritual run guide-walkthrough --guide first-product # open or resume, apply the kit, print the briefA listed guide is walked on its home project. Any other .md file is walked on
the project given, keyed by its file name. The run body carries a three-item
checklist, and close refuses a run with none of them ticked:
- Open the guide.
- Do each step with the operator, in order and exactly as written.
- Record each place it was wrong as a finding.
Replacing a to-do. A to-do that asked the operator to follow the guide is
converted with --replaces <todo> (repeatable):
bob ritual open guide-walkthrough --guide first-product --replaces 2575 --dry-runbob ritual open guide-walkthrough --guide first-product --replaces 2575Once the run exists, filed now or already open, each to-do gets a comment
linking the run and is closed as not planned. It never gets completed, so
nothing resumes from it; the walkthrough stays on bob ritual due instead.
Only an open todo without completed is replaced: a closed one, a completed
one or an issue that is not a to-do is refused (exit 2) before a run is filed.
A to-do the forge would not close is named and the command exits 4; run it
again.
The session brief for a walkthrough replaces “gather the evidence yourself” with leading the guide one step at a time: the agent says the step, the operator does it as written, and each difference between the page and what happened is a finding.
Checklists
Section titled “Checklists”A review that walks a list nobody wrote down covers whatever came to mind that day. A declaration may therefore name a checklist — a registry runbook the run embeds as tickable items — so every run of the ritual walks the same list at a known version, and what it skipped is recorded rather than forgotten (#2241):
{ "name": "security-review", "cadence": "before-stable-release", "gates": ["stable-release"], "checklist": "bob-attack-surface-review" }The checklist runbook carries two things ritual.js reads:
| In the runbook | Read as |
|---|---|
**Checklist version:** \1`` |
the version — bumped in the same PR as any change to the list |
a | # | Item | Surface | How to assess | table |
one tickable item per row; the last column links the runbook section that says how |
What it changes:
- Opening. The run’s body gains a Checklist section between Kit and
Evidence: the runbook, its version, and each row as
- [ ] N. <item> (`<surface>`). The marker stampschecklist: {name, version}, so the run knows which version it was opened against even after the list changes. A runbook with no version line or no rows is embedded as unversioned with a visible gap line, never silently empty; a declared checklist whose runbook does not exist is refused (unknown-checklist). - Holding.
bob ritual runprints the checklist line beside the packet, and the session brief has the agent walk the items. Each item is assessed as its runbook section says and ticked; an item left unticked is skipped on purpose —— not assessed: <why>after it records the reason. - Closing.
bob ritual closerefuses a run with nothing ticked (it was not held). Otherwise the marker’sclosed_withgainschecklist: {name, version, items, assessed, not_assessed: [n…]}, the close prints the record, and the delta gains a Checklist line — this run’s version, count assessed and items not assessed, then the previous run’s, with a note when the versions differ (items are only comparable at the same version). - Gate.
checklistnames aregistry/runbooks/file (unknown-checklist) and is refused on adrill, whose checklist is its runbook’s timed one (checklist-on-drill).
The docs review. docs-review (#2625) is the human pass over the
documentation that make docs-check cannot make: its checklist,
docs-review-checklist,
carried by the review kit, re-verifies stale and aging pages, walks one
tutorial end to end on a clean project, checks that new commands and skills
are reachable from a how-to, that no page presents a retired item as live,
that the live site matches the latest build, the “Start here” nav, the
generated references, the troubleshooting page against the period’s errors,
and the orphan and archive candidates. comprehensive-review keeps
doc-audit as one of its inputs; docs-review is the focused pass, so the
two do not duplicate each other. It is farm-eligible — the farm prepares the
packet, a person holds the run — and its default cadence is monthly while a
project is active and quarterly while it is dormant. A project that declares
its own _meta.rituals adds it with the checklist:
{ "name": "docs-review", "cadence": "P1M", "checklist": "docs-review-checklist" }Its durable outputs are the ones every run has — bob ritual close refuses a
finding that links no req, bug or todo and gives no no-action reason —
plus one PR of the pages the run re-verified, each with a restamped
last_reviewed, whose body says Refs #<run> so it is listed on the run
issue. The packet names the tutorial the run walks
(Tutorial rotation). bigbrain’s declaration waits
for the vocabulary to reach master, because the operator repo is shared with
the master checkout (#3447).
Holding one: Hold a Ritual → Docs review.
BoB’s attack-surface review. bigbrain’s security-review walks
bob-attack-surface-review,
carried by the security kit: hooks on every tool call, the automation
profiles and the full-auto floor, the farm tick’s unattended merge, the RDB
bridge and Telegram channel, gh token scopes per machine, Tailscale ACLs and
SSH, BOB_CHECKOUT_TOKEN and the CI credentials, registry items that run
shell, .mcp.json servers, the self-hosted runner, and the credential
inventory against the stores. Each item names the commands whose output is
its evidence and what a clean result looks like; the session runs the
security-auditor agent once per surface and files each finding as a
security issue. It is held on a laptop — the farm-worker role forbids the
security kit — before each stable release, and the release
gate holds bob release promote until the run is closed.
Its first run is #2422,
opened 2026-09-15 with checklist v1 embedded; the gate is on for farm-01 (the
machine that promotes), so the first stable release after #2241 waits for
that run to close — todo
#2423 is the human’s
handle for holding it.
Cadence
Section titled “Cadence”| Form | Meaning |
|---|---|
P<n>D, P<n>W, P<n>M, P<n>Y |
Due that long after the ritual’s last run — P14D, P2W, P3M, P1Y. n ≥ 1; hours and combined durations are refused. |
before-stable-release |
Due ahead of each stable release rather than on a clock. With gates: ["stable-release"] it holds the stable promotion until it has been held since the last release — see The release gate. |
Declaring a project’s rituals
Section titled “Declaring a project’s rituals”{ "_meta": { "project": "bigbrain", "rituals": [ { "name": "comprehensive-review", "cadence": "P3M" }, { "name": "security-review", "cadence": "before-stable-release", "gates": ["stable-release"], "why": "security review keeps pace with releases" }, { "name": "drill", "cadence": "P6M", "drill_kind": "restore" } ] }}| Field | Rule |
|---|---|
name |
required; one of the vocabulary, except portfolio-review |
cadence |
required; a cadence as above |
kit |
optional; a kit in registry/kits.json — defaults to the vocabulary entry’s |
farm_eligible |
optional boolean — defaults to the vocabulary entry’s |
drill_kind |
required on a drill, refused on anything else; two drills of different kinds may coexist |
drill |
a BoB-level drill by name — only on its home project, to restate its cadence (Drills); a drill_kind alongside must match |
why |
optional one line — why this project departs from the default |
gates |
optional; a non-empty list of what the ritual holds while overdue — today only stable-release (The release gate) |
checklist |
optional; a registry/runbooks/ basename the run embeds as tickable items at its declared version (Checklists, #2241); refused on a drill |
advisories_at |
optional, dependency-audit only; critical | high | moderate | low — the severity at or above which this project’s audit files advisories as issues, over the vocabulary’s high (Advisories, #2242) |
Declaring _meta.rituals — even as an empty list — replaces the defaults:
the project’s schedule is exactly what it lists. A project with no
_meta.rituals gets the default cadences for its liveness.
Defaults by liveness
Section titled “Defaults by liveness”A project that declares nothing is classified, and scheduled with that column of the vocabulary table:
| Liveness | When | Schedule |
|---|---|---|
active |
its last commit falls inside liveness.active_within (P3M), or a warp run is live |
the active defaults |
dormant |
its last commit is older than the window | the dormant defaults — a dependency audit every six months, nothing else |
unknown |
nothing measured it | the active defaults, so an unmeasured project is shown its rituals rather than let lapse |
| archived | _meta.active: false |
no rituals |
Liveness is read, never declared:
- The fleet snapshots (
$BOB_FLEET_SNAPSHOTS, default~/.claude/fleet/snapshots/*.json) — each project row’slast_commit.atandwarp.session. The newest measurement across machines wins; a row whose has neither a last commit nor a live run did not measure the project. - The checkout on this machine, when no snapshot measured it and
_meta.pathexists — read throughscripts/fleet/project-live.js, the same reader the snapshot uses, so the two can never disagree about a project.
The report names which one it used (snapshot:<machine> or live).
Run history — ritual-run issues
Section titled “Run history — ritual-run issues”Every held ritual is a GitHub issue in the project’s repo, so run history is shared across machines with no local ledger (#2231):
| Label | Meaning |
|---|---|
ritual-run |
this issue is one ritual run — an execution artifact, never lesson or decision |
ritual:<name> |
which ritual — one label per vocabulary entry; a drill’s kind is in the body |
Both are part of the standard label set scripts/bootstrap-labels.sh ensure
ships. The run’s body carries a one-line marker:
<!-- bob-ritual-run/1 {"project":"bigbrain","ritual":"comprehensive-review","held_on":"2026-09-10"} -->drill_kind is added for a drill; a BoB-level drill adds drill (its name,
which keys the run as drill:<name>) and machine when the run named one
(Drills). Last run per (project, ritual) is derived, never
recorded:
- only closed runs count; a run closed as not planned was cancelled and does not;
- the day is the marker’s
held_on, else the date the issue closed; - a marker naming another project is skipped; a run with no marker counts for every project in the repo;
- the newest day wins, ties to the higher issue number, which is reported as
last_run_issue.
bigbrain’s first recorded run is the 2026-09-10 architecture review, backfilled
as comprehensive-review run #2372
(held_on 2026-09-10, closed as held) with the issues it filed as its findings.
The repo is --repo owner/name, else the github.com origin of the project’s
checkout on this machine. Reads go through gh (and the bin/gh shim’s forge
cache), one call per repo per report. No repo, or a forge that cannot be
read, makes every dated ritual unknown — never “never run”, so a degraded
forge cannot flip a whole fleet to due.
Opening a run — ritual.js open
Section titled “Opening a run — ritual.js open”node scripts/rituals/ritual.js open comprehensive-review --project bigbrain --dry-runnode scripts/rituals/ritual.js open drill --drill-kind restore --project bigbrainnode scripts/rituals/ritual.js open --ritual guide-walkthrough --guide first-product --dry-runThe ritual is the positional name or --ritual <name>.
open reads the repo’s ritual-run issues first and refuses a duplicate: if
a run of the same ritual (a drill: the same kind) is already open for the
project, nothing is filed and the open run is named. Otherwise it files one
issue labelled ritual-run and ritual:<name>, titled
ritual: <name> — <project> (<held_on>), whose body is the standard shape:
| Section | Holds |
|---|---|
| marker | bob-ritual-run/1 — project, ritual, drill_kind, checklist ({name, version}, #2241), held_on |
| header table | project and manifest, held date, kit, cadence (declared, default, or not on the schedule) |
| Scope | the ritual’s purpose from registry/rituals.json, to narrow before the session |
| How the session runs | the session brief as a numbered list, so a session that starts from the issue alone is led the same way (#3611) |
| Kit | claude --kit <kit> |
| Checklist | a run whose declaration names a checklist (#2241): the runbook, its version and each of its rows as a tickable item — see Checklists |
| Drill | a drill run only (#2240): the drill, kind, machine and runbook, then the runbook’s timed checklist as tickable steps with their target minutes and a done at: __:__ slot; a tabletop says nothing is executed |
| Revocation list | a credential-rotation drill with --machine only: the machine’s revocation list generated from secrets-inventory.json by cred-check.js revocation (secrets-inventory.md) — its gaps are already findings |
| Evidence | a placeholder — the packet is posted as a comment (farm-prepared when the ritual is farm-eligible, #2233); a drill says the rehearsal itself is the evidence |
| Outcome | a drill run only: the outcome template — machine, started, recovered, time to recover, steps that failed, undeclared state discovered, steps over target — filled in the session; close refuses while it is unfilled |
| Findings | a checklist; each finding is its own bug, req, task, security or todo issue, linked, or a no-action: <why> line |
| Journal | lessons and decisions from the run, each its own /journal issue, linked — never a label on the run (#2062) |
| Delta vs previous run | the previous closed run for this project and ritual, or “first recorded run”; filled in at close |
| Portfolio decisions | a portfolio run only: one row per project with its status now, to decide in the session (Portfolio decisions) |
Close the run with bob ritual close when
the ritual is held — that close is what makes it the last run. Close it as not
planned to cancel.
| Flag | Meaning |
|---|---|
<ritual> |
required; a vocabulary name |
--project <p> |
the project (default: the one holding the current directory) |
--drill <name> |
a BoB-level drill — restore, lost-laptop, farm-compromise — filed on its home (--project may only name the home); its kind and runbook come from the vocabulary (Drills) |
--drill-kind <kind> |
a project’s own drill: required for drill without --drill, refused on any other ritual; with --drill it must match the drill’s kind |
--machine <id> |
a machines.json id the drill rehearses losing or rebuilding — required by lost-laptop, allowed on any drill, refused elsewhere; recorded in the marker and the title |
--repo owner/name |
file in this repo instead of the checkout’s origin |
--held-on YYYY-MM-DD |
the held_on stamped in the marker (default today, UTC) — a backfill passes the day it was held |
--dry-run |
print the title, labels and body; file nothing |
--json |
emit bob-ritual-open/1 |
--root DIR |
read registry/, schemas/ and provisions/ from another checkout |
Exit codes: 0 filed, queued, or --dry-run · 2 usage or config error
(unknown ritual or project, a kindless drill, a bad --held-on, no resolvable
repo) · 3 a run of this ritual is already open · 4 nothing filed — the
forge could not be read (a duplicate cannot be ruled out) or refused the create
(a missing label: scripts/bootstrap-labels.sh ensure --repo <repo>).
bob-ritual-open/1: verdict (filed | queued | dry-run |
duplicate | forge-unreadable | not-filed), project, repo, ritual,
drill_kind, key, held_on, kit, checklist ({name, version, items}
— the checklist embedded, or null), title, labels, issue, url,
duplicate, previous ({date, issue} or null), body and error. A create
the bin/gh write queue spooled during an outage is queued with its
BOB-Q<n> handle as issue — never reported as filed.
Evidence packets — ritual.js prepare
Section titled “Evidence packets — ritual.js prepare”Gathering evidence is mechanical and cheap next to the judgement the session
spends, so it happens before the session: prepare runs the ritual’s
collectors report-only and posts one packet comment to the open run
issue (#2233).
node scripts/rituals/ritual.js prepare 2372 --dry-run # print the packet, post nothingnode scripts/rituals/ritual.js prepare 2372 --json # gather and post (or replace) itEach ritual names its collectors in registry/rituals.json → evidence, from
this fixed set (schemas/rituals.schema.json → definitions.collector,
implemented in scripts/rituals/evidence.js):
| Collector | Runs | Needs |
|---|---|---|
cost-churn |
scripts/dashboard/series.js --json — the project’s bob-series/1 row (#2072) |
the project registered on this machine |
status-report |
the /what-next --report data through gh: issues closed in the last 14 days, open capability progress, the approved queue, open todos |
the repo |
aged-issues |
scripts/warp-drive/cap-status.js audit --json — divergent cap labels, long-in-progress and parked issues (#2064) |
the checkout |
usage-gaps |
scripts/orchestrator/gap-detect.js divergence --json and bob tour --no-backfill --json, plus the continuing-education queues: bob u status --json and loop-notes.js retire --dry-run --json / review --json |
the checkout |
doc-audit |
scripts/doc-keeper/audit.js --root <checkout> --json |
the checkout |
memory-audit |
scripts/memory-audit/audit.js --root <checkout> --json |
the checkout |
dependency-audit |
npm audit --json / pnpm audit --json / composer audit --format=json --locked for each lockfile found — n-a when there is none; on a dependency-audit run the advisories are then filed, commented on and closed (Advisories) |
the checkout |
fleet-readiness |
scripts/fleet/readiness.js projectReadiness(<checkout>) (#446) |
the checkout |
doc-staleness |
scripts/doc-keeper/staleness.js --json (#2552) — every stale and aging page by path, grade and age |
the checkout |
doc-coverage |
check-howto-coverage.js --json (#2553) and check-troubleshooting.js --json (#2562) — commands no how-to runs, entries that no longer match their message |
the checkout |
doc-changes |
git from the previous docs-review run’s close (or the last 30 days) to now — bob commands, skills and retired items added that no page under docs/, runbooks/ or README.md names; generated references do not count — n-a when the checkout has none of the three |
the checkout; the run history |
docs-tutorial |
the tutorial this run walks: the least recently walked across the closed runs’ packets, never-walked first (Tutorial rotation) — n-a without docs/tutorials/ |
the checkout; the run history |
docs-site-live |
the live site’s /version.json (#2620) against the default branch’s HEAD and the newest release, and the last docs-deploy.yml run — a gap until the site serves the stamp; n-a without docs-site.json |
the checkout; the site; the repo |
portfolio |
every managed project at once — liveness, readiness, overdue rituals, open p1/p2, cost/churn, harness defects (details, #2235) | provisions/; each project’s snapshot row, checkout or repo where it has one |
The checkout is the project’s _meta.path on this machine. Nothing a collector
runs writes to the tracker, a manifest or the checkout — bob tour runs with
--no-backfill for that reason, and a non-zero “found something” exit
(cap-status 3, gap-detect 3, npm audit 1) is an outcome, not a failure.
check-rituals refuses a farm-eligible ritual that declares no collectors.
A collector that cannot run is a gap, never a failure. No checkout on this
machine, a tool that is not installed, a non-zero exit, output that is not JSON,
a timeout or a spent budget each become a named gap with its reason; the other
collectors still run and the packet is still posted. A collector that reads
half of what it needs (usage-gaps with gap-detect but not the tour) keeps
the half and names the rest.
Tutorial rotation (#2625)
Section titled “Tutorial rotation (#2625)”Each docs-review run walks one tutorial end to end. Which one is derived,
never stored: docs-tutorial reads the packets of the closed docs-review
runs (the newest 24; a run closed as not planned walked nothing), where each
packet names its tutorial in a <!-- bob-docs-review-tutorial/1 {"tutorial": …} -->
line, and picks a tutorial no run has walked yet — in file order — or else the
one walked least recently. The packet shows the whole rotation as a table.
doc-changes reads the same history for its window, so prepare reads the
run list once for both.
Continuing education — the usage-gaps queues (#2905)
Section titled “Continuing education — the usage-gaps queues (#2905)”Continuing education is not a separate ritual (decision #2906). It is evidence
on the two rituals that already hold usage-gaps: comprehensive-review and
vision-planning, and no others. It adds no ritual name and no collector enum
value. usage-gaps reports two queues, each as its own section of the packet
and as a clause of its summary line:
| Queue | Read from | Lists |
|---|---|---|
| Disposition backlog | bob u status --json (Dispositions) |
Every item with no disposition, with its applicability bucket and fleet usage, and every certification flagged re-examine, naming the model it was certified on and the model it runs on now (Re-examination, #2904). |
| Loop notes due for review | loop-notes.js retire --dry-run --json and loop-notes.js review --json, in the checkout (Loop memory) |
Notes whose defect: has closed but that are still in the store, and untagged notes recorded at least _workflow.loop_note_review_after_days (default 30) ago that have not recurred inside that window. |
Both are report-only. The packet shows what retire would drop, and the
attended session applies it with loop-notes.js retire --cwd <checkout>. For a
due untagged note, keep it by tagging its defect or recording it again, or
remove it from .claude/loop-notes.json. A long list is cut at 25 rows with the
rest counted; the raw data keeps every row. When one queue cannot be read, its
clause says so and usage-gaps still reports the rest.
The packet opens with a marker, then a summary table (collector, status
ok | n-a | gap, one-line summary), a Gaps section naming each gap, a
section for any collector with more to show than one line (the portfolio
table, the usage-gaps continuing-education queues), and each collector’s raw JSON in a collapsed <details> block, truncated so the
comment stays under GitHub’s size limit:
<!-- bob-ritual-packet/1 {"run":2372,"project":"bigbrain","ritual":"comprehensive-review","prepared_at":"2026-09-14T23:41:43.681Z"} -->Re-running replaces, never duplicates. prepare reads the run’s comments
first; a comment carrying the marker is edited in place, otherwise a new comment
is added. When the comments cannot be read nothing is gathered or posted — an
existing packet could not be ruled out.
| Flag | Meaning |
|---|---|
<run-issue> |
required; the ritual-run issue number (# optional) |
--project <p> |
the project (default: the run marker’s project, else the one holding the current directory); refused when the marker names another |
--repo owner/name |
the repo holding the run (default: the project checkout’s github.com origin) |
--budget-seconds N |
bound the whole gathering pass: a running collector’s timeout is cut to what is left and later collectors become budget gaps (each collector’s own timeout is 180 s) |
--dry-run |
gather and print the packet; read no comments and post nothing |
--json |
emit bob-ritual-prepare/1 |
--root DIR |
read registry/, schemas/ and provisions/ from another checkout |
Exit codes: 0 posted, updated, queued, or --dry-run · 2 usage or
config error (not a ritual-run issue, a project mismatch, no resolvable repo or
project) · 3 nothing to prepare — the run is closed, or its ritual declares no
collectors · 4 nothing posted — the run or its comments could not be read, or
the forge refused the write.
bob-ritual-prepare/1: verdict (posted | updated | queued |
dry-run | closed | no-collectors | forge-unreadable | not-posted),
project, repo, issue, ritual (the key — drill:<kind> for a drill),
kit, checkout (the path, or null when absent here), collectors[]
(name, status, summary, reason, ms), gaps[] (name, reason),
advisories (a dependency-audit run’s filing outcome — below; null on every
other ritual), comment (id, url; both null when queued), body and
error. A comment the bin/gh write queue spooled during an outage is
queued, never posted.
Advisories — filed from the dependency audit
Section titled “Advisories — filed from the dependency audit”The collectors are report-only; a dependency-audit run adds one write
step after them (#2242). prepare hands the audit output the collector
gathered to scripts/security/advisories.js, which reconciles it against the
repo’s open advisory issues before the packet is rendered — so bob tick,
which prepares farm-eligible rituals due within 48 hours, files advisories on
the ritual’s cadence (monthly for an active project, every six months for a
dormant one) with no one at the keyboard.
Each advisory is one per advisory ID + package — the GHSA slug where the
tool gives one (npm, pnpm), else the CVE, else the tool’s own id
(composer’s PKSA-…) — and the issue that tracks it opens with a marker:
<!-- bob-advisory/1 {"id":"GHSA-xvch-5gv4-984h","package":"minimist","tool":"npm"} -->| The audit reports | The repo holds | prepare does |
|---|---|---|
| an advisory at or above the threshold | no open issue with its marker | files a security, advisory, task issue on the owning repo — p1-critical for critical, p2-high for high, p3-medium for moderate, p4-low for low — titled by package, title and ID, in the task shape below |
| an advisory (any severity) | an open issue with its marker | comments “still reported on <date>” — a re-run never files a second issue |
| an advisory below the threshold | no open issue | nothing — it is counted as below_threshold and stays in the packet’s raw data for the attended session |
| nothing for an open issue’s advisory, and its tool ran | an open issue with its marker | closes it with the fixing commit when git shows one — the newest commit since the issue was filed that touched that tool’s lockfile with the package named on a changed line (a bump rewrites its resolved URL); otherwise “not detected” |
| nothing, but its tool did not run (no lockfile now, or the audit gapped) | an open issue | leaves it open as unverified — an audit that did not run resolves nothing |
A filed advisory is a task (#3406). It carries the task label and the
body /task writes, so warp-drive’s task
loop picks it up and its definition-of-done gate judges it
(The task work type):
## Description— the advisory table: package (direct or transitive), linked advisory ID, title, vulnerable range, fix and lockfile.## Verification— three steps the loop runs and ticks: the package resolves to a fixed version in the tool’s lockfile (or, with no fix reported, to a version outside the vulnerable range);npm audit,pnpm auditorcomposer audit --lockedno longer reports the ID; the project’s tests pass on the updated lockfile.## What to do— triage, patch or accept, with the fix path for its tool.## Notes—Priority:and aTrigger:line naming the linked advisory ID, the package, the audit tool, the date and the run number.
The marker still opens the body, so dedupe is unchanged: an issue filed before
the task shape is found by its marker and commented on, never filed twice.
Those older issues are not changed by an audit. advisories.js relabel [<checkout>] [--repo owner/name] [--dry-run] [--json] brings the open ones up
to the task shape once: it adds task and appends the Verification checklist
and the Trigger: line, read from the issue’s own marker and table, keeping
every line the body had. An issue already in the task shape is current and
left alone, so a second pass changes nothing. Exit 0 done, 2 usage error,
4 the forge could not be read or refused an edit.
An advisory whose tool reports no severity (older composer output) is
never dropped: it is filed at the threshold with p3-medium and its severity
shown as unknown. The threshold is the vocabulary’s
advisories.file_at (high), which a project restates with advisories_at on
its dependency-audit declaration (Declaring a project’s rituals).
The three labels come from scripts/bootstrap-labels.sh; a create the repo
refuses for a missing label is that advisory’s error, with the ensure
command to run.
Nothing is written when the open issues cannot be read — a duplicate could
not be ruled out — and --dry-run reads them, says what it would file,
comment on and close (fixing commit included) and writes nothing, the state
file included. A create the bin/gh write queue spooled is queued, with no
issue number yet.
The packet’s summary table keeps the collector’s one-line count; a
### dependency-audit section beneath the gaps reports the outcome — one
line of counts, then a row per advisory at the threshold, resolved issue and
unverified issue with its action and issue number. The document’s advisories
carries verdict (ok | partial | dry-run | forge-unreadable |
skipped — no audit ran), threshold, open (at the threshold),
by_severity, filed, commented, queued, closed, unverified,
below_threshold, errors, issues[], state_file and error.
The state file. Every non-dry reconcile writes
<checkout>/.claude/.dependency-advisories.json (bob-dependency-advisories/1):
audited_at, run, threshold, tools (ok | gap per lockfile), open[]
(id, package, severity, tool, issue, url), counts (by severity
and total), below_threshold, unverified[] and actions. It is derived
session state (scripts/lib/derived-paths.json, so the managed ignore block
covers it) and the reader the fleet snapshot uses for a project’s open-advisory
counts — no forge call. Outside a ritual:
node scripts/security/advisories.js run --dry-run # audit the checkout here, say what would be filednode scripts/security/advisories.js run --severity moderate --jsonnode scripts/security/advisories.js status # what the last audit left: exit 0 clean · 1 open · 3 never auditedrun exits 0 with nothing open at the threshold, 1 while advisories are
open, 2 on a usage error, 4 when the forge could not be read or refused a
write. It needs a github.com origin or --repo.
In the fleet snapshot. audit.js snapshot reads the state file for every
present project — no forge call — into the project row’s advisories
(open, critical, high, moderate, low, unknown, threshold,
audited_at, run, issues[], unverified; null when the project was never
audited on that machine) and rolls it up in summary.advisories (projects
audited, with_open, the counts summed, names with something open,
oldest_audit, and by_project with each project’s counts, issues and audit
time). audit.js view renders a Dependency advisories table whose FLEET
row counts each project once, by its newest audit across machines. That
snapshot field is what the briefing collector (#2243) reads for open-advisory
counts per project; rendering them in the briefing belongs to #2224.
Every due ritual — prepare --due
Section titled “Every due ritual — prepare --due”What bob tick runs after its fleet snapshot (#2233):
node scripts/rituals/ritual.js prepare --due --all --within P2D --budget-seconds 17400 --jsonnode scripts/rituals/ritual.js prepare --due --dry-run # the project holding the current directoryA ritual is a target when it is farm-eligible, declares collectors, and is
overdue or due with --within as the due window (default P2D, the next 48
hours — so a never-run ritual is always a target). unknown rituals are not:
with no readable run history there is no repo to post to. Rituals are taken most
urgent first, project by project. For each target:
- the open run of that ritual (for a drill, that kind) is reused; with none,
one is opened through
ritual.js open— the same duplicate refusal, so a target never gets a second run; - its packet is prepared as above, with the remaining budget passed down.
--budget-seconds bounds the whole pass. A target reached after the budget is
spent is listed as not prepared, and no run is opened for it. --dry-run reads
the runs but opens nothing, posts nothing and runs no collector. It lists each
target as would_prepare with the open run it would use, or opens: true. A run
the forge write queue spooled, a refused create, or a packet that could not be
posted is listed as not prepared with its reason; none of these is thrown.
| Flag | Meaning |
|---|---|
--due |
prepare every due target instead of one run issue |
--project <p> / --all |
as for due (neither: the project holding the current directory) |
--within <duration> |
the due window (default P2D) |
--budget-seconds N |
the evidence budget for the whole pass |
--dry-run |
list the targets; write and gather nothing |
--now YYYY-MM-DD, --snapshots DIR, --root DIR |
as for due |
--repo belongs to prepare <run-issue> only; each target’s repo comes from its
run history.
Exit codes: 0 every target prepared, nothing due, or --dry-run · 1 at
least one target not prepared · 2 usage or config error.
bob-ritual-prepare-due/1: now, within, budget_seconds, dry_run,
verdict (ok | partial), summary (targets, prepared,
would_prepare, failed). Three lists follow, each entry carrying project,
ritual, status and next_due:
prepared[]addsissue,opened,verdict(posted|updated|queued),gaps(a count),commentandadvisories(adependency-auditpacket’s filing outcome, null otherwise) — the tick digest carries itsverdict,threshold,open,filed,commented,closedandunverified;would_prepare[]addsissue(null when a run would be opened) andopens;failed[]addsissueandreason.
Holding a run — bob ritual run
Section titled “Holding a run — bob ritual run”The attended session is where the judgement happens. run starts it prepared
(#2234):
bob ritual run comprehensive-review # the project holding the current directorybob ritual run drill --drill-kind restore --project bigbrainbob ritual run dependency-audit --dry-run --json # what it would resume or open; writes nothingbob ritual <verb> routes to node scripts/rituals/ritual.js <verb> —
run, due, open, prepare, close and decide are rows in scripts/bob/commands.json.
- The role gate. The ritual’s kit is checked against this machine’s global
manifest (
kits.forbid, #2228). A forbidden kit is refused, exit3, naming the manifest — before any run is opened or any overlay applied. A farm worker forbidssecurityandvision-planning, so a dependency audit or vision session is never held there. - Resume or open. The open run of this ritual for the project (for a drill,
that kind) is resumed; with none, one is opened through
open, with the same duplicate refusal. - Apply the kit.
bob provision overlay apply --kit <kit> --ritual-run <owner/repo#N>runs against the project’s checkout on this machine. The overlay is stamped with the run, sobob provision overlay statusnames it and a session abandoned without a close stays visible (Session Kits). With no checkout here the run is a config error unless--no-kitis passed. - Present. It prints the evidence packet’s summary table. The packet is
read back from the run’s
bob-ritual-packet/1comment. A fresh run has none yet, sorunnamesbob ritual prepare <N>instead. It also prints the previous closed run’s Findings checklist, each line with the issue it links, and — for a ritual with a declared checklist — the checklist’s name, version and item count on the run issue. - Brief. A run that started or resumed ends with the
session brief. A
--dry-runstarts no session and prints none.
The session brief
Section titled “The session brief”A ritual is a session the agent leads; the operator decides. Without that
instruction an agent assesses everything and hands back a report: a list of
findings, one question per finding, and “rotate per the runbook” for work it
could have done (#3611). So run ends with a brief, one numbered line per
step, in the order the session runs:
| # | Step | What it tells the agent |
|---|---|---|
| 1 | Open in plain words | Before any work, say what this ritual is (the vocabulary’s purpose, quoted), how long it will take and what the operator’s part will be. Assume it is the operator’s first time: no jargon, and no runbook or command named without saying what it does |
| 2 | Gather the evidence yourself | Read the packet and walk the checklist, the drill’s timed checklist or the project. Ask the operator nothing while gathering |
| 3 | One recommended plan, one decision | Put the findings into a plan, each with a recommendation, and ask once: do everything recommended / do part of it / stop for now, with the recommended answers pre-selected. Never one question per finding |
| 4 | Run the follow-up yourself | Follow-up a runbook describes (a rotation, a cleanup, a config fix) is the agent’s to run on the operator’s go, saying what will be run and what the operator will see. The operator is handed a step only for a login, a vendor page or a decision, and that step says where to go, what to type and what to expect |
| 5 | Open every page with “Your steps” | Any page or briefing the session produces opens with a “Your steps” list: the operator’s actions, numbered, written to the todo-writing rules. Findings come after it; when nothing needs the operator, the list says so |
| 6 | Record and close | File each finding as its own issue, link it on the run, tick what was assessed, then bob ritual close <N> |
The brief is the same for every ritual and every drill; only the purpose, the
thing walked in step 2 and the run number change. It is built by run from
the vocabulary, so a change to it reaches every open run. The same lines are
written into the run issue’s How the session runs section when the run is
opened, and each checklist runbook’s Holding the run section restates them
for its own list.
| Flag | Meaning |
|---|---|
<ritual> |
required; a vocabulary name |
--project <p> |
the project (default: the one holding the current directory) |
--drill <name> |
a BoB-level drill — restore, lost-laptop, farm-compromise — resumed or opened on its home; the printed line names the drill and machine and points at the runbook (Drills) |
--drill-kind <kind> |
a project’s own drill: required for drill without --drill, refused otherwise |
--machine <id> |
the machines.json id the drill rehearses — required by lost-laptop (its run body embeds that machine’s revocation list) |
--repo owner/name |
the repo holding the runs (default: the checkout’s github.com origin) |
--no-kit |
resume or open the run without applying the overlay |
--dry-run |
open nothing and apply nothing — report the run it would resume, or that it would open one |
--json |
emit bob-ritual-run/1 |
--root DIR |
read registry/, schemas/ and provisions/ from another checkout |
Exit codes: 0 started, resumed, or --dry-run · 2 usage or config
error (unknown ritual or project, a kindless drill, no checkout here without
--no-kit) · 3 refused — the machine’s role forbids the kit · 4 not
started — the forge could not be read (an open run cannot be ruled out),
refused the create, or spooled it to the write queue · 5 the run is open but
its kit overlay could not be applied (provision’s output is carried).
bob-ritual-run/1: verdict (started | resumed | dry-run |
refused | forge-unreadable | not-started), project, repo, ritual
(the key), kit, checklist ({name, version, items} or null, #2241), issue, url, opened, checkout, machine (id,
role, manifest, forbid, error), overlay (applied, skipped —
--no-kit | dry-run | null, exit, output), packet (prepared_at,
comment, collectors[] of {name, status, summary}, gaps; null when there
is none), packet_error, previous (date, issue, findings[] of {text, checked, issues, no_action}, error; null for a first run), brief (the
session brief as an array of lines; null unless the
verdict is started or resumed, #3611) and error.
Closing a run — bob ritual close
Section titled “Closing a run — bob ritual close”A review that ends in conversation evaporates. close only closes a run whose
outputs are durable (#2234):
bob ritual close 2372 --dry-run # check everything, print the closed body, write nothingbob ritual close 2372 --finding 2380 --journal 2381 # add a finding and a lesson, then closebob ritual close 2390 --no-action "lockfile already patched upstream"bob ritual close 2391 --no-findings # held, found nothing — recorded-
Findings. Every line of the run’s Findings checklist, plus each
--finding Nand--no-action "<why>", must either link an issue in the run’s repo labelledbug,req,task,securityortodo, or sayno-action. Anything else refuses the close:- an unlinked line;
- a missing issue or a pull request;
- an issue with none of those labels;
- an issue labelled
lessonordecision— that is a journal entry, so it is linked with--journal.
-
No findings. A run whose checklist is empty closes only with
--no-findings. The Findings section then says No findings with the day it was held, and the marker recordsclosed_with.--no-findingson a run that lists findings is refused. -
Journal. Lessons and decisions are filed as their own
/journalissues. They are linked under the run’s Journal section, from its existing links plus each--journal N. A linked issue must carrylessonordecisionand must not be a ritual run. A run that itself carrieslessonordecisionis refused, because a run is an execution artifact (#2062). -
Delta. The previous closed run of the ritual for the project is compared with this run, by the issues each run’s findings link:
Bucket Holds Resolved since the previous run its findings that are now closed and not found again Recurring its findings still open, or found again this run New this run this run’s findings the previous run did not have, plus this run’s no-action lines A first run says so, and every finding in it is new. A portfolio run is also checked against its Portfolio decisions table here.
-
Outcome (a drill run, #2240). A run whose marker names a drill or a machine — or whose body carries an Outcome section — closes only with the outcome recorded: Time to recover (or
tabletop), Steps that failed and Undeclared state discovered must be filled, not the italic placeholders. A value in either of the last two that is notnoneis a gap, and a gap must be filed: closing with--no-findings, or with no finding that links an issue, is refused naming the gap. This is how a drill’s gaps become issues from the run. -
Checklist (a run with a declared checklist, #2241). A run whose body carries a Checklist section with items closes only when at least one is ticked — nothing ticked means the ritual was not held (
checklist). Every unticked item is recorded as not assessed, with the reason given after— not assessed:when there is one; nothing is refiled or re-ticked. -
One write. Refusals are collected together and printed, and nothing is written until there are none. The Findings, Journal and Delta sections are rewritten, the marker gains
held_on(--held-onrestamps it) andclosed_with: {findings, no_action, journal}— plus, for a drill,outcome: {time_to_recover, steps_failed, undeclared_state, gaps}whiledrill,drill_kindandmachineare kept, and, for a checklist run,checklist: {name, version, items, assessed, not_assessed: [n…]}while the marker’s openingcheckliststamp is kept. The issue is then closed as completed in the same PATCH. -
Restore the kit. Once the run is closed, the kit overlay
bob ritual runapplied is restored in the project’s checkout on this machine (bob provision overlay restore). Only an overlay stamped with this run is restored; the close says why it left one alone:Overlay Result none, or no checkout here nothing to restore a kit stamped with another run, or an unstamped kit applied by hand left as is the run’s kit together with a flight-plan source left for that warp run to restore unreadable state left as is — restore it by hand A refused close restores nothing, since the session is not over.
--dry-runreports whether the overlay would be restored.
| Flag | Meaning |
|---|---|
<run-issue> |
required; the ritual-run issue number (# optional) |
--project <p> |
the project (default: the run marker’s, else the one holding the current directory) |
--repo owner/name |
the repo holding the run (default: the checkout’s github.com origin) |
--finding N |
a finding issue to add to the checklist (repeatable) |
--no-action "<why>" |
a finding that needs nothing, with its reason (repeatable) |
--journal N |
a /journal lesson or decision issue to link (repeatable) |
--no-findings |
close a run with an empty checklist, recording that it found nothing |
--held-on YYYY-MM-DD |
the day the ritual was held (default: the marker’s) |
--dry-run |
run every check and print the closed body; write nothing |
--json |
emit bob-ritual-close/1 |
--root DIR |
read registry/, schemas/ and provisions/ from another checkout |
Exit codes: 0 closed, queued, or --dry-run · 2 usage or config error
(not a ritual-run issue, a project mismatch, a bad --finding/--journal
number, an empty --no-action) · 3 refused (each problem named, the run left
open) or already closed · 4 the forge could not be read — the run, a linked
issue, the run history or the previous run — or it refused the write · 5
closed, but the kit overlay stamped with the run could not be restored
(restore’s output is carried).
bob-ritual-close/1: verdict (closed | queued | dry-run |
refused | already-closed | forge-unreadable | not-closed), project,
repo, issue, url, ritual, held_on, findings[] (text, issues,
no_action, status filed | no-action | problem, reason),
journal[] (issue, kind, title, status, reason), no_findings,
decisions (a portfolio run: [{project, was, decision, changed, journal, status, reason}]; null otherwise), outcome (a drill run: {present, rows, filled, unfilled[], gaps[]} from its Outcome table; null otherwise),
checklist (a checklist run: {name, version, items, assessed, not_assessed: [{n, text, reason}]}; null otherwise, #2241),
delta (previous {date, issue} or null, resolved, recurring, new,
new_no_action, and checklist — this run’s {name, version, items, assessed, not_assessed: [n…]} with the previous run’s record as previous, or null),
problems[] (kind finding | journal |
knowledge-label | no-findings | portfolio-decision | outcome | checklist, message), overlay (restored,
skipped, exit, output, ritual_run, and would_restore on a dry run;
null when the close wrote nothing), body and error.
What is due — ritual.js due
Section titled “What is due — ritual.js due”node scripts/rituals/ritual.js due # the project holding the current directorynode scripts/rituals/ritual.js due --project bigbrainnode scripts/rituals/ritual.js due --all --json # every active project manifest| Flag | Meaning |
|---|---|
--project <p> |
one project, by _meta.project or manifest file name |
--all |
every provisions/*.json except archived projects and the _-prefixed templates and global manifests |
| (neither) | the project whose _meta.path contains the current directory (deepest match) |
--json |
emit bob-rituals-due/1 |
--now YYYY-MM-DD |
grade as of this date (default today, UTC) |
--window <duration> |
how far ahead counts as due (default P14D) |
--repo owner/name |
read ritual-run issues from this repo instead of the checkout’s origin (one project only — refused with --all) |
--runs FILE |
last-run dates, {"<project>": {"<ritual>": "YYYY-MM-DD"}} with a project drill keyed drill:<kind> and a BoB-level drill drill:<name> — replaces the forge read entirely (offline use, fixtures); a project the file omits has never run, and no guide walkthrough is listed |
--snapshots DIR |
read snapshots from here instead |
--root DIR |
read registry/, schemas/ and provisions/ from another checkout |
--last-release YYYY-MM-DD |
the date of the project’s last stable release, for the release gate, instead of the newest v* tag in its checkout (one project only — refused with --all) |
Each ritual is graded:
| Status | When |
|---|---|
overdue |
its next due date (last run + cadence) has passed — days_overdue says by how much |
due |
it has never run, or its next due date falls inside the window; a guide walkthrough whose requirement still has its criterion unticked (trigger names it) |
unknown |
its run history could not be read (no repo, or the forge is unreadable) — never counted as overdue; an event cadence grades unknown too, with trigger set; a guide walkthrough whose requirement could not be read, with reason set |
upcoming |
its next due date is beyond the window, or its cadence is an event (before-stable-release, with trigger set and next_due null) |
Rituals are listed most urgent first. A month cadence clamps at the month end (a run on 31 January is next due 28 February).
Exit codes: 0 nothing overdue · 1 at least one ritual overdue · 2 a
usage or config error — an unknown project, --project with --all, a bad
--now/--window/--runs, an unreadable vocabulary, or a _meta.rituals
that check-rituals refuses. With --json an error is
{"schema": "bob-rituals-due/1", "verdict": "error", "error": "…"}.
bob-rituals-due/1: now, window, verdict (ok | overdue),
summary (overdue, due, unknown, upcoming counts, and held — the
projects whose stable promotion is held) and projects[] —
each with project, file, archived, source (declared |
default:active | default:dormant), liveness (state, source,
last_activity, warp_active; null for a declared schedule), run_history
(source issues | runs-file, repo, known, reason; null when archived),
rituals[] (key, name, drill (a BoB-level drill’s name, else null), drill_kind, cadence, kit,
farm_eligible, scope (project | portfolio | drill), gates, last_run, last_run_issue,
open_run (the ritual’s open run issue, or null), next_due, trigger,
days_overdue, status) and release_gate (below; null when archived).
The release gate
Section titled “The release gate”Security review should keep pace with stable releases rather than drift behind them (#2236). A project can therefore make a ritual hold its stable promotion:
- Declare what the ritual holds —
"gates": ["stable-release"]on its_meta.ritualsentry (any cadence;before-stable-releaseis the natural one). - Opt the project in —
_workflow.ritual_release_gate: truein its.claude/settings.local.json(workflow-config.md). The gate is off by default: with it off,duestill reports what would hold, and nothing refuses — exceptbob release promote --auto, the farm tick’s unattended promotion, which refuses (exit 3) while the gate is off or its setting cannot be read, naming_workflow.ritual_release_gate(#3627). Auto-promotion never runs past an overdue security review because the gate happened to be off; a human can still promote by hand withbob release promote.
ritual.js due grades each project’s release_gate:
| Field | Meaning |
|---|---|
enabled |
the resolved _workflow.ritual_release_gate (false when the project has no checkout on this machine) |
rituals |
the keys declared with gates: ["stable-release"] |
last_release |
{tag, date} — the newest v* tag in the project’s checkout and its commit date, or --last-release (tag null), or null when neither is known |
holding |
every gating ritual that would hold the promotion: key, status, reason, last_run, last_run_issue, open_run, next_due, days_overdue |
held |
enabled and holding is not empty — the promotion is held |
A gating ritual holds when:
| Cadence | Holds while |
|---|---|
before-stable-release |
it has never been held, or its last run is before last_release.date — not held since the last stable release. With no release known and a run on record it does not hold. |
a duration (P1M, …) |
it is overdue — past its next due date; merely due does not hold |
| any | its run history is unknown — the gate never promotes blind |
A held gate is informational here: due still exits 1 only for an overdue
ritual. The human line reads release gate: HELD — <ritual> (<reason>; open run #N),
release gate: off (_workflow.ritual_release_gate) — would hold: …, or
release gate: clear — …, each ending with the last stable release it judged
against. bob release promote reads the same document and refuses a held
promotion; the farm digest lists it (see versioning.md
and farm-tick.md).
Where due rituals surface
Section titled “Where due rituals surface”A schedule nobody sees lapses silently, so every place that reads ritual.js due
reads the same bob-rituals-due/1 grading (#2232):
| Surface | What it shows |
|---|---|
Fleet snapshot (audit.js snapshot) |
projects[].rituals — {overdue, due, upcoming, unknown, overdue_keys, due_keys} — and summary.rituals; see fleet-snapshot-timer.md |
audit.js view |
a Rituals table per machine plus a FLEET row that counts each project once |
bob status --scope project |
an informational rituals section (this document) and rituals_overdue; see bob-status.md |
bob status --scope fleet |
an informational rituals section (the fleet roll-up) and rituals_overdue |
bob tick digest |
rituals: {prepared, overdue, due, unknown, error, prepare} — the rituals whose evidence packet the tick posted ([{project, ritual, issue, opened, verdict, gaps}], from prepare --due, #2233), what it would prepare or could not, in prepare (ran, reason, budget_seconds, would_prepare, failed), and every ritual still overdue ([{project, ritual, next_due, days_overdue, kit}]); a Rituals row in the markdown and rituals overdue N on the bob notify line; see farm-tick.md |
/what-next |
overdue rituals in a block above the ranked work |
/what-next --report |
a Rituals block: overdue, plus the due rituals of the next 14 days |
/groom |
Step 5h — one rituals: <project> — N overdue: … line per project, report-only |
bob release promote |
reads the project’s release_gate and holds the stable promotion (exit 4) while it is held; the tick digest’s rituals.held lists what was held (The release gate, versioning.md) |
Overdue rituals are informational everywhere but one: they never move a health
verdict or an exit code, except that a project which opted into the release gate
has its stable promotion held (bob release promote exit 4) while a gating
ritual is overdue.
The gate
Section titled “The gate”node scripts/checks/check-rituals.js [--json] [--root DIR] — exit 0 clean,
1 violations, 2 a file it needs is unreadable or a usage error. --json
emits bob-rituals-check/1: verdict, rituals (the vocabulary entries),
declaring (the manifests with _meta.rituals) and problems.
| Problem | Meaning |
|---|---|
schema |
registry/rituals.json does not validate against the schema |
duplicate-ritual |
the vocabulary file describes a ritual twice |
missing-ritual |
a name the schema enumerates has no entry in registry/rituals.json |
unknown-kit |
a vocabulary entry or a declaration names a kit registry/kits.json does not declare |
no-evidence |
a farm-eligible vocabulary entry declares no evidence collectors (an unknown or repeated collector is a schema problem) |
declaration-schema |
a _meta.rituals entry names an unknown ritual, has an invalid cadence, lacks a cadence, carries an unknown key, or has a gates list that is empty, repeats a gate or names one the schema does not enumerate |
drill-kind |
a drill with no drill_kind, or a drill_kind on a ritual that is not a drill |
duplicate-declaration |
a project declares the same ritual (for a drill, the same kind) twice |
portfolio-declared |
a project declares a ritual held once for the portfolio (portfolio-review) |
unknown-home |
a portfolio ritual’s portfolio.home names no project manifest |
portfolio-defaults |
a portfolio ritual carries liveness defaults — its cadence is portfolio.cadence |
portfolio-status |
a park or archive project declares something other than the dormant defaults (a subset written before a ritual joined the vocabulary passes), an archive project is not active: false, or an inactive project declares another status (Portfolio status) |
drills-on-non-drill |
a vocabulary entry other than drill carries a drills list (Drills) |
duplicate-drill |
the drill entry declares the same BoB-level drill name twice |
unknown-runbook |
a BoB-level drill’s runbook is not a file in registry/runbooks/ (skipped when the directory is unreadable) |
drill-name |
a declaration gives a ritual that is not a drill a drill name |
unknown-drill |
a declaration’s drill names no BoB-level drill |
drill-declared |
a project other than the drill’s home declares a BoB-level drill, or the home declares a plain drill whose kind is a BoB-level drill’s key |
checklist-on-drill |
a declaration gives a drill a checklist — a drill’s checklist is its runbook’s timed one (Checklists, #2241) |
unknown-checklist |
a declaration’s checklist is not a file in registry/runbooks/ (skipped when the directory is unreadable) |
Tests: tests/test-rituals.js (make test-rituals).
Related
Section titled “Related”- Hold a Ritual — the task, step by step: due → run → prepare → close → decide
- Tracker labels —
ritual-runamong the execution artifacts - Session Kits — the kit each ritual applies; the
incidentkit carries the drill runbooks, thesecuritykit the attack-surface checklist - Credential Inventory — what the lost-laptop drill’s revocation list is generated from
- Verification System —
check-ritualsinmake check - Capability #2222 — recurring rituals