Skip to main content

Runbook types

A runbook is an ordered set of phases; each phase is an ordered set of steps; each step records the UI target it acted on, the captures that prove the result, and the outcome.

The model is capture-first: a step with no evidence is expressible, but it cannot honestly claim CONFIRMED confidence.

Step​

export type StepMechanism =
| 'browser'
| 'azure-cli'
| 'rest'
| 'powershell'
| 'manual';

export interface StepTarget {
testId?: string;
semanticId?: string;
role?: string;
name?: string;
}

export interface RunbookStep extends Evidenced {
ordinal: number;
title: string;
mechanism: StepMechanism;
target?: StepTarget;
captures: string[];
outcome: OutcomeStatus;
constraint?: string;
}

mechanism records how a step was actually carried out, and it is the field that makes the project's central claim checkable. The build was UI-only - except where it demonstrably was not. Every step is browser apart from Phase 3, where Foundry IQ provisioning required azure-cli and rest. Recording the mechanism per step turns "UI-only, except Phase 3" from a caveat in prose into a queryable property:

phases.flatMap(p => p.steps).filter(s => s.mechanism !== 'browser');

target is optional and holds all four ways to address an element. A step can carry a raw data-testid, a normalized coral.* semantic id, or an ARIA role plus accessible name - or several at once. Both addressing styles are kept because they fail differently: test ids are stable across copy changes but ambiguous for 76 of 177 ids, while role-plus-name is unambiguous but drifts with wording.

captures is a required array, not optional. An empty array is the honest representation of an unevidenced step, and requiring the field forces that to be stated rather than omitted.

constraint carries the gotcha a step surfaced - the 100 KB icon ceiling, the Basic-tier floor, the published-only rule. These are the sentences a reader actually needs, so they are a first-class field rather than prose buried in the step title.

Phase​

export interface RunbookPhase extends Evidenced {
number: number;
title: string;
summary: string;
outcome: OutcomeStatus;
steps: RunbookStep[];
blockedReason?: string;
}

blockedReason is populated when outcome is BLOCKED or PARTIAL. The pairing is a documented invariant rather than a structural one; a discriminated union on outcome would make the five-variant type awkward for the three outcomes that never carry a reason.

OutcomeStatus​

export type OutcomeStatus =
| 'DONE'
| 'PARTIAL'
| 'BLOCKED'
| 'FAILED'
| 'NOT-ATTEMPTED';

Five values, and the distinctions among the last four are the point:

ValueMeaning
DONECompleted as intended.
PARTIALCompleted, with a material caveat. Phase 5: published and grounded, but the evaluation scored zero.
BLOCKEDCould not complete due to an external condition. Phase 6: tenant consent never granted.
FAILEDAttempted and did not work, with no external blocker to point at.
NOT-ATTEMPTEDNever tried.

A three-value success/failure/skipped enum would have collapsed Phase 5 and Phase 6 into the same bucket. They are not the same: Phase 5 produced a working agent with one broken measurement, while Phase 6 produced nothing and could not have, regardless of maker effort. BLOCKED versus FAILED carries the further distinction of whether anyone at the maker level could have done something differently.

RAYBOT_PHASES​

export const RAYBOT_PHASES: readonly {
number: number;
id: string;
title: string;
outcome: OutcomeStatus;
summary: string;
}[];

The canonical seven-phase index:

#idTitleOutcome
1buildBuildDONE
2right-railRight rail configurationDONE
3foundry-iqFoundry IQ knowledge baseDONE
4settingsSettingsDONE
5publish-evaluatePublish, Preview, Evaluate, MonitorPARTIAL
6teamsTeams channelBLOCKED
7reauditRuntime re-auditDONE

Note the shape: it is an inline type carrying number, id, title, outcome, and summary - but not steps. It is deliberately not RunbookPhase[].

The step detail is narrative. It changes as the product changes, it is long, and it is already written as prose in the runbook pages. Duplicating it into a constant would create two sources of truth that drift. The constant carries the index - the part that is stable, short, and needed programmatically for navigation and status rollups. RunbookPhase and RunbookStep remain available for consumers modelling their own runbooks.

id is the slug used in the documentation route, so the constant doubles as a navigation model: phase 3 is /docs/runbook/phase-3-foundry-iq.

Runbook​

export interface Runbook extends Evidenced {
title: string;
validatedOn: IsoDate;
phases: RunbookPhase[];
}

validatedOn is the date the content was last checked end to end, not the date it was written. A runbook that documents a moving product is only as trustworthy as its last validation, and dating that separately from authorship makes staleness visible.