@cleocode/playbooks 2026.4.93 → 2026.4.95

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.
@@ -0,0 +1,113 @@
1
+ /**
2
+ * HMAC-SHA256 resume tokens for HITL approval gates.
3
+ *
4
+ * Tokens bind `{runId, nodeId, bindings}` so they cannot be forged or replayed
5
+ * across different executions. The secret defaults to a well-known dev value —
6
+ * production deployments MUST set the `CLEO_PLAYBOOK_SECRET` env var to a
7
+ * high-entropy secret. If the secret rotates, existing tokens are invalidated
8
+ * because the HMAC output changes.
9
+ *
10
+ * Binding canonicalization uses sorted-keys JSON so that `{a:1,b:2}` and
11
+ * `{b:2,a:1}` produce the same token — semantically identical payloads
12
+ * should always yield the same gate identity.
13
+ *
14
+ * @task T889 / T908 / W4-16
15
+ */
16
+ import type { DatabaseSync } from 'node:sqlite';
17
+ import type { PlaybookApproval } from '@cleocode/contracts';
18
+ /**
19
+ * Error code: approval token not found in the DB.
20
+ * Raised by {@link approveGate} / {@link rejectGate}.
21
+ */
22
+ export declare const E_APPROVAL_NOT_FOUND: "E_APPROVAL_NOT_FOUND";
23
+ /**
24
+ * Error code: approval has already transitioned out of `pending`.
25
+ * Raised by {@link approveGate} / {@link rejectGate} to prevent re-decisions.
26
+ */
27
+ export declare const E_APPROVAL_ALREADY_DECIDED: "E_APPROVAL_ALREADY_DECIDED";
28
+ /**
29
+ * Resolve the HMAC secret for resume-token generation.
30
+ *
31
+ * @param env - Override env source (defaults to `process.env`). Used in tests.
32
+ * @returns The configured secret, or a dev-only fallback if unset.
33
+ */
34
+ export declare function getPlaybookSecret(env?: NodeJS.ProcessEnv): string;
35
+ /**
36
+ * Generate a deterministic 32-char hex HMAC-SHA256 resume token.
37
+ *
38
+ * The token is derived from `HMAC(secret, "runId:nodeId:canonicalBindings")`
39
+ * and truncated to 32 hex chars (128 bits). Determinism is an intentional
40
+ * design choice: the same (runId, nodeId, bindings, secret) tuple always
41
+ * produces the same token, preventing duplicate gates for the same step.
42
+ *
43
+ * @param runId - Playbook run identifier.
44
+ * @param nodeId - Node identifier within the run graph.
45
+ * @param bindings - Current runtime bindings (canonicalized via sorted-keys JSON).
46
+ * @param secret - HMAC secret (defaults to {@link getPlaybookSecret}).
47
+ * @returns A 32-char lowercase hex string.
48
+ */
49
+ export declare function generateResumeToken(runId: string, nodeId: string, bindings: Record<string, unknown>, secret?: string): string;
50
+ /**
51
+ * Input for {@link createApprovalGate}.
52
+ */
53
+ export interface CreateApprovalGateInput {
54
+ /** Run identifier (FK to `playbook_runs.run_id`). */
55
+ runId: string;
56
+ /** Node identifier within the run graph. */
57
+ nodeId: string;
58
+ /** Runtime bindings at gate creation time. */
59
+ bindings: Record<string, unknown>;
60
+ /** If true, gate is created pre-approved (policy auto-pass). Default false. */
61
+ autoPassed?: boolean;
62
+ /** Optional approver identity (required if `autoPassed=true` recorded by policy). */
63
+ approver?: string;
64
+ /** Optional human-readable reason (policy name, approval note, etc.). */
65
+ reason?: string;
66
+ /** Override secret for token generation. Defaults to env-resolved secret. */
67
+ secret?: string;
68
+ }
69
+ /**
70
+ * Create an HITL approval gate row in `playbook_approvals`.
71
+ *
72
+ * If `autoPassed` is true, the gate is written with `status='approved'`
73
+ * and `auto_passed=1` — used by the policy engine to short-circuit gates
74
+ * that match auto-pass rules. Otherwise status is `'pending'` and the
75
+ * runtime blocks until {@link approveGate} or {@link rejectGate} is called.
76
+ *
77
+ * @param db - Open `node:sqlite` handle with the T889 migration applied.
78
+ * @param input - Gate parameters.
79
+ * @returns The inserted {@link PlaybookApproval}, round-tripped from the DB.
80
+ */
81
+ export declare function createApprovalGate(db: DatabaseSync, input: CreateApprovalGateInput): PlaybookApproval;
82
+ /**
83
+ * Transition an approval gate to `approved` state.
84
+ *
85
+ * @param db - Open sqlite handle.
86
+ * @param token - The resume token returned from {@link createApprovalGate}.
87
+ * @param approver - Identity of the approver (agent id, user email, etc.).
88
+ * @param reason - Optional justification note.
89
+ * @returns The updated {@link PlaybookApproval} record.
90
+ * @throws Error with `E_APPROVAL_NOT_FOUND` code if no gate matches the token.
91
+ * @throws Error with `E_APPROVAL_ALREADY_DECIDED` code if the gate is not pending.
92
+ */
93
+ export declare function approveGate(db: DatabaseSync, token: string, approver: string, reason?: string): PlaybookApproval;
94
+ /**
95
+ * Transition an approval gate to `rejected` state. Same semantics as
96
+ * {@link approveGate} but records a rejection — runtime will halt the run.
97
+ *
98
+ * @param db - Open sqlite handle.
99
+ * @param token - The resume token.
100
+ * @param approver - Identity of the rejector.
101
+ * @param reason - Optional justification.
102
+ * @returns The updated {@link PlaybookApproval} record.
103
+ * @throws Error with `E_APPROVAL_NOT_FOUND` if the token is unknown.
104
+ * @throws Error with `E_APPROVAL_ALREADY_DECIDED` if the gate is not pending.
105
+ */
106
+ export declare function rejectGate(db: DatabaseSync, token: string, approver: string, reason?: string): PlaybookApproval;
107
+ /**
108
+ * List all gates that are still awaiting a decision, oldest first.
109
+ *
110
+ * @param db - Open sqlite handle.
111
+ * @returns Pending {@link PlaybookApproval} records ordered by `requested_at`.
112
+ */
113
+ export declare function getPendingApprovals(db: DatabaseSync): PlaybookApproval[];
@@ -0,0 +1,244 @@
1
+ /**
2
+ * HMAC-SHA256 resume tokens for HITL approval gates.
3
+ *
4
+ * Tokens bind `{runId, nodeId, bindings}` so they cannot be forged or replayed
5
+ * across different executions. The secret defaults to a well-known dev value —
6
+ * production deployments MUST set the `CLEO_PLAYBOOK_SECRET` env var to a
7
+ * high-entropy secret. If the secret rotates, existing tokens are invalidated
8
+ * because the HMAC output changes.
9
+ *
10
+ * Binding canonicalization uses sorted-keys JSON so that `{a:1,b:2}` and
11
+ * `{b:2,a:1}` produce the same token — semantically identical payloads
12
+ * should always yield the same gate identity.
13
+ *
14
+ * @task T889 / T908 / W4-16
15
+ */
16
+ import { createHmac, randomUUID } from 'node:crypto';
17
+ /**
18
+ * Dev-only fallback secret. Surfaced through {@link getPlaybookSecret} so
19
+ * production code paths can override via `CLEO_PLAYBOOK_SECRET`.
20
+ */
21
+ const DEFAULT_SECRET = 'cleo-playbook-dev-secret-do-not-use-in-production';
22
+ /**
23
+ * Token length (hex chars). 32 hex chars = 128 bits of HMAC output — enough
24
+ * for collision resistance while keeping tokens URL-safe and log-friendly.
25
+ */
26
+ const TOKEN_LENGTH = 32;
27
+ /**
28
+ * Error code: approval token not found in the DB.
29
+ * Raised by {@link approveGate} / {@link rejectGate}.
30
+ */
31
+ export const E_APPROVAL_NOT_FOUND = 'E_APPROVAL_NOT_FOUND';
32
+ /**
33
+ * Error code: approval has already transitioned out of `pending`.
34
+ * Raised by {@link approveGate} / {@link rejectGate} to prevent re-decisions.
35
+ */
36
+ export const E_APPROVAL_ALREADY_DECIDED = 'E_APPROVAL_ALREADY_DECIDED';
37
+ /**
38
+ * Resolve the HMAC secret for resume-token generation.
39
+ *
40
+ * @param env - Override env source (defaults to `process.env`). Used in tests.
41
+ * @returns The configured secret, or a dev-only fallback if unset.
42
+ */
43
+ export function getPlaybookSecret(env = process.env) {
44
+ return env['CLEO_PLAYBOOK_SECRET'] ?? DEFAULT_SECRET;
45
+ }
46
+ /**
47
+ * Generate a deterministic 32-char hex HMAC-SHA256 resume token.
48
+ *
49
+ * The token is derived from `HMAC(secret, "runId:nodeId:canonicalBindings")`
50
+ * and truncated to 32 hex chars (128 bits). Determinism is an intentional
51
+ * design choice: the same (runId, nodeId, bindings, secret) tuple always
52
+ * produces the same token, preventing duplicate gates for the same step.
53
+ *
54
+ * @param runId - Playbook run identifier.
55
+ * @param nodeId - Node identifier within the run graph.
56
+ * @param bindings - Current runtime bindings (canonicalized via sorted-keys JSON).
57
+ * @param secret - HMAC secret (defaults to {@link getPlaybookSecret}).
58
+ * @returns A 32-char lowercase hex string.
59
+ */
60
+ export function generateResumeToken(runId, nodeId, bindings, secret = getPlaybookSecret()) {
61
+ // Canonicalize bindings via sorted-keys JSON for determinism.
62
+ const canonical = JSON.stringify(bindings, Object.keys(bindings).sort());
63
+ const payload = `${runId}:${nodeId}:${canonical}`;
64
+ return createHmac('sha256', secret).update(payload).digest('hex').slice(0, TOKEN_LENGTH);
65
+ }
66
+ /**
67
+ * Narrow a raw status string to {@link PlaybookApprovalStatus}, guarding
68
+ * against unexpected DB values that would otherwise poison downstream types.
69
+ *
70
+ * @internal
71
+ */
72
+ function narrowStatus(s) {
73
+ if (s === 'pending' || s === 'approved' || s === 'rejected')
74
+ return s;
75
+ throw new Error(`invariant: unknown playbook_approvals.status '${s}'`);
76
+ }
77
+ /**
78
+ * Read a required string column from a raw sqlite row.
79
+ *
80
+ * @internal
81
+ */
82
+ function readString(row, key) {
83
+ const v = row[key];
84
+ if (typeof v !== 'string') {
85
+ throw new Error(`invariant: expected string for column ${key}, got ${typeof v}`);
86
+ }
87
+ return v;
88
+ }
89
+ /**
90
+ * Read a required integer column from a raw sqlite row.
91
+ *
92
+ * @internal
93
+ */
94
+ function readInt(row, key) {
95
+ const v = row[key];
96
+ if (typeof v === 'number')
97
+ return v;
98
+ if (typeof v === 'bigint')
99
+ return Number(v);
100
+ throw new Error(`invariant: expected integer for column ${key}, got ${typeof v}`);
101
+ }
102
+ /**
103
+ * Read an optional string column. Returns `undefined` for both `null`
104
+ * (SQL NULL) and missing keys.
105
+ *
106
+ * @internal
107
+ */
108
+ function readOptionalString(row, key) {
109
+ const v = row[key];
110
+ if (v === null || v === undefined)
111
+ return undefined;
112
+ if (typeof v !== 'string') {
113
+ throw new Error(`invariant: expected string|null for column ${key}, got ${typeof v}`);
114
+ }
115
+ return v;
116
+ }
117
+ /**
118
+ * Map a raw `playbook_approvals` row to the camelCase {@link PlaybookApproval}
119
+ * contract shape. Validates types, converts the `auto_passed` 0/1 integer to
120
+ * a boolean, and strips nullable fields rather than emitting `null`.
121
+ *
122
+ * @internal
123
+ */
124
+ function rowToApproval(row) {
125
+ const approval = {
126
+ approvalId: readString(row, 'approval_id'),
127
+ runId: readString(row, 'run_id'),
128
+ nodeId: readString(row, 'node_id'),
129
+ token: readString(row, 'token'),
130
+ requestedAt: readString(row, 'requested_at'),
131
+ status: narrowStatus(readString(row, 'status')),
132
+ autoPassed: readInt(row, 'auto_passed') === 1,
133
+ };
134
+ const approvedAt = readOptionalString(row, 'approved_at');
135
+ const approver = readOptionalString(row, 'approver');
136
+ const reason = readOptionalString(row, 'reason');
137
+ if (approvedAt !== undefined)
138
+ approval.approvedAt = approvedAt;
139
+ if (approver !== undefined)
140
+ approval.approver = approver;
141
+ if (reason !== undefined)
142
+ approval.reason = reason;
143
+ return approval;
144
+ }
145
+ /**
146
+ * Create an HITL approval gate row in `playbook_approvals`.
147
+ *
148
+ * If `autoPassed` is true, the gate is written with `status='approved'`
149
+ * and `auto_passed=1` — used by the policy engine to short-circuit gates
150
+ * that match auto-pass rules. Otherwise status is `'pending'` and the
151
+ * runtime blocks until {@link approveGate} or {@link rejectGate} is called.
152
+ *
153
+ * @param db - Open `node:sqlite` handle with the T889 migration applied.
154
+ * @param input - Gate parameters.
155
+ * @returns The inserted {@link PlaybookApproval}, round-tripped from the DB.
156
+ */
157
+ export function createApprovalGate(db, input) {
158
+ const token = generateResumeToken(input.runId, input.nodeId, input.bindings, input.secret);
159
+ const approvalId = randomUUID();
160
+ const autoPassed = input.autoPassed ?? false;
161
+ const status = autoPassed ? 'approved' : 'pending';
162
+ const stmt = db.prepare(`
163
+ INSERT INTO playbook_approvals
164
+ (approval_id, run_id, node_id, token, status, auto_passed, approver, reason)
165
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
166
+ `);
167
+ stmt.run(approvalId, input.runId, input.nodeId, token, status, autoPassed ? 1 : 0, input.approver ?? null, input.reason ?? null);
168
+ const row = db
169
+ .prepare('SELECT * FROM playbook_approvals WHERE approval_id = ?')
170
+ .get(approvalId);
171
+ if (row === undefined) {
172
+ throw new Error(`${E_APPROVAL_NOT_FOUND}: insert did not round-trip (approval_id=${approvalId})`);
173
+ }
174
+ return rowToApproval(row);
175
+ }
176
+ /**
177
+ * Transition an approval gate to `approved` state.
178
+ *
179
+ * @param db - Open sqlite handle.
180
+ * @param token - The resume token returned from {@link createApprovalGate}.
181
+ * @param approver - Identity of the approver (agent id, user email, etc.).
182
+ * @param reason - Optional justification note.
183
+ * @returns The updated {@link PlaybookApproval} record.
184
+ * @throws Error with `E_APPROVAL_NOT_FOUND` code if no gate matches the token.
185
+ * @throws Error with `E_APPROVAL_ALREADY_DECIDED` code if the gate is not pending.
186
+ */
187
+ export function approveGate(db, token, approver, reason) {
188
+ return transitionGate(db, token, 'approved', approver, reason);
189
+ }
190
+ /**
191
+ * Transition an approval gate to `rejected` state. Same semantics as
192
+ * {@link approveGate} but records a rejection — runtime will halt the run.
193
+ *
194
+ * @param db - Open sqlite handle.
195
+ * @param token - The resume token.
196
+ * @param approver - Identity of the rejector.
197
+ * @param reason - Optional justification.
198
+ * @returns The updated {@link PlaybookApproval} record.
199
+ * @throws Error with `E_APPROVAL_NOT_FOUND` if the token is unknown.
200
+ * @throws Error with `E_APPROVAL_ALREADY_DECIDED` if the gate is not pending.
201
+ */
202
+ export function rejectGate(db, token, approver, reason) {
203
+ return transitionGate(db, token, 'rejected', approver, reason);
204
+ }
205
+ /**
206
+ * Internal shared transition logic for approve/reject. Performs row lookup,
207
+ * state validation, update, and round-trip fetch in a single atomic flow.
208
+ *
209
+ * @internal
210
+ */
211
+ function transitionGate(db, token, next, approver, reason) {
212
+ const existing = db.prepare('SELECT * FROM playbook_approvals WHERE token = ?').get(token);
213
+ if (existing === undefined) {
214
+ throw new Error(`${E_APPROVAL_NOT_FOUND}: no approval gate for token`);
215
+ }
216
+ const existingStatus = narrowStatus(readString(existing, 'status'));
217
+ if (existingStatus !== 'pending') {
218
+ const approvalId = readString(existing, 'approval_id');
219
+ throw new Error(`${E_APPROVAL_ALREADY_DECIDED}: gate ${approvalId} is already ${existingStatus}`);
220
+ }
221
+ db.prepare(`UPDATE playbook_approvals
222
+ SET status = ?, approved_at = datetime('now'), approver = ?, reason = ?
223
+ WHERE token = ?`).run(next, approver, reason ?? null, token);
224
+ const row = db.prepare('SELECT * FROM playbook_approvals WHERE token = ?').get(token);
225
+ if (row === undefined) {
226
+ // Unreachable: UPDATE just succeeded on this token.
227
+ throw new Error(`${E_APPROVAL_NOT_FOUND}: row vanished after update (token=${token})`);
228
+ }
229
+ return rowToApproval(row);
230
+ }
231
+ /**
232
+ * List all gates that are still awaiting a decision, oldest first.
233
+ *
234
+ * @param db - Open sqlite handle.
235
+ * @returns Pending {@link PlaybookApproval} records ordered by `requested_at`.
236
+ */
237
+ export function getPendingApprovals(db) {
238
+ const rows = db
239
+ .prepare(`SELECT * FROM playbook_approvals
240
+ WHERE status = 'pending'
241
+ ORDER BY requested_at ASC, approval_id ASC`)
242
+ .all();
243
+ return rows.map(rowToApproval);
244
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @cleocode/playbooks — Playbook DSL + runtime for T889 Orchestration Coherence v3.
3
+ *
4
+ * This package is scaffolded in Wave 0. Subsequent waves will populate:
5
+ * - `schema.ts` (W4-6) — types + Drizzle table defs
6
+ * - `parser.ts` (W4-7) — .cantbook YAML parser
7
+ * - `state.ts` (W4-8) — DB CRUD for playbook_runs + playbook_approvals
8
+ * - `policy.ts` (W4-9) — HITL auto-policy rules
9
+ * - `runtime.ts` (W4-10) — state machine executor
10
+ * - `approval.ts` (W4-16) — resume token generation + approval ops
11
+ * - `skill-composer.ts` (W4-2..5) — three-source skill bundle composer
12
+ *
13
+ * @remarks
14
+ * Only the {@link PLAYBOOKS_PACKAGE_VERSION} constant is exported from the
15
+ * Wave 0 scaffold. Each follow-up wave adds a named barrel export here.
16
+ *
17
+ * @task T889 Orchestration Coherence v3 — Wave 0 scaffold
18
+ */
19
+ /**
20
+ * Package version string matching the monorepo's CalVer cadence.
21
+ *
22
+ * Consumers can use this to assert dependency alignment at runtime
23
+ * (e.g. ensuring the `@cleocode/playbooks` runtime matches CLEO core).
24
+ */
25
+ export declare const PLAYBOOKS_PACKAGE_VERSION: string;
26
+ export { approveGate, type CreateApprovalGateInput, createApprovalGate, E_APPROVAL_ALREADY_DECIDED, E_APPROVAL_NOT_FOUND, generateResumeToken, getPendingApprovals, getPlaybookSecret, rejectGate, } from './approval.js';
27
+ export { type ParsePlaybookResult, PlaybookParseError, parsePlaybook, } from './parser.js';
28
+ export { DEFAULT_POLICY_RULES, type EvaluatePolicyResult, evaluatePolicy, type PolicyRule, } from './policy.js';
29
+ export { type AgentDispatcher, type AgentDispatchInput, type AgentDispatchResult, type DeterministicRunInput, type DeterministicRunner, type DeterministicRunResult, E_PLAYBOOK_RESUME_BLOCKED, E_PLAYBOOK_RUNTIME_INVALID, type ExecutePlaybookOptions, type ExecutePlaybookResult, executePlaybook, type PlaybookTerminalStatus, type ResumePlaybookOptions, resumePlaybook, } from './runtime.js';
30
+ export { type CreatePlaybookApprovalInput, type CreatePlaybookRunInput, createPlaybookApproval, createPlaybookRun, deletePlaybookRun, getPlaybookApprovalByToken, getPlaybookRun, type ListPlaybookRunsOptions, listPlaybookApprovals, listPlaybookRuns, updatePlaybookApproval, updatePlaybookRun, } from './state.js';
package/dist/index.js ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @cleocode/playbooks — Playbook DSL + runtime for T889 Orchestration Coherence v3.
3
+ *
4
+ * This package is scaffolded in Wave 0. Subsequent waves will populate:
5
+ * - `schema.ts` (W4-6) — types + Drizzle table defs
6
+ * - `parser.ts` (W4-7) — .cantbook YAML parser
7
+ * - `state.ts` (W4-8) — DB CRUD for playbook_runs + playbook_approvals
8
+ * - `policy.ts` (W4-9) — HITL auto-policy rules
9
+ * - `runtime.ts` (W4-10) — state machine executor
10
+ * - `approval.ts` (W4-16) — resume token generation + approval ops
11
+ * - `skill-composer.ts` (W4-2..5) — three-source skill bundle composer
12
+ *
13
+ * @remarks
14
+ * Only the {@link PLAYBOOKS_PACKAGE_VERSION} constant is exported from the
15
+ * Wave 0 scaffold. Each follow-up wave adds a named barrel export here.
16
+ *
17
+ * @task T889 Orchestration Coherence v3 — Wave 0 scaffold
18
+ */
19
+ /**
20
+ * Package version string matching the monorepo's CalVer cadence.
21
+ *
22
+ * Consumers can use this to assert dependency alignment at runtime
23
+ * (e.g. ensuring the `@cleocode/playbooks` runtime matches CLEO core).
24
+ */
25
+ export const PLAYBOOKS_PACKAGE_VERSION = '2026.4.85';
26
+ export { approveGate, createApprovalGate, E_APPROVAL_ALREADY_DECIDED, E_APPROVAL_NOT_FOUND, generateResumeToken, getPendingApprovals, getPlaybookSecret, rejectGate, } from './approval.js';
27
+ // W4-7: .cantbook YAML parser → PlaybookDefinition
28
+ export { PlaybookParseError, parsePlaybook, } from './parser.js';
29
+ // W4-9: HITL auto-policy evaluator
30
+ export { DEFAULT_POLICY_RULES, evaluatePolicy, } from './policy.js';
31
+ // W4-10 / T930: playbook runtime state machine + HITL resume
32
+ export { E_PLAYBOOK_RESUME_BLOCKED, E_PLAYBOOK_RUNTIME_INVALID, executePlaybook, resumePlaybook, } from './runtime.js';
33
+ // W4-8: state layer CRUD for playbook_runs + playbook_approvals
34
+ export { createPlaybookApproval, createPlaybookRun, deletePlaybookRun, getPlaybookApprovalByToken, getPlaybookRun, listPlaybookApprovals, listPlaybookRuns, updatePlaybookApproval, updatePlaybookRun, } from './state.js';
@@ -0,0 +1,60 @@
1
+ /**
2
+ * .cantbook YAML parser → PlaybookDefinition.
3
+ *
4
+ * Grammar (see contracts/playbook.ts):
5
+ * version: "1.0"
6
+ * name: <string>
7
+ * description?: <string>
8
+ * inputs?: [{name, required?, default?, description?}]
9
+ * nodes: [<agentic | deterministic | approval node>]
10
+ * edges: [{from, to, contract?:{requires?[], ensures?[]}}]
11
+ * error_handlers?: [{on, action, message?}]
12
+ *
13
+ * Validation:
14
+ * - version MUST be "1.0"
15
+ * - name MUST be non-empty
16
+ * - node ids MUST be unique
17
+ * - every edge.from + edge.to MUST reference a known node id
18
+ * - nodes form a DAG when combined with edges (no cycles)
19
+ * - agentic nodes MUST have skill OR agent (at least one)
20
+ * - deterministic nodes MUST have command + args
21
+ * - approval nodes MUST have prompt
22
+ * - depends[] entries MUST be valid node ids
23
+ * - iteration_cap (max_iterations) MUST be 0..10 (hard limit)
24
+ *
25
+ * @task T889 / T904 / W4-7
26
+ */
27
+ import type { PlaybookDefinition } from '@cleocode/contracts';
28
+ /**
29
+ * Error thrown on any structural or semantic parse failure. Carries a
30
+ * `code`/`exitCode` pair so callers can bubble up consistent LAFS envelopes.
31
+ */
32
+ export declare class PlaybookParseError extends Error {
33
+ readonly field?: string | undefined;
34
+ readonly value?: unknown | undefined;
35
+ /** Stable envelope error code for LAFS. */
36
+ readonly code = "E_PLAYBOOK_PARSE";
37
+ /** Process exit code used by CLI wrappers when a parse fails. */
38
+ readonly exitCode = 70;
39
+ /**
40
+ * @param message - Human-readable reason the playbook is invalid.
41
+ * @param field - Offending field path (e.g. `"nodes[0].id"`).
42
+ * @param value - Offending value for diagnostics (never re-thrown).
43
+ */
44
+ constructor(message: string, field?: string | undefined, value?: unknown | undefined);
45
+ }
46
+ /** Result of a successful {@link parsePlaybook} call. */
47
+ export interface ParsePlaybookResult {
48
+ /** Validated, normalized definition ready for runtime execution. */
49
+ definition: PlaybookDefinition;
50
+ /** SHA-256 hex of the input source (for tamper detection). */
51
+ sourceHash: string;
52
+ }
53
+ /**
54
+ * Parse raw .cantbook YAML text into a validated {@link PlaybookDefinition}.
55
+ *
56
+ * @param source - Raw .cantbook YAML text.
57
+ * @returns The validated definition plus a deterministic SHA-256 source hash.
58
+ * @throws {PlaybookParseError} On any structural or semantic violation.
59
+ */
60
+ export declare function parsePlaybook(source: string): ParsePlaybookResult;