@sublang/playbook 17.8.1 → 17.10.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/docs/configuration.md +29 -2
- package/package.json +2 -2
- package/reference/sdlc/captain.md +1 -1
- package/reference/sdlc/captain.playbook/captain.fsm.js +1 -1
- package/reference/sdlc/captain.playbook/captain.fsm.ts +1 -1
- package/reference/sdlc/captain.playbook/captain.gears.md +1 -1
- package/reference/sdlc/code.playbook/bin/launch-config.js +55 -13
- package/reference/sdlc/code.playbook/bin/repository-effects.js +26 -2
- package/reference/sdlc/code.playbook/bin/session-store.js +3 -0
- package/reference/sdlc/code.playbook/host-capabilities.d.ts +1 -0
- package/reference/sdlc/code.playbook/playbook-captain.d.ts +2 -1
- package/reference/sdlc/code.playbook/playbook-captain.js +170 -48
- package/reference/sdlc/code.playbook/playbook-captain.ts +227 -63
- package/slc/link.md +25 -3
- package/src/runtime.d.ts +13 -0
- package/src/runtime.ts +14 -0
- package/src/xstate-playbook-runtime.d.ts +4 -3
- package/src/xstate-playbook-runtime.js +163 -19
- package/src/xstate-playbook-runtime.ts +197 -18
- package/src/xstate-runtime.d.ts +7 -5
- package/src/xstate-runtime.js +40 -9
- package/src/xstate-runtime.ts +53 -24
package/src/xstate-runtime.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
|
|
14
14
|
import { assertPlaybookFailureCause } from './runtime.js';
|
|
15
15
|
import type {
|
|
16
|
+
PlaybookBossExchange,
|
|
16
17
|
CaptainResult,
|
|
17
18
|
JsonValue,
|
|
18
19
|
NormalizedError,
|
|
@@ -992,6 +993,20 @@ function effectInteger(value: unknown, path: string, minimum = 0): number {
|
|
|
992
993
|
return value as number;
|
|
993
994
|
}
|
|
994
995
|
|
|
996
|
+
// DR-090 §4: an observation's branch is evidence for the Boss only; two
|
|
997
|
+
// observations of one repository state are equal whatever branch each names.
|
|
998
|
+
function observedStatesEqual(
|
|
999
|
+
left: PlaybookRepositoryObservation,
|
|
1000
|
+
right: PlaybookRepositoryObservation,
|
|
1001
|
+
): boolean {
|
|
1002
|
+
const { branch: _left, ...leftState } = left;
|
|
1003
|
+
const { branch: _right, ...rightState } = right;
|
|
1004
|
+
return jsonValuesEqual(
|
|
1005
|
+
leftState as unknown as JsonValue,
|
|
1006
|
+
rightState as unknown as JsonValue,
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
995
1010
|
function jsonValuesEqual(left: JsonValue, right: JsonValue): boolean {
|
|
996
1011
|
if (Object.is(left, right)) return true;
|
|
997
1012
|
if (Array.isArray(left) || Array.isArray(right)) {
|
|
@@ -1110,7 +1125,7 @@ function effectObservation(
|
|
|
1110
1125
|
if (!isRecord(value)) throw new TypeError(`${path} must be an object`);
|
|
1111
1126
|
rejectUnknownKeys(
|
|
1112
1127
|
value,
|
|
1113
|
-
['worktree', 'gitDir', 'head', 'projection', 'projectionDigest'],
|
|
1128
|
+
['worktree', 'gitDir', 'head', 'branch', 'projection', 'projectionDigest'],
|
|
1114
1129
|
path,
|
|
1115
1130
|
);
|
|
1116
1131
|
requireNonEmptyString(value.worktree, `${path}.worktree`);
|
|
@@ -1119,6 +1134,10 @@ function effectObservation(
|
|
|
1119
1134
|
if (!EFFECT_OID_PATTERN.test(head)) {
|
|
1120
1135
|
throw new TypeError(`${path}.head must be a canonical Git commit OID`);
|
|
1121
1136
|
}
|
|
1137
|
+
// DR-090 §4: the branch HEAD names, evidence for the Boss only.
|
|
1138
|
+
if (own(value, 'branch')) {
|
|
1139
|
+
requireNonEmptyString(value.branch, `${path}.branch`);
|
|
1140
|
+
}
|
|
1122
1141
|
if (!isRecord(value.projection)) {
|
|
1123
1142
|
throw new TypeError(`${path}.projection must be a path-keyed object`);
|
|
1124
1143
|
}
|
|
@@ -1252,10 +1271,7 @@ function effectReceipt(
|
|
|
1252
1271
|
if (
|
|
1253
1272
|
classification === 'unchanged' &&
|
|
1254
1273
|
after !== undefined &&
|
|
1255
|
-
!
|
|
1256
|
-
baseline as unknown as JsonValue,
|
|
1257
|
-
after as unknown as JsonValue,
|
|
1258
|
-
)
|
|
1274
|
+
!observedStatesEqual(baseline, after)
|
|
1259
1275
|
) {
|
|
1260
1276
|
throw new TypeError(`${path} classified unchanged observations that differ`);
|
|
1261
1277
|
}
|
|
@@ -1370,8 +1386,8 @@ export interface PlaybookSemanticOutcomeSpec {
|
|
|
1370
1386
|
|
|
1371
1387
|
/**
|
|
1372
1388
|
* The JSON value shape a compiled machine admits for one semantic-owned field
|
|
1373
|
-
* (DR-089). A field the machine admits as a string
|
|
1374
|
-
* machine does not reveal
|
|
1389
|
+
* (DR-089). A field the machine admits as a string is `string`; a field whose
|
|
1390
|
+
* shape the machine does not reveal has none (DR-091).
|
|
1375
1391
|
*/
|
|
1376
1392
|
export type PlaybookSemanticValueShape =
|
|
1377
1393
|
| 'string'
|
|
@@ -1382,8 +1398,9 @@ export type PlaybookSemanticValueShape =
|
|
|
1382
1398
|
| 'boolean';
|
|
1383
1399
|
|
|
1384
1400
|
/**
|
|
1385
|
-
* Per outcome, the
|
|
1386
|
-
*
|
|
1401
|
+
* Per outcome, the shapes of its semantic-owned fields; a field left out has
|
|
1402
|
+
* no revealed shape and takes any JSON value, the machine's own validator
|
|
1403
|
+
* deciding it (DR-091).
|
|
1387
1404
|
*/
|
|
1388
1405
|
export type PlaybookSemanticValueShapes = Readonly<
|
|
1389
1406
|
Record<string, Readonly<Record<string, PlaybookSemanticValueShape>>>
|
|
@@ -1444,7 +1461,8 @@ export interface PlaybookSemanticEvidenceInput {
|
|
|
1444
1461
|
readonly runtimeFields?: unknown;
|
|
1445
1462
|
/**
|
|
1446
1463
|
* DR-089: the shape the compiled machine admits for each semantic-owned
|
|
1447
|
-
* field, keyed by outcome;
|
|
1464
|
+
* field, keyed by outcome; a field it leaves out takes any JSON value
|
|
1465
|
+
* (DR-091).
|
|
1448
1466
|
*/
|
|
1449
1467
|
readonly semanticValueShapes?: PlaybookSemanticValueShapes;
|
|
1450
1468
|
}
|
|
@@ -1548,14 +1566,13 @@ function snapshotSemanticCandidate(
|
|
|
1548
1566
|
);
|
|
1549
1567
|
}
|
|
1550
1568
|
// DR-089: each semantic-owned field has the shape the compiled machine
|
|
1551
|
-
// admits for it
|
|
1569
|
+
// admits for it. DR-091: a field whose shape the machine did not reveal
|
|
1570
|
+
// takes any JSON value, and the machine's own validator decides it.
|
|
1552
1571
|
const outcomeShapes =
|
|
1553
1572
|
shapes !== undefined && own(shapes, guard) ? shapes[guard] : undefined;
|
|
1554
1573
|
for (const field of semanticFields) {
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
? outcomeShapes[field]!
|
|
1558
|
-
: 'string';
|
|
1574
|
+
if (outcomeShapes === undefined || !own(outcomeShapes, field)) continue;
|
|
1575
|
+
const shape = outcomeShapes[field]!;
|
|
1559
1576
|
if (!isPlaybookSemanticValueShape(detached[field], shape)) {
|
|
1560
1577
|
semanticCandidateStructureError(
|
|
1561
1578
|
shape === 'string'
|
|
@@ -2099,7 +2116,7 @@ function effectBoundary(value: unknown, index: number): PlaybookEffectBoundary {
|
|
|
2099
2116
|
}
|
|
2100
2117
|
if (own(value, 'restored')) {
|
|
2101
2118
|
const restored = effectObservation(value.restored, `${path}.restored`);
|
|
2102
|
-
if (!
|
|
2119
|
+
if (!observedStatesEqual(restored, baseline) ||
|
|
2103
2120
|
!isRecord(value.physicalReceipt) || after?.head !== baseline.head ||
|
|
2104
2121
|
(value.dispositions as string[]).some((item) => item !== 'unchanged') ||
|
|
2105
2122
|
value.cohortId !== undefined || value.logicalOperationId !== undefined) {
|
|
@@ -2237,10 +2254,7 @@ function effectLogicalOperation(
|
|
|
2237
2254
|
if (
|
|
2238
2255
|
previous.physicalReceipt === undefined ||
|
|
2239
2256
|
previous.after === undefined ||
|
|
2240
|
-
!
|
|
2241
|
-
boundary.baseline as unknown as JsonValue,
|
|
2242
|
-
previous.after as unknown as JsonValue,
|
|
2243
|
-
)
|
|
2257
|
+
!observedStatesEqual(boundary.baseline, previous.after)
|
|
2244
2258
|
) {
|
|
2245
2259
|
throw new TypeError(
|
|
2246
2260
|
`${path}.boundaryIds must form one completed checkpoint chain`,
|
|
@@ -2259,10 +2273,7 @@ function effectLogicalOperation(
|
|
|
2259
2273
|
);
|
|
2260
2274
|
if (
|
|
2261
2275
|
latestAfter === undefined ||
|
|
2262
|
-
!
|
|
2263
|
-
checkpoint as unknown as JsonValue,
|
|
2264
|
-
latestAfter as unknown as JsonValue,
|
|
2265
|
-
)
|
|
2276
|
+
!observedStatesEqual(checkpoint, latestAfter)
|
|
2266
2277
|
) {
|
|
2267
2278
|
throw new TypeError(`${path}.checkpoint must equal its latest boundary after`);
|
|
2268
2279
|
}
|
|
@@ -2737,6 +2748,7 @@ export function assertPlaybookRuntimeSnapshot(
|
|
|
2737
2748
|
'failedEffectAttempt',
|
|
2738
2749
|
'recoveryCheckpoint',
|
|
2739
2750
|
'suspendedCall',
|
|
2751
|
+
'bossExchanges',
|
|
2740
2752
|
],
|
|
2741
2753
|
'runtime snapshot',
|
|
2742
2754
|
);
|
|
@@ -3044,6 +3056,22 @@ export function assertPlaybookRuntimeSnapshot(
|
|
|
3044
3056
|
}
|
|
3045
3057
|
recoveryCheckpoint = Object.freeze({stateId, prompt, boundaryPrefix, machine: snapshotJsonValue(checkpoint.machine), ...(own(checkpoint, 'id') ? { id: effectUuid(checkpoint.id, 'recoveryCheckpoint.id') } : {}), ...(own(checkpoint, 'result') ? { result: snapshotJsonValue(checkpoint.result) } : {}), ...(checkpoint.delivered === true ? { delivered: true as const } : {})});
|
|
3046
3058
|
}
|
|
3059
|
+
// DR-090 §1: the run's Boss exchanges, present only when there is one.
|
|
3060
|
+
let bossExchanges: readonly PlaybookBossExchange[] | undefined;
|
|
3061
|
+
if (own(snapshot, 'bossExchanges')) {
|
|
3062
|
+
const list = snapshot.bossExchanges;
|
|
3063
|
+
if (!Array.isArray(list) || list.length === 0) {
|
|
3064
|
+
throw new TypeError('runtime snapshot bossExchanges must be a nonempty list');
|
|
3065
|
+
}
|
|
3066
|
+
bossExchanges = Object.freeze(list.map((exchange: unknown, index: number) => {
|
|
3067
|
+
const path = `runtime snapshot bossExchanges[${index}]`;
|
|
3068
|
+
if (!isRecord(exchange)) throw new TypeError(`${path} must be an object`);
|
|
3069
|
+
rejectUnknownKeys(exchange, ['question', 'reply'], path);
|
|
3070
|
+
const question = requireNonEmptyString(exchange.question, `${path}.question`);
|
|
3071
|
+
const reply = requireNonEmptyString(exchange.reply, `${path}.reply`);
|
|
3072
|
+
return Object.freeze({ question, reply });
|
|
3073
|
+
}));
|
|
3074
|
+
}
|
|
3047
3075
|
const fields = {
|
|
3048
3076
|
playbookId,
|
|
3049
3077
|
machine,
|
|
@@ -3067,6 +3095,7 @@ export function assertPlaybookRuntimeSnapshot(
|
|
|
3067
3095
|
schemaVersion: 4,
|
|
3068
3096
|
...fields,
|
|
3069
3097
|
...(suspendedCall === undefined ? {} : { suspendedCall }),
|
|
3098
|
+
...(bossExchanges === undefined ? {} : { bossExchanges }),
|
|
3070
3099
|
});
|
|
3071
3100
|
}
|
|
3072
3101
|
|