@sublang/playbook 14.0.0 → 15.0.0
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 +1 -1
- package/docs/cli.md +14 -14
- package/docs/configuration.md +15 -9
- package/package.json +1 -1
- package/reference/sdlc/branch.playbook/branch.fsm.d.ts +1 -1
- package/reference/sdlc/code.md +1 -1
- package/reference/sdlc/code.playbook/bin/launch-config.js +55 -16
- package/reference/sdlc/code.playbook/bin/playbook.js +14 -9
- package/reference/sdlc/code.playbook/bin/repository-effects.js +187 -14
- package/reference/sdlc/code.playbook/bin/run.js +12 -8
- package/reference/sdlc/code.playbook/code.fsm.d.ts +1 -1
- package/reference/sdlc/code.playbook/code.fsm.js +1 -1
- package/reference/sdlc/code.playbook/code.fsm.ts +1 -1
- package/reference/sdlc/code.playbook/code.gears.md +2 -2
- package/reference/sdlc/code.playbook/host-capabilities.d.ts +6 -0
- package/reference/sdlc/code.playbook/playbook-captain.d.ts +3 -1
- package/reference/sdlc/code.playbook/playbook-captain.js +357 -16
- package/reference/sdlc/code.playbook/playbook-captain.ts +459 -34
- package/reference/sdlc/code.playbook/session-host.d.ts +6 -3
- package/reference/sdlc/decide.md +1 -1
- package/reference/sdlc/decide.playbook/decide.fsm.d.ts +1 -1
- package/reference/sdlc/decide.playbook/decide.fsm.js +1 -1
- package/reference/sdlc/decide.playbook/decide.fsm.ts +1 -1
- package/reference/sdlc/decide.playbook/decide.gears.md +1 -1
- package/reference/sdlc/decide.playbook/decide.playbook.js +25 -1
- package/reference/sdlc/decide.playbook/decide.playbook.ts +35 -2
- package/reference/sdlc/dev.md +15 -8
- package/reference/sdlc/dev.playbook/dev.fsm.d.ts +1 -1
- package/reference/sdlc/dev.playbook/dev.fsm.js +18 -12
- package/reference/sdlc/dev.playbook/dev.fsm.ts +18 -13
- package/reference/sdlc/dev.playbook/dev.gears.md +19 -12
- package/reference/sdlc/dev.playbook/dev.playbook.js +1 -1
- package/reference/sdlc/dev.playbook/dev.playbook.ts +1 -2
- package/reference/sdlc/dev.playbook/dev.registry.js +1 -1
- package/reference/sdlc/dev.playbook/dev.registry.ts +1 -1
- package/reference/sdlc/pr.playbook/pr.fsm.d.ts +1 -1
- package/reference/sdlc/review.md +1 -1
- package/reference/sdlc/review.playbook/review.fsm.d.ts +1 -1
- package/reference/sdlc/review.playbook/review.fsm.js +1 -1
- package/reference/sdlc/review.playbook/review.fsm.ts +1 -1
- package/reference/sdlc/review.playbook/review.gears.md +1 -1
- package/slc/link.md +32 -0
- package/src/runtime.d.ts +70 -0
- package/src/runtime.js +232 -1
- package/src/runtime.ts +358 -1
- package/src/xstate-playbook-runtime.d.ts +9 -10
- package/src/xstate-playbook-runtime.js +372 -20
- package/src/xstate-playbook-runtime.ts +460 -19
- package/src/xstate-runtime.d.ts +9 -1
- package/src/xstate-runtime.js +299 -19
- package/src/xstate-runtime.ts +362 -21
package/src/runtime.d.ts
CHANGED
|
@@ -32,10 +32,65 @@ export interface CaptainResult {
|
|
|
32
32
|
export type JsonValue = null | boolean | number | string | readonly JsonValue[] | {
|
|
33
33
|
readonly [key: string]: JsonValue;
|
|
34
34
|
};
|
|
35
|
+
export declare const PLAYBOOK_FAILURE_CODES: readonly ["commit-missing", "commit-residual", "pre-existing-lost", "commits-more-than-one", "history-rewritten", "foreign-change", "observation-unstable", "attribution-ambiguous", "receipt-missing", "judge-failed", "player-failed", "aborted", "child-failed", "runtime-defect"];
|
|
36
|
+
export type PlaybookFailureCode = (typeof PLAYBOOK_FAILURE_CODES)[number];
|
|
37
|
+
/** The bounded path lists one failure code's evidence may carry (DR-063 §1). */
|
|
38
|
+
export interface PlaybookFailurePaths {
|
|
39
|
+
/** Paths changed in the worktree that no commit carries. */
|
|
40
|
+
readonly uncommitted?: readonly string[];
|
|
41
|
+
/** Pre-existing baseline paths the call changed before committing. */
|
|
42
|
+
readonly altered?: readonly string[];
|
|
43
|
+
/** Pre-existing baseline paths whose content is nowhere. */
|
|
44
|
+
readonly lost?: readonly string[];
|
|
45
|
+
/** Paths whose entry differs or is missing on either side. */
|
|
46
|
+
readonly changed?: readonly string[];
|
|
47
|
+
/** Paths omitted from the lists above by the 32-path bound. */
|
|
48
|
+
readonly truncated?: number;
|
|
49
|
+
}
|
|
50
|
+
/** The bounded error record a failure cause may carry (DR-063 §1). */
|
|
51
|
+
export interface PlaybookFailureErrorEvidence {
|
|
52
|
+
readonly name: string;
|
|
53
|
+
readonly message: string;
|
|
54
|
+
}
|
|
55
|
+
/** The closed evidence object of one failure cause (DR-063 §1). */
|
|
56
|
+
export interface PlaybookFailureEvidence {
|
|
57
|
+
readonly required?: PlaybookRepositoryDisposition;
|
|
58
|
+
readonly observed?: PlaybookRepositoryReceipt['classification'];
|
|
59
|
+
readonly baselineHead?: string;
|
|
60
|
+
readonly afterHead?: string;
|
|
61
|
+
readonly commitOid?: string;
|
|
62
|
+
readonly paths?: PlaybookFailurePaths;
|
|
63
|
+
readonly reason?: string;
|
|
64
|
+
readonly error?: PlaybookFailureErrorEvidence;
|
|
65
|
+
readonly errorCode?: string;
|
|
66
|
+
readonly roleId?: string;
|
|
67
|
+
readonly playerId?: string;
|
|
68
|
+
readonly playbookId?: string;
|
|
69
|
+
readonly cause?: PlaybookFailureCause;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Why a parked workflow failed (DR-063 §1): one code from the closed list and
|
|
73
|
+
* the closed evidence object that code names. Evidence holds repository paths,
|
|
74
|
+
* dispositions, classifications, and revision identities only — never file
|
|
75
|
+
* content, player prose, or internal call and session identities.
|
|
76
|
+
*/
|
|
77
|
+
export interface PlaybookFailureCause {
|
|
78
|
+
readonly code: PlaybookFailureCode;
|
|
79
|
+
readonly evidence: PlaybookFailureEvidence;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Validate, detach, and freeze one failure cause (DR-063 §1). The check is
|
|
83
|
+
* closed per code: exactly the evidence members that code names, nothing else,
|
|
84
|
+
* and a `child-failed` cause nests at most four deep. A value this rejects is
|
|
85
|
+
* not a cause, so a caller omits it rather than publishing an invented one.
|
|
86
|
+
*/
|
|
87
|
+
export declare function assertPlaybookFailureCause(value: unknown): PlaybookFailureCause;
|
|
35
88
|
export interface NormalizedError {
|
|
36
89
|
name: string;
|
|
37
90
|
message: string;
|
|
38
91
|
stack?: string;
|
|
92
|
+
/** DR-063 §2: present exactly when the underlying error carried a valid one. */
|
|
93
|
+
cause?: PlaybookFailureCause;
|
|
39
94
|
}
|
|
40
95
|
export type PlaybookStateValue = string | {
|
|
41
96
|
readonly [key: string]: PlaybookStateValue;
|
|
@@ -181,12 +236,23 @@ export interface PlaybookRepositoryObservation {
|
|
|
181
236
|
readonly projection: Readonly<Record<string, JsonValue>>;
|
|
182
237
|
readonly projectionDigest: string;
|
|
183
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* The fate of the baseline projection entries a governed call absorbed into
|
|
241
|
+
* its one commit, altered, or lost (DR-062). Each list holds sorted unique
|
|
242
|
+
* baseline paths and the three are pairwise disjoint.
|
|
243
|
+
*/
|
|
244
|
+
export interface PlaybookRepositoryPreExistingChanges {
|
|
245
|
+
readonly absorbed: readonly string[];
|
|
246
|
+
readonly altered: readonly string[];
|
|
247
|
+
readonly lost: readonly string[];
|
|
248
|
+
}
|
|
184
249
|
/** The fail-closed classification of one complete physical or logical receipt. */
|
|
185
250
|
export interface PlaybookRepositoryReceipt {
|
|
186
251
|
readonly classification: 'unchanged' | 'one-descendant-commit' | 'multiple-commits' | 'rewritten-or-non-descendant' | 'worktree-only-change' | 'concurrent-or-foreign-change' | 'observation-ambiguous';
|
|
187
252
|
readonly baseline: PlaybookRepositoryObservation;
|
|
188
253
|
readonly after?: PlaybookRepositoryObservation;
|
|
189
254
|
readonly commitOid?: string;
|
|
255
|
+
readonly preExisting?: PlaybookRepositoryPreExistingChanges;
|
|
190
256
|
}
|
|
191
257
|
/** One durably ordered physical governed-player boundary (DR-040). */
|
|
192
258
|
export interface PlaybookEffectBoundary {
|
|
@@ -324,9 +390,13 @@ export interface PlaybookRuntimeSnapshot {
|
|
|
324
390
|
};
|
|
325
391
|
suspendedCall?: PlaybookSuspendedCall;
|
|
326
392
|
}
|
|
393
|
+
export type PlaybookControlStanding = 'ready' | 'no-op' | 'blocked';
|
|
394
|
+
export type PlaybookControlActionReason = 'receipt-complete';
|
|
327
395
|
export interface PlaybookControlAction {
|
|
328
396
|
id: string;
|
|
329
397
|
label: string;
|
|
398
|
+
standing?: PlaybookControlStanding;
|
|
399
|
+
reason?: PlaybookControlActionReason;
|
|
330
400
|
}
|
|
331
401
|
export interface PlaybookControlView {
|
|
332
402
|
state: PlaybookState;
|
package/src/runtime.js
CHANGED
|
@@ -7,4 +7,235 @@
|
|
|
7
7
|
// one way: linked playbook runtimes (e.g. code.playbook.ts) import and
|
|
8
8
|
// re-export these names rather than redefining them
|
|
9
9
|
// (PBRT-5, PBRT-34, DR-004 Addendum A4).
|
|
10
|
-
|
|
10
|
+
// DR-063 §1: the closed list of failure codes. There is no unknown code — an
|
|
11
|
+
// unrecognized failure is `runtime-defect` carrying its message as `reason` —
|
|
12
|
+
// so a host catalogue is complete exactly when it covers this list.
|
|
13
|
+
export const PLAYBOOK_FAILURE_CODES = [
|
|
14
|
+
'commit-missing',
|
|
15
|
+
'commit-residual',
|
|
16
|
+
'pre-existing-lost',
|
|
17
|
+
'commits-more-than-one',
|
|
18
|
+
'history-rewritten',
|
|
19
|
+
'foreign-change',
|
|
20
|
+
'observation-unstable',
|
|
21
|
+
'attribution-ambiguous',
|
|
22
|
+
'receipt-missing',
|
|
23
|
+
'judge-failed',
|
|
24
|
+
'player-failed',
|
|
25
|
+
'aborted',
|
|
26
|
+
'child-failed',
|
|
27
|
+
'runtime-defect',
|
|
28
|
+
];
|
|
29
|
+
/** Evidence members admitted for each code, in the order a sentence reads them. */
|
|
30
|
+
const FAILURE_EVIDENCE_MEMBERS = {
|
|
31
|
+
'commit-missing': {
|
|
32
|
+
required: ['required', 'observed', 'baselineHead', 'afterHead', 'paths'],
|
|
33
|
+
optional: [],
|
|
34
|
+
paths: ['uncommitted'],
|
|
35
|
+
},
|
|
36
|
+
'commit-residual': {
|
|
37
|
+
required: ['required', 'observed', 'baselineHead', 'afterHead', 'paths'],
|
|
38
|
+
optional: ['commitOid'],
|
|
39
|
+
paths: ['uncommitted', 'altered'],
|
|
40
|
+
},
|
|
41
|
+
'pre-existing-lost': {
|
|
42
|
+
required: ['required', 'observed', 'baselineHead', 'paths'],
|
|
43
|
+
optional: ['afterHead'],
|
|
44
|
+
paths: ['lost'],
|
|
45
|
+
},
|
|
46
|
+
'commits-more-than-one': {
|
|
47
|
+
required: ['required', 'observed', 'baselineHead', 'afterHead'],
|
|
48
|
+
optional: [],
|
|
49
|
+
paths: [],
|
|
50
|
+
},
|
|
51
|
+
'history-rewritten': {
|
|
52
|
+
required: ['required', 'observed', 'baselineHead', 'afterHead'],
|
|
53
|
+
optional: [],
|
|
54
|
+
paths: [],
|
|
55
|
+
},
|
|
56
|
+
'foreign-change': {
|
|
57
|
+
required: ['required', 'observed', 'baselineHead', 'afterHead', 'paths'],
|
|
58
|
+
optional: [],
|
|
59
|
+
paths: ['changed'],
|
|
60
|
+
},
|
|
61
|
+
'observation-unstable': {
|
|
62
|
+
required: ['required', 'observed', 'baselineHead'],
|
|
63
|
+
optional: [],
|
|
64
|
+
paths: [],
|
|
65
|
+
},
|
|
66
|
+
'attribution-ambiguous': {
|
|
67
|
+
required: ['required', 'observed', 'baselineHead'],
|
|
68
|
+
optional: ['afterHead'],
|
|
69
|
+
paths: [],
|
|
70
|
+
},
|
|
71
|
+
'receipt-missing': { required: ['baselineHead'], optional: [], paths: [] },
|
|
72
|
+
'judge-failed': { required: ['reason'], optional: ['error'], paths: [] },
|
|
73
|
+
'player-failed': {
|
|
74
|
+
required: ['roleId', 'error'],
|
|
75
|
+
optional: ['playerId', 'errorCode'],
|
|
76
|
+
paths: [],
|
|
77
|
+
},
|
|
78
|
+
aborted: { required: [], optional: [], paths: [] },
|
|
79
|
+
'child-failed': { required: ['playbookId'], optional: ['cause'], paths: [] },
|
|
80
|
+
'runtime-defect': { required: ['reason'], optional: [], paths: [] },
|
|
81
|
+
};
|
|
82
|
+
const FAILURE_PATH_LIMIT = 32;
|
|
83
|
+
const FAILURE_CAUSE_DEPTH_LIMIT = 4;
|
|
84
|
+
const REPOSITORY_DISPOSITIONS = [
|
|
85
|
+
'unchanged',
|
|
86
|
+
'one-descendant-commit',
|
|
87
|
+
'deferred',
|
|
88
|
+
];
|
|
89
|
+
const RECEIPT_CLASSIFICATIONS = [
|
|
90
|
+
'unchanged',
|
|
91
|
+
'one-descendant-commit',
|
|
92
|
+
'multiple-commits',
|
|
93
|
+
'rewritten-or-non-descendant',
|
|
94
|
+
'worktree-only-change',
|
|
95
|
+
'concurrent-or-foreign-change',
|
|
96
|
+
'observation-ambiguous',
|
|
97
|
+
];
|
|
98
|
+
function failureCauseRecord(value, path) {
|
|
99
|
+
if (value === null ||
|
|
100
|
+
typeof value !== 'object' ||
|
|
101
|
+
Array.isArray(value) ||
|
|
102
|
+
(Object.getPrototypeOf(value) !== Object.prototype &&
|
|
103
|
+
Object.getPrototypeOf(value) !== null)) {
|
|
104
|
+
throw new TypeError(`${path} must be a plain object`);
|
|
105
|
+
}
|
|
106
|
+
return value;
|
|
107
|
+
}
|
|
108
|
+
function failureCauseString(value, path) {
|
|
109
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
110
|
+
throw new TypeError(`${path} must be a nonempty string`);
|
|
111
|
+
}
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
function failureCausePathList(value, path) {
|
|
115
|
+
if (!Array.isArray(value))
|
|
116
|
+
throw new TypeError(`${path} must be an array`);
|
|
117
|
+
if (value.length > FAILURE_PATH_LIMIT) {
|
|
118
|
+
throw new TypeError(`${path} must hold at most ${FAILURE_PATH_LIMIT} paths`);
|
|
119
|
+
}
|
|
120
|
+
const paths = value.map((entry, index) => failureCauseString(entry, `${path}[${index}]`));
|
|
121
|
+
for (const [index, entry] of paths.entries()) {
|
|
122
|
+
if (index > 0 && !(paths[index - 1] < entry)) {
|
|
123
|
+
throw new TypeError(`${path} must be sorted and free of duplicates`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return Object.freeze(paths);
|
|
127
|
+
}
|
|
128
|
+
function failureCausePaths(value, admitted, path) {
|
|
129
|
+
const record = failureCauseRecord(value, path);
|
|
130
|
+
const allowed = new Set([...admitted, 'truncated']);
|
|
131
|
+
for (const key of Object.keys(record)) {
|
|
132
|
+
if (!allowed.has(key)) {
|
|
133
|
+
throw new TypeError(`${path} must not carry ${JSON.stringify(key)}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const paths = {};
|
|
137
|
+
for (const member of admitted) {
|
|
138
|
+
if (!Object.hasOwn(record, member)) {
|
|
139
|
+
throw new TypeError(`${path}.${member} is required`);
|
|
140
|
+
}
|
|
141
|
+
paths[member] = failureCausePathList(record[member], `${path}.${member}`);
|
|
142
|
+
}
|
|
143
|
+
if (Object.hasOwn(record, 'truncated')) {
|
|
144
|
+
const truncated = record.truncated;
|
|
145
|
+
if (typeof truncated !== 'number' ||
|
|
146
|
+
!Number.isSafeInteger(truncated) ||
|
|
147
|
+
truncated <= 0) {
|
|
148
|
+
throw new TypeError(`${path}.truncated must be a positive integer`);
|
|
149
|
+
}
|
|
150
|
+
paths.truncated = truncated;
|
|
151
|
+
}
|
|
152
|
+
return Object.freeze(paths);
|
|
153
|
+
}
|
|
154
|
+
function failureCauseErrorEvidence(value, path) {
|
|
155
|
+
const record = failureCauseRecord(value, path);
|
|
156
|
+
for (const key of Object.keys(record)) {
|
|
157
|
+
if (key !== 'name' && key !== 'message') {
|
|
158
|
+
throw new TypeError(`${path} must not carry ${JSON.stringify(key)}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (typeof record.message !== 'string') {
|
|
162
|
+
throw new TypeError(`${path}.message must be a string`);
|
|
163
|
+
}
|
|
164
|
+
return Object.freeze({
|
|
165
|
+
name: failureCauseString(record.name, `${path}.name`),
|
|
166
|
+
message: record.message,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
function validateFailureCause(value, path, depth) {
|
|
170
|
+
if (depth > FAILURE_CAUSE_DEPTH_LIMIT) {
|
|
171
|
+
throw new TypeError(`${path} must nest at most ${FAILURE_CAUSE_DEPTH_LIMIT} causes`);
|
|
172
|
+
}
|
|
173
|
+
const record = failureCauseRecord(value, path);
|
|
174
|
+
for (const key of Object.keys(record)) {
|
|
175
|
+
if (key !== 'code' && key !== 'evidence') {
|
|
176
|
+
throw new TypeError(`${path} must not carry ${JSON.stringify(key)}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const code = record.code;
|
|
180
|
+
if (typeof code !== 'string' ||
|
|
181
|
+
!PLAYBOOK_FAILURE_CODES.includes(code)) {
|
|
182
|
+
throw new TypeError(`${path}.code must be one of the declared codes`);
|
|
183
|
+
}
|
|
184
|
+
const spec = FAILURE_EVIDENCE_MEMBERS[code];
|
|
185
|
+
const evidenceRecord = failureCauseRecord(record.evidence, `${path}.evidence`);
|
|
186
|
+
const allowed = new Set([...spec.required, ...spec.optional]);
|
|
187
|
+
for (const key of Object.keys(evidenceRecord)) {
|
|
188
|
+
if (!allowed.has(key)) {
|
|
189
|
+
throw new TypeError(`${path}.evidence must not carry ${JSON.stringify(key)}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const evidence = {};
|
|
193
|
+
for (const member of [...spec.required, ...spec.optional]) {
|
|
194
|
+
const required = spec.required.includes(member);
|
|
195
|
+
if (!Object.hasOwn(evidenceRecord, member)) {
|
|
196
|
+
if (required) {
|
|
197
|
+
throw new TypeError(`${path}.evidence.${member} is required`);
|
|
198
|
+
}
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const memberValue = evidenceRecord[member];
|
|
202
|
+
const memberPath = `${path}.evidence.${member}`;
|
|
203
|
+
if (member === 'required') {
|
|
204
|
+
if (!REPOSITORY_DISPOSITIONS.includes(memberValue)) {
|
|
205
|
+
throw new TypeError(`${memberPath} must be a repository disposition`);
|
|
206
|
+
}
|
|
207
|
+
evidence[member] = memberValue;
|
|
208
|
+
}
|
|
209
|
+
else if (member === 'observed') {
|
|
210
|
+
if (!RECEIPT_CLASSIFICATIONS.includes(memberValue)) {
|
|
211
|
+
throw new TypeError(`${memberPath} must be a receipt classification`);
|
|
212
|
+
}
|
|
213
|
+
evidence[member] = memberValue;
|
|
214
|
+
}
|
|
215
|
+
else if (member === 'paths') {
|
|
216
|
+
evidence[member] = failureCausePaths(memberValue, spec.paths, memberPath);
|
|
217
|
+
}
|
|
218
|
+
else if (member === 'error') {
|
|
219
|
+
evidence[member] = failureCauseErrorEvidence(memberValue, memberPath);
|
|
220
|
+
}
|
|
221
|
+
else if (member === 'cause') {
|
|
222
|
+
evidence[member] = validateFailureCause(memberValue, memberPath, depth + 1);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
evidence[member] = failureCauseString(memberValue, memberPath);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return Object.freeze({
|
|
229
|
+
code: code,
|
|
230
|
+
evidence: Object.freeze(evidence),
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Validate, detach, and freeze one failure cause (DR-063 §1). The check is
|
|
235
|
+
* closed per code: exactly the evidence members that code names, nothing else,
|
|
236
|
+
* and a `child-failed` cause nests at most four deep. A value this rejects is
|
|
237
|
+
* not a cause, so a caller omits it rather than publishing an invented one.
|
|
238
|
+
*/
|
|
239
|
+
export function assertPlaybookFailureCause(value) {
|
|
240
|
+
return validateFailureCause(value, 'playbook failure cause', 1);
|
|
241
|
+
}
|