@sublang/playbook 17.8.0 → 17.9.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/package.json +1 -1
- package/reference/sdlc/captain.md +3 -3
- package/reference/sdlc/captain.playbook/captain.fsm.js +3 -3
- package/reference/sdlc/captain.playbook/captain.fsm.ts +3 -3
- package/reference/sdlc/captain.playbook/captain.gears.md +3 -3
- package/reference/sdlc/code.playbook/bin/repository-effects.js +26 -2
- package/reference/sdlc/code.playbook/host-capabilities.d.ts +1 -0
- package/reference/sdlc/code.playbook/playbook-captain.js +157 -46
- package/reference/sdlc/code.playbook/playbook-captain.ts +210 -59
- 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 +152 -14
- package/src/xstate-playbook-runtime.ts +186 -13
- package/src/xstate-runtime.d.ts +7 -5
- package/src/xstate-runtime.js +40 -9
- package/src/xstate-runtime.ts +53 -24
|
@@ -1414,6 +1414,31 @@ function boundedPathList(paths: readonly string[]): string {
|
|
|
1414
1414
|
: printed.join(', ');
|
|
1415
1415
|
}
|
|
1416
1416
|
|
|
1417
|
+
// DR-090 §5: the report lines the shell appends to a reply carry marks and
|
|
1418
|
+
// identifiers, never an English sentence, so a reply in any language reads
|
|
1419
|
+
// them as structure. The settlement facts a model reads keep their words.
|
|
1420
|
+
const MARK_DONE = '✓';
|
|
1421
|
+
const MARK_FAILED = '✕';
|
|
1422
|
+
const MARK_TO = '⟶';
|
|
1423
|
+
const MARK_BRANCH = '⎇';
|
|
1424
|
+
const MARK_SEP = '·';
|
|
1425
|
+
const MARK_READY = '▸';
|
|
1426
|
+
const MARK_NOT_READY = '▹';
|
|
1427
|
+
|
|
1428
|
+
/** At most 24 items, then `+<n>` for the rest (DR-090 §5). */
|
|
1429
|
+
function markedList(items: readonly string[], separator = ', ', more = 0): string {
|
|
1430
|
+
const printed = items.slice(0, CARRIED_CHANGE_PATH_LIMIT);
|
|
1431
|
+
const omitted = items.length - printed.length + more;
|
|
1432
|
+
if (printed.length === 0) return omitted > 0 ? `+${omitted}` : '';
|
|
1433
|
+
return omitted > 0
|
|
1434
|
+
? `${printed.join(separator)}${separator}+${omitted}`
|
|
1435
|
+
: printed.join(separator);
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
function markedBranch(branch: string | undefined): string {
|
|
1439
|
+
return branch === undefined ? '' : ` ${MARK_BRANCH} ${branch}`;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1417
1442
|
/**
|
|
1418
1443
|
* The carried pre-existing changes of the boundaries completed after
|
|
1419
1444
|
* `boundaryPrefixCount`, one entry per commit OID in boundary sequence order.
|
|
@@ -1499,6 +1524,14 @@ interface CommitBeforeFailure {
|
|
|
1499
1524
|
readonly commitOid: string;
|
|
1500
1525
|
/** Every new commit, oldest first, where the step made more than one (DR-088). */
|
|
1501
1526
|
readonly commits?: readonly string[];
|
|
1527
|
+
/** The branch the commits landed on, where the receipt names one (DR-090 §4). */
|
|
1528
|
+
readonly branch?: string;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
/** DR-090 §4: where a step's commits landed, as its receipt names it. */
|
|
1532
|
+
function onBranch(receipt: PlaybookRepositoryReceipt): string {
|
|
1533
|
+
const branch = receipt.after?.branch;
|
|
1534
|
+
return branch === undefined ? '' : ` on branch ${branch}`;
|
|
1502
1535
|
}
|
|
1503
1536
|
|
|
1504
1537
|
function commitsBeforeFailure(
|
|
@@ -1534,6 +1567,9 @@ function commitsBeforeFailure(
|
|
|
1534
1567
|
baselineHead: receipt.baseline.head,
|
|
1535
1568
|
commitOid: receipt.commitOid,
|
|
1536
1569
|
...(receipt.commits === undefined ? {} : { commits: receipt.commits }),
|
|
1570
|
+
...(receipt.after?.branch === undefined
|
|
1571
|
+
? {}
|
|
1572
|
+
: { branch: receipt.after.branch }),
|
|
1537
1573
|
});
|
|
1538
1574
|
}
|
|
1539
1575
|
return commits;
|
|
@@ -1544,29 +1580,35 @@ function commitBeforeFailureClause({
|
|
|
1544
1580
|
baselineHead,
|
|
1545
1581
|
commitOid,
|
|
1546
1582
|
commits,
|
|
1583
|
+
branch,
|
|
1547
1584
|
}: CommitBeforeFailure): string {
|
|
1548
1585
|
const made =
|
|
1549
1586
|
commits === undefined
|
|
1550
1587
|
? 'made one new commit'
|
|
1551
1588
|
: `made ${commits.length} new commits, oldest first: ${boundedPathList(commits)}`;
|
|
1552
|
-
|
|
1589
|
+
const landed = branch === undefined ? '' : ` on branch ${branch}`;
|
|
1590
|
+
return `${made}; HEAD moved from ${baselineHead} to the proven commit ${commitOid}${landed}.`;
|
|
1553
1591
|
}
|
|
1554
1592
|
|
|
1555
1593
|
function commitBeforeFailureFact(commit: CommitBeforeFailure): string {
|
|
1556
1594
|
return `Before the failure, a step ${commitBeforeFailureClause(commit)}`;
|
|
1557
1595
|
}
|
|
1558
1596
|
|
|
1597
|
+
// DR-090 §5: one marked line per receipt, `✓ <baselineHead> ⟶ <commits>`
|
|
1598
|
+
// and the branch they landed on.
|
|
1559
1599
|
function commitsBeforeFailureReport(
|
|
1560
1600
|
commits: readonly CommitBeforeFailure[],
|
|
1561
1601
|
): string | undefined {
|
|
1562
1602
|
if (commits.length === 0) return undefined;
|
|
1563
|
-
return
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1603
|
+
return commits
|
|
1604
|
+
.map(
|
|
1605
|
+
({ baselineHead, commitOid, commits: listed, branch }) =>
|
|
1606
|
+
`${MARK_DONE} ${baselineHead} ${MARK_TO} ${markedList(
|
|
1607
|
+
listed ?? [commitOid],
|
|
1608
|
+
` ${MARK_SEP} `,
|
|
1609
|
+
)}${markedBranch(branch)}`,
|
|
1610
|
+
)
|
|
1611
|
+
.join('\n');
|
|
1570
1612
|
}
|
|
1571
1613
|
|
|
1572
1614
|
/**
|
|
@@ -1583,19 +1625,29 @@ function finalTextNamesCommit(finalText: string, oid: string): boolean {
|
|
|
1583
1625
|
return false;
|
|
1584
1626
|
}
|
|
1585
1627
|
|
|
1628
|
+
/** One step's commit statement and the reply's marked line for it (DR-088 §3, DR-090 §4, §5). */
|
|
1629
|
+
interface StepCommitStatement {
|
|
1630
|
+
readonly text: string;
|
|
1631
|
+
/** The marked line appended to the presented reply, where one is. */
|
|
1632
|
+
readonly shown?: string;
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1586
1635
|
/**
|
|
1587
|
-
* DR-088 §3: what the steps completed after `boundaryPrefixCount`
|
|
1588
|
-
* one statement each, in boundary sequence order
|
|
1589
|
-
*
|
|
1590
|
-
*
|
|
1591
|
-
*
|
|
1592
|
-
*
|
|
1636
|
+
* DR-088 §3, DR-090 §4: what the steps completed after `boundaryPrefixCount`
|
|
1637
|
+
* committed, one statement each, in boundary sequence order, naming the
|
|
1638
|
+
* branch where the receipt names one: every new commit where a receipt
|
|
1639
|
+
* proves more than one, the commit already at HEAD where a step whose
|
|
1640
|
+
* outcomes all commit or ask named it over an `unchanged` receipt, and —
|
|
1641
|
+
* as a fact for the closing reply only, and only where `oneCommit` asks —
|
|
1642
|
+
* the one new commit of a step that made one. A step in a deferred chain
|
|
1643
|
+
* reads the chain's cumulative receipt at its last boundary.
|
|
1593
1644
|
*/
|
|
1594
1645
|
function stepCommitStatements(
|
|
1595
1646
|
ledger: PlaybookEffectLedger,
|
|
1596
1647
|
boundaryPrefixCount: number,
|
|
1597
|
-
|
|
1598
|
-
|
|
1648
|
+
oneCommit = true,
|
|
1649
|
+
): readonly StepCommitStatement[] {
|
|
1650
|
+
const statements: StepCommitStatement[] = [];
|
|
1599
1651
|
for (const boundary of ledger.boundaries) {
|
|
1600
1652
|
if (boundary.sequence <= boundaryPrefixCount) continue;
|
|
1601
1653
|
let receipt = boundary.physicalReceipt;
|
|
@@ -1611,9 +1663,23 @@ function stepCommitStatements(
|
|
|
1611
1663
|
receipt.classification === 'one-descendant-commit' &&
|
|
1612
1664
|
receipt.commits !== undefined
|
|
1613
1665
|
) {
|
|
1614
|
-
statements.push(
|
|
1615
|
-
`The step made ${receipt.commits.length} new commits, oldest first: ${boundedPathList(receipt.commits)}.`,
|
|
1616
|
-
|
|
1666
|
+
statements.push({
|
|
1667
|
+
text: `The step made ${receipt.commits.length} new commits${onBranch(receipt)}, oldest first: ${boundedPathList(receipt.commits)}.`,
|
|
1668
|
+
shown: `${MARK_DONE} ${markedList(receipt.commits, ` ${MARK_SEP} `)}${markedBranch(receipt.after?.branch)}`,
|
|
1669
|
+
});
|
|
1670
|
+
continue;
|
|
1671
|
+
}
|
|
1672
|
+
if (
|
|
1673
|
+
receipt.classification === 'one-descendant-commit' &&
|
|
1674
|
+
receipt.commitOid !== undefined
|
|
1675
|
+
) {
|
|
1676
|
+
if (oneCommit) {
|
|
1677
|
+
statements.push({
|
|
1678
|
+
text: `The step made one new commit, ${receipt.commitOid}${
|
|
1679
|
+
receipt.after?.branch === undefined ? '' : `,${onBranch(receipt)}`
|
|
1680
|
+
}.`,
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1617
1683
|
continue;
|
|
1618
1684
|
}
|
|
1619
1685
|
const candidate = boundary.semanticCandidate;
|
|
@@ -1634,9 +1700,10 @@ function stepCommitStatements(
|
|
|
1634
1700
|
guard !== BOSS_QUESTION_OUTCOME &&
|
|
1635
1701
|
finalTextNamesCommit(boundary.finalText?.trim() ?? '', head)
|
|
1636
1702
|
) {
|
|
1637
|
-
statements.push(
|
|
1638
|
-
`The step made no new commit; it named ${head}, already at HEAD, as its work.`,
|
|
1639
|
-
|
|
1703
|
+
statements.push({
|
|
1704
|
+
text: `The step made no new commit; it named ${head}, already at HEAD${onBranch(receipt)}, as its work.`,
|
|
1705
|
+
shown: `${MARK_DONE} = ${head}${markedBranch(receipt.after?.branch)}`,
|
|
1706
|
+
});
|
|
1640
1707
|
}
|
|
1641
1708
|
}
|
|
1642
1709
|
return statements;
|
|
@@ -1648,16 +1715,6 @@ function stepCommitStatements(
|
|
|
1648
1715
|
// content, player prose, or internal identity: paths, revisions, dispositions,
|
|
1649
1716
|
// and the reported error message are what a cause carries.
|
|
1650
1717
|
|
|
1651
|
-
/** The Boss-facing phrase for each closed control-action reason (DR-063 §3). */
|
|
1652
|
-
const CONTROL_REASON_PHRASES: Readonly<Record<string, string>> = {
|
|
1653
|
-
'receipt-complete': 'nothing has changed since it failed',
|
|
1654
|
-
};
|
|
1655
|
-
|
|
1656
|
-
function controlReasonPhrase(reason: string | undefined): string {
|
|
1657
|
-
if (reason === undefined) return 'no reason was published';
|
|
1658
|
-
return CONTROL_REASON_PHRASES[reason] ?? reason;
|
|
1659
|
-
}
|
|
1660
|
-
|
|
1661
1718
|
function causePathList(
|
|
1662
1719
|
paths: readonly string[] | undefined,
|
|
1663
1720
|
truncated: number | undefined,
|
|
@@ -2170,11 +2227,70 @@ function failureControlLine(action: {
|
|
|
2170
2227
|
readonly standing?: PlaybookControlStanding;
|
|
2171
2228
|
readonly reason?: string;
|
|
2172
2229
|
}): string {
|
|
2173
|
-
// DR-063 §3: an action that publishes no standing is `ready`.
|
|
2230
|
+
// DR-063 §3: an action that publishes no standing is `ready`. DR-090 §5:
|
|
2231
|
+
// the standing and its reason read as their closed identifiers.
|
|
2174
2232
|
const standing = action.standing ?? 'ready';
|
|
2175
2233
|
return standing === 'ready'
|
|
2176
|
-
?
|
|
2177
|
-
:
|
|
2234
|
+
? `${MARK_READY} ${action.label}`
|
|
2235
|
+
: `${MARK_NOT_READY} ${action.label} ${MARK_SEP} ${standing}${
|
|
2236
|
+
action.reason === undefined ? '' : ` ${MARK_SEP} ${action.reason}`
|
|
2237
|
+
}`;
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
/**
|
|
2241
|
+
* DR-090 §5: the failure line's marked record of a cause — its code and the
|
|
2242
|
+
* identities its evidence carries, a nested cause after its playbook. No
|
|
2243
|
+
* recorded reason or message reaches it: the settlement facts the closing
|
|
2244
|
+
* reply reads state the failure in words (CAPTAIN-71).
|
|
2245
|
+
*/
|
|
2246
|
+
function markedFailure(
|
|
2247
|
+
cause: PlaybookFailureCause,
|
|
2248
|
+
describeNestedPlaybook: (playbookId: string) => string,
|
|
2249
|
+
): string {
|
|
2250
|
+
const { evidence } = cause;
|
|
2251
|
+
const parts: string[] = [cause.code];
|
|
2252
|
+
if (evidence.roleId !== undefined) parts.push(evidence.roleId);
|
|
2253
|
+
if (evidence.playbookId !== undefined) {
|
|
2254
|
+
parts.push(describeNestedPlaybook(evidence.playbookId));
|
|
2255
|
+
}
|
|
2256
|
+
if (
|
|
2257
|
+
cause.code === 'attribution-ambiguous' &&
|
|
2258
|
+
evidence.required !== undefined &&
|
|
2259
|
+
evidence.observed !== undefined
|
|
2260
|
+
) {
|
|
2261
|
+
parts.push(`${evidence.required} ${MARK_TO} ${evidence.observed}`);
|
|
2262
|
+
}
|
|
2263
|
+
if (evidence.baselineHead !== undefined) {
|
|
2264
|
+
parts.push(
|
|
2265
|
+
evidence.afterHead === undefined
|
|
2266
|
+
? evidence.baselineHead
|
|
2267
|
+
: `${evidence.baselineHead} ${MARK_TO} ${evidence.afterHead}`,
|
|
2268
|
+
);
|
|
2269
|
+
}
|
|
2270
|
+
if (
|
|
2271
|
+
evidence.commitOid !== undefined &&
|
|
2272
|
+
evidence.commitOid !== evidence.afterHead
|
|
2273
|
+
) {
|
|
2274
|
+
parts.push(evidence.commitOid);
|
|
2275
|
+
}
|
|
2276
|
+
const paths = evidence.paths;
|
|
2277
|
+
if (paths !== undefined) {
|
|
2278
|
+
const listed = markedList(
|
|
2279
|
+
[
|
|
2280
|
+
...(paths.uncommitted ?? []),
|
|
2281
|
+
...(paths.altered ?? []),
|
|
2282
|
+
...(paths.lost ?? []),
|
|
2283
|
+
...(paths.changed ?? []),
|
|
2284
|
+
],
|
|
2285
|
+
', ',
|
|
2286
|
+
paths.truncated ?? 0,
|
|
2287
|
+
);
|
|
2288
|
+
if (listed !== '') parts.push(listed);
|
|
2289
|
+
}
|
|
2290
|
+
if (evidence.cause !== undefined) {
|
|
2291
|
+
parts.push(markedFailure(evidence.cause, describeNestedPlaybook));
|
|
2292
|
+
}
|
|
2293
|
+
return parts.join(` ${MARK_SEP} `);
|
|
2178
2294
|
}
|
|
2179
2295
|
|
|
2180
2296
|
/** A report appended to the visible reply (CAPTAIN-69). */
|
|
@@ -2192,7 +2308,11 @@ type PresentationReport =
|
|
|
2192
2308
|
| { readonly kind: 'effects'; readonly lines: readonly string[] };
|
|
2193
2309
|
|
|
2194
2310
|
interface FailureReport {
|
|
2195
|
-
/** The
|
|
2311
|
+
/** The failure line's marked record (DR-090 §5). */
|
|
2312
|
+
readonly mark: string;
|
|
2313
|
+
/** The cause's code, the whole record where the mark's words fail CAPTAIN-9. */
|
|
2314
|
+
readonly code: PlaybookFailureCode;
|
|
2315
|
+
/** The failure statement in words, which the settlement facts carry. */
|
|
2196
2316
|
readonly sentence: string;
|
|
2197
2317
|
/** The reason the line states, which weighs it (CAPTAIN-69). */
|
|
2198
2318
|
readonly reason: string;
|
|
@@ -2319,10 +2439,7 @@ function guardedStatement(
|
|
|
2319
2439
|
};
|
|
2320
2440
|
for (const reason of phrased) {
|
|
2321
2441
|
if (!fails(reason)) continue;
|
|
2322
|
-
guarded =
|
|
2323
|
-
guarded === `Failure: ${reason}`
|
|
2324
|
-
? `Failure: the step failed with ${WITHHELD_REASON}.`
|
|
2325
|
-
: guarded.split(`: ${reason}`).join(` with ${WITHHELD_REASON}.`);
|
|
2442
|
+
guarded = guarded.split(`: ${reason}`).join(` with ${WITHHELD_REASON}.`);
|
|
2326
2443
|
}
|
|
2327
2444
|
for (const subject of shellWords.subjects) {
|
|
2328
2445
|
if (guarded.startsWith(`${subject} `) && fails(subject)) {
|
|
@@ -2353,8 +2470,11 @@ function assembleStatements(
|
|
|
2353
2470
|
guard: (text: string) => string = (text) => text,
|
|
2354
2471
|
): readonly (string | undefined)[] {
|
|
2355
2472
|
const rendered = [...new Set(reasons)];
|
|
2473
|
+
// DR-090 §5: the marked failure line is no sentence and gains no mark.
|
|
2474
|
+
const finish = (kind: string, text: string): string =>
|
|
2475
|
+
kind === 'failure' ? text.trim() : oneTerminalMark(text);
|
|
2356
2476
|
const weighed = statements.map(({ text, kind = 'fact', reason }) => {
|
|
2357
|
-
const statement =
|
|
2477
|
+
const statement = finish(kind, text);
|
|
2358
2478
|
if (reason !== undefined) return { text: statement, kind, own: [reason] };
|
|
2359
2479
|
const held = rendered.filter((candidate) =>
|
|
2360
2480
|
alreadyStates(statement, candidate),
|
|
@@ -2378,7 +2498,7 @@ function assembleStatements(
|
|
|
2378
2498
|
return weighed.map(({ text, kind, own }, index) =>
|
|
2379
2499
|
stays[index] &&
|
|
2380
2500
|
(kind !== 'failure' || !carried(own, [...entries, ...facts]))
|
|
2381
|
-
?
|
|
2501
|
+
? finish(kind, guard(text))
|
|
2382
2502
|
: undefined,
|
|
2383
2503
|
);
|
|
2384
2504
|
}
|
|
@@ -2431,13 +2551,11 @@ function presentationSuffix(
|
|
|
2431
2551
|
});
|
|
2432
2552
|
const failure = turn.failureReport;
|
|
2433
2553
|
if (failure !== undefined) {
|
|
2434
|
-
|
|
2435
|
-
[
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
].join('\n'),
|
|
2440
|
-
);
|
|
2554
|
+
const lines = [
|
|
2555
|
+
...(assembled.failure === undefined ? [] : [assembled.failure]),
|
|
2556
|
+
...failure.controls,
|
|
2557
|
+
];
|
|
2558
|
+
if (lines.length > 0) parts.push(lines.join('\n'));
|
|
2441
2559
|
}
|
|
2442
2560
|
return parts.length === 0 ? undefined : parts.join('\n\n');
|
|
2443
2561
|
}
|
|
@@ -4939,11 +5057,17 @@ export function createPlaybookCaptainShell(
|
|
|
4939
5057
|
const reportCarriedPreExistingChanges = (turn: ActiveTurn): void => {
|
|
4940
5058
|
if (turn.carriedEffectPrefix !== undefined) return;
|
|
4941
5059
|
let carried: readonly CarriedPreExistingChanges[];
|
|
4942
|
-
let commitStatements: readonly
|
|
5060
|
+
let commitStatements: readonly StepCommitStatement[];
|
|
4943
5061
|
try {
|
|
4944
5062
|
const ledger = assertPlaybookEffectLedger(currentEffectLedger());
|
|
4945
5063
|
carried = carriedPreExistingChanges(ledger, presentedEffectPrefix);
|
|
4946
|
-
|
|
5064
|
+
// DR-090 §4: a failed turn states its one-commit steps in the
|
|
5065
|
+
// commits-before-failure report instead (DR-089 §3), never twice.
|
|
5066
|
+
commitStatements = stepCommitStatements(
|
|
5067
|
+
ledger,
|
|
5068
|
+
presentedEffectPrefix,
|
|
5069
|
+
!leafParkedInFailure(),
|
|
5070
|
+
);
|
|
4947
5071
|
turn.carriedEffectPrefix = ledger.boundaries.length;
|
|
4948
5072
|
} catch {
|
|
4949
5073
|
return;
|
|
@@ -4956,16 +5080,22 @@ export function createPlaybookCaptainShell(
|
|
|
4956
5080
|
joinSettlementFact(turn, fact);
|
|
4957
5081
|
}
|
|
4958
5082
|
}
|
|
4959
|
-
// DR-088 §3: the commits each step took, read from the same
|
|
4960
|
-
|
|
5083
|
+
// DR-088 §3, DR-090 §4: the commits each step took, read from the same
|
|
5084
|
+
// boundaries; a step's one new commit reaches the closing reply's facts
|
|
5085
|
+
// only.
|
|
5086
|
+
const shownStatements = commitStatements.filter(
|
|
5087
|
+
(statement): statement is StepCommitStatement & { readonly shown: string } =>
|
|
5088
|
+
statement.shown !== undefined,
|
|
5089
|
+
);
|
|
5090
|
+
if (shownStatements.length > 0) {
|
|
4961
5091
|
appendMandatoryPresentationSuffix(
|
|
4962
5092
|
turn,
|
|
4963
|
-
|
|
4964
|
-
|
|
5093
|
+
shownStatements.map(({ shown }) => shown).join('\n'),
|
|
5094
|
+
shownStatements.map(({ text }) => text),
|
|
4965
5095
|
);
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
5096
|
+
}
|
|
5097
|
+
for (const { text } of commitStatements) {
|
|
5098
|
+
joinSettlementFact(turn, text);
|
|
4969
5099
|
}
|
|
4970
5100
|
};
|
|
4971
5101
|
|
|
@@ -8057,7 +8187,13 @@ export function createPlaybookCaptainShell(
|
|
|
8057
8187
|
? []
|
|
8058
8188
|
: [
|
|
8059
8189
|
{
|
|
8060
|
-
|
|
8190
|
+
// CAPTAIN-69: a mark whose words CAPTAIN-9 refuses — a path
|
|
8191
|
+
// naming control vocabulary, say — reads its code alone.
|
|
8192
|
+
text: `${MARK_FAILED} ${
|
|
8193
|
+
replyRejection(failure.mark) === undefined
|
|
8194
|
+
? failure.mark
|
|
8195
|
+
: failure.code
|
|
8196
|
+
}`,
|
|
8061
8197
|
kind: 'failure' as const,
|
|
8062
8198
|
reason: failure.reason,
|
|
8063
8199
|
},
|
|
@@ -9098,6 +9234,19 @@ export function createPlaybookCaptainShell(
|
|
|
9098
9234
|
// DR-063 §4: appended once, beside the unresolved-effect and pre-existing
|
|
9099
9235
|
// change reports, whenever the turn settles with the leaf parked in its
|
|
9100
9236
|
// failure state or behind the retained-effect fence.
|
|
9237
|
+
// The condition under which reportLeafFailure states a failure: the leaf
|
|
9238
|
+
// parked in its failure state, or behind the retained-effect fence.
|
|
9239
|
+
const leafParkedInFailure = (): boolean => {
|
|
9240
|
+
if (retainedEffectReconciliation !== undefined) return true;
|
|
9241
|
+
const leaf = leafFrame();
|
|
9242
|
+
if (!leaf || typeof leaf.runtime.describe !== 'function') return false;
|
|
9243
|
+
try {
|
|
9244
|
+
return leaf.runtime.describe().state.stateId === 'failed';
|
|
9245
|
+
} catch {
|
|
9246
|
+
return false;
|
|
9247
|
+
}
|
|
9248
|
+
};
|
|
9249
|
+
|
|
9101
9250
|
const reportLeafFailure = (
|
|
9102
9251
|
turn: ActiveTurn,
|
|
9103
9252
|
unresolvedEffects: readonly PlaybookCaptainUnresolvedEffect[],
|
|
@@ -9138,6 +9287,8 @@ export function createPlaybookCaptainShell(
|
|
|
9138
9287
|
: statement.restate(failure);
|
|
9139
9288
|
const speak = statement?.speak ?? ((text: string) => text);
|
|
9140
9289
|
turn.failureReport = {
|
|
9290
|
+
mark: markedFailure(cause, describeNestedPlaybook),
|
|
9291
|
+
code: cause.code,
|
|
9141
9292
|
...failureLine(
|
|
9142
9293
|
noteReason(
|
|
9143
9294
|
failureReason(failure, describeNestedPlaybook, speakableWords),
|
package/slc/link.md
CHANGED
|
@@ -332,6 +332,7 @@ interface PlaybookRepositoryObservation {
|
|
|
332
332
|
readonly worktree: string;
|
|
333
333
|
readonly gitDir: string;
|
|
334
334
|
readonly head: string;
|
|
335
|
+
readonly branch?: string;
|
|
335
336
|
readonly projection: Readonly<Record<string, JsonValue>>;
|
|
336
337
|
readonly projectionDigest: string;
|
|
337
338
|
}
|
|
@@ -521,6 +522,13 @@ interface PlaybookRuntimeSnapshot {
|
|
|
521
522
|
/** Interrupted invocation, captured before its external call (DR-069). */
|
|
522
523
|
recoveryCheckpoint?: PlaybookRecoveryCheckpoint;
|
|
523
524
|
suspendedCall?: PlaybookSuspendedCall;
|
|
525
|
+
/** The run's Boss exchanges so far, in order (DR-090). */
|
|
526
|
+
bossExchanges?: readonly PlaybookBossExchange[];
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
interface PlaybookBossExchange {
|
|
530
|
+
readonly question: string;
|
|
531
|
+
readonly reply: string;
|
|
524
532
|
}
|
|
525
533
|
|
|
526
534
|
type PlaybookRunResult =
|
|
@@ -1084,6 +1092,15 @@ A resumed call omits that question; a fresh call includes it before `Boss reply:
|
|
|
1084
1092
|
Both retain the authored task body exactly once, with two newlines between blocks.
|
|
1085
1093
|
These framework blocks never enter `invoke.input.prompt` or persisted FSM context.
|
|
1086
1094
|
|
|
1095
|
+
The shared runtime hands a composer the call's input without `pendingBossQuestion` and `bossReply` and builds the continuation itself, so a player of any language reads it as structure (DR-090).
|
|
1096
|
+
Before the composer's result it places, in order:
|
|
1097
|
+
|
|
1098
|
+
- `Role: <roleId> · Model: <promptIdentity>`, naming the call's local role and its current binding's prompt identity, or `Role: <roleId>` alone without bindings;
|
|
1099
|
+
- once the run has heard a Boss reply, the run's Boss exchanges so far, each a `? ⟶` line and the verbatim question, then a `Boss ⟶` line and the verbatim reply, whatever the role and whether or not the Source retains them, leaving out the one exchange the call's own continuation carries;
|
|
1100
|
+
- for a call after a Boss reply, the marked verbatim question, only for a fresh conversation, and the marked verbatim reply.
|
|
1101
|
+
|
|
1102
|
+
A run begins at the entry event or a finished machine's restart, and its exchanges travel in the runtime snapshot as `bossExchanges`.
|
|
1103
|
+
|
|
1087
1104
|
The runtime chooses the compact prompt only after selecting a conversation token and carries the complete prompt as optional `PlayerCallOptions.freshPrompt`.
|
|
1088
1105
|
A host starting fresh after a definite pre-execution token rejection uses `freshPrompt`, when supplied, instead of the compact prompt.
|
|
1089
1106
|
Without that option, the given prompt remains complete.
|
|
@@ -1290,9 +1307,11 @@ output is delivered.
|
|
|
1290
1307
|
That type is the value shape the compiled FSM's own validator admits for the
|
|
1291
1308
|
field: the shared engine reads it once at construction by presenting each
|
|
1292
1309
|
governed outcome's accepting arm with sample values of a closed set of shapes,
|
|
1293
|
-
a field whose shape the FSM admits as a string
|
|
1294
|
-
|
|
1295
|
-
|
|
1310
|
+
a field whose shape the FSM admits as a string stays a string, a field whose
|
|
1311
|
+
shape it does not reveal takes any JSON value for the FSM's own guard to
|
|
1312
|
+
decide, and the judge prompt and validation error name every non-string shape
|
|
1313
|
+
([DR-089](../specs/decisions/089-judge-replies-take-the-machines-shapes.md),
|
|
1314
|
+
[DR-091](../specs/decisions/091-an-unrevealed-shape-is-the-machines-to-judge.md)).
|
|
1296
1315
|
The judge prompt shall render each governed outcome's description with its
|
|
1297
1316
|
meaning verbatim and its `Output shall include` clause replaced by that reply
|
|
1298
1317
|
contract — exactly `guard` plus the semantic-owned fields, each keeping its
|
|
@@ -1972,6 +1991,9 @@ At a safe capture point it shall return a JSON-safe
|
|
|
1972
1991
|
`{ kind: 'captain' }` or `{ kind: 'role', roleId }`, empty when the
|
|
1973
1992
|
parked state awaits no reply. This list exists so hosts can surface the
|
|
1974
1993
|
question without parsing status lines or telemetry.
|
|
1994
|
+
- `bossExchanges`: omitted when the run has heard no Boss reply; otherwise
|
|
1995
|
+
every `{ question, reply }` exchange of the run so far, in order, which
|
|
1996
|
+
`restore` and adoption take back.
|
|
1975
1997
|
- `suspendedCall`: omitted when no nested call is pending; otherwise the
|
|
1976
1998
|
shared nested bridge's complete `callId`, source `stateId`, target
|
|
1977
1999
|
`playbookId`, exact handed-off `text`, and `childSessionId`, enriched with
|
package/src/runtime.d.ts
CHANGED
|
@@ -236,6 +236,12 @@ export interface PlaybookRepositoryObservation {
|
|
|
236
236
|
readonly worktree: string;
|
|
237
237
|
readonly gitDir: string;
|
|
238
238
|
readonly head: string;
|
|
239
|
+
/**
|
|
240
|
+
* The branch HEAD names, absent where HEAD is detached or the worktree is
|
|
241
|
+
* not a repository yet (DR-090 §4). Evidence for the Boss only: no
|
|
242
|
+
* comparison of two observations reads it.
|
|
243
|
+
*/
|
|
244
|
+
readonly branch?: string;
|
|
239
245
|
readonly projection: Readonly<Record<string, JsonValue>>;
|
|
240
246
|
readonly projectionDigest: string;
|
|
241
247
|
}
|
|
@@ -367,6 +373,11 @@ export interface PlaybookEffectLedgerCapability {
|
|
|
367
373
|
snapshot(): PlaybookEffectLedger;
|
|
368
374
|
writeAhead(commands: PlaybookEffectLedgerCommandBatch): Promise<PlaybookEffectLedger>;
|
|
369
375
|
}
|
|
376
|
+
/** One Boss exchange of a run: the question as asked and the reply verbatim (DR-090 §1). */
|
|
377
|
+
export interface PlaybookBossExchange {
|
|
378
|
+
readonly question: string;
|
|
379
|
+
readonly reply: string;
|
|
380
|
+
}
|
|
370
381
|
export interface PlaybookRuntimeSnapshot {
|
|
371
382
|
schemaVersion: 4;
|
|
372
383
|
playbookId: string;
|
|
@@ -402,6 +413,8 @@ export interface PlaybookRuntimeSnapshot {
|
|
|
402
413
|
/** Interrupted invocation, captured before its external call (DR-069). */
|
|
403
414
|
recoveryCheckpoint?: PlaybookRecoveryCheckpoint;
|
|
404
415
|
suspendedCall?: PlaybookSuspendedCall;
|
|
416
|
+
/** The run's Boss exchanges so far, in order; absent when there are none (DR-090 §1). */
|
|
417
|
+
bossExchanges?: readonly PlaybookBossExchange[];
|
|
405
418
|
}
|
|
406
419
|
export interface PlaybookRecoveryCheckpoint {
|
|
407
420
|
readonly id?: string;
|
package/src/runtime.ts
CHANGED
|
@@ -590,6 +590,12 @@ export interface PlaybookRepositoryObservation {
|
|
|
590
590
|
readonly worktree: string;
|
|
591
591
|
readonly gitDir: string;
|
|
592
592
|
readonly head: string;
|
|
593
|
+
/**
|
|
594
|
+
* The branch HEAD names, absent where HEAD is detached or the worktree is
|
|
595
|
+
* not a repository yet (DR-090 §4). Evidence for the Boss only: no
|
|
596
|
+
* comparison of two observations reads it.
|
|
597
|
+
*/
|
|
598
|
+
readonly branch?: string;
|
|
593
599
|
readonly projection: Readonly<Record<string, JsonValue>>;
|
|
594
600
|
readonly projectionDigest: string;
|
|
595
601
|
}
|
|
@@ -752,6 +758,12 @@ export interface PlaybookEffectLedgerCapability {
|
|
|
752
758
|
): Promise<PlaybookEffectLedger>;
|
|
753
759
|
}
|
|
754
760
|
|
|
761
|
+
/** One Boss exchange of a run: the question as asked and the reply verbatim (DR-090 §1). */
|
|
762
|
+
export interface PlaybookBossExchange {
|
|
763
|
+
readonly question: string;
|
|
764
|
+
readonly reply: string;
|
|
765
|
+
}
|
|
766
|
+
|
|
755
767
|
// DR-014 §1 / DR-031 §5 / DR-032: JSON-safe capture of a parked or nested-call
|
|
756
768
|
// suspended session. `machine` is the opaque XState persisted snapshot;
|
|
757
769
|
// pending Boss questions and a suspended call are first-class so a
|
|
@@ -789,6 +801,8 @@ export interface PlaybookRuntimeSnapshot {
|
|
|
789
801
|
/** Interrupted invocation, captured before its external call (DR-069). */
|
|
790
802
|
recoveryCheckpoint?: PlaybookRecoveryCheckpoint;
|
|
791
803
|
suspendedCall?: PlaybookSuspendedCall;
|
|
804
|
+
/** The run's Boss exchanges so far, in order; absent when there are none (DR-090 §1). */
|
|
805
|
+
bossExchanges?: readonly PlaybookBossExchange[];
|
|
792
806
|
}
|
|
793
807
|
|
|
794
808
|
export interface PlaybookRecoveryCheckpoint {
|
|
@@ -565,9 +565,10 @@ export declare function stateDescriptionsFromMachine(machine: AnyStateMachine):
|
|
|
565
565
|
*/
|
|
566
566
|
export declare function terminalOutcomesFromMachine(machine: AnyStateMachine, label?: string): ReadonlyMap<string, 'success' | 'failure'>;
|
|
567
567
|
/**
|
|
568
|
-
* DR-089: the
|
|
569
|
-
*
|
|
570
|
-
*
|
|
568
|
+
* DR-089: the JSON value shape the compiled machine admits for each
|
|
569
|
+
* semantic-owned field of each governed outcome, keyed by state id and then
|
|
570
|
+
* by outcome. A field, outcome, or state the probe reveals no shape for is
|
|
571
|
+
* left out (DR-091). Exported so linked machinery of an artifact's own reads
|
|
571
572
|
* the same contract the shared factory enforces.
|
|
572
573
|
*/
|
|
573
574
|
export declare function inferSemanticValueShapes(machine: AnyStateMachine, authority: XStateOutcomeAuthoritySpec): ReadonlyMap<string, PlaybookSemanticValueShapes>;
|