# Runbook pack specification v0.2 Status: **adopted 2026-09-29** (Bernd's review answers are in the Decisions section). Derived from `docs/ux-review-2026-09-29.md`. v0.1 stays valid; v0.2 is a superset. Machine-checkable form: `rtut/schema/runbook-pack-v0.2.schema.json` plus the semantic checks in `rtut/v2.py`. The engine and UI do not run v0.2 packs yet (RBT-WP-0007). ## Principle The spec describes *what the procedure is*: structure, data, actors, gates and order. It carries no presentation. Rendering (layout, theme, dark mode, the questionnaire and the dimension view) is the renderer's job and must follow from the semantics alone. If a UI feature needs a new spec field, that field has to be justified as meaning, not appearance. ## The model ```text pack |- framing what to keep in mind (shown first) |- actors who does or approves what, with privileges |- datapoints the non-secret facts collected and applied -> state nil/set/err/ok |- stages hierarchy of the procedure | |- steps the work -> state todo/running/done/failed | |- gates QA checks tied to the stage and to goals -> state open/passed/failed/waived |- artefacts what a run produces and where it lives (non-secret references) ``` ### Two states, two things | Thing | States | Meaning | | --- | --- | --- | | **Datapoint** (parameter, role assignment, artefact location, custody reference) | `nil` no value, `set` value given, `err` invalid or rejected, `ok` confirmed in the real system | Is the data known and right? | | **Step** | `todo`, `running`, `done`, `failed` (`skipped` for read-only) | Has it been applied? | | **Gate** | `open`, `passed`, `failed`, `waived` | Is the quality condition met? | A datapoint becomes `ok` when the step or gate named in its `confirmed_by` passes. A gate must not require a datapoint (`datapoints_ok`) that it is itself `confirmed_by`: that is circular and the validator rejects it. A v0.1 `parameter` is a datapoint that has a value type and is asked as a question. Steps `uses:` datapoints; steps may `produces:` datapoints (for example an artefact location). ### Hierarchy and order - `stages` nest to at most three levels. A stage has `title`, `goal`, `steps`, and optional child `stages`. - Every step and stage may declare `requires:` (ids of steps, stages, gates, or datapoints in state `ok`). This forms a dependency graph, so alternatives and parallel work are expressible. `one_of:` groups declare alternatives: any one satisfies the group. - **Canonical serialization.** The order in which the pack lists things is the recommended order. The engine computes: *ready* = every requirement met; *recommended next* = the first ready item in canonical order. Operators may take any other ready item. A pack that lists no `requires` behaves as a linear sequence, which is exactly a v0.1 pack. ### Gates A gate is a QA condition, not a step. ```yaml gates: - id: custody-approved title: Custody strategy approved stage: custody # exit gate of this stage goal: platform-root-custody # quality objective; canon standard id where one exists blocking: true # blocks dependents and the stage exit checks: # same check shape as step verify - {done_when: "signed roster verifies", command: "...", expect: exit-0} - {datapoints_ok: [custody_mode, roster_location]} waivable: {by: king, needs_reason: true} # omit if never waivable ``` - A gate is tied to exactly one stage (its exit or entry) and names the quality goal it protects, so gates are traceable to standards instead of floating. - `requires: [gate:custody-approved]` makes a step wait for a gate. - A waiver records who, when and why, and appears in the overview. ### Actors, roles and privileges ```yaml actors: - id: king role: Platform-root custodian description: Holds platform-root recovery authority. privileges: [approve-custody, revoke-root-token] ``` Steps and gates name an `actor` (who acts or approves). `owner` stays the repository or tool that does the work. The pack thereby explains what is relevant and who is responsible; the overview can group by actor. ### Datapoints and dimensions ```yaml datapoints: - id: custody_mode label: Custody mode type: enum values: [temporary-single-king, two-of-three-ready] dimensions: {actor: king, subsystem: openbao, kind: decision} ask_in: custody # stage in which the questionnaire asks for it ``` `dimensions` are free key/value tags, using canon vocabulary where it exists (actor, role, privilege, subsystem, kind, assurance goal). They drive the **dimension view**: a random-access table of all datapoints grouped or filtered by any dimension, editable independently of the flow. Secrets are never datapoints; only references ("stored in the platform-root safe entry") are. ### Artefacts `artefacts:` lists what a run yields, each with a `location` datapoint (a reference, never a value) and the `produced_by` step. The artefacts view is what makes a run more than a script. ### Confirmation Each step declares `reversible: true|false` (default `true`). - reversible: a confirm button; - not reversible: the operator types the pack's `confirm_phrase` (default `I understand this cannot be undone`; a step may set its own). This replaces v0.1's `risk` for the purpose of confirmation. v0.1 `risk` values map as: `destructive` gives `reversible: false`, all others `true`. ### Framing `threat_checks` becomes `framing:` (list of statements, optionally per stage): things to keep in mind, shown before the work starts. They are not gates. v0.1 `threat_checks` load as pack-level framing. ## Compatibility - The engine loads `runbook-pack/v0.1` and `v0.2`. A v0.1 pack is interpreted as one stage, a linear `requires` chain, no gates, no actors, its parameters as datapoints. - `engine: legacy-command` is unchanged. - The run record gains `datapoints`, `gates` and `waivers` sections (`runbook-run/v0.2`); v0.1 records still load. ## What this is not - No layout, color, icon or copy-text fields. - No embedded scripting, and no execution mode (still RBT-WP-0006). - No secret values anywhere. ## Operating modes, overrides and deviations A **run** has a mode. The mode belongs to the run, not to the pack. | Mode | Who confirms | Checks, gates, preconditions | | --- | --- | --- | | **manual** | the operator | shown with their state; the operator confirms each | | **auto-confirm** | the engine, when every precondition is met | evaluated by the engine; confirmation is applied automatically | ### Manual mode: override If preconditions are not met the operator may **override** and continue anyway. Doing so must (1) show a dialog that names the unmet preconditions and the downstream risk (`downstream_risk` text declared on the step or gate, else a generic warning), (2) require an explicit confirmation, and (3) record a **deviation** in the run. ### Auto-confirm mode: timeout and push-through - A step or gate may declare `timeout: {seconds: N, on_timeout: stop|push-through}`; a run may set a default. Without a timeout, auto-confirm waits. - `stop` (default): on timeout the run stops and reports. - `push-through`: on timeout the engine confirms the item, records a deviation, and moves on, ignoring the problem. This gathers the most information about problems in one run and reduces refinement runs. - Irreversible steps are never confirmed automatically unless the run was started with an explicit, recorded pre-authorization naming those steps (and the pack's `confirm_phrase`). Push-through does not bypass that rule. This is a safety default, open to review. - A step may declare `auto_confirm: manual-only` to forbid automation entirely. - Auto-confirm applies *confirmation* only. It does not run a step's own command; execution is a later, separate level (RBT-WP-0006). ### Deviations An item passed over by push-through gets the distinct state `pushed` (steps, gates and datapoints): not done, not set, not an error. Every override, waiver, push-through and change of a value that steps already used (`changed-after-use`) is a **deviation** in the run record: `kind` (`override|waiver|push-through`), `item` (step or gate id), `mode`, `unmet` (what was not satisfied), `risk_acknowledged`, `reason` (required for overrides and waivers; automatic text for push-through), `who`, `at`. A run with deviations is marked so in the overview and never counts towards maturity. A **waiver** is declared in advance (`waivable` on a gate); an **override** is not. ## Decisions (2026-09-29, Bernd) 1. **Waivers:** allowed only where a gate declares `waivable`, always with a reason. Undeclared departures are overrides (above), also recorded. 2. **Gate checks:** a gate may run a command (operator-confirmed in manual mode) or read datapoint states and step results. 3. **Dimension vocabulary:** free tags first; a shared registry may be promoted to canon. 4. **Nesting:** at most three levels. If procedures grow beyond that, dependencies between runbooks are needed (see below), not deeper nesting. 5. **Datapoint `ok`:** reached by a check, or by a recorded manual confirmation. Handling of missing preconditions is defined by the operating modes above. ## Later: dependencies between runbooks **Idempotency** is a declared characteristic of a step: `idempotent: idempotent | non-idempotent | conditional` (`conditional` needs an `idempotent_note`). Undeclared means unknown. The runbook's overall value is derived. See `docs/maturity-and-projects.md`. Not in v0.2. When a procedure needs another runbook (for example "OpenBao operating path requires the SSH access pack"), packs will declare `requires_packs` with a required result (a maturity level, or a named gate passed). Tracked in RBT-WP-0006. ## Recommendation (carried out) Keep `runbook-pack/v0.1` as shipped (three NetKingdom packs use it). Use the old console's seven runbooks and 14 gates as the first real v0.2 test.