@cleocode/playbooks 2026.4.88 → 2026.4.91

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/README.md ADDED
@@ -0,0 +1,207 @@
1
+ # @cleocode/playbooks
2
+
3
+ **Playbook DSL + runtime for CLEO — T889 Orchestration Coherence v3.**
4
+
5
+ Playbooks are `.cantbook` YAML documents that describe a multi-step agent workflow as a DAG of nodes (agentic, deterministic, and approval gates) connected by typed edges. This package parses them, persists execution state to `tasks.db`, evaluates HITL auto-policies, and manages HMAC-signed approval tokens for human-in-the-loop gates.
6
+
7
+ ## Status
8
+
9
+ | Wave | Concern | Status |
10
+ |------|---------|--------|
11
+ | W4-6 | Drizzle tables + types | shipped |
12
+ | W4-7 | `.cantbook` YAML parser | shipped |
13
+ | W4-8 | State layer CRUD | shipped |
14
+ | W4-9 | HITL auto-policy evaluator | shipped |
15
+ | W4-10 | State-machine runtime | pending |
16
+ | W4-16 | Approval resume tokens (HMAC) | shipped |
17
+
18
+ The runtime executor (`runtime.ts`) is the next ship — parser + state + policy + approval primitives are in place.
19
+
20
+ ## Install
21
+
22
+ This is an internal monorepo package. Consumers use it via workspace dependency:
23
+
24
+ ```jsonc
25
+ // package.json
26
+ {
27
+ "dependencies": {
28
+ "@cleocode/playbooks": "workspace:*"
29
+ }
30
+ }
31
+ ```
32
+
33
+ ## What a `.cantbook` looks like
34
+
35
+ ```yaml
36
+ version: "1.0"
37
+ name: release-cut
38
+ description: Cut a CalVer release and publish to npm
39
+
40
+ inputs:
41
+ - name: version
42
+ required: true
43
+ description: Target version (e.g. 2026.4.90)
44
+
45
+ nodes:
46
+ - id: build
47
+ type: deterministic
48
+ command: pnpm
49
+ args: [run, build]
50
+
51
+ - id: test
52
+ type: deterministic
53
+ command: pnpm
54
+ args: [run, test]
55
+ depends: [build]
56
+
57
+ - id: review
58
+ type: approval
59
+ prompt: "Build + tests green. Publish {{ version }} to npm?"
60
+ depends: [test]
61
+
62
+ - id: publish
63
+ type: agentic
64
+ skill: release-publisher
65
+ depends: [review]
66
+
67
+ edges:
68
+ - from: build
69
+ to: test
70
+ contract:
71
+ requires: [dist-exists]
72
+ ensures: [tests-passed]
73
+ - from: test
74
+ to: review
75
+ - from: review
76
+ to: publish
77
+
78
+ error_handlers:
79
+ - on: test-failed
80
+ action: abort
81
+ message: "Tests failed — aborting release."
82
+ ```
83
+
84
+ Validation rules enforced by the parser:
85
+ - `version` MUST be `"1.0"`.
86
+ - `name` MUST be non-empty.
87
+ - Node `id`s MUST be unique.
88
+ - Every `edges[].from` / `edges[].to` MUST reference a known node id.
89
+ - Nodes + edges MUST form a DAG (no cycles).
90
+ - `agentic` nodes MUST have `skill` OR `agent` (at least one).
91
+ - `deterministic` nodes MUST have `command` + `args`.
92
+ - `approval` nodes MUST have `prompt`.
93
+ - `depends[]` entries MUST be valid node ids.
94
+ - `iteration_cap` / `max_iterations` MUST be in `0..10` (hard limit).
95
+
96
+ ## Quick API
97
+
98
+ ```typescript
99
+ import {
100
+ parsePlaybook,
101
+ createPlaybookRun,
102
+ updatePlaybookRun,
103
+ evaluatePolicy,
104
+ createApprovalGate,
105
+ approveGate,
106
+ rejectGate,
107
+ DEFAULT_POLICY_RULES,
108
+ } from '@cleocode/playbooks';
109
+
110
+ // 1. Parse a .cantbook file
111
+ const { playbook, hash } = parsePlaybook(yamlSource);
112
+
113
+ // 2. Create a persisted run
114
+ const run = createPlaybookRun(tasksDb, {
115
+ runId: 'run_abc123',
116
+ playbookName: playbook.name,
117
+ playbookHash: hash,
118
+ bindings: { version: '2026.4.90' },
119
+ epicId: 'T889',
120
+ sessionId: 'ses_xyz',
121
+ });
122
+
123
+ // 3. Evaluate auto-policy at an approval node
124
+ const { autoPassed, reason } = evaluatePolicy(node, DEFAULT_POLICY_RULES, context);
125
+
126
+ // 4. Create an approval gate (pending) or auto-pass it
127
+ const approval = createApprovalGate(tasksDb, {
128
+ runId: run.runId,
129
+ nodeId: 'review',
130
+ autoPassed,
131
+ });
132
+
133
+ // 5. Resolve the gate (human approves via the resume token)
134
+ approveGate(tasksDb, approval.token, { approver: 'keaton', reason: 'LGTM' });
135
+ ```
136
+
137
+ ## Database tables
138
+
139
+ Both tables live in `tasks.db`. Migration: `packages/core/migrations/drizzle-tasks/20260417220000_t889-playbook-tables/`.
140
+
141
+ ### `playbook_runs`
142
+
143
+ | Column | Type | Notes |
144
+ |--------|------|-------|
145
+ | `run_id` | text PK | caller-supplied run id |
146
+ | `playbook_name` | text | from parsed playbook |
147
+ | `playbook_hash` | text | SHA-256 of source (parser computes) |
148
+ | `current_node` | text | id of the active node, `null` when done |
149
+ | `bindings` | json text | accumulated input + per-node output bindings |
150
+ | `error_context` | json text | populated when `status = 'failed'` |
151
+ | `status` | text | `running \| paused \| failed \| succeeded \| cancelled` |
152
+ | `iteration_counts` | json text | `{ nodeId: count }` — enforced against `iteration_cap` |
153
+ | `epic_id` | text | linked task epic (optional) |
154
+ | `session_id` | text | linked CLEO session (optional) |
155
+ | `started_at` | text | ISO-8601, defaults to `now()` |
156
+ | `completed_at` | text | ISO-8601 when terminal |
157
+
158
+ ### `playbook_approvals`
159
+
160
+ | Column | Type | Notes |
161
+ |--------|------|-------|
162
+ | `approval_id` | text PK | |
163
+ | `run_id` | text | FK → `playbook_runs.run_id` |
164
+ | `node_id` | text | approval node id in the playbook |
165
+ | `token` | text UNIQUE | HMAC resume token |
166
+ | `requested_at` | text | ISO-8601 |
167
+ | `approved_at` | text | ISO-8601 on approve/reject |
168
+ | `approver` | text | who acted |
169
+ | `reason` | text | free-form rationale |
170
+ | `status` | text | `pending \| approved \| rejected` |
171
+ | `auto_passed` | int 0/1 | set by the policy evaluator |
172
+
173
+ ## Approval tokens (HMAC)
174
+
175
+ Resume tokens are HMAC-SHA-256 signed with the secret resolved from `getPlaybookSecret(env)`:
176
+
177
+ 1. `CLEO_PLAYBOOK_SECRET` (preferred)
178
+ 2. `CLEO_SECRET` (fallback)
179
+ 3. Hard error if neither is set — approval nodes cannot be created without a secret.
180
+
181
+ Tokens encode `{runId, nodeId, issuedAt}` and are verified before any `approveGate` / `rejectGate` call. Replay-resistant: an already-decided approval returns `E_APPROVAL_ALREADY_DECIDED`.
182
+
183
+ ## Error codes
184
+
185
+ | Constant | Meaning |
186
+ |----------|---------|
187
+ | `E_APPROVAL_NOT_FOUND` | Token does not match any pending approval row |
188
+ | `E_APPROVAL_ALREADY_DECIDED` | Approval is already `approved` or `rejected` |
189
+ | `PlaybookParseError` | Thrown by `parsePlaybook` with a list of validation issues |
190
+
191
+ ## Policy evaluator
192
+
193
+ `evaluatePolicy(node, rules, context)` matches an approval node against an ordered list of `PolicyRule`s. The first rule that matches decides: `autoPassed: true | false` plus a `reason`. Used to let low-risk approval gates auto-pass (e.g. "build dist unchanged from last green run") without bothering a human. `DEFAULT_POLICY_RULES` ships a safe conservative default set.
194
+
195
+ ## Related packages
196
+
197
+ - **`@cleocode/contracts`** — `PlaybookDefinition`, `PlaybookRun`, `PlaybookApproval` type contracts consumed here.
198
+ - **`@cleocode/core`** — owns `tasks.db` lifecycle. Playbook migrations are applied by core's drizzle runner.
199
+ - **`@cleocode/cleo`** — CLI surface that will expose `cleo playbook run`, `cleo playbook approve <token>` once the runtime (W4-10) lands.
200
+
201
+ ## Testing
202
+
203
+ ```bash
204
+ pnpm --filter @cleocode/playbooks test
205
+ ```
206
+
207
+ Smoke, parser, schema, state, policy, and approval test suites cover the shipped surface.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleocode/playbooks",
3
- "version": "2026.4.88",
3
+ "version": "2026.4.91",
4
4
  "description": "Playbook DSL + runtime for CLEO — T889 Orchestration Coherence v3",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -18,8 +18,8 @@
18
18
  "dependencies": {
19
19
  "drizzle-orm": "1.0.0-beta.19-d95b7a4",
20
20
  "js-yaml": "^4.1.0",
21
- "@cleocode/contracts": "2026.4.88",
22
- "@cleocode/core": "2026.4.88"
21
+ "@cleocode/contracts": "2026.4.91",
22
+ "@cleocode/core": "2026.4.91"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/js-yaml": "^4.0.9",
@@ -1,113 +0,0 @@
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[];
package/dist/approval.js DELETED
@@ -1,244 +0,0 @@
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
- }
package/dist/index.d.ts DELETED
@@ -1,29 +0,0 @@
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 CreatePlaybookApprovalInput, type CreatePlaybookRunInput, createPlaybookApproval, createPlaybookRun, deletePlaybookRun, getPlaybookApprovalByToken, getPlaybookRun, type ListPlaybookRunsOptions, listPlaybookApprovals, listPlaybookRuns, updatePlaybookApproval, updatePlaybookRun, } from './state.js';
package/dist/index.js DELETED
@@ -1,32 +0,0 @@
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-8: state layer CRUD for playbook_runs + playbook_approvals
32
- export { createPlaybookApproval, createPlaybookRun, deletePlaybookRun, getPlaybookApprovalByToken, getPlaybookRun, listPlaybookApprovals, listPlaybookRuns, updatePlaybookApproval, updatePlaybookRun, } from './state.js';