@formigio/fazemos-cli 0.10.23 → 0.10.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/budget.d.ts +37 -0
- package/dist/budget.js +530 -0
- package/dist/budget.js.map +1 -0
- package/dist/index.js +88 -0
- package/dist/index.js.map +1 -1
- package/dist/runbook/checks.d.ts +78 -0
- package/dist/runbook/checks.js +404 -0
- package/dist/runbook/checks.js.map +1 -0
- package/dist/runbook/schema.d.ts +153 -0
- package/dist/runbook/schema.js +136 -0
- package/dist/runbook/schema.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { Finding } from '../yaml/load.js';
|
|
2
|
+
/**
|
|
3
|
+
* Split a markdown step-doc into its YAML frontmatter block + body, then
|
|
4
|
+
* js-yaml-parse the block. The CLI already has a regex splitter for the
|
|
5
|
+
* `name:`-only agent-upload path (index.ts:7335) and vendors js-yaml — but it
|
|
6
|
+
* has no full-block parse + Runbook-shape awareness. This extends the splitter
|
|
7
|
+
* pattern (`^---\n…\n---`) and js-yaml-parses the whole block (F39 §9.1).
|
|
8
|
+
*/
|
|
9
|
+
export interface ParsedFrontmatter {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
frontmatter?: Record<string, unknown>;
|
|
12
|
+
findings: Finding[];
|
|
13
|
+
}
|
|
14
|
+
export declare function parseFrontmatter(raw: string, source: string): ParsedFrontmatter;
|
|
15
|
+
/**
|
|
16
|
+
* Walk up from `startDir` looking for the canonical schema at
|
|
17
|
+
* `projects/fazemos/runbooks/runbook.schema.json`. Returns the parsed JSON +
|
|
18
|
+
* its path, or null if not found within the clone (then the vendored copy is
|
|
19
|
+
* used as the fallback cache — F39 §9.2 lockstep decision).
|
|
20
|
+
*/
|
|
21
|
+
export declare function findCanonicalSchema(startDir: string): {
|
|
22
|
+
schema: any;
|
|
23
|
+
path: string;
|
|
24
|
+
} | null;
|
|
25
|
+
export interface FlipStateStep {
|
|
26
|
+
step_name: string;
|
|
27
|
+
runbook?: string;
|
|
28
|
+
step_type?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface FlipStateTemplate {
|
|
31
|
+
template: string;
|
|
32
|
+
template_id?: string;
|
|
33
|
+
flipped: boolean;
|
|
34
|
+
flipped_at?: string | null;
|
|
35
|
+
steps?: FlipStateStep[];
|
|
36
|
+
}
|
|
37
|
+
export interface FlipState {
|
|
38
|
+
ledger_version?: string;
|
|
39
|
+
templates: FlipStateTemplate[];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Walk up from `startDir` for `projects/fazemos/runbooks/flip-state.json`.
|
|
43
|
+
* Returns { ledger } or { error } so the caller can emit
|
|
44
|
+
* `runbook.flip_state_unreadable` when the ledger can't be parsed (F39 §9.3).
|
|
45
|
+
*/
|
|
46
|
+
export declare function loadFlipState(startDir: string): {
|
|
47
|
+
ledger?: FlipState;
|
|
48
|
+
path?: string;
|
|
49
|
+
error?: string;
|
|
50
|
+
};
|
|
51
|
+
/** Find the flip-state entry whose `steps[].step_name` matches a step (by file basename). */
|
|
52
|
+
export declare function flipStateForStep(ledger: FlipState | undefined, stepName: string): {
|
|
53
|
+
template: FlipStateTemplate;
|
|
54
|
+
step: FlipStateStep;
|
|
55
|
+
} | null;
|
|
56
|
+
export interface ValidateOptions {
|
|
57
|
+
/** Directory to anchor canonical-schema + flip-state lookups. Defaults to dirname(filePath). */
|
|
58
|
+
startDir?: string;
|
|
59
|
+
/** Pre-loaded flip-state ledger (so a directory run loads it once). */
|
|
60
|
+
flipState?: FlipState;
|
|
61
|
+
/** Whether a ledger was attempted but unreadable (drives flip_state_unreadable). */
|
|
62
|
+
flipStateError?: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Validate a single Runbook step-doc's frontmatter against the frozen schema +
|
|
66
|
+
* the per-template flip-state invariant. Pure: returns findings, never throws.
|
|
67
|
+
*/
|
|
68
|
+
export declare function validateRunbookDoc(raw: string, filePath: string, opts?: ValidateOptions): Finding[];
|
|
69
|
+
export interface RunbookValidateResult {
|
|
70
|
+
source: string;
|
|
71
|
+
findings: Finding[];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Validate a single file or a directory of `.md` Runbook docs. Loads the
|
|
75
|
+
* flip-state ledger once and cross-checks `runbook` id uniqueness across the
|
|
76
|
+
* validated set (F39 §9.2 #4).
|
|
77
|
+
*/
|
|
78
|
+
export declare function validatePath(target: string): RunbookValidateResult[];
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import { readFileSync, existsSync, statSync, readdirSync } from 'fs';
|
|
2
|
+
import { dirname, join, basename, resolve as resolvePath } from 'path';
|
|
3
|
+
import yaml from 'js-yaml';
|
|
4
|
+
import * as AjvNs from 'ajv';
|
|
5
|
+
import { runbookSchema, RUNBOOK_SCHEMA_VERSIONS } from './schema.js';
|
|
6
|
+
// ajv 8.x ships as CJS; in ESM contexts the constructor lands on the namespace's
|
|
7
|
+
// `.default` property. Resolve at runtime so `new Ajv(...)` works under Node ESM.
|
|
8
|
+
const Ajv = AjvNs.default ?? AjvNs;
|
|
9
|
+
export function parseFrontmatter(raw, source) {
|
|
10
|
+
const findings = [];
|
|
11
|
+
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
12
|
+
if (!match) {
|
|
13
|
+
findings.push({
|
|
14
|
+
severity: 'error',
|
|
15
|
+
rule: 'runbook.no_frontmatter',
|
|
16
|
+
message: 'No YAML frontmatter block found (expected a leading `---` … `---` fence).',
|
|
17
|
+
path: source,
|
|
18
|
+
});
|
|
19
|
+
return { ok: false, findings };
|
|
20
|
+
}
|
|
21
|
+
let parsed;
|
|
22
|
+
try {
|
|
23
|
+
parsed = yaml.load(match[1], { filename: source });
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
const finding = {
|
|
27
|
+
severity: 'error',
|
|
28
|
+
rule: 'runbook.frontmatter_parse_error',
|
|
29
|
+
message: err?.reason || err?.message || String(err),
|
|
30
|
+
path: source,
|
|
31
|
+
};
|
|
32
|
+
if (err?.mark && typeof err.mark === 'object') {
|
|
33
|
+
finding.line = (err.mark.line ?? 0) + 1;
|
|
34
|
+
finding.column = (err.mark.column ?? 0) + 1;
|
|
35
|
+
}
|
|
36
|
+
findings.push(finding);
|
|
37
|
+
return { ok: false, findings };
|
|
38
|
+
}
|
|
39
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
40
|
+
findings.push({
|
|
41
|
+
severity: 'error',
|
|
42
|
+
rule: 'runbook.shape',
|
|
43
|
+
message: 'Runbook frontmatter must be a YAML mapping (object).',
|
|
44
|
+
path: source,
|
|
45
|
+
});
|
|
46
|
+
return { ok: false, findings };
|
|
47
|
+
}
|
|
48
|
+
return { ok: true, frontmatter: parsed, findings };
|
|
49
|
+
}
|
|
50
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
51
|
+
// Canonical schema resolution (canonical wins; vendored is the fallback cache)
|
|
52
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
53
|
+
/**
|
|
54
|
+
* Walk up from `startDir` looking for the canonical schema at
|
|
55
|
+
* `projects/fazemos/runbooks/runbook.schema.json`. Returns the parsed JSON +
|
|
56
|
+
* its path, or null if not found within the clone (then the vendored copy is
|
|
57
|
+
* used as the fallback cache — F39 §9.2 lockstep decision).
|
|
58
|
+
*/
|
|
59
|
+
export function findCanonicalSchema(startDir) {
|
|
60
|
+
let dir = resolvePath(startDir);
|
|
61
|
+
// Cap the walk to avoid an unbounded climb on odd filesystems.
|
|
62
|
+
for (let i = 0; i < 40; i++) {
|
|
63
|
+
const candidate = join(dir, 'projects', 'fazemos', 'runbooks', 'runbook.schema.json');
|
|
64
|
+
if (existsSync(candidate) && statSync(candidate).isFile()) {
|
|
65
|
+
try {
|
|
66
|
+
return { schema: JSON.parse(readFileSync(candidate, 'utf-8')), path: candidate };
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return null; // malformed canonical file → fall back to vendored
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const parent = dirname(dir);
|
|
73
|
+
if (parent === dir)
|
|
74
|
+
break;
|
|
75
|
+
dir = parent;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Walk up from `startDir` for `projects/fazemos/runbooks/flip-state.json`.
|
|
81
|
+
* Returns { ledger } or { error } so the caller can emit
|
|
82
|
+
* `runbook.flip_state_unreadable` when the ledger can't be parsed (F39 §9.3).
|
|
83
|
+
*/
|
|
84
|
+
export function loadFlipState(startDir) {
|
|
85
|
+
let dir = resolvePath(startDir);
|
|
86
|
+
for (let i = 0; i < 40; i++) {
|
|
87
|
+
const candidate = join(dir, 'projects', 'fazemos', 'runbooks', 'flip-state.json');
|
|
88
|
+
if (existsSync(candidate) && statSync(candidate).isFile()) {
|
|
89
|
+
try {
|
|
90
|
+
const parsed = JSON.parse(readFileSync(candidate, 'utf-8'));
|
|
91
|
+
if (!parsed || typeof parsed !== 'object' || !Array.isArray(parsed.templates)) {
|
|
92
|
+
return { path: candidate, error: 'flip-state.json is missing a `templates` array.' };
|
|
93
|
+
}
|
|
94
|
+
return { ledger: parsed, path: candidate };
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
return { path: candidate, error: err?.message ?? String(err) };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const parent = dirname(dir);
|
|
101
|
+
if (parent === dir)
|
|
102
|
+
break;
|
|
103
|
+
dir = parent;
|
|
104
|
+
}
|
|
105
|
+
return {}; // not found — no ledger context available
|
|
106
|
+
}
|
|
107
|
+
/** Find the flip-state entry whose `steps[].step_name` matches a step (by file basename). */
|
|
108
|
+
export function flipStateForStep(ledger, stepName) {
|
|
109
|
+
if (!ledger)
|
|
110
|
+
return null;
|
|
111
|
+
for (const tmpl of ledger.templates) {
|
|
112
|
+
for (const step of tmpl.steps ?? []) {
|
|
113
|
+
if (step.step_name === stepName)
|
|
114
|
+
return { template: tmpl, step };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
120
|
+
// The binding/DAG fields that must NEVER leak into a Runbook doc (F39 §5.5, AC#9)
|
|
121
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
122
|
+
const BINDING_LEAK_FIELDS = [
|
|
123
|
+
'depends_on',
|
|
124
|
+
'upstream_step_ids',
|
|
125
|
+
'source_step_id',
|
|
126
|
+
'source_output_name',
|
|
127
|
+
'parallel_group',
|
|
128
|
+
'sort_order',
|
|
129
|
+
'step_type',
|
|
130
|
+
'template',
|
|
131
|
+
'cadence',
|
|
132
|
+
'triggered_by',
|
|
133
|
+
'absorbed_by',
|
|
134
|
+
];
|
|
135
|
+
// `reviewer`/`eval_max_turns` are legal ONLY inside `defaults`; a top-level
|
|
136
|
+
// occurrence is a binding-override leak.
|
|
137
|
+
const BINDING_LEAK_TOPLEVEL_ONLY = ['reviewer', 'eval_max_turns'];
|
|
138
|
+
const KEBAB_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
139
|
+
/**
|
|
140
|
+
* Validate a single Runbook step-doc's frontmatter against the frozen schema +
|
|
141
|
+
* the per-template flip-state invariant. Pure: returns findings, never throws.
|
|
142
|
+
*/
|
|
143
|
+
export function validateRunbookDoc(raw, filePath, opts = {}) {
|
|
144
|
+
const findings = [];
|
|
145
|
+
const startDir = opts.startDir ?? dirname(resolvePath(filePath));
|
|
146
|
+
const stepName = basename(filePath, '.md');
|
|
147
|
+
// 1. Parse frontmatter ---------------------------------------------------
|
|
148
|
+
const parsed = parseFrontmatter(raw, filePath);
|
|
149
|
+
findings.push(...parsed.findings);
|
|
150
|
+
if (!parsed.ok || !parsed.frontmatter)
|
|
151
|
+
return findings;
|
|
152
|
+
const fm = parsed.frontmatter;
|
|
153
|
+
// 2. Schema validation (canonical wins; vendored fallback) ---------------
|
|
154
|
+
const canonical = findCanonicalSchema(startDir);
|
|
155
|
+
const schema = canonical?.schema ?? runbookSchema;
|
|
156
|
+
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
157
|
+
const validate = ajv.compile(schema);
|
|
158
|
+
if (!validate(fm)) {
|
|
159
|
+
for (const err of validate.errors ?? []) {
|
|
160
|
+
findings.push(schemaErrorToFinding(err));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// 3. runbook_schema_version present + known ------------------------------
|
|
164
|
+
if (fm.runbook_schema_version === undefined) {
|
|
165
|
+
findings.push({
|
|
166
|
+
severity: 'error',
|
|
167
|
+
rule: 'runbook.version_missing',
|
|
168
|
+
message: 'runbook_schema_version is required. The Runbook contract is load-bearing for F40 — a missing version is a hard error. ' +
|
|
169
|
+
`Add \`runbook_schema_version: "${RUNBOOK_SCHEMA_VERSIONS[RUNBOOK_SCHEMA_VERSIONS.length - 1]}"\` at the top.`,
|
|
170
|
+
path: '(root)',
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
else if (typeof fm.runbook_schema_version === 'string' &&
|
|
174
|
+
!RUNBOOK_SCHEMA_VERSIONS.includes(fm.runbook_schema_version)) {
|
|
175
|
+
findings.push({
|
|
176
|
+
severity: 'warning',
|
|
177
|
+
rule: 'runbook.version_unknown',
|
|
178
|
+
message: `runbook_schema_version "${fm.runbook_schema_version}" is not in known set [${RUNBOOK_SCHEMA_VERSIONS.join(', ')}]. Validation continued.`,
|
|
179
|
+
path: 'runbook_schema_version',
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
// 4. description re-applies the F18 validateDescription contract ----------
|
|
183
|
+
checkDescription(fm, findings);
|
|
184
|
+
// 5. runbook id shape ----------------------------------------------------
|
|
185
|
+
if (typeof fm.runbook === 'string' && !KEBAB_RE.test(fm.runbook)) {
|
|
186
|
+
findings.push({
|
|
187
|
+
severity: 'error',
|
|
188
|
+
rule: 'runbook.id_shape',
|
|
189
|
+
message: `runbook id "${fm.runbook}" must be kebab-case (lowercase alphanumerics separated by single hyphens).`,
|
|
190
|
+
path: 'runbook',
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
// 6. No binding/DAG leak -------------------------------------------------
|
|
194
|
+
checkBindingLeak(fm, findings);
|
|
195
|
+
// 7. Flip-state invariant ------------------------------------------------
|
|
196
|
+
checkFlipStateInvariant(fm, stepName, opts, findings);
|
|
197
|
+
return findings;
|
|
198
|
+
}
|
|
199
|
+
function schemaErrorToFinding(err) {
|
|
200
|
+
const instancePath = err.instancePath || '(root)';
|
|
201
|
+
let msg = err.message ?? 'schema violation';
|
|
202
|
+
let rule = `schema.${err.keyword}`;
|
|
203
|
+
if (err.keyword === 'required') {
|
|
204
|
+
const missing = err.params?.missingProperty;
|
|
205
|
+
msg = missing ? `missing required property: ${missing}` : msg;
|
|
206
|
+
rule = 'schema.required';
|
|
207
|
+
}
|
|
208
|
+
else if (err.keyword === 'additionalProperties') {
|
|
209
|
+
msg = `unexpected property: ${err.params?.additionalProperty}`;
|
|
210
|
+
}
|
|
211
|
+
else if (err.keyword === 'enum') {
|
|
212
|
+
// Map to the more specific runbook.shape/schema.enum slug
|
|
213
|
+
msg = `${msg}: ${JSON.stringify(err.params?.allowedValues)}`;
|
|
214
|
+
rule = 'schema.enum';
|
|
215
|
+
}
|
|
216
|
+
else if (err.keyword === 'pattern') {
|
|
217
|
+
rule = 'schema.pattern';
|
|
218
|
+
}
|
|
219
|
+
else if (err.keyword === 'type') {
|
|
220
|
+
rule = 'schema.type';
|
|
221
|
+
}
|
|
222
|
+
return { severity: 'error', rule, message: msg, path: instancePath };
|
|
223
|
+
}
|
|
224
|
+
// Mirrors the F18 validateDescription codes: MISSING / EMPTY / MULTILINE.
|
|
225
|
+
function checkDescription(fm, findings) {
|
|
226
|
+
const value = fm.description;
|
|
227
|
+
if (value === undefined || value === null) {
|
|
228
|
+
findings.push({
|
|
229
|
+
severity: 'error',
|
|
230
|
+
rule: 'runbook.description_invalid',
|
|
231
|
+
message: 'description is MISSING. Step doc frontmatter requires a non-empty `description`.',
|
|
232
|
+
path: 'description',
|
|
233
|
+
});
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (typeof value !== 'string') {
|
|
237
|
+
findings.push({
|
|
238
|
+
severity: 'error',
|
|
239
|
+
rule: 'runbook.description_invalid',
|
|
240
|
+
message: 'description is MISSING: must be a string (single paragraph of prose).',
|
|
241
|
+
path: 'description',
|
|
242
|
+
});
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (value.trim() === '') {
|
|
246
|
+
findings.push({
|
|
247
|
+
severity: 'error',
|
|
248
|
+
rule: 'runbook.description_invalid',
|
|
249
|
+
message: 'description is EMPTY: cannot be empty or whitespace-only.',
|
|
250
|
+
path: 'description',
|
|
251
|
+
});
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (/\r|\n/.test(value)) {
|
|
255
|
+
findings.push({
|
|
256
|
+
severity: 'error',
|
|
257
|
+
rule: 'runbook.description_invalid',
|
|
258
|
+
message: 'description is MULTILINE: must be a single line with no line breaks.',
|
|
259
|
+
path: 'description',
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function checkBindingLeak(fm, findings) {
|
|
264
|
+
for (const field of BINDING_LEAK_FIELDS) {
|
|
265
|
+
if (field in fm) {
|
|
266
|
+
findings.push({
|
|
267
|
+
severity: 'error',
|
|
268
|
+
rule: 'runbook.binding_leak',
|
|
269
|
+
message: `Binding/DAG field "${field}" must not appear in a Runbook doc — composition/wiring lives in the binding, not the Runbook (F39 §5.5).`,
|
|
270
|
+
path: field,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
for (const field of BINDING_LEAK_TOPLEVEL_ONLY) {
|
|
275
|
+
if (field in fm) {
|
|
276
|
+
findings.push({
|
|
277
|
+
severity: 'error',
|
|
278
|
+
rule: 'runbook.binding_leak',
|
|
279
|
+
message: `"${field}" is a binding override and is legal only inside \`defaults\` — a top-level \`${field}\` is a binding leak (F39 §5.5).`,
|
|
280
|
+
path: field,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* The dual-shape / flip-state invariant (F39 §9.3 — the load-bearing piece).
|
|
287
|
+
*
|
|
288
|
+
* - Flipped template (flipped:true): the doc is authoritative. Valid Runbook
|
|
289
|
+
* frontmatter is REQUIRED; empty inline `sections` are correct, so the
|
|
290
|
+
* validator must NEVER false-alarm an EMPTY_STEP_BODY-class defect here.
|
|
291
|
+
* - Un-flipped template (flipped:false or absent): inline `sections` are
|
|
292
|
+
* authoritative. Runbook frontmatter is ACCEPTED if present (transitional)
|
|
293
|
+
* but the file is not required to be the source — no "doc must be
|
|
294
|
+
* authoritative" alarm.
|
|
295
|
+
*/
|
|
296
|
+
function checkFlipStateInvariant(fm, stepName, opts, findings) {
|
|
297
|
+
// If a ledger was attempted but unreadable, that is a hard error — the
|
|
298
|
+
// validator can't know which invariant to apply.
|
|
299
|
+
if (opts.flipStateError) {
|
|
300
|
+
findings.push({
|
|
301
|
+
severity: 'error',
|
|
302
|
+
rule: 'runbook.flip_state_unreadable',
|
|
303
|
+
message: `flip-state ledger is unreadable (${opts.flipStateError}). Cannot determine the per-template flip invariant.`,
|
|
304
|
+
path: '(flip-state)',
|
|
305
|
+
});
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const entry = flipStateForStep(opts.flipState, stepName);
|
|
309
|
+
if (!entry) {
|
|
310
|
+
// Step not in any ledger entry → treat as un-flipped (inline authoritative).
|
|
311
|
+
// No alarm: Runbook frontmatter on an un-ledgered step is transitional and fine.
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const { template, step } = entry;
|
|
315
|
+
// Cross-check: a doc's runbook id should match the ledger's recorded id for
|
|
316
|
+
// this step. A mismatch implies the doc and the ledger disagree about state.
|
|
317
|
+
if (typeof fm.runbook === 'string' &&
|
|
318
|
+
step.runbook &&
|
|
319
|
+
fm.runbook !== step.runbook) {
|
|
320
|
+
findings.push({
|
|
321
|
+
severity: 'error',
|
|
322
|
+
rule: 'runbook.flip_state_mismatch',
|
|
323
|
+
message: `Doc declares runbook "${fm.runbook}" but flip-state ledger records "${step.runbook}" for step "${stepName}" in template "${template.template}".`,
|
|
324
|
+
path: 'runbook',
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
// Flipped template: the doc MUST carry valid Runbook frontmatter (the schema
|
|
328
|
+
// checks above already enforce shape). We assert the required keys are present
|
|
329
|
+
// so a flipped step can never resolve to STEP_UNRESOLVED/EMPTY_STEP_BODY for a
|
|
330
|
+
// missing contract. We deliberately do NOT inspect inline `sections` — empty
|
|
331
|
+
// sections are correct on a flipped template, never a false EMPTY_STEP_BODY alarm.
|
|
332
|
+
if (template.flipped) {
|
|
333
|
+
if (typeof fm.runbook !== 'string' || !fm.contract) {
|
|
334
|
+
findings.push({
|
|
335
|
+
severity: 'error',
|
|
336
|
+
rule: 'runbook.flip_state_mismatch',
|
|
337
|
+
message: `Template "${template.template}" is flipped (doc-authoritative) but step "${stepName}" lacks required Runbook frontmatter (runbook + contract). A flipped step's doc is the sole source of its body.`,
|
|
338
|
+
path: '(root)',
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
// Un-flipped: nothing required; frontmatter present is fine (transitional).
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Validate a single file or a directory of `.md` Runbook docs. Loads the
|
|
346
|
+
* flip-state ledger once and cross-checks `runbook` id uniqueness across the
|
|
347
|
+
* validated set (F39 §9.2 #4).
|
|
348
|
+
*/
|
|
349
|
+
export function validatePath(target) {
|
|
350
|
+
const abs = resolvePath(target);
|
|
351
|
+
if (!existsSync(abs)) {
|
|
352
|
+
return [{ source: target, findings: [{ severity: 'error', rule: 'read.not_found', message: `Path not found: ${target}`, path: target }] }];
|
|
353
|
+
}
|
|
354
|
+
const isDir = statSync(abs).isDirectory();
|
|
355
|
+
const files = isDir
|
|
356
|
+
? readdirSync(abs).filter((f) => f.endsWith('.md')).map((f) => join(abs, f)).sort()
|
|
357
|
+
: [abs];
|
|
358
|
+
if (isDir && files.length === 0) {
|
|
359
|
+
return [{ source: target, findings: [{ severity: 'warning', rule: 'runbook.empty_dir', message: `No .md files found in directory: ${target}`, path: target }] }];
|
|
360
|
+
}
|
|
361
|
+
const startDir = isDir ? abs : dirname(abs);
|
|
362
|
+
const flip = loadFlipState(startDir);
|
|
363
|
+
const results = [];
|
|
364
|
+
const idLocations = new Map();
|
|
365
|
+
for (const file of files) {
|
|
366
|
+
let raw;
|
|
367
|
+
try {
|
|
368
|
+
raw = readFileSync(file, 'utf-8');
|
|
369
|
+
}
|
|
370
|
+
catch (err) {
|
|
371
|
+
results.push({ source: file, findings: [{ severity: 'error', rule: 'read.io_error', message: `Failed to read: ${err?.message ?? String(err)}`, path: file }] });
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
const findings = validateRunbookDoc(raw, file, {
|
|
375
|
+
startDir,
|
|
376
|
+
flipState: flip.ledger,
|
|
377
|
+
flipStateError: flip.error,
|
|
378
|
+
});
|
|
379
|
+
results.push({ source: file, findings });
|
|
380
|
+
// Collect runbook ids for the cross-file uniqueness check.
|
|
381
|
+
const fm = parseFrontmatter(raw, file).frontmatter;
|
|
382
|
+
if (fm && typeof fm.runbook === 'string') {
|
|
383
|
+
const list = idLocations.get(fm.runbook) ?? [];
|
|
384
|
+
list.push(file);
|
|
385
|
+
idLocations.set(fm.runbook, list);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// Duplicate runbook ids across the validated set → error on each offender.
|
|
389
|
+
for (const [id, locs] of idLocations) {
|
|
390
|
+
if (locs.length > 1) {
|
|
391
|
+
for (const loc of locs) {
|
|
392
|
+
const r = results.find((res) => res.source === loc);
|
|
393
|
+
r?.findings.push({
|
|
394
|
+
severity: 'error',
|
|
395
|
+
rule: 'runbook.id_duplicate',
|
|
396
|
+
message: `runbook id "${id}" is not unique within the validated set: ${locs.map((l) => basename(l)).join(', ')}.`,
|
|
397
|
+
path: 'runbook',
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return results;
|
|
403
|
+
}
|
|
404
|
+
//# sourceMappingURL=checks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checks.js","sourceRoot":"","sources":["../../src/runbook/checks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vendored copy of the FROZEN Runbook frontmatter / I/O-contract schema (F39).
|
|
3
|
+
*
|
|
4
|
+
* SOURCE OF TRUTH: the canonical artifact lives in the workspace at
|
|
5
|
+
* projects/fazemos/runbooks/runbook.schema.json
|
|
6
|
+
* (F39 tech spec §5.6, OQ-6). This vendored copy is a **cache** used only as
|
|
7
|
+
* the absent-canonical fallback: `loadRunbookSchema()` (checks.ts) loads the
|
|
8
|
+
* canonical file when run inside a workspace clone — the normal case, since the
|
|
9
|
+
* tool is validating files in that clone — and falls back to this copy only
|
|
10
|
+
* when the canonical file cannot be found.
|
|
11
|
+
*
|
|
12
|
+
* LOCKSTEP (F39 §9.2, AC#10): the vendored copy must be byte-identical to, or a
|
|
13
|
+
* strict superset of, the canonical schema. A unit test diffs the two and FAILS
|
|
14
|
+
* at ERROR severity on any drift (it may never define an enum value the
|
|
15
|
+
* canonical schema lacks, nor omit one it has). Because the canonical file is
|
|
16
|
+
* the source of truth and this is a cache, silent divergence would let the CLI
|
|
17
|
+
* accept/reject Runbooks differently from the contract F40 reads — so it is a
|
|
18
|
+
* hard CI failure, not a soft warning.
|
|
19
|
+
*
|
|
20
|
+
* Enum evolution is append-only within a major version (§5.2): new values may be
|
|
21
|
+
* ADDED via a minor bump (e.g. "1.0" -> "1.1"); any value REMOVAL is a breaking
|
|
22
|
+
* change requiring a major bump (e.g. "1.x" -> "2.0"). Bumped together with the
|
|
23
|
+
* CLI version; mirrors the established KNOWN_MANIFEST_VERSIONS pattern.
|
|
24
|
+
*/
|
|
25
|
+
export declare const RUNBOOK_SCHEMA_VERSIONS: readonly ["1.0"];
|
|
26
|
+
export declare const RUNBOOK_TYPE_ENUM: readonly ["text", "number", "boolean", "url", "filepath", "json"];
|
|
27
|
+
export declare const RUNBOOK_FORMAT_ENUM: readonly ["text", "filepath", "url", "commit_sha", "integer", "markdown", "json"];
|
|
28
|
+
export declare const RUNBOOK_SOURCE_HINT_ENUM: readonly ["upstream", "pipeline_param", "dispatch_param", "workspace_path"];
|
|
29
|
+
/**
|
|
30
|
+
* The vendored JSON-Schema (draft-07). Kept structurally identical to the
|
|
31
|
+
* canonical runbook.schema.json. The schema-drift unit test (§9.2, AC#10)
|
|
32
|
+
* compares the canonical file against this object and fails on any divergence.
|
|
33
|
+
*/
|
|
34
|
+
export declare const runbookSchema: {
|
|
35
|
+
readonly $schema: "http://json-schema.org/draft-07/schema#";
|
|
36
|
+
readonly $id: "https://fazemos/schemas/runbook.json";
|
|
37
|
+
readonly title: "Runbook frontmatter / I/O-contract schema";
|
|
38
|
+
readonly description: "Frozen schema for the Runbook primitive's step-doc frontmatter (F39). The single source of truth that F39's CLI validator (`fazemos runbook validate`) AND F40's dispatch param-validation both read. Frozen at runbook_schema_version 1.0. Enum evolution is append-only within a major version (a minor bump, e.g. 1.0 -> 1.1); any enum value removal requires a major bump (e.g. 1.x -> 2.0). See specs/tech/platform/F39-runbook-substrate-authority-flip-tech-spec.md sections 5.1-5.3 + 5.6.";
|
|
39
|
+
readonly type: "object";
|
|
40
|
+
readonly required: readonly ["runbook_schema_version", "runbook", "title", "description", "role", "contract"];
|
|
41
|
+
readonly additionalProperties: false;
|
|
42
|
+
readonly properties: {
|
|
43
|
+
readonly runbook_schema_version: {
|
|
44
|
+
readonly type: "string";
|
|
45
|
+
readonly description: "The contract version. Required (F39 section 5.3): the Runbook contract is load-bearing for F40, so a missing version is a hard error. Frozen set for F39 is [\"1.0\"].";
|
|
46
|
+
};
|
|
47
|
+
readonly runbook: {
|
|
48
|
+
readonly type: "string";
|
|
49
|
+
readonly pattern: "^[a-z0-9]+(-[a-z0-9]+)*$";
|
|
50
|
+
readonly description: "Stable kebab-case id; the callable \"function name\" that F40's runbook_ref resolves. Library-unique. Replaces the legacy step:/playbook: keys. Independent of the resolver-mandated physical filename (which must equal the step name).";
|
|
51
|
+
};
|
|
52
|
+
readonly title: {
|
|
53
|
+
readonly type: "string";
|
|
54
|
+
readonly minLength: 1;
|
|
55
|
+
readonly description: "Human label.";
|
|
56
|
+
};
|
|
57
|
+
readonly description: {
|
|
58
|
+
readonly type: "string";
|
|
59
|
+
readonly minLength: 1;
|
|
60
|
+
readonly description: "Single-line, non-empty description. The F18 validateDescription contract enforces non-empty + no newline at render; the CLI re-applies it as runbook.description_invalid before a render.";
|
|
61
|
+
};
|
|
62
|
+
readonly role: {
|
|
63
|
+
readonly type: "string";
|
|
64
|
+
readonly minLength: 1;
|
|
65
|
+
readonly description: "Capability HINT (role-slug, post-F25). Overridable at dispatch. NEVER a hard filler pin (F39 section 6.4). A Runbook is what to do, never who.";
|
|
66
|
+
};
|
|
67
|
+
readonly contract: {
|
|
68
|
+
readonly type: "object";
|
|
69
|
+
readonly required: readonly ["inputs", "outputs"];
|
|
70
|
+
readonly additionalProperties: false;
|
|
71
|
+
readonly properties: {
|
|
72
|
+
readonly inputs: {
|
|
73
|
+
readonly type: "array";
|
|
74
|
+
readonly items: {
|
|
75
|
+
readonly type: "object";
|
|
76
|
+
readonly required: readonly ["name", "type", "required", "source_hint"];
|
|
77
|
+
readonly additionalProperties: false;
|
|
78
|
+
readonly properties: {
|
|
79
|
+
readonly name: {
|
|
80
|
+
readonly type: "string";
|
|
81
|
+
readonly minLength: 1;
|
|
82
|
+
readonly description: "The name the PROCEDURE BODY uses. The binding maps the actual wire (section 5.5); the renamed wire name never leaks into the Runbook.";
|
|
83
|
+
};
|
|
84
|
+
readonly type: {
|
|
85
|
+
readonly type: "string";
|
|
86
|
+
readonly enum: readonly ["text", "number", "boolean", "url", "filepath", "json"];
|
|
87
|
+
readonly description: "FROZEN closed enum (F39 section 5.2). Closed at 6.";
|
|
88
|
+
};
|
|
89
|
+
readonly required: {
|
|
90
|
+
readonly type: "boolean";
|
|
91
|
+
};
|
|
92
|
+
readonly source_hint: {
|
|
93
|
+
readonly type: "string";
|
|
94
|
+
readonly enum: readonly ["upstream", "pipeline_param", "dispatch_param", "workspace_path"];
|
|
95
|
+
readonly description: "FROZEN closed enum (F39 section 5.2). The shape of how this input is typically satisfied, NEVER the wire. Closed at 4.";
|
|
96
|
+
};
|
|
97
|
+
readonly description: {
|
|
98
|
+
readonly type: "string";
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
readonly outputs: {
|
|
104
|
+
readonly type: "array";
|
|
105
|
+
readonly items: {
|
|
106
|
+
readonly type: "object";
|
|
107
|
+
readonly required: readonly ["name", "type", "required", "success_criteria"];
|
|
108
|
+
readonly additionalProperties: false;
|
|
109
|
+
readonly properties: {
|
|
110
|
+
readonly name: {
|
|
111
|
+
readonly type: "string";
|
|
112
|
+
readonly minLength: 1;
|
|
113
|
+
};
|
|
114
|
+
readonly type: {
|
|
115
|
+
readonly type: "string";
|
|
116
|
+
readonly enum: readonly ["text", "number", "boolean", "url", "filepath", "json"];
|
|
117
|
+
readonly description: "FROZEN closed enum (F39 section 5.2). Closed at 6.";
|
|
118
|
+
};
|
|
119
|
+
readonly required: {
|
|
120
|
+
readonly type: "boolean";
|
|
121
|
+
};
|
|
122
|
+
readonly format: {
|
|
123
|
+
readonly type: "string";
|
|
124
|
+
readonly enum: readonly ["text", "filepath", "url", "commit_sha", "integer", "markdown", "json"];
|
|
125
|
+
readonly description: "FROZEN closed enum (F39 section 5.2). Optional; refines type. Absent => unconstrained beyond type. Closed at 7.";
|
|
126
|
+
};
|
|
127
|
+
readonly success_criteria: {
|
|
128
|
+
readonly type: "string";
|
|
129
|
+
readonly minLength: 1;
|
|
130
|
+
readonly description: "What turns the contract into a real function signature (Ollie section 1: the single highest-value piece). What a pipeline evaluator, chat dispatcher, and inbox auto-wake all agree on for \"did this Runbook return what it promised.\"";
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
readonly defaults: {
|
|
138
|
+
readonly type: "object";
|
|
139
|
+
readonly additionalProperties: false;
|
|
140
|
+
readonly description: "SUGGESTED defaults only; the binding override wins when present (section 5.5). Honors BUG36 (eval budget keys off step shape).";
|
|
141
|
+
readonly properties: {
|
|
142
|
+
readonly reviewer: {
|
|
143
|
+
readonly type: readonly ["string", "null"];
|
|
144
|
+
readonly description: "role-slug or null.";
|
|
145
|
+
};
|
|
146
|
+
readonly eval_max_turns: {
|
|
147
|
+
readonly type: readonly ["integer", "null"];
|
|
148
|
+
readonly description: "null => engine default.";
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
};
|